diff --git a/docs/add_ons.txt b/docs/add_ons.txt index 813ddb2ad32..f7b3056ef0e 100644 --- a/docs/add_ons.txt +++ b/docs/add_ons.txt @@ -77,7 +77,11 @@ sites A light framework that lets you operate multiple Web sites off of the same database and Django installation. It gives you hooks for associating objects to -one or more sites. This is not yet documented. +one or more sites. + +See the `sites documentation`_. + +.. _sites documentation: http://www.djangoproject.com/documentation/sites/ syndication =========== diff --git a/docs/authentication.txt b/docs/authentication.txt index 6a0c6ffffbe..4f1276f691a 100644 --- a/docs/authentication.txt +++ b/docs/authentication.txt @@ -355,7 +355,7 @@ variables: * ``next``: The URL to redirect to after successful login. This may contain a query string, too. * ``site_name``: The name of the current ``Site``, according to the - ``SITE_ID`` setting. + ``SITE_ID`` setting. See the `site framework docs`_. Here's a sample ``registration/login.html`` template you can use as a starting point. It assumes you have a ``base.html`` template that defines a ``content`` @@ -382,6 +382,7 @@ block:: {% endblock %} .. _forms documentation: http://www.djangoproject.com/documentation/forms/ +.. _site framework docs: http://www.djangoproject.com/documentation/sites/ Limiting access to logged-in users that pass a test --------------------------------------------------- diff --git a/docs/settings.txt b/docs/settings.txt index 766f26edbbe..80000fad5b4 100644 --- a/docs/settings.txt +++ b/docs/settings.txt @@ -619,6 +619,10 @@ The ID, as an integer, of the current site in the ``django_site`` database table. This is used so that application data can hook into specific site(s) and a single database can manage content for multiple sites. +See the `site framework docs`_. + +.. _site framework docs: http://www.djangoproject.com/documentation/sites/ + TEMPLATE_CONTEXT_PROCESSORS --------------------------- diff --git a/docs/sites.txt b/docs/sites.txt new file mode 100644 index 00000000000..5ab585e77f8 --- /dev/null +++ b/docs/sites.txt @@ -0,0 +1,256 @@ +===================== +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/' + +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: http://www.djangoproject.com/documentation/redirects/ +.. _flatpages framework: http://www.djangoproject.com/documentation/flatpages/ +.. _syndication framework: http://www.djangoproject.com/documentation/syndication/ +.. _authentication framework: http://www.djangoproject.com/documentation/syndication/