Skip to content

Instantly share code, notes, and snippets.

@pvanfas
Last active December 4, 2025 08:24
Show Gist options
  • Select an option

  • Save pvanfas/6da287111dee1b08d325b33c984505a6 to your computer and use it in GitHub Desktop.

Select an option

Save pvanfas/6da287111dee1b08d325b33c984505a6 to your computer and use it in GitHub Desktop.
Django is a high-level Python Web framework that encourages rapid development and clean, pragmatic design. Built by experienced developers, it takes care of much of the hassle of Web development, so you can focus on writing your app without needing to reinvent the wheel. It’s free and open source.

Django Snippets & User Guide

This repo collects a minimal-yet-practical Django playbook: shell helpers, starter settings, template snippets, admin tweaks, and deployment notes. Use it as a copy-and-paste companion while you build your next project.

How to use this tutorial

  • Skim the tables below to find the file you need, then jump to that section via the in-page link.
  • Every section starts with a short “Why it matters” blurb so you know when to drop the snippet into your project.
  • Code blocks are production-ready—copy them straight into your app, then tweak names/paths as needed.
  • When the snippet depends on another file, the dependency is called out explicitly so you can avoid guesswork.

Table of contents

  1. Why Django?
  2. Quick start
  3. Core building blocks
  4. Advanced recipes
  5. Plugins
  6. Optimization & deployment
  7. Keep going

Why Django?

  • Ship faster: batteries-included admin, ORM, and authentication cut boilerplate.
  • Stay safe: CSRF, XSS, SQL injection, and host header protections are on by default.
  • Scale up: battle-tested cache + DB layers power some of the busiest sites on the web.
  • Stay flexible: mix relational databases, internationalization, and async views as needed.

If you are totally new to the framework, skim the official tutorial or the project overview before diving in here.

Quick start

# Section summary Snippet
00 Read-first overview and navigation tips 00_django.md
01 Provision Ubuntu dev machines consistently 01_setup.md
02 Auto-generate Django project + app skeleton 02_create_project.sh
03 Drop-in settings tuned for env vars 03_settings.py
04 Template for secrets and runtime config 04.env
05 Root URLConf with docs, sitemap, and media 05_project_urls.py
06 Hello-world views for smoke tests 06_views.py
07 Namespaced URL patterns for the demo app 07_urls.py
08 Bootstrap base template with nav 08_base.html
09 Homepage layout backed by Blog data 09_index.html
10 Data models with mixins and cleanup hooks 10_models.py
11 Styled model forms with validation 11_forms.py
12 Admin registrations, actions, and import/export 12_admin.py
13 AJAX-aware contact view and blog detail 13_views.py
14 Slug routes powering the advanced views 14_urls.py
15 Responsive blog listing markup 15_index.html
16 Three ways to render contact forms 16_contact.html
17 Global context helper for templates 17_context_processors.py
18 How-to notes for popular Django plugins 18_plugins.md
19 Sitemap classes and URL wiring 19_sitemap.md
20 Decorators for auth, throttling, and AJAX 20_decorators.md
21 SweetAlert-backed AJAX helper script 21_ajax.js
22 Date filter cheat sheet + request helpers 22 Date Template Filter
23 Enable admindocs inside /admin 23_admin_docs.md
24 Maintenance + quality assurance commands 24_optimization.md
25 DigitalOcean deployment checklist 25_hosting.md
26 HTTPS-ready Apache vhost examples 26_apache_virtualhost.md
27 Enable + secure static site vhost 27_static_site_virtualhost.md
28 Customize Django admin ordering 28_custom_ordering.md
29 Black/Isort/Ruff shared configuration 29_pyproject.toml

Tip: drop your secrets (DB credentials, email settings, third-party keys) into 04.env and load them via python-decouple as shown in the sample settings.

Core building blocks

Area File(s) What you get
Settings & configuration 03_settings.py Opinionated defaults: decouple, Postgres/SQLite toggle, custom time zone, date formats, media/static layout.
Environment 04.env Ready-to-fill placeholders for secret key, DB credentials, and email transport.
Project routing 05_project_urls.py Root URLConf with admin, app URLs, and optional documentation routes.
App views & URLs 06_views.py, 07_urls.py, 13_views.py, 14_urls.py Class-based and function-based samples (home, contact, CRUD list/detail).
Templates 08_base.html, 09_index.html, 15_index.html, 16_contact.html Base layout with blocks, responsive nav, form rendering, and DB-driven listings.
Data layer 10_models.py Example Contact, Project, and timestamp mixins with verbose names and admin-friendly ordering.
Forms 11_forms.py Model forms with custom widgets, placeholders, and basic validation hooks.
Admin 12_admin.py Inline editing, list filters, search, and read-only timestamp shortcuts.

Skim these snippets when you need a reminder of how to wire an end-to-end feature—URL → view → template → model/admin.

Advanced recipes

Topic File Highlights
Context processors 17_context_processors.py Inject site-wide metadata (menus, settings) into every template.
Ajax helpers 21_ajax.js Lightweight fetch wrapper with CSRF header and error handling.
Decorators 20_decorators.md Guard views with custom permissions, caching, or profiling snippets.
Custom ordering 28_custom_ordering.md Override admin change list sort behavior.
Sitemaps & admin docs 19_sitemap.md, 23_admin_docs.md Expose structured content for SEO and ship in-app developer docs.
Date helpers 22 Date Template Filter Human-friendly formatting filters for templates.

Use these when you need convenience features that sit just beyond the default tutorial material.

Plugins

Python’s pip ecosystem lets you slot in powerful functionality with a single install. The 18_plugins.md file captures fully worked examples; the table below links straight to the relevant section and official docs.

Plugin Repo Notes Official reference
Registration Redux 18_plugins.md django-registration-redux
Versatile Image Field 18_plugins.md versatileimagefield
Django Import Export 18_plugins.md django-import-export

Each section covers installation, required settings, admin integration, and code samples for immediate use.

Optimization & deployment

Stage File Focus
App profiling & cleanup 24_optimization.md Caching, query inspection, template tuning.
Hosting checklist 25_hosting.md DNS, SSL, media storage, monitoring.
Apache vhosts 26_apache_virtualhost.md, 27_static_site_virtualhost.md Production-ready mod_wsgi and static site configs.
Packaging 29_pyproject.toml Pin dependencies with Poetry/PEP 621 metadata.

Keep going

  • Compare these snippets with the latest official docs to stay current with security patches and new APIs.
  • Add your own snippets (REST framework, Celery, payments) to keep this guide aligned with the projects you ship most often.
  • Share improvements! Even small copy fixes or comments make the next build smoother.

Django Overview

Why it matters: Sets the narrative arc for the tutorial and links every other section, so point teammates here first. Copy it when: You need a high-level overview or want to reference the curated tables before diving into the code.

Setup

Why it matters: Provides a reproducible Ubuntu workstation bootstrap script so everyone codes against the same stack. Copy it when: You are provisioning a new VM or laptop and want a ready-made install script you can tweak with package additions.

Development System Setup

#!/bin/bash

set -e  # Exit immediately if a command exits with a non-zero status
set -x  # Print commands and their arguments as they are executed

# Update and upgrade system packages
sudo apt-get update -y
sudo apt-get upgrade -y

# Install essential Python packages and dependencies
sudo apt-get install -y \
    python3 python3-apt python3-pip python3-dev python3-venv \
    build-essential python3-setuptools python3-distutils-extra libpq-dev \
    postgresql postgresql-contrib libncurses-dev libgdbm-dev libc6-dev zlib1g-dev \
    libsqlite3-dev tk-dev libssl-dev openssl libffi-dev libreadline-dev \
    libpcap-dev python3-cffi python3-brotli libpango-1.0-0 libpangoft2-1.0-0 \
    apache2 libapache2-mod-wsgi-py3 python3-certbot-apache \
    libtk8.6 python3-smbus phppgadmin python3-virtualenv

sudo pip3 install --upgrade pip
sudo pip3 install virtualenv

# Enable Apache modules
sudo a2enmod rewrite
sudo a2enmod wsgi
sudo a2enmod ssl

# Install GDAL and dependencies
sudo add-apt-repository ppa:ubuntugis/ubuntugis-unstable -y
sudo apt-get update -y
sudo apt-get install -y gdal-bin python3-gdal

# Install wkhtmltopdf and wkhtmltoimage
wget https://github.com/wkhtmltopdf/packaging/releases/download/0.12.6.1-2/wkhtmltox_0.12.6.1-2.jammy_amd64.deb -O wkhtmltox.deb
sudo dpkg -i wkhtmltox.deb || sudo apt-get install -f -y
rm wkhtmltox.deb

# Final update
sudo apt-get update -y

# Clean up
sudo apt-get autoremove -y
sudo apt-get clean

echo "Installation completed successfully!"

Make it Executable and Run

chmod +x install_dependencies.sh
sudo ./install_dependencies.sh

Install a text editor

Atom

sudo add-apt-repository ppa:webupd8team/atom
sudo apt-get update
sudo apt-get install atom -y

VS Code

sudo apt update
sudo apt install software-properties-common apt-transport-https wget
wget -q https://packages.microsoft.com/keys/microsoft.asc -O- | sudo apt-key add -
sudo add-apt-repository "deb [arch=amd64] https://packages.microsoft.com/repos/vscode stable main"
sudo apt update
sudo apt install code -y

Sublime text

wget -qO - https://download.sublimetext.com/sublimehq-pub.gpg | sudo apt-key add -
echo "deb https://download.sublimetext.com/ apt/stable/" | sudo tee /etc/apt/sources.list.d/sublime-text.list
sudo apt-get update
sudo apt-get install sublime-text -y

Install git & configure

sudo apt install git -y
git config --global user.name "Name"
git config --global user.email "example@gmail.com"

Install Chrome

wget https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb
sudo dpkg -i google-chrome-stable_current_amd64.deb

Create Project Script

Why it matters: Automates the repetitive django-admin + virtualenv scaffolding so you can spin up fresh projects in minutes. Copy it when: You need to scaffold a new project/app quickly—drop the script in any folder and run it with your preferred names.

#!/usr/bin/env bash
#
# The script assumes Python 3.10+ is installed and that it is run
# from the directory where you want the project folder to live.

set -euo pipefail

PROJECT_NAME="${PROJECT_NAME:-project}"
APP_NAME="${APP_NAME:-web}"
PYTHON_BIN="${PYTHON_BIN:-python3}"
VENV_DIR="${VENV_DIR:-venv}"

log() {
    printf "\n[setup] %s\n" "$1"
}

run() {
    echo "+ $*"
    "$@"
}

log "Creating virtual environment (${VENV_DIR})"
run "${PYTHON_BIN}" -m venv "${VENV_DIR}"
# shellcheck disable=SC1090
source "${VENV_DIR}/bin/activate"

log "Upgrading pip and installing base dependencies"
run pip install --upgrade pip
run pip install "django>=4.2,<5.0" psycopg2-binary python-decouple

log "Scaffolding Django project (${PROJECT_NAME})"
run django-admin startproject "${PROJECT_NAME}"
cd "${PROJECT_NAME}"

log "Creating static/media/templates directories"
mkdir -p static media templates

log "Starting reusable app (${APP_NAME})"
run python manage.py startapp "${APP_NAME}"

log "Applying initial migrations"
run python manage.py makemigrations
run python manage.py migrate

cat <<'INSTRUCTIONS'

✅ Project scaffold complete.

Next steps
1. Add the new app to INSTALLED_APPS.
2. Copy the snippets from this gist into the project (settings, URLs, templates, etc.).
3. Run `python manage.py runserver` and start building!

INSTRUCTIONS

Settings

Why it matters: Opinionated settings with env-driven secrets, custom time formats, and pluggable context processors reduce copy/paste errors. Copy it when: You are starting a project and want production-friendly defaults that still work locally—paste the file over Django’s stock settings.

"""Starter Django settings that pair with the snippets/user guide."""
from pathlib import Path

from decouple import config

BASE_DIR = Path(__file__).resolve().parent.parent

SECRET_KEY = config("SECRET_KEY")
DEBUG = config("DEBUG", default=True, cast=bool)

ALLOWED_HOSTS = [host for host in config("ALLOWED_HOSTS", default="*").split(",") if host]
CSRF_TRUSTED_ORIGINS = [origin for origin in config("CSRF_TRUSTED_ORIGINS", default="").split(",") if origin]

INSTALLED_APPS = [
    "django.contrib.admin",
    "django.contrib.admindocs",
    "django.contrib.auth",
    "django.contrib.contenttypes",
    "django.contrib.sessions",
    "django.contrib.messages",
    "django.contrib.staticfiles",
    "web",
]

MIDDLEWARE = [
    ...
]

ROOT_URLCONF = "project.urls"

TEMPLATES = [
    {
        "BACKEND": "django.template.backends.django.DjangoTemplates",
        "DIRS": [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",
                "web.context_processors.main_context",
            ],
        },
    },
]

WSGI_APPLICATION = "project.wsgi.application"

DATABASES = {
    "default": {
        "ENGINE": config("DB_ENGINE", default="django.db.backends.sqlite3"),
        "NAME": config("DB_NAME", default=str(BASE_DIR / "db.sqlite3")),
        "USER": config("DB_USER", default=""),
        "PASSWORD": config("DB_PASSWORD", default=""),
        "HOST": config("DB_HOST", default="localhost"),
        "PORT": config("DB_PORT", default="5432"),
        "OPTIONS": {},
    }
}

AUTH_PASSWORD_VALIDATORS = [
    ...
]

LANGUAGE_CODE = "en-us"
TIME_ZONE = config("TIME_ZONE", default="Asia/Kolkata")
USE_I18N = True
USE_L10N = False
USE_TZ = True

DATE_INPUT_FORMATS = (
    "%d/%m/%Y",
    "%d-%m-%Y",
    "%Y-%m-%d",
    "%d %b %Y",
    "%d %B %Y",
)

DATETIME_INPUT_FORMATS = (
    "%Y-%m-%d %H:%M:%S",
    "%Y-%m-%d %H:%M",
    "%d/%m/%Y %H:%M:%S",
    "%d/%m/%Y %H:%M",
)

MEDIA_URL = "/media/"
MEDIA_ROOT = BASE_DIR / "media"

STATIC_URL = "/static/"
STATICFILES_DIRS = [BASE_DIR / "static"]
STATIC_ROOT = BASE_DIR / "assets"

DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"

Env Template

Why it matters: Centralizes secrets and deployment toggles so .env stays in sync with the settings file’s expectations. Copy it when: You need an environment template for new developers or deployment targets—fill in the blanks and keep it out of version control.

SECRET_KEY=replace-me
DEBUG=True

# Database configuration
DB_ENGINE=django.db.backends.postgresql
DB_NAME=project_db
DB_USER=project_dbuser
DB_PASSWORD=db_password
DB_HOST=localhost
DB_PORT=5432

# Optional: comma–separated values, e.g. example.com,.example.com
ALLOWED_HOSTS=localhost,127.0.0.1
CSRF_TRUSTED_ORIGINS=https://example.com

# Email (used by registration/login helpers)
EMAIL_BACKEND=django.core.mail.backends.smtp.EmailBackend
EMAIL_HOST=smtp.example.com
EMAIL_PORT=587
EMAIL_HOST_USER=hello@example.com
EMAIL_HOST_PASSWORD=smtp-password

Project URLs

Why it matters: Shows a fully wired root URLConf with admindocs, sitemap/robots, and static helpers, saving you from hunting docs. Copy it when: You want a drop-in urls.py that already handles docs and media serving; adapt the app namespace and you’re good to go.

"""Project level URL configuration."""
from django.conf import settings
from django.conf.urls.static import static
from django.contrib import admin
from django.urls import include, path
from django.views.generic import TemplateView

urlpatterns = [
    path("admin/doc/", include("django.contrib.admindocs.urls")),
    path("admin/", admin.site.urls),
    path("", include(("web.urls", "web"), namespace="web")),
    path("sitemap.xml", TemplateView.as_view(template_name="sitemap.xml", content_type="application/xml"), name="sitemap"),
    path("robots.txt", TemplateView.as_view(template_name="robots.txt", content_type="text/plain"), name="robots"),
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)

admin.site.site_header = "Project administration"
admin.site.site_title = "Project admin"
admin.site.index_title = "Welcome to the dashboard"

Django Views

Why it matters: Demonstrates the simplest views powering the quick-start pages, perfect for smoke tests and template wiring. Copy it when: You need lightweight placeholder views while designing templates or verifying URL routing.

"""Minimal views used by the quick-start tutorial."""
from django.shortcuts import render


def index(request):
    """Render a simple landing page."""
    context = {"is_index": True}
    return render(request, "web/index.html", context)


def contact(request):
    """Render a static contact page."""
    context = {"is_contact": True}
    return render(request, "web/contact.html", context)

App URLs

Why it matters: Provides app-level URL patterns with namespaced routes so reverse lookups work immediately. Copy it when: Creating a new app and you want conventional paths (/, /about/, /contact/) without retyping boilerplate.

"""Example URL patterns for the lightweight demo app."""
from django.urls import path
from django.views.generic import TemplateView

from . import views

app_name = "web"

urlpatterns = [
    path("", views.index, name="index"),
    path("about/", TemplateView.as_view(template_name="web/about.html"), name="about"),
    path("contact/", views.contact, name="contact"),
]

Base Template

Why it matters: Supplies a Bootstrap-ready base template with nav highlighting, making every child template consistent. Copy it when: You are building the first templates for an app and want sensible blocks, assets, and nav wiring out of the gate.

<!DOCTYPE html>
{% load static %}
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>{% block title %}My Django Site{% endblock %}</title>
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css">
    <link rel="stylesheet" href="{% static 'web/css/style.css' %}">
    {% block css_plugins %}{% endblock %}
</head>
<body>
<header class="border-bottom mb-4">
    <nav class="navbar navbar-expand-md navbar-light bg-white">
        <div class="container">
            <a class="navbar-brand fw-semibold" href="{% url 'web:index' %}">Django Snippets</a>
            <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
                <span class="navbar-toggler-icon"></span>
            </button>
            <div class="collapse navbar-collapse" id="navbarNav">
                <ul class="navbar-nav ms-auto">
                    <li class="nav-item"><a class="nav-link {% if is_index %}active{% endif %}" href="{% url 'web:index' %}">Home</a></li>
                    <li class="nav-item"><a class="nav-link {% if is_contact %}active{% endif %}" href="{% url 'web:contact' %}">Contact</a></li>
                    <li class="nav-item"><a class="nav-link" href="{% url 'web:about' %}">About</a></li>
                </ul>
            </div>
        </div>
    </nav>
</header>

<main class="container py-4">
    {% block content %}{% endblock %}
</main>

<footer class="border-top py-3 text-center text-muted">
    Built with Django · {{ domain|default:"localhost" }}
</footer>

<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
<script src="{% static 'web/js/script.js' %}"></script>
{% block js_plugins %}{% endblock %}
{% block javascript %}{% endblock %}
</body>
</html>

Index Template

Why it matters: Offers a homepage layout that demonstrates loops, date formatting, and CTA buttons for quick demos. Copy it when: You need a portfolio/blog style landing page tied to the Blog model sample.

{% extends "web/base.html" %}
{% block title %}Home · Django snippets{% endblock %}
{% block content %}
<section class="row g-4 align-items-center">
    <div class="col-md-6">
        <h1 class="display-5 fw-semibold mb-3">Kick-start your next Django build</h1>
        <p class="lead text-muted">
            Copy the settings, URLs, models, templates, and admin helpers from this guide to deliver features faster.
        </p>
        <div class="d-flex gap-2">
            <a class="btn btn-primary" href="{% url 'web:contact' %}">Contact us</a>
            <a class="btn btn-outline-secondary" href="https://docs.djangoproject.com/en/stable/">Official docs</a>
        </div>
    </div>
    <div class="col-md-6">
        <div class="card shadow-sm">
            <div class="card-header bg-light">
                <strong>Recently published posts</strong>
            </div>
            <ul class="list-group list-group-flush">
                {% for blog in blogs %}
                    <li class="list-group-item d-flex justify-content-between align-items-center">
                        <a href="{% url 'web:blogview' slug=blog.slug %}" class="fw-semibold text-decoration-none">
                            {{ blog.title }}
                        </a>
                        <span class="text-muted small">{{ blog.timestamp|date:"M d, Y" }}</span>
                    </li>
                {% empty %}
                    <li class="list-group-item text-muted">No blog posts yet — create one from the admin.</li>
                {% endfor %}
            </ul>
        </div>
    </div>
</section>
{% endblock %}

Django Models

Why it matters: Contains reusable base models, domain models, and cleanup patterns (UUIDs, soft toggles, image deletion). Copy it when: Defining new data models and you want to inherit battle-tested fields/behaviors instead of starting from scratch.

"""Sample models used throughout the guide."""
from uuid import uuid4

from django.db import models


class BaseModel(models.Model):
    """Abstract base shared by everything else."""

    id = models.UUIDField(primary_key=True, default=uuid4, editable=False)
    created = models.DateTimeField(auto_now_add=True, db_index=True)
    updated = models.DateTimeField(auto_now=True)
    is_active = models.BooleanField(default=True)

    class Meta:
        abstract = True
        ordering = ("-created",)


class Contact(models.Model):
    name = models.CharField(max_length=120)
    timestamp = models.DateTimeField(auto_now_add=True, db_index=True)
    email = models.EmailField(blank=True, null=True)
    phone = models.CharField(max_length=120, blank=True, null=True)
    place = models.CharField(max_length=120, blank=True, null=True)
    message = models.TextField()

    class Meta:
        ordering = ("-timestamp",)

    def __str__(self) -> str:
        return self.name

Django Forms

Why it matters: Converts the models into polished forms with widgets, validation, and translation hooks ready for the admin or public site. Copy it when: You are turning the models into user-facing forms and want consistent styling/validation.

"""Reusable forms showcased in the snippets."""
from django import forms
from django.forms import widgets
from django.utils.translation import gettext_lazy as _

from .models import Blog, Contact, Registration


class ContactForm(forms.ModelForm):
    class Meta:
        model = Contact
        exclude = ("timestamp",)
        widgets = {
            "name": widgets.TextInput(attrs={"class": "form-control", "placeholder": "Your name"}),
            "phone": widgets.TextInput(attrs={"class": "form-control", "placeholder": "Phone"}),
            "place": widgets.TextInput(attrs={"class": "form-control", "placeholder": "City / country"}),
            "email": widgets.EmailInput(attrs={"class": "form-control", "placeholder": "Email"}),
            "message": widgets.Textarea(attrs={"class": "form-control", "placeholder": "How can we help?"}),
        }
        error_messages = {
            "name": {"required": _("Let us know who you are.")},
            "message": {"required": _("Please describe your request.")},
        }

Django Admin

Why it matters: Tunes the Django admin with actions, import/export integration, and creator tracking so the back office feels premium. Copy it when: You are enabling staff workflows—paste the admin classes, adjust the registered models, and enjoy a richer admin instantly.

"""Admin customisations referenced throughout the guide."""
from django.contrib import admin, messages
from django.contrib.auth import get_user_model
from django.contrib.auth.models import Group
from django.utils.translation import ngettext
from import_export.admin import ImportExportActionModelAdmin, ImportExportModelAdmin

from .models import Blog, Registration


def mark_active(modeladmin, request, queryset):
    updated = queryset.update(is_active=True)
    modeladmin.message_user(
        request,
        ngettext(
            "%d record was successfully marked as active.",
            "%d records were successfully marked as active.",
            updated,
        )
        % updated,
        messages.SUCCESS,
    )


def mark_inactive(modeladmin, request, queryset):
    updated = queryset.update(is_active=False)
    modeladmin.message_user(
        request,
        ngettext(
            "%d record was successfully marked as inactive.",
            "%d records were successfully marked as inactive.",
            updated,
        )
        % updated,
        messages.SUCCESS,
    )


@admin.register(Blog)
class BlogAdmin(admin.ModelAdmin):
    list_display = ("title", "author", "category", "is_active", "timestamp")
    list_filter = ("category", "is_active", "timestamp")
    search_fields = ("title", "author__name")
    readonly_fields = ("auto_id",)
    autocomplete_fields = ("author",)
    prepopulated_fields = {"slug": ("title",)}
    actions = (mark_active, mark_inactive)


class BaseTrackedAdmin(ImportExportModelAdmin):
    """Example parent admin that auto-populates creator fields."""

    exclude = ("creator",)
    actions = (mark_active, mark_inactive)
    readonly_fields = ("created", "updated", "is_active")

    def save_model(self, request, obj, form, change):
        if not obj.pk:
            obj.creator = request.user
        super().save_model(request, obj, form, change)


# Hide default auth models if you prefer a custom user app
User = get_user_model()
try:
    admin.site.unregister(User)
except admin.sites.NotRegistered:
    pass

try:
    admin.site.unregister(Group)
except admin.sites.NotRegistered:
    pass

Advanced Views

Why it matters: Adds production-style views (AJAX-aware forms, slug detail pages) so you can see how the pieces fit end-to-end. Copy it when: Implementing real interactions such as contact forms or blog detail pages and you want a tested pattern to follow.

"""Higher-level views that showcase form handling and AJAX replies."""
from django.http import JsonResponse
from django.shortcuts import get_object_or_404, render
from django.views.decorators.http import require_http_methods

from .forms import ContactForm
from .models import Blog


def index(request):
    blogs = Blog.objects.filter(is_active=True).order_by("-timestamp")[:5]
    context = {"is_index": True, "blogs": blogs}
    return render(request, "web/index.html", context)


def blogview(request, slug):
    instance = get_object_or_404(Blog, slug=slug, is_active=True)
    return render(request, "web/blog.html", {"is_blog": True, "instance": instance})


@require_http_methods(["GET", "POST"])
def contact(request):
    form = ContactForm(request.POST or None)

    if request.method == "POST":
        if form.is_valid():
            instance = form.save()
            payload = {
                "status": "true",
                "title": "Thanks!",
                "message": "Your message was received — we’ll reply soon.",
                "pk": str(instance.pk),
            }
        else:
            payload = {"status": "false", "title": "Fix the highlighted fields"}
        if request.headers.get("x-requested-with") == "XMLHttpRequest":
            data = payload.copy()
            if data["status"] == "false":
                data["errors"] = form.errors.get_json_data()
            return JsonResponse(data)
        if payload["status"] == "true":
            form = ContactForm()
        return render(request, "web/contact.html", {"form": form, "is_contact": True, "response": payload})

    return render(request, "web/contact.html", {"form": form, "is_contact": True})

Advanced URLs

Why it matters: Extends the URL layer for the richer views, illustrating slug paths and named routes used by templates. Copy it when: You adopt the advanced views or need examples of clean, namespaced blog/contact routes.

"""URL patterns for the more complete blog/contact demo."""
from django.urls import path

from . import views

app_name = "web"

urlpatterns = [
    path("", views.index, name="index"),
    path("contact/", views.contact, name="contact"),
    path("blog/<slug:slug>/", views.blogview, name="blogview"),
]

Index Page

Why it matters: Shows how to render collections with Bootstrap cards, ownership badges, and fallbacks—handy for any list page. Copy it when: Building a blog listing or catalog view and you want ready-made markup tied to the provided models.

<!-- Display all blogs -->
<div class="row g-4">
    {% for blog in blogs %}
        <div class="col-md-4">
            <article class="card h-100 shadow-sm">
                <a href="{% url 'web:blogview' slug=blog.slug %}" class="text-decoration-none text-dark">
                    {% if blog.featured_image %}
                        <img src="{{ blog.featured_image.url }}" alt="{{ blog.title }}" class="card-img-top">
                    {% endif %}
                    <div class="card-body">
                        <h3 class="h5">{{ forloop.counter }}. {{ blog.title }}</h3>
                        <p class="text-muted small mb-2">{{ blog.timestamp|date:"M d, Y" }} · {{ blog.author }}</p>
                        <div class="card-text">
                            {{ blog.content|truncatewords:30|safe }}
                        </div>
                    </div>
                </a>
                {% if blog.assign_to.all %}
                    <div class="card-footer bg-white small text-muted">
                        Owners:
                        {% for person in blog.assign_to.all %}
                            <a href="{% url 'employees:profile' pk=person.pk %}">{{ person }}</a>{% if not forloop.last %}, {% endif %}
                        {% endfor %}
                    </div>
                {% endif %}
            </article>
        </div>
    {% empty %}
        <div class="col-12 text-muted">No blog posts found. Create one from the admin.</div>
    {% endfor %}
</div>

Contact Page

Why it matters: Demonstrates three rendering strategies (auto, loop, manual) plus CSRF-safe AJAX classes for form posts. Copy it when: You need to wire a contact/support form quickly and want flexibility in how fields are rendered.

<!-- Method 1: `form.as_p` -->
<form action="" method="post" class="ajax reload mb-5">
    {% csrf_token %}
    {{ form.as_p }}
    <button type="submit" class="btn btn-primary">Submit</button>
</form>

<!-- Method 2: iterate fields -->
<form action="" method="post" class="ajax reload mb-5">
    {% csrf_token %}
    {% for field in form %}
        <div class="mb-3">
            {{ field.label_tag }}
            {{ field }}
            {% if field.help_text %}<small class="text-muted d-block">{{ field.help_text }}</small>{% endif %}
            {% for error in field.errors %}
                <div class="text-danger small">{{ error }}</div>
            {% endfor %}
        </div>
    {% endfor %}
    <button type="submit" class="btn btn-primary">Submit</button>
</form>

<!-- Method 3: manual inputs -->
<form action="" method="post" class="ajax reload">
    {% csrf_token %}
    <div class="mb-3">
        <label for="{{ form.name.id_for_label }}" class="form-label">{{ form.name.label }}</label>
        {{ form.name }}
    </div>
    <div class="mb-3">
        <label for="{{ form.phone.id_for_label }}" class="form-label">{{ form.phone.label }}</label>
        {{ form.phone }}
    </div>
    <div class="mb-3">
        <label for="{{ form.place.id_for_label }}" class="form-label">{{ form.place.label }}</label>
        {{ form.place }}
    </div>
    <div class="mb-3">
        <label for="{{ form.email.id_for_label }}" class="form-label">{{ form.email.label }}</label>
        {{ form.email }}
    </div>
    <div class="mb-3">
        <label for="{{ form.message.id_for_label }}" class="form-label">{{ form.message.label }}</label>
        {{ form.message }}
    </div>
    <button type="submit" class="btn btn-primary">Submit</button>
</form>

Context Processors

Why it matters: Explains how to inject global context like domains and support info, reducing repeated template logic. Copy it when: You catch yourself passing the same variables to every view—drop in this processor and configure it in settings.

"""
Context processors inject reusable data into every template.

Example use cases
- navigation menus sourced from the database
- feature flags read from settings or an API
- branding details (domain, phone, email)
"""
import datetime
from django.conf import settings


def main_context(request):
    today = datetime.date.today()
    return {
        "domain": request.get_host(),
        "current_year": today.year,
        "support_email": getattr(settings, "SUPPORT_EMAIL", "support@example.com"),
    }


# settings.py (snippet)
TEMPLATES = [
    {
        "BACKEND": "django.template.backends.django.DjangoTemplates",
        "DIRS": [BASE_DIR / "templates"],
        "APP_DIRS": True,
        "OPTIONS": {
            "context_processors": [
                # ...
                "web.context_processors.main_context",
            ],
        },
    },
]

Plugins

Why it matters: Curates setup notes for common ecosystem plugins so you can add registration, media, or CSV tooling without deep dives. Copy it when: You are integrating one of the listed packages—copy the install/config steps and adapt the snippets for your app.

Django plugin snippets

Each section below follows the same flow: install → configure → wire URLs/admin → verify. Copy the snippets directly into your project and tweak paths or model names as needed.

Django Registration Redux

  1. Install
pip install django-registration-redux
  1. Enable the app
INSTALLED_APPS = [
    # …
    "registration",
]
  1. Configure authentication settings (place near your other auth config):
ACCOUNT_ACTIVATION_DAYS = 7
REGISTRATION_AUTO_LOGIN = True
SEND_ACTIVATION_EMAIL = False
REGISTRATION_EMAIL_SUBJECT_PREFIX = ""

REGISTRATION_OPEN = True
LOGIN_URL = "/app/accounts/login/"
LOGOUT_URL = "/app/accounts/logout/"
LOGIN_REDIRECT_URL = "/admin/"

EMAIL_BACKEND = config("EMAIL_BACKEND")
EMAIL_HOST = config("EMAIL_HOST")
EMAIL_PORT = config("EMAIL_PORT")
EMAIL_HOST_USER = config("EMAIL_HOST_USER")
EMAIL_HOST_PASSWORD = config("EMAIL_HOST_PASSWORD")
EMAIL_USE_TLS = True

DEFAULT_FROM_EMAIL = config("DEFAULT_FROM_EMAIL", default=EMAIL_HOST_USER)
DEFAULT_BCC_EMAIL = config("DEFAULT_BCC_EMAIL", default=EMAIL_HOST_USER)
DEFAULT_REPLY_TO_EMAIL = config("DEFAULT_REPLY_TO_EMAIL", default=EMAIL_HOST_USER)
SERVER_EMAIL = config("SERVER_EMAIL", default=EMAIL_HOST_USER)
ADMIN_EMAIL = config("ADMIN_EMAIL", default=EMAIL_HOST_USER)
  1. URLs
from django.urls import include, path

urlpatterns = [
    path("accounts/", include("registration.backends.default.urls")),
    # Or switch to the simple backend:
    # path("accounts/", include("registration.backends.simple.urls")),
]
  1. Templates

Use the default templates as a starting point. Helpful auth URLs:

{% url "auth_login" %}              {# Login #}
{% url "auth_logout" %}             {# Logout #}
{% url "auth_password_change" %}    {# Change password #}
{% url "auth_password_reset" %}     {# Reset password #}
{% url "registration_register" %}   {# Sign-up #}

Versatile Image Field

  1. Install
pip install django-versatileimagefield
  1. Enable and configure
INSTALLED_APPS = [
    # …
    "versatileimagefield",
]

VERSATILEIMAGEFIELD_SETTINGS = {
    "cache_length": 2_592_000,
    "cache_name": "versatileimagefield_cache",
    "jpeg_resize_quality": 70,
    "sized_directory_name": "__sized__",
    "filtered_directory_name": "__filtered__",
    "placeholder_directory_name": "__placeholder__",
    "create_images_on_demand": True,
    "image_key_post_processor": None,
    "progressive_jpeg": False,
}
  1. Use in models
from versatileimagefield.fields import VersatileImageField


class Customer(models.Model):
    photo = VersatileImageField("Photo", upload_to="customers/", blank=True, null=True)
  1. Migrate & render
python manage.py makemigrations
python manage.py migrate

Template helpers:

<img src="{{ customer.photo.crop.200x200 }}" alt="">
<img src="{{ customer.photo.thumbnail.600x600 }}" alt="">

Django Import Export

  1. Install
pip install django-import-export
  1. Enable
INSTALLED_APPS = [
    # …
    "import_export",
]
  1. Admin integration
from import_export.admin import ImportExportActionModelAdmin, ImportExportModelAdmin
from import_export import resources


class RegistrationResource(resources.ModelResource):
    class Meta:
        model = models.Registration
        exclude = ("imported",)
        fields = ("id", "name", "author", "price")
        import_id_fields = ("isbn",)
        export_order = ("id", "price", "author__name")


@admin.register(models.Designation)
class DesignationAdmin(ImportExportActionModelAdmin):
    pass


@admin.register(models.Registration)
class RegistrationAdmin(ImportExportModelAdmin):
    resource_class = RegistrationResource

Run python manage.py createsuperuser, log into /admin, and you will see Import/Export buttons plus reusable actions.

Sitemap

Why it matters: Walks through Django’s sitemap framework so search engines discover your content automatically. Copy it when: You’re ready to expose dynamic URLs (products, blogs, etc.) to Google/Bing—copy the sitemap.py and URL wiring.

Django sitemap framework

A sitemap is an XML file that helps search engines crawl your site. Django’s contrib.sitemaps app builds the file automatically based on the URLs (static or dynamic) you expose.

Install & configure

  1. Enable apps and the Sites framework:
INSTALLED_APPS = [
    # ...
    "django.contrib.sites",
    "django.contrib.sitemaps",
]
SITE_ID = 1
  1. Ensure you have a DjangoTemplates backend with APP_DIRS=True (default).

Create web/sitemap.py

from django.contrib.sitemaps import Sitemap
from django.urls import reverse

from products.models import Product


class StaticSitemap(Sitemap):
    changefreq = "yearly"
    priority = 1.0
    protocol = "https"

    def items(self):
        return ["web:index", "web:about", "web:products", "web:contact"]

    def location(self, item):
        return reverse(item)


class FeaturedProductSitemap(Sitemap):
    changefreq = "weekly"
    priority = 0.8
    protocol = "https"

    def items(self):
        return Product.objects.filter(is_featured=True)

    def lastmod(self, obj):
        return obj.timestamp

    def location(self, obj):
        return reverse("web:product_view", args=[obj.slug])


sitemaps = {
    "static": StaticSitemap,
    "featured_products": FeaturedProductSitemap,
}

Wire the URL

from django.contrib.sitemaps.views import sitemap
from web.sitemap import sitemaps

urlpatterns = [
    # ...
    path("sitemap.xml", sitemap, {"sitemaps": sitemaps}, name="sitemap"),
]

Visit http://127.0.0.1:8000/sitemap.xml to verify the output. Add more sitemap classes (blog posts, categories, etc.) as needed and include them in the sitemaps dict.

Decorators

Why it matters: Collects decorator patterns for auth, perf, and templating so you can protect or instrument views on demand. Copy it when: You need to wrap a view with login, group, or AJAX guards—grab the snippet that matches your use case.

Useful view decorators

Django ships with batteries-included decorators and it’s easy to craft your own. This cheat sheet highlights common patterns.

@login_required

from django.contrib.auth.decorators import login_required


@login_required
def my_view(request):
    ...

@require_http_methods

from django.views.decorators.http import require_http_methods


@require_http_methods(["GET", "POST"])
def my_view(request):
    ...

Numeric for loop helper

from django import template

register = template.Library()


@register.filter(name="times")
def times(number):
    return range(number)
{% load my_filters %}
{% for i in 15|times %}
    <li>Item {{ i }}</li>
{% endfor %}

@group_required

Restrict views to users in any of the supplied groups.

from django.contrib.auth.decorators import user_passes_test


def group_required(*group_names):
    def in_groups(user):
        if user.is_authenticated:
            return user.is_superuser or user.groups.filter(name__in=group_names).exists()
        return False

    return user_passes_test(in_groups)


@group_required("admins", "seller")
def my_view(request, pk):
    ...

@anonymous_required

Redirect authenticated users away from login/register pages.

from django.conf import settings
from django.contrib.auth.decorators import user_passes_test


def anonymous_required(function=None, redirect_url=None):
    redirect_url = redirect_url or settings.LOGIN_REDIRECT_URL

    actual_decorator = user_passes_test(lambda u: u.is_anonymous, login_url=redirect_url)
    if function:
        return actual_decorator(function)
    return actual_decorator

@superuser_only

from django.core.exceptions import PermissionDenied


def superuser_only(function):
    def _inner(request, *args, **kwargs):
        if not request.user.is_superuser:
            raise PermissionDenied
        return function(request, *args, **kwargs)

    return _inner

@ajax_required

from django.http import HttpResponseBadRequest


def ajax_required(function):
    def wrap(request, *args, **kwargs):
        if request.headers.get("x-requested-with") != "XMLHttpRequest":
            return HttpResponseBadRequest("AJAX requests only.")
        return function(request, *args, **kwargs)

    wrap.__doc__ = function.__doc__
    wrap.__name__ = function.__name__
    return wrap

@timeit

import time
from functools import wraps


def timeit(function):
    @wraps(function)
    def wrapped(*args, **kwargs):
        start = time.perf_counter()
        result = function(*args, **kwargs)
        elapsed = time.perf_counter() - start
        print(f"{function.__name__} took {elapsed:.2f}s")
        return result

    return wrapped

AJAX Helpers

Why it matters: Supplies a lightweight, SweetAlert-powered AJAX helper so forms/actions feel modern without a framework. Copy it when: You are enhancing forms with AJAX submissions or confirmation dialogs; include the script and reference the CSS classes.

/* global Swal */
/**
 * Ajax helpers for forms and action buttons.
 * Requires jQuery + SweetAlert2 (loaded separately).
 */

let isLoading = false;

const handleSuccess = (data, { reset, reload, redirect, redirectUrl }, formEl) => {
    const title = data.title || (data.status === "true" ? "Success" : "Error");
    const icon = data.status === "true" ? "success" : "error";

    Swal.fire({ title, html: data.message, icon }).then(() => {
        if (data.status === "true") {
            if (redirect) window.location.href = redirectUrl;
            if (reload) window.location.reload();
            if (reset && formEl) formEl.reset();
        }
    });
};

$(document).on("submit", "form.ajax", function (event) {
    event.preventDefault();
    if (isLoading) return;

    const $form = $(this);
    const submitBtn = $form.find('[type="submit"]');
    const payload = new FormData(this);
    const actionUrl = $form.attr("action");
    const options = {
        reset: $form.hasClass("reset"),
        reload: $form.hasClass("reload"),
        redirect: $form.hasClass("redirect"),
        redirectUrl: $form.data("redirect") || "",
    };

    isLoading = true;
    submitBtn.prop("disabled", true);

    $.ajax({
        url: actionUrl,
        type: "POST",
        data: payload,
        cache: false,
        contentType: false,
        processData: false,
        dataType: "json",
    })
        .done((data) => handleSuccess(data, options, this))
        .fail(() => Swal.fire({ title: "Oops", html: "Something went wrong.", icon: "error" }))
        .always(() => {
            isLoading = false;
            submitBtn.prop("disabled", false);
        });
});

$(document).on("click", ".action-button", function (event) {
    event.preventDefault();
    const $btn = $(this);
    const url = $btn.attr("href");
    const title = $btn.data("title") || "Are you sure?";
    const text = $btn.data("text") || "This action cannot be undone.";

    Swal.fire({ title, html: text, icon: "question", showCancelButton: true }).then((result) => {
        if (!result.isConfirmed) return;
        $.getJSON(url, { pk: $btn.data("id") })
            .done((data) => handleSuccess(data, { redirect: data.redirect, reload: data.reload, redirectUrl: data.redirect_url }))
            .fail(() => Swal.fire({ title: "Error", html: "Request failed.", icon: "error" }));
    });
});

Date Template Filter

Why it matters: Keeps popular date filter combos and request helpers at your fingertips, saving time digging through docs. Copy it when: You forget the exact date format token or need to show the current URL in a template—copy the relevant row.

Date Template Filter

List of the most used Django date template filters to format date according to a given format, semantically ordered.

Usage {{ date|date:"j" }}

Code Description Output
d Day of the month, 2 digits with leading zeros 01 to 31
j Day of the month without leading zeros. 1 to 31
S English ordinal suffix for day of the month, 2 characters. st, nd, rd or th
m Month, 2 digits with leading zeros. 01 to 12
n Month without leading zeros. 1 to 12
b Month, textual, 3 letters, lowercase. jan
M Month, textual, 3 letters. Jan
F Month, textual, long. January
y Year, 2 digits. 20
Y Year, 4 digits. 2020
D Day of the week, textual, 3 letters. Fri
l Day of the week, textual, long. Friday
G Hour, 24-hour format without leading zeros. 0 to 23
H Hour, 24-hour format. 00 to 23
g Hour, 12-hour format without leading zeros. 1 to 12
h Hour, 12-hour format. 01 to 12
a a.m. or p.m. a.m
A AM or PM. AM
i Minutes. 00 to 59
s Seconds, 2 digits with leading zeros. 0 to 59
Get the Current URL Within a Django Template
Method Output Explanation
{{request.path}} /home/ request.path returns the path component of the URL, which is the part of the URL after the domain name. In this case, it is "/home/".
{{request.get_full_path}} /home/?q=test request.get_full_path returns the full path of the URL, including the query parameters. In this case, it is "/home/?q=test".
{{request.build_absolute_uri}} http://127.0.0.1:8000/home/?q=test request.build_absolute_uri returns the complete absolute URI of the requested URL, including the scheme (e.g., "http" or "https"), domain, port, path, and query parameters. In this case, it is "http://127.0.0.1:8000/home/?q=test".

Admin Docs

Why it matters: Documents how to surface admindocs so teammates can read model/view docstrings without leaving the admin. Copy it when: You want /admin/doc/ available in a project—enable the app, add the URLs, and ship internal docs instantly.

django.contrib.admindocs renders documentation from your model/view docstrings directly inside the Django admin.

Enable admindocs

  1. Add the app:
INSTALLED_APPS = [
    # ...
    "django.contrib.admindocs",
]
  1. Install docutils (required to render reStructuredText):
python -m pip install docutils
  1. Wire URLs before the default admin pattern:
urlpatterns = [
    path("admin/doc/", include("django.contrib.admindocs.urls")),
    path("admin/", admin.site.urls),
]
  1. Optional: enable django.contrib.admindocs.middleware.XViewMiddleware to use the built-in bookmarklets.

Visit /admin/doc/ or click "Documentation" in the admin header to browse the generated content. Keep your docstrings meaningful—the output mirrors whatever you write.

Optimization

Why it matters: Groups the maintenance commands (fixtures, migrations, quality tooling) into a single operational playbook. Copy it when: Running housekeeping tasks—copy the command block you need and run it from your project root.

Operational playbook

Dump & restore data

python manage.py dumpdata > database.json
python manage.py loaddata database.json

Reset migrations safely

find . -path "*/migrations/*.py[c|o]" -delete
find . -path "*/migrations/*.py" -not -name "__init__.py" -delete
python manage.py makemigrations
python manage.py migrate

Code quality combo

python -m pip install --upgrade isort autoflake black flake8 ruff
autoflake -i -r --expand-star-imports --remove-all-unused-imports --remove-duplicate-keys --remove-unused-variables --exclude venv,env .
isort .
black .
flake8 --exclude venv,env
ruff check --fix .

Create timestamped fixtures

python manage.py dumpdata \
  --exclude auth.permission \
  --exclude contenttypes \
  --exclude admin.logentry \
  > "fixtures/$(date +'%Y_%m_%d_%H_%M').json"

Clear admin log entries

python manage.py shell
from django.contrib.admin.models import LogEntry
LogEntry.objects.all().delete()

Provision PostgreSQL

sudo -u postgres createdb project_db
sudo -u postgres createuser project_dbuser -P
sudo -u postgres psql
GRANT ALL PRIVILEGES ON DATABASE project_db TO project_dbuser;

.gitignore essentials

*.log
*.pot
*.pyc
__pycache__/
migrations/*.py
!migrations/__init__.py
local_settings.py
db.sqlite3*
media/
*.zip

Remember to document any project-specific commands in README.md so others (and future you) can follow along.

Hosting

Why it matters: Acts as a production checklist for DigitalOcean-style servers, ensuring nothing is missed during deploys. Copy it when: Bringing up a fresh droplet or VM—work through the numbered steps and tick them off as you go.

DigitalOcean server checklist

  1. Update the OS
sudo apt-get update && sudo apt-get upgrade -y
  1. Install Python tooling (see 01_setup.md for the full list)
sudo apt-get install -y python3 python3-venv python3-distutils python3-pip
  1. Create a deploy user
sudo adduser deploy
sudo usermod -aG sudo deploy
  1. Clone the project & set up virtualenv
sudo -u deploy git clone git@github.com:you/project.git /home/deploy/project
cd /home/deploy/project
python3 -m venv venv
source venv/bin/activate
pip install -r requirements.txt
  1. Configure Apache or Nginx + Gunicorn
  • Add ServerName 127.0.0.1 and WSGIPassAuthorization On to /etc/apache2/apache2.conf (or use the virtual host snippet in 26_apache_virtualhost.md).
  • Enable modules: a2enmod rewrite headers ssl proxy proxy_http wsgi.
  1. Collect static files & run migrations
python manage.py collectstatic --noinput
python manage.py migrate
  1. Issue TLS certificates
sudo certbot --apache -d example.com -d www.example.com
  1. Reload services
sudo systemctl daemon-reload
sudo systemctl restart apache2

Keep firewall rules tight (ufw allow "Apache Full") and monitor logs (/var/log/apache2/error.log) after each deploy.

Apache VirtualHost

Why it matters: Provides hardened Apache vhost snippets for both static hosting and mod_wsgi-powered Django apps. Copy it when: You are configuring Apache—paste the block that matches your deployment and swap domains/paths.

Apache virtual host snippets

Static site + HTTPS

<IfModule mod_ssl.c>
<VirtualHost *:443>
        ServerName domain.com
        ServerAlias www.domain.com
        DocumentRoot /home/srv/project

        <Directory "/home/srv/project">
                Options +Indexes +Includes +FollowSymLinks +MultiViews
                AllowOverride All
                Require all granted
                RewriteEngine On
                RewriteCond %{REQUEST_FILENAME} -f [OR]
                RewriteCond %{REQUEST_FILENAME} -d
                RewriteRule ^ - [L]
                RewriteRule ^ index.html [L]
        </Directory>

        Include /etc/letsencrypt/options-ssl-apache.conf
        SSLCertificateFile /etc/letsencrypt/live/domain.com/fullchain.pem
        SSLCertificateKeyFile /etc/letsencrypt/live/domain.com/privkey.pem
</VirtualHost>
</IfModule>

<VirtualHost *:80>
        ServerName domain.com
        ServerAlias www.domain.com
        DocumentRoot /home/srv/project
        RewriteEngine On
        RewriteRule ^ https://%{SERVER_NAME}%{REQUEST_URI} [END,NE,R=permanent]
</VirtualHost>

Django + mod_wsgi + SSL

<VirtualHost *:80>
        ServerName domain.com
        Redirect permanent / https://www.domain.com/
</VirtualHost>

<VirtualHost *:443>
        ServerName www.domain.com
        ServerAdmin webmaster@domain.com
        DocumentRoot /home/srv/project

        ErrorLog ${APACHE_LOG_DIR}/error.log
        CustomLog ${APACHE_LOG_DIR}/access.log combined

        Alias /static /home/srv/project/project/static
        <Directory /home/srv/project/project/static>
                Require all granted
        </Directory>

        Alias /media /home/srv/project/project/media
        <Directory /home/srv/project/project/media>
                Require all granted
        </Directory>

        <Directory /home/srv/project/project/project>
                <Files wsgi.py>
                        Require all granted
                </Files>
        </Directory>

        WSGIDaemonProcess project python-path=/home/srv/project/project python-home=/home/srv/project/venv
        WSGIProcessGroup project
        WSGIScriptAlias / /home/srv/project/project/project/wsgi.py

        Include /etc/letsencrypt/options-ssl-apache.conf
        SSLCertificateFile /etc/letsencrypt/live/domain.com/fullchain.pem
        SSLCertificateKeyFile /etc/letsencrypt/live/domain.com/privkey.pem
</VirtualHost>

Static Site VirtualHost

Why it matters: Summarizes the enable+SSL workflow for the static vhost so you don’t forget the a2ensite/Certbot sequence. Copy it when: Enabling a new site file—follow the steps verbatim, updating the domain names as needed.

  1. Create /etc/apache2/sites-available/domain.com.conf with the blocks from 26_apache_virtualhost.md.
  2. Test and enable the site:
sudo apachectl configtest
sudo a2ensite domain.com.conf
sudo systemctl reload apache2
  1. Issue SSL certificates:
sudo certbot --apache -d domain.com -d www.domain.com

When prompted, choose option 2 to redirect HTTP → HTTPS (recommended).

  1. Reload Apache to apply any changes:
sudo systemctl reload apache2

Custom Ordering

Why it matters: Shows how to override app/model order inside the admin, delivering a curated experience for staff. Copy it when: You want a friendlier admin landing page—copy the APP_ORDER, filter, and template overrides.

Custom admin ordering

By default Django sorts apps and models alphabetically. Use the pattern below to define your own order.

# settings.py
from collections import OrderedDict

APP_ORDER = OrderedDict(
        [
                ("app", ["Settings", "Vendor"]),
                ("web", ["Slider", "Category", "Subcategory", "Product", "Order", "BulkOrder", "ServiceOrder", "Contact"]),
        ]
)
# templatetags/tags.py

from django import template
from django.conf import settings

register = template.Library()


def pop_and_get_app(items, key, value):
        for index, item in enumerate(items):
                if item[key] == value:
                        return items.pop(index)
        return None


@register.filter
def sort_apps(apps):
        ordered_apps = []
        for app_label in settings.APP_ORDER.keys():
                obj = pop_and_get_app(apps, "app_label", app_label)
                if obj:
                        ordered_apps.append(obj)
        apps = ordered_apps + apps
        for app in apps:
                models = app.get("models", [])
                desired_models = settings.APP_ORDER.get(app.get("app_label"), [])
                ordered_models = []
                for model_name in desired_models:
                        obj = pop_and_get_app(models, "object_name", model_name)
                        if obj:
                                ordered_models.append(obj)
                app["models"] = ordered_models + models
        return apps
{# templates/admin/app_list.html #}
{% load i18n tags %}

{% if app_list %}
    {% for app in app_list|sort_apps %}
        <div class="app-{{ app.app_label }} module{% if app.app_url in request.path %} current-app{% endif %}">
            <table>
                <caption>
                    <a href="{{ app.app_url }}" class="section" title="{% blocktranslate with name=app.name %}Models in the {{ name }} application{% endblocktranslate %}">{{ app.name }}</a>
                </caption>
                {% for model in app.models %}
                    <tr class="model-{{ model.object_name|lower }}{% if model.admin_url in request.path %} current-model{% endif %}">
                        {% if model.admin_url %}
                            <th scope="row"><a href="{{ model.admin_url }}"{% if model.admin_url in request.path %} aria-current="page"{% endif %}>{{ model.name }}</a></th>
                        {% else %}
                            <th scope="row">{{ model.name }}</th>
                        {% endif %}

                        {% if model.add_url %}
                            <td><a href="{{ model.add_url }}" class="addlink">{% translate "Add" %}</a></td>
                        {% else %}
                            <td></td>
                        {% endif %}

                        {% if model.admin_url and show_changelinks %}
                            {% if model.view_only %}
                                <td><a href="{{ model.admin_url }}" class="viewlink">{% translate "View" %}</a></td>
                            {% else %}
                                <td><a href="{{ model.admin_url }}" class="changelink">{% translate "Change" %}</a></td>
                            {% endif %}
                        {% elif show_changelinks %}
                            <td></td>
                        {% endif %}
                    </tr>
                {% endfor %}
            </table>
        </div>
    {% endfor %}
{% else %}
    <p>{% translate "You don’t have permission to view or edit anything." %}</p>
{% endif %}

Pyproject Config

Why it matters: Locks in formatter and linter configs so the whole team shares the same style rules. Copy it when: You’re standardizing tooling—drop this pyproject.toml into your repo or merge the sections you need.

[tool.black]
line-length = 120
target-version = ["py38", "py39", "py310", "py311"]
skip-string-normalization = true

[tool.isort]
profile = "black"
line_length = 120
combine_as_imports = true
include_trailing_comma = true

[tool.ruff]
select = ["E", "F", "W", "I"]
ignore = []
line-length = 120
target-version = "py39"

[tool.ruff.per-file-ignores]
"*/migrations/*.py" = ["E501"]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment