LEARN DJANGO WEB FRAMEWORKS
Learn Django: From Surface to Core
Goal: Deeply understand how Django works under the hood—from the WSGI interface to the ORM’s query generation to the admin’s magic—and how it compares to other Python and non-Python web frameworks.
Why Learn Django Internals?
Django is “the web framework for perfectionists with deadlines.” It powers Instagram, Spotify, Pinterest, Dropbox, and Mozilla. But most developers use it without understanding how it works.
Understanding Django deeply teaches you:
- Python web standards - WSGI/ASGI, the foundation of all Python web frameworks
- ORM design - How Django translates Python to SQL (and back)
- The “batteries included” philosophy - Why Django includes so much
- Signals and hooks - The observer pattern in web frameworks
- Admin magic - How Django auto-generates admin interfaces
- Framework design principles - Applicable to any language
After completing these projects, you will:
- Understand every step from HTTP request to database query to response
- Know how Django’s ORM builds and executes queries
- Be able to build your own Python web framework from scratch
- Appreciate the trade-offs between Django, Flask, FastAPI, and others
- Debug Django applications at any level of the stack
Core Concept Analysis
The Django Philosophy
┌─────────────────────────────────────────────────────────────────────────────┐
│ THE DJANGO WAY │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ 1. "BATTERIES INCLUDED" │
│ └── Everything you need: ORM, admin, auth, forms, templates, etc. │
│ └── Vs Flask's "bring your own batteries" │
│ │
│ 2. "EXPLICIT IS BETTER THAN IMPLICIT" (The Zen of Python) │
│ └── URL → View mapping is explicit, not magical │
│ └── Contrast with Rails' "convention over configuration" │
│ │
│ 3. "DON'T REPEAT YOURSELF" (DRY) │
│ └── Define things once: models define schema, forms, admin │
│ └── One source of truth │
│ │
│ 4. LOOSE COUPLING, TIGHT COHESION │
│ └── Components are independent but work well together │
│ └── You can use Django ORM without Django views │
│ │
│ 5. "QUICK DEVELOPMENT" + "CLEAN, PRAGMATIC DESIGN" │
│ └── Ship fast without accumulating technical debt │
│ └── "Perfectionists with deadlines" │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
Request Lifecycle: From HTTP to Response
┌─────────────────────────────────────────────────────────────────────────────┐
│ DJANGO REQUEST LIFECYCLE │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ HTTP Request (from browser/client) │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ WEB SERVER (Nginx, Apache) │ │
│ │ • Handles static files directly (if configured) │ │
│ │ • Passes dynamic requests to WSGI/ASGI server │ │
│ └───────────────────────────────┬─────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ WSGI/ASGI SERVER (Gunicorn, uWSGI, Daphne, Uvicorn) │ │
│ │ • Creates environ dict from HTTP request │ │
│ │ • Calls Django's WSGIHandler/ASGIHandler │ │
│ └───────────────────────────────┬─────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ DJANGO WSGI/ASGI HANDLER │ │
│ │ • Creates HttpRequest object from environ │ │
│ │ • Loads settings, signals request_started │ │
│ └───────────────────────────────┬─────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ MIDDLEWARE STACK (process_request phase) │ │
│ │ • SecurityMiddleware (HTTPS, headers) │ │
│ │ • SessionMiddleware (load session) │ │
│ │ • CommonMiddleware (URL normalization) │ │
│ │ • CsrfViewMiddleware (CSRF protection) │ │
│ │ • AuthenticationMiddleware (attach user to request) │ │
│ │ • MessageMiddleware (flash messages) │ │
│ │ • ... (custom middleware) │ │
│ └───────────────────────────────┬─────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ URL RESOLVER (django.urls) │ │
│ │ • Matches request path against urlpatterns │ │
│ │ • Extracts URL parameters (named groups) │ │
│ │ • Returns view function/class + kwargs │ │
│ └───────────────────────────────┬─────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ MIDDLEWARE (process_view phase) │ │
│ │ • Can inspect/modify before view execution │ │
│ │ • Can short-circuit and return response │ │
│ └───────────────────────────────┬─────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ VIEW (function-based or class-based) │ │
│ │ • Receives HttpRequest + URL kwargs │ │
│ │ • Queries database via ORM │ │
│ │ • Processes business logic │ │
│ │ • Returns HttpResponse (or calls render()) │ │
│ └───────────────────────────────┬─────────────────────────────────────┘ │
│ │ │
│ ┌───────────────────────┼───────────────────────┐ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌──────────────┐ ┌──────────────────┐ ┌─────────────────┐ │
│ │ MODEL (ORM) │ │ TEMPLATE ENGINE │ │ FORM PROCESSING │ │
│ │ • QuerySet │ │ • Load template │ │ • Validate data │ │
│ │ • SQL gen │ │ • Context dict │ │ • Clean fields │ │
│ │ • Validation │ │ • Render HTML │ │ • Save to model │ │
│ └──────────────┘ └──────────────────┘ └─────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ MIDDLEWARE (process_response phase) - REVERSE ORDER │ │
│ │ • Can modify response before sending │ │
│ │ • GZip compression, CORS headers, etc. │ │
│ └───────────────────────────────┬─────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ HTTP Response → Client │ │
│ │ • Status code, headers, body │ │
│ │ • Signal: request_finished │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
Django Component Architecture
┌─────────────────────────────────────────────────────────────────────────────┐
│ DJANGO COMPONENT ARCHITECTURE │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────┐ │
│ │ django.core │ │
│ │ • Management │ │
│ │ • Handlers │ │
│ │ • Signals │ │
│ │ • Checks │ │
│ └─────────┬───────────┘ │
│ │ │
│ ┌───────────────────────────┼───────────────────────────┐ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌───────────────┐ ┌───────────────┐ ┌───────────────┐ │
│ │ django.db │ │ django.http │ │django.template│ │
│ │ │ │ │ │ │ │
│ │ • Models │ │ • HttpRequest │ │ • Engine │ │
│ │ • ORM/QuerySet│ │ • HttpResponse│ │ • Loaders │ │
│ │ • Migrations │ │ • Cookies │ │ • Context │ │
│ │ • Backends │ │ • JsonResponse│ │ • Tags/Filters│ │
│ └───────────────┘ └───────────────┘ └───────────────┘ │
│ │ │ │ │
│ └───────────────────────────┼───────────────────────────┘ │
│ │ │
│ ┌─────────▼───────────┐ │
│ │ django.views │ │
│ │ • generic/ │ │
│ │ • decorators │ │
│ │ • defaults │ │
│ └─────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ django.contrib (Apps) │ │
│ │ │ │
│ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │
│ │ │ admin │ │ auth │ │ sessions │ │contenttypes│ │ │
│ │ │ • ModelAdmin│ • User │ │ • Backend│ │ • Generic │ │ │
│ │ │ • Site │ │ • Groups │ │ • Store │ │ relations│ │ │
│ │ └──────────┘ │ • Perms │ └──────────┘ └──────────┘ │ │
│ │ └──────────┘ │ │
│ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │
│ │ │ messages │ │ staticfiles│ │ sites │ │ postgres │ │ │
│ │ │ • Storage│ │ • Finders│ │ • Multi- │ │ • Fields │ │ │
│ │ │ • Levels │ │ • Serving│ │ tenant │ │ • Search │ │ │
│ │ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ django.urls & django.forms │ │
│ │ ┌───────────────────────┐ ┌───────────────────────┐ │ │
│ │ │ URL Routing │ │ Forms │ │ │
│ │ │ • path(), re_path() │ │ • Form, ModelForm │ │ │
│ │ │ • include() │ │ • Widgets │ │ │
│ │ │ • URL namespaces │ │ • Validation │ │ │
│ │ └───────────────────────┘ └───────────────────────┘ │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
Django ORM Internals
┌─────────────────────────────────────────────────────────────────────────────┐
│ DJANGO ORM ARCHITECTURE │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ User.objects.filter(active=True).order_by('name')[:10] │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ Manager (User.objects) │ │
│ │ • "The interface through which database query operations │ │
│ │ are provided to Django models" │ │
│ │ • Returns QuerySet │ │
│ └───────────────────────────────┬─────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ QuerySet (lazy, chainable) │ │
│ │ │ │
│ │ Key properties: │ │
│ │ • LAZY: No database hit until evaluated │ │
│ │ • IMMUTABLE: Each method returns NEW QuerySet │ │
│ │ • CHAINABLE: .filter().exclude().order_by()... │ │
│ │ • CACHEABLE: Results cached after first evaluation │ │
│ │ │ │
│ │ Internal state: │ │
│ │ • query: django.db.models.sql.Query object │ │
│ │ • _result_cache: None until evaluated │ │
│ └───────────────────────────────┬─────────────────────────────────────┘ │
│ │ (on iteration, len(), list(), etc.) │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ Query (SQL builder) │ │
│ │ │ │
│ │ • Stores: where clauses, joins, ordering, limits │ │
│ │ • Uses: WhereNode for conditions │ │
│ │ • Builds: SQL query structure │ │
│ └───────────────────────────────┬─────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ SQLCompiler │ │
│ │ │ │
│ │ • Converts Query object to SQL string │ │
│ │ • Database-specific (PostgreSQL, MySQL, SQLite) │ │
│ │ • Handles quoting, escaping, dialect differences │ │
│ └───────────────────────────────┬─────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ Database Backend (django.db.backends.*) │ │
│ │ │ │
│ │ • Connection management │ │
│ │ • Cursor execution │ │
│ │ • Type conversion │ │
│ │ │ │
│ │ Generated SQL: │ │
│ │ SELECT "auth_user".* FROM "auth_user" │ │
│ │ WHERE "auth_user"."active" = true │ │
│ │ ORDER BY "auth_user"."name" ASC │ │
│ │ LIMIT 10 │ │
│ └───────────────────────────────┬─────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ Model Instantiation │ │
│ │ │ │
│ │ • Each row → Model instance │ │
│ │ • Field types handle conversion (CharField → str, etc.) │ │
│ │ • Deferred fields loaded on access │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
Framework Comparison: Python Frameworks
| Aspect | Django | Flask | FastAPI |
|---|---|---|---|
| Philosophy | Batteries included | Micro-framework | Modern, async-first |
| ORM | Django ORM (built-in) | SQLAlchemy (external) | SQLAlchemy/Tortoise |
| Admin | Built-in, powerful | None (extensions) | None (extensions) |
| Templates | Django Templates | Jinja2 | Jinja2 |
| Forms | Django Forms | WTForms (external) | Pydantic |
| Auth | Built-in | Flask-Login (external) | Custom/libraries |
| Async | ASGI support (3.0+) | Limited | Native async/await |
| API docs | DRF (extension) | Flask-RESTX | Auto-generated (Swagger) |
| Type hints | Optional | Optional | Required (validation) |
| Learning curve | Moderate-steep | Easy | Easy-moderate |
| Best for | Full-stack web apps | APIs, microservices | High-perf APIs |
Framework Comparison: Cross-Language
| Aspect | Django (Python) | Rails (Ruby) | Laravel (PHP) | Phoenix (Elixir) |
|---|---|---|---|---|
| ORM Pattern | Active Record-ish | Active Record | Active Record (Eloquent) | Repository (Ecto) |
| Philosophy | Explicit, batteries | Convention over config | Elegant, expressive | Functional, fault-tolerant |
| Admin | Built-in (excellent) | Gems (ActiveAdmin) | Nova (paid) | Custom |
| Real-time | Channels (ASGI) | Action Cable | Laravel Echo | Channels (built-in) |
| Concurrency | Multi-process/ASGI | Multi-process | PHP-FPM | BEAM (millions of procs) |
| Performance | Good | Good | Good | Excellent |
| Community | Large (Python) | Large (declining) | Very large | Growing |
Project List
Projects are ordered from fundamentals to advanced implementations, building deep understanding of Django and web frameworks in general.
Project 1: WSGI Application from Scratch (The Foundation)
- File: LEARN_DJANGO_WEB_FRAMEWORKS.md
- Main Programming Language: Python
- Alternative Programming Languages: N/A (WSGI is Python-specific)
- Coolness Level: Level 3: Genuinely Clever
- Business Potential: 1. The “Resume Gold”
- Difficulty: Level 2: Intermediate
- Knowledge Area: Web Servers / HTTP Protocol
- Software or Tool: Python, Gunicorn/uWSGI
- Main Book: “Flask Web Development” by Miguel Grinberg (for WSGI concepts)
What you’ll build: A minimal web application using only raw WSGI—no Django, no Flask—that handles HTTP requests, parses parameters, manages cookies, and returns responses.
Why it teaches web framework fundamentals: Every Python web framework (Django, Flask, FastAPI, Pyramid) is built on WSGI or ASGI. Understanding WSGI means understanding the foundation everything else is built upon.
Core challenges you’ll face:
- Understanding the WSGI interface → maps to environ dict, start_response callable
- Parsing HTTP requests → maps to query strings, form data, headers
- Managing state → maps to cookies and sessions
- Composing middleware → maps to how Django middleware works
Resources for key challenges:
Key Concepts:
- WSGI Protocol: PEP 3333
- HTTP Basics: “HTTP: The Definitive Guide” by Gourley & Totty
- Python Web: Full Stack Python
Difficulty: Intermediate Time estimate: 1 week Prerequisites: Python basics, understanding of HTTP
Real world outcome:
# Your minimal WSGI app running:
$ gunicorn app:application
[INFO] Starting gunicorn 21.2.0
[INFO] Listening at: http://127.0.0.1:8000
# In browser or curl:
$ curl "http://localhost:8000/hello?name=World"
Hello, World!
$ curl -X POST -d "username=alice" http://localhost:8000/login
Welcome, alice! Session ID: abc123
$ curl -b "session_id=abc123" http://localhost:8000/profile
Profile for alice (authenticated)
Implementation Hints:
The WSGI interface is simple—a callable with two arguments:
def application(environ, start_response):
"""
environ: dict with request info
- REQUEST_METHOD: 'GET', 'POST', etc.
- PATH_INFO: '/hello'
- QUERY_STRING: 'name=World'
- wsgi.input: file-like object for POST body
- HTTP_*: headers (HTTP_HOST, HTTP_COOKIE, etc.)
start_response: callable to send status/headers
"""
status = '200 OK'
headers = [('Content-Type', 'text/html')]
start_response(status, headers)
return [b'<h1>Hello, World!</h1>']
Parsing query strings and POST data:
from urllib.parse import parse_qs
def application(environ, start_response):
# GET parameters
query_string = environ.get('QUERY_STRING', '')
params = parse_qs(query_string)
name = params.get('name', ['World'])[0]
# POST data
if environ['REQUEST_METHOD'] == 'POST':
content_length = int(environ.get('CONTENT_LENGTH', 0))
body = environ['wsgi.input'].read(content_length)
post_data = parse_qs(body.decode())
start_response('200 OK', [('Content-Type', 'text/html')])
return [f'<h1>Hello, {name}!</h1>'.encode()]
Building middleware:
class LoggingMiddleware:
def __init__(self, app):
self.app = app
def __call__(self, environ, start_response):
print(f"{environ['REQUEST_METHOD']} {environ['PATH_INFO']}")
return self.app(environ, start_response)
# Stack middleware:
application = LoggingMiddleware(my_app)
Questions to guide your implementation:
- What’s in the
environdictionary? (Print it and explore!) - How do you read cookies from environ? (
HTTP_COOKIE) - How do you set cookies in the response? (headers)
- How do you implement routing (URL → handler mapping)?
Learning milestones:
- Basic request/response works → You understand the WSGI interface
- You parse GET and POST parameters → You understand HTTP message formats
- Cookie-based sessions work → You understand state management
- Multiple middleware compose → You understand the middleware pattern
Project 2: Build a Minimal Python Web Framework
- File: LEARN_DJANGO_WEB_FRAMEWORKS.md
- Main Programming Language: Python
- Alternative Programming Languages: N/A
- Coolness Level: Level 4: Hardcore Tech Flex
- Business Potential: 1. The “Resume Gold”
- Difficulty: Level 3: Advanced
- Knowledge Area: Web Framework Design / MVT Pattern
- Software or Tool: Python, Jinja2
- Main Book: “Flask Web Development” by Miguel Grinberg
What you’ll build: A mini Django/Flask-like framework with routing (decorators and URL patterns), views (functions), templates (Jinja2), and basic ORM concepts.
Why it teaches framework internals: “To see how all the magic works beneath the scenes in Flask, Django, and other frameworks.” This project demystifies framework magic by implementing it yourself.
Core challenges you’ll face:
- Routing with decorators → maps to Flask-style @app.route
- URL pattern matching → maps to Django-style path()
- Template rendering → maps to context passing, template loading
- Request/Response objects → maps to wrapping WSGI
Resources for key challenges:
Key Concepts:
- Decorators: Python decorator pattern
- URL Routing: Regex-based and path-based routing
- Templates: Jinja2 or roll your own
Difficulty: Advanced Time estimate: 3-4 weeks Prerequisites: Project 1, Python decorators, regex
Real world outcome:
# Your framework in action:
from myframework import App, render_template
app = App()
@app.route('/')
def home(request):
return render_template('home.html', title='Welcome')
@app.route('/users/<int:id>')
def user_detail(request, id):
user = User.get(id)
return render_template('user.html', user=user)
@app.route('/api/users', methods=['GET', 'POST'])
def users_api(request):
if request.method == 'POST':
user = User.create(**request.json)
return JsonResponse(user.to_dict(), status=201)
return JsonResponse([u.to_dict() for u in User.all()])
# Run it:
# $ python app.py
# Serving on http://localhost:8000
Implementation Hints:
Router with decorators:
class App:
def __init__(self):
self.routes = {}
def route(self, path, methods=['GET']):
def decorator(handler):
self.routes[path] = {
'handler': handler,
'methods': methods,
'pattern': self._compile_pattern(path)
}
return handler
return decorator
def _compile_pattern(self, path):
# /users/<int:id> → /users/(?P<id>\d+)
import re
pattern = path
pattern = re.sub(r'<int:(\w+)>', r'(?P<\1>\\d+)', pattern)
pattern = re.sub(r'<(\w+)>', r'(?P<\1>[^/]+)', pattern)
return re.compile(f'^{pattern}$')
def __call__(self, environ, start_response):
# This is the WSGI application
request = Request(environ)
response = self.handle_request(request)
return response(environ, start_response)
def handle_request(self, request):
for path, route in self.routes.items():
match = route['pattern'].match(request.path)
if match and request.method in route['methods']:
kwargs = {k: int(v) if v.isdigit() else v
for k, v in match.groupdict().items()}
return route['handler'](request, **kwargs)
return Response('Not Found', status=404)
Request wrapper:
class Request:
def __init__(self, environ):
self.environ = environ
self.method = environ['REQUEST_METHOD']
self.path = environ['PATH_INFO']
self._body = None
@property
def body(self):
if self._body is None:
length = int(self.environ.get('CONTENT_LENGTH', 0) or 0)
self._body = self.environ['wsgi.input'].read(length)
return self._body
@property
def json(self):
import json
return json.loads(self.body)
@property
def form(self):
from urllib.parse import parse_qs
return parse_qs(self.body.decode())
Template rendering:
from jinja2 import Environment, FileSystemLoader
def render_template(template_name, **context):
env = Environment(loader=FileSystemLoader('templates'))
template = env.get_template(template_name)
html = template.render(**context)
return Response(html, content_type='text/html')
Learning milestones:
- Decorator routing works → You understand Flask-style routing
- URL parameters extracted → You understand pattern matching
- Templates render with context → You understand template engines
- JSON APIs work → You’ve built a usable framework
Project 3: Build an ORM (Understanding Django ORM)
- File: LEARN_DJANGO_WEB_FRAMEWORKS.md
- Main Programming Language: Python
- Alternative Programming Languages: N/A
- Coolness Level: Level 4: Hardcore Tech Flex
- Business Potential: 1. The “Resume Gold”
- Difficulty: Level 4: Expert
- Knowledge Area: ORM Design / Database Patterns
- Software or Tool: Python, SQLite
- Main Book: “Fluent Python” by Luciano Ramalho (for descriptors/metaclasses)
What you’ll build: A minimal ORM with model definition using classes, field types, migrations, QuerySet-like chainable queries, and relationships (ForeignKey).
Why it teaches Django ORM: “QuerySets are lazy… immutable… chainable.” Building one forces you to understand every abstraction in Django’s ORM.
Core challenges you’ll face:
- Model definition with fields → maps to Field classes, metaclasses
- Lazy QuerySet evaluation → maps to building queries without executing
- Chainable API → maps to returning new QuerySet on each call
- Field lookups → maps to
__gt,__contains, etc.
Key Concepts:
- Metaclasses: “Fluent Python” Chapter 24 - Ramalho
- Descriptors: “Fluent Python” Chapter 23 - Ramalho
- Query Building: Django ORM source code
Difficulty: Expert Time estimate: 4-5 weeks Prerequisites: Projects 1-2, SQL, Python advanced features
Real world outcome:
# Your ORM in action:
from myorm import Model, CharField, IntegerField, ForeignKey
class Author(Model):
name = CharField(max_length=100)
age = IntegerField(null=True)
class Book(Model):
title = CharField(max_length=200)
author = ForeignKey(Author)
pages = IntegerField(default=0)
# Migrations
Author.create_table()
Book.create_table()
# CRUD
author = Author.objects.create(name='Alice', age=30)
book = Book.objects.create(title='My Book', author=author, pages=300)
# Lazy, chainable queries
books = (Book.objects
.filter(pages__gt=100)
.filter(author__name='Alice')
.order_by('-pages')
.limit(10))
# Not executed yet!
print(books.query) # Shows SQL
# Now it executes:
for book in books:
print(f"{book.title} by {book.author.name}")
# Aggregations
avg_pages = Book.objects.filter(author=author).aggregate(Avg('pages'))
Implementation Hints:
Model metaclass:
class ModelMeta(type):
def __new__(mcs, name, bases, namespace):
fields = {}
for key, value in namespace.items():
if isinstance(value, Field):
value.name = key
fields[key] = value
namespace['_fields'] = fields
namespace['_table_name'] = name.lower() + 's'
cls = super().__new__(mcs, name, bases, namespace)
cls.objects = Manager(cls)
return cls
class Model(metaclass=ModelMeta):
def __init__(self, **kwargs):
for name, field in self._fields.items():
value = kwargs.get(name, field.default)
setattr(self, name, value)
def save(self):
# INSERT or UPDATE
pass
Lazy QuerySet:
class QuerySet:
def __init__(self, model):
self.model = model
self._filters = []
self._order_by = []
self._limit = None
self._result_cache = None # Lazy!
def filter(self, **kwargs):
# Return NEW QuerySet, don't modify self
clone = self._clone()
clone._filters.append(kwargs)
return clone
def order_by(self, *fields):
clone = self._clone()
clone._order_by.extend(fields)
return clone
def _clone(self):
clone = QuerySet(self.model)
clone._filters = self._filters.copy()
clone._order_by = self._order_by.copy()
clone._limit = self._limit
return clone
def __iter__(self):
# Execute query on first iteration
if self._result_cache is None:
self._result_cache = self._execute()
return iter(self._result_cache)
def _execute(self):
sql = self._build_sql()
rows = database.execute(sql)
return [self.model(**row) for row in rows]
@property
def query(self):
return self._build_sql()
Field lookups (__gt, __contains):
def _parse_lookup(self, key, value):
# author__name → JOIN + filter
# pages__gt → pages > value
if '__' in key:
parts = key.split('__')
field = parts[0]
lookup = parts[-1] if len(parts) > 1 else 'exact'
lookups = {
'exact': '= ?',
'gt': '> ?',
'lt': '< ?',
'gte': '>= ?',
'lte': '<= ?',
'contains': 'LIKE ?', # Add % around value
'startswith': 'LIKE ?',
'in': 'IN (?)',
}
return f"{field} {lookups[lookup]}", value
Learning milestones:
- Models define tables → You understand metaclasses
- QuerySet is lazy → You understand deferred execution
- Chaining works → You understand immutable query building
- Lookups work → You understand query parsing
Project 4: Django Signals & Hooks (Observer Pattern)
- File: LEARN_DJANGO_WEB_FRAMEWORKS.md
- Main Programming Language: Python
- Alternative Programming Languages: N/A
- Coolness Level: Level 3: Genuinely Clever
- Business Potential: 1. The “Resume Gold”
- Difficulty: Level 2: Intermediate
- Knowledge Area: Design Patterns / Event-Driven
- Software or Tool: Python
- Main Book: “Head First Design Patterns” (Observer Pattern)
What you’ll build: A signal/event system like Django’s, implementing pre_save, post_save, pre_delete, and custom signals with the ability to connect/disconnect receivers.
Why it teaches Django internals: Signals are how Django components communicate without tight coupling. Understanding signals teaches you the Observer pattern and Django’s internal events.
Core challenges you’ll face:
- Signal dispatch → maps to calling all registered receivers
- Weak references → maps to preventing memory leaks
- Sender filtering → maps to only receiving from specific senders
- Thread safety → maps to concurrent signal dispatch
Key Concepts:
- Observer Pattern: “Head First Design Patterns” Chapter 2
- Django Signals: Django Docs
- Weak References: Python
weakrefmodule
Difficulty: Intermediate Time estimate: 1 week Prerequisites: Projects 1-3, understanding of decorators
Real world outcome:
# Your signal system in action:
from mysignals import Signal
# Define signals
pre_save = Signal()
post_save = Signal()
user_logged_in = Signal()
# Connect receivers
@pre_save.connect(sender=User)
def hash_password(sender, instance, **kwargs):
if instance.password_changed:
instance.password = hash(instance.password)
@post_save.connect(sender=User)
def send_welcome_email(sender, instance, created, **kwargs):
if created:
send_email(instance.email, 'Welcome!')
@user_logged_in.connect
def log_login(sender, user, **kwargs):
print(f"{user.username} logged in at {datetime.now()}")
# Send signals
class User(Model):
def save(self):
pre_save.send(sender=User, instance=self)
# ... actual save ...
post_save.send(sender=User, instance=self, created=is_new)
# Disconnect if needed
post_save.disconnect(send_welcome_email, sender=User)
Implementation Hints:
Signal class:
import weakref
import threading
class Signal:
def __init__(self):
self._receivers = []
self._lock = threading.Lock()
def connect(self, receiver=None, sender=None, weak=True):
"""Can be used as decorator or called directly"""
def _connect(receiver):
with self._lock:
if weak:
receiver_ref = weakref.ref(receiver)
else:
receiver_ref = lambda: receiver
self._receivers.append({
'receiver': receiver_ref,
'sender': sender,
})
return receiver
if receiver is None:
return _connect
return _connect(receiver)
def disconnect(self, receiver, sender=None):
with self._lock:
self._receivers = [
r for r in self._receivers
if not (r['receiver']() == receiver and r['sender'] == sender)
]
def send(self, sender, **kwargs):
responses = []
for receiver_info in self._receivers:
# Check sender matches (None means any sender)
if receiver_info['sender'] is not None:
if receiver_info['sender'] != sender:
continue
receiver = receiver_info['receiver']()
if receiver is not None: # Weak ref still valid
response = receiver(sender=sender, **kwargs)
responses.append((receiver, response))
return responses
Built-in model signals:
# Define standard signals
pre_save = Signal()
post_save = Signal()
pre_delete = Signal()
post_delete = Signal()
pre_init = Signal()
post_init = Signal()
class Model:
def __init__(self, **kwargs):
pre_init.send(sender=self.__class__, instance=self)
# ... initialization ...
post_init.send(sender=self.__class__, instance=self)
def save(self):
created = self.pk is None
pre_save.send(sender=self.__class__, instance=self)
# ... actual save ...
post_save.send(sender=self.__class__, instance=self, created=created)
def delete(self):
pre_delete.send(sender=self.__class__, instance=self)
# ... actual delete ...
post_delete.send(sender=self.__class__, instance=self)
Learning milestones:
- Basic signal dispatch works → You understand pub/sub
- Sender filtering works → You understand targeted events
- Weak references prevent leaks → You understand memory management
- You integrate with your ORM → You see how Django uses signals
Project 5: Django Admin Clone (The Magic Interface)
- File: LEARN_DJANGO_WEB_FRAMEWORKS.md
- Main Programming Language: Python
- Alternative Programming Languages: N/A
- Coolness Level: Level 5: Pure Magic
- Business Potential: 3. The “Service & Support” Model
- Difficulty: Level 4: Expert
- Knowledge Area: Auto-generated Interfaces / Introspection
- Software or Tool: Python, your framework from Project 2
- Main Book: “Two Scoops of Django” by Greenfeld & Roy
What you’ll build: An auto-generated admin interface like Django Admin—register models and get full CRUD interfaces, list views with search/filter, and form generation.
Why it teaches Django’s power: Django Admin is often cited as Django’s “killer feature.” Building one teaches you Python introspection, model metadata, and dynamic UI generation.
Core challenges you’ll face:
- Model introspection → maps to reading field definitions
- Dynamic form generation → maps to fields → form inputs
- List view with filtering → maps to search, filters, pagination
- Registration system → maps to ModelAdmin configuration
Key Concepts:
- Python Introspection:
getattr,hasattr,type() - Django Admin: Django Docs
- Form Generation: Model → Form mapping
Difficulty: Expert Time estimate: 4-5 weeks Prerequisites: Projects 1-4
Real world outcome:
# Your admin system:
from myadmin import AdminSite, ModelAdmin
site = AdminSite()
class UserAdmin(ModelAdmin):
list_display = ['username', 'email', 'is_active', 'created_at']
list_filter = ['is_active', 'created_at']
search_fields = ['username', 'email']
ordering = ['-created_at']
fieldsets = [
('Account', {'fields': ['username', 'email', 'password']}),
('Status', {'fields': ['is_active', 'is_staff']}),
]
site.register(User, UserAdmin)
site.register(Post) # Default admin
# Mount in your app
app = App()
app.mount('/admin', site)
# Visit /admin/users/ → List of users with search/filter
# Visit /admin/users/1/ → Edit form for user 1
# Visit /admin/users/add/ → Create new user form
Implementation Hints:
AdminSite and registration:
class AdminSite:
def __init__(self):
self._registry = {}
def register(self, model, admin_class=None):
if admin_class is None:
admin_class = ModelAdmin
self._registry[model] = admin_class(model, self)
def __call__(self, environ, start_response):
request = Request(environ)
# Route: /admin/{model_name}/{pk?}/{action?}
response = self.handle(request)
return response(environ, start_response)
def handle(self, request):
path_parts = request.path.strip('/').split('/')
# /admin/ → index
# /admin/users/ → list
# /admin/users/1/ → detail/edit
# /admin/users/add/ → add form
...
Model introspection for form generation:
class ModelAdmin:
list_display = ['__str__']
list_filter = []
search_fields = []
def __init__(self, model, admin_site):
self.model = model
self.admin_site = admin_site
def get_fields(self):
"""Get all editable fields from model"""
return [
(name, field)
for name, field in self.model._fields.items()
if field.editable
]
def render_form(self, instance=None):
"""Generate HTML form from model fields"""
html = '<form method="post">'
for name, field in self.get_fields():
value = getattr(instance, name, '') if instance else ''
input_type = self._get_input_type(field)
html += f'''
<label>{name}</label>
<input type="{input_type}" name="{name}" value="{value}">
'''
html += '<button type="submit">Save</button></form>'
return html
def _get_input_type(self, field):
type_map = {
'CharField': 'text',
'IntegerField': 'number',
'BooleanField': 'checkbox',
'DateField': 'date',
'EmailField': 'email',
'TextField': 'textarea',
}
return type_map.get(field.__class__.__name__, 'text')
List view with search and filter:
def list_view(self, request):
queryset = self.model.objects.all()
# Apply search
search = request.GET.get('q')
if search and self.search_fields:
# OR search across search_fields
for field in self.search_fields:
queryset = queryset.filter(**{f'{field}__contains': search})
# Apply filters
for filter_field in self.list_filter:
filter_value = request.GET.get(filter_field)
if filter_value:
queryset = queryset.filter(**{filter_field: filter_value})
# Pagination
page = int(request.GET.get('page', 1))
per_page = 25
queryset = queryset.limit(per_page).offset((page - 1) * per_page)
return render_template('admin/list.html',
objects=list(queryset),
model_name=self.model.__name__,
list_display=self.list_display)
Learning milestones:
- Model registration works → You understand the registry pattern
- Forms auto-generate → You understand introspection
- List views work → You understand data presentation
- Search/filter works → You’ve built a real admin
Project 6: Class-Based Views Implementation
- File: LEARN_DJANGO_WEB_FRAMEWORKS.md
- Main Programming Language: Python
- Alternative Programming Languages: N/A
- Coolness Level: Level 3: Genuinely Clever
- Business Potential: 1. The “Resume Gold”
- Difficulty: Level 3: Advanced
- Knowledge Area: OOP / Design Patterns
- Software or Tool: Python
- Main Book: “Fluent Python” by Luciano Ramalho
What you’ll build: A class-based view system like Django’s—View, TemplateView, ListView, CreateView, UpdateView, DeleteView—with mixins and customizable methods.
Why it teaches CBV internals: Django’s CBVs have 10+ ancestor classes. Building them yourself shows you how inheritance, mixins, and method resolution work together.
Core challenges you’ll face:
- dispatch() method → maps to routing HTTP methods to handlers
- Mixin composition → maps to reusable behavior chunks
- get_queryset(), get_context_data() → maps to customization hooks
- as_view() class method → maps to converting class to callable
Key Concepts:
- Classy CBV: ccbv.co.uk - Explore Django CBV hierarchy
- Multiple Inheritance: Python MRO (Method Resolution Order)
- Mixins: Composition over inheritance
Difficulty: Advanced Time estimate: 2 weeks Prerequisites: Projects 1-5, strong OOP understanding
Real world outcome:
# Your CBV system:
from myviews import View, TemplateView, ListView, CreateView
class HomeView(TemplateView):
template_name = 'home.html'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['featured_posts'] = Post.objects.filter(featured=True)[:5]
return context
class PostListView(ListView):
model = Post
template_name = 'posts/list.html'
paginate_by = 10
def get_queryset(self):
queryset = super().get_queryset()
category = self.request.GET.get('category')
if category:
queryset = queryset.filter(category=category)
return queryset
class PostCreateView(CreateView):
model = Post
fields = ['title', 'content', 'category']
success_url = '/posts/'
def form_valid(self, form):
form.instance.author = self.request.user
return super().form_valid(form)
# URL configuration
urlpatterns = [
path('/', HomeView.as_view()),
path('/posts/', PostListView.as_view()),
path('/posts/new/', PostCreateView.as_view()),
]
Implementation Hints:
Base View class:
class View:
http_method_names = ['get', 'post', 'put', 'patch', 'delete', 'head', 'options']
def __init__(self, **kwargs):
for key, value in kwargs.items():
setattr(self, key, value)
@classmethod
def as_view(cls, **initkwargs):
"""Convert class to WSGI-compatible callable"""
def view(request, *args, **kwargs):
self = cls(**initkwargs)
self.request = request
self.args = args
self.kwargs = kwargs
return self.dispatch(request, *args, **kwargs)
return view
def dispatch(self, request, *args, **kwargs):
"""Route to appropriate handler method"""
method = request.method.lower()
if method in self.http_method_names:
handler = getattr(self, method, self.http_method_not_allowed)
else:
handler = self.http_method_not_allowed
return handler(request, *args, **kwargs)
def http_method_not_allowed(self, request, *args, **kwargs):
return Response('Method Not Allowed', status=405)
Mixins for reusable behavior:
class ContextMixin:
def get_context_data(self, **kwargs):
return kwargs
class TemplateResponseMixin:
template_name = None
def render_to_response(self, context):
return render_template(self.template_name, **context)
class TemplateView(TemplateResponseMixin, ContextMixin, View):
def get(self, request, *args, **kwargs):
context = self.get_context_data()
return self.render_to_response(context)
ListView with pagination:
class MultipleObjectMixin(ContextMixin):
model = None
queryset = None
paginate_by = None
def get_queryset(self):
if self.queryset is not None:
return self.queryset.all()
if self.model is not None:
return self.model.objects.all()
raise ValueError("No model or queryset defined")
def paginate_queryset(self, queryset, page_size):
page = int(self.request.GET.get('page', 1))
return queryset.limit(page_size).offset((page - 1) * page_size)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
queryset = self.get_queryset()
if self.paginate_by:
queryset = self.paginate_queryset(queryset, self.paginate_by)
context['object_list'] = list(queryset)
return context
class ListView(MultipleObjectMixin, TemplateResponseMixin, View):
def get(self, request, *args, **kwargs):
context = self.get_context_data()
return self.render_to_response(context)
Learning milestones:
- as_view() works → You understand class-to-callable conversion
- dispatch() routes methods → You understand HTTP method handling
- Mixins compose → You understand MRO and inheritance
- Generic views work → You’ve recreated Django’s CBV system
Project 7: Build Flask (Minimal Microframework)
- File: LEARN_DJANGO_WEB_FRAMEWORKS.md
- Main Programming Language: Python
- Alternative Programming Languages: N/A
- Coolness Level: Level 4: Hardcore Tech Flex
- Business Potential: 2. The “Micro-SaaS / Pro Tool”
- Difficulty: Level 3: Advanced
- Knowledge Area: Framework Design / Minimalism
- Software or Tool: Python, Werkzeug (optional)
- Main Book: “Flask Web Development” by Miguel Grinberg
What you’ll build: A Flask-like microframework with the application/request context, blueprints for modular applications, and extension points.
Why it teaches microframework design: Flask’s simplicity hides sophisticated design. Understanding it reveals the difference between “batteries included” (Django) and “bring your own batteries” (Flask).
Core challenges you’ll face:
- Application context → maps to current_app, request context
- Thread-local state → maps to Werkzeug’s LocalStack
- Blueprints → maps to modular application structure
- Extension pattern → maps to init_app, Flask-SQLAlchemy style
Key Concepts:
- Context Locals: Werkzeug’s LocalStack
- Flask Architecture: “Flask Web Development” Chapters 1-4
- Extension Pattern: How Flask extensions work
Difficulty: Advanced Time estimate: 2-3 weeks Prerequisites: Projects 1-6
Real world outcome:
# Your Flask-like framework:
from myflask import Flask, Blueprint, request, current_app
app = Flask(__name__)
app.config['SECRET_KEY'] = 'dev'
# Blueprint for modular structure
auth_bp = Blueprint('auth', __name__, url_prefix='/auth')
@auth_bp.route('/login', methods=['GET', 'POST'])
def login():
if request.method == 'POST':
# Access request data
username = request.form['username']
# Access app config
secret = current_app.config['SECRET_KEY']
return redirect('/')
return render_template('login.html')
app.register_blueprint(auth_bp)
# Extensions pattern
class MyExtension:
def __init__(self, app=None):
if app is not None:
self.init_app(app)
def init_app(self, app):
app.config.setdefault('MY_SETTING', 'default')
app.extensions['my_extension'] = self
# Run
if __name__ == '__main__':
app.run(debug=True)
Implementation Hints:
Context locals:
import threading
from functools import partial
class LocalStack:
def __init__(self):
self._local = threading.local()
def push(self, obj):
stack = getattr(self._local, 'stack', None)
if stack is None:
self._local.stack = stack = []
stack.append(obj)
def pop(self):
stack = getattr(self._local, 'stack', None)
if stack:
return stack.pop()
@property
def top(self):
stack = getattr(self._local, 'stack', None)
if stack:
return stack[-1]
return None
# Global stacks
_request_ctx_stack = LocalStack()
_app_ctx_stack = LocalStack()
# Proxies
class LocalProxy:
def __init__(self, lookup_func):
self._lookup = lookup_func
def __getattr__(self, name):
return getattr(self._lookup(), name)
request = LocalProxy(lambda: _request_ctx_stack.top.request)
current_app = LocalProxy(lambda: _app_ctx_stack.top.app)
Flask application:
class Flask:
def __init__(self, name):
self.name = name
self.config = {}
self.url_map = {}
self.blueprints = {}
self.extensions = {}
def route(self, rule, **options):
def decorator(f):
self.url_map[rule] = {'handler': f, **options}
return f
return decorator
def register_blueprint(self, blueprint, **options):
blueprint.register(self, options)
self.blueprints[blueprint.name] = blueprint
def wsgi_app(self, environ, start_response):
# Push contexts
ctx = RequestContext(self, environ)
ctx.push()
try:
response = self.full_dispatch(environ)
return response(environ, start_response)
finally:
ctx.pop()
def __call__(self, environ, start_response):
return self.wsgi_app(environ, start_response)
def run(self, host='127.0.0.1', port=5000, debug=False):
from wsgiref.simple_server import make_server
server = make_server(host, port, self)
print(f' * Running on http://{host}:{port}')
server.serve_forever()
Blueprints:
class Blueprint:
def __init__(self, name, import_name, url_prefix=None):
self.name = name
self.import_name = import_name
self.url_prefix = url_prefix or ''
self.deferred_functions = []
def route(self, rule, **options):
def decorator(f):
self.deferred_functions.append(
lambda app: app.url_map[self.url_prefix + rule] = {
'handler': f, **options
}
)
return f
return decorator
def register(self, app, options):
for func in self.deferred_functions:
func(app)
Learning milestones:
- Basic routing works → You understand Flask’s simplicity
- Context locals work → You understand thread-local state
- Blueprints work → You understand modular applications
- Extensions pattern works → You understand Flask’s extensibility
Project 8: Build FastAPI (Modern Async Framework)
- File: LEARN_DJANGO_WEB_FRAMEWORKS.md
- Main Programming Language: Python
- Alternative Programming Languages: N/A
- Coolness Level: Level 5: Pure Magic
- Business Potential: 3. The “Service & Support” Model
- Difficulty: Level 4: Expert
- Knowledge Area: Async Programming / Type Hints / OpenAPI
- Software or Tool: Python 3.10+, Starlette, Pydantic
- Main Book: “Using Asyncio in Python” by Caleb Hattingh
What you’ll build: A FastAPI-like framework with async/await, automatic OpenAPI documentation from type hints, and Pydantic validation.
Why it teaches modern Python: FastAPI represents the future of Python web development—async, typed, and auto-documented. Building one teaches you Python’s newest features.
Core challenges you’ll face:
- ASGI vs WSGI → maps to async application interface
- Type hint extraction → maps to inspect module, typing module
- Pydantic integration → maps to automatic validation
- OpenAPI generation → maps to schema building from types
Key Concepts:
- ASGI: Async Server Gateway Interface
- Type Hints: Python typing module, PEP 484
- Pydantic: Data validation library
Difficulty: Expert Time estimate: 3-4 weeks Prerequisites: Projects 1-7, async/await knowledge
Real world outcome:
# Your FastAPI-like framework:
from myfastapi import FastAPI, Query, Path, Body
from pydantic import BaseModel
app = FastAPI(title="My API", version="1.0.0")
class Item(BaseModel):
name: str
price: float
description: str | None = None
@app.get("/items/{item_id}")
async def get_item(
item_id: int = Path(..., gt=0),
q: str | None = Query(None, max_length=50)
) -> Item:
"""Get an item by ID."""
return Item(name="Example", price=9.99)
@app.post("/items/", status_code=201)
async def create_item(item: Item = Body(...)) -> Item:
"""Create a new item."""
# item is already validated by Pydantic!
return item
# Auto-generated OpenAPI docs at /docs
# Async handling built-in
# Type-based validation automatic
if __name__ == "__main__":
import uvicorn
uvicorn.run(app)
Implementation Hints:
ASGI application:
class FastAPI:
def __init__(self, title="API", version="1.0.0"):
self.title = title
self.version = version
self.routes = []
async def __call__(self, scope, receive, send):
"""ASGI interface"""
if scope['type'] == 'http':
request = Request(scope, receive)
response = await self.handle(request)
await response.send(send)
async def handle(self, request):
for route in self.routes:
match = route['pattern'].match(request.path)
if match and request.method == route['method']:
# Extract and validate parameters
kwargs = await self.extract_params(request, route, match)
# Call async handler
result = await route['handler'](**kwargs)
return self.make_response(result, route)
return Response({'error': 'Not Found'}, status=404)
Extract and validate from type hints:
import inspect
from typing import get_type_hints
async def extract_params(self, request, route, match):
handler = route['handler']
hints = get_type_hints(handler)
sig = inspect.signature(handler)
kwargs = {}
for param_name, param in sig.parameters.items():
annotation = hints.get(param_name)
# Path parameter
if param_name in match.groupdict():
value = match.group(param_name)
kwargs[param_name] = annotation(value) # Cast to type
# Query parameter
elif isinstance(param.default, Query):
value = request.query.get(param_name, param.default.default)
if value is not None:
kwargs[param_name] = annotation(value)
# Body parameter (Pydantic model)
elif isinstance(param.default, Body):
body = await request.json()
kwargs[param_name] = annotation(**body) # Pydantic validation
return kwargs
OpenAPI schema generation:
def generate_openapi(self):
paths = {}
for route in self.routes:
hints = get_type_hints(route['handler'])
path_item = {
route['method'].lower(): {
'summary': route['handler'].__doc__,
'parameters': self._extract_parameters(route),
'responses': {
'200': {
'content': {
'application/json': {
'schema': self._type_to_schema(hints.get('return'))
}
}
}
}
}
}
paths[route['path']] = path_item
return {
'openapi': '3.0.0',
'info': {'title': self.title, 'version': self.version},
'paths': paths
}
Learning milestones:
- ASGI app works → You understand async web interfaces
- Type hints validate → You understand runtime introspection
- Pydantic integrates → You understand validation
- OpenAPI generates → You’ve built a modern API framework
Project 9: Django Channels Clone (Real-Time)
- File: LEARN_DJANGO_WEB_FRAMEWORKS.md
- Main Programming Language: Python
- Alternative Programming Languages: JavaScript (client)
- Coolness Level: Level 4: Hardcore Tech Flex
- Business Potential: 3. The “Service & Support” Model
- Difficulty: Level 4: Expert
- Knowledge Area: WebSockets / ASGI / Async
- Software or Tool: Python, ASGI, Redis
- Main Book: “Using Asyncio in Python” by Caleb Hattingh
What you’ll build: A real-time system like Django Channels—WebSocket handling, channel layers (pub/sub), and consumer classes.
Why it teaches real-time web: WebSockets are essential for modern apps. Understanding Channels teaches you ASGI, async consumers, and the pub/sub pattern.
Core challenges you’ll face:
- WebSocket lifecycle → maps to connect, receive, disconnect
- Consumer classes → maps to handling WebSocket events
- Channel layer → maps to pub/sub across processes
- Routing → maps to URL → consumer mapping
Key Concepts:
- WebSocket Protocol: RFC 6455
- Django Channels: Channels Documentation
- ASGI: Async Server Gateway Interface
Difficulty: Expert Time estimate: 3 weeks Prerequisites: Projects 1-8, async Python
Real world outcome:
# Your Channels-like system:
from mychannels import WebSocketConsumer, ChannelLayer
class ChatConsumer(WebSocketConsumer):
async def connect(self):
self.room_name = self.scope['url_route']['kwargs']['room_name']
self.room_group_name = f'chat_{self.room_name}'
# Join room group
await self.channel_layer.group_add(
self.room_group_name,
self.channel_name
)
await self.accept()
async def disconnect(self, close_code):
await self.channel_layer.group_discard(
self.room_group_name,
self.channel_name
)
async def receive(self, text_data):
data = json.loads(text_data)
message = data['message']
# Broadcast to room group
await self.channel_layer.group_send(
self.room_group_name,
{
'type': 'chat_message',
'message': message
}
)
async def chat_message(self, event):
await self.send(text_data=json.dumps({
'message': event['message']
}))
# Routing
websocket_urlpatterns = [
path('ws/chat/<str:room_name>/', ChatConsumer.as_asgi()),
]
Implementation Hints:
WebSocket consumer base class:
import json
class WebSocketConsumer:
def __init__(self, scope):
self.scope = scope
self.channel_name = f"channel_{id(self)}"
self.channel_layer = get_channel_layer()
@classmethod
def as_asgi(cls):
async def app(scope, receive, send):
consumer = cls(scope)
await consumer.handle(receive, send)
return app
async def handle(self, receive, send):
self._send = send
await self.connect()
try:
while True:
message = await receive()
if message['type'] == 'websocket.receive':
await self.receive(text_data=message.get('text'))
elif message['type'] == 'websocket.disconnect':
await self.disconnect(message.get('code'))
break
except Exception:
await self.disconnect(1011)
async def accept(self):
await self._send({'type': 'websocket.accept'})
async def send(self, text_data=None, bytes_data=None):
await self._send({
'type': 'websocket.send',
'text': text_data,
'bytes': bytes_data
})
async def close(self, code=1000):
await self._send({'type': 'websocket.close', 'code': code})
Channel layer (in-memory for simplicity, Redis for production):
class InMemoryChannelLayer:
def __init__(self):
self.channels = {}
self.groups = {}
async def send(self, channel, message):
if channel in self.channels:
await self.channels[channel].put(message)
async def group_add(self, group, channel):
if group not in self.groups:
self.groups[group] = set()
self.groups[group].add(channel)
async def group_discard(self, group, channel):
if group in self.groups:
self.groups[group].discard(channel)
async def group_send(self, group, message):
if group in self.groups:
for channel in self.groups[group]:
await self.send(channel, message)
Learning milestones:
- WebSocket connections work → You understand ASGI WebSockets
- Consumers handle events → You understand WebSocket lifecycle
- Channel layer pub/sub works → You understand group messaging
- Chat app works → You’ve built real-time functionality
Project 10: Django from Scratch (Putting It All Together)
- File: LEARN_DJANGO_WEB_FRAMEWORKS.md
- Main Programming Language: Python
- Alternative Programming Languages: N/A
- Coolness Level: Level 5: Pure Magic
- Business Potential: 1. The “Resume Gold”
- Difficulty: Level 5: Master
- Knowledge Area: Complete Framework Architecture
- Software or Tool: Python, everything from previous projects
- Main Book: Django source code on GitHub
What you’ll build: Combine all previous projects into a complete Django-like framework: WSGI/ASGI, ORM, middleware, routing, views, templates, forms, admin, auth, and management commands.
Why this is the ultimate project: You’ll have built every major component of Django yourself. When someone asks “How does Django work?”, you’ll know—because you built it.
Core challenges you’ll face:
- Integration → maps to making all components work together
- Settings system → maps to lazy loading configuration
- App system → maps to reusable application packages
- Management commands → maps to ./manage.py runserver, migrate
Difficulty: Master Time estimate: 6-8 weeks Prerequisites: All previous projects completed
Real world outcome:
# Your framework works like Django!
$ ./manage.py startproject mysite
Creating project: mysite
mysite/
__init__.py
settings.py
urls.py
wsgi.py
manage.py
$ cd mysite
$ ./manage.py startapp blog
Creating app: blog
blog/
__init__.py
models.py
views.py
urls.py
admin.py
$ ./manage.py makemigrations
Migrations for 'blog':
blog/migrations/0001_initial.py
- Create model Post
$ ./manage.py migrate
Applying blog.0001_initial... OK
$ ./manage.py createsuperuser
Username: admin
Email: admin@example.com
Password: ****
Superuser created successfully.
$ ./manage.py runserver
Starting development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.
Implementation Hints:
Management command system:
# manage.py
#!/usr/bin/env python
import sys
def main():
from mydjango.core.management import execute_from_command_line
execute_from_command_line(sys.argv)
if __name__ == '__main__':
main()
# mydjango/core/management/__init__.py
def execute_from_command_line(argv):
command = argv[1] if len(argv) > 1 else 'help'
commands = {
'runserver': RunServerCommand,
'migrate': MigrateCommand,
'makemigrations': MakeMigrationsCommand,
'startproject': StartProjectCommand,
'startapp': StartAppCommand,
'createsuperuser': CreateSuperuserCommand,
'shell': ShellCommand,
}
if command in commands:
cmd = commands[command]()
cmd.execute(argv[2:])
else:
print(f"Unknown command: {command}")
Settings lazy loading:
import os
import importlib
class LazySettings:
_wrapped = None
def __getattr__(self, name):
if self._wrapped is None:
self._setup()
return getattr(self._wrapped, name)
def _setup(self):
settings_module = os.environ.get('MYDJANGO_SETTINGS_MODULE')
if not settings_module:
raise Exception("MYDJANGO_SETTINGS_MODULE not set")
self._wrapped = Settings(settings_module)
class Settings:
def __init__(self, settings_module):
mod = importlib.import_module(settings_module)
for setting in dir(mod):
if setting.isupper():
setattr(self, setting, getattr(mod, setting))
settings = LazySettings()
App configuration system:
class AppConfig:
def __init__(self, app_name, app_module):
self.name = app_name
self.module = app_module
self.models_module = None
def ready(self):
"""Override in subclass for app initialization"""
pass
class Apps:
def __init__(self):
self.app_configs = {}
self.all_models = {}
def populate(self, installed_apps):
for app in installed_apps:
app_config = self.create_app_config(app)
self.app_configs[app_config.name] = app_config
# Import models
for app_config in self.app_configs.values():
self.import_models(app_config)
# Call ready()
for app_config in self.app_configs.values():
app_config.ready()
apps = Apps()
Learning milestones:
- manage.py commands work → You understand Django’s CLI
- Settings load properly → You understand lazy loading
- Apps register → You understand app configuration
- A blog app works end-to-end → You’ve built Django!
Project Comparison Table
| Project | Difficulty | Time | Depth of Understanding | Fun Factor |
|---|---|---|---|---|
| 1. WSGI Application | ⭐⭐ | 1 week | ⚡⚡⚡ | 🎮🎮🎮 |
| 2. Minimal MVC Framework | ⭐⭐⭐ | 3-4 weeks | ⚡⚡⚡⚡ | 🎮🎮🎮🎮 |
| 3. Build an ORM | ⭐⭐⭐⭐ | 4-5 weeks | ⚡⚡⚡⚡⚡ | 🎮🎮🎮🎮 |
| 4. Signals System | ⭐⭐ | 1 week | ⚡⚡⚡ | 🎮🎮🎮 |
| 5. Django Admin Clone | ⭐⭐⭐⭐ | 4-5 weeks | ⚡⚡⚡⚡⚡ | 🎮🎮🎮🎮🎮 |
| 6. Class-Based Views | ⭐⭐⭐ | 2 weeks | ⚡⚡⚡⚡ | 🎮🎮🎮 |
| 7. Build Flask | ⭐⭐⭐ | 2-3 weeks | ⚡⚡⚡⚡ | 🎮🎮🎮🎮 |
| 8. Build FastAPI | ⭐⭐⭐⭐ | 3-4 weeks | ⚡⚡⚡⚡⚡ | 🎮🎮🎮🎮🎮 |
| 9. Django Channels Clone | ⭐⭐⭐⭐ | 3 weeks | ⚡⚡⚡⚡ | 🎮🎮🎮🎮 |
| 10. Django from Scratch | ⭐⭐⭐⭐⭐ | 6-8 weeks | ⚡⚡⚡⚡⚡ | 🎮🎮🎮🎮🎮 |
Recommended Learning Path
Your Starting Point
If you’re a Django user wanting to understand internals: Projects 1 → 2 → 3 → 5 → 10 (Core Django components)
If you’re comparing Python frameworks: Projects 1 → 2 → 7 → 8 (Build Flask and FastAPI)
If you’re into async/real-time: Projects 1 → 8 → 9 (Modern async Python)
Recommended Sequence
Phase 1: Foundation (1-2 weeks)
└── Project 1: WSGI Application → Understand Python web interface
Phase 2: Core Framework (7-9 weeks)
├── Project 2: Web Framework → Routing, views, templates
├── Project 3: ORM → Database abstraction
└── Project 4: Signals → Observer pattern
Phase 3: Django Specifics (6-7 weeks)
├── Project 5: Admin → Auto-generated interfaces
└── Project 6: Class-Based Views → OOP patterns
Phase 4: Compare Frameworks (5-7 weeks)
├── Project 7: Flask Clone → Microframework design
└── Project 8: FastAPI Clone → Modern async APIs
Phase 5: Advanced (9-11 weeks)
├── Project 9: Channels → Real-time WebSockets
└── Project 10: Django from Scratch → Complete integration
Final Project: Production Django Application
- File: LEARN_DJANGO_WEB_FRAMEWORKS.md
- Main Programming Language: Python (Django)
- Alternative Programming Languages: JavaScript (frontend)
- Coolness Level: Level 5: Pure Magic
- Business Potential: 4. The “Open Core” Infrastructure
- Difficulty: Level 5: Master
- Knowledge Area: Full-Stack Web Development
- Software or Tool: Django, PostgreSQL, Redis, Celery
- Main Book: “Two Scoops of Django” by Greenfeld & Roy
What you’ll build: A production-ready SaaS application demonstrating mastery of Django—custom user model, permissions, background tasks, caching, API, real-time updates, and deployment.
Core features:
- Multi-tenant SaaS architecture
- Custom user model with social auth
- Role-based permissions
- Background tasks (Celery)
- REST API (Django REST Framework)
- Real-time updates (Channels)
- Caching (Redis)
- Admin customization
- Comprehensive tests
- CI/CD pipeline
- Production deployment
Difficulty: Master Time estimate: 2-3 months Prerequisites: All 10 projects completed
Essential Resources
Books (Primary)
| Book | Author | Best For |
|---|---|---|
| Two Scoops of Django | Greenfeld & Roy | Django best practices |
| Fluent Python | Luciano Ramalho | Advanced Python (metaclasses, descriptors) |
| Django for Professionals | William Vincent | Production Django |
| Using Asyncio in Python | Caleb Hattingh | Async Python |
| Flask Web Development | Miguel Grinberg | Flask & WSGI concepts |
Online Resources
| Resource | URL | Description |
|---|---|---|
| Django Docs | docs.djangoproject.com | Official documentation |
| Django Source | github.com/django/django | Read the code |
| Classy CBV | ccbv.co.uk | CBV inheritance browser |
| TestDriven.io | testdriven.io | Practical tutorials |
| Real Python | realpython.com | Django tutorials |
| Full Stack Python | fullstackpython.com | Comprehensive Python web |
Framework Comparison
| Resource | URL |
|---|---|
| JetBrains Comparison | blog.jetbrains.com |
| Loopwerk Analysis | loopwerk.io |
| BetterStack Guide | betterstack.com |
Summary
| # | Project | Main Language | Knowledge Area |
|---|---|---|---|
| 1 | WSGI Application | Python | Web Servers / HTTP |
| 2 | Minimal Web Framework | Python | Framework Design / MVT |
| 3 | Build an ORM | Python | ORM / Database Patterns |
| 4 | Signals System | Python | Observer Pattern / Events |
| 5 | Django Admin Clone | Python | Introspection / Auto UI |
| 6 | Class-Based Views | Python | OOP / Design Patterns |
| 7 | Build Flask | Python | Microframework Design |
| 8 | Build FastAPI | Python | Async / Type Hints |
| 9 | Django Channels Clone | Python | WebSockets / Real-Time |
| 10 | Django from Scratch | Python | Complete Framework |
| Final | Production SaaS | Python | Full-Stack Development |
Getting Started Checklist
Before starting Project 1:
- Python 3.10+ installed:
python --version - Virtual environments:
python -m venv venv - Understand HTTP basics (methods, headers, status codes)
- Basic SQL knowledge
- Read PEP 3333 (WSGI)
- Install Gunicorn:
pip install gunicorn
Welcome to the world of Python web framework internals! 🐍
Generated for deep understanding of Django and Python web frameworks