Django Master Hub + Recipes (Python Web)
Project structure, models/ORM, forms/CBVs, templates, auth, DRF, settings/env, static/media, testing, deployment—copy‑paste ready.
Quickstart & Project Structure
# Install
pip install "Django>=4.2" dj-database-url python-dotenv
# Create project & app
django-admin startproject config .
python manage.py startapp core
# Project layout (suggested)
.
├─ config/ # settings, urls, wsgi/asgi
│ ├─ settings.py
│ ├─ urls.py
│ ├─ asgi.py / wsgi.py
├─ core/ # your app(s)
│ ├─ models.py, views.py, urls.py, forms.py, tests.py
├─ templates/ # global templates
├─ static/ # global static
├─ media/ # uploaded files
├─ .env # environment variables (never commit)
└─ manage.py
Models, Migrations, ORM
# core/models.py
from django.db import models
from django.contrib.auth import get_user_model
User = get_user_model()
class Post(models.Model):
title = models.CharField(max_length=200, db_index=True)
slug = models.SlugField(unique=True)
body = models.TextField()
author = models.ForeignKey(User, on_delete=models.CASCADE, related_name="posts")
created = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(auto_now=True)
published = models.BooleanField(default=False)
class Meta:
ordering = ["-created"]
def __str__(self): return self.title
# Migrations
python manage.py makemigrations
python manage.py migrate
# ORM examples
from core.models import Post
Post.objects.filter(published=True).select_related("author")[:10]
Post.objects.create(title="Hello", slug="hello", body="...", author=request.user)
Views & Class‑Based Views (CBVs)
# core/urls.py
from django.urls import path
from . import views
urlpatterns = [
path("", views.HomeView.as_view(), name="home"),
path("post/<slug:slug>/", views.PostDetail.as_view(), name="post-detail"),
]
# config/urls.py
from django.urls import path, include
urlpatterns = [ path("", include("core.urls")) ]
# core/views.py
from django.views.generic import TemplateView, DetailView, ListView
from .models import Post
class HomeView(ListView):
model = Post
queryset = Post.objects.filter(published=True)
template_name = "home.html"
context_object_name = "posts"
paginate_by = 10
class PostDetail(DetailView):
model = Post
template_name = "post_detail.html"
Templates & Tags
# templates/base.html
<!doctype html>
<html>
<head> <title>{% block title %}Site{% endblock %}</title> </head>
<body>
<header><a href="/">Home</a></header>
<main>{% block content %}{% endblock %}</main>
</body>
</html>
# templates/home.html
{% extends "base.html" %}
{% block title %}Home{% endblock %}
{% block content %}
{% for post in posts %}
<article>
<h2><a href="{% url 'post-detail' slug=post.slug %}">{{ post.title }}</a></h2>
<p>{{ post.created|date:"Y-m-d" }} by {{ post.author }}</p>
</article>
{% empty %}
<p>No posts yet.</p>
{% endfor %}
{% if is_paginated %}
{% if page_obj.has_previous %}<a href="?page={{ page_obj.previous_page_number }}">Prev</a>{% endif %}
Page {{ page_obj.number }} / {{ paginator.num_pages }}
{% if page_obj.has_next %}<a href="?page={{ page_obj.next_page_number }}">Next</a>{% endif %}
{% endif %}
{% endblock %}
Forms & Validation
# core/forms.py
from django import forms
from .models import Post
class PostForm(forms.ModelForm):
class Meta:
model = Post
fields = ["title","slug","body","published"]
def clean_title(self):
t = self.cleaned_data["title"]
if len(t) < 3:
raise forms.ValidationError("Title too short")
return t
# core/views.py (CreateView)
from django.views.generic import CreateView
from django.urls import reverse_lazy
from .forms import PostForm
class PostCreate(CreateView):
form_class = PostForm
template_name = "post_form.html"
success_url = reverse_lazy("home")
Auth: Users, Permissions
# URLs: login/logout
from django.urls import path
from django.contrib.auth import views as auth_views
urlpatterns = [
path("login/", auth_views.LoginView.as_view(template_name="login.html"), name="login"),
path("logout/", auth_views.LogoutView.as_view(), name="logout"),
]
# Require login on CBV
from django.contrib.auth.mixins import LoginRequiredMixin, PermissionRequiredMixin
class DashboardView(LoginRequiredMixin, TemplateView):
template_name = "dashboard.html"
class ManagePosts(PermissionRequiredMixin, ListView):
permission_required = "core.change_post"
model = Post
Django REST Framework (DRF)
# Install
pip install djangorestframework
# settings.py
INSTALLED_APPS = ["rest_framework", "core", ...]
# core/api.py
from rest_framework import serializers, viewsets, routers
from .models import Post
class PostSerializer(serializers.ModelSerializer):
class Meta: model = Post; fields = ["id","title","slug","body","author","created","published"]
class PostViewSet(viewsets.ReadOnlyModelViewSet):
queryset = Post.objects.filter(published=True)
serializer_class = PostSerializer
# config/urls.py
from django.urls import path, include
from rest_framework.routers import DefaultRouter
from core.api import PostViewSet
router = DefaultRouter(); router.register("posts", PostViewSet)
urlpatterns = [ path("api/", include(router.urls)) ]
Custom Middleware
# core/middleware.py
import time
class TimingMiddleware:
def __init__(self, get_response): self.get_response = get_response
def __call__(self, request):
t0 = time.time()
resp = self.get_response(request)
resp["X-Render-Time"] = f"{(time.time()-t0)*1000:.1f}ms"
return resp
# settings.py
MIDDLEWARE += ["core.middleware.TimingMiddleware"]
Settings, Environments, .env
# settings.py (snippet)
import os
from pathlib import Path
import dj_database_url
from dotenv import load_dotenv
load_dotenv()
BASE_DIR = Path(__file__).resolve().parent.parent
SECRET_KEY = os.getenv("DJANGO_SECRET_KEY","change-me")
DEBUG = os.getenv("DEBUG","0") == "1"
ALLOWED_HOSTS = os.getenv("ALLOWED_HOSTS","localhost,127.0.0.1").split(",")
DATABASES = {"default": dj_database_url.parse(os.getenv("DATABASE_URL","sqlite:///db.sqlite3"))}
STATIC_URL = "/static/"
STATIC_ROOT = BASE_DIR / "staticfiles"
MEDIA_URL = "/media/"
MEDIA_ROOT = BASE_DIR / "media"
CSRF_TRUSTED_ORIGINS = os.getenv("CSRF_TRUSTED","").split(",") if os.getenv("CSRF_TRUSTED") else []
Static & Media Files
# Development
python manage.py collectstatic # for production
# URL config
from django.conf import settings
from django.conf.urls.static import static
urlpatterns = [...] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
Testing & Factories
# Install
pip install pytest pytest-django model-bakery
# pytest.ini
[pytest]
DJANGO_SETTINGS_MODULE = config.settings
python_files = tests.py test_*.py
# tests/test_post.py
import pytest
from model_bakery import baker
from django.urls import reverse
@pytest.mark.django_db
def test_home_lists_posts(client):
baker.make("core.Post", published=True, _quantity=3)
resp = client.get(reverse("home"))
assert resp.status_code == 200
assert "posts" in resp.context
Deployment (Gunicorn/Nginx)
# Gunicorn
pip install gunicorn
gunicorn config.wsgi:application --bind 0.0.0.0:8000 --workers 3
# Systemd service (snippet)
[Service]
User=www-data
WorkingDirectory=/srv/app
ExecStart=/srv/app/.venv/bin/gunicorn config.wsgi:application -b 127.0.0.1:8000 -w 3
Environment="DJANGO_SECRET_KEY=..."
Environment="DATABASE_URL=postgres://..."
Restart=always
Troubleshooting & FAQ
Static files 404 in production
Run collectstatic; configure STATIC_ROOT; serve via WhiteNoise/CDN and correct URL paths.
CSRF verification failed
Ensure CSRF cookie is sent; add domain to CSRF_TRUSTED_ORIGINS; include {% csrf_token %} in forms.
N+1 query performance
Add select_related/prefetch_related; use Django Debug Toolbar in dev to spot issues.
DisallowedHost
Add your domain to ALLOWED_HOSTS; check reverse proxy Host header.
Media uploads not saving
Verify MEDIA_ROOT permissions and model FileField upload_to; ensure request.FILES used in form.
Migrations conflicts
Rebase/merge carefully; run makemigrations per app; resolve conflicting dependencies then migrate.
FAQ
Custom user model—when? Start with a custom user immediately if you expect to extend fields later; changing mid‑project is painful.
Function‑ vs class‑based views? CBVs speed up CRUD and list/detail patterns; FBVs for bespoke flows or when clarity matters more than reuse.
Monolith vs multiple apps? Keep a single project, multiple small apps by domain (accounts, blog, billing). Aim for loose coupling and clear boundaries.
© 2025 Pythoneo · Designed to be highly linkable: comprehensive, evergreen, copy‑paste friendly, with testing and deployment built‑in.