323 lines
13 KiB
Plaintext
323 lines
13 KiB
Plaintext
=====================
|
|
The "sites" framework
|
|
=====================
|
|
|
|
Django comes with an optional "sites" framework. It's a hook for associating
|
|
objects and functionality to particular Web sites, and it's a holding place for
|
|
the domain names and "verbose" names of your Django-powered sites.
|
|
|
|
Use it if your single Django installation powers more than one site and you
|
|
need to differentiate between those sites in some way.
|
|
|
|
The whole sites framework 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 this is up to you, but Django uses it in a couple of ways
|
|
automatically via simple conventions.
|
|
|
|
Example usage
|
|
=============
|
|
|
|
Why would you use sites? It's best explained through examples.
|
|
|
|
Associating content with multiple sites
|
|
---------------------------------------
|
|
|
|
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 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 is simple: Both sites use the same article database, and an
|
|
article is associated with one or more sites. In Django model terminology,
|
|
that's represented by a ``ManyToManyField`` in the ``Article`` model::
|
|
|
|
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)
|
|
|
|
This accomplishes several things quite nicely:
|
|
|
|
* It lets the site producers edit all content -- on both sites -- in a
|
|
single interface (the Django admin).
|
|
|
|
* It means the same story doesn't have to be published twice in the
|
|
database; it only has a single record in the database.
|
|
|
|
* It lets the site developers use the same Django view code for both sites.
|
|
The view code that displays a given story just checks to make sure the
|
|
requested story is on the current site. It looks something like this::
|
|
|
|
from django.conf import settings
|
|
|
|
def article_detail(request, article_id):
|
|
try:
|
|
a = Article.objects.get(id=article_id, sites__id__exact=settings.SITE_ID)
|
|
except Article.DoesNotExist:
|
|
raise Http404
|
|
# ...
|
|
|
|
.. _ljworld.com: http://www.ljworld.com/
|
|
.. _lawrence.com: http://www.lawrence.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 only allowed on a single site, you'd 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 what 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. This sort of
|
|
hard-coding is best for hackish fixes that you need done quickly. 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 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.
|
|
|
|
Getting the current domain for display
|
|
--------------------------------------
|
|
|
|
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'd 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
|
|
signing up" 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`` and ``domain``.
|
|
|
|
Here's an example of what the form-handling view looks like::
|
|
|
|
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])
|
|
|
|
# ...
|
|
|
|
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 "Thanks for
|
|
subscribing to LJWorld.com alerts." Same goes for the e-mail's message body.
|
|
|
|
Note that 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 farm out
|
|
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, 'editor@ljworld.com', [user.email])
|
|
|
|
# ...
|
|
|
|
In this case, you'd have to create ``subject.txt`` and ``message.txt`` template
|
|
files for both the LJWorld.com and Lawrence.com template directories. 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'
|
|
URL 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. 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/'
|
|
|
|
The ``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.
|
|
|
|
Put another way, 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``.
|
|
|
|
Finally, note that you'll probably want to keep a normal (non-site-specific)
|
|
``Manager`` on your model, even if you use ``CurrentSiteManager``. As explained
|
|
in the `manager documentation`_, if you define a manager manually, then Django
|
|
won't create the automatic ``objects = models.Manager()`` manager for you.
|
|
Also, note that 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``.
|
|
|
|
.. _manager: ../model-api/#managers
|
|
.. _manager documentation: ../model-api/#managers
|
|
|
|
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 the two
|
|
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 ``FlatpageFallbackMiddleware`` 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 }}}``.
|
|
|
|
* The shortcut view (``django.views.defaults.shortcut``) uses the domain of
|
|
the current ``Site`` object when calculating an object's URL.
|
|
|
|
.. _redirects framework: ../redirects/
|
|
.. _flatpages framework: ../flatpages/
|
|
.. _syndication framework: ../syndication/
|
|
.. _authentication framework: ../authentication/
|