From 3110794afc0ccbc4878eb6c7ea53090af44880ba Mon Sep 17 00:00:00 2001 From: Carl Meyer Date: Tue, 27 Jan 2015 16:43:29 -0700 Subject: [PATCH 1/4] Fix 'add_*_manager' signal handlers for Django 1.8+. --- model_utils/models.py | 49 +++++++++++++++++++++++++++++-------------- 1 file changed, 33 insertions(+), 16 deletions(-) diff --git a/model_utils/models.py b/model_utils/models.py index 8030bea..94d2077 100644 --- a/model_utils/models.py +++ b/model_utils/models.py @@ -36,6 +36,7 @@ class TimeFramedModel(models.Model): class Meta: abstract = True + class StatusModel(models.Model): """ An abstract base class model with a ``status`` field that @@ -51,6 +52,7 @@ class StatusModel(models.Model): class Meta: abstract = True + def add_status_query_managers(sender, **kwargs): """ Add a Querymanager for each status item dynamically. @@ -59,16 +61,15 @@ def add_status_query_managers(sender, **kwargs): if not issubclass(sender, StatusModel): return for value, display in getattr(sender, 'STATUS', ()): - try: - sender._meta.get_field(value) - raise ImproperlyConfigured("StatusModel: Model '%s' has a field " - "named '%s' which conflicts with a " - "status of the same name." - % (sender.__name__, value)) - except FieldDoesNotExist: - pass + if _field_exists(sender, value): + raise ImproperlyConfigured( + "StatusModel: Model '%s' has a field named '%s' which " + "conflicts with a status of the same name." + % (sender.__name__, value) + ) sender.add_to_class(value, QueryManager(status=value)) + def add_timeframed_query_manager(sender, **kwargs): """ Add a QueryManager for a specific timeframe. @@ -76,14 +77,12 @@ def add_timeframed_query_manager(sender, **kwargs): """ if not issubclass(sender, TimeFramedModel): return - try: - sender._meta.get_field('timeframed') - raise ImproperlyConfigured("Model '%s' has a field named " - "'timeframed' which conflicts with " - "the TimeFramedModel manager." - % sender.__name__) - except FieldDoesNotExist: - pass + if _field_exists(sender, 'timeframed'): + raise ImproperlyConfigured( + "Model '%s' has a field named 'timeframed' " + "which conflicts with the TimeFramedModel manager." + % sender.__name__ + ) sender.add_to_class('timeframed', QueryManager( (models.Q(start__lte=now) | models.Q(start__isnull=True)) & (models.Q(end__gte=now) | models.Q(end__isnull=True)) @@ -92,3 +91,21 @@ def add_timeframed_query_manager(sender, **kwargs): models.signals.class_prepared.connect(add_status_query_managers) models.signals.class_prepared.connect(add_timeframed_query_manager) + + +def _field_exists(model_class, field_name): + if hasattr(model_class._meta, '_get_fields'): + # Django 1.8+ + field_exists = bool([ + f for f in model_class._meta._get_fields(reverse=False) + if f.name == field_name + ]) + else: + # Django 1.7 and previous + try: + model_class._meta.get_field(field_name) + except FieldDoesNotExist: + field_exists = False + else: + field_exists = True + return field_exists From 3f9b1cfac8a3e316fbb49c03b68a4eb629a67795 Mon Sep 17 00:00:00 2001 From: Carl Meyer Date: Tue, 27 Jan 2015 16:48:06 -0700 Subject: [PATCH 2/4] Fix select_subclasses for Django 1.8. --- model_utils/managers.py | 36 ++++++++++++++++++++---------------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/model_utils/managers.py b/model_utils/managers.py index 0028f84..b6496ba 100644 --- a/model_utils/managers.py +++ b/model_utils/managers.py @@ -8,7 +8,7 @@ from django.core.exceptions import ObjectDoesNotExist try: from django.db.models.constants import LOOKUP_SEP from django.utils.six import string_types -except ImportError: # Django < 1.5 +except ImportError: # Django < 1.5 from django.db.models.sql.constants import LOOKUP_SEP string_types = (basestring,) @@ -53,20 +53,18 @@ class InheritanceQuerySetMixin(object): new_qs.subclasses = subclasses return new_qs - def _clone(self, klass=None, setup=False, **kwargs): for name in ['subclasses', '_annotated']: if hasattr(self, name): kwargs[name] = getattr(self, name) - return super(InheritanceQuerySetMixin, self)._clone(klass, setup, **kwargs) - + return super(InheritanceQuerySetMixin, self)._clone( + klass, setup, **kwargs) def annotate(self, *args, **kwargs): qset = super(InheritanceQuerySetMixin, self).annotate(*args, **kwargs) qset._annotated = [a.default_alias for a in args] + list(kwargs.keys()) return qset - def iterator(self): iter = super(InheritanceQuerySetMixin, self).iterator() if getattr(self, 'subclasses', False): @@ -95,7 +93,6 @@ class InheritanceQuerySetMixin(object): for obj in iter: yield obj - def _get_subclasses_recurse(self, model, levels=None): """ Given a Model class, find all related objects, exploring children @@ -115,11 +112,11 @@ class InheritanceQuerySetMixin(object): if levels or levels is None: for subclass in self._get_subclasses_recurse( rel.field.model, levels=levels): - subclasses.append(rel.get_accessor_name() + LOOKUP_SEP + subclass) + subclasses.append( + rel.get_accessor_name() + LOOKUP_SEP + subclass) subclasses.append(rel.get_accessor_name()) return subclasses - def _get_ancestors_path(self, model, levels=None): """ Serves as an opposite to _get_subclasses_recurse, instead walking from @@ -127,23 +124,27 @@ class InheritanceQuerySetMixin(object): select_related string backwards. """ if not issubclass(model, self.model): - raise ValueError("%r is not a subclass of %r" % (model, self.model)) + raise ValueError( + "%r is not a subclass of %r" % (model, self.model)) ancestry = [] # should be a OneToOneField or None - parent = model._meta.get_ancestor_link(self.model) + parent_link = model._meta.get_ancestor_link(self.model) if levels: levels -= 1 - while parent is not None: - ancestry.insert(0, parent.related.get_accessor_name()) + while parent_link is not None: + ancestry.insert(0, parent_link.related.get_accessor_name()) if levels or levels is None: - parent = parent.related.parent_model._meta.get_ancestor_link( + if django.VERSION < (1, 8): + parent_model = parent_link.related.parent_model + else: + parent_model = parent_link.related.model + parent_link = parent_model._meta.get_ancestor_link( self.model) else: - parent = None + parent_link = None return LOOKUP_SEP.join(ancestry) - def _get_sub_obj_recurse(self, obj, s): rel, _, s = s.partition(LOOKUP_SEP) try: @@ -170,6 +171,7 @@ class InheritanceQuerySetMixin(object): levels = 1 return levels + class InheritanceManagerMixin(object): use_for_related_fields = True @@ -188,6 +190,7 @@ class InheritanceManagerMixin(object): class InheritanceQuerySet(InheritanceQuerySetMixin, QuerySet): pass + class InheritanceManager(InheritanceManagerMixin, models.Manager): pass @@ -271,7 +274,8 @@ class PassThroughManagerMixin(object): @classmethod def for_queryset_class(cls, queryset_cls): - return create_pass_through_manager_for_queryset_class(cls, queryset_cls) + return create_pass_through_manager_for_queryset_class( + cls, queryset_cls) class PassThroughManager(PassThroughManagerMixin, models.Manager): From fce5b4391d496711ea86088ec83f0bfd45fe7ce4 Mon Sep 17 00:00:00 2001 From: Carl Meyer Date: Tue, 27 Jan 2015 16:49:16 -0700 Subject: [PATCH 3/4] Remove allow-failures from Travis config. --- .travis.yml | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/.travis.yml b/.travis.yml index cc17a3a..c2aa337 100644 --- a/.travis.yml +++ b/.travis.yml @@ -33,15 +33,4 @@ install: script: - tox -matrix: - allow_failures: - - env: TOXENV=py27-django18 - - env: TOXENV=py32-django18 - - env: TOXENV=py33-django18 - - env: TOXENV=py34-django18 - - env: TOXENV=py27-django_trunk - - env: TOXENV=py32-django_trunk - - env: TOXENV=py33-django_trunk - - env: TOXENV=py34-django_trunk - after_success: coveralls From a4680a32c65f9cbea35428834723a4dea652b5ee Mon Sep 17 00:00:00 2001 From: Carl Meyer Date: Tue, 27 Jan 2015 16:59:22 -0700 Subject: [PATCH 4/4] Add Django trunk builds back to allowed-failures. --- .travis.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.travis.yml b/.travis.yml index c2aa337..b24d579 100644 --- a/.travis.yml +++ b/.travis.yml @@ -33,4 +33,11 @@ install: script: - tox +matrix: + allow_failures: + - env: TOXENV=py27-django_trunk + - env: TOXENV=py32-django_trunk + - env: TOXENV=py33-django_trunk + - env: TOXENV=py34-django_trunk + after_success: coveralls