Skip to main content

Django installation

Django Template Testimonial Widget Embed

Django template inheritance gives the embed a natural shape: a small include renders the widget div wherever you need it, and a block in base.html carries the script once per page. Keep the markup in templates, not in a model field, or autoescaping will print it as text.

  • An include that takes the widget id with "with"
  • A base.html block so the script appears once
  • SECURE_CSP settings for Django 6.0 projects

Template generator

Templates with your widget id in place

Enter the widget id. Part 1 is a one time change to base.html, Part 2 is the reusable include, Part 3 shows a page using both, and Part 4 is only for projects that send a Content Security Policy.

Shown in StoryProof when you copy a widget’s embed code.

Part 1 · Base template: an empty block for the script

templates/base.html

{# templates/base.html: add one empty block near the end of <body> #}
<body>
  {% block content %}{% endblock %}

  {% block storyproof_script %}{% endblock %}
</body>

Part 2 · The include that renders the div

templates/includes/storyproof_widget.html

{# templates/includes/storyproof_widget.html #}
{# Only the div. The script is added once per page by the block below. #}
<div data-storyproof-widget="{{ widget_id }}"></div>

Part 3 · A page that extends base.html

templates/pages/home.html

{# templates/pages/home.html #}
{% extends "base.html" %}

{% block content %}
  <section>
    <h2>What our clients say</h2>
    {% include "includes/storyproof_widget.html" with widget_id="YOUR_WIDGET_ID" %}
  </section>
{% endblock %}

{% block storyproof_script %}
  <script src="https://app.storyproof.io/embed.js" async></script>
{% endblock %}

Part 4 · Optional: allow the host in SECURE_CSP

settings.py

# settings.py (Django 6.0 or later, built-in CSP middleware)
from django.utils.csp import CSP

SECURE_CSP = {
    "default-src": [CSP.SELF],
    # Keep your current script sources and add the StoryProof host.
    "script-src": [CSP.SELF, "https://app.storyproof.io"],
}

Run the development server and load the page. If the box is empty, open the browser console: a blocked script message points to Part 4, and visible tags on the page point to escaping.

Your widget id is only substituted into the templates shown here, in your own browser.

The problem, solved

Testimonials in a Django project: the usual dead ends

  • Problem 1

    The embed code prints on the page

    Someone pasted the snippet into a TextField so editors could change it, and Django escaped the angle brackets on output, so visitors see raw tags.

  • Problem 2

    Two widgets, two copies of the script

    A page with a grid in the body and a badge near the form ends up loading the same script twice when each include carries it.

  • Problem 3

    Our Testimonial model has no video

    A home made model handles a name and a quote. Video uploads, an approval queue and a request flow are a project of their own.

  • Problem 4

    Every page wants a different look

    The home page needs a wall, the pricing page a compact strip, and hand built templates for each layout keep growing.

In StoryProof

Add it to your Django templates

Step 1 of 5

Prepare one widget per placement

In StoryProof, approve testimonials, then create a widget for each spot, pick its layout and choose what it shows.

Stored snippet versus template include

Snippet in a model field

  • Rendered as escaped text unless someone adds |safe
  • Marking it safe trusts whatever an editor pastes next
  • Each copy carries its own script tag

Id in context, markup in templates

  • The id is escaped like any variable, which is harmless
  • The markup is reviewed in code like the rest of the site
  • The script is written once, in base.html

Before you start

What to keep in mind on Django

  • Template language only

    The recipe uses the Django template language. Projects on Jinja2 or a headless front end need the same two lines in their own syntax.

  • CSP outside Django

    If a reverse proxy, a CDN or a third party package sets the CSP header instead of Django 6.0 middleware, add the StoryProof host there.

  • No Django app to install

    StoryProof is not a pip package and adds nothing to INSTALLED_APPS. The testimonials stay in StoryProof, not in your database.

  • Badge until a higher plan

    A small "Powered by StoryProof" badge shows on widgets until Pro.

  • Steps follow the Django docs, 2026-11-13

    Template and CSP details come from the Django documentation as read on 2026-11-13. The StoryProof team has not yet run this recipe in a live project.

FAQ

Questions people ask

Short answers to what usually comes up next.

Can I keep the widget id in settings or the database?

Yes. Pass it into the template context and hand it to the include. Only the id is stored, so autoescaping does not get in the way.

What if a CMS sits on top of Django?

If its page templates use the Django template language, the include and the base block go in those templates. When editors need to choose the widget, give them a plain text field for the id rather than an HTML field.

Why not put the script inside the include?

You can when a page shows a single widget. Splitting them keeps the script to one copy on pages that include the widget more than once.

Do I need a nonce for the script?

Django nonces are for inline scripts. This is an external script, so allowing its host in script-src is the usual route; check your policy if you have chosen to rely on nonces only.

Still have a question? Ask the StoryProof team or book a demo.

Get your first widget id

Collect testimonials, approve the good ones and pass one id into your Django include.

Sources

  1. The Django template language, Django documentation. Checked 2026-11-13.
  2. Built-in template tags and filters: include, Django documentation. Checked 2026-11-13.
  3. Content Security Policy, Django documentation. Checked 2026-11-13.

Last updated 2026-11-13.