Django development tips

Archie To

This article contains tips that I wish I knew when starting out with Django. Following these tips will help your development process with Django a lot faster and make your code way cleaner. This article assumes you already have basic Django knowledge. (Django’s full documentation can be found here.)

Note: All the code examples below assume there is no bug within each code, all dependencies for each code have been set up and each code runs correctly. The purpose of this tutorial is not to teach you Django code, but to show you the available options that Django offers to developers.

Template inheritance and inclusion

Template inheritance

For example, you are building an app that contains a page title, imports Bootstrap and Jquery and has a navigation bar on every page. The code might look like this:

index.html:

<!DOCTYPE html>
<html lang="en">
<head>
    <title>Home page</title>
    <script src="<jquery link>"></script>
    <link href="<bootstrap css link>" rel="stylesheet">
    <script src="<bootstrap javascript link>"></script>
</head>
<body>
    <nav>
        <a href="/">Home</a>
        <a href="/about">About</a>
        <a href="/contact">Contact</a>
    </nav>
    <h1>Home page</h1>
</body>
</html>

about.html:

<!DOCTYPE html>
<html lang="en">
<head>
    <title>About page</title>
    <script src="<jquery link>"></script>
    <link href="<bootstrap css link>" rel="stylesheet">
    <script src="<bootstrap javascript link>"></script>
</head>
<body>
    <nav>
        <a href="/">Home</a>
        <a href="/about">About</a>
        <a href="/contact">Contact</a>
    </nav>
    <h1>About page</h1>
</body>
</html>

As you can see, there is a lot of code repetition in the example above. To prevent this, Django allows developers a way to define a skeleton template, and have other templates inherit this skeleton template. Using the example above, the code can be transformed into the followings:

Create base.html:

<!DOCTYPE html>
<html lang="en">
<head>
    <title>{% block title %}{% endblock %}</title>
    <script src="<jquery link>"></script>
    <link href="<bootstrap css link>" rel="stylesheet">
    <script src="<bootstrap javascript link>"></script>
</head>
<body>
    <nav>
        <a href="/">Home</a>
        <a href="/about">About</a>
    </nav>
    {% block content %}{% endblock %}
</body>
</html>

index.html:

{% extends "base.html" %}
{% block title %}Home page{% endblock %}
{% block content %}
<h1>Home page</h1>
{% endblock %}

about.html:

{% extends "base.html" %}
{% block title %}About page{% endblock %}
{% block content %}
<h1>About page</h1>
{% endblock %}

The resulting index.html and about.html has the exact same contents as the initial ones shown above.

Reference: Django template inheritance

Template inclusion

Django also allows developers to include other HTML files in an HTML file. For example, if you are using a form on two pages: index.html:

<body>
    <h1>Home page</h1>
    <form>
        <input type="text" name="name" placeholder="Your name">
        <input type="number" name="age" placeholder="Your age">
        <input type="submit">
    </form>
</body>

info.html:

<body>
    <h1>Info page</h1>
    <form>
        <input type="text" name="name" placeholder="Your name">
        <input type="number" name="age" placeholder="Your age">
        <input type="submit">
    </form>
</body>

You can write a separate template for the form, and use the {% include %} tag to include the template in both index.html and info.html:

Create form.html:

<form>
    <input type="text" name="name" placeholder="Your name">
    <input type="number" name="age" placeholder="Your age">
    <input type="submit">
</form>

index.html:

<body>
    <h1>Home page</h1>
    {% include 'form.html' %}
</body>

info.html:

<body>
    <h1>Info page</h1>
    {% include 'form.html' %}
</body>

The resulting index.html and info.html has the exact same contents as the initial ones shown above.

Reference: Django template include tag

Write context processor to create global template context variables

For example, you want to create multiple templates that has the same context variable user, you’d write it as follows:

views.py:

def home(request):
    return render(request, 'index.html', {'user': 'user@example.org'})

def about(request):
    return render(request, 'about.html', {'user': 'user@example.org'})

index.html:

<body>
<h1>Home page</h1>
<h2>{{ user }}</h2>
</body>

about.html:

<body>
<h1>About page</h1>
<h2>{{ user }}</h2>
</body>

In the above, we have to define the context variable user twice. This can add up quickly as the number of templates and repeating context variable rises. To avoid this, we can create our own context processor as follows:

settings.py:

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [os.path.join(BASE_DIR, 'templates')],
        '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',
                'app_starter.custom_contexts.get_user',     # We add this entry, all the aboves are default
            ],
        },
    },
]

app_starter/custom_contexts.py:

def get_user(request):
    return {'user': 'user@example.org'}

views.py:

def home(request):
    return render(request, 'index.html')

def about(request):
    return render(request, 'about.html')

Note: The file path has to match how we includes it in settings.py.

With index.html and about.html remaining the same, user will be displayed in both pages.

Reference: Write your own context processor

Django built-in admin page

Django offers developers a built-in admin page. With this admin page, one can create, edit, delete and view model instances with ease for development process. To use the admin page:

Build a Question model in models.py

class Question(models.Model):
    question_text = models.CharField(max_length=200)
    pub_date = models.DateTimeField('date published')

Register Question model in admin.py:

from django.contrib import admin
from .models import Question

admin.site.register(Question)

Visit localhost:<port>/admin, you see this page:

Django admin page

We need to create an account. In your terminal, run:

$ python manage.py createsuperuser
Username: admin
Email address: admin@example.com
Password: **********
Password (again): *********
Superuser created successfully.

Now you can log in with that account you just created. After logging in, you should see:

Django admin dashboard page

After clicking on Questions, you can view all Question instances that you have created. You can create, edit and delete Question instances using this admin dashboard.

Reference: Introducing the Django Admin

Middleware

Middleware is a framework of hooks into Django’s request/response processing. It’s a light, low-level “plugin” system for globally altering Django’s input or output.

In my case, I use middleware to perform actions that are included in most of my views, so I don’t have to rewrite the actions for each view. For example, you want only authenticated users to visit your app. If an user is not authenticated, they should be redirected to the Login page at /login. You might write:

views.py:

def home(request):
    if not request.session.get('user', None):
        return redirect('/login')
    return render(request, 'index.html', {'user': request.session.get('user', None)})

def about(request):
    if not request.session.get('user', None):
        return redirect('/login')
    return render(request, 'about.html', {'user': request.session.get('user', None)})

def contact(request):
    if not request.session.get('user', None):
        return redirect('/login')
    return render(request, 'contact.html', {'user': request.session.get('user', None)})

def login(request):
    request.session['user'] = 'user@example.org'
    return render(request, 'login.html', {})

See how you have to repeat 2 lines of code for checking user authentication status for each view. You can solve this by doing the following:

settings.py:

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    "whitenoise.middleware.WhiteNoiseMiddleware",
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
    'middleware.authenticate_middleware.AuthenticateMiddleware',    # We added this line, all the aboves are default 
]

middleware/authenticate_middleware.py:

from django.shortcuts import redirect

class AuthenticateMiddleware():
    def __init__(self, get_response):
        """This function is required"""
        self.get_response = get_response

    def __call__(self, request):
        """Actions preformed by this middleware are defined here"""
        if request.path != "/login" and not request.session.get('user', None):
            return redirect('/login')
        response = self.get_response(request)
        return response

Note: The file path has to match how we includes it in settings.py.

views.py:

def home(request):
    return render(request, 'index.html', {'user': request.session.get('user', None)})

def about(request):
    return render(request, 'about.html', {'user': request.session.get('user', None)})

def contact(request):
    return render(request, 'contact.html', {'user': request.session.get('user', None)})

def login(request):
    request.session['user'] = 'user@example.org'
    return render(request, 'login.html', {})

Reference: Write your own middleware

Custom Django commands

Django provides a way for you to write your own python manage.py command. This can come in really handy in the development process. For example, you want to list the names all of the users that have been created for your Django app, you can write a command python manage.py listusers as follows:

Assuming you have an application called myapp, you need to created a file myapp/management/commands/listusers.py. The file path needs to be exactly like that.

listusers.py:

from django.core.management.base import BaseCommand, CommandError
from django.contrib.auth.models import User

class Command(BaseCommand):
    help = 'List the names of all created users, separated by a comma'

    # def add_arguments(self, parser):
        """ Define arguments that can be received by the command.
        We are not using this right now but it's useful to put it here.
        """
        # parser.add_argument('user_ids', nargs='+', type=int)

    def handle(self, *args, **options):
        """ Define what the command will do """
        self.stdout.write(", ".join(list(User.objects.all().value_lists('username', flat=True))))

You can test this out by running python manage.py shell. When a Python shell is opened, run:

>>> from django.contrib.auth.models import User
>>> User.objects.create(username="user@example.org", password="testinguser")
>>> User.objects.create(username="admin@example.org", password="testingadmin")
>>> exit()

Back to your terminal shell, run:

$ python manage.py listusers
user@example.org, admin@example.org

Reference: Create custom django-admin commands