Django Supplementary Notes
This article covers several supplementary Django topics: the full request/response lifecycle, how to apply decorators to class-based views, running Django code from external scripts, the key differences between Django 1.x, 2.x, and 3.x, and a few view-layer and request-object notes worth keeping in one place.
Django Request/Response Lifecycle
The lifecycle of a Django request looks like this:
Browser
└─ sends HTTP request
│
▼
WSGI Gateway Interface (wsgiref in development, uwsgi in production)
├─ parses the raw HTTP request and wraps it into a Python object
└─ packages the response back into HTTP before sending it to the browser
│
▼
Django Backend
├─ 1. Middleware (request phase) — acts as a global gatekeeper
├─ 2. urls.py — URL routing: matches URL to view function
├─ 3. views.py — view layer: main business logic
├─ 4. templates/ — template layer: HTML rendering
├─ 5. models.py — model layer: ORM database interaction
└─ Middleware (response phase) — processes the outgoing response
│
▼
Browser receives responseKey notes:
wsgiref(Django’s development server) handles low concurrency — max ~1000 simultaneous connections. In production, replace it withuwsgiorgunicorn.- WSGI is the protocol specification;
wsgirefanduwsgiare implementations of that protocol. - A cache layer (Redis/Memcached) is often added between the view and the database to serve pre-computed responses and reduce database load.
Applying Decorators to Class-Based Views
Django discourages applying decorators directly to CBV methods — use method_decorator instead:
from django.views import View
from django.utils.decorators import method_decorator
from django.http import HttpResponse
def login_auth(func):
def inner(request, *args, **kwargs):
if request.COOKIES.get('username'):
return func(request, *args, **kwargs)
from django.shortcuts import redirect
return redirect('/login/')
return inner
# Method 1: apply the decorator directly to a specific method
class MyLogin(View):
@method_decorator(login_auth) # applies only to get()
def get(self, request):
return HttpResponse('GET request')
def post(self, request):
return HttpResponse('POST request')
# Method 2: apply at the class level, targeting a specific HTTP method by name
@method_decorator(login_auth, name='get')
@method_decorator(login_auth, name='post')
class MyLogin(View):
def get(self, request):
return HttpResponse('GET request')
def post(self, request):
return HttpResponse('POST request')
# Method 3: apply to dispatch() — affects ALL methods in the class
class MyLogin(View):
@method_decorator(login_auth)
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)
def get(self, request):
return HttpResponse('GET request')
def post(self, request):
return HttpResponse('POST request')Calling Django from an External Script
To use Django models or settings in a standalone Python script (outside the normal manage.py workflow), initialize Django’s environment first:
import os
import django
# Point to your project's settings module
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "myproject.settings")
django.setup()
# Now you can use Django ORM, models, etc.
from app01 import models
models.Book.objects.create(
title='New Book',
price=200,
)This is useful for data-import scripts, scheduled jobs (cron), or one-off maintenance tasks.
Django Version Differences
URL Routing: 1.x vs. 2.x/3.x
| Feature | Django 1.x | Django 2.x / 3.x |
|---|---|---|
| URL function | url() | path() |
| Regex support | Yes — url(r'^index/', index) | No — path('index/', index) |
| Regex equivalent | — | re_path(r'^index/', index) |
# Django 1.x
from django.conf.urls import url
urlpatterns = [
url(r'^login/', login),
]
# Django 2.x / 3.x
from django.urls import path, re_path
urlpatterns = [
path('login/', login), # no regex
re_path(r'^login/', login), # regex — equivalent to 1.x url()
]Path Converters (2.x / 3.x)
path() supports five built-in type converters:
| Converter | Description |
|---|---|
str | Default — matches any non-empty string that does not contain / |
int | Matches zero or positive integers; passes an int to the view |
slug | Matches ASCII letters, numbers, hyphens, and underscores |
uuid | Matches a formatted UUID, e.g. 075194d3-6885-417e-a8a8-6c931e272f00 |
path | Matches any non-empty string, including / |
from django.urls import path
from app01 import views
urlpatterns = [
path('index/<int:id>/', views.index),
# The <int:id> segment is extracted, converted to int, and passed as keyword arg "id"
]
def index(request, id):
print(id, type(id)) # e.g. 5 <class 'int'>
return HttpResponse('index')Custom Path Converters
# app01/converters.py
class MonthConverter:
regex = r'\d{2}' # must be named "regex"
def to_python(self, value):
return int(value)
def to_url(self, value):
return value # must match the regex (two digits)
# urls.py
from django.urls import path, register_converter
from app01.converters import MonthConverter
register_converter(MonthConverter, 'mon') # register with the name 'mon'
urlpatterns = [
path('articles/<int:year>/<mon:month>/<slug:other>/', views.article_detail, name='aaa'),
]ForeignKey on_delete (2.x / 3.x)
In Django 1.x, ForeignKey cascaded deletes by default. Starting in 2.x, on_delete is a required parameter:
# Django 1.x — worked without on_delete
models.ForeignKey(to='Publish')
# Django 2.x / 3.x — on_delete is required
models.ForeignKey(to='Publish', on_delete=models.CASCADE)
# Common on_delete options:
# models.CASCADE — delete child rows when the parent is deleted
# models.SET_NULL — set FK to NULL (requires null=True)
# models.SET_DEFAULT — set FK to default value
# models.PROTECT — raise ProtectedError, preventing deletion
# models.DO_NOTHING — do nothing (risks integrity errors)View Layer Notes
HttpResponse— returns a plain string.render— returns an HTML page, and lets you pass data to the template before it reaches the browser.redirect— issues a redirect.
Every view function must return an HttpResponse object (or a subclass of it) — Django enforces this itself:
The view app01.views.index didn't return an HttpResponse object. It returned None instead.render()’s internals are just Template + Context:
from django.template import Template, Context
res = Template('<h1>{{ user }}</h1>')
con = Context({'user': {'username': 'jason', 'password': 123}})
ret = res.render(con)
print(ret)
return HttpResponse(ret)JsonResponse Object
JSON is the standard bridge format for exchanging data between frontend and backend, since it isn’t tied to any particular language:
JavaScript Python
JSON.stringify() <-> json.dumps()
JSON.parse() <-> json.loads()import json
from django.http import JsonResponse
def ab_json(request):
user_dict = {'username': 'jason', 'password': '123', 'hobby': 'girl'}
l = [111, 222, 333, 444, 555]
# Option 1: serialize manually and return as a plain string
# json_str = json.dumps(user_dict, ensure_ascii=False)
# return HttpResponse(json_str)
# Option 2: use JsonResponse directly
# return JsonResponse(user_dict, json_dumps_params={'ensure_ascii': False})
# To serialize a non-dict object (e.g. a list), set safe=False
# return JsonResponse(l, safe=False)
# By default JsonResponse can only serialize dicts; anything else requires safe=FalseForm File Upload and Backend Handling
"""
To upload a file through an HTML form:
1. method must be POST
2. enctype must be multipart/form-data
"""
def ab_file(request):
if request.method == 'POST':
# request.POST only contains ordinary key/value data -- not files
print(request.FILES) # retrieve uploaded file data
# <MultiValueDict: {'file': [<InMemoryUploadedFile: photo.jpg (image/jpeg)>]}>
file_obj = request.FILES.get('file') # the file object
print(file_obj.name)
with open(file_obj.name, 'wb') as f:
for chunk in file_obj.chunks(): # chunks() reads the file in pieces (recommended)
f.write(chunk)
return render(request, 'form.html')request Object Methods
request.method # HTTP method, always uppercase
request.POST # POST data (excludes files)
request.GET # query-string data
request.FILES # uploaded files
request.body # raw binary request body
request.path # URL path, without the query string
request.path_info # same as request.path in most cases
request.get_full_path() # full path including the query stringprint(request.path) # /app01/ab_file/
print(request.path_info) # /app01/ab_file/
print(request.get_full_path()) # /app01/ab_file/?username=jason