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

This commit is contained in:
Carl Meyer 2011-04-16 14:51:14 -05:00
parent c514c68676
commit 69d0985db1
6 changed files with 222 additions and 153 deletions

View file

@ -4,6 +4,11 @@ CHANGES
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)
------------------

View file

@ -24,7 +24,7 @@ your ``INSTALLED_APPS`` setting.
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/
@ -272,50 +272,16 @@ default manager for the model.
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
================
@ -348,107 +314,26 @@ set the ordering of the ``QuerySet`` returned by the ``QueryManager``
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
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)
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()
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::
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
@ -474,5 +359,49 @@ it will automatically use that class when creating querysets for the manager::
Post.objects.published()
Post.objects.by_author(user=request.user).unpublished()
.. _contributed by Paul McLanahan: http://paulm.us/post/3717466639/passthroughmanager-for-django
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
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.

View file

@ -1,3 +1,12 @@
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
@ -5,7 +14,7 @@ class ChoiceEnum(object):
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.
@ -25,12 +34,12 @@ class ChoiceEnum(object):
'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))
@ -50,7 +59,7 @@ class ChoiceEnum(object):
def __repr__(self):
return '%s(%s)' % (self.__class__.__name__,
', '.join(("'%s'" % i[1] for i in self._choices)))
class Choices(object):
"""
@ -116,8 +125,8 @@ 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]

View file

@ -1,4 +1,5 @@
from types import ClassType
import warnings
from django.contrib.contenttypes.models import ContentType
from django.db import models
@ -140,6 +141,10 @@ def manager_from(*mixins, **kwds):
(``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 = {}

View file

@ -1,3 +1,5 @@
import warnings
from datetime import datetime
from django.db import models
@ -18,11 +20,21 @@ class InheritanceCastModel(models.Model):
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()

View file

@ -1,7 +1,4 @@
try:
import cPickle as pickle
except ImportError:
import pickle
import pickle, sys, warnings
from datetime import datetime, timedelta
@ -25,84 +22,104 @@ from model_utils.tests.models import (
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)
@ -113,15 +130,18 @@ class MonitorFieldTests(TestCase):
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()
@ -129,33 +149,41 @@ class MonitorFieldTests(TestCase):
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(
@ -164,6 +192,7 @@ class LabelChoicesTests(ChoicesTests):
'DELETED',
)
def test_iteration(self):
self.assertEquals(tuple(self.STATUS), (
('DRAFT', 'is draft'),
@ -171,15 +200,19 @@ class LabelChoicesTests(ChoicesTests):
('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("
@ -188,6 +221,7 @@ class LabelChoicesTests(ChoicesTests):
"('DELETED', 'DELETED', 'DELETED'))")
class IdentifierChoicesTests(ChoicesTests):
def setUp(self):
self.STATUS = Choices(
@ -195,18 +229,22 @@ class IdentifierChoicesTests(ChoicesTests):
(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("
@ -220,28 +258,45 @@ class InheritanceCastModelTests(TestCase):
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]
@ -252,12 +307,15 @@ class InheritanceCastQuerysetTests(TestCase):
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([
@ -265,11 +323,13 @@ if django.VERSION >= (1, 2):
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(
@ -278,12 +338,14 @@ if django.VERSION >= (1, 2):
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()
@ -291,37 +353,44 @@ class TimeStampedModelTests(TestCase):
self.assert_(t2.modified < t1.modified)
class TimeFramedModelTests(TestCase):
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):
class TimeFrameManagerAddedTests(TestCase):
def test_manager_available(self):
self.assert_(isinstance(TimeFrameManagerAdded.timeframed, QueryManager))
def test_conflict_error(self):
def _run():
class ErrorModel(TimeFramedModel):
@ -329,12 +398,14 @@ class TimeFrameManagerAddedTests(TestCase):
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()
@ -342,6 +413,7 @@ class StatusModelTests(TestCase):
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
@ -359,17 +431,20 @@ class StatusModelTests(TestCase):
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):
class StatusManagerAddedTests(TestCase):
def test_manager_available(self):
self.assert_(isinstance(StatusManagerAdded.active, QueryManager))
def test_conflict_error(self):
def _run():
class ErrorModel(StatusModel):
@ -381,6 +456,7 @@ class StatusManagerAddedTests(TestCase):
self.assertRaises(ImproperlyConfigured, _run)
class QueryManagerTests(TestCase):
def setUp(self):
data = ((True, True, 0),
@ -392,26 +468,37 @@ class QueryManagerTests(TestCase):
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,
@ -419,36 +506,55 @@ if 'south' in settings.INSTALLED_APPS:
'_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)
@ -456,12 +562,14 @@ class PassThroughManagerTests(TestCase):
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)
@ -469,6 +577,7 @@ class PassThroughManagerTests(TestCase):
Dude.abiders.all().get_stats()
self.assertRaises(AttributeError, notonqs)
def test_queryset_pickling(self):
qs = Dude.objects.all()
saltyqs = pickle.dumps(qs)