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) |