Skip to content

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 response

Key notes:

  • wsgiref (Django’s development server) handles low concurrency — max ~1000 simultaneous connections. In production, replace it with uwsgi or gunicorn.
  • WSGI is the protocol specification; wsgiref and uwsgi are 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

FeatureDjango 1.xDjango 2.x / 3.x
URL functionurl()path()
Regex supportYes — url(r'^index/', index)No — path('index/', index)
Regex equivalentre_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:

ConverterDescription
strDefault — matches any non-empty string that does not contain /
intMatches zero or positive integers; passes an int to the view
slugMatches ASCII letters, numbers, hyphens, and underscores
uuidMatches a formatted UUID, e.g. 075194d3-6885-417e-a8a8-6c931e272f00
pathMatches 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=False
Get comfortable reading Django’s source code — it’s often the fastest way to pin down exactly where an exception is coming from.

Form 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 string
print(request.path)            # /app01/ab_file/
print(request.path_info)       # /app01/ab_file/
print(request.get_full_path()) # /app01/ab_file/?username=jason
Last updated on