Commits

Anton Strogonoff committed 425f13b Merge

Merged.

  • Participants
  • Parent commits e260b81, 48ad311

Comments (0)

Files changed (7)

 .coverage
 .tox/
 Django-*.egg
+*.pyc
 Gregor Müllegger <gregor@muellegger.de>
 Jeff Elmore <jeffelmore.org>
 Paul McLanahan <paul@mclanahan.net>
+Ryan Kaskel
 zyegfryed
 sayane
-
 tip (unreleased)
 ----------------
 
+- Added ``PassThroughManager.for_queryset_class()``, which fixes use of
+  ``PassThroughManager`` with related fields. Thanks Ryan Kaskel for report and
+  fix.
+
+- Added ``InheritanceManager.get_subclass()``. Thanks smacker.
+
 1.0.0 (2011.06.16)
 ------------------
 
     nearby_places = Place.objects.select_subclasses("restaurant")
     # restaurants will be Restaurant instances, bars will still be Place instances
 
-If you don't explicitly call ``select_subclasses()``, an ``InheritanceManager``
-behaves identically to a normal ``Manager``; so it's safe to use as your
-default manager for the model.
+``InheritanceManager`` also provides a subclass-fetching alternative to the
+``get()`` method::
+    
+    place = Place.objects.get_subclass(id=some_id)
+    # "place" will automatically be an instance of Place, Restaurant, or Bar
+
+If you don't explicitly call ``select_subclasses()`` or ``get_subclass()``,
+an ``InheritanceManager`` behaves identically to a normal ``Manager``; so
+it's safe to use as your default manager for the model.
 
 .. note::
     ``InheritanceManager`` currently only supports a single level of model
     inheritance; it won't work for grandchild models.
 
 .. note::
+    The implementation of ``InheritanceManager`` uses ``select_related``
+    internally.  Due to `Django bug #16855`_, this currently means that it
+    will override any previous ``select_related`` calls on the ``QuerySet``.
+
+.. note::
     ``InheritanceManager`` requires Django 1.2 or later. Previous versions of
     django-model-utils included ``InheritanceCastModel``, an alternative (and
     inferior) approach to this problem that is Django 1.1
     its use in new code is discouraged.
 
 .. _contributed by Jeff Elmore: http://jeffelmore.org/2010/11/11/automatic-downcasting-of-inherited-models-in-django/
+.. _Django bug #16855: https://code.djangoproject.com/ticket/16855
 
 
 TimeStampedModel
 To use ``PassThroughManager``, rather than defining a custom manager with
 additional methods, define a custom ``QuerySet`` subclass with the additional
 methods you want, and pass that ``QuerySet`` subclass to the
-``PassThroughManager`` constructor. ``PassThroughManager`` will always return
-instances of your custom ``QuerySet``, and you can also call methods of your
-custom ``QuerySet`` directly on the manager::
+``PassThroughManager.for_queryset_class()`` class method. The returned
+``PassThroughManager`` subclass will always return instances of your custom
+``QuerySet``, and you can also call methods of your custom ``QuerySet``
+directly on the manager::
 
     from datetime import datetime
     from django.db import models
         user = models.ForeignKey(User)
         published = models.DateTimeField()
     
-        objects = PassThroughManager(PostQuerySet)
+        objects = PassThroughManager.for_queryset_class(PostQuerySet)()
     
     Post.objects.published()
     Post.objects.by_author(user=request.user).unpublished()
 
-If you want certain methods available only on the manager, or you need to
-override other manager methods (particularly ``get_query_set``), you can also
-define a custom manager that inherits from ``PassThroughManager``::
-
-    from datetime import datetime
-    from django.db import models
-    from django.db.models.query import QuerySet
-    
-    class PostQuerySet(QuerySet):
-        def by_author(self, user):
-            return self.filter(user=user)
-    
-        def published(self):
-            return self.filter(published__lte=datetime.now())
-    
-        def unpublished(self):
-            return self.filter(published__gte=datetime.now())
-    
-    class PostManager(PassThroughManager):
-        def get_query_set(self):
-            return PostQuerySet(self.model, using=self._db)
-    
-        def get_stats(self):
-            return {
-                'published_count': self.published().count(),
-                'unpublished_count': self.unpublished().count(),
-            }
-    
-    class Post(models.Model):
-        user = models.ForeignKey(User)
-        published = models.DateTimeField()
-    
-        objects = PostManager()
-    
-    Post.objects.get_stats()
-    Post.objects.published()
-    Post.objects.by_author(user=request.user).unpublished()
-
 .. note::
 
    Previous versions of django-model-utils included ``manager_from``, a

File model_utils/managers.py

 from django.db.models.manager import Manager
 from django.db.models.query import QuerySet
 
-from polymorphic import PolymorphicManager
-
 
 class InheritanceQuerySet(QuerySet):
     def select_subclasses(self, *subclasses):
     def select_subclasses(self, *subclasses):
         return self.get_query_set().select_subclasses(*subclasses)
 
+    def get_subclass(self, *args, **kwargs):
+        return self.get_query_set().select_subclasses().get(*args, **kwargs)
+
 
 class InheritanceCastMixin(object):
     def cast(self):
         return super(PassThroughManager, self).get_query_set()
 
 class PassThroughManager(PassThroughManagerBase, models.Manager):
+    @classmethod
+    def for_queryset_class(cls, queryset_cls):
+        class _PassThroughManager(cls):
+            def __init__(self):
+                return super(_PassThroughManager, self).__init__()
+            def get_query_set(self):
+                kwargs = {}
+                if hasattr(self, "_db"):
+                    kwargs["using"] = self._db
+                return queryset_cls(self.model, **kwargs)
+        return _PassThroughManager
+
+try:
+    from polymorphic import PolymorphicManager
+except ImportError:
     pass
-
-class PassThroughPolymorphicManager(PolymorphicManager, PassThroughManagerBase):
-    pass
-
+else:
+    class PassThroughPolymorphicManager(PolymorphicManager,
+    PassThroughManagerBase):
+        pass
 
 def manager_from(*mixins, **kwds):
     """

File model_utils/tests/models.py

 
     objects = PassThroughManager(DudeQuerySet)
     abiders = AbidingManager()
+
+
+class Car(models.Model):
+    name = models.CharField(max_length=20)
+    owner = models.ForeignKey(Dude, related_name='cars_owned')
+
+    objects = PassThroughManager(DudeQuerySet)
+
+
+class SpotQuerySet(models.query.QuerySet):
+    def closed(self):
+        return self.filter(closed=True)
+
+    def secured(self):
+        return self.filter(secure=True)
+
+
+class Spot(models.Model):
+    name = models.CharField(max_length=20)
+    secure = models.BooleanField(default=True)
+    closed = models.BooleanField(default=False)
+    owner = models.ForeignKey(Dude, related_name='spots_owned')
+
+    objects = PassThroughManager.for_queryset_class(SpotQuerySet)()

File model_utils/tests/tests.py

     InheritanceManagerTestParent, InheritanceManagerTestChild1,
     InheritanceManagerTestChild2, TimeStamp, Post, Article, Status,
     StatusPlainTuple, TimeFrame, Monitored, StatusManagerAdded,
-    TimeFrameManagerAdded, Entry, Dude, SplitFieldAbstractParent)
+    TimeFrameManagerAdded, Entry, Dude, SplitFieldAbstractParent, Car, Spot)
 
 
 
                 set([self.child1,
                      InheritanceManagerTestParent(pk=self.child2.pk)]))
 
+        def test_get_subclass(self):
+            self.assertEquals(
+                self.get_manager().get_subclass(pk=self.child1.pk),
+                self.child1)
+
 
     class InheritanceManagerRelatedTests(InheritanceManagerTests):
         def setUp(self):
         saltyqs = pickle.dumps(qs)
         unqs = pickle.loads(saltyqs)
         self.assertEqual(unqs.by_name('The Dude').count(), 1)
+
+    def test_queryset_not_available_on_related_manager(self):
+        dude = Dude.objects.by_name('Duder').get()
+        Car.objects.create(name='Ford', owner=dude)
+        self.assertFalse(hasattr(dude.cars_owned, 'by_name'))
+
+
+class CreatePassThroughManagerTests(TestCase):
+    def setUp(self):
+        self.dude = Dude.objects.create(name='El Duderino')
+        Spot.objects.create(name='The Crib', owner=self.dude, closed=True,
+                            secure=True)
+
+    def test_reverse_manager(self):
+        self.assertEqual(self.dude.spots_owned.closed().count(), 1)
+
+    def test_related_queryset_pickling(self):
+        qs = self.dude.spots_owned.closed()
+        pickled_qs = pickle.dumps(qs)
+        unpickled_qs = pickle.loads(pickled_qs)
+        self.assertEqual(unpickled_qs.secured().count(), 1)