Rendering Template Fragments with django-template-partials

This post looks at the django-template-partials library, a package developed by Carlton Gibson that allows you to render HTML template fragments in response to requests in Django.

This can be paired with hypermedia-driven libraries such as HTMX in order to create engaging applications that avoid extensive JavaScript dependencies!

With HTMX, we often have to render HTML partials that are "subsets" of a larger page, in response to user actions.

Rather than breaking every piece of HTML content into its own dedicated file, and returning that with the render() method in response to HTMX requests, we can instead utilize the concept of Template Fragments in Django.

We’ve seen how to do that with django-render-block, but there’s a newer library by Carlton Gibson that we can now use, that is slightly more idiomatic in how it allows you to return partials. Let’s dive in.

The associated video for this post can be found below:


Objectives

In this post, we will:

  • Install and use django-template-partials
  • Understand how to use django-template-partials to return an HTML fragment that we define in our Django templates
  • See a contextual example with HTMX

Installing django-template-partials

Let's start by installing the library in a Python environment. Of course, you will also need Django install to follow along with this video, and we'll also install Django Extensions.

Run the following command:

pip install django django-template-partials django-extensions

Once installed, we can use django-template-partials within a Django project.

There is starter code available here with a small Django app set up - clone this repository if you'd like to follow along and build an example with HTMX!

To start with, let's go the Django Admin and add some films - I have added 5, but you can add as many as you like for the tutorial!

With that, we can start developing the application.

Creating HTML Template with Create Film Form

We will work in the index.html template to build a small page that contains a form allowing new films to be added to the database. This will directly link up to the Film model in the starter code.

Within forms.py, we have a simple ModelForm that allows us to create a new film and add it to the database.

from django import forms
from core.models import Film

class FilmForm(forms.ModelForm):
    class Meta:
        model = Film 
        fields = ('name', 'director')

Let's start by adding this form to our template context within the Django view. We'll also add all the films in the database to our context, too.

In views.py:

from django.shortcuts import render
from core.forms import FilmForm
from core.models import Film

def index(request):
    context = {'form': FilmForm(), 'films': Film.objects.all()}
    return render(request, 'index.html', context)

Now, within the index.html template, we'll add two sections on the page - one for our form, one for the list. We'll use Bootstrap to do that, so get the CSS from here and add it to base.html

Once Bootstrap is added, let's amend the index.html template:

<div class="row">
    <div class="col-12 col-sm-6">
        <h2>Add a Film</h2>
        <form>
            {{ form.as_div }}

            <button type="submit" class="btn btn-primary mt-2">Submit</button>
        </form>
    </div>

    <div class="col-12 col-sm-6">
        <h2>Film List</h2>

        <ul class="list-group" id="film-list">
            {% for film in films %}

                <li class="list-group-item">
                    {{ film.name }} - by {{ film.director }}
                </li>
            
            {% endfor %}
        </ul>
    </div>
</div>

This displays a very simple, unstyled form on the left, which we're now going to augment with some HTMX attributes!

Submitting Form with HTMX

Let's start by adding HTMX to the base template via this CDN link here.

Once added, we can add the following attributes to our form element in index.html:

<form hx-post="{% url 'index' %}" hx-target="#film-list" hx-swap="beforeend">

On submission, we'll send a POST request to the URL with name index, and we specify that the returned HTML from that URL should be swapped into the Film List section, as the last element.

In order to send a POST request, we need to also add the CSRF Token to the request. We can do this with a handy piece of HTML here, that we add to the body tag:

<body hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}'>

Credit to the django-htmx docs here for this tip!

Now, let's try submitting the form and see what happens. Nothing!

Nothing happens because our Django view is not set up to handle a POST request. We'll do that now, and this is where we'll start using django-template-partials.

Let's add the following code to the View:

def index(request):
    if request.method == 'POST':
        form = FilmForm(request.POST)
        if form.is_valid():
            form.save()


    context = {'form': FilmForm(), 'films': Film.objects.all()}
    return render(request, 'index.html', context)

This will save the submitted data to the database, and return the entire index.html template as a response.

Let's see if this works...

You should see that the submission is successful, but the entire page being returned swaps all this content into the target element.

We need to return a subset of the page - only the list element that represents the newly added film - to HTMX on the frontend.

To do this, we could create a NEW partial template with only the <li> element and the new film's details. This seems like overkill though, since we're returning such as simple piece of HTML - why create a new file for this?

Let's see how to do it with template fragments, and django-template-partials.

Firstly, we have installed the package at the beginning, but we need to add it to the INSTALLED_APPS setting:

INSTALLED_APPS = [
    "template_partials",
    ...,
]

Also within settings.py, we need to add the Template Loader settings for Django to work with this library. We can get these from the Github page here, and copy them in, as below:

# Install app and configure loader.
default_loaders = [
    "django.template.loaders.filesystem.Loader",
    "django.template.loaders.app_directories.Loader",
]
cached_loaders = [("django.template.loaders.cached.Loader", default_loaders)]
partial_loaders = [("template_partials.loader.Loader", cached_loaders)]
TEMPLATES = [
    {
        "BACKEND": "django.template.backends.django.DjangoTemplates",
        "DIRS": [],
        # Comment this out when manually defining loaders.
        # "APP_DIRS": True,
        "OPTIONS": {
            "context_processors": [
                "django.template.context_processors.debug",
                "django.template.context_processors.request",
                "django.contrib.auth.context_processors.auth",
                "django.contrib.messages.context_processors.messages",
            ],
            "debug": True,
            # TODO: Add wrap_loaded function to the called from an AppConfig.ready().
            "loaders": partial_loaders,
        },
    },
]

Now, we can run the Django development server again.

Creating the Partial

What we need to return is just the <li> element with the new film's details. So we can create a partial for this purpose within the index.html template.

What we do here is create a partial using the {% startpartial %} template tag that django-template-partials adds, and then reference that tag where we need it in the template.

{% extends 'base.html' %}
{% load partials %}

{% startpartial film-item %}
    <li class="list-group-item">
        {{ film.name }} - by {{ film.director }}
    </li>
{% endpartial %}

...
<!-- Reference in <ul> tag -->

{% for film in films %}

    {% partial film-item %}

{% endfor %}

So we create the partial that encapsulates our HTML, and then reference that in the <ul> tag.

Now, when we submit a POST request, we want to return that partial content, instead of the entire page. We can do that with the following syntax, using the # to separate the parent template with the name of the partial.

def index(request):
    if request.method == 'POST':
        form = FilmForm(request.POST)
        if form.is_valid():
            film = form.save()
            context = {'film': film}
            return render(request, 'index.html#film-item', context)

Note that we also provide context to the partial, which is required to fill in the details of the Film.

When you test this out, you should find that it works! We're only rendering the single list element and appending it to the existing list, using django-template-partials.

Let's finish by showing how to clear the existing form fields. We'll use raw JavaScript for this

Add the following event handler:

<script>
    let form = document.querySelector('form')
    form.addEventListener('htmx:afterRequest', () => {
        form.reset()
    });
</script>

The form should now clear after submission.

Summary

This post has summarized django-template-partials, a library that allows you to extract partials (or fragments) of HTML code into their own blocks, and then return those fragments from your Django views.

If you enjoyed this post, please subscribe to our YouTube channel and follow us on Twitter to keep up with our new content!

Please also consider buying us a coffee, to encourage us to create more posts and videos!

;