Skip to main content

Django template tag for dynamic template inclusion

Project description

django-render-fragment

PyPI version License Python versions

Provides a Django template tag, render_fragment, that renders template fragments with full support for {% extends %}. It lets you build reusable HTML components — cards, buttons, badges, form fields, list items — on top of shared base templates, the same way regular Django pages do.

Perfect for:

  • Reusable UI components (cards, buttons, badges, alerts)
  • Form-field wrappers built on a shared base layout
  • List or grid item templates rendered inside a loop
  • Email and document partials that share a common skeleton
  • htmx / hotwire fragments that compose with a base template

Why Use This?

Django's built-in {% include %} tag does not support {% extends %} inside the included template, which makes it awkward to build component libraries where each component inherits from a shared base layout.

render_fragment renders the target template through render_to_string, so {% extends %} works exactly the same as it does for any top-level template. You get real HTML components with all of Django's template-inheritance machinery available.

A base layout:

{# components/_base_card.html #}
<div class="card">
  <div class="card-header">{% block card_title %}{% endblock %}</div>
  <div class="card-body">{% block card_body %}{% endblock %}</div>
</div>

A component that extends it:

{# components/card.html #}
{% extends "components/_base_card.html" %}

{% block card_title %}{{ title }}{% endblock %}

{% block card_body %}{{ body }}{% endblock %}

Used from any page:

{% load render_fragment_tags %}

{% render_fragment "components/card.html" with title="Welcome" body=intro only %}

Design Philosophy

  • Minimal — one tag, no surprises
  • No external dependencies — pure Django
  • Built on render_to_string so {% extends %} just works
  • Explicit isolation via only, matching {% include %} conventions
  • Safe and predictable behavior
  • Suitable for reusable Django apps

Installation

pip install django-render-fragment

Setup

Add it to your Django project:

INSTALLED_APPS = [
    ...
    "render_fragment",
]

Usage

Load the tag library in any template that needs it:

{% load render_fragment_tags %}

Full syntax

{% render_fragment template_name [with k=v ...] [only] [as varname] %}
Part Optional Description
template_name required Literal string or a context variable resolving to a template path.
with k=v ... optional Keyword arguments layered on top of the (inherited) context.
only optional Render in isolation: ignore parent context and request.
as varname optional Store the rendered output in varname instead of writing in place.

By default the fragment inherits the full parent context plus the current request, so context processors and tags such as {% csrf_token %} keep working inside the fragment. The only modifier turns the fragment into a pure function of its declared inputs.

Rendering in place

{% render_fragment "components/greeting.html" with name=user.first_name %}

Capturing into a variable

{% render_fragment "components/button.html" with label="Save" variant="primary" as save_btn %}

<form method="post">
    {% csrf_token %}
    ...
    {{ save_btn }}
</form>

Using {% extends %} inside the fragment

The fragment is rendered through render_to_string, so it can extend any base template:

{# templates/components/_base_card.html #}
<article class="card">
    <header>{% block card_title %}{% endblock %}</header>
    <section>{% block card_body %}{% endblock %}</section>
</article>
{# templates/components/product_card.html #}
{% extends "components/_base_card.html" %}

{% block card_title %}{{ product.name }}{% endblock %}

{% block card_body %}{{ product.summary }}{% endblock %}
{% for product in products %}
    {% render_fragment "components/product_card.html" with product=product only %}
{% endfor %}

Isolating components with only

Without only, the fragment sees every variable from the surrounding template. That is convenient for ad-hoc partials but risky for a component library: a card might silently start depending on whichever variables happen to be in scope.

only makes the component a pure function of its declared inputs:

{% render_fragment "components/card.html" with title="Hi" body="There" only %}

Inside the fragment only title and body are visible. Neither the parent context nor request is forwarded — pass them explicitly when needed:

{% render_fragment "components/comment_form.html" with post=post request=request only %}

This is the recommended default for any template you intend to reuse from multiple call sites.

Multiline support

Set RENDER_FRAGMENT_MULTILINE_TAGS = True in your Django settings to support multiline tags like:

{% render_fragment 
  "components/comment_form.html" with 
  post=post 
  request=request 
  only 
%}

Note that by setting this, you will be able to use multiple lines for other template tags too.

Caching fragments

Because only makes a fragment depend on exactly the values you pass in, it composes very cleanly with Django's built-in {% cache %} tag.

Caching a self-contained component

{% load cache render_fragment_tags %}

{% cache 600 product_card product.id product.updated_at %}
    {% render_fragment "components/product_card.html" with product=product only %}
{% endcache %}

The vary_on values (product.id, product.updated_at) form the cache key, so the cached HTML is invalidated automatically when the product changes. Because the fragment uses only, you can be confident that nothing outside product affects its output.

Caching per request.user

For fragments that render differently per user (a "save / unsave" button, a personalised greeting, a permission-gated panel), include request.user.id in vary_on:

{% load cache render_fragment_tags %}

{% cache 300 save_button request.user.id product.id %}
    {% render_fragment "components/save_button.html"
         with product=product user=request.user only %}
{% endcache %}

For anonymous users request.user.id resolves to None, which is a valid and stable cache-key component.

Caching per language

{% load cache i18n render_fragment_tags %}

{% get_current_language as LANGUAGE_CODE %}
{% cache 3600 hero_banner LANGUAGE_CODE %}
    {% render_fragment "components/hero.html" only %}
{% endcache %}

Tips

  • Always pair {% cache %} with only for reusable components, so the cache key fully captures the fragment's inputs.
  • Use request.user.id (not request.user) in vary_on — model instances get stringified into cache keys, which can be expensive or unstable.
  • Use a dedicated cache backend (CACHES['render_fragment']) and {% cache ... using="render_fragment" %} when you want a different eviction policy from the default cache.

Performance

render_fragment is more expensive than {% include %} because every call dispatches through render_to_string, which builds a fresh context and runs Django's {% extends %} machinery for the fragment. That extra work is what makes {% extends %}-based components possible in the first place.

The repository ships a benchmark script, benchmark_performance.py, that renders the same final HTML with both approaches:

  • a document calling {% render_fragment %} 25 times against a child template that {% extends %} a shared base layout,
  • a document calling {% include %} 25 times against an equivalent self-contained template.

A representative run (Django 6.0, Python 3.14, CPython on a developer laptop, 200 renders × 5 repeats):

{% render_fragment %} + extends   best=2.23 ms   per inclusion ≈ 89 µs
{% include %} (single file)       best=1.50 ms   per inclusion ≈ 60 µs

So render_fragment costs roughly 1.5× a plain {% include %}, or about ~30 µs of extra overhead per inclusion on this hardware. Numbers will vary across machines and Django versions — run the script in your environment to get a figure that matches your workload.

Practical implications

  • For a typical page rendering a handful of components, the overhead is in the tens-of-microseconds-per-component range and is dominated by everything else the view does (DB queries, serialization, middleware).
  • For pages that render the same component many times per request (long lists, dashboards, tables), the overhead adds up linearly. In those cases:
    • Wrap each fragment in {% cache %} with only, as shown above, so repeated requests skip the work entirely.
    • Or, if you do not actually need {% extends %} for that particular component, prefer a plain {% include %} of a self-contained template — render_fragment is meant for components that genuinely benefit from template inheritance.
  • The overhead is per inclusion, not per render, so caching the surrounding view fragment (or using template-fragment caching) is usually more effective than micro-optimising the components themselves.

django-render-fragment in Production

django-render-fragment is used in production at

Testing

The package is tested against multiple Django versions using tox.

To run tests locally:

tox

License

MIT License

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

django_render_fragment-1.2.0.tar.gz (6.6 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

django_render_fragment-1.2.0-py3-none-any.whl (7.8 kB view details)

Uploaded Python 3

File details

Details for the file django_render_fragment-1.2.0.tar.gz.

File metadata

  • Download URL: django_render_fragment-1.2.0.tar.gz
  • Upload date:
  • Size: 6.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.0

File hashes

Hashes for django_render_fragment-1.2.0.tar.gz
Algorithm Hash digest
SHA256 6389850d4e6cd12f1a9856066afb310b7f5abff2506356af1bfa3b996efb8597
MD5 4a6be4df1ef96c4604069c056f594ef7
BLAKE2b-256 0770ad1074da0f6d8f217522e9b3f41eacbab0957af14192b46f944e7c0519f7

See more details on using hashes here.

File details

Details for the file django_render_fragment-1.2.0-py3-none-any.whl.

File metadata

File hashes

Hashes for django_render_fragment-1.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 cc712854552aa74eb9b5ef854c3427aaa980286a6ab9cbe2de39a4cff3fa8529
MD5 9d3d7ca67b067d331f46a005da14391c
BLAKE2b-256 064820633da3a26346a017fc8aa16a9ec0c20304712c5c803e53831bb1b30d91

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page