IAN WALDRON IAN WALDRON

Flatpages In Django Part 1: Basic Implementation

How to quickly set up Django flatpages for building generic, one-off pages like "about us," "privacy policy, "terms and conditions," and so forth.
September 8, 2023

Introduction

For constructing and maintaining generic, one-off pages, such as for your privacy policy or terms and conditions, where building a dedicated app would be overkill, Django has the native app flatpages that is capable of handling content and html alike. Flatpages is a complete solution that requires very little set up to get up and running. In this article, I'll show you how to do just that.

Before we begin, let's look at the model behind the app. The flatpages model, "FlatPage," contains the following fields:


class FlatPage(models.Model):
    url = models.CharField(_("URL"), max_length=100, db_index=True)
    title = models.CharField(_("title"), max_length=200)
    content = models.TextField(_("content"), blank=True)
    enable_comments = models.BooleanField(_("enable comments"), default=False)
    template_name = models.CharField(
        _("template name"),
        max_length=70,
        blank=True,
        help_text=_(
            "Example: “flatpages/contact_page.html”. If this isn’t provided, "
            "the system will use “flatpages/default.html”."
        ),
    )
    registration_required = models.BooleanField(
        _("registration required"),
        help_text=_(
            "If this is checked, only logged-in users will be able to view the page."
        ),
        default=False,
    )
    sites = models.ManyToManyField(Site, verbose_name=_("sites"))

For my purposes, I'm primarily interested in the url, title, and content fields. The url field is particularly interesting. It specifies the location of the page and is also used for lookups by default. If you use the flatpages middleware, which I tend not to, the middleware will pass the url to the default flatpages view function when a 404 response status code is encountered and see if a page with a matching url exists.

The other fields are interesting in that they make the model quite flexible. For example, the "registration_required" field allows for using flatpages behind password protection. In my opinion, that's too broad of an application for the projects I work with. But I appreciate the power that this adds to the model. Given the breadth of use cases Django is involved in, I'm sure there's a great reason for using this field. I just haven't encountered it yet. 

Let's get started with flatpages.

Set Up

Django flatpages can be integrated into your project with just a couple quick steps. For the following demonstration, I'll be using Django version 4.2.

Step 1: Add flatpages to installed apps.

Within your project settings, add the following to INSTALLED_APPS:


INSTALLED_APPS = [
    ...
    'django.contrib.sites'
    'django.contrib.flatpages',
]

The flatpages app uses the 'sites' framework as a dependency, so make sure to add it to INSTALLED_APPS first. Furthermore, you'll need to specify the SITE_ID variable for the given settings file. If you're using one site with your project, then add:


SITE_ID = 1

If your project handles multiple sites, make sure to point to the correct site id.

Step 2: Handle URLs

In your root urls.py file, add the following:


from django.urls import path, include
urlpatterns = [
    path("pages/", include("django.contrib.flatpages.urls")),
]

You could instead use the flatpages middleware to handle urls based upon the url field value. However, it's easier in my mind to handle conceptually explicitly defined url patterns so I tend to use the first approach. But for completeness, I'll show next how to use the middleware.

First, remove the url pattern defined above. You don't need it. The middleware handles the url routing based upon the url value stored with the object. However, you now need to include the entire url in the field value. For example, with the first approach a url value of "/about-us/" would route to "/pages/about-us/." When using the middleware, you need to add the "/pages/" component explicitly if that's where you desire the resource to be located.

Next, add the middleware to your project settings:


MIDDLEWARE = [
    ...
    'django.contrib.flatpages.middleware.FlatpageFallbackMiddleware',
]

Just like that, you're ready to handle urls for FlatPage objects.

Step 3: Migrate

Both the sites and flatpages apps have migrations, so be sure to run the following:


python manage.py migrate

Step 4: Build A Page (with Admin)

As mentioned above, flatpages can be used out of the box with the Django admin interface. You don't have to use it if you want to build your own admin interface, which is my approach. But this is the simplest and fastest way to get up and running out of the gate. The admin framework is installed by default. But if you had disabled it from the beginning, make sure to add it back to INSTALLED_APPS and migrate.

Now, simply log in to your admin site located at "/admin/login/" by default and begin building pages.

The final step involves building a template. But first, let's look at what can go wrong using the Admin site.

Step 4(a): Error - Site Matching Query Does Not Exist

If you had disabled admin to begin with, a site object may not have been created by default. An exception will be raised when you try to access the admin site. To fix this manually, open the Django shell:


python manage.py shell
>>> from django.contrib.sites.models import Site
>>> Site.objects.create(name="example.come", domain="example.com")

Additionally, leaving out SITE_ID can cause the exception.

Step 5: Template

This is pretty much the one thing Django doesn't handle for you. Unfortunately, there's really no way it could handle building even a generic template for your flatpages because it wouldn't know what base template you're extending.

The docs instruct you to establish the default template at/as "flatpages/default.html." This wasn't super clear to me what that meant. Django doesn't want you to add the default template to the actual Django flatpages app. But it will check in several places for this resource. A great way to see where those locations are for your specific project would be to attempt to navigate to a flatpages url before adding this file. Then analyzing the stack trace which will show you where Django checked in trying to find the default template.

I generally like to place my default file in the same app as my project settings. There is one difference in the template look up to keep in mind is that the loader doesn't look for "app name/templates/app name/template.html." The second app name is excluded. So instead, its "app name/templates/flatpages/default.html."

Once you create your default.html file, build the html to extend your base template and reference the title and content attributes within your template. With your template built out, you're now ready to go. It's that easy.

Conclusion

Django flatpages provides a simple and efficient method for creating and managing basic pages. Using this approach can be a great way to save time not needing to build components to manage generic pages. With the five basic steps outlined above, you can be up and running in no time. The FlatPage model is a powerful tool that can be used in a wide variety of situations.

Next, I'll explore how I extend the flatpages app to better suite my needs in this article.