Sites

Django’s sites system is a generic framework that lets you operate multiple Web sites from the same database and Django project. This is an abstract concept, and it can be tricky to understand, so we’ll start with a couple of scenarios where it would be useful.

Scenario 1: Reusing Data on Multiple Sites – As we explained in earlier chapter, the Django-powered sites LJWorld.com and Lawrence.com are operated by the same news organization: the Lawrence Journal-World newspaper in Lawrence, Kansas. LJWorld.com focuses on news, while Lawrence.com focuses on local entertainment. But sometimes editors want to publish an article on both sites.

The brain-dead way of solving the problem would be to use a separate database for each site and to require site producers to publish the same story twice: once for LJWorld.com and again for Lawrence.com. But that’s inefficient for site producers, and it’s redundant to store multiple copies of the same story in the database. The better solution? Both sites use the same article database, and an article is associated with one or more sites via a many-to-many relationship. The Django sites framework provides the database table to which articles can be related. It’s a hook for associating data with one or more “sites.”

Scenario 2: Storing Your Site Name/Domain in One Place – LJWorld.com and Lawrence.com both have e-mail alert functionality, which lets readers sign up to get notifications when news happens. It’s pretty basic: a reader signs up on a Web form, and he immediately gets an e-mail saying, “Thanks for your subscription.”

It would be inefficient and redundant to implement this signup-processing code twice, so the sites use the same code behind the scenes. But the “Thank you for your subscription” notice needs to be different for each site. By using Site objects, we can abstract the thank-you notice to use the values of the current site’s name (e.g., ‘LJWorld.com’) and domain (e.g., ‘www.ljworld.com’). The Django sites framework provides a place for you to store the name and domain for each site in your Django project, which means you can reuse those values in a generic way.

How to Use the Sites Framework – The sites framework is more a series of conventions than a framework. The whole thing is based on two simple concepts:

  •  The Site model, found in django.contrib.sites, has domain and name fields.
  • The SITE_ID setting specifies the database ID of the Site object associated with that particular settings file.

How you use these two concepts is up to you, but Django uses them in a couple of ways automatically via simple conventions. To install the sites application, follow these steps:

  • Add ‘django.contrib.sites’ to your INSTALLED_APPS.
  • Run the command manage.py syncdb to install the django_site table into your database.
  • Add one or more Site objects, either through the Django admin site or via the Python API. Create a Site object for each site/domain that this Django project powers.
  • Define the SITE_ID setting in each of your settings files. This value should be the database ID of the Site object for the site powered by that settings file.

The Sites Framework’s Capabilities – The sections that follow describe the various things you can do with the sites framework.

Reusing Data on Multiple Sites – To reuse data on multiple sites, as explained in the first scenario, just create a ManyToManyField to Site in your models, for example:

from django.db import models
from django.contrib.sites.models import Site
class Article(models.Model):
headline = models.CharField(maxlength=200)
# …
sites = models.ManyToManyField(Site)

That’s the infrastructure you need to associate articles with multiple sites in your database. With that in place, you can reuse the same Django view code for multiple sites. Continuing the Article model example, here’s what an article_detail view might look like:

from django.conf import settings
def article_detail(request, article_id):
try:
a = Article.objects.get(id=article_id, sites__id=settings.SITE_ID)
except Article.DoesNotExist:
raise Http404
# …

This view function is reusable because it checks the article’s site dynamically, according to the value of the SITE_ID setting. For example, say LJWorld.com’s settings file has a SITE_ID set to 1, and Lawrence.com’s settings file has a SITE_ID set to 2. If this view is called when LJWorld.com’s settings file is active, then it will limit the article lookup to articles in which the list of sites includes LJWorld.com.

Associating Content with a Single Site – Similarly, you can associate a model to the Site model in a many-to-one relationship using ForeignKey. For example, if an article is allowed on only a single site, you could use a model like this:

from django.db import models
from django.contrib.sites.models import Site
class Article(models.Model):
headline = models.CharField(maxlength=200)
# …
site = models.ForeignKey(Site)

This has the same benefits as described in the last section.

Hooking Into the Current Site from Views – On a lower level, you can use the sites framework in your Django views to do particular things based on the site in which the view is being called, for example:

from django.conf import settings

def my_view(request):
if settings.SITE_ID == 3:
# Do something.
else:
# Do something else.

Of course, it’s ugly to hard-code the site IDs like that. A slightly cleaner way of accomplishing the same thing is to check the current site’s domain:

from django.conf import settings
from django.contrib.sites.models import Site

def my_view(request):
current_site = Site.objects.get(id=settings.SITE_ID)
if current_site.domain == ‘foo.com’:
# Do something
else:
# Do something else.

The idiom of retrieving the Site object for the value of settings.SITE_ID is quite common, so the Site model’s manager (Site.objects) has a get_current() method. This example is equivalent to the previous one:

from django.contrib.sites.models import Site
def my_view(request):
current_site = Site.objects.get_current()
if current_site.domain == ‘foo.com’:
# Do something
else:
# Do something else.

Note – In this final example, you don’t have to import django.conf.settings.

Getting the Current Domain for Display – For a DRY (Don’t Repeat Yourself) approach to storing your site’s name and domain name, as explained in “Scenario 2: Storing Your Site Name/Domain in One Place,” just reference the name and domain of the current Site object. For example:

from django.contrib.sites.models import Site
from django.core.mail import send_mail

def register_for_newsletter(request):
# Check form values, etc., and subscribe the user.
# …
current_site = Site.objects.get_current()
send_mail(‘Thanks for subscribing to %s alerts’ % current_site.name,
‘Thanks for your subscription. We appreciate it.\n\n-The %s team.’ % current_site.name,
‘editor@%s’ % current_site.domain,
[user_email])
# …
Continuing our ongoing example of LJWorld.com and Lawrence.com, on Lawrence.com this e-mail has the subject line “Thanks for subscribing to lawrence.com alerts.” On LJWorld.com, the e-mail has the subject line “Thanks for subscribing to LJWorld.com alerts.” This same site-specific behavior is applied to the e-mails’ message body.

An even more flexible (but more heavyweight) way of doing this would be to use Django’s template system. Assuming Lawrence.com and LJWorld.com have different template directories (TEMPLATE_DIRS), you could simply delegate to the template system like so:

from django.core.mail import send_mail
from django.template import loader, Context

def register_for_newsletter(request):
# Check form values, etc., and subscribe the user.
# …
subject = loader.get_template(‘alerts/subject.txt’).render(Context({}))
message = loader.get_template(‘alerts/message.txt’).render(Context({}))
send_mail(subject, message, ‘[email protected]’, [user_email])
# …

In this case, you have to create subject.txt and message.txt templates in both the LJWorld.com and Lawrence.com template directories. As mentioned previously, that gives you more flexibility, but it’s also more complex. It’s a good idea to exploit the Site objects as much as possible to remove unneeded complexity and redundancy.

Getting the Current Domain for Full URLs – Django’s get_absolute_url() convention is nice for getting your objects’ URLs without the domain name, but in some cases you might want to display the full URL — with http:// and the domain and everything — for an object. To do this, you can use the sites framework. Here’s a simple example:

>>> from django.contrib.sites.models import Site
>>> obj = MyModel.objects.get(id=3)
>>> obj.get_absolute_url()
‘/mymodel/objects/3/’
>>> Site.objects.get_current().domain
‘example.com’
>>> ‘http://%s%s’ % (Site.objects.get_current().domain, obj.get_absolute_url())
‘http://example.com/mymodel/objects/3/’

CurrentSiteManager – If Site“s play a key role in your application, consider using the helpful “CurrentSiteManager in your model(s). It’s a model manager that automatically filters its queries to include only objects associated with the current Site. Use CurrentSiteManager by adding it to your model explicitly. For example:

from django.db import models
from django.contrib.sites.models import Site
from django.contrib.sites.managers import CurrentSiteManager

class Photo(models.Model):
photo = models.FileField(upload_to=’/home/photos’)
photographer_name = models.CharField(maxlength=100)
pub_date = models.DateField()
site = models.ForeignKey(Site)
objects = models.Manager()
on_site = CurrentSiteManager()

With this model, Photo.objects.all() will return all Photo objects in the database, but Photo.on_site.all() will return only the Photo objects associated with the current site, according to the SITE_ID setting. In other words, these two statements are equivalent:

Photo.objects.filter(site=settings.SITE_ID)
Photo.on_site.all()

How did CurrentSiteManager know which field of Photo was the Site? It defaults to looking for a field called site. If your model has a ForeignKey or ManyToManyField called something other than site, you need to explicitly pass that as the parameter to CurrentSiteManager. The following model, which has a field called publish_on, demonstrates this:

from django.db import models
from django.contrib.sites.models import Site
from django.contrib.sites.managers import CurrentSiteManager

class Photo(models.Model):
photo = models.FileField(upload_to=’/home/photos’)
photographer_name = models.CharField(maxlength=100)
pub_date = models.DateField()
publish_on = models.ForeignKey(Site)
objects = models.Manager()
on_site = CurrentSiteManager(‘publish_on’)

If you attempt to use CurrentSiteManager and pass a field name that doesn’t exist, Django will raise a ValueError.

Note – You’ll probably want to keep a normal (non-site-specific) Manager on your model, even if you use CurrentSiteManager. As explained in Appendix B, if you define a manager manually, then Django won’t create the automatic objects = models.Manager() manager for you.
Also, certain parts of Django — namely, the Django admin site and generic views — use whichever manager is defined first in the model, so if you want your admin site to have access to all objects (not just site-specific ones), put objects = models.Manager() in your model, before you define CurrentSiteManager.

How Django Uses the Sites Framework – Although it’s not required that you use the sites framework, it’s strongly encouraged, because Django takes advantage of it in a few places. Even if your Django installation is powering only a single site, you should take a few seconds to create the site object with your domain and name, and point to its ID in your SITE_ID setting. Here’s how Django uses the sites framework:

  • In the redirects framework, each redirect object is associated with a particular site. When Django searches for a redirect, it takes into account the current SITE_ID.
  • In the comments framework, each comment is associated with a particular site. When a comment is posted, its site is set to the current SITE_ID, and when comments are listed via the appropriate template tag, only the comments for the current site are displayed.
  • In the flatpages framework, each flatpage is associated with a particular site. When a flatpage is created, you specify its site, and the flatpage middleware checks the current SITE_ID in retrieving flatpages to display.
  • In the syndication framework, the templates for title and description automatically have access to a variable {{ site }}, which is the Site object representing the current site. Also, the hook for providing item URLs will use the domain from the current Site object if you don’t specify a fully qualified domain.
  • In the authentication framework, the django.contrib.auth.views.login view passes the current Site name to the template as {{ site_name }}.

Back to Tutorial

Share this post
[social_warfare]
The Django Standard Library
Introduction to Tomcat Logging

Get industry recognized certification – Contact us

keyboard_arrow_up