Skip to main content

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_id and _get_site_by_request check the cache before querying the database.
  • Invalidation: The cache is global within the process. If you update Site objects directly via the database, you must call Site.objects.clear_cache() to ensure the application sees the changes.

Configuration Requirements

To use the sites framework effectively:

  1. Add 'django.contrib.sites' to INSTALLED_APPS.
  2. Define a SITE_ID in your settings file (e.g., SITE_ID = 1).
  3. Ensure SitesConfig is allowed to run its post_migrate signal 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.