diff --git a/django/contrib/sites/managers.py b/django/contrib/sites/managers.py
index 3df485a..58896ee 100644
a
|
b
|
|
1 | 1 | from django.conf import settings |
2 | 2 | from django.db import models |
3 | 3 | from django.db.models.fields import FieldDoesNotExist |
| 4 | from django.db.models.sql import constants |
4 | 5 | |
5 | 6 | class CurrentSiteManager(models.Manager): |
6 | 7 | "Use this to limit objects to those associated with the current site." |
… |
… |
class CurrentSiteManager(models.Manager):
|
8 | 9 | super(CurrentSiteManager, self).__init__() |
9 | 10 | self.__field_name = field_name |
10 | 11 | self.__is_validated = False |
11 | | |
| 12 | |
12 | 13 | def _validate_field_name(self): |
13 | | field_names = self.model._meta.get_all_field_names() |
14 | | |
15 | | # If a custom name is provided, make sure the field exists on the model |
16 | | if self.__field_name is not None and self.__field_name not in field_names: |
17 | | raise ValueError("%s couldn't find a field named %s in %s." % \ |
18 | | (self.__class__.__name__, self.__field_name, self.model._meta.object_name)) |
19 | | |
20 | | # Otherwise, see if there is a field called either 'site' or 'sites' |
21 | | else: |
22 | | for potential_name in ['site', 'sites']: |
| 14 | """ |
| 15 | Given the field identifier, goes down the chain to check that each |
| 16 | specified field |
| 17 | |
| 18 | a) exists, |
| 19 | b) is of type ForeignKey or ManyToManyField |
| 20 | |
| 21 | If no field name is specified when instantiating |
| 22 | CurrentSiteManager, it tries to find either 'site' or 'sites' |
| 23 | as the site link. |
| 24 | """ |
| 25 | if self.__field_name is None: |
| 26 | # Guess at field name |
| 27 | field_names = self.model._meta.get_all_field_names() |
| 28 | for potential_name in ('site', 'sites'): |
23 | 29 | if potential_name in field_names: |
24 | 30 | self.__field_name = potential_name |
25 | | self.__is_validated = True |
26 | 31 | break |
27 | | |
28 | | # Now do a type check on the field (FK or M2M only) |
| 32 | else: |
| 33 | raise ValueError( |
| 34 | "%s couldn't find a field named either " |
| 35 | "'site' or 'sites' in %s." % |
| 36 | (self.__class__.__name__, self.model._meta.object_name)) |
| 37 | |
| 38 | fieldname_chain = self.__field_name.split(constants.LOOKUP_SEP) |
| 39 | model = self.model |
| 40 | |
| 41 | for field_name in fieldname_chain: |
| 42 | # Throws an exception if anything goes bad |
| 43 | self._validate_single_field_name(model, field_name) |
| 44 | model = self._get_related_model(model, field_name) |
| 45 | |
| 46 | # If we get this far without an exception, everything is good |
| 47 | self.__is_validated = True |
| 48 | |
| 49 | def _validate_single_field_name(self, model, field_name): |
| 50 | """ |
| 51 | Checks if the given field name can be used to make a link between a |
| 52 | model and a site with the CurrentSiteManager class |
| 53 | """ |
29 | 54 | try: |
30 | | field = self.model._meta.get_field(self.__field_name) |
31 | | if not isinstance(field, (models.ForeignKey, models.ManyToManyField)): |
32 | | raise TypeError("%s must be a ForeignKey or ManyToManyField." %self.__field_name) |
| 55 | field = model._meta.get_field(field_name) |
| 56 | if not isinstance(field, (models.ForeignKey, |
| 57 | models.ManyToManyField)): |
| 58 | raise TypeError( |
| 59 | "Field %s of model %s must be a ForeignKey " |
| 60 | "or ManyToManyField." % |
| 61 | (field_name, model._meta.object_name)) |
33 | 62 | except FieldDoesNotExist: |
34 | | raise ValueError("%s couldn't find a field named %s in %s." % \ |
35 | | (self.__class__.__name__, self.__field_name, self.model._meta.object_name)) |
36 | | self.__is_validated = True |
37 | | |
| 63 | raise ValueError( |
| 64 | "%s couldn't find a field named %s in %s." % |
| 65 | (self.__class__.__name__, field_name, model._meta.object_name)) |
| 66 | |
| 67 | def _get_related_model(self, model, fieldname): |
| 68 | """ |
| 69 | Given a model and the name of a ForeignKey or ManyToManyField field |
| 70 | name as a string, returns the associated model. |
| 71 | """ |
| 72 | return model._meta.get_field_by_name(fieldname)[0].rel.to |
| 73 | |
38 | 74 | def get_query_set(self): |
39 | 75 | if not self.__is_validated: |
40 | 76 | self._validate_field_name() |
diff --git a/docs/ref/contrib/sites.txt b/docs/ref/contrib/sites.txt
index 8fc434b..7cf5106 100644
a
|
b
|
fallback for cases where it is not installed.
|
174 | 174 | .. function:: get_current_site(request) |
175 | 175 | |
176 | 176 | Checks if contrib.sites is installed and returns either the current |
177 | | :class:`~django.contrib.sites.models.Site` object or a |
| 177 | :class:`~django.contrib.sites.models.Site` object or a |
178 | 178 | :class:`~django.contrib.sites.models.RequestSite` object based on |
179 | 179 | the request. |
180 | 180 | |
… |
… |
demonstrates this::
|
349 | 349 | If you attempt to use :class:`~django.contrib.sites.managers.CurrentSiteManager` |
350 | 350 | and pass a field name that doesn't exist, Django will raise a ``ValueError``. |
351 | 351 | |
| 352 | Spanning relationships |
| 353 | ---------------------- |
| 354 | |
| 355 | .. versionchanged:: 1.5 |
| 356 | |
| 357 | :class:`~django.contrib.sites.managers.CurrentSiteManager` can span |
| 358 | multiple models by using the same syntax as queries, as per the |
| 359 | :ref:`models and database queries documentation |
| 360 | <lookups-that-span-relationships>`. For example, using the ``Photo`` |
| 361 | model defined above:: |
| 362 | |
| 363 | from django.db import models |
| 364 | from django.contrib.sites.managers import CurrentSiteManager |
| 365 | |
| 366 | class PhotoLocation(models.Model): |
| 367 | name = models.CharField(max_length=100) |
| 368 | photo = models.ForeignKey(Photo) |
| 369 | objects = models.Manager() |
| 370 | on_site = CurrentSiteManager('photo__publish_on') |
| 371 | |
| 372 | ``PhotoLocation.on_site.all()`` will return all ``PhotoLocation`` objects |
| 373 | in the database associated with ``Photo`` objects which themselves are |
| 374 | associated with the current site. |
| 375 | |
| 376 | Keeping default manager |
| 377 | ----------------------- |
| 378 | |
352 | 379 | Finally, note that you'll probably want to keep a normal |
353 | 380 | (non-site-specific) ``Manager`` on your model, even if you use |
354 | 381 | :class:`~django.contrib.sites.managers.CurrentSiteManager`. As |
… |
… |
fallback when the database-backed sites framework is not available.
|
437 | 464 | |
438 | 465 | Sets the ``name`` and ``domain`` attributes to the value of |
439 | 466 | :meth:`~django.http.HttpRequest.get_host`. |
440 | | |
| 467 | |
441 | 468 | |
442 | 469 | A :class:`~django.contrib.sites.models.RequestSite` object has a similar |
443 | 470 | interface to a normal :class:`~django.contrib.sites.models.Site` object, except |
diff --git a/tests/regressiontests/sites_framework/models.py b/tests/regressiontests/sites_framework/models.py
index 9ecc3e6..90d26f1 100644
a
|
b
|
class InvalidArticle(AbstractArticle):
|
34 | 34 | |
35 | 35 | class ConfusedArticle(AbstractArticle): |
36 | 36 | site = models.IntegerField() |
| 37 | |
| 38 | class ArticleComment(models.Model): |
| 39 | parent_article = models.ForeignKey(ExclusiveArticle) |
| 40 | text = models.CharField(max_length=50) |
| 41 | |
| 42 | objects = models.Manager() |
| 43 | on_site = CurrentSiteManager("parent_article__site") |
diff --git a/tests/regressiontests/sites_framework/tests.py b/tests/regressiontests/sites_framework/tests.py
index 8e664fd..73a07d0 100644
a
|
b
|
from django.contrib.sites.models import Site
|
5 | 5 | from django.test import TestCase |
6 | 6 | |
7 | 7 | from .models import (SyndicatedArticle, ExclusiveArticle, CustomArticle, |
8 | | InvalidArticle, ConfusedArticle) |
| 8 | InvalidArticle, ConfusedArticle, ArticleComment) |
9 | 9 | |
10 | 10 | |
11 | 11 | class SitesFrameworkTestCase(TestCase): |
… |
… |
class SitesFrameworkTestCase(TestCase):
|
36 | 36 | def test_invalid_field_type(self): |
37 | 37 | article = ConfusedArticle.objects.create(title="More Bad News!", site=settings.SITE_ID) |
38 | 38 | self.assertRaises(TypeError, ConfusedArticle.on_site.all) |
| 39 | |
| 40 | def test_indirect_link(self): |
| 41 | article_inside = ExclusiveArticle.objects.create( |
| 42 | title="Article in current site", |
| 43 | site_id=settings.SITE_ID) |
| 44 | article_outside = ExclusiveArticle.objects.create( |
| 45 | title="Article in another site", |
| 46 | site_id=settings.SITE_ID + 1) |
| 47 | |
| 48 | comment_inside = ArticleComment.objects.create( |
| 49 | parent_article=article_inside, |
| 50 | text="Post to article in current side.") |
| 51 | comment_outside = ArticleComment.objects.create( |
| 52 | parent_article=article_outside, |
| 53 | text="Post to article in another site.") |
| 54 | |
| 55 | self.assertEqual(ArticleComment.on_site.all().get(), comment_inside) |