Packages and Tools

This section provides an overview of the primary packages and tools, along with some of the design choices incorporated into a project generated with Falco.

Let’s start with the straightforward components, about which there isn’t much to elaborate:

  • environs: Used for configuring settings via environment variables.

  • django-allauth: Handles login and signup processes.

  • Amazon SES and Anymail: Amazon SES is used for production email, facilitated by Anymail.

  • Docker and s6-overlay: Docker is configured for production, with s6-overlay enabling concurrent operation of django and django-q within a single container.

  • Sentry: Utilized for performance and error monitoring.

  • Whitenoise: Used to serve static files.

  • pre-commit: Integrated by default to identify simple issues before pushing code to remote.

  • django-browser-reload: Automatically reloads your browser on code changes in development.

Login via email instead of username

I completely removed the username field from the User model and replaced it with the email field as the user unique identifier. The email field is what I configured as the login field using django-allauth. More often then not when I create a new django project I need to use something other than the username field provided by django as the unique identifier of the user, and the username field just becomes an annoyance to deal with. It is also more common nowadays for modern web and mobile applications to rely on a unique identifier such as an email address or phone number instead of a username.

I also removed the first_name and last_name fields that are available by default on the Django User model. I don’t always need them, and when I do, I generally have a separate Profile model to store users’ personal informations, keeping the User model focused on authentication and authorization. My reasoning for this is to avoid asking for unnecessary data (following the principle of YAGNI). A positive consequence of this approach is that having less data on your users/customers increases the chances of being GDPR compliant. You can always add these fields later if needed.

However, if my arguments are not convincing enough or for the particular project you are working you need to have the username field on your User model for login purposes, the required changes are quite simple and can be summarized as follows:

First update the User models to look exactly like in the code below.

users/models.py
from django.contrib.auth.models import AbstractUser

class User(AbstractUser):
    pass

Then delete the forms.py, admin.py and migrations/0001_initial.py files in the users app. With that you should be good to go, if you want something a little more complete to start with you can grab some code from the cookiecutter-django users app.

HTMX and template partials

The project comes set up with django-template-partials and htmx for the times when you need to add some interactivity to your web app. The interactive user interfaces guide goes into more detail on this, but for a brief overview:

  • django-template-partials is used to define reusable fragments of HTML

  • htmx’s job is to make requests to the backend, get a piece of HTML fragment in response, and patch the DOM using it. Basically, htmx allows you to write declarative code to make AJAX (Asynchronous JavaScript And XML) requests.

Let’s look at a quick example:

elements.html
 1{% block main %}
 2<ul id="element-list">
 3   {% for el in elements %}
 4      {% partialdef element-partial inline=True %}
 5         <li>{{ el }}</li>
 6      {% endpartialdef %}
 7   {% endfor %}
 8</ul>
 9
10<form
11hx-post="{% url 'add_element' %}"
12hx-target="#element-list"
13hx-swap="beforeend"
14>
15   <!-- Let's assume some form fields are defined here -->
16   <button type="submit">Submit</button>
17</form>
18
19{% endblock main %}

The htmx attributes (prefixed with hx-) defined above basically say:

when the form is submitted, make an asynchronous JavaScript request to the URL {% url 'add_element' %} and add the content of the response before the end (before the last child) element of the element with the ID element-list .

The complementary Django code on the backend would look something like this:

views.py
1def add_element(request):
2   new_element = add_new_element(request.POST)
3   if request.htmx:
4      return render(request, "myapp/elements.html#element-partial", {"el": new_element})
5   else:
6      redirect("elements_list")

The highlighted line showcases a syntax feature provided by django-template-partials. It enables you to selectively choose the specific HTML fragment from the elements.html file that is enclosed within the partialdef tag with the name element-partial.

The htmx attribute on the request element is provided by django-htmx, which is already configured in the project.

This example illustrates how you can create a button that adds a new element to a list of elements on a page without reloading the entire page. Although this might not seem particularly exciting, the interactive user interfaces guide provides more practical examples that demonstrate the extensive possibilities offered by this approach.

Django-q2

django-q2 is my preferred background task queue system for Django. In most projects, I always utilize either the task queue processing, scheduling, or sometimes both. Regarding scheduling, there is also django-q-registry included, which is a django-q2 extension that helps with easily registering scheduling jobs.

Here is an example of how using both looks:

tasks.py
from django.core.mail import send_mail
from django_q.models import Schedule
from django_q_registry import register_task

@register_task(
    name="Send periodic test email",
    schedule_type=Schedule.MONTHLY,
)
def send_test_email():
    send_mail(
        subject="Test email",
        message="This is a test email.",
        from_email="noreply@example.com",
        recipient_list=["johndoe@example.com"],
    )


def long_running_task(user_id):
    # a simple task meant to be run in background
    ...

It is a good idea to organize any task or scheduling job function in a tasks.py file in the relevant Django application.

Hint

For more details on task queues and scheduling, check out my guide on the topic.

DjangoFastDev

The DjangoFastDev package helps catch small mistakes early in your project. When installed you may occasionally encounter a FastDevVariableDoesNotExist error, this exception is thrown during template rendering by django-fastdev when you try to access a variable that is not defined in the context of the view associated with that template. This is intended to help you avoid typos and small errors that will have you scratching your head for hours, read the project readme to see all the features it provides. If you find the package’s errors to be too frequent or annoying, you can disable it by removing the django-fastdev application entirely or by commenting it out in the settings.py file.

THIRD_PARTY_APPS = [
    ...
    # 'django_fastdev',
]

Dj Notebook

This is a recent addition to the project. It allows you to use your shell_plus in a Jupyter notebook. In the root of the generated project, you will find a file named playground.ipynb which is configured with dj-notebook. As the name suggests, I use this as a playground to play with the Django ORM. Having it saved in a file is particularly useful for storing frequently used queries in text format, eliminating the need to retype them or search through command line history. Before running any additional cells you add, make sure to run the first cell in the notebook to set up Django. It’s important to note that dj-notebook does not automatically detect file changes, so you will need to restart the kernel after making any code modifications. If you need a refresher on Jupyter notebooks, you can refer to this primer.

Marimo

There is a new alternative to Jupyter notebooks, namely, marimo. The main features that I appreciate are:

  • Notebooks are straightforward Python scripts.

  • It has a beautiful UI.

  • It provides a really nice tutorial: pip install marimo && marimo tutorial intro.

Its main advertised feature is having reactive notebooks, but for my use case in my Django project, I don’t really care about that.

If you want to test marimo with your Django project, it’s quite simple. Install it in your project environment and run:

marimo edit notebook.py

Or using hatch:

hatch run marimo edit notebook.py

As with dj-notebook, for your Django code to work, you need some kind of activation mechanism. With dj-notebook, the first cell needs to run the code from dj_notebook import activate; plus = activate(). With marimo, the cell below should do the trick.

import django
import os

os.environ["DJANGO_SETTINGS_MODULE"] = "<your_project>.settings"
django.setup()