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.
- 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.
- Why Django?
- Quick start
- Core building blocks
- Advanced recipes
- Plugins
- Optimization & deployment
- Keep going
- 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.
| # | 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.envand load them viapython-decoupleas shown in the sample settings.
| 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.
| 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.
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.
| 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. |
- 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.
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.
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.
#!/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!"
chmod +x install_dependencies.sh
sudo ./install_dependencies.sh
sudo add-apt-repository ppa:webupd8team/atom
sudo apt-get update
sudo apt-get install atom -ysudo 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 -ywget -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 -ysudo apt install git -y
git config --global user.name "Name"
git config --global user.email "example@gmail.com"wget https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb
sudo dpkg -i google-chrome-stable_current_amd64.debWhy 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
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"Why it matters: Centralizes secrets and deployment toggles so
.envstays 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
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.pythat 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"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)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"),
]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>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
Blogmodel 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 %}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
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.")},
}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:
passWhy 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})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"),
]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>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>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",
],
},
},
]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.
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.
- Install
pip install django-registration-redux- Enable the app
INSTALLED_APPS = [
# …
"registration",
]- 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)- 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")),
]- 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 #}
- Install
pip install django-versatileimagefield- 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,
}- Use in models
from versatileimagefield.fields import VersatileImageField
class Customer(models.Model):
photo = VersatileImageField("Photo", upload_to="customers/", blank=True, null=True)- Migrate & render
python manage.py makemigrations
python manage.py migrateTemplate helpers:
<img src="{{ customer.photo.crop.200x200 }}" alt="">
<img src="{{ customer.photo.thumbnail.600x600 }}" alt="">
- Install
pip install django-import-export- Enable
INSTALLED_APPS = [
# …
"import_export",
]- 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 = RegistrationResourceRun python manage.py createsuperuser, log into /admin, and you will see Import/Export buttons plus reusable actions.
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.pyand URL wiring.
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.
- Enable apps and the Sites framework:
INSTALLED_APPS = [
# ...
"django.contrib.sites",
"django.contrib.sitemaps",
]
SITE_ID = 1- Ensure you have a
DjangoTemplatesbackend withAPP_DIRS=True(default).
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,
}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.
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.
Django ships with batteries-included decorators and it’s easy to craft your own. This cheat sheet highlights common patterns.
from django.contrib.auth.decorators import login_required
@login_required
def my_view(request):
...from django.views.decorators.http import require_http_methods
@require_http_methods(["GET", "POST"])
def my_view(request):
...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 %}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):
...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_decoratorfrom 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 _innerfrom 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 wrapimport 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 wrappedWhy 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" }));
});
});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.
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 |
| 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". |
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.
- Add the app:
INSTALLED_APPS = [
# ...
"django.contrib.admindocs",
]
- Install docutils (required to render reStructuredText):
python -m pip install docutils
- Wire URLs before the default admin pattern:
urlpatterns = [
path("admin/doc/", include("django.contrib.admindocs.urls")),
path("admin/", admin.site.urls),
]
- Optional: enable
django.contrib.admindocs.middleware.XViewMiddlewareto 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.
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.
python manage.py dumpdata > database.json
python manage.py loaddata database.json
find . -path "*/migrations/*.py[c|o]" -delete
find . -path "*/migrations/*.py" -not -name "__init__.py" -delete
python manage.py makemigrations
python manage.py migrate
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 .
python manage.py dumpdata \
--exclude auth.permission \
--exclude contenttypes \
--exclude admin.logentry \
> "fixtures/$(date +'%Y_%m_%d_%H_%M').json"
python manage.py shell
from django.contrib.admin.models import LogEntry
LogEntry.objects.all().delete()
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;
*.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.
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.
- Update the OS
sudo apt-get update && sudo apt-get upgrade -y
- Install Python tooling (see
01_setup.mdfor the full list)
sudo apt-get install -y python3 python3-venv python3-distutils python3-pip
- Create a deploy user
sudo adduser deploy
sudo usermod -aG sudo deploy
- 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
- Configure Apache or Nginx + Gunicorn
- Add
ServerName 127.0.0.1andWSGIPassAuthorization Onto/etc/apache2/apache2.conf(or use the virtual host snippet in26_apache_virtualhost.md). - Enable modules:
a2enmod rewrite headers ssl proxy proxy_http wsgi.
- Collect static files & run migrations
python manage.py collectstatic --noinput
python manage.py migrate
- Issue TLS certificates
sudo certbot --apache -d example.com -d www.example.com
- 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.
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.
<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>
<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>
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.
- Create
/etc/apache2/sites-available/domain.com.confwith the blocks from26_apache_virtualhost.md. - Test and enable the site:
sudo apachectl configtest
sudo a2ensite domain.com.conf
sudo systemctl reload apache2
- Issue SSL certificates:
sudo certbot --apache -d domain.com -d www.domain.com
When prompted, choose option 2 to redirect HTTP → HTTPS (recommended).
- Reload Apache to apply any changes:
sudo systemctl reload apache2
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.
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 %}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.tomlinto 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"]