Django Internals and Contributing

Django Internals and Contributing

Understand Django's internal architecture, learn how to contribute to the Django project, and become part of the Django community.

Django Internals and Contributing

This guide explores Django's internal architecture and provides a comprehensive path to contributing to the Django project itself.

Understanding Django's Architecture

Request/Response Lifecycle

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
"""

Core Components

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

Signal System

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

Middleware Chain

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

URL Resolution

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's Source Code Structure

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

Contributing to Django

Getting Started

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

Contribution Workflow

1. Find an Issue

  • Browse Django's Trac
  • Look for "Easy pickings" tickets
  • Check "Accepted" tickets ready for work
  • Join #django-dev on IRC/Discord

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

Contribution Guidelines

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

Types of Contributions

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's Development Process

Release Cycle

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)

Feature Deprecation

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

Decision Making

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.

Core Team Structure

  • BDFL-Delegate: Makes final decisions on DEPs
  • Mergers: Review and merge pull requests
  • Releasers: Manage Django releases
  • Technical Board: Oversees project direction

Advanced Internals

Custom Database Backend

# 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

Custom Template Tags

# 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}"

Custom Model Fields

# 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 []

Resources

Official Resources

Community

  • #django-dev on Libera.Chat IRC
  • Django Discord server
  • Django Forum (Developers category)
  • DjangoCon conferences

Learning

  • Django source code reading
  • Django's test suite examples
  • Core developer blogs
  • Django Under The Hood talks

Next Steps