How to add a cookie consent banner to a Django site
In Django, paste the snippet in the head of the base template every page extends (usually templates/base.html), above any block where child templates add scripts. Pass the site key from settings with a small context processor, mark tracking tags in the same template with type="text/plain" and data-cq-category, and list Django's sessionid and csrftoken cookies as strictly necessary.Steps
Where does the snippet go in Django?
Django's template inheritance builds every page from a base "skeleton" template, which child templates name with {% extends "base.html" %} and fill through {% block %} tags. The snippet belongs in the head of that base template, above any block where pages add scripts:
django{# templates/base.html #}<!DOCTYPE html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><script src="https://cookiequiet.com/cq.js" data-site="{{ COOKIEQUIET_SITE }}" data-consent-mode="1" data-auto-block="1" defer></script><title>{% block title %}My site{% endblock %}</title>{% block extra_head %}{% endblock %}</head><body>{% block content %}{% endblock %}</body></html>
Every view that renders a template extending base.html now loads the banner, Wagtail pages included, since Wagtail projects use the same template inheritance.
Pass the key from settings. A context processor adds a variable to every template rendered with a request:
python# settings.pyCOOKIEQUIET_SITE = os.environ.get("COOKIEQUIET_SITE", "")# mysite/context_processors.pyfrom django.conf import settingsdef cookiequiet(request):return {"COOKIEQUIET_SITE": settings.COOKIEQUIET_SITE}
Then add "mysite.context_processors.cookiequiet" to the context_processors list in TEMPLATES. Writing the key directly in the template works too: it is public, it appears in every page.
How do I block my tags until consent?
Paste tracking tags under the snippet and change their opening tag:
html<script type="text/plain" data-cq-category="analytics" src="https://www.googletagmanager.com/gtag/js?id=G-XXXXXXX"></script><script type="text/plain" data-cq-category="analytics">window.dataLayer = window.dataLayer || [];function gtag(){dataLayer.push(arguments);}gtag('js', new Date());gtag('config', 'G-XXXXXXX');</script>
Cookiequiet runs marked tags when the category is granted, and at once on later visits where the stored choice includes it. Tags that a page adds in {% block extra_head %} work the same way when the block sits below the snippet. The Google Analytics 4 guide explains the Consent Mode alternative, and the guide on blocking scripts covers embeds and other tags. Scripts created at runtime by JavaScript are held by the auto-blocker when they come from one of the about 80 trackers it knows.
Django admin. The admin uses its own templates and does not extend your base.html, so it gets no banner. It is used by your staff, not by visitors, and sets only Django's own cookies.
What about Django's own cookies?
Django sets sessionid when the session is used (for example after a login) and csrftoken for cross-site request forgery protection on pages with forms. Both serve a function the visitor asked for, so they are strictly necessary and need no consent in most readings of the ePrivacy rules. List them in the cookie table under necessary; the banner does not block them. The cookie scanner finds them along with any third-party cookies.
How does it work with Django's Content Security Policy?
Django 6.0 added built-in Content Security Policy support: a middleware sends the header, the SECURE_CSP setting holds the policy, and the django.template.context_processors.csp context processor exposes a csp_nonce variable. Add https://cookiequiet.com to script-src and connect-src (the banner fetches its configuration, the geo lookup and the consent records from that origin), and give inline scripts nonce="{{ csp_nonce }}". Marked inline scripts take it too (<script type="text/plain" data-cq-category="analytics" nonce="{{ csp_nonce }}">): Cookiequiet copies the nonce to the script it runs after consent, so the policy lets it through.
How do I add the Cookie preferences link?
html<a href="#cq-preferences" data-cq-show-preferences>Cookie preferences</a>
How do I test it?
- Run the development server, or deploy, and view the page source:
cq.jsis the first script in the head. - Open the site in a private window and click Reject all. The cookies are
cq_consentand, on pages with forms or after a login,csrftokenandsessionid. - Clear the cookies, click Accept all, reload: the marked tags run and
_gaappears. - Check the consent log in the dashboard.
Setup is one tag in one template, with no Django package to install. Cookiequiet is free for one website with geo-targeting; Starter is €3 per website per month with no pageview limit.