From 8cb21aabbcba6aac389b2c073024fea08878126e Mon Sep 17 00:00:00 2001 From: Sachi King Date: Sun, 2 Apr 2017 21:34:55 +1000 Subject: [PATCH] Support django 1.11 iterator changes Django starting with 1.9 switched to using a class to provide an iterator for the querymanager. Between 1.9 and 1.10 changes slowly stopped referencing that function and instead started calling _iterator_class directly. As the functionality model-utils is patching has moved, this patch moves the iterator logic to a class to match the changes that have been made in Django in version 1.9. As Django 1.8 is a LTS release that is still supported, iterator() is retained in the InheritanceQuerySetMixin and can be removed when support for Django 1.8 is removed. This goes for the try-except in the import statements as well. --- CHANGES.rst | 1 + model_utils/managers.py | 42 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 88fa89e..2d99060 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -7,6 +7,7 @@ master (unreleased) * Drop support for Python 2.6. * Drop support for Django 1.4, 1.5, 1.6, 1.7. * Exclude tests from the distribution, fixes GH-258. +* Add support for Django 1.11 GH-269 2.6.1 (2017.01.11) diff --git a/model_utils/managers.py b/model_utils/managers.py index 6ec9301..9dc68a2 100644 --- a/model_utils/managers.py +++ b/model_utils/managers.py @@ -3,13 +3,54 @@ import django from django.db import models from django.db.models.fields.related import OneToOneField, OneToOneRel from django.db.models.query import QuerySet +try: + from django.db.models.query import BaseIterable, ModelIterable +except ImportError: + # Django 1.8 does not have iterable classes + BaseIterable = object from django.core.exceptions import ObjectDoesNotExist from django.db.models.constants import LOOKUP_SEP from django.utils.six import string_types +class InheritanceIterable(BaseIterable): + def __iter__(self): + queryset = self.queryset + iter = ModelIterable(queryset) + if getattr(queryset, 'subclasses', False): + extras = tuple(queryset.query.extra.keys()) + # sort the subclass names longest first, + # so with 'a' and 'a__b' it goes as deep as possible + subclasses = sorted(queryset.subclasses, key=len, reverse=True) + for obj in iter: + sub_obj = None + for s in subclasses: + sub_obj = queryset._get_sub_obj_recurse(obj, s) + if sub_obj: + break + if not sub_obj: + sub_obj = obj + + if getattr(queryset, '_annotated', False): + for k in queryset._annotated: + setattr(sub_obj, k, getattr(obj, k)) + + for k in extras: + setattr(sub_obj, k, getattr(obj, k)) + + yield sub_obj + else: + for obj in iter: + yield obj + + class InheritanceQuerySetMixin(object): + def __init__(self, *args, **kwargs): + super(InheritanceQuerySetMixin, self).__init__(*args, **kwargs) + if django.VERSION > (1, 8): + self._iterable_class = InheritanceIterable + def select_subclasses(self, *subclasses): levels = self._get_maximum_depth() calculated_subclasses = self._get_subclasses_recurse( @@ -64,6 +105,7 @@ class InheritanceQuerySetMixin(object): return qset def iterator(self): + # Maintained for Django 1.8 compatability iter = super(InheritanceQuerySetMixin, self).iterator() if getattr(self, 'subclasses', False): extras = tuple(self.query.extra.keys())