mirror of
				https://github.com/django/django.git
				synced 2025-10-31 01:25:32 +00:00 
			
		
		
		
	git-svn-id: http://code.djangoproject.com/svn/django/trunk@9330 bcc190cf-cafb-0310-a4f2-bffc1f526a37
		
			
				
	
	
		
			402 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			Plaintext
		
	
	
	
	
	
			
		
		
	
	
			402 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			Plaintext
		
	
	
	
	
	
| .. _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`.
 |