diff --git a/CHANGES.rst b/CHANGES.rst index 656a68c..58396ae 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -11,7 +11,8 @@ tip (unreleased) .. _BitBucket: https://bitbucket.org/carljm/django-model-utils/overview .. _GitHub: https://github.com/carljm/django-model-utils/ -- Removed deprecated ``ChoiceEnum`` class. +- Removed deprecated ``ChoiceEnum``, ``InheritanceCastModel``, + ``InheritanceCastManager``, and ``manager_from``. - Added ``UpdateOrCreateMixin`` for custom queryset subclasses. Thanks Antti Kaihola. diff --git a/README.rst b/README.rst index 084decc..1316766 100644 --- a/README.rst +++ b/README.rst @@ -305,14 +305,6 @@ it's safe to use as your default manager for the model. 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 - 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/ .. _Django bug #16855: https://code.djangoproject.com/ticket/16855 @@ -396,13 +388,6 @@ directly on the manager:: 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. UpdateOrCreateMixin diff --git a/model_utils/fields.py b/model_utils/fields.py index ef9899f..dff44b0 100644 --- a/model_utils/fields.py +++ b/model_utils/fields.py @@ -3,8 +3,6 @@ from datetime import datetime from django.db import models from django.conf import settings -from model_utils import Choices - try: from django.utils.timezone import now as now diff --git a/model_utils/managers.py b/model_utils/managers.py index 8aa7f89..8b297e6 100644 --- a/model_utils/managers.py +++ b/model_utils/managers.py @@ -1,11 +1,7 @@ -from types import ClassType import sys -import warnings -from django.contrib.contenttypes.models import ContentType from django.db import IntegrityError, models, transaction from django.db.models.fields.related import OneToOneField -from django.db.models.manager import Manager from django.db.models.query import QuerySet from django.core.exceptions import ObjectDoesNotExist @@ -67,26 +63,6 @@ class InheritanceManager(models.Manager): return self.get_query_set().select_subclasses().get(*args, **kwargs) -class InheritanceCastMixin(object): - def cast(self): - results = tuple(self.values_list('pk', 'real_type')) - type_to_pks = {} - for pk, real_type_id in results: - type_to_pks.setdefault(real_type_id, []).append(pk) - content_types = ContentType.objects.in_bulk(type_to_pks.keys()) - pk_to_child = {} - for real_type_id, pks in type_to_pks.iteritems(): - content_type = content_types[real_type_id] - child_type = content_type.model_class() - children = child_type._default_manager.in_bulk(pks) - for pk, child in children.iteritems(): - pk_to_child[pk] = child - children = [] - # sort children into same order as parents where returned - for pk, real_type_id in results: - children.append(pk_to_child[pk]) - return children - class QueryManager(models.Manager): use_for_related_fields = True @@ -180,62 +156,6 @@ def unpickle_pass_through_manager_for_queryset_class(base, queryset_cls): return cls.__new__(cls) -def manager_from(*mixins, **kwds): - """ - Returns a Manager instance with extra methods, also available and - chainable on generated querysets. - - (By George Sakkis, originally posted at - http://djangosnippets.org/snippets/2117/) - - :param mixins: Each ``mixin`` can be either a class or a function. The - generated manager and associated queryset subclasses extend the mixin - classes and include the mixin functions (as methods). - - :keyword queryset_cls: The base queryset class to extend from - (``django.db.models.query.QuerySet`` by default). - - :keyword manager_cls: The base manager class to extend from - (``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 = {} - for mixin in mixins: - if isinstance(mixin, (ClassType, type)): - bases.append(mixin) - else: - try: methods[mixin.__name__] = mixin - except AttributeError: - raise TypeError('Mixin must be class or function, not %s' % - mixin.__class__) - # create the QuerySet subclass - id = hash(mixins + tuple(kwds.iteritems())) - new_queryset_cls = type('Queryset_%d' % id, tuple(bases), methods) - # create the Manager subclass - bases[0] = manager_cls = kwds.get('manager_cls', Manager) - new_manager_cls = type('Manager_%d' % id, tuple(bases), methods) - # and finally override new manager's get_query_set - super_get_query_set = manager_cls.get_query_set - def get_query_set(self): - # first honor the super manager's get_query_set - qs = super_get_query_set(self) - # and then try to bless the returned queryset by reassigning it to the - # newly created Queryset class, though this may not be feasible - if not issubclass(new_queryset_cls, qs.__class__): - raise TypeError('QuerySet subclass conflict: cannot determine a ' - 'unique class for queryset instance') - qs.__class__ = new_queryset_cls - return qs - new_manager_cls.get_query_set = get_query_set - return new_manager_cls() - - class UpdateOrCreateMixin(object): def update_or_create(self, **kwargs): """ diff --git a/model_utils/models.py b/model_utils/models.py index c22e8d7..40e7aa8 100644 --- a/model_utils/models.py +++ b/model_utils/models.py @@ -1,15 +1,11 @@ -import warnings - from datetime import datetime from django.db import models -from django.contrib.contenttypes.models import ContentType from django.utils.translation import ugettext_lazy as _ from django.db.models.fields import FieldDoesNotExist from django.core.exceptions import ImproperlyConfigured -from model_utils.managers import manager_from, InheritanceCastMixin, \ - QueryManager +from model_utils.managers import QueryManager from model_utils.fields import AutoCreatedField, AutoLastModifiedField, \ StatusField, MonitorField @@ -19,42 +15,6 @@ except ImportError: now = datetime.now -class InheritanceCastModel(models.Model): - """ - An abstract base class that provides a ``real_type`` FK to ContentType. - - 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() - super(InheritanceCastModel, self).save(*args, **kwargs) - - def _get_real_type(self): - return ContentType.objects.get_for_model(type(self)) - - def cast(self): - return self.real_type.get_object_for_this_type(pk=self.pk) - - class Meta: - abstract = True - class TimeStampedModel(models.Model): """ diff --git a/model_utils/tests/models.py b/model_utils/tests/models.py index df9fdad..27fd4a1 100644 --- a/model_utils/tests/models.py +++ b/model_utils/tests/models.py @@ -1,34 +1,13 @@ from django.db import models from django.utils.translation import ugettext_lazy as _ -from model_utils.models import InheritanceCastModel, TimeStampedModel, StatusModel, TimeFramedModel -from model_utils.managers import QueryManager, manager_from, InheritanceManager, PassThroughManager, UpdateOrCreateMixin +from model_utils.models import TimeStampedModel, StatusModel, TimeFramedModel +from model_utils.managers import QueryManager, InheritanceManager, PassThroughManager, UpdateOrCreateMixin from model_utils.fields import SplitField, MonitorField from model_utils import Choices -class InheritParent(InheritanceCastModel): - non_related_field_using_descriptor = models.FileField(upload_to="test") - normal_field = models.TextField() - pass - - - -class InheritChild(InheritParent): - non_related_field_using_descriptor_2 = models.FileField(upload_to="test") - normal_field_2 = models.TextField() - pass - - - -class InheritChild2(InheritParent): - non_related_field_using_descriptor_3 = models.FileField(upload_to="test") - normal_field_3 = models.TextField() - pass - - - class InheritanceManagerTestRelated(models.Model): pass @@ -178,19 +157,6 @@ class FeaturedManager(models.Manager): -class Entry(models.Model): - author = models.CharField(max_length=20) - published = models.BooleanField() - feature = models.BooleanField(default=False) - - objects = manager_from(AuthorMixin, PublishedMixin, unpublished) - broken = manager_from(PublishedMixin, manager_cls=FeaturedManager) - featured = manager_from(PublishedMixin, - manager_cls=FeaturedManager, - queryset_cls=ByAuthorQuerySet) - - - class DudeQuerySet(models.query.QuerySet): def abiding(self): return self.filter(abides=True) diff --git a/model_utils/tests/tests.py b/model_utils/tests/tests.py index 5f9f637..b049bd5 100644 --- a/model_utils/tests/tests.py +++ b/model_utils/tests/tests.py @@ -1,6 +1,6 @@ from __future__ import with_statement -import pickle, warnings +import pickle from datetime import date, datetime, timedelta @@ -9,18 +9,16 @@ from django.db.models.fields import FieldDoesNotExist from django.core.exceptions import ImproperlyConfigured from django.test import TestCase -from django.contrib.contenttypes.models import ContentType - from model_utils import Choices from model_utils.fields import get_excerpt, MonitorField -from model_utils.managers import QueryManager, manager_from +from model_utils.managers import QueryManager from model_utils.models import StatusModel, TimeFramedModel from model_utils.tests.models import ( - InheritParent, InheritChild, InheritChild2, InheritanceManagerTestRelated, + InheritanceManagerTestRelated, InheritanceManagerTestParent, InheritanceManagerTestChild1, InheritanceManagerTestChild2, TimeStamp, Post, Article, Status, StatusPlainTuple, TimeFrame, Monitored, StatusManagerAdded, - TimeFrameManagerAdded, Entry, Dude, SplitFieldAbstractParent, Car, Spot, + TimeFrameManagerAdded, Dude, SplitFieldAbstractParent, Car, Spot, Person) @@ -277,57 +275,6 @@ class IdentifierChoicesTests(ChoicesTests): "(2, 'DELETED', 'is deleted'))") -class InheritanceCastModelTests(TestCase): - def setUp(self): - 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) - - - 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] - self.assertEquals(obj.__class__, InheritChild) - self.assertEquals(set(InheritChild2.objects.all().cast()), - set([self.child2])) - self.assertEquals(set(InheritParent.objects.all().cast()), - set([parent, self.child, self.child2])) - - class InheritanceManagerTests(TestCase): def setUp(self): @@ -579,51 +526,6 @@ if introspector: -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) - pickle.loads(pqs) - self.assertRaises(pickle.PicklingError, dump_load) - - - 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)