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.
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.
How StoryProof solves it
The StoryProof embed only varies by its widget id. Store or pass the id, keep the div and script in the template, and nothing needs marking safe.
- 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.
How StoryProof solves it
One embed.js fills every StoryProof div on the page, so the include holds only the div and the script sits in a single base template block.
- 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.
How StoryProof solves it
Customers can record video, leave an audio note or write from one collection link, and submissions wait for approval before any widget can show them.
- 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.
How StoryProof solves it
Each widget has its own layout and its own chosen testimonials, so the include stays the same and only the id you pass changes.
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.
Step 2 of 5
Add the script block to base.html
Place an empty storyproof_script block just before the closing body tag. Pages that do not fill it load nothing extra.
Step 3 of 5
Create the include
Save Part 2 under your templates directory. It renders the div from a widget_id variable and nothing else.
Step 4 of 5
Use it from a page template
In a template that starts with extends, call include with widget_id set, and fill the script block once. Remember extends must be the first tag in the file.
Step 5 of 5
Allow the host if you use CSP
With the Django 6.0 CSP middleware switched on, add the StoryProof host to script-src in SECURE_CSP. A report only policy lets you watch for violations before enforcing.
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
- The Django template language, Django documentation. Checked 2026-11-13.
- Built-in template tags and filters: include, Django documentation. Checked 2026-11-13.
- Content Security Policy, Django documentation. Checked 2026-11-13.
Last updated 2026-11-13.