You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
401 lines
16 KiB
401 lines
16 KiB
.. _ref-contrib-sites: |
|
|
|
===================== |
|
The "sites" framework |
|
===================== |
|
|
|
.. module:: django.contrib.sites |
|
:synopsis: Lets you operate multiple web sites from the same database and |
|
Django project |
|
|
|
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 a simple model: |
|
|
|
.. class:: django.contrib.sites.models.Site |
|
|
|
This model has :attr:`~django.contrib.sites.models.Site.domain` and |
|
:attr:`~django.contrib.sites.models.Site.name` fields. The :setting:`SITE_ID` |
|
setting specifies the database ID of the |
|
:class:`~django.contrib.sites.models.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 :class:`~django.db.models.ManyToManyField` in the |
|
``Article`` model:: |
|
|
|
from django.db import models |
|
from django.contrib.sites.models import Site |
|
|
|
class Article(models.Model): |
|
headline = models.CharField(max_length=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 :class:`~django.contrib.sites.models.Site` |
|
model in a many-to-one relationship, using |
|
:class:`~django.db.models.fields.related.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(max_length=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. 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 :class:`~django.contrib.sites.models.Site` object |
|
for the value of :setting:`settings.SITE_ID <SITE_ID>` is quite common, so |
|
the :class:`~django.contrib.sites.models.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 |
|
:class:`~django.contrib.sites.models.Site` |
|
objects, we can abstract the "thank you" notice to use the values of the |
|
current site's :attr:`~django.contrib.sites.models.Site.name` and |
|
:attr:`~django.contrib.sites.models.Site.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 (:setting:`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 :file:`subject.txt` and :file:`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 :class:`~django.contrib.sites.models.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/' |
|
|
|
Caching the current ``Site`` object |
|
=================================== |
|
|
|
.. versionadded:: 1.0 |
|
|
|
As the current site is stored in the database, each call to |
|
``Site.objects.get_current()`` could result in a database query. But Django is a |
|
little cleverer than that: on the first request, the current site is cached, and |
|
any subsequent call returns the cached data instead of hitting the database. |
|
|
|
If for any reason you want to force a database query, you can tell Django to |
|
clear the cache using ``Site.objects.clear_cache()``:: |
|
|
|
# First call; current site fetched from database. |
|
current_site = Site.objects.get_current() |
|
# ... |
|
|
|
# Second call; current site fetched from cache. |
|
current_site = Site.objects.get_current() |
|
# ... |
|
|
|
# Force a database query for the third call. |
|
Site.objects.clear_cache() |
|
current_site = Site.objects.get_current() |
|
|
|
The ``CurrentSiteManager`` |
|
========================== |
|
|
|
.. class:: django.contrib.sites.managers.CurrentSiteManager |
|
|
|
If :class:`~django.contrib.sites.models.Site`\s play a key role in your application, |
|
consider using the helpful |
|
:class:`~django.contrib.sites.managers.CurrentSiteManager` in your model(s). |
|
It's a model :ref:`manager <topics-db-managers>` that automatically filters |
|
its queries to include only objects associated with the current |
|
:class:`~django.contrib.sites.models.Site`. |
|
|
|
Use :class:`~django.contrib.sites.managers.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(max_length=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 :setting:`SITE_ID` setting. |
|
|
|
Put another way, these two statements are equivalent:: |
|
|
|
Photo.objects.filter(site=settings.SITE_ID) |
|
Photo.on_site.all() |
|
|
|
How did :class:`~django.contrib.sites.managers.CurrentSiteManager` know which |
|
field of ``Photo`` was the :class:`~django.contrib.sites.models.Site`? It |
|
defaults to looking for a field called |
|
:class:`~django.contrib.sites.models.Site`. If your model has a |
|
:class:`~django.db.models.fields.related.ForeignKey` or |
|
:class:`~django.db.models.fields.related.ManyToManyField` called something |
|
*other* than :class:`~django.contrib.sites.models.Site`, you need to explicitly |
|
pass that as the parameter to |
|
:class:`~django.contrib.sites.managers.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(max_length=100) |
|
pub_date = models.DateField() |
|
publish_on = models.ForeignKey(Site) |
|
objects = models.Manager() |
|
on_site = CurrentSiteManager('publish_on') |
|
|
|
If you attempt to use :class:`~django.contrib.sites.managers.CurrentSiteManager` |
|
and pass a field name that doesn't exist, Django will raise a :exc:`ValueError`. |
|
|
|
Finally, note that you'll probably want to keep a normal (non-site-specific) |
|
``Manager`` on your model, even if you use |
|
:class:`~django.contrib.sites.managers.CurrentSiteManager`. As explained |
|
in the :ref:`manager documentation <topics-db-managers>`, 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 |
|
:class:`~django.contrib.sites.managers.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 the two |
|
seconds to create the site object with your ``domain`` and ``name``, and point |
|
to its ID in your :setting:`SITE_ID` setting. |
|
|
|
Here's how Django uses the sites framework: |
|
|
|
* In the :mod:`redirects framework <django.contrib.redirects>`, each |
|
redirect object is associated with a particular site. When Django searches |
|
for a redirect, it takes into account the current :setting:`SITE_ID`. |
|
|
|
* In the comments framework, each comment is associated with a particular |
|
site. When a comment is posted, its |
|
:class:`~django.contrib.sites.models.Site` is set to the current |
|
:setting:`SITE_ID`, and when comments are listed via the appropriate |
|
template tag, only the comments for the current site are displayed. |
|
|
|
* In the :mod:`flatpages framework <django.contrib.flatpages>`, each |
|
flatpage is associated with a particular site. When a flatpage is created, |
|
you specify its :class:`~django.contrib.sites.models.Site`, and the |
|
:class:`~django.contrib.flatpages.middleware.FlatpageFallbackMiddleware` |
|
checks the current :setting:`SITE_ID` in retrieving flatpages to display. |
|
|
|
* In the :mod:`syndication framework <django.contrib.syndication>`, the |
|
templates for ``title`` and ``description`` automatically have access to a |
|
variable ``{{ site }}``, which is the |
|
:class:`~django.contrib.sites.models.Site` object representing the current |
|
site. Also, the hook for providing item URLs will use the ``domain`` from |
|
the current :class:`~django.contrib.sites.models.Site` object if you don't |
|
specify a fully-qualified domain. |
|
|
|
* In the :mod:`authentication framework <django.contrib.auth>`, the |
|
:func:`django.contrib.auth.views.login` view passes the current |
|
:class:`~django.contrib.sites.models.Site` name to the template as |
|
``{{ site_name }}``. |
|
|
|
* The shortcut view (:func:`django.views.defaults.shortcut`) uses the domain |
|
of the current :class:`~django.contrib.sites.models.Site` object when |
|
calculating an object's URL. |
|
|
|
* In the admin framework, the "view on site" link uses the current |
|
:class:`~django.contrib.sites.models.Site` to work out the domain for the |
|
site that it will redirect to. |
|
|
|
|
|
``RequestSite`` objects |
|
======================= |
|
|
|
.. _requestsite-objects: |
|
|
|
.. versionadded:: 1.0 |
|
|
|
Some :ref:`django.contrib <ref-contrib-index>` applications take advantage of |
|
the sites framework but are architected in a way that doesn't *require* the |
|
sites framework to be installed in your database. (Some people don't want to, or |
|
just aren't *able* to install the extra database table that the sites framework |
|
requires.) For those cases, the framework provides a |
|
:class:`~django.contrib.sites.models.RequestSite` class, which can be used as a |
|
fallback when the database-backed sites framework is not available. |
|
|
|
A :class:`~django.contrib.sites.models.RequestSite` object has a similar |
|
interface to a normal :class:`~django.contrib.sites.models.Site` object, except |
|
its :meth:`~django.contrib.sites.models.RequestSite.__init__()` method takes an |
|
:class:`~django.http.HttpRequest` object. It's able to deduce the |
|
:attr:`~django.contrib.sites.models.RequestSite.domain` and |
|
:attr:`~django.contrib.sites.models.RequestSite.name` by looking at the |
|
request's domain. It has :meth:`~django.contrib.sites.models.RequestSite.save()` |
|
and :meth:`~django.contrib.sites.models.RequestSite.delete()` methods to match |
|
the interface of :class:`~django.contrib.sites.models.Site`, but the methods |
|
raise :exc:`NotImplementedError`.
|
|
|