|
1 ===================== |
|
2 The "sites" framework |
|
3 ===================== |
|
4 |
|
5 Django comes with an optional "sites" framework. It's a hook for associating |
|
6 objects and functionality to particular Web sites, and it's a holding place for |
|
7 the domain names and "verbose" names of your Django-powered sites. |
|
8 |
|
9 Use it if your single Django installation powers more than one site and you |
|
10 need to differentiate between those sites in some way. |
|
11 |
|
12 The whole sites framework is based on two simple concepts: |
|
13 |
|
14 * The ``Site`` model, found in ``django.contrib.sites``, has ``domain`` and |
|
15 ``name`` fields. |
|
16 * The ``SITE_ID`` setting specifies the database ID of the ``Site`` object |
|
17 associated with that particular settings file. |
|
18 |
|
19 How you use this is up to you, but Django uses it in a couple of ways |
|
20 automatically via simple conventions. |
|
21 |
|
22 Example usage |
|
23 ============= |
|
24 |
|
25 Why would you use sites? It's best explained through examples. |
|
26 |
|
27 Associating content with multiple sites |
|
28 --------------------------------------- |
|
29 |
|
30 The Django-powered sites LJWorld.com_ and Lawrence.com_ are operated by the |
|
31 same news organization -- the Lawrence Journal-World newspaper in Lawrence, |
|
32 Kansas. LJWorld.com focuses on news, while Lawrence.com focuses on local |
|
33 entertainment. But sometimes editors want to publish an article on *both* |
|
34 sites. |
|
35 |
|
36 The brain-dead way of solving the problem would be to require site producers to |
|
37 publish the same story twice: once for LJWorld.com and again for Lawrence.com. |
|
38 But that's inefficient for site producers, and it's redundant to store |
|
39 multiple copies of the same story in the database. |
|
40 |
|
41 The better solution is simple: Both sites use the same article database, and an |
|
42 article is associated with one or more sites. In Django model terminology, |
|
43 that's represented by a ``ManyToManyField`` in the ``Article`` model:: |
|
44 |
|
45 from django.db import models |
|
46 from django.contrib.sites.models import Site |
|
47 |
|
48 class Article(models.Model): |
|
49 headline = models.CharField(maxlength=200) |
|
50 # ... |
|
51 sites = models.ManyToManyField(Site) |
|
52 |
|
53 This accomplishes several things quite nicely: |
|
54 |
|
55 * It lets the site producers edit all content -- on both sites -- in a |
|
56 single interface (the Django admin). |
|
57 |
|
58 * It means the same story doesn't have to be published twice in the |
|
59 database; it only has a single record in the database. |
|
60 |
|
61 * It lets the site developers use the same Django view code for both sites. |
|
62 The view code that displays a given story just checks to make sure the |
|
63 requested story is on the current site. It looks something like this:: |
|
64 |
|
65 from django.conf import settings |
|
66 |
|
67 def article_detail(request, article_id): |
|
68 try: |
|
69 a = Article.objects.get(id=article_id, sites__id__exact=settings.SITE_ID) |
|
70 except Article.DoesNotExist: |
|
71 raise Http404 |
|
72 # ... |
|
73 |
|
74 .. _ljworld.com: http://www.ljworld.com/ |
|
75 .. _lawrence.com: http://www.lawrence.com/ |
|
76 |
|
77 Associating content with a single site |
|
78 -------------------------------------- |
|
79 |
|
80 Similarly, you can associate a model to the ``Site`` model in a many-to-one |
|
81 relationship, using ``ForeignKey``. |
|
82 |
|
83 For example, if an article is only allowed on a single site, you'd use a model |
|
84 like this:: |
|
85 |
|
86 from django.db import models |
|
87 from django.contrib.sites.models import Site |
|
88 |
|
89 class Article(models.Model): |
|
90 headline = models.CharField(maxlength=200) |
|
91 # ... |
|
92 site = models.ForeignKey(Site) |
|
93 |
|
94 This has the same benefits as described in the last section. |
|
95 |
|
96 Hooking into the current site from views |
|
97 ---------------------------------------- |
|
98 |
|
99 On a lower level, you can use the sites framework in your Django views to do |
|
100 particular things based on what site in which the view is being called. |
|
101 For example:: |
|
102 |
|
103 from django.conf import settings |
|
104 |
|
105 def my_view(request): |
|
106 if settings.SITE_ID == 3: |
|
107 # Do something. |
|
108 else: |
|
109 # Do something else. |
|
110 |
|
111 Of course, it's ugly to hard-code the site IDs like that. This sort of |
|
112 hard-coding is best for hackish fixes that you need done quickly. A slightly |
|
113 cleaner way of accomplishing the same thing is to check the current site's |
|
114 domain:: |
|
115 |
|
116 from django.conf import settings |
|
117 from django.contrib.sites.models import Site |
|
118 |
|
119 def my_view(request): |
|
120 current_site = Site.objects.get(id=settings.SITE_ID) |
|
121 if current_site.domain == 'foo.com': |
|
122 # Do something |
|
123 else: |
|
124 # Do something else. |
|
125 |
|
126 The idiom of retrieving the ``Site`` object for the value of |
|
127 ``settings.SITE_ID`` is quite common, so the ``Site`` model's manager has a |
|
128 ``get_current()`` method. This example is equivalent to the previous one:: |
|
129 |
|
130 from django.contrib.sites.models import Site |
|
131 |
|
132 def my_view(request): |
|
133 current_site = Site.objects.get_current() |
|
134 if current_site.domain == 'foo.com': |
|
135 # Do something |
|
136 else: |
|
137 # Do something else. |
|
138 |
|
139 Getting the current domain for display |
|
140 -------------------------------------- |
|
141 |
|
142 LJWorld.com and Lawrence.com both have e-mail alert functionality, which lets |
|
143 readers sign up to get notifications when news happens. It's pretty basic: A |
|
144 reader signs up on a Web form, and he immediately gets an e-mail saying, |
|
145 "Thanks for your subscription." |
|
146 |
|
147 It'd be inefficient and redundant to implement this signup-processing code |
|
148 twice, so the sites use the same code behind the scenes. But the "thank you for |
|
149 signing up" notice needs to be different for each site. By using ``Site`` |
|
150 objects, we can abstract the "thank you" notice to use the values of the |
|
151 current site's ``name`` and ``domain``. |
|
152 |
|
153 Here's an example of what the form-handling view looks like:: |
|
154 |
|
155 from django.contrib.sites.models import Site |
|
156 from django.core.mail import send_mail |
|
157 |
|
158 def register_for_newsletter(request): |
|
159 # Check form values, etc., and subscribe the user. |
|
160 # ... |
|
161 |
|
162 current_site = Site.objects.get_current() |
|
163 send_mail('Thanks for subscribing to %s alerts' % current_site.name, |
|
164 'Thanks for your subscription. We appreciate it.\n\n-The %s team.' % current_site.name, |
|
165 'editor@%s' % current_site.domain, |
|
166 [user.email]) |
|
167 |
|
168 # ... |
|
169 |
|
170 On Lawrence.com, this e-mail has the subject line "Thanks for subscribing to |
|
171 lawrence.com alerts." On LJWorld.com, the e-mail has the subject "Thanks for |
|
172 subscribing to LJWorld.com alerts." Same goes for the e-mail's message body. |
|
173 |
|
174 Note that an even more flexible (but more heavyweight) way of doing this would |
|
175 be to use Django's template system. Assuming Lawrence.com and LJWorld.com have |
|
176 different template directories (``TEMPLATE_DIRS``), you could simply farm out |
|
177 to the template system like so:: |
|
178 |
|
179 from django.core.mail import send_mail |
|
180 from django.template import loader, Context |
|
181 |
|
182 def register_for_newsletter(request): |
|
183 # Check form values, etc., and subscribe the user. |
|
184 # ... |
|
185 |
|
186 subject = loader.get_template('alerts/subject.txt').render(Context({})) |
|
187 message = loader.get_template('alerts/message.txt').render(Context({})) |
|
188 send_mail(subject, message, 'editor@ljworld.com', [user.email]) |
|
189 |
|
190 # ... |
|
191 |
|
192 In this case, you'd have to create ``subject.txt`` and ``message.txt`` template |
|
193 files for both the LJWorld.com and Lawrence.com template directories. That |
|
194 gives you more flexibility, but it's also more complex. |
|
195 |
|
196 It's a good idea to exploit the ``Site`` objects as much as possible, to remove |
|
197 unneeded complexity and redundancy. |
|
198 |
|
199 Getting the current domain for full URLs |
|
200 ---------------------------------------- |
|
201 |
|
202 Django's ``get_absolute_url()`` convention is nice for getting your objects' |
|
203 URL without the domain name, but in some cases you might want to display the |
|
204 full URL -- with ``http://`` and the domain and everything -- for an object. |
|
205 To do this, you can use the sites framework. A simple example:: |
|
206 |
|
207 >>> from django.contrib.sites.models import Site |
|
208 >>> obj = MyModel.objects.get(id=3) |
|
209 >>> obj.get_absolute_url() |
|
210 '/mymodel/objects/3/' |
|
211 >>> Site.objects.get_current().domain |
|
212 'example.com' |
|
213 >>> 'http://%s%s' % (Site.objects.get_current().domain, obj.get_absolute_url()) |
|
214 'http://example.com/mymodel/objects/3/' |
|
215 |
|
216 The ``CurrentSiteManager`` |
|
217 ========================== |
|
218 |
|
219 If ``Site``\s play a key role in your application, consider using the helpful |
|
220 ``CurrentSiteManager`` in your model(s). It's a model manager_ that |
|
221 automatically filters its queries to include only objects associated with the |
|
222 current ``Site``. |
|
223 |
|
224 Use ``CurrentSiteManager`` by adding it to your model explicitly. For example:: |
|
225 |
|
226 from django.db import models |
|
227 from django.contrib.sites.models import Site |
|
228 from django.contrib.sites.managers import CurrentSiteManager |
|
229 |
|
230 class Photo(models.Model): |
|
231 photo = models.FileField(upload_to='/home/photos') |
|
232 photographer_name = models.CharField(maxlength=100) |
|
233 pub_date = models.DateField() |
|
234 site = models.ForeignKey(Site) |
|
235 objects = models.Manager() |
|
236 on_site = CurrentSiteManager() |
|
237 |
|
238 With this model, ``Photo.objects.all()`` will return all ``Photo`` objects in |
|
239 the database, but ``Photo.on_site.all()`` will return only the ``Photo`` |
|
240 objects associated with the current site, according to the ``SITE_ID`` setting. |
|
241 |
|
242 Put another way, these two statements are equivalent:: |
|
243 |
|
244 Photo.objects.filter(site=settings.SITE_ID) |
|
245 Photo.on_site.all() |
|
246 |
|
247 How did ``CurrentSiteManager`` know which field of ``Photo`` was the ``Site``? |
|
248 It defaults to looking for a field called ``site``. If your model has a |
|
249 ``ForeignKey`` or ``ManyToManyField`` called something *other* than ``site``, |
|
250 you need to explicitly pass that as the parameter to ``CurrentSiteManager``. |
|
251 The following model, which has a field called ``publish_on``, demonstrates |
|
252 this:: |
|
253 |
|
254 from django.db import models |
|
255 from django.contrib.sites.models import Site |
|
256 from django.contrib.sites.managers import CurrentSiteManager |
|
257 |
|
258 class Photo(models.Model): |
|
259 photo = models.FileField(upload_to='/home/photos') |
|
260 photographer_name = models.CharField(maxlength=100) |
|
261 pub_date = models.DateField() |
|
262 publish_on = models.ForeignKey(Site) |
|
263 objects = models.Manager() |
|
264 on_site = CurrentSiteManager('publish_on') |
|
265 |
|
266 If you attempt to use ``CurrentSiteManager`` and pass a field name that doesn't |
|
267 exist, Django will raise a ``ValueError``. |
|
268 |
|
269 Finally, note that you'll probably want to keep a normal (non-site-specific) |
|
270 ``Manager`` on your model, even if you use ``CurrentSiteManager``. As explained |
|
271 in the `manager documentation`_, if you define a manager manually, then Django |
|
272 won't create the automatic ``objects = models.Manager()`` manager for you. |
|
273 Also, note that certain parts of Django -- namely, the Django admin site and |
|
274 generic views -- use whichever manager is defined *first* in the model, so if |
|
275 you want your admin site to have access to all objects (not just site-specific |
|
276 ones), put ``objects = models.Manager()`` in your model, before you define |
|
277 ``CurrentSiteManager``. |
|
278 |
|
279 .. _manager: ../model_api/#managers |
|
280 .. _manager documentation: ../model_api/#managers |
|
281 |
|
282 How Django uses the sites framework |
|
283 =================================== |
|
284 |
|
285 Although it's not required that you use the sites framework, it's strongly |
|
286 encouraged, because Django takes advantage of it in a few places. Even if your |
|
287 Django installation is powering only a single site, you should take the two |
|
288 seconds to create the site object with your ``domain`` and ``name``, and point |
|
289 to its ID in your ``SITE_ID`` setting. |
|
290 |
|
291 Here's how Django uses the sites framework: |
|
292 |
|
293 * In the `redirects framework`_, each redirect object is associated with a |
|
294 particular site. When Django searches for a redirect, it takes into |
|
295 account the current ``SITE_ID``. |
|
296 |
|
297 * In the comments framework, each comment is associated with a particular |
|
298 site. When a comment is posted, its ``site`` is set to the current |
|
299 ``SITE_ID``, and when comments are listed via the appropriate template |
|
300 tag, only the comments for the current site are displayed. |
|
301 |
|
302 * In the `flatpages framework`_, each flatpage is associated with a |
|
303 particular site. When a flatpage is created, you specify its ``site``, |
|
304 and the ``FlatpageFallbackMiddleware`` checks the current ``SITE_ID`` in |
|
305 retrieving flatpages to display. |
|
306 |
|
307 * In the `syndication framework`_, the templates for ``title`` and |
|
308 ``description`` automatically have access to a variable ``{{{ site }}}``, |
|
309 which is the ``Site`` object representing the current site. Also, the |
|
310 hook for providing item URLs will use the ``domain`` from the current |
|
311 ``Site`` object if you don't specify a fully-qualified domain. |
|
312 |
|
313 * In the `authentication framework`_, the ``django.contrib.auth.views.login`` |
|
314 view passes the current ``Site`` name to the template as ``{{{ site_name }}}``. |
|
315 |
|
316 * The shortcut view (``django.views.defaults.shortcut``) uses the domain of |
|
317 the current ``Site`` object when calculating an object's URL. |
|
318 |
|
319 .. _redirects framework: ../redirects/ |
|
320 .. _flatpages framework: ../flatpages/ |
|
321 .. _syndication framework: ../syndication/ |
|
322 .. _authentication framework: ../authentication/ |