PageRenderTime 141ms CodeModel.GetById 132ms app.highlight 3ms RepoModel.GetById 1ms app.codeStats 0ms

/docs/ref/contrib/sites.txt

https://code.google.com/p/mango-py/
Plain Text | 449 lines | 338 code | 111 blank | 0 comment | 0 complexity | 91346811eb90a1838bc7a5fb2d72a282 MD5 | raw file
  1=====================
  2The "sites" framework
  3=====================
  4
  5.. module:: django.contrib.sites
  6   :synopsis: Lets you operate multiple Web sites from the same database and
  7              Django project
  8
  9.. currentmodule:: django.contrib.sites.models
 10
 11Django comes with an optional "sites" framework. It's a hook for associating
 12objects and functionality to particular Web sites, and it's a holding place for
 13the domain names and "verbose" names of your Django-powered sites.
 14
 15Use it if your single Django installation powers more than one site and you
 16need to differentiate between those sites in some way.
 17
 18The whole sites framework is based on a simple model:
 19
 20.. class:: Site
 21
 22    A model for storing the ``domain`` and ``name`` attributes of a Web site.
 23    The :setting:`SITE_ID` setting specifies the database ID of the
 24    :class:`~django.contrib.sites.models.Site` object associated with that
 25    particular settings file.
 26
 27    .. attribute:: domain
 28
 29        The domain name associated with the Web site.
 30
 31    .. attribute:: name
 32
 33        A human-readable "verbose" name for the Web site.
 34
 35
 36How you use this is up to you, but Django uses it in a couple of ways
 37automatically via simple conventions.
 38
 39Example usage
 40=============
 41
 42Why would you use sites? It's best explained through examples.
 43
 44Associating content with multiple sites
 45---------------------------------------
 46
 47The Django-powered sites LJWorld.com_ and Lawrence.com_ are operated by the
 48same news organization -- the Lawrence Journal-World newspaper in Lawrence,
 49Kansas. LJWorld.com focuses on news, while Lawrence.com focuses on local
 50entertainment. But sometimes editors want to publish an article on *both*
 51sites.
 52
 53The brain-dead way of solving the problem would be to require site producers to
 54publish the same story twice: once for LJWorld.com and again for Lawrence.com.
 55But that's inefficient for site producers, and it's redundant to store
 56multiple copies of the same story in the database.
 57
 58The better solution is simple: Both sites use the same article database, and an
 59article is associated with one or more sites. In Django model terminology,
 60that's represented by a :class:`~django.db.models.ManyToManyField` in the
 61``Article`` model::
 62
 63    from django.db import models
 64    from django.contrib.sites.models import Site
 65
 66    class Article(models.Model):
 67        headline = models.CharField(max_length=200)
 68        # ...
 69        sites = models.ManyToManyField(Site)
 70
 71This accomplishes several things quite nicely:
 72
 73    * It lets the site producers edit all content -- on both sites -- in a
 74      single interface (the Django admin).
 75
 76    * It means the same story doesn't have to be published twice in the
 77      database; it only has a single record in the database.
 78
 79    * It lets the site developers use the same Django view code for both sites.
 80      The view code that displays a given story just checks to make sure the
 81      requested story is on the current site. It looks something like this::
 82
 83          from django.conf import settings
 84
 85          def article_detail(request, article_id):
 86              try:
 87                  a = Article.objects.get(id=article_id, sites__id__exact=settings.SITE_ID)
 88              except Article.DoesNotExist:
 89                  raise Http404
 90              # ...
 91
 92.. _ljworld.com: http://www.ljworld.com/
 93.. _lawrence.com: http://www.lawrence.com/
 94
 95Associating content with a single site
 96--------------------------------------
 97
 98Similarly, you can associate a model to the
 99:class:`~django.contrib.sites.models.Site`
100model in a many-to-one relationship, using
101:class:`~django.db.models.ForeignKey`.
102
103For example, if an article is only allowed on a single site, you'd use a model
104like this::
105
106    from django.db import models
107    from django.contrib.sites.models import Site
108
109    class Article(models.Model):
110        headline = models.CharField(max_length=200)
111        # ...
112        site = models.ForeignKey(Site)
113
114This has the same benefits as described in the last section.
115
116.. _hooking-into-current-site-from-views:
117
118Hooking into the current site from views
119----------------------------------------
120
121You can use the sites framework in your Django views to do
122particular things based on the site in which the view is being called.
123For example::
124
125    from django.conf import settings
126
127    def my_view(request):
128        if settings.SITE_ID == 3:
129            # Do something.
130        else:
131            # Do something else.
132
133Of course, it's ugly to hard-code the site IDs like that. This sort of
134hard-coding is best for hackish fixes that you need done quickly. A slightly
135cleaner way of accomplishing the same thing is to check the current site's
136domain::
137
138    from django.conf import settings
139    from django.contrib.sites.models import Site
140
141    def my_view(request):
142        current_site = Site.objects.get(id=settings.SITE_ID)
143        if current_site.domain == 'foo.com':
144            # Do something
145        else:
146            # Do something else.
147
148The idiom of retrieving the :class:`~django.contrib.sites.models.Site` object
149for the value of :setting:`settings.SITE_ID <SITE_ID>` is quite common, so
150the :class:`~django.contrib.sites.models.Site` model's manager has a
151``get_current()`` method. This example is equivalent to the previous one::
152
153    from django.contrib.sites.models import Site
154
155    def my_view(request):
156        current_site = Site.objects.get_current()
157        if current_site.domain == 'foo.com':
158            # Do something
159        else:
160            # Do something else.
161
162.. versionchanged:: 1.3
163
164For code which relies on getting the current domain but cannot be certain
165that the sites framework will be installed for any given project, there is a
166utility function :func:`~django.contrib.sites.models.get_current_site` that
167takes a request object as an argument and returns either a Site instance (if
168the sites framework is installed) or a RequestSite instance (if it is not).
169This allows loose coupling with the sites framework and provides a usable
170fallback for cases where it is not installed.
171
172.. versionadded:: 1.3
173
174.. function:: get_current_site(request)
175
176    Checks if contrib.sites is installed and returns either the current
177    :class:`~django.contrib.sites.models.Site` object or a 
178    :class:`~django.contrib.sites.models.RequestSite` object based on
179    the request.
180
181Getting the current domain for display
182--------------------------------------
183
184LJWorld.com and Lawrence.com both have e-mail alert functionality, which lets
185readers sign up to get notifications when news happens. It's pretty basic: A
186reader signs up on a Web form, and he immediately gets an e-mail saying,
187"Thanks for your subscription."
188
189It'd be inefficient and redundant to implement this signup-processing code
190twice, so the sites use the same code behind the scenes. But the "thank you for
191signing up" notice needs to be different for each site. By using
192:class:`~django.contrib.sites.models.Site`
193objects, we can abstract the "thank you" notice to use the values of the
194current site's :attr:`~django.contrib.sites.models.Site.name` and
195:attr:`~django.contrib.sites.models.Site.domain`.
196
197Here's an example of what the form-handling view looks like::
198
199    from django.contrib.sites.models import Site
200    from django.core.mail import send_mail
201
202    def register_for_newsletter(request):
203        # Check form values, etc., and subscribe the user.
204        # ...
205
206        current_site = Site.objects.get_current()
207        send_mail('Thanks for subscribing to %s alerts' % current_site.name,
208            'Thanks for your subscription. We appreciate it.\n\n-The %s team.' % current_site.name,
209            'editor@%s' % current_site.domain,
210            [user.email])
211
212        # ...
213
214On Lawrence.com, this e-mail has the subject line "Thanks for subscribing to
215lawrence.com alerts." On LJWorld.com, the e-mail has the subject "Thanks for
216subscribing to LJWorld.com alerts." Same goes for the e-mail's message body.
217
218Note that an even more flexible (but more heavyweight) way of doing this would
219be to use Django's template system. Assuming Lawrence.com and LJWorld.com have
220different template directories (:setting:`TEMPLATE_DIRS`), you could simply farm out
221to the template system like so::
222
223    from django.core.mail import send_mail
224    from django.template import loader, Context
225
226    def register_for_newsletter(request):
227        # Check form values, etc., and subscribe the user.
228        # ...
229
230        subject = loader.get_template('alerts/subject.txt').render(Context({}))
231        message = loader.get_template('alerts/message.txt').render(Context({}))
232        send_mail(subject, message, 'editor@ljworld.com', [user.email])
233
234        # ...
235
236In this case, you'd have to create :file:`subject.txt` and :file:`message.txt` template
237files for both the LJWorld.com and Lawrence.com template directories. That
238gives you more flexibility, but it's also more complex.
239
240It's a good idea to exploit the :class:`~django.contrib.sites.models.Site`
241objects as much as possible, to remove unneeded complexity and redundancy.
242
243Getting the current domain for full URLs
244----------------------------------------
245
246Django's ``get_absolute_url()`` convention is nice for getting your objects'
247URL without the domain name, but in some cases you might want to display the
248full URL -- with ``http://`` and the domain and everything -- for an object.
249To do this, you can use the sites framework. A simple example::
250
251    >>> from django.contrib.sites.models import Site
252    >>> obj = MyModel.objects.get(id=3)
253    >>> obj.get_absolute_url()
254    '/mymodel/objects/3/'
255    >>> Site.objects.get_current().domain
256    'example.com'
257    >>> 'http://%s%s' % (Site.objects.get_current().domain, obj.get_absolute_url())
258    'http://example.com/mymodel/objects/3/'
259
260Caching the current ``Site`` object
261===================================
262
263As the current site is stored in the database, each call to
264``Site.objects.get_current()`` could result in a database query. But Django is a
265little cleverer than that: on the first request, the current site is cached, and
266any subsequent call returns the cached data instead of hitting the database.
267
268If for any reason you want to force a database query, you can tell Django to
269clear the cache using ``Site.objects.clear_cache()``::
270
271    # First call; current site fetched from database.
272    current_site = Site.objects.get_current()
273    # ...
274
275    # Second call; current site fetched from cache.
276    current_site = Site.objects.get_current()
277    # ...
278
279    # Force a database query for the third call.
280    Site.objects.clear_cache()
281    current_site = Site.objects.get_current()
282
283.. currentmodule:: django.contrib.sites.managers
284
285The ``CurrentSiteManager``
286==========================
287
288.. class:: CurrentSiteManager
289
290If :class:`~django.contrib.sites.models.Site` plays a key role in your
291application, consider using the helpful
292:class:`~django.contrib.sites.managers.CurrentSiteManager` in your
293model(s). It's a model :doc:`manager </topics/db/managers>` that
294automatically filters its queries to include only objects associated
295with the current :class:`~django.contrib.sites.models.Site`.
296
297Use :class:`~django.contrib.sites.managers.CurrentSiteManager` by adding it to
298your model explicitly. For example::
299
300    from django.db import models
301    from django.contrib.sites.models import Site
302    from django.contrib.sites.managers import CurrentSiteManager
303
304    class Photo(models.Model):
305        photo = models.FileField(upload_to='/home/photos')
306        photographer_name = models.CharField(max_length=100)
307        pub_date = models.DateField()
308        site = models.ForeignKey(Site)
309        objects = models.Manager()
310        on_site = CurrentSiteManager()
311
312With this model, ``Photo.objects.all()`` will return all ``Photo`` objects in
313the database, but ``Photo.on_site.all()`` will return only the ``Photo`` objects
314associated with the current site, according to the :setting:`SITE_ID` setting.
315
316Put another way, these two statements are equivalent::
317
318    Photo.objects.filter(site=settings.SITE_ID)
319    Photo.on_site.all()
320
321How did :class:`~django.contrib.sites.managers.CurrentSiteManager`
322know which field of ``Photo`` was the
323:class:`~django.contrib.sites.models.Site`? By default,
324:class:`~django.contrib.sites.managers.CurrentSiteManager` looks for a
325either a :class:`~django.db.models.ForeignKey` called
326``site`` or a
327:class:`~django.db.models.ManyToManyField` called
328``sites`` to filter on. If you use a field named something other than
329``site`` or ``sites`` to identify which
330:class:`~django.contrib.sites.models.Site` objects your object is
331related to, then you need to explicitly pass the custom field name as
332a parameter to
333:class:`~django.contrib.sites.managers.CurrentSiteManager` on your
334model. The following model, which has a field called ``publish_on``,
335demonstrates this::
336
337    from django.db import models
338    from django.contrib.sites.models import Site
339    from django.contrib.sites.managers import CurrentSiteManager
340
341    class Photo(models.Model):
342        photo = models.FileField(upload_to='/home/photos')
343        photographer_name = models.CharField(max_length=100)
344        pub_date = models.DateField()
345        publish_on = models.ForeignKey(Site)
346        objects = models.Manager()
347        on_site = CurrentSiteManager('publish_on')
348
349If you attempt to use :class:`~django.contrib.sites.managers.CurrentSiteManager`
350and pass a field name that doesn't exist, Django will raise a ``ValueError``.
351
352Finally, note that you'll probably want to keep a normal
353(non-site-specific) ``Manager`` on your model, even if you use
354:class:`~django.contrib.sites.managers.CurrentSiteManager`. As
355explained in the :doc:`manager documentation </topics/db/managers>`, if
356you define a manager manually, then Django won't create the automatic
357``objects = models.Manager()`` manager for you. Also note that certain
358parts of Django -- namely, the Django admin site and generic views --
359use whichever manager is defined *first* in the model, so if you want
360your admin site to have access to all objects (not just site-specific
361ones), put ``objects = models.Manager()`` in your model, before you
362define :class:`~django.contrib.sites.managers.CurrentSiteManager`.
363
364How Django uses the sites framework
365===================================
366
367Although it's not required that you use the sites framework, it's strongly
368encouraged, because Django takes advantage of it in a few places. Even if your
369Django installation is powering only a single site, you should take the two
370seconds to create the site object with your ``domain`` and ``name``, and point
371to its ID in your :setting:`SITE_ID` setting.
372
373Here's how Django uses the sites framework:
374
375* In the :mod:`redirects framework <django.contrib.redirects>`, each
376  redirect object is associated with a particular site. When Django searches
377  for a redirect, it takes into account the current :setting:`SITE_ID`.
378
379* In the comments framework, each comment is associated with a particular
380  site. When a comment is posted, its
381  :class:`~django.contrib.sites.models.Site` is set to the current
382  :setting:`SITE_ID`, and when comments are listed via the appropriate
383  template tag, only the comments for the current site are displayed.
384
385* In the :mod:`flatpages framework <django.contrib.flatpages>`, each
386  flatpage is associated with a particular site. When a flatpage is created,
387  you specify its :class:`~django.contrib.sites.models.Site`, and the
388  :class:`~django.contrib.flatpages.middleware.FlatpageFallbackMiddleware`
389  checks the current :setting:`SITE_ID` in retrieving flatpages to display.
390
391* In the :mod:`syndication framework <django.contrib.syndication>`, the
392  templates for ``title`` and ``description`` automatically have access to a
393  variable ``{{ site }}``, which is the
394  :class:`~django.contrib.sites.models.Site` object representing the current
395  site. Also, the hook for providing item URLs will use the ``domain`` from
396  the current :class:`~django.contrib.sites.models.Site` object if you don't
397  specify a fully-qualified domain.
398
399* In the :mod:`authentication framework <django.contrib.auth>`, the
400  :func:`django.contrib.auth.views.login` view passes the current
401  :class:`~django.contrib.sites.models.Site` name to the template as
402  ``{{ site_name }}``.
403
404* The shortcut view (``django.views.defaults.shortcut``) uses the domain
405  of the current :class:`~django.contrib.sites.models.Site` object when
406  calculating an object's URL.
407
408* In the admin framework, the "view on site" link uses the current
409  :class:`~django.contrib.sites.models.Site` to work out the domain for the
410  site that it will redirect to.
411
412.. currentmodule:: django.contrib.sites.models
413
414``RequestSite`` objects
415=======================
416
417.. _requestsite-objects:
418
419Some :doc:`django.contrib </ref/contrib/index>` applications take advantage of
420the sites framework but are architected in a way that doesn't *require* the
421sites framework to be installed in your database. (Some people don't want to, or
422just aren't *able* to install the extra database table that the sites framework
423requires.) For those cases, the framework provides a
424:class:`~django.contrib.sites.models.RequestSite` class, which can be used as a
425fallback when the database-backed sites framework is not available.
426
427.. class:: RequestSite
428
429    A class that shares the primary interface of
430    :class:`~django.contrib.sites.models.Site` (i.e., it has
431    ``domain`` and ``name`` attributes) but gets its data from a Django
432    :class:`~django.http.HttpRequest` object rather than from a database.
433
434    The ``save()`` and ``delete()`` methods raise ``NotImplementedError``.
435
436    .. method:: __init__(request)
437
438        Sets the ``name`` and ``domain`` attributes to the value of
439        :meth:`~django.http.HttpRequest.get_host`.
440        
441
442A :class:`~django.contrib.sites.models.RequestSite` object has a similar
443interface to a normal :class:`~django.contrib.sites.models.Site` object, except
444its :meth:`~django.contrib.sites.models.RequestSite.__init__()` method takes an
445:class:`~django.http.HttpRequest` object. It's able to deduce the
446``domain`` and ``name`` by looking at the request's domain. It has ``save()``
447and ``delete()`` methods to match the interface of
448:class:`~django.contrib.sites.models.Site`, but the methods raise
449``NotImplementedError``.