mirror of
https://github.com/Hopiu/django-model-utils.git
synced 2026-03-16 20:00:23 +00:00
Started deprecation for manager_from, InheritanceCastModel, and Django 1.1 support.
This commit is contained in:
parent
c514c68676
commit
69d0985db1
6 changed files with 222 additions and 153 deletions
|
|
@ -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)
|
||||
------------------
|
||||
|
||||
|
|
|
|||
203
README.rst
203
README.rst
|
|
@ -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.
|
||||
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
||||
|
|
|
|||
|
|
@ -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 = {}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
Loading…
Reference in a new issue