Django Master Class: Three-Day Classroom Course
A three-day classroom master class for experienced Django developers, taught on the code of a production Django platform. Over 18 hours, students take apart the parts of Smarter that go past Django’s defaults: settings validated before Django reads them, a startup sequence that copes without a database, ten custom middleware classes, many hosts in one site, an ORM extended for resource ownership, signals, caching, Celery, async streaming over Redis publish-subscribe, API contracts in Pydantic, OAuth, template tags that host React apps, and the tests and hooks that keep it all honest. Each topic is a one-hour module, a 30-minute lecture and a 30-minute lab on the student’s own running copy of Smarter. Day 1 covers configuration, startup and the request, day 2 data, asynchrony and Redis, and day 3 APIs, identity, the front end and quality. See Course Formats and Grading for the grading scale and the course policies, and Django Master Class for an overview of the subject.
At a Glance
Students |
Professional Python developers with prior Django experience who maintain, or will maintain, a large Django codebase. |
Format |
Three days of six hours of instruction, each day six one-hour modules. Breaks and lunch are not counted. |
Deployment |
Smarter from source on each student’s laptop, under Docker Compose, working before day 1. |
Workload |
About 2 hours each evening for the day’s assignment, and 8 to 12 hours for the capstone in the week after the course. |
Cost |
Free, plus an LLM provider API key for the modules that send prompts: a few dollars at most. |
Learning Objectives
By the end of the course, students can:
Manage a Django project’s configuration from the environment through a validated settings class, in keeping with the twelve factors.
Explain Django’s startup sequence and the request’s path through middleware, and write middleware, host routing and feature flags that fit into them safely.
Extend the ORM with abstract models, managers and querysets, including ownership and sharing of resources between accounts.
Choose between signals, caching, Celery tasks and async views for a given job, and implement each correctly, including invalidation, routing and streaming over Redis publish-subscribe.
Build APIs with Django REST Framework, Pydantic contracts and custom authentication, and add OAuth sign-in.
Write template tags, serve complex static assets such as React apps, and style pages with Bootstrap or Tailwind.
Keep a large codebase healthy with tests, pre-commit hooks and coverage targets.
Technical Prerequisites
Required: proficient Python programming.
Required: prior experience building a Django application: models, views, templates, URLs and the admin.
Required: the Model-View-Template pattern, LAMP and similar web application stacks.
Required: working in a terminal with common Linux commands, and Git.
Required: familiarity with The Twelve-Factor App.
Required before day 1: Docker, and Smarter running from source as described in Developer Setup Guide. Send students the instructions at least a week ahead, and ask for a screenshot of the running web console.
Helpful: Redis, Celery, and reading React and TypeScript.
Schedule
Each module is described under The Modules.
Day |
Morning (3 hours) |
Afternoon (3 hours) |
Evening |
|---|---|---|---|
1 |
Modules 1 to 3: 12-Factor and settings; the startup sequence; logging. |
Modules 4 to 6: the request and middleware chain; writing middleware; many hosts and Waffle. |
Assignment 1, due at the start of day 2. |
2 |
Modules 7 to 9: customizing the ORM; the user and ownership; signals and receivers. |
Modules 10 to 12: caching complex objects; Celery and Beat; ASGI and publish-subscribe. |
Assignment 2, due at the start of day 3. |
3 |
Modules 13 to 15: API best practices; OAuth; mixins, enums and data helpers. |
Modules 16 to 18: template tags, static assets and styles; testing and quality; the terminal window. |
Assignment 3, due two days after the course. Capstone choice. |
Each day opens with 10 minutes of questions on the previous evening’s assignment, taken from the first lecture’s time. The capstone is due one week after the course.
The Modules
Each module is a 30-minute lecture followed by a lab of about 30 minutes. Labs are done on each student’s own clone of Smarter, running under Docker Compose, on a branch per lab. The file references are listed in full under The Modules in the Source Code.
# |
Lecture |
Lab |
|---|---|---|
1 |
Django and 12-Factor; managing settings. The twelve factors as they apply to Django.
Why Smarter reads every setting from the environment into a Pydantic |
Add a typed, validated setting to |
2 |
The startup sequence. What runs, in order, from |
Add logging to an app’s |
3 |
Getting more out of Python logging. Loggers, handlers, filters and formatters; one logger factory; verbose logging switched on per subsystem with Waffle, not by editing code; request context added to every record by middleware; logs to standard output. ADR 011. |
Add a Waffle-switched logger to a module, turn it on in the Django admin, and filter its records by user with the log context middleware. |
4 |
The request object and the middleware chain. How each middleware sees the request on
the way in and the response on the way out, in |
Turn on the diagnostic middleware, then add a middleware that prints the request’s attributes at three points in the chain, and explain each difference. |
5 |
Writing middleware. Function and class middleware, |
Write a middleware that adds a request id to the request and the response, and logs it.
Test it with Django’s |
6 |
Many hosts in one site; Waffle. |
Add a host pattern for a new subdomain with its own URLconf, and gate one of its views with a new Waffle switch. |
7 |
Customizing the ORM. Abstract base models, custom managers and querysets, chaining
queryset methods, typing generic managers, |
Add a queryset method to a model’s manager that chains with |
8 |
The user and resource ownership. A custom |
Create a model on |
9 |
Signals and receivers. Built-in model signals, custom signals, |
Declare a custom signal, send it from a view, and write two receivers: one that logs, and one that invalidates a cache entry. |
10 |
Caching complex objects. What can be pickled and what should not be; caching
querysets, model instances and Pydantic models; deterministic keys from arguments; caching
|
Cache an expensive query with |
11 |
Celery and Celery Beat. Brokers, workers, queues and routing; retries and idempotency;
a Beat schedule; why |
Write a task that aggregates rows for an account, route it to the right queue, schedule it in Beat, and watch it run in the worker’s logs. |
12 |
ASGI and publish-subscribe. WSGI against ASGI, uvicorn, Channels’ |
Write an async view that streams Server-Sent Events from a Redis channel, and publish to that channel from the Celery task of Module 11. |
13 |
API best practices. Django REST Framework viewsets and serializers, Pydantic models as the API contract, a published JSON Schema, versioning, and custom authentication with hashed API keys in an authentication class and a middleware. ADR 023, ADR 031. |
Add an endpoint whose response is built from a Pydantic model and dumped to JSON,
authenticated by API key. Call it with |
14 |
Working with OAuth. The OAuth 2.0 authorization code flow, Python Social Auth’s
backends and pipeline, adding Smarter’s own steps to the pipeline, handling its
exceptions in middleware, and keeping client secrets in |
Register a GitHub OAuth app for |
15 |
Mixins, enums and Django’s data helpers. Smarter’s helper mixins and enumerated
classes; |
Write a validator, with tests, for a field that Module 8’s model will store, and use an enumerated class for its choices. |
16 |
Template tags, static assets and styles. Simple and inclusion tags; how a template tag
reads a React app’s Vite manifest and renders its scripts and root element;
|
Write a template tag that renders an account’s resource count as a Bootstrap badge, and use it on a console page. |
17 |
Testing, pre-commit and coverage. Test base classes, fixtures built once, cleanup without transaction rollback, mocking Celery and outside services, the pre-commit hooks (black, isort, flake8, bandit and more), and coverage targets per app. ADR 013, ADR 028. |
Install the pre-commit hooks, run them on the course branch and fix what they find, then raise the coverage of the Module 8 model and validator to 90%. |
18 |
Putting it all together: the terminal window. The web console’s terminal window, from the setting and the Waffle switch that enable it, through the template tag and the React app, to the logging handler that publishes each record to Redis and the view that streams it to the browser. |
Add a filter to the terminal window’s stream, for example by logger name, from the query string through the view, with a test. |
The Modules in the Source Code
Every lecture, lab and assignment in this course rests on the files below. Every reference is to
the main branch of github.com/smarter-sh/smarter,
so that students can open each one during class. Python paths are under smarter/smarter/.
Configuration and Startup
Module |
What to read |
Where to look |
|---|---|---|
|
Every setting is read from the environment, validated and typed by a Pydantic |
common/conf/settings.py, common/conf/env.py, common/conf/defaults.py, settings/, .env.example |
|
Every app’s |
apps/dashboard/apps.py,
lib/django/waffle/ready.py,
lib/cache/lazy_cache.py,
common/mixins/helper_mixin.py
( |
|
A logger factory, loggers switched on per subsystem by Waffle, a filter, a middleware that adds the user to every record, and a handler that publishes records to Redis. ADR 011. |
lib/logging/
( |
The Request
Module |
What to read |
Where to look |
|---|---|---|
|
The |
settings/base.py
( |
|
A shared base class, replacements for Django’s CORS, CSRF and security middleware, an API key authentication middleware, and middleware that blocks probes for sensitive files and bursts of 404s. |
lib/django/middleware/, common/mixins/middleware_mixin.py, lib/drf/middleware.py, apps/llmclient/middleware/security.py |
|
|
hosts.py,
urls/,
lib/django/waffle/
( |
Data
Module |
What to read |
Where to look |
|---|---|---|
|
Abstract base models for timestamps and metadata (name, description, version, tags and annotations), and generic querysets and managers whose methods return the subclass’s own types. ADR 017. |
lib/django/models/
( |
|
Smarter keeps Django’s |
apps/account/models/user_profile.py,
apps/account/models/metadata_with_ownership.py,
apps/account/mixins.py,
common/enum.py
( |
|
Every app declares its own signals in |
apps/llmclient/signals.py, apps/llmclient/receivers.py, lib/drf/receivers.py, lib/journal/receivers.py |
Asynchrony and Redis
Module |
What to read |
Where to look |
|---|---|---|
|
A decorator that builds a deterministic key from a function’s arguments, caches |
lib/cache/
( |
|
One Celery app, two queues with a worker each, and a Beat schedule that names each task’s queue. ADR 009, ADR 030. |
lib/celery_conf/,
workers/
( |
|
The ASGI application routes HTTP and WebSockets. A prompt runs in a worker thread while an
async generator streams its progress as Server-Sent Events, forwarded from signals through
a |
asgi.py, consumers.py, apps/prompt/progress.py, lib/logging/redis_log_handler.py, terminal_emulator/api/streams.py |
APIs and Identity
Module |
What to read |
Where to look |
|---|---|---|
|
A |
lib/drf/
( |
|
Google and GitHub sign-in with Python Social Auth, a pipeline with Smarter’s own steps for creating users, copying their details and redirecting inactive accounts, custom backends, and a middleware for the errors that the pipeline raises. |
settings/base.py
( |
|
Mixins shared by views, models and middleware; enumerated classes; a singleton metaclass; camelCase and snake_case conversion; validators; read-only YAML and CSV files; and time zone aware timestamps. |
common/mixins/, common/enum.py, common/utils/conversion.py, lib/django/validators.py, common/utils/file_handlers.py, lib/django/models/timestamped_model.py |
Front End, Quality and the Whole
Module |
What to read |
Where to look |
|---|---|---|
|
A template tag manager that reads a React app’s Vite manifest and renders its scripts and root element; one small tag per app; the web console’s Keen theme, built on Bootstrap; and Tailwind in the Smarter Chat package. ADR 019, ADR 025, ADR 026. |
lib/django/templatetags/, apps/dashboard/templatetags/, templates/, smarter/react/packages/ |
|
Test base classes without transaction rollback, an in-memory infrastructure provider, pre-commit hooks as the lint standard, and coverage targets per app. ADR 013, ADR 028. |
lib/unittest/, apps/infrastructure/providers/memory.py, .pre-commit-config.yaml, Makefile, .github/workflows/test.yml |
|
A page gated by a setting and a Waffle switch, a template tag that hosts a React app, a login-protected view that streams from an async generator subscribed to a Redis channel, a logging handler that publishes to that channel, and the React component that renders the stream. |
apps/dashboard/views/terminal_emulator/, apps/dashboard/templatetags/react_terminal_emulator.py, smarter-terminal-emulator |
Grading
Component |
Weight |
|---|---|
Graded assignments |
45% (3 × 15%) |
Participation (labs and questions) |
10% |
Capstone project |
45% |
The grading scale, the rubric levels and the course policies are those of Course Formats and Grading.
Graded Assignments
Each assignment builds on the day’s labs, on the student’s branch of Smarter, and is submitted as a pull request against their own fork.
Assignment 1: Configuration and the Request
Students add a middleware that enforces a per-account request limit read from a new, validated
setting in smarter_settings. A Waffle switch turns it on, a Waffle-switched logger records
each refusal, and the middleware runs only for the API host.
Turn in: the pull request, with tests, and a paragraph on where the middleware sits in the chain and why.
Criterion |
Pts |
Exemplary |
Developing |
|---|---|---|---|
Configuration |
5 |
Typed, defaulted and validated in |
Read from |
Middleware |
7 |
Correct place in the chain, with the reason; API host only. |
Appended to the end of |
Flags and logging |
4 |
Switch and logger both registered and working. |
Hard-coded on. |
Tests |
4 |
Both sides of the switch and the limit. |
None. |
Assignment 2: An Owned, Cached, Asynchronous Resource
Students finish the model of Module 8: a cached per-account count, invalidated by receivers, a Celery task that recalculates it on a Beat schedule, and an event published to Redis whenever it changes.
Turn in: the pull request, with tests.
Criterion |
Pts |
Exemplary |
Developing |
|---|---|---|---|
Ownership |
5 |
Uses the ownership manager, with a test across two accounts. |
Filters by hand. |
Caching |
6 |
Plain-value cache key, invalidated by receivers, tested. |
Stale values, or a model instance in the key. |
Task |
5 |
Idempotent, passes ids, on the right queue and schedule. |
Passes model instances. |
Publishing |
4 |
One channel per account, published after commit. |
One channel for everyone. |
Assignment 3: An API with a Contract
Students expose the Module 8 model through an API endpoint whose response is a Pydantic model, authenticated by API key, show it on a console page with a template tag of their own, and bring everything they have written in the course through the pre-commit hooks and to 90% coverage.
Turn in: the pull request, and the coverage report.
Criterion |
Pts |
Exemplary |
Developing |
|---|---|---|---|
Contract |
6 |
Built from the model and dumped, with |
A dict validated afterwards. |
Authentication |
4 |
API key required, with a test for refusal. |
Session only. |
Template tag |
4 |
Small, tested, and used on a page. |
Logic in the template. |
Quality |
6 |
Hooks pass, 90% coverage. |
Hooks skipped. |
Capstone Projects
Students choose one at the end of day 3, and submit it one week after the course.
Capstone A: A Live Activity Feed for Smarter
Learning objective. Build one feature that crosses every layer of a production Django platform, following the patterns that the platform already uses.
Students add an activity Django app to their clone of Smarter. Receivers of signals that
Smarter already sends record each event in a model that inherits ownership, so that each account
sees only its own. A cached summary per account is invalidated by the same receivers, and a Beat
task rolls up yesterday’s events. Every new event is published to a Redis channel and streamed to
a console page by an async view, and the page shows the summary with a template tag of their own.
An API endpoint returns the feed as a Pydantic model, authenticated by API key. A setting in
smarter_settings and a Waffle switch turn the feature on.
What students turn in:
A pull request against their fork, with the pre-commit hooks passing and at least 90% coverage of the new app.
A one-page design note: the signals chosen, the cache key and its invalidation, and the queue that the task runs on.
A six-minute recorded walkthrough, from enabling the switch to an event arriving in the browser.
How it is graded (100 points):
Criterion |
Pts |
Exemplary |
Developing |
|---|---|---|---|
Data and ownership |
20 |
Model on the ownership base class, with tests that accounts cannot see each other’s rows. |
Filters by owner by hand in each view. |
Signals, caching and tasks |
20 |
Cache invalidated by receivers, task routed to the right queue, both tested. |
Stale cache, or a task that only runs eagerly. |
Streaming |
15 |
Async view, Redis channel per account, keepalives and clean shutdown. |
Polling, or one channel for everyone. |
API and configuration |
15 |
Pydantic response, API key authentication, a validated setting and a Waffle switch. |
Hand-built JSON, or configuration read from |
Quality |
20 |
Hooks pass, 90% coverage, docstrings, and code that reads like the code around it. |
Hooks skipped, or tests that do not clean up. |
Walkthrough |
10 |
Clear and on time. |
Overruns, or the feature does not run. |
Capstone B: The Patterns, Brought Home
Learning objective. Judge which of the course’s patterns a real Django project needs, and apply them well.
Students take a Django project of their own, their employer’s (with permission), or an open-source one, and apply at least six patterns from the course, for example a validated settings class, an ownership manager, a middleware, a cached and invalidated query, a Celery Beat task, an async streaming view, a Pydantic API contract, a template tag, or pre-commit and coverage in CI. For each pattern, a short note compares their implementation with Smarter’s and says what they changed and why. They also name one pattern they chose not to adopt, and why.
What students turn in:
A pull request or patch series against the project, with tests.
A four- to six-page design note, one section per pattern.
A six-minute recorded walkthrough.
How it is graded (100 points):
Criterion |
Pts |
Exemplary |
Developing |
|---|---|---|---|
Choice of patterns |
15 |
Each solves a real problem in the project, and one is rejected with reasons. |
Patterns added because the course covered them. |
Implementation |
30 |
Idiomatic, and consistent with the project’s own conventions. |
Copied from Smarter without adapting. |
Tests and quality |
20 |
Tested, linted, and covered. |
Untested. |
Design note |
25 |
Compares with Smarter, with trade-offs. |
Describes the code only. |
Walkthrough |
10 |
Clear and on time. |
Overruns. |
Recommended Texts
Required: Daniel Feldroy and Audrey Feldroy, Two Scoops of Django 3.x (Two Scoops Press, 2020), the chapters on settings, models, queries, the user model, signals and testing, before and during the course.
Required: Adam Wiggins, The Twelve-Factor App, before day 1.
Reference: William S. Vincent, Django for Professionals (latest edition), for deployment, security and performance.
Reference: Adam Johnson, Boost Your Django DX (2023), on pre-commit and developer tooling, and Speed Up Your Django Tests (2020).
Reference: Harry Percival and Bob Gregory, Architecture Patterns with Python (O’Reilly, 2020), free at cosmicpython.com.
Smarter documentation: ADR, 12-Factor App, Developer Guidelines, URL Patterns and Security.
Further Study
Django’s own documentation: the topic guides on middleware, signals, asynchronous support, applications and ready(), managers, customizing authentication and custom template tags.
Libraries used in this course: Celery, Django Channels, django-hosts, django-waffle, Python Social Auth, Django REST Framework, Pydantic, and Redis publish-subscribe.
Style guides: the HackSoft Django Styleguide, on where business logic belongs in a large Django project.
Blogs: Adam Johnson’s blog, on Django internals, testing and tooling.
Podcasts: Django Chat, with Will Vincent and Carlton Gibson.
YouTube: the DjangoCon US talk archive, and the recorded talks of DjangoCon Europe.
Community: the Django Forum, where Django’s developers answer questions about its internals.