This guide explores Django's internal architecture and provides a comprehensive path to contributing to the Django project itself.
Django's request handling follows a well-defined path:
# Simplified request flow
"""
1. WSGI/ASGI Handler receives request
↓
2. Middleware (request phase)
↓
3. URL Resolver matches pattern
↓
4. View function/class executes
↓
5. Template rendering (if applicable)
↓
6. Middleware (response phase)
↓
7. Response returned to client
"""
1. django.core
The foundation of Django:
# django/core/handlers/base.py
class BaseHandler:
"""
Base handler for all Django request handling.
Both WSGI and ASGI handlers inherit from this.
"""
def load_middleware(self):
"""
Populate middleware lists from settings.MIDDLEWARE.
"""
# Middleware is loaded and chained here
pass
def get_response(self, request):
"""
Main request handling logic.
"""
# URL resolution
# View execution
# Response processing
pass
2. django.db
Database abstraction layer:
# django/db/models/query.py
class QuerySet:
"""
Represents a lazy database lookup for a set of objects.
"""
def __init__(self, model=None, query=None, using=None, hints=None):
self.model = model
self._db = using
self._hints = hints or {}
self._query = query or sql.Query(self.model)
self._result_cache = None
self._sticky_filter = False
self._for_write = False
self._prefetch_related_lookups = ()
self._prefetch_done = False
self._known_related_objects = {}
self._iterable_class = ModelIterable
self._fields = None
3. django.template
Template engine:
# django/template/base.py
class Template:
"""
Represents a compiled template.
"""
def __init__(self, template_string, origin=None, name=None, engine=None):
# Compile template into nodes
self.nodelist = self.compile_nodelist()
def render(self, context):
"""
Render the template with the given context.
"""
return self.nodelist.render(context)
4. django.forms
Form handling:
# django/forms/forms.py
class BaseForm:
"""
Base class for all forms.
"""
def __init__(self, data=None, files=None, auto_id='id_%s',
prefix=None, initial=None, error_class=ErrorList,
label_suffix=None, empty_permitted=False,
field_order=None, use_required_attribute=None,
renderer=None):
# Initialize form state
self.is_bound = data is not None or files is not None
self.data = MultiValueDict() if data is None else data
self.files = MultiValueDict() if files is None else files
# ... more initialization
Django's signal dispatcher:
# django/dispatch/dispatcher.py
class Signal:
"""
Base class for all signals.
"""
def __init__(self, use_caching=False):
self.receivers = []
self.use_caching = use_caching
self.sender_receivers_cache = weakref.WeakKeyDictionary() if use_caching else {}
self._dead_receivers = False
def connect(self, receiver, sender=None, weak=True, dispatch_uid=None):
"""
Connect receiver to sender for signal.
"""
# Register receiver
pass
def send(self, sender, **named):
"""
Send signal from sender to all connected receivers.
"""
# Notify all receivers
pass
How middleware is processed:
# django/core/handlers/base.py
def _get_response(self, request):
"""
Resolve and call the view, then apply view, exception, and
template response middleware.
"""
response = None
callback, callback_args, callback_kwargs = self.resolve_request(request)
# Apply view middleware
for middleware_method in self._view_middleware:
response = middleware_method(request, callback, callback_args, callback_kwargs)
if response:
break
if response is None:
# Call the view
response = callback(request, *callback_args, **callback_kwargs)
# Apply template response middleware
if hasattr(response, 'render') and callable(response.render):
for middleware_method in self._template_response_middleware:
response = middleware_method(request, response)
return response
How Django matches URLs:
# django/urls/resolvers.py
class URLResolver:
"""
A URL resolver that matches URL patterns to views.
"""
def resolve(self, path):
"""
Match path against URL patterns.
"""
tried = []
match = self.pattern.match(path)
if match:
new_path, args, kwargs = match
for pattern in self.url_patterns:
try:
sub_match = pattern.resolve(new_path)
except Resolver404 as e:
tried.extend(e.args[0]['tried'])
else:
if sub_match:
# Merge captured arguments
sub_match_dict = {**kwargs, **sub_match.kwargs}
return ResolverMatch(
sub_match.func,
sub_match.args,
sub_match_dict,
sub_match.url_name,
[self.app_name] + sub_match.app_names,
[self.namespace] + sub_match.namespaces,
)
raise Resolver404({'tried': tried, 'path': path})
django/
├── apps/ # Application registry
├── conf/ # Global settings
│ └── urls/ # Default URL patterns
├── contrib/ # "Batteries included" apps
│ ├── admin/
│ ├── auth/
│ ├── contenttypes/
│ ├── sessions/
│ ├── messages/
│ ├── staticfiles/
│ └── ...
├── core/ # Core functionality
│ ├── cache/
│ ├── checks/
│ ├── files/
│ ├── handlers/
│ ├── mail/
│ ├── management/
│ ├── serializers/
│ └── validators/
├── db/ # Database layer
│ ├── backends/ # Database adapters
│ ├── migrations/
│ └── models/
├── dispatch/ # Signal system
├── forms/ # Form handling
├── http/ # HTTP request/response
├── middleware/ # Built-in middleware
├── template/ # Template engine
│ ├── backends/
│ └── loaders/
├── test/ # Testing framework
├── urls/ # URL routing
├── utils/ # Utility functions
└── views/ # Generic views
1. Set Up Development Environment
# Fork Django on GitHub
# Clone your fork
git clone https://github.com/YOUR_USERNAME/django.git
cd django
# Add upstream remote
git remote add upstream https://github.com/django/django.git
# Create virtual environment
python -m venv venv
source venv/bin/activate # On Windows: venv\Scripts\activate
# Install in editable mode
pip install -e .
# Install development dependencies
pip install -r tests/requirements/py3.txt
2. Run Django's Test Suite
# Run all tests (takes a while!)
./runtests.py
# Run specific test module
./runtests.py admin_views
# Run specific test class
./runtests.py admin_views.AdminViewBasicTest
# Run specific test method
./runtests.py admin_views.AdminViewBasicTest.test_login
# Run tests with coverage
coverage run ./runtests.py admin_views
coverage report
# Run tests for specific database
./runtests.py --settings=test_sqlite
./runtests.py --settings=test_postgres
3. Code Style
Django follows PEP 8 with some modifications:
# Install pre-commit hooks
pip install pre-commit
pre-commit install
# Run linters
flake8 django/
# Format imports
isort django/
# Check for common issues
python -m django check --deploy
1. Find an Issue
2. Create a Branch
# Update your fork
git fetch upstream
git checkout main
git merge upstream/main
# Create feature branch
git checkout -b ticket_12345
3. Make Changes
# Example: Adding a new model field option
# django/db/models/fields/__init__.py
class CharField(Field):
def __init__(self, *args, strip_whitespace=False, **kwargs):
self.strip_whitespace = strip_whitespace
super().__init__(*args, **kwargs)
def get_prep_value(self, value):
value = super().get_prep_value(value)
if self.strip_whitespace and isinstance(value, str):
value = value.strip()
return value
4. Write Tests
# tests/model_fields/test_charfield.py
class CharFieldTests(TestCase):
def test_strip_whitespace(self):
"""
CharField with strip_whitespace=True removes leading/trailing spaces.
"""
class TestModel(models.Model):
name = models.CharField(max_length=100, strip_whitespace=True)
class Meta:
app_label = 'model_fields'
obj = TestModel(name=' John Doe ')
obj.full_clean()
self.assertEqual(obj.name, 'John Doe')
5. Update Documentation
.. docs/ref/models/fields.txt
``strip_whitespace``
~~~~~~~~~~~~~~~~~~~~
.. attribute:: CharField.strip_whitespace
If ``True``, leading and trailing whitespace will be removed from the value
before saving. Defaults to ``False``.
Example::
name = models.CharField(max_length=100, strip_whitespace=True)
6. Create Pull Request
# Commit changes
git add .
git commit -m "Fixed #12345 -- Added strip_whitespace option to CharField"
# Push to your fork
git push origin ticket_12345
# Create PR on GitHub
# Reference the Trac ticket in description
Commit Messages
# Format: Fixed #ticket -- Short description
git commit -m "Fixed #12345 -- Added strip_whitespace option to CharField"
# For new features
git commit -m "Added support for PostgreSQL range fields"
# For documentation
git commit -m "Docs -- Clarified QuerySet.select_related() behavior"
# For tests
git commit -m "Tests -- Added coverage for Model.save() with update_fields"
Code Quality
# Good: Clear, documented, tested
class MyFeature:
"""
Brief description of the feature.
Detailed explanation of how it works and why.
"""
def process(self, data):
"""
Process the given data.
Args:
data: The data to process
Returns:
Processed data
Raises:
ValueError: If data is invalid
"""
if not data:
raise ValueError("Data cannot be empty")
return self._transform(data)
# Bad: Unclear, undocumented
class MyFeature:
def process(self, data):
return self._transform(data)
Backwards Compatibility
# Good: Maintains backwards compatibility
def my_function(arg1, arg2=None, new_arg=None):
"""
New parameter added with default value.
"""
if new_arg is None:
new_arg = 'default'
# Implementation
# Bad: Breaks existing code
def my_function(arg1, new_arg, arg2=None):
"""
New required parameter breaks existing calls.
"""
pass
1. Bug Fixes
# Before: Bug in QuerySet.distinct()
def distinct(self, *field_names):
if field_names:
# Bug: Doesn't validate field names
return self._clone()
# After: Fixed validation
def distinct(self, *field_names):
if field_names:
# Validate field names exist
self._validate_field_names(field_names)
return self._clone()
2. New Features
# Adding JSONField support for SQLite
# django/db/backends/sqlite3/features.py
class DatabaseFeatures(BaseDatabaseFeatures):
supports_json_field = True # New in SQLite 3.38+
@cached_property
def supports_json_field_contains(self):
return self.connection.sqlite_version_info >= (3, 38, 0)
3. Performance Improvements
# Before: N+1 query problem
def get_articles_with_authors():
articles = Article.objects.all()
for article in articles:
print(article.author.name) # Queries for each article
# After: Optimized with select_related
def get_articles_with_authors():
articles = Article.objects.select_related('author')
for article in articles:
print(article.author.name) # Single query
4. Documentation
.. docs/topics/db/queries.txt
Retrieving specific objects with get()
=======================================
:meth:`~django.db.models.query.QuerySet.get` returns a single object matching
the given lookup parameters::
>>> Entry.objects.get(id=1)
<Entry: First entry>
If no object is found, :meth:`~django.db.models.query.QuerySet.get` raises
:exc:`~django.core.exceptions.ObjectDoesNotExist`::
>>> Entry.objects.get(id=999)
Traceback (most recent call last):
...
DoesNotExist: Entry matching query does not exist.
5. Translations
# Update translation files
cd django/conf/locale/es/LC_MESSAGES
# Edit django.po
msgid "Enter a valid email address."
msgstr "Introduce una dirección de correo electrónico válida."
# Compile translations
python manage.py compilemessages
Django follows a predictable release schedule:
Version X.0 (December)
├── Alpha (October)
├── Beta (November)
├── RC (November)
└── Final (December)
Version X.1 (April)
├── Alpha (February)
├── Beta (March)
├── RC (March)
└── Final (April)
Version X.2 (August) - LTS every 3 years
├── Alpha (June)
├── Beta (July)
├── RC (July)
└── Final (August)
Django's deprecation policy:
# Version 4.0: Feature deprecated
import warnings
def old_function():
warnings.warn(
'old_function() is deprecated and will be removed in Django 5.0. '
'Use new_function() instead.',
RemovedInDjango50Warning,
stacklevel=2
)
# Old implementation
# Version 5.0: Feature removed
# old_function() no longer exists
Django Enhancement Proposals (DEPs)
Major changes require a DEP:
# DEP 0001: Example Enhancement
## Abstract
Brief summary of the proposal.
## Motivation
Why is this change needed?
## Specification
Detailed technical specification.
## Backwards Compatibility
How does this affect existing code?
## Implementation
Implementation plan and timeline.
## Copyright
This document is placed in the public domain.
# myproject/db_backend/base.py
from django.db.backends.postgresql import base
class DatabaseWrapper(base.DatabaseWrapper):
"""
Custom PostgreSQL backend with additional features.
"""
vendor = 'postgresql_custom'
def get_connection_params(self):
params = super().get_connection_params()
# Add custom connection parameters
params['options'] = '-c statement_timeout=5000'
return params
# django/template/defaulttags.py (simplified)
from django import template
register = template.Library()
@register.tag
def custom_tag(parser, token):
"""
Custom template tag implementation.
"""
try:
tag_name, arg = token.split_contents()
except ValueError:
raise template.TemplateSyntaxError(
f"{token.contents.split()[0]} tag requires exactly one argument"
)
return CustomNode(arg)
class CustomNode(template.Node):
def __init__(self, arg):
self.arg = template.Variable(arg)
def render(self, context):
value = self.arg.resolve(context)
return f"Processed: {value}"
# myapp/fields.py
from django.db import models
from django.core import checks
class UpperCaseCharField(models.CharField):
"""
CharField that stores values in uppercase.
"""
description = "CharField that converts to uppercase"
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
def get_prep_value(self, value):
value = super().get_prep_value(value)
if value is not None:
return value.upper()
return value
def from_db_value(self, value, expression, connection):
if value is None:
return value
return value.upper()
def check(self, **kwargs):
return [
*super().check(**kwargs),
*self._check_max_length_attribute(**kwargs),
]
def _check_max_length_attribute(self, **kwargs):
if self.max_length is None:
return [
checks.Error(
'UpperCaseCharFields must define a max_length attribute.',
obj=self,
id='fields.E120',
)
]
return []
External Packages and Ecosystem
Explore the rich Django ecosystem, popular third-party packages, and best practices for selecting and integrating external tools into your Django projects.
Microservices with Django
Welcome to the comprehensive guide on building microservices with Django. This chapter explores how to architect, develop, deploy, and maintain microservices using Django as your primary framework.