Multi-site Support with the Sites Framework
The sites framework allows you to manage multiple websites from a single Django installation. By associating content with a specific Site instance, you can serve different data, templates, and configurations based on the domain name or a configured site ID.
The Site Model
The core of the framework is the Site model defined in django/contrib/sites/models.py. It is a simple registry of domains:
class Site(models.Model):
domain = models.CharField(
_("domain name"),
max_length=100,
validators=[_simple_domain_name_validator],
unique=True,
)
name = models.CharField(_("display name"), max_length=50)
objects = SiteManager()
# ...
Each Site has a domain (e.g., example.com) and a name for display purposes. When you first migrate your database with django.contrib.sites in your INSTALLED_APPS, the SitesConfig.ready() method triggers create_default_site from django/contrib/sites/management.py, which creates an initial "example.com" site with a primary key matching your SITE_ID setting (defaulting to 1).
Identifying the Current Site
The framework provides multiple ways to determine which site is currently being accessed.
Using SITE_ID
The most common method is setting SITE_ID in your project's settings. The SiteManager.get_current() method uses this ID to retrieve the site:
# django/contrib/sites/models.py
def get_current(self, request=None):
from django.conf import settings
if getattr(settings, "SITE_ID", ""):
site_id = settings.SITE_ID
return self._get_site_by_id(site_id)
# ...
Request-based Detection
If SITE_ID is not set, or if you need to detect the site dynamically, SiteManager can look up the site based on the Host header of the incoming request:
def _get_site_by_request(self, request):
host = request.get_host()
try:
if host not in SITE_CACHE:
SITE_CACHE[host] = self.get(domain__iexact=host)
return SITE_CACHE[host]
except Site.DoesNotExist:
# Fallback to stripping port from host
domain, port = split_domain_port(host)
# ...
The get_current_site Shortcut
The get_current_site(request) function in django/contrib/sites/shortcuts.py is the recommended way to access the current site in views. It handles cases where the sites framework might not be installed by falling back to a RequestSite object.
def get_current_site(request):
if apps.is_installed("django.contrib.sites"):
from .models import Site
return Site.objects.get_current(request)
else:
return RequestSite(request)
RequestSite (found in django/contrib/sites/requests.py) mimics the Site interface by providing domain and name attributes derived from request.get_host(), but it does not persist to the database.
Filtering Content with CurrentSiteManager
To automatically restrict model queries to the current site, you can use CurrentSiteManager in django/contrib/sites/managers.py. This manager filters querysets based on the SITE_ID setting.
Implementation Example
In tests/sites_framework/models.py, the framework demonstrates how to apply this to a model:
from django.contrib.sites.managers import CurrentSiteManager
from django.contrib.sites.models import Site
from django.db import models
class ExclusiveArticle(models.Model):
title = models.CharField(max_length=50)
site = models.ForeignKey(Site, models.CASCADE)
objects = models.Manager()
on_site = CurrentSiteManager() # Automatically filters by settings.SITE_ID
When you use ExclusiveArticle.on_site.all(), the manager automatically appends a filter for the current site:
# django/contrib/sites/managers.py
def get_queryset(self):
return (
super()
.get_queryset()
.filter(**{self._get_field_name() + "__id": settings.SITE_ID})
)
Validation and Constraints
CurrentSiteManager performs system checks to ensure the field it uses for filtering is valid. It must be either a ForeignKey or a ManyToManyField. By default, it looks for a field named site or sites, but you can specify a custom field name during initialization: CurrentSiteManager("publication_site").
Performance and Caching
To minimize database hits, SiteManager maintains an internal SITE_CACHE. Sites are cached by their ID and their domain name.
- Retrieval:
_get_site_by_idand_get_site_by_requestcheck the cache before querying the database. - Invalidation: The cache is global within the process. If you update
Siteobjects directly via the database, you must callSite.objects.clear_cache()to ensure the application sees the changes.
Configuration Requirements
To use the sites framework effectively:
- Add
'django.contrib.sites'toINSTALLED_APPS. - Define a
SITE_IDin your settings file (e.g.,SITE_ID = 1). - Ensure
SitesConfigis allowed to run itspost_migratesignal to create the initial site record.
If SITE_ID is missing and you attempt to call Site.objects.get_current() without a request object, the framework raises an ImproperlyConfigured exception.