.. _custom_templates:
###############################
How to create custom templates
###############################
djangocms-stories ships with minimal, unstyled templates that render every field but leave
visual design to you. This guide explains how to override individual templates, create
complete template sets, and build plugin-specific layouts.
How template loading works
===========================
All story templates extend ``djangocms_stories/base.html``, which in turn extends your site's
``base.html`` and pulls content into a ``content`` block. The hierarchy looks like this:
.. code-block:: text
your_site/base.html
└── djangocms_stories/base.html
├── djangocms_stories/post_list.html
├── djangocms_stories/post_detail.html
├── djangocms_stories/category_list.html
└── ...
Because Django's template loader checks your project's ``templates/`` directory before the
app's built-in templates, you can override any single file by placing a file with the same
path in your project. There is no need to copy all templates — override only what you want to
change.
Overriding the base template
==============================
If your site's ``base.html`` doesn't define a ``content`` block, or you want a different
wrapper around story pages, create your own base template:
**templates/djangocms_stories/base.html:**
.. code-block:: html+django
{% extends "base.html" %}
{% load static %}
{% block content %}
{% block content_blog %}{% endblock %}
{% endblock %}
Every other story template extends this file, so changing it once affects all views.
Complete template sets
========================
Sometimes a single override isn't enough — you want an entirely different look for a section
of the site. djangocms-stories supports this through **template prefixes**: a directory name
that the app prepends to every template path.
To set one up:
1. Copy the default templates into a new directory:
.. code-block:: bash
cp -a djangocms_stories/templates/djangocms_stories/* \
your_project/templates/my_stories/
2. Open the ``StoriesConfig`` admin and enter ``my_stories`` in the **Template prefix** field.
3. Edit the copied templates to your liking.
From now on, that configuration will load ``my_stories/post_list.html`` instead of
``djangocms_stories/post_list.html``. If a template is missing from the custom set, the
default is used as a fallback, so you only need to include files you've actually changed.
This is especially useful with :doc:`multiple-configurations`: the tech blog can use a
card-based grid while the company news uses a traditional list, each driven by its own
template prefix.
Plugin template folders
========================
Plugins like "Latest Blog Articles" or "Categories" also support multiple template variants.
By defining ``STORIES_PLUGIN_TEMPLATE_FOLDERS`` in your settings, you give editors a choice of
layout per plugin instance:
.. code-block:: python
from django.utils.translation import gettext_lazy as _
STORIES_PLUGIN_TEMPLATE_FOLDERS = (
('plugins', _('Default')),
('timeline', _('Timeline')),
('cards', _('Card grid')),
)
Each entry names a sub-folder inside the current template prefix. When an editor adds a
"Latest Blog Articles" plugin, they can pick "Timeline" from a dropdown, and the plugin will
render using ``my_stories/timeline/latest_entries.html`` instead of
``my_stories/plugins/latest_entries.html``.
You must provide **all** plugin templates for each folder you define — the app won't mix
templates from different folders within a single plugin instance.
Template resolution order
==========================
When djangocms-stories looks for a template, it checks these locations in order:
1. ``your_project/templates//post_list.html`` — project templates (highest priority)
2. ``djangocms_stories/templates//post_list.html`` — app templates with prefix
3. ``djangocms_stories/templates/djangocms_stories/post_list.html`` — default app templates
This means you can start with the defaults, override one or two files in your project, and
only create a full set when you truly need it.
Tips
====
- **Start by copying.** Don't write templates from scratch — copy a default, then modify it.
This avoids missing context variables or block names.
- **Use ``post_content`` in templates.** The detail and list views pass ``PostContent`` objects
(translated content). Access language-independent data like the author or publication date
through ``post_content.post``.
- **Choose between placeholders and rich text.** If ``use_placeholder`` is enabled in the
configuration, use ``{% placeholder "content" %}`` in the detail template. Otherwise, render
the ``post_text`` field with ``{% render_model post_content "post_text" %}``.