🧩 Django templates
init --frontend templates makes a project where Django renders the site
with its own templates: no frontend/ directory, no frontend service and
no Node. The pages, the blog, the feeds and the contact form are the same as
in the Astro starter, with the same stylesheet.
npx @django-stack/cli init my-site --frontend templates
What it has
- A
websiteapp inbackend/website/, yours to change:views.py: the pages, the blog, categories, tags, the feeds and the contact form.urls.py: their routes, included last inconfig/urls.py.templates/website/:base.html(the layout) and the pages,includes/, andblocks/with one template per block type.static/website/global.css: the stylesheet.forms.py: the contact form.tests/: tests of every page.
- Django serves the whole domain,
SITE_PORTin development (http://localhost:3000) and behind Caddy in production. Templates and CSS reload on save. - The pages read the content with the ORM, in the same process: no API key
and no HTTP round trip.
published(queryset, request)applies the same rules as the content API: drafts and scheduled posts stay hidden, except in preview. - The content API stays open for the read-only key, for a mobile app or a JavaScript frontend later on.
- The package manager option does not apply.
The shared parts are in the django-stack package, django_stack.contrib.site,
so dstack update brings their fixes.
Preview
The admin's Preview button opens /preview with a signed token. The
site keeps it in a cookie until it expires, shows drafts and a banner, and
skips the page cache. /preview/exit leaves the preview.
The page cache
@site_page on a view keeps its page in Django's cache, the database by
default, for SITE_CACHE_TTL seconds (300). A save or a delete of content,
in the admin or anywhere else, empties it, so a change shows on the next
visit. The cache is skipped in preview, with DJANGO_DEBUG=1, and for pages
with a form. With --cache off each process has its own cache, so a change
can take up to SITE_CACHE_TTL to show everywhere.
Blocks, images and SEO
{% load stack %}
{% blocks page.body %}
{% image post.cover size="card" sizes="(min-width: 40rem) 22rem, 100vw" %}
{% seo post site type="article" %}
{% blocks %}renders each block withwebsite/templates/website/blocks/<type>.html, which gets the block's fields asvalue. To add a block type, add its template; a type without one renders nothing.{% image %}renders an<img>of a media library item with asrcsetof its image sizes, its alt text and its focal point.{% seo %}renders the title, the description, the canonical URL and the Open Graph tags; empty fields fall back to the site settings.{{ item|link_href }}is the URL of a menu link;is_currentmarks the current page in the menu.
Security
@site_page sets a Content-Security-Policy: scripts and styles only from
the site, no inline scripts, images from the site and the media origin
(S3 or a CDN). Put scripts and styles in website/static/website/. The
contact page also allows what the captcha widget needs.
The contact form posts to its own page, with Django's CSRF protection, the
captcha (CaptchaField) and the API's rate limit per IP
address (PUBLIC_WRITE_THROTTLE_RATE). Submissions appear in the admin and
notify the editors.
Redirects and analytics
The redirects editors add in the admin answer paths that do not exist, with
RedirectMiddleware. Page analytics works as with the
other frontends: Umami's tracker loads from /_a/, which Caddy serves in
production and Django in development.