DEV Community

Cover image for Advanced Django Cheat Sheet
Julien Cortesi
Julien Cortesi

Posted on • Originally published at julienc.net

Advanced Django Cheat Sheet

This article was originally posted to my personal blog

Be aware it's not an exhaustive list.

If you have ideas, correction or recommendation do not hesitate and do so on Github or in the comments section.

Sections

Preparing enviro­nnement

  • Create project folder and navigate to it.
mkdir projec­t_name && cd $_
Enter fullscreen mode Exit fullscreen mode
  • Create Python virtual env.
python -m venv env_name
Enter fullscreen mode Exit fullscreen mode
  • Activate virtual env. (Replace "­bin­" by "­Scr­ipt­s" in Windows).
source env_na­me­\bin­\ac­tivate
Enter fullscreen mode Exit fullscreen mode
  • Deactivate virtual env.
deactivate
Enter fullscreen mode Exit fullscreen mode
  • Install Django.
pip install django~=4.2.2
Enter fullscreen mode Exit fullscreen mode
  • Create requir­ements file.
pip freeze > requir­eme­nts.txt
Enter fullscreen mode Exit fullscreen mode
  • Install all required depend­encies based on your pip freeze command.
pip install -r requir­eme­nts.txt
Enter fullscreen mode Exit fullscreen mode

Creating a Django project

  • Starting a new Django project. A config directory wil be created in your current directory.
django-admin startproject config .
Enter fullscreen mode Exit fullscreen mode
  • Running the server
python manage.py runserver
Enter fullscreen mode Exit fullscreen mode

Creating a Django app

  • Creating an my_app directory and all default files/f­olders inside.
python manage.py startapp my_app
Enter fullscreen mode Exit fullscreen mode
  • Adding the app to settings.py.
INSTAL­LED­_APPS = [
 'my_app',
 ...
Enter fullscreen mode Exit fullscreen mode
  • Adding app urls into the urls.py from project folder.
urlpat­terns = [
 path('admin/', admin.s­it­e.u­rls),
 path('my_app/', include('my_app.urls')),
]
Enter fullscreen mode Exit fullscreen mode

Custom User

Custom User Model

Django documentation: Using a custom user model when starting a project

  1. Create a CustomUser model

    The CustomUser model will live within its own app (for example, 'accounts').

    # accounts/models.py
    from django.contrib.auth.models import AbstractUser
    from django.db import models
    
    class CustomUser(AbstractUser):
        pass
    
  2. Update settings.py

    • Add the 'accounts' app to the INSTALLED_APPS section
    • Add a AUTH_USER_MODEL config:
    AUTH_USER_MODEL = "accounts.CustomUser"
    
- Create migrations file
Enter fullscreen mode Exit fullscreen mode
```
python manage.py makemigrations accounts
```
Enter fullscreen mode Exit fullscreen mode
- Migrate
Enter fullscreen mode Exit fullscreen mode

Custom User Forms

Updating the built-in forms to point to the custom user model instead of User.

# accounts/forms.py
from django.contrib.auth import get_user_model
from django.contrib.auth.forms import UserCreationForm, UserChangeForm

class CustomUserCreationForm(UserCreationForm):
    class Meta:
        model = get_user_model()
        fields = (
            "email",
            "username",
        )


class CustomUserChangeForm(UserChangeForm):
    class Meta:
        model = get_user_model()
        fields = (
            "email",
            "username",
        )
Enter fullscreen mode Exit fullscreen mode

Custom User Admin

Extending the existing UserAdmin into CustomUserAdmin.

# accounts/admin.py
from django.contrib import admin
from django.contrib.auth import get_user_model
from django.contrib.auth.admin import UserAdmin

from .forms import CustomUserCreationForm, CustomUserChangeForm

CustomUser = get_user_model()

class CustomUserAdmin(UserAdmin):
    add_form = CustomUserCreationForm
    form = CustomUserChangeForm
    model = CustomUser
    list_display = [
        "email",
        "username",
        "is_superuser",
    ]
Enter fullscreen mode Exit fullscreen mode

Superuser

python manage.py createsuperuser
Enter fullscreen mode Exit fullscreen mode

Migration

makemigration and migrate

Migrations in the Django Doc

makemigrations: This command generates migration files based on the changes detected in your models.
It compares the current state of your models with the migration files already created and determines the SQL commands required to propagate the changes to your database schema.

python manage.py makemigrations
Enter fullscreen mode Exit fullscreen mode

migrate: This command applies the migration files to your database, executing the SQL commands generated by makemigrations.

python manage.py migrate
Enter fullscreen mode Exit fullscreen mode

This will update your database schema with the changes made to your models.

Fake initial migration

In Django, a "fake initial migration" refers to a concept where you mark a migration as applied without actually executing the database schema changes associated with that migration.
It allows you to synchronize the state of the migrations with the database without performing any database modifications.

python manage.py migrate --fake-initial
Enter fullscreen mode Exit fullscreen mode

It's important to note that faking the initial migration assumes that the existing database schema matches what the initial migration would have created.

Models

Django Documentation: Models

Model Style Ordering

  • Choices
  • Database fields
  • Custom manager attributes
  • Meta class
  • def __str__()
  • def save()
  • def get_absolute_url()
  • Custom methods

Model and Field Names

# my_book_app/models.py
from django.db import models

class Book(models.Model):
    title = models.CharField(max_length=100)
Enter fullscreen mode Exit fullscreen mode

Models represents a single object and should always be Capitalized and singular (Book, not Books).

Fields should all be snake_case, not camelCase.

Choices

If choices are defined for a given model field, define each choice as a tuple of tuples, with an all-uppercase name as a class attribute on the model (source).

# my_book_app/models.py
from django.db import models

class Book(models.Model):
    BOOK_CATEGORY = (
        ("FICTION", "A fiction book"),
        ("NON_FICTION", "A non-fiction book")
    )
    title = models.CharField(max_length=100)
    book_type = models.CharField(
        choices=BOOK_CATEGORY,
        max_lenght=100,
        verbose_name="type of book",
    )
Enter fullscreen mode Exit fullscreen mode

Blank and Null Fields

  • Null: Database-related. Defines if a given database column will accept null values or not.
  • Blank: Validation-related. It will be used during forms validation, when calling form.is_valid().

Do not use null with string-based fields like CharField or TextField as this leads to two possible values for "no data". The Django convention is instead to use the empty string "", not NULL (source.

Meta class

Django Documentation: Meta class

An example, using [indexes](#indexes), ordering, verbose_name and verbose_name_plural.

(Don't order results if you don't need to. There can be performance hit to ordering results.)

# my_book_app/models.py
from django.db import models

class Book(models.Model):
    BOOK_CATEGORY = (
        ("FICTION", "A fiction book"),
        ("NON_FICTION", "A non-fiction book")
    )
    title = models.CharField(max_length=100)
    book_type = models.CharField(
        choices=BOOK_CATEGORY,
        max_lenght=100,
        verbose_name="type of book",
    )

    class Meta:
        indexes = [models.Index(fields=["title"])]
        ordering = ["-title"]
        verbose_name = "book"
        verbose_name_plural = "books"
Enter fullscreen mode Exit fullscreen mode

The str Method

Django Documentation: str()

The str method defines a string representation, a more descriptive name/title, for any of our objects that is displayed in the Django admin site and in the Django shell.

# my_book_app/models.py
from django.db import models

class Book(models.Model):
    BOOK_CATEGORY = (
        ("FICTION", "A fiction book"),
        ("NON_FICTION", "A non-fiction book")
    )
    title = models.CharField(max_length=100)
    book_type = models.CharField(
        choices=BOOK_CATEGORY,
        max_lenght=100,
        verbose_name="type of book",
    )

    class Meta:
        indexes = [models.Index(fields=["title"])]
        ordering = ["-title"]
        verbose_name = "book"
        verbose_name_plural = "books"

    def __str__(self):
        return self.title
Enter fullscreen mode Exit fullscreen mode

The get_absolute_url Method

Django Documentation: get_absolute_url()

The str method method sets a canonical URL for the model.

# my_book_app/models.py
from django.db import models

class Book(models.Model):
    BOOK_CATEGORY = (
        ("FICTION", "A fiction book"),
        ("NON_FICTION", "A non-fiction book")
    )
    title = models.CharField(max_length=100)
    book_type = models.CharField(
        choices=BOOK_CATEGORY,
        max_lenght=100,
        verbose_name="type of book",
    )

    class Meta:
        indexes = [models.Index(fields=["title"])]
        ordering = ["-title"]
        verbose_name = "book"
        verbose_name_plural = "books"

    def __str__(self):
        return self.title

    def get_absolute_url(self):
        return reverse("book_detail", args=[str(self.id)])
Enter fullscreen mode Exit fullscreen mode

Using the get_absolute_url in our templates:

<a href="{{ object.get_absolute_url }}/">{{ object.title }}</a>
Enter fullscreen mode Exit fullscreen mode

UniqueConstraint

Django Documentation: uniqueConstraint

Use UniqueConstraint when you want to enforce uniqueness on a combination of fields or need additional functionality like custom constraint names or conditional constraints

class Booking(models.Model):
    room = models.ForeignKey(Room, on_delete=models.CASCADE)
    date = models.DateField()

    class Meta:
        constraints = [
            models.UniqueConstraint(fields=['room', 'date'], name='unique_booking')
        ]
Enter fullscreen mode Exit fullscreen mode

Models: Further reading

Model Managers

Django Documentation: Managers

Giving a custom name to the default manager

class Author(models.Model):
    ...
    authors = models.Manager() //now the default manager is named as authors
Enter fullscreen mode Exit fullscreen mode

All the operation on the student database table have to be done using the “authors” manager

Author.authors.filter(...)
Enter fullscreen mode Exit fullscreen mode

Creating custom managers

Django Documentation: Custom managers

from django.db import models
from django.db.models.functions import Coalesce


class PollManager(models.Manager):
    def with_counts(self):
        return self.annotate(num_responses=Coalesce(models.Count("response"), 0))


class OpinionPoll(models.Model):
    question = models.CharField(max_length=200)
    objects = PollManager()


class Response(models.Model):
    poll = models.ForeignKey(OpinionPoll, on_delete=models.CASCADE)
    # ...
Enter fullscreen mode Exit fullscreen mode

If you use custom Manager objects, take note that the first Manager Django encounters (in the order in which they’re defined in the model) has a special status. Django interprets the first Manager defined in a class as the “default” Manager, and several parts of Django (including dumpdata) will use that Manager exclusively for that model. As a result, it’s a good idea to be careful in your choice of default manager in order to avoid a situation where overriding get_queryset() results in an inability to retrieve objects you’d like to work with.

Modifying a manager’s initial QuerySet

Django Documenation: Modifying a managers's initial QuerySet

# First, define the Manager subclass.
class DahlBookManager(models.Manager):
    def get_queryset(self):
        return super().get_queryset().filter(author="Roald Dahl")


# Then hook it into the Book model explicitly.
class Book(models.Model):
    title = models.CharField(max_length=100)
    author = models.CharField(max_length=50)

    objects = models.Manager()  # The default manager.
    dahl_objects = DahlBookManager()  # The Dahl-specific manager.
Enter fullscreen mode Exit fullscreen mode

With this sample model, Book.objects.all() will return all books in the database, but Book.dahl_objects.all() will only return the ones written by Roald Dahl.

Model registration in admin

Django doc: ModelAdmin objects

Model registration in Django's admin interface is the process of making your models accessible through the admin site.

from django.contrib import admin
from .models import Author

admin.site.register(Author)
Enter fullscreen mode Exit fullscreen mode
  • Customizing the display of a model
from django.contrib import admin
from .models import Book

class BookAdmin(admin.ModelAdmin):
    list_display = ('title', 'author', 'publication_date')

admin.site.register(Book, BookAdmin)
Enter fullscreen mode Exit fullscreen mode
  • Adding a search field
from django.contrib import admin
from .models import Publisher

class PublisherAdmin(admin.ModelAdmin):
    search_fields = ['name']

admin.site.register(Publisher, PublisherAdmin)
Enter fullscreen mode Exit fullscreen mode
  • Adding filters
from django.contrib import admin
from .models import Category

class CategoryAdmin(admin.ModelAdmin):
    list_filter = ('is_active',)

admin.site.register(Category, CategoryAdmin)
Enter fullscreen mode Exit fullscreen mode
  • Inline formsets
from django.contrib import admin
from .models import Order, OrderItem

class OrderItemInline(admin.TabularInline):
    model = OrderItem
    extra = 1

class OrderAdmin(admin.ModelAdmin):
    inlines = [OrderItemInline]

admin.site.register(Order, OrderAdmin)
Enter fullscreen mode Exit fullscreen mode

Django Signals

  • pre_save: Django Doc: pre_save Using a pre_save signal is required to execute code related to another part of your application prior to saving the object in the database.
from django.db.models.signals import pre_save
from django.dispatch import receiver

@receiver(pre_save, sender=NewOrder)
def validate_order(sender, instance, **kwargs):
    stock_item = Stock.objects.get(id=instance.stock_item.id)

    if instance.quantity > stock_item.quantity:
        raise Exception("Insufficient stock quantity.")
Enter fullscreen mode Exit fullscreen mode
  • post_save: Django Doc: post_save Using a post_save signal is required to execute code related to another part of your application after the object is saved to the database.
from django.db.models.signals import post_save
from django.dispatch import receiver


@receiver(post_save, sender=NewOrder)
def remove_from_inventory(sender, instance, **kwargs):
    stock_item = Inventory.objects.get(id=instance.stock_item.id)
    stock_item.quantity = stock_item.quantity - instance.quantity

    stock_item.save()
Enter fullscreen mode Exit fullscreen mode
  • pre_delete: Django Doc: pre_delete Using a pre_delete signal is necessary to execute code related to another part of your application before the deletion event of an object occurs.
from django.db.models.signals import pre_delete
from django.dispatch import receiver


@receiver(pre_delete, sender=Book)
def pre_delete_book(sender, **kwargs):
    print("You are about to delete a book")
Enter fullscreen mode Exit fullscreen mode
  • post_delete Django Doc: post_delete Using a post_delete signal is necessary to execute code related to another part of your application after the deletion event of an object occurs.
@receiver(post_delete, sender=Book)
def delete_book(sender, **kwargs):
    print("You have just deleted a book")
Enter fullscreen mode Exit fullscreen mode
  • m2m_changed Django Doc: m2m_changed To send a Django signal when a ManyToManyField is changed on a model instance.

Consider this model:

class Student(models.Model):
    # ...


class Course(models.Model):
    students = models.ManyToManyField(Student)
Enter fullscreen mode Exit fullscreen mode

m2m_changed signal:

from django.db.models.signals import m2m_changed

def my_signal_name(sender, instance, **kwargs):
    students = instance.students.all()
    # ...

m2m_changed.connect(my_signal_name, sender=Course.students.through)
Enter fullscreen mode Exit fullscreen mode

Queries and QuerySet

Django Documentation: Making queries

Using Q objects for complex queries

Django Documentation: Q objects

Q objects can be combined using the & (AND) and | (OR) operators

Inventory.objects.filter(
    Q(quantity__lt=10) &
    Q(next_shipping__gt=datetime.datetime.today()+datetime.timedelta(days=10))
)
Enter fullscreen mode Exit fullscreen mode
Inventory.objects.filter(
    Q(name__icontains="robot") |
    Q(title__icontains="vacuum")
Enter fullscreen mode Exit fullscreen mode

Aggregation

Django documenation: Aggregation

In Django, aggregation allows you to perform calculations such as counting, summing, averaging, finding the maximum or minimum value, and more, on a specific field or set of fields in a queryset.

from django.db.models import Sum

total_ratings = Movies.objects.aggregate(ratings_sum=Sum('ratings_count'))
Enter fullscreen mode Exit fullscreen mode

Utilizing Aggregation in Views and Templates

In the view:

from django.shortcuts import render

def example(request):
    data = Movies.objects.aggregate(ratings_sum=Sum('ratings_count'))
    return render(request, 'index.html', {'data': data})
Enter fullscreen mode Exit fullscreen mode

In the template:

<p>Total Ratings: {{ data.ratings_sum }}</p>
Enter fullscreen mode Exit fullscreen mode

Latest element in QuerySet

Django Documentation: latest()

This example returns the latest Entry in the table, according to the pub_date field:

Entry.objects.latest("pub_date")
Enter fullscreen mode Exit fullscreen mode

You can also choose the latest based on several fields.
For example, to select the Entry with the earliest expire_date when two entries have the same pub_date:

Entry.objects.latest("pub_date", "-expire_date")
Enter fullscreen mode Exit fullscreen mode

The negative sign in '-expire_date' means to sort expire_date in descending order. Since latest() gets the last result, the Entry with the earliest expire_date is selected.

Union of QuerySets

union() in the Django Doc

Uses SQL’s UNION operator to combine the results of two or more QuerySets.
For example:

>>> qs1.union(qs2, qs3)
Enter fullscreen mode Exit fullscreen mode

union() return model instances of the type of the first QuerySet even if the arguments are QuerySets of other models.
Passing different models works as long as the SELECT list is the same in all QuerySets (at least the types, the names don’t matter as long as the types are in the same order).
In such cases, you must use the column names from the first QuerySet in QuerySet methods applied to the resulting QuerySet.
For example:

>>> qs1 = Author.objects.values_list("name")
>>> qs2 = Entry.objects.values_list("headline")
>>> qs1.union(qs2).order_by("name")
Enter fullscreen mode Exit fullscreen mode

In addition, only LIMIT, OFFSET, COUNT(*), ORDER BY, and specifying columns (i.e. slicing, count(), exists(), order_by(), and values()/values_list()) are allowed on the resulting QuerySet.

Fixing the N+1 Queries Problem

See select_related and prefetch_related

Performing raw SQL queries

Django Documentation: Performing raw SQL queries

from django.db import models


class Project(models.Model):
    title = models.CharField(max_length=70)
Enter fullscreen mode Exit fullscreen mode
Project.objects.raw('SELECT id, title FROM myapp_project')
Enter fullscreen mode Exit fullscreen mode

Custom sql or raw queries sould be both used with extrement caution since they could open up a vulnerability to SQL injection.

View

Function-based views (FBVs)

Django docucmentation: Writing views

From Django Views - The Right Way: Why TemplateResponse over render ?

The issue with just using render is that you get a plain HttpResponse object back that has no memory that it ever came from a template. Sometimes, however, it is useful to have functions return a value that does remember what it’s “made of” — something that stores the template it is from, and the context. This can be really useful in testing, but also if we want to something outside of our view function (such as decorators or middleware) to check or even change what’s in the response before it finally gets ‘rendered’ and sent to the user.

from django.template.response import TemplateResponse
from django.shortcuts import get_object_or_404, redirect

from .forms import TaskForm, ConfirmForm
from .models import Task


def task_list_view(request):
    return TemplateResponse(request, 'task_list.html', {
        'tasks': Task.objects.all(),
    })


def task_create_view(request):
    if request.method == 'POST':
        form = TaskForm(data=request.POST)
        if form.is_valid():
            task = form.save()
            return redirect('task_detail', pk=task.pk)

    return TemplateResponse(request, 'task_create.html', {
        'form': TaskForm(),
    })


def task_detail_view(request, pk):
    task = get_object_or_404(Task, pk=pk)

    return TemplateResponse(request, 'task_detail.html', {
        'task': task,
    })


def task_update_view(request, pk):
    task = get_object_or_404(Task, pk=pk)

    if request.method == 'POST':
        form = TaskForm(instance=task, data=request.POST)
        if form.is_valid():
            form.save()
            return redirect('task_detail', pk=task.pk)

    return TemplateResponse(request, 'task_edit.html', {
        'task': task,
        'form': TaskForm(instance=task),
    })


def task_delete_view(request, pk):
    task = get_object_or_404(Task, pk=pk)

    if request.method == 'POST':
        form = ConfirmForm(data=request.POST)
        if form.is_valid():
            task.delete()
            return redirect('task_list')

    return TemplateResponse(request, 'task_delete.html', {
        'task': task,
        'form': ConfirmForm(),
    })
Enter fullscreen mode Exit fullscreen mode

Class-based views (CBVs)

Django Documentation : Class-based views

from django.views.generic import ListView, DetailView
from django.views.generic.edit import CreateView, UpdateView, DeleteView
from django.urls import reverse_lazy

from .models import Task


class TaskListView(ListView):
    model = Task
    template_name = "task_list.html"


class BlogDetailView(DetailView):
    model = Task
    template_name = "task_detail.html"


class TaskCreateView(CreateView):
    model = Task
    template_name = "task_create.html"
    fields = ["name", "body", "author"]


class TaskUpdateView(UpdateView):
    model = Task
    template_name = "task_edit.html"
    fields = ["name", "body"]


class TaskDeleteView(DeleteView):
    model = Task
    template_name = "task_delete.html"
    success_url = reverse_lazy("task_list")
Enter fullscreen mode Exit fullscreen mode

Redirect from view:

Django Documentation: redirect()

from django.shortcuts import redirect

# Using the redirect() function by passing an object:
def my_view(request):
    ...
    obj = MyModel.objects.get(...)
    return redirect(obj)

# Using the redirect() function by passing the name of a view
# and optionally some positional or keyword arguments:
def my_view(request):
    ...
    return redirect("some-view-name", foo="bar")

# Using the redirect() function by passing an hardcoded URL:
def my_view(request):
    ...
    return redirect("/some/url/")
    # This also works with full URLs:
    # return redirect("https://example.com/")
Enter fullscreen mode Exit fullscreen mode

By default, redirect() returns a temporary redirect.

All of the above forms accept a permanent argument; if set to True a permanent redirect will be returned:

def my_view(request):
    ...
    obj = MyModel.objects.get(...)
    return redirect(obj, permanent=True)
Enter fullscreen mode Exit fullscreen mode

View: Further reading

Routing

Django Documentation: django.urls functions for use in URLconfs

  • path(): Returns an element for inclusion in urlpatterns
from django.urls import include, path

urlpatterns = [
    path("index/", views.index, name="main-view"),
    path("bio/<username>/", views.bio, name="bio"),
    path("articles/<slug:title>/", views.article, name="article-detail"),
    path("articles/<slug:title>/<int:section>/", views.section, name="article-section"),
    path("blog/", include("blog.urls")),
    ...,
]
Enter fullscreen mode Exit fullscreen mode
  • re_path(): Returns eturns an element for inclusion in urlpatterns. The route argument should be a string or gettext_lazy() that contains a regular expression compatible with Python’s re module.
from django.urls import include, re_path

urlpatterns = [
    re_path(r"^index/$", views.index, name="index"),
    re_path(r"^bio/(?P<username>\w+)/$", views.bio, name="bio"),
    re_path(r"^blog/", include("blog.urls")),
    ...,
]
Enter fullscreen mode Exit fullscreen mode
  • include(): A function that takes a full Python import path to another URLconf module that should be “included” in this place.
from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path("/admin/", admin.site.urls),
    path("books/", include ("books.urls")),
]
Enter fullscreen mode Exit fullscreen mode
  • static(): Helper function to return a URL pattern for serving files in debug mode.
from django.conf import settings
from django.conf.urls.static import static

urlpatterns = [
    # ... the rest of your URLconf goes here ...
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
Enter fullscreen mode Exit fullscreen mode

Authentication

Authentication views and URLs

Django Documentation: Using the views

Add Django site authentication urls (for login, logout, password management):

# config/urls.py
from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path("admin/", admin.site.urls),
    path("accounts/", include("django.contrib.auth.urls")),
]
Enter fullscreen mode Exit fullscreen mode

Urls provided by the auth app:

accounts/login/ [name='login']
accounts/logout/ [name='logout']
accounts/password_change/ [name='password_change']
accounts/password_change/done/ [name='password_change_done']
accounts/password_reset/ [name='password_reset']
accounts/password_reset/done/ [name='password_reset_done']
accounts/reset/<uidb64>/<token>/ [name='password_reset_confirm']
accounts/reset/done/ [name='password_reset_complete']
Enter fullscreen mode Exit fullscreen mode

Updating settings.py with LOGIN_REDIRECT_URL and LOGOUT_REDIRECT_URL

# config/urls.py
    ...
    path("", TemplateView.as_view(template_name="home.html"), name="home"),
    ...

# config/settings.py
LOGIN_REDIRECT_URL = "home"
LOGOUT_REDIRECT_URL = "home"
Enter fullscreen mode Exit fullscreen mode

Signup

To create a sign up page we will need to make our own view and url.

python manage.py startapp accounts
Enter fullscreen mode Exit fullscreen mode
# config/settings.py
INSTALLED_APPS = [
    "django.contrib.admin",
    "django.contrib.auth",
    "django.contrib.contenttypes",
    "django.contrib.sessions",
    "django.contrib.messages",
    "django.contrib.staticfiles",
    "accounts",
]
Enter fullscreen mode Exit fullscreen mode

Then add a project-level url for the accounts app above our included Django auth app.

# django_project/urls.py
from django.contrib import admin
from django.urls import path, include
from django.views.generic.base import TemplateView

urlpatterns = [
    path("admin/", admin.site.urls),
    path("accounts/", include("accounts.urls")),  # new
    path("accounts/", include("django.contrib.auth.urls")),
    path("", TemplateView.as_view(template_name="home.html"), name="home"),
]
Enter fullscreen mode Exit fullscreen mode

The views file:

# accounts/views.py
from django.contrib.auth.forms import UserCreationForm
from django.urls import reverse_lazy
from django.views import generic


class SignUpView(generic.CreateView):
    form_class = UserCreationForm
    success_url = reverse_lazy("login")
    template_name = "registration/signup.html"
Enter fullscreen mode Exit fullscreen mode

From LearnDjango:

We're subclassing the generic class-based view CreateView in our SignUp class. We specify the use of the built-in UserCreationForm and the not-yet-created template at signup.html. And we use reverse_lazy to redirect the user to the login page upon successful registration.

Create a new urls file in the accounts app.

# accounts/urls.py
from django.urls import path

from .views import SignUpView


urlpatterns = [
    path("signup/", SignUpView.as_view(), name="signup"),
]
Enter fullscreen mode Exit fullscreen mode

Then, create a new template templates/registration/signup.html

<!-- templates/registration/signup.html -->
{% extends "base.html" %}

{% block title %}Sign Up{% endblock %}

{% block content %}
  <h2>Sign up</h2>
  <form method="post">
    {% csrf_token %}
    {{ form.as_p }}
    <button type="submit">Sign Up</button>
  </form>
{% endblock %}
Enter fullscreen mode Exit fullscreen mode

Password reset

For development purposes Django let us store emails either in the console or as a file.

  • Console backend:
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
Enter fullscreen mode Exit fullscreen mode
  • File backend:
EMAIL_BACKEND = 'django.core.mail.backends.filebased.EmailBackend'
EMAIL_FILE_PATH = '/tmp/app-messages' # change this to a proper location
Enter fullscreen mode Exit fullscreen mode

For production, see Sending Email

OAuth

The Django OAuth Toolkit package provides OAuth 2.0 support and uses OAuthLib.

Authentication: Further reading

Custom Permissions

Django Doc: Custom permissions

Adding custom permissions to a Django model:

from django.db import models

class Task(models.Model):
    title = models.CharField(max_length=70)
    body = models.TextField()
    is_opened = models.Boolean(default=False)

    class Meta:
        permissions = [
            ("set_task_status", "Can change the status of tasks")
        ]
Enter fullscreen mode Exit fullscreen mode

the following checks if a user may close tasks:

user.has_perm("app.close_task")
Enter fullscreen mode Exit fullscreen mode

You still have to enforce it in the views:

For function-based views, use the permission_required decorator:

from django.contrib.auth.decorators import permission_required

@permission_required("book.view_book")
def book_list_view(request):
    return HttpResponse()
Enter fullscreen mode Exit fullscreen mode

For class-based views, use the PermissionRequiredMixin:

from django.contrib.auth.mixins import PermissionRequiredMixin
from django.views.generic import ListView

from .models import Book

class BookListView(PermissionRequiredMixin, ListView):
    permission_required = "book.view_book"
    template_name = "books/book_list.html"
    model = Book
Enter fullscreen mode Exit fullscreen mode

permission_required can be either a single permission or an iterable of permissions.
If using an iterable, the user must possess all the permissions in order to access the view.

from django.contrib.auth.mixins import PermissionRequiredMixin
from django.views.generic import ListView

from .models import Book

class BookListView(PermissionRequiredMixin, ListView):
    permission_required = ("book.view_book", "book.add_book")
    template_name = "books/book_list.html"
    model = Book
Enter fullscreen mode Exit fullscreen mode

Checking permission in templates:

Using perms:

{% if perms.blog.view_post %}
  {# Your content here #}
{% endif %}
Enter fullscreen mode Exit fullscreen mode

Middleware

Django Documentation: Middleware

Custom Middleware

# my_app/custom_middlware.py

import time

class CustomMiddleware:
    def __init__(self, get_response):
        self.get_response = get_response

    def __call__(self, request):
        start_time = time.time()
        response = self.get_response(request)
        end_time = time.time()

        time_taken = end_time - start_time
        response['Time-Taken'] = str(time_taken)

        return response
Enter fullscreen mode Exit fullscreen mode

Adding the custom middleware to our Django project:

MIDDLEWARE = [
    # ...
    'my_app.middleware.custom_middleware.CustomMiddleware',
    # ...
]
Enter fullscreen mode Exit fullscreen mode

Middleware ordering

While processing request object middlware works from top to bottom and while processing response object middleware works from bottom to top.

Django Documentation: Middleware ordering

Form and Form Validation

Form

Django Documentation: Creating forms from models

ModelForm

from django.forms import ModelForm
from myapp.models import Article

class ArticleForm(ModelForm):
     class Meta:
         model = Article
         fields = '__all__'
Enter fullscreen mode Exit fullscreen mode

The view:

#my_app/views.py

from .forms import ArticleForm

def article_create(request):
    if request.method == 'POST':
        form = ArticleForm(request.POST)
        if form.is_valid():
            article = form.save()
            return redirect('article-detail', article.id))

    else:
        form = ArticleForm()

    return render(request,
            'listings/article_create.html',
            {'form': form})
Enter fullscreen mode Exit fullscreen mode

Selecting the fields to use

  • Set the fields attribute to the special value '__all__' to indicate that all fields in the model should be used.
from django.forms import ModelForm


class ArticleForm(ModelForm):
    class Meta:
        model = Article
        fields = "__all__"
Enter fullscreen mode Exit fullscreen mode
  • Set the exclude attribute of the ModelForm’s inner Meta class to a list of fields to be excluded from the form.
class PartialAuthorForm(ModelForm):
    class Meta:
        model = Article
        exclude = ["headline"]
Enter fullscreen mode Exit fullscreen mode

Form template

<form action="" method="POST">
    {% csrf_token %}
    {{ form }}
    <input type="submit" name="save" value="Save">
    <input type="submit" name="preview" value="Preview">
</form>
Enter fullscreen mode Exit fullscreen mode

Custom form field validators

# my_app/validators.py
from django.core.exceptions import ValidationError


def validate_proton_mail(value):
    """Raise a ValidationError if the value doesn't contains @proton.me.
    """
    if "@proton.me" in value:
        return value
    else:
        raise ValidationError("This field accepts mail id of Proton Mail only")
Enter fullscreen mode Exit fullscreen mode

Adding validate_hello in our form:

# my_app/forms.py

from django import forms

from .models import MyModel
from .validators import validate_proton_mail

class MyModelForm(forms.ModelForm):

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.fields['example_mail'].validators.append(validate_proton_mail)

    class Meta:
        model = MyModel
        fields = '__all__'

Enter fullscreen mode Exit fullscreen mode

clean()

Performing validation on more than one field at a time.

# my_app/forms.py
from django import forms

from .models import MyModel


class MyForm(forms.ModelForm):

    class Meta:
        model = MyModel
        fields = '__all__'

    def clean(self):
        cleaned_data = super().clean()
        slug = cleaned_data.get('slug', '')
        title = cleaned_data.get('title', '')

        # slug and title should be same example
        if slug != title.lower():
            msg = "slug and title should be same"
            raise forms.ValidationError(msg)
        return cleaned_data
Enter fullscreen mode Exit fullscreen mode

clean_field_name()

Performing validation on a specific field.

from django import forms

from .models import Product
from .validators import validate_amazing


class ProductForm(forms.ModelForm):

    class Meta:
        model = Product
        fields = '__all__'


    def clean_quantity(self):
        quantity = self.cleaned_data['quantity']
        if quantity > 100:
            msg = 'Quantity should be less than 100'
            raise forms.ValidationError(msg)
        return quantity
Enter fullscreen mode Exit fullscreen mode

Template

Django Documentation: The Django template language

Template inheritance and inclusion

  • Inheritance
<!-- templates/base.html -->
<!DOCTYPE html>
<html lang="en">
<head>
    <link rel="stylesheet" href="style.css">
    <title>{% block title %}My amazing site{% endblock %}</title>
</head>

<body>
    <div id="sidebar">
        {% block sidebar %}
        <ul>
            <li><a href="/">Home</a></li>
            <li><a href="/blog/">Blog</a></li>
        </ul>
        {% endblock %}
    </div>

    <div id="content">
        {% block content %}{% endblock %}
    </div>
</body>
</html>
Enter fullscreen mode Exit fullscreen mode
<!-- templates/home.html -->
{% extends "base.html" %}

{% block title %}My amazing blog{% endblock %}

{% block content %}
{% for entry in blog_entries %}
    <h2>{{ entry.title }}</h2>
    <p>{{ entry.body }}</p>
{% endfor %}
{% endblock %}
Enter fullscreen mode Exit fullscreen mode
  • Inclusion
{% include 'header.html' %}
Enter fullscreen mode Exit fullscreen mode

Common template tags

  • static
{% load static %}
{% static 'css/main.css' %}
Enter fullscreen mode Exit fullscreen mode
  • url passing positional arguments
{% url 'some-url-name' v1 v2 %}
Enter fullscreen mode Exit fullscreen mode

Django Documentation: url

  • block (Defines a block that can be overridden by child templates)
<div id="content">
        {% block content %}{% endblock %}
    </div>
Enter fullscreen mode Exit fullscreen mode

A child template might look like this:

{% extends "base.html" %}

{% block title %}My amazing blog{% endblock %}

{% block content %}
{% for entry in blog_entries %}
    <h2>{{ entry.title }}</h2>
    <p>{{ entry.body }}</p>
{% endfor %}
{% endblock %}
Enter fullscreen mode Exit fullscreen mode
  • for
<ul>
{% for athlete in athlete_list %}
    <li>{{ athlete.name }}</li>
{% endfor %}
</ul>
Enter fullscreen mode Exit fullscreen mode
  • if, elif, else
{% if athlete_list %}
    Number of athletes: {{ athlete_list|length }}
{% elif athlete_in_locker_room_list %}
    Athletes should be out of the locker room soon!
{% else %}
    No athletes.
{% endif %}
Enter fullscreen mode Exit fullscreen mode
  • now (Outputs the current date and/or time.)
{% now "SHORT_DATETIME_FORMAT" %}
Enter fullscreen mode Exit fullscreen mode
  • Current path
{{ request.path }}
Enter fullscreen mode Exit fullscreen mode
  • Dates and Times
<p>Copyright 2005-{% now "Y" %}</p>
Enter fullscreen mode Exit fullscreen mode
  • Comments
{% comment "Optional note" %}
    <p>Commented out text with {{ create_date|date:"c" }}</p>
{% endcomment %}
Enter fullscreen mode Exit fullscreen mode

Note that single lines of text can be commented out using {# and #}:

{# This is a comment. #}
Enter fullscreen mode Exit fullscreen mode
  • Special Characters
{% autoescape off %}
    {{ content }}
{% endautoescape %}
Enter fullscreen mode Exit fullscreen mode

Sending Email

Django Documentation: Sending email

Quick example:

from django.core.mail import send_mail

send_mail(
    "Subject here",
    "Here is the message.",
    "from@example.com",
    ["to@example.com"],
    fail_silently=False,
)
Enter fullscreen mode Exit fullscreen mode

Email backend

Development:

EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
Enter fullscreen mode Exit fullscreen mode

Production:

config/settings.py
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
EMAIL_HOST = 'smtp.yourserver.com'
EMAIL_USE_TLS = False
EMAIL_PORT = 465
EMAIL_USE_SSL = True
EMAIL_HOST_USER = 'your@djangoapp.com'
EMAIL_HOST_PASSWORD = 'your password'
Enter fullscreen mode Exit fullscreen mode

Performance

django-debug-toolbar

Django Debug Toolbar Documentation

Install:

python -m pip install django-debug-toolbar

settings.py

INSTALLED_APPS = [
    # ...
    "debug_toolbar",
    # ...
]
Enter fullscreen mode Exit fullscreen mode

urls.py

from django.urls import include, path

urlpatterns = [
    # ...
    path("__debug__/", include("debug_toolbar.urls")),
]
Enter fullscreen mode Exit fullscreen mode

select_related and prefetch_related

Django provides two QuerySet methods that can turn the N queries back into one query, solving the performance issue.

These two methods are:

select_related

Django Documentation: select_related

select_related returns a QuerySet that will “follow” foreign-key relationships (either One-to-One
or One-to-Many), selecting additional related-object data when it executes its query.

from django.db import models

class Author(models.Model):
    name = models.CharField(max_length=50)

class Book(models.Model):
    title = models.CharField(max_length=50)
    author = models.ForeignKey(Author, on_delete=models.CASCADE)

# With select_related
authors = Author.objects.all().select_related('book')
for author in authors:
    books = author.book_set.all()
Enter fullscreen mode Exit fullscreen mode

prefetch_related

Django Documentation: prefetch_related

prefetch_related performs a separate lookup for each relationship and “joins” them together
with Python, not SQL.

This allows it to prefetch many-to-many and many-to-one objects, which
cannot be done using select_related

from django.db import models

class Author(models.Model):
    name = models.CharField(max_length=50)

class Book(models.Model):
    title = models.CharField(max_length=50)
    author = models.ForeignKey(Author, on_delete=models.CASCADE)

# With prefetch_related
authors = Author.objects.all().prefetch_related('book')
for author in authors:
    books = author.book_set.all()
Enter fullscreen mode Exit fullscreen mode

Indexes

Django Documentation: Model index reference

If a particular field is consistently utilized, accounting for around 10-25% of all queries, it is a prime candidate
to be indexed.

The downside is that indexes require additional space on a disk so they must be used with care.

from django.db import models

class Author(models.Model):
    name = models.CharField(max_length=50)

    class Meta:
            indexes = [models.Index(fields=["name"])]
Enter fullscreen mode Exit fullscreen mode

Caching

Redis

Django Documentation: Redis

  1. Setting up a Redis server locally or on a remote machine.
  2. Installing redis-py. Installing hiredis-py is also recommended.
  3. Set BACKEND to django.core.cache.backends.redis.RedisCache.
  4. Set LOCATION to the URL pointing to your Redis instance, using the appropriate scheme. See the redis-py docs for details on the available schemes. For example, if Redis is running on localhost (127.0.0.1) port 6379:
CACHES = {
    "default": {
        "BACKEND": "django.core.cache.backends.redis.RedisCache",
        "LOCATION": "redis://127.0.0.1:6379",
    }
}
Enter fullscreen mode Exit fullscreen mode

In order to supply a username and password, add them in the LOCATION along with the URL:

 CACHES = {
    "default": {
        "BACKEND": "django.core.cache.backends.redis.RedisCache",
        "LOCATION": [
            "redis://127.0.0.1:6379",  # leader
            "redis://127.0.0.1:6378",  # read-replica 1
            "redis://127.0.0.1:6377",  # read-replica 2
        ],
    }
}
Enter fullscreen mode Exit fullscreen mode

Database caching

Django Documentation: Database caching

Django can store its cached data in your database.
This works best if you’ve got a fast, well-indexed database server.
In this example, the cache table’s name is my_cache_table:

CACHES = {
    "default": {
        "BACKEND": "django.core.cache.backends.db.DatabaseCache",
        "LOCATION": "my_cache_table",
    }
}
Enter fullscreen mode Exit fullscreen mode

Creating the cache table:

python manage.py createcachetable
Enter fullscreen mode Exit fullscreen mode

per-view cache

Django Documentation: The per-view cache

In Django, the cache_page decorator is used to cache the output of a view function.
It takes a single argument, timeout, which specifies the duration in seconds for which the output should be cached.

from django.views.decorators.cache import cache_page

@cache_page(60 * 15)  # Cache the page for 15 minutes
def my_view(request):
    # View logic ...
Enter fullscreen mode Exit fullscreen mode

Specifying per-view cache in the URLconf:

You can do so by wrapping the view function with cache_page when you refer to it in the URLconf.

from django.views.decorators.cache import cache_page

urlpatterns = [
    path("foo/<int:code>/", cache_page(60 * 15)(my_view)),
]
Enter fullscreen mode Exit fullscreen mode

per-site cache

Django Documentation: per-site cache

# config/settings.py

MIDDLEWARE = [
    'django.middleware.cache.UpdateCacheMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.cache.FetchFromCacheMiddleware',
]
Enter fullscreen mode Exit fullscreen mode

The order of the middleware is important

UpdateCacheMiddleware must come before FetchFromCacheMiddleware.

template fragment caching

Django Documentation: Template fragment caching

{% load cache %}

{% cache 500 book_list %}
  <ul>
    {% for book in books %}
      <li>{{ book.title }}</li>
    {% endfor %}
  </ul>
{% endcache %}
Enter fullscreen mode Exit fullscreen mode

The cache template tag expects a cache timeout in second with the name of the cache fragment book_list

Security

Django Documentation: Deployment checklist

Admin Hardening

Changing the URL path

# config/urls.py

from django.contrib import admin
from django.urls import path

urlpatterns = [
    path("another_admin_path/", admin.site.urls),
]

Enter fullscreen mode Exit fullscreen mode

Cross site request forgery (CSRF) protection

Django's CSRF protection is turned on by default. You should always use the {% csrf_token %} template tag in your forms and use POST for requests that might change or add data to the database.

Enforcing SSL HTTPS

ALLOWED_HOSTS

Django Documentation: ALLOWED_HOSTS
Use ALLOWED_HOSTS to only accept requests from trusted hosts.

Further Reading

Top comments (2)

Collapse
 
rob212 profile image
Rob McBryde

Thanks for sharing, very useful resource.

Collapse
 
kathe profile image
Katarzyna

Perfect! You did a lot of work, thanks!