From 0efaad1218fd95a10de6a14f6ada71ea39c80c1a Mon Sep 17 00:00:00 2001 From: Bruno Alla Date: Thu, 5 Jan 2017 14:29:35 +0000 Subject: [PATCH] Fix issue when extend QuerySet and Manager - fixes #249 --- model_utils/managers.py | 14 ++++--- model_utils/tests/managers.py | 15 +++++++ model_utils/tests/models.py | 41 ++++++------------- model_utils/tests/test_managers/__init__.py | 1 + .../test_managers/test_softdelete_manager.py | 28 +++++++++++++ 5 files changed, 64 insertions(+), 35 deletions(-) create mode 100644 model_utils/tests/managers.py create mode 100644 model_utils/tests/test_managers/test_softdelete_manager.py diff --git a/model_utils/managers.py b/model_utils/managers.py index c6a2989..1c758cd 100644 --- a/model_utils/managers.py +++ b/model_utils/managers.py @@ -192,11 +192,16 @@ class InheritanceQuerySetMixin(object): return levels +class InheritanceQuerySet(InheritanceQuerySetMixin, QuerySet): + pass + + class InheritanceManagerMixin(object): use_for_related_fields = True + _queryset_class = InheritanceQuerySet def get_queryset(self): - return InheritanceQuerySet(self.model) + return self._queryset_class(self.model) get_query_set = get_queryset @@ -207,10 +212,6 @@ class InheritanceManagerMixin(object): return self.get_queryset().get_subclass(*args, **kwargs) -class InheritanceQuerySet(InheritanceQuerySetMixin, QuerySet): - pass - - class InheritanceManager(InheritanceManagerMixin, models.Manager): pass @@ -265,6 +266,7 @@ class SoftDeletableManager(models.Manager): Manager that limits the queryset by default to show only not removed instances of model. """ + use_for_related_fields = True _queryset_class = SoftDeletableQuerySet def get_queryset(self): @@ -275,6 +277,6 @@ class SoftDeletableManager(models.Manager): if hasattr(self, '_hints'): kwargs['hints'] = self._hints - return SoftDeletableQuerySet(**kwargs).filter(is_removed=False) + return self._queryset_class(**kwargs).filter(is_removed=False) get_query_set = get_queryset diff --git a/model_utils/tests/managers.py b/model_utils/tests/managers.py new file mode 100644 index 0000000..4a055f2 --- /dev/null +++ b/model_utils/tests/managers.py @@ -0,0 +1,15 @@ +from __future__ import unicode_literals, absolute_import + +from model_utils.managers import SoftDeletableQuerySet, SoftDeletableManager + + +class CustomSoftDeleteQuerySet(SoftDeletableQuerySet): + def only_read(self): + return self.filter(is_read=True) + + +class CustomSoftDeleteManager(SoftDeletableManager): + _queryset_class = CustomSoftDeleteQuerySet + + def only_read(self): + return self.get_queryset().only_read() diff --git a/model_utils/tests/models.py b/model_utils/tests/models.py index 7876828..c10468d 100644 --- a/model_utils/tests/models.py +++ b/model_utils/tests/models.py @@ -4,25 +4,24 @@ from django.db import models from django.utils.encoding import python_2_unicode_compatible from django.utils.translation import ugettext_lazy as _ +from model_utils import Choices +from model_utils.fields import SplitField, MonitorField, StatusField +from model_utils.managers import QueryManager, InheritanceManager from model_utils.models import ( SoftDeletableModel, StatusModel, TimeFramedModel, TimeStampedModel, ) -from model_utils.tracker import FieldTracker, ModelTracker -from model_utils.managers import QueryManager, InheritanceManager -from model_utils.fields import SplitField, MonitorField, StatusField from model_utils.tests.fields import MutableField -from model_utils import Choices - +from model_utils.tests.managers import CustomSoftDeleteManager +from model_utils.tracker import FieldTracker, ModelTracker class InheritanceManagerTestRelated(models.Model): pass - @python_2_unicode_compatible class InheritanceManagerTestParent(models.Model): # FileField is just a handy descriptor-using field. Refs #6. @@ -40,8 +39,7 @@ class InheritanceManagerTestParent(models.Model): return "%s(%s)" % ( self.__class__.__name__[len('InheritanceManagerTest'):], self.pk, - ) - + ) class InheritanceManagerTestChild1(InheritanceManagerTestParent): @@ -50,23 +48,19 @@ class InheritanceManagerTestChild1(InheritanceManagerTestParent): objects = InheritanceManager() - class InheritanceManagerTestGrandChild1(InheritanceManagerTestChild1): text_field = models.TextField() - class InheritanceManagerTestGrandChild1_2(InheritanceManagerTestChild1): text_field = models.TextField() - class InheritanceManagerTestChild2(InheritanceManagerTestParent): non_related_field_using_descriptor_2 = models.FileField(upload_to="test") normal_field_2 = models.TextField() - class InheritanceManagerTestChild3(InheritanceManagerTestParent): parent_ptr = models.OneToOneField( InheritanceManagerTestParent, related_name='manual_onetoone', @@ -77,29 +71,24 @@ class TimeStamp(TimeStampedModel): pass - class TimeFrame(TimeFramedModel): pass - class TimeFrameManagerAdded(TimeFramedModel): pass - class Monitored(models.Model): name = models.CharField(max_length=25) name_changed = MonitorField(monitor="name") - class MonitorWhen(models.Model): name = models.CharField(max_length=25) name_changed = MonitorField(monitor="name", when=["Jose", "Maria"]) - class MonitorWhenEmpty(models.Model): name = models.CharField(max_length=25) name_changed = MonitorField(monitor="name", when=[]) @@ -120,7 +109,6 @@ class Status(StatusModel): ) - class StatusPlainTuple(StatusModel): STATUS = ( ("active", _("active")), @@ -129,7 +117,6 @@ class StatusPlainTuple(StatusModel): ) - class StatusManagerAdded(StatusModel): STATUS = ( ("active", _("active")), @@ -138,7 +125,6 @@ class StatusManagerAdded(StatusModel): ) - class Post(models.Model): published = models.BooleanField(default=False) confirmed = models.BooleanField(default=False) @@ -154,22 +140,18 @@ class Post(models.Model): ordering = ("order",) - class Article(models.Model): title = models.CharField(max_length=50) body = SplitField() - class SplitFieldAbstractParent(models.Model): content = SplitField() - class Meta: abstract = True - class NoRendered(models.Model): """ Test that the no_excerpt_field keyword arg works. This arg should @@ -179,29 +161,24 @@ class NoRendered(models.Model): body = SplitField(no_excerpt_field=True) - class AuthorMixin(object): def by_author(self, name): return self.filter(author=name) - class PublishedMixin(object): def published(self): return self.filter(published=True) - def unpublished(self): return self.filter(published=False) - class ByAuthorQuerySet(models.query.QuerySet, AuthorMixin): pass - class FeaturedManager(models.Manager): def get_queryset(self): kwargs = {} @@ -326,3 +303,9 @@ class SoftDeletable(SoftDeletableModel): name = models.CharField(max_length=20) all_objects = models.Manager() + + +class CustomSoftDelete(SoftDeletableModel): + is_read = models.BooleanField(default=False) + + objects = CustomSoftDeleteManager() diff --git a/model_utils/tests/test_managers/__init__.py b/model_utils/tests/test_managers/__init__.py index 0f8aec6..92661aa 100644 --- a/model_utils/tests/test_managers/__init__.py +++ b/model_utils/tests/test_managers/__init__.py @@ -2,3 +2,4 @@ from .test_inheritance_manager import * from .test_query_manager import * from .test_status_manager import * +from .test_softdelete_manager import * diff --git a/model_utils/tests/test_managers/test_softdelete_manager.py b/model_utils/tests/test_managers/test_softdelete_manager.py new file mode 100644 index 0000000..3f5ed47 --- /dev/null +++ b/model_utils/tests/test_managers/test_softdelete_manager.py @@ -0,0 +1,28 @@ +from __future__ import unicode_literals + +from django.test import TestCase + +from model_utils.tests.models import CustomSoftDelete + + +class CustomSoftDeleteManagerTests(TestCase): + + def test_custom_manager_empty(self): + qs = CustomSoftDelete.objects.only_read() + self.assertEqual(qs.count(), 0) + + def test_custom_qs_empty(self): + qs = CustomSoftDelete.objects.all().only_read() + self.assertEqual(qs.count(), 0) + + def test_is_read(self): + for is_read in [True, False, True, False]: + CustomSoftDelete.objects.create(is_read=is_read) + qs = CustomSoftDelete.objects.only_read() + self.assertEqual(qs.count(), 2) + + def test_is_read_removed(self): + for is_read, is_removed in [(True, True), (True, False), (False, False), (False, True)]: + CustomSoftDelete.objects.create(is_read=is_read, is_removed=is_removed) + qs = CustomSoftDelete.objects.only_read() + self.assertEqual(qs.count(), 1)