Commits

Carl Meyer committed 0ae5149

Started deprecation for manager_from, InheritanceCastModel, and Django 1.1 support.

Comments (0)

Files changed (6)

 tip (unreleased)
 ----------------
 
+- Added pending-deprecation warnings for ``InheritanceCastModel``,
+  ``manager_from``, and Django 1.1 support. Removed documentation for the
+  deprecated utilities. Bumped ``ChoiceEnum`` from pending-deprecation to
+  deprecation.
+
 0.6.0 (2011.02.18)
 ------------------
 
 Dependencies
 ------------
 
-Most of ``django-model-utils`` works with `Django`_ 1.0 or later.
+Most of ``django-model-utils`` works with `Django`_ 1.1 or later.
 `InheritanceManager`_ and `SplitField`_ require Django 1.2 or later.
 
 .. _Django: http://www.djangoproject.com/
     inheritance; it won't work for grandchild models.
 
 .. note::
-    ``InheritanceManager`` requires Django 1.2 or later.
+    ``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
+    compatible. ``InheritanceCastModel`` will remain in django-model-utils
+    until support for Django 1.1 is removed, but it is no longer documented and
+    its use in new code is discouraged.
 
 .. _contributed by Jeff Elmore: http://jeffelmore.org/2010/11/11/automatic-downcasting-of-inherited-models-in-django/
 
 
-InheritanceCastModel
-====================
-
-This abstract base class can be inherited by the root (parent) model in a
-model-inheritance tree. It solves the same problem as `InheritanceManager`_ in
-a way that requires more database queries and is less convenient to use, but is
-compatible with Django versions prior to 1.2. Whenever possible,
-`InheritanceManager`_ should be used instead.
-
-Usage::
-
-    from model_utils.models import InheritanceCastModel
-
-    class Place(InheritanceCastModel):
-        # ...
-
-    class Restaurant(Place):
-        # ...
-
-    class Bar(Place):
-        # ...
-
-    nearby_places = Place.objects.filter(location='here')
-    for place in nearby_places:
-        restaurant_or_bar = place.cast() # ...
-
-This is inefficient for large querysets, as it results in a new query for every
-individual returned object.  You can use the ``cast()`` method on a queryset to
-reduce this to as many queries as subtypes are involved::
-
-    nearby_places = Place.objects.filter(location='here')
-    for place in nearby_places.cast():
-        # ...
-
-.. note::
-    The ``cast()`` queryset method does *not* return another queryset but an
-    already evaluated result of the database query.  This means that you cannot
-    chain additional queryset methods after ``cast()``.
-
 TimeStampedModel
 ================
 
 by chaining a call to ``.order_by()`` on the ``QueryManager`` (this is
 not required).
 
-manager_from
-============
-
-A common "gotcha" when defining methods on a custom manager class is
-that those same methods are not automatically also available on the
-QuerySet used by that model, so are not "chainable". This can be
-counterintuitive, as most of the public QuerySet API is also available
-on managers. It is possible to create a custom Manager that returns
-QuerySets that have the same additional methods, but this requires
-boilerplate code.
-
-The ``manager_from`` function (`created by George Sakkis`_ and
-included here by permission) solves this problem with zero
-boilerplate. It creates and returns a Manager subclass with additional
-behavior defined by mixin subclasses or functions you pass it, and the
-returned Manager will return instances of a custom QuerySet with those
-same additional methods::
-
-    from datetime import datetime
-    from django.db import models
-    
-    class AuthorMixin(object):
-        def by_author(self, user):
-            return self.filter(user=user)
-    
-    class PublishedMixin(object):
-        def published(self):
-            return self.filter(published__lte=datetime.now())
-    
-    def unpublished(self):
-        return self.filter(published__gte=datetime.now())
-    
-    
-    class Post(models.Model):
-        user = models.ForeignKey(User)
-        published = models.DateTimeField()
-    
-        objects = manager_from(AuthorMixin, PublishedMixin, unpublished)
-    
-    Post.objects.published()
-    Post.objects.by_author(user=request.user).unpublished()
-
-.. _created by George Sakkis: http://djangosnippets.org/snippets/2117/
 
 PassThroughManager
 ==================
 
-The ``PassThroughManager`` class (`contributed by Paul McLanahan`_) solves
-the same problem as the above ``manager_from`` function. This class, however,
-accomplishes it in a different way. The reason it exists is that the dynamically
-generated ``QuerySet`` classes created by the ``manager_from`` function are
-not picklable. It's probably not often that a ``QuerySet`` is pickled, but
-it is a documented feature of the Django ``QuerySet`` class, and this method
-maintains that functionality.
+A common "gotcha" when defining methods on a custom manager class is that those
+same methods are not automatically also available on the QuerySets returned by
+that manager, so are not "chainable". This can be counterintuitive, as most of
+the public QuerySet API is mirrored on managers. It is possible to create a
+custom Manager that returns QuerySets that have the same additional methods,
+but this requires boilerplate code. The ``PassThroughManager`` class
+(`contributed by Paul McLanahan`_) removes this boilerplate.
 
-``PassThroughManager`` is a subclass of ``django.db.models.manager.Manager``,
-so all that is required is that you change your custom managers to inherit from
-``PassThroughManager`` instead of Django's built-in ``Manager`` class. Once you
-do this, create your custom ``QuerySet`` class, and have your manager's
-``get_query_set`` method return instances of said class, then all of the
-methods you add to your custom ``QuerySet`` class will be available from your
-manager as well::
+.. _contributed by Paul McLanahan: http://paulm.us/post/3717466639/passthroughmanager-for-django
+
+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::
 
     from datetime import datetime
     from django.db import models
         def unpublished(self):
             return self.filter(published__gte=datetime.now())
     
+    
+    class Post(models.Model):
+        user = models.ForeignKey(User)
+        published = models.DateTimeField()
+    
+        objects = PassThroughManager(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):
-            PostQuerySet(self.model, using=self._db)
-        
+            return PostQuerySet(self.model, using=self._db)
+    
         def get_stats(self):
             return {
                 'published_count': self.published().count(),
     Post.objects.published()
     Post.objects.by_author(user=request.user).unpublished()
 
-Alternatively, if you don't need any methods on your manager that shouldn't also
-be on your queryset, a shortcut is available. ``PassThroughManager``'s
-constructor takes an optional argument. If you pass it a ``QuerySet`` subclass
-it will automatically use that class when creating querysets for the manager::
+.. note::
 
-    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 Post(models.Model):
-        user = models.ForeignKey(User)
-        published = models.DateTimeField()
-    
-        objects = PassThroughManager(PostQuerySet)
-    
-    Post.objects.published()
-    Post.objects.by_author(user=request.user).unpublished()
+   Previous versions of django-model-utils included ``manager_from``, a
+   function that solved the same problem as ``PassThroughManager``. The
+   ``manager_from`` approach created dynamic ``QuerySet`` subclasses on the
+   fly, which broke pickling of those querysets. For this reason,
+   ``PassThroughManager`` is recommended instead.
 
-.. _contributed by Paul McLanahan: http://paulm.us/post/3717466639/passthroughmanager-for-django
-

model_utils/__init__.py

+from django import VERSION
+if VERSION < (1, 2):
+    import warnings
+    warnings.warn(
+        "Django 1.1 support in django-model-utils is pending deprecation.",
+        PendingDeprecationWarning)
+
+
+
 class ChoiceEnum(object):
     """
     DEPRECATED: Use ``Choices`` (below) instead. This class has less
     surprising data corruption if new choices are inserted in the
     middle of the list. Automatic assignment of numeric IDs is not
     such a great idea after all.
-    
+
     A class to encapsulate handy functionality for lists of choices
     for a Django model field.
 
     'PUBLISHED'
     >>> tuple(STATUS)
     ((0, 'DRAFT'), (1, 'PUBLISHED'))
-    
+
     """
     def __init__(self, *choices):
         import warnings
         warnings.warn("ChoiceEnum is deprecated, use Choices instead.",
-                      PendingDeprecationWarning)
+                      DeprecationWarning)
         self._choices = tuple(enumerate(choices))
         self._choice_dict = dict(self._choices)
         self._reverse_dict = dict(((i[1], i[0]) for i in self._choices))
     def __repr__(self):
         return '%s(%s)' % (self.__class__.__name__,
                            ', '.join(("'%s'" % i[1] for i in self._choices)))
-    
+
 
 class Choices(object):
     """
         try:
             return self._choice_dict[attname]
         except KeyError:
-            raise AttributeError(attname)    
-    
+            raise AttributeError(attname)
+
     def __getitem__(self, index):
         return self._choices[index]
 

model_utils/managers.py

 from types import ClassType
+import warnings
 
 from django.contrib.contenttypes.models import ContentType
 from django.db import models
         (``django.db.models.manager.Manager`` by default).
 
     """
+    warnings.warn(
+        "manager_from is pending deprecation; use PassThroughManager instead.",
+        PendingDeprecationWarning,
+        stacklevel=2)
     # collect separately the mixin classes and methods
     bases = [kwds.get('queryset_cls', QuerySet)]
     methods = {}

model_utils/models.py

+import warnings
+
 from datetime import datetime
 
 from django.db import models
     For use in trees of inherited models, to be able to downcast
     parent instances to their child types.
 
+    Pending deprecation; use InheritanceManager instead.
+
     """
     real_type = models.ForeignKey(ContentType, editable=False, null=True)
 
     objects = manager_from(InheritanceCastMixin)
 
+    def __init__(self, *args, **kwargs):
+        warnings.warn(
+            "InheritanceCastModel is pending deprecation. "
+            "Use InheritanceManager instead.",
+            PendingDeprecationWarning,
+            stacklevel=2)
+        super(InheritanceCastModel, self).__init__(*args, **kwargs)
+
     def save(self, *args, **kwargs):
         if not self.id:
             self.real_type = self._get_real_type()

model_utils/tests/tests.py

-try:
-    import cPickle as pickle
-except ImportError:
-    import pickle
+import pickle, sys, warnings
 
 from datetime import datetime, timedelta
 
     StatusManagerAdded, TimeFrameManagerAdded, Entry, Dude)
 
 
+
 class GetExcerptTests(TestCase):
     def test_split(self):
         e = get_excerpt("some content\n\n<!-- split -->\n\nsome more")
         self.assertEquals(e, 'some content\n')
 
+
     def test_auto_split(self):
         e = get_excerpt("para one\n\npara two\n\npara three")
         self.assertEquals(e, 'para one\n\npara two')
 
+
     def test_middle_of_para(self):
         e = get_excerpt("some text\n<!-- split -->\nmore text")
         self.assertEquals(e, 'some text')
 
+
     def test_middle_of_line(self):
         e = get_excerpt("some text <!-- split --> more text")
         self.assertEquals(e, "some text <!-- split --> more text")
 
+
+
 class SplitFieldTests(TestCase):
     full_text = u'summary\n\n<!-- split -->\n\nmore'
     excerpt = u'summary\n'
 
+
     def setUp(self):
         self.post = Article.objects.create(
             title='example post', body=self.full_text)
 
+
     def test_unicode_content(self):
         self.assertEquals(unicode(self.post.body), self.full_text)
 
+
     def test_excerpt(self):
         self.assertEquals(self.post.body.excerpt, self.excerpt)
 
+
     def test_content(self):
         self.assertEquals(self.post.body.content, self.full_text)
 
+
     def test_has_more(self):
         self.failUnless(self.post.body.has_more)
 
+
     def test_not_has_more(self):
         post = Article.objects.create(title='example 2',
                                       body='some text\n\nsome more\n')
         self.failIf(post.body.has_more)
 
+
     def test_load_back(self):
         post = Article.objects.get(pk=self.post.pk)
         self.assertEquals(post.body.content, self.post.body.content)
         self.assertEquals(post.body.excerpt, self.post.body.excerpt)
 
+
     def test_assign_to_body(self):
         new_text = u'different\n\n<!-- split -->\n\nother'
         self.post.body = new_text
         self.post.save()
         self.assertEquals(unicode(self.post.body), new_text)
 
+
     def test_assign_to_content(self):
         new_text = u'different\n\n<!-- split -->\n\nother'
         self.post.body.content = new_text
         self.post.save()
         self.assertEquals(unicode(self.post.body), new_text)
 
+
     def test_assign_to_excerpt(self):
         def _invalid_assignment():
             self.post.body.excerpt = 'this should fail'
         self.assertRaises(AttributeError, _invalid_assignment)
 
+
     def test_access_via_class(self):
         def _invalid_access():
             Article.body
         self.assertRaises(AttributeError, _invalid_access)
 
+
     def test_none(self):
         a = Article(title='Some Title', body=None)
         self.assertEquals(a.body, None)
 
+
     def test_assign_splittext(self):
         a = Article(title='Some Title')
         a.body = self.post.body
         self.assertEquals(a.body.excerpt, u'summary\n')
 
+
     def test_value_to_string(self):
         f = self.post._meta.get_field('body')
         self.assertEquals(f.value_to_string(self.post), self.full_text)
         self.instance = Monitored(name='Charlie')
         self.created = self.instance.name_changed
 
+
     def test_save_no_change(self):
         self.instance.save()
         self.assertEquals(self.instance.name_changed, self.created)
 
+
     def test_save_changed(self):
         self.instance.name = 'Maria'
         self.instance.save()
         self.failUnless(self.instance.name_changed > self.created)
 
+
     def test_double_save(self):
         self.instance.name = 'Jose'
         self.instance.save()
         self.instance.save()
         self.assertEquals(self.instance.name_changed, changed)
 
+
     def test_no_monitor_arg(self):
         self.assertRaises(TypeError, MonitorField)
 
 
+
 class ChoicesTests(TestCase):
     def setUp(self):
         self.STATUS = Choices('DRAFT', 'PUBLISHED')
 
+
     def test_getattr(self):
         self.assertEquals(self.STATUS.DRAFT, 'DRAFT')
 
+
     def test_indexing(self):
         self.assertEquals(self.STATUS[1], ('PUBLISHED', 'PUBLISHED'))
 
+
     def test_iteration(self):
         self.assertEquals(tuple(self.STATUS), (('DRAFT', 'DRAFT'), ('PUBLISHED', 'PUBLISHED')))
 
+
     def test_repr(self):
         self.assertEquals(repr(self.STATUS),
                           "Choices("
                           "('DRAFT', 'DRAFT', 'DRAFT'), "
                           "('PUBLISHED', 'PUBLISHED', 'PUBLISHED'))")
 
+
     def test_wrong_length_tuple(self):
         self.assertRaises(ValueError, Choices, ('a',))
 
 
+
 class LabelChoicesTests(ChoicesTests):
     def setUp(self):
         self.STATUS = Choices(
             'DELETED',
         )
 
+
     def test_iteration(self):
         self.assertEquals(tuple(self.STATUS), (
             ('DRAFT', 'is draft'),
             ('DELETED', 'DELETED'))
         )
 
+
     def test_indexing(self):
         self.assertEquals(self.STATUS[1], ('PUBLISHED', 'is published'))
 
+
     def test_default(self):
         self.assertEquals(self.STATUS.DELETED, 'DELETED')
 
+
     def test_provided(self):
         self.assertEquals(self.STATUS.DRAFT, 'DRAFT')
 
+
     def test_repr(self):
         self.assertEquals(repr(self.STATUS),
                           "Choices("
                           "('DELETED', 'DELETED', 'DELETED'))")
 
 
+
 class IdentifierChoicesTests(ChoicesTests):
     def setUp(self):
         self.STATUS = Choices(
             (1, 'PUBLISHED', 'is published'),
             (2, 'DELETED', 'is deleted'))
 
+
     def test_iteration(self):
         self.assertEqual(tuple(self.STATUS), (
                 (0, 'is draft'),
                 (1, 'is published'),
                 (2, 'is deleted')))
 
+
     def test_indexing(self):
         self.assertEquals(self.STATUS[1], (1, 'is published'))
 
+
     def test_getattr(self):
         self.assertEquals(self.STATUS.DRAFT, 0)
 
+
     def test_repr(self):
         self.assertEquals(repr(self.STATUS),
                           "Choices("
         self.parent = InheritParent.objects.create()
         self.child = InheritChild.objects.create()
 
+
     def test_parent_real_type(self):
         self.assertEquals(self.parent.real_type,
                           ContentType.objects.get_for_model(InheritParent))
 
+
     def test_child_real_type(self):
         self.assertEquals(self.child.real_type,
                           ContentType.objects.get_for_model(InheritChild))
 
+
     def test_cast(self):
         obj = InheritParent.objects.get(pk=self.child.pk).cast()
         self.assertEquals(obj.__class__, InheritChild)
 
 
+    # @@@ Use proper test skipping once Django 1.2 is minimum supported version.
+    if sys.version_info >= (2, 6):
+        # @@@ catch_warnings only available in Python 2.6 and newer
+        def test_pending_deprecation(self):
+            with warnings.catch_warnings(record=True) as w:
+                warnings.simplefilter("always")
+                InheritParent()
+                self.assertEqual(len(w), 1)
+                assert issubclass(w[-1].category, PendingDeprecationWarning)
+
+
+
 class InheritanceCastQuerysetTests(TestCase):
     def setUp(self):
         self.child = InheritChild.objects.create()
         self.child2 = InheritChild2.objects.create()
 
+
     def test_cast_manager(self):
         self.assertEquals(set(InheritParent.objects.cast()),
                           set([self.child, self.child2]))
 
+
     def test_cast(self):
         parent = InheritParent.objects.create()
         obj = InheritParent.objects.filter(pk=self.child.pk).cast()[0]
                           set([parent, self.child, self.child2]))
 
 
+
+# @@@ Use proper test skipping once 1.2 is minimum supported version.
 if django.VERSION >= (1, 2):
     class InheritanceManagerTests(TestCase):
         def setUp(self):
             self.child1 = InheritanceManagerTestChild1.objects.create()
             self.child2 = InheritanceManagerTestChild2.objects.create()
 
+
         def test_normal(self):
             self.assertEquals(set(InheritanceManagerTestParent.objects.all()),
                               set([
                         InheritanceManagerTestParent(pk=self.child2.pk),
                         ]))
 
+
         def test_select_all_subclasses(self):
             self.assertEquals(
                 set(InheritanceManagerTestParent.objects.select_subclasses()),
                 set([self.child1, self.child2]))
 
+
         def test_select_specific_subclasses(self):
             self.assertEquals(
                 set(InheritanceManagerTestParent.objects.select_subclasses(
                      InheritanceManagerTestParent(pk=self.child2.pk)]))
 
 
+
 class TimeStampedModelTests(TestCase):
     def test_created(self):
         t1 = TimeStamp.objects.create()
         t2 = TimeStamp.objects.create()
         self.assert_(t2.created > t1.created)
 
+
     def test_modified(self):
         t1 = TimeStamp.objects.create()
         t2 = TimeStamp.objects.create()
         self.assert_(t2.modified < t1.modified)
 
 
+
 class TimeFramedModelTests(TestCase):
-
     def setUp(self):
         self.now = datetime.now()
 
+
     def test_not_yet_begun(self):
         TimeFrame.objects.create(start=self.now+timedelta(days=2))
         self.assertEquals(TimeFrame.timeframed.count(), 0)
 
+
     def test_finished(self):
         TimeFrame.objects.create(end=self.now-timedelta(days=1))
         self.assertEquals(TimeFrame.timeframed.count(), 0)
 
+
     def test_no_end(self):
         TimeFrame.objects.create(start=self.now-timedelta(days=10))
         self.assertEquals(TimeFrame.timeframed.count(), 1)
 
+
     def test_no_start(self):
         TimeFrame.objects.create(end=self.now+timedelta(days=2))
         self.assertEquals(TimeFrame.timeframed.count(), 1)
 
+
     def test_within_range(self):
         TimeFrame.objects.create(start=self.now-timedelta(days=1),
                                  end=self.now+timedelta(days=1))
         self.assertEquals(TimeFrame.timeframed.count(), 1)
 
+
+
 class TimeFrameManagerAddedTests(TestCase):
-
     def test_manager_available(self):
         self.assert_(isinstance(TimeFrameManagerAdded.timeframed, QueryManager))
 
+
     def test_conflict_error(self):
         def _run():
             class ErrorModel(TimeFramedModel):
         self.assertRaises(ImproperlyConfigured, _run)
 
 
+
 class StatusModelTests(TestCase):
     def setUp(self):
         self.model = Status
         self.on_hold = Status.STATUS.on_hold
         self.active = Status.STATUS.active
 
+
     def test_created(self):
         c1 = self.model.objects.create()
         c2 = self.model.objects.create()
         self.assertEquals(self.model.active.count(), 2)
         self.assertEquals(self.model.deleted.count(), 0)
 
+
     def test_modification(self):
         t1 = self.model.objects.create()
         date_created = t1.status_changed
         self.assert_(t1.status_changed > date_active_again)
 
 
+
 class StatusModelPlainTupleTests(StatusModelTests):
     def setUp(self):
         self.model = StatusPlainTuple
         self.on_hold = StatusPlainTuple.STATUS[2][0]
         self.active = StatusPlainTuple.STATUS[0][0]
 
+
+
 class StatusManagerAddedTests(TestCase):
-
     def test_manager_available(self):
         self.assert_(isinstance(StatusManagerAdded.active, QueryManager))
 
+
     def test_conflict_error(self):
         def _run():
             class ErrorModel(StatusModel):
         self.assertRaises(ImproperlyConfigured, _run)
 
 
+
 class QueryManagerTests(TestCase):
     def setUp(self):
         data = ((True, True, 0),
         for p, c, o in data:
             Post.objects.create(published=p, confirmed=c, order=o)
 
+
     def test_passing_kwargs(self):
         qs = Post.public.all()
         self.assertEquals([p.order for p in qs], [0, 1, 4, 5])
 
+
     def test_passing_Q(self):
         qs = Post.public_confirmed.all()
         self.assertEquals([p.order for p in qs], [0, 1])
 
+
     def test_ordering(self):
         qs = Post.public_reversed.all()
         self.assertEquals([p.order for p in qs], [5, 4, 1, 0])
 
-if 'south' in settings.INSTALLED_APPS:
+
+
+# @@@ Use proper test skipping once Django 1.2 is minimum supported version.
+try:
+    from south.modelsinspector import introspector
+except ImportError:
+    introspector = None
+
+if introspector is not None:
     class SouthFreezingTests(TestCase):
         def test_introspector_adds_no_excerpt_field(self):
-            from south.modelsinspector import introspector
             mf = Article._meta.get_field('body')
             args, kwargs = introspector(mf)
             self.assertEquals(kwargs['no_excerpt_field'], 'True')
 
+
         def test_no_excerpt_field_works(self):
             from models import NoRendered
             self.assertRaises(FieldDoesNotExist,
                               '_body_excerpt')
 
 
+
 class ManagerFromTests(TestCase):
     def setUp(self):
         Entry.objects.create(author='George', published=True)
         Entry.objects.create(author='George', published=False)
         Entry.objects.create(author='Paul', published=True, feature=True)
 
+
     def test_chaining(self):
         self.assertEqual(Entry.objects.by_author('George').published().count(),
                          1)
 
+
     def test_function(self):
         self.assertEqual(Entry.objects.unpublished().count(), 1)
 
+
     def test_typecheck(self):
         self.assertRaises(TypeError, manager_from, 'somestring')
 
+
     def test_custom_get_query_set(self):
         self.assertEqual(Entry.featured.published().count(), 1)
 
+
     def test_cant_reconcile_qs_class(self):
         self.assertRaises(TypeError, Entry.broken.all)
 
+
     def test_queryset_pickling_fails(self):
         qs = Entry.objects.all()
         def dump_load():
             pqs = pickle.dumps(qs)
-            upqs = pickle.loads(pqs)
+            pickle.loads(pqs)
         self.assertRaises(pickle.PicklingError, dump_load)
 
 
+    # @@@ Use proper test skipping once Django 1.2 is minimum supported version.
+    if sys.version_info >= (2, 6):
+        # @@@ catch_warnings only available in Python 2.6 and newer
+        def test_pending_deprecation(self):
+            with warnings.catch_warnings(record=True) as w:
+                warnings.simplefilter("always")
+                manager_from()
+                self.assertEqual(len(w), 1)
+                assert issubclass(w[-1].category, PendingDeprecationWarning)
+
+
+
 class PassThroughManagerTests(TestCase):
     def setUp(self):
         Dude.objects.create(name='The Dude', abides=True, has_rug=False)
         Dude.objects.create(name='Duder', abides=False, has_rug=False)
         Dude.objects.create(name='El Duderino', abides=True, has_rug=True)
 
+
     def test_chaining(self):
         self.assertEqual(Dude.objects.by_name('Duder').count(), 1)
         self.assertEqual(Dude.objects.all().by_name('Duder').count(), 1)
         self.assertEqual(Dude.abiders.rug_positive().count(), 1)
         self.assertEqual(Dude.abiders.all().rug_positive().count(), 1)
 
+
     def test_manager_only_methods(self):
         stats = Dude.abiders.get_stats()
         self.assertEqual(stats['rug_count'], 1)
             Dude.abiders.all().get_stats()
         self.assertRaises(AttributeError, notonqs)
 
+
     def test_queryset_pickling(self):
         qs = Dude.objects.all()
         saltyqs = pickle.dumps(qs)