From 0c7b1aa657abe92faccbad69bea251a187465e6b Mon Sep 17 00:00:00 2001 From: Jerome Leclanche Date: Fri, 19 May 2017 09:58:34 +0300 Subject: [PATCH] Drop support for Django 1.8 --- polymorphic/admin/helpers.py | 29 --------- polymorphic/admin/parentadmin.py | 34 ++-------- polymorphic/base.py | 108 +------------------------------ polymorphic/formsets/generic.py | 9 +-- polymorphic/formsets/models.py | 6 +- polymorphic/query.py | 78 ++++++---------------- polymorphic/query_translate.py | 10 +-- polymorphic/tests/test_orm.py | 35 ++-------- 8 files changed, 38 insertions(+), 271 deletions(-) diff --git a/polymorphic/admin/helpers.py b/polymorphic/admin/helpers.py index d7fdef6..c415414 100644 --- a/polymorphic/admin/helpers.py +++ b/polymorphic/admin/helpers.py @@ -3,12 +3,7 @@ Rendering utils for admin forms; This makes sure that admin fieldsets/layout settings are exported to the template. """ -import json - from django.contrib.admin.helpers import InlineAdminFormSet, InlineAdminForm, AdminField -from django.utils.encoding import force_text -from django.utils.text import capfirst -from django.utils.translation import ugettext from polymorphic.formsets import BasePolymorphicModelFormSet @@ -82,30 +77,6 @@ class PolymorphicInlineAdminFormSet(InlineAdminFormSet): fields.update(child_inline.get_prepopulated_fields(self.request, self.obj)) return fields - # The polymorphic template follows the same method like all other inlines do in Django 1.10. - # This method is added for compatibility with older Django versions. - def inline_formset_data(self): - """ - A JavaScript data structure for the JavaScript code - """ - verbose_name = self.opts.verbose_name - return json.dumps({ - 'name': '#%s' % self.formset.prefix, - 'options': { - 'prefix': self.formset.prefix, - 'addText': ugettext('Add another %(verbose_name)s') % { - 'verbose_name': capfirst(verbose_name), - }, - 'childTypes': [ - { - 'type': model._meta.model_name, - 'name': force_text(model._meta.verbose_name) - } for model in self.formset.child_forms.keys() - ], - 'deleteText': ugettext('Remove'), - } - }) - class PolymorphicInlineSupportMixin(object): """ diff --git a/polymorphic/admin/parentadmin.py b/polymorphic/admin/parentadmin.py index da7dffc..ad791be 100644 --- a/polymorphic/admin/parentadmin.py +++ b/polymorphic/admin/parentadmin.py @@ -4,7 +4,6 @@ The parent admin displays the list view of the base model. import sys import warnings -import django from django.conf.urls import url from django.contrib import admin from django.contrib.admin.helpers import AdminErrorList, AdminForm @@ -270,31 +269,10 @@ class PolymorphicParentModelAdmin(admin.ModelAdmin): if not self._compat_mode: return urls - info = _get_opt(self.model) - - # Patch the change view URL so it's not a big catch-all; allowing all - # custom URLs to be added to the end. This is done by adding '/$' to the - # end of the regex. The url needs to be recreated, patching url.regex - # is not an option Django 1.4's LocaleRegexProvider changed it. - if django.VERSION < (1, 9): - # On Django 1.9, the change view URL has been changed from - # //// to ////change/, which is - # why we can skip this workaround for Django >= 1.9. - new_change_url = url( - r'^{0}/$'.format(self.pk_regex), - self.admin_site.admin_view(self.change_view), - name='{0}_{1}_change'.format(*info) - ) - - redirect_urls = [] - for i, oldurl in enumerate(urls): - if oldurl.name == new_change_url.name: - urls[i] = new_change_url - else: - # For Django 1.9, the redirect at the end acts as catch all. - # The custom urls need to be inserted before that. - redirect_urls = [pat for pat in urls if not pat.name] # redirect URL has no name. - urls = [pat for pat in urls if pat.name] + # The redirect at the end acts as catch all. + # The custom urls need to be inserted before that. + redirect_urls = [pat for pat in urls if not pat.name] # redirect URL has no name. + urls = [pat for pat in urls if pat.name] # Define the catch-all for custom views custom_urls = [ @@ -421,7 +399,3 @@ class PolymorphicParentModelAdmin(admin.ModelAdmin): "admin/%s/change_list.html" % base_app_label, "admin/change_list.html" ] - - -def _get_opt(model): - return model._meta.app_label, model._meta.model_name diff --git a/polymorphic/base.py b/polymorphic/base.py index 89fa482..cd6e225 100644 --- a/polymorphic/base.py +++ b/polymorphic/base.py @@ -4,11 +4,10 @@ PolymorphicModel Meta Class """ from __future__ import absolute_import +import inspect import os import sys -import inspect -import django from django.db import models from django.db.models.base import ModelBase from django.db.models.manager import ManagerDescriptor @@ -22,11 +21,6 @@ POLYMORPHIC_SPECIAL_Q_KWORDS = ['instance_of', 'not_instance_of'] DUMPDATA_COMMAND = os.path.join('django', 'core', 'management', 'commands', 'dumpdata.py') -try: - from django.db.models.manager import AbstractManagerDescriptor # Django 1.5 -except ImportError: - AbstractManagerDescriptor = None - ################################################################################### # PolymorphicModel meta class @@ -60,11 +54,6 @@ class PolymorphicModelBase(ModelBase): # Workaround compatibility issue with six.with_metaclass() and custom Django model metaclasses: if not attrs and model_name == 'NewBase': - if django.VERSION < (1, 5): - # Let Django fully ignore the class which is inserted in between. - # Django 1.5 fixed this, see https://code.djangoproject.com/ticket/19688 - attrs['__module__'] = 'django.utils.six' - attrs['Meta'] = type('Meta', (), {'abstract': True}) return super(PolymorphicModelBase, self).__new__(self, model_name, bases, attrs) # create new model @@ -73,33 +62,9 @@ class PolymorphicModelBase(ModelBase): # check if the model fields are all allowed self.validate_model_fields(new_class) - # create list of all managers to be inherited from the base classes - if django.VERSION < (1, 10): - inherited_managers = new_class.get_inherited_managers(attrs) - - # add the managers to the new model - for source_name, mgr_name, manager in inherited_managers: - # print '** add inherited manager from model %s, manager %s, %s' % (source_name, mgr_name, manager.__class__.__name__) - new_manager = manager._copy_to_model(new_class) - if mgr_name == '_default_manager': - new_class._default_manager = new_manager - else: - new_class.add_to_class(mgr_name, new_manager) - - # get first user defined manager; if there is one, make it the _default_manager - # this value is used by the related objects, restoring access to custom queryset methods on related objects. - user_manager = self.get_first_user_defined_manager(new_class) - if user_manager: - # print '## add default manager', type(def_mgr) - new_class._default_manager = user_manager._copy_to_model(new_class) - new_class._default_manager._inherited = False # the default mgr was defined by the user, not inherited - - # validate resulting default manager (only on non-abstract and non-swapped models) + # validate resulting default manager if not new_class._meta.abstract and not new_class._meta.swapped: - if django.VERSION >= (1, 10): - self.validate_model_manager(new_class.objects, model_name, 'objects') - else: - self.validate_model_manager(new_class._default_manager, model_name, '_default_manager') + self.validate_model_manager(new_class.objects, model_name, "objects") # for __init__ function of this class (monkeypatching inheritance accessors) new_class.polymorphic_super_sub_accessors_replaced = False @@ -113,73 +78,6 @@ class PolymorphicModelBase(ModelBase): return new_class - if django.VERSION < (1, 10): - def get_inherited_managers(self, attrs): - """ - Return list of all managers to be inherited/propagated from the base classes; - use correct mro, only use managers with _inherited==False (they are of no use), - skip managers that are overwritten by the user with same-named class attributes (in attrs) - """ - # print "** ", self.__name__ - add_managers = [] - add_managers_keys = set() - for base in self.__mro__[1:]: - if not issubclass(base, models.Model): - continue - if not getattr(base, 'polymorphic_model_marker', None): - continue # leave managers of non-polym. models alone - - for key, manager in base.__dict__.items(): - if type(manager) == models.manager.ManagerDescriptor: - manager = manager.manager - - # As of Django 1.5, the abstract models don't get any managers, only a - # AbstractManagerDescriptor as substitute. - if type(manager) == AbstractManagerDescriptor and base.__name__ == 'PolymorphicModel': - model = manager.model - if key == 'objects': - manager = PolymorphicManager() - manager.model = model - elif key == 'base_objects': - manager = models.Manager() - manager.model = model - - if AbstractManagerDescriptor is not None: - # Django 1.4 unconditionally assigned managers to a model. As of Django 1.5 however, - # the abstract models don't get any managers, only a AbstractManagerDescriptor as substitute. - # Pretend that the manager is still there, so all code works like it used to. - if type(manager) == AbstractManagerDescriptor and base.__name__ == 'PolymorphicModel': - model = manager.model - if key == 'objects': - manager = PolymorphicManager() - manager.model = model - elif key == 'base_objects': - manager = models.Manager() - manager.model = model - - if not isinstance(manager, models.Manager): - continue - if key == '_base_manager': - continue # let Django handle _base_manager - if key in attrs: - continue - if key in add_managers_keys: - continue # manager with that name already added, skip - if manager._inherited: - continue # inherited managers (on the bases) have no significance, they are just copies - # print '## {0} {1}'.format(self.__name__, key) - - if isinstance(manager, PolymorphicManager): # validate any inherited polymorphic managers - self.validate_model_manager(manager, self.__name__, key) - add_managers.append((base.__name__, key, manager)) - add_managers_keys.add(key) - - # The ordering in the base.__dict__ may randomly change depending on which method is added. - # Make sure base_objects is on top, and 'objects' and '_default_manager' follow afterwards. - # This makes sure that the _base_manager is also assigned properly. - add_managers = sorted(add_managers, key=lambda item: (item[1].startswith('_'), item[1])) - return add_managers - @classmethod def get_first_user_defined_manager(mcs, new_class): # See if there is a manager attribute directly stored at this inheritance level. diff --git a/polymorphic/formsets/generic.py b/polymorphic/formsets/generic.py index c085757..610fb25 100644 --- a/polymorphic/formsets/generic.py +++ b/polymorphic/formsets/generic.py @@ -1,4 +1,3 @@ -import django from django.contrib.contenttypes.forms import BaseGenericInlineFormSet, generic_inlineformset_factory from django.contrib.contenttypes.models import ContentType from django.db import models @@ -32,12 +31,8 @@ class GenericPolymorphicFormSetChild(PolymorphicFormSetChild): opts = self.model._meta ct_field = opts.get_field(self.ct_field) - if django.VERSION >= (1, 9): - if not isinstance(ct_field, models.ForeignKey) or ct_field.remote_field.model != ContentType: - raise Exception("fk_name '%s' is not a ForeignKey to ContentType" % ct_field) - else: - if not isinstance(ct_field, models.ForeignKey) or ct_field.rel.to != ContentType: - raise Exception("fk_name '%s' is not a ForeignKey to ContentType" % ct_field) + if not isinstance(ct_field, models.ForeignKey) or ct_field.remote_field.model != ContentType: + raise Exception("fk_name '%s' is not a ForeignKey to ContentType" % ct_field) fk_field = opts.get_field(self.fk_field) # let the exception propagate exclude.extend([ct_field.name, fk_field.name]) diff --git a/polymorphic/formsets/models.py b/polymorphic/formsets/models.py index b13adf8..fd6e2d2 100644 --- a/polymorphic/formsets/models.py +++ b/polymorphic/formsets/models.py @@ -1,6 +1,5 @@ from collections import OrderedDict -import django from django import forms from django.contrib.contenttypes.models import ContentType from django.core.exceptions import ImproperlyConfigured, ValidationError @@ -226,10 +225,7 @@ class BasePolymorphicModelFormSet(BaseModelFormSet): """ forms = [] for model, form_class in self.child_forms.items(): - if django.VERSION >= (1, 9): - kwargs = self.get_form_kwargs(None) # New Django 1.9 method - else: - kwargs = {} + kwargs = self.get_form_kwargs(None) form = form_class( auto_id=self.auto_id, diff --git a/polymorphic/query.py b/polymorphic/query.py index 3d881d8..b780c37 100644 --- a/polymorphic/query.py +++ b/polymorphic/query.py @@ -7,9 +7,8 @@ from __future__ import absolute_import import copy from collections import defaultdict -import django from django.contrib.contenttypes.models import ContentType -from django.db.models.query import Q, QuerySet +from django.db.models.query import ModelIterable, Q, QuerySet from django.utils import six from .query_translate import translate_polymorphic_filter_definitions_in_kwargs, translate_polymorphic_filter_definitions_in_args @@ -54,23 +53,19 @@ def _polymorphic_iterator(queryset, base_iter): return -if django.VERSION >= (1, 9): - # We ignore this on django < 1.9, as ModelIterable didn't yet exist. - from django.db.models.query import ModelIterable +class PolymorphicModelIterable(ModelIterable): + """ + ModelIterable for PolymorphicModel - class PolymorphicModelIterable(ModelIterable): - """ - ModelIterable for PolymorphicModel + Yields real instances if qs.polymorphic_disabled is False, + otherwise acts like a regular ModelIterable. + """ - Yields real instances if qs.polymorphic_disabled is False, - otherwise acts like a regular ModelIterable. - """ - - def __iter__(self): - base_iter = super(PolymorphicModelIterable, self).__iter__() - if self.queryset.polymorphic_disabled: - return base_iter - return _polymorphic_iterator(self.queryset, base_iter) + def __iter__(self): + base_iter = super(PolymorphicModelIterable, self).__iter__() + if self.queryset.polymorphic_disabled: + return base_iter + return _polymorphic_iterator(self.queryset, base_iter) def transmogrify(cls, obj): @@ -104,9 +99,7 @@ class PolymorphicQuerySet(QuerySet): def __init__(self, *args, **kwargs): super(PolymorphicQuerySet, self).__init__(*args, **kwargs) - if django.VERSION >= (1, 9): - # On django < 1.9 we override the iterator() method instead - self._iterable_class = PolymorphicModelIterable + self._iterable_class = PolymorphicModelIterable self.polymorphic_disabled = False # A parallel structure to django.db.models.query.Query.deferred_loading, @@ -282,16 +275,13 @@ class PolymorphicQuerySet(QuerySet): qs = self.non_polymorphic() return super(PolymorphicQuerySet, qs).aggregate(*args, **kwargs) - if django.VERSION >= (1, 9): - # On Django < 1.9, 'qs.values(...)' returned a new special ValuesQuerySet - # object, which our polymorphic modifications didn't apply to. - # Starting with Django 1.9, the copy returned by 'qs.values(...)' has the - # same class as 'qs', so our polymorphic modifications would apply. - # We want to leave values queries untouched, so we set 'polymorphic_disabled'. - def _values(self, *args, **kwargs): - clone = super(PolymorphicQuerySet, self)._values(*args, **kwargs) - clone.polymorphic_disabled = True - return clone + # Starting with Django 1.9, the copy returned by 'qs.values(...)' has the + # same class as 'qs', so our polymorphic modifications would apply. + # We want to leave values queries untouched, so we set 'polymorphic_disabled'. + def _values(self, *args, **kwargs): + clone = super(PolymorphicQuerySet, self)._values(*args, **kwargs) + clone.polymorphic_disabled = True + return clone # Since django_polymorphic 'V1.0 beta2', extra() always returns polymorphic results. # The resulting objects are required to have a unique primary key within the result set @@ -445,34 +435,6 @@ class PolymorphicQuerySet(QuerySet): return resultlist - if django.VERSION < (1, 9): - # On django 1.9+, we can define self._iterator_class instead of iterator() - def iterator(self): - """ - This function is used by Django 1.8 and earlier for all object retrieval. - By overriding it, we modify the objects that this queryset returns - when it is evaluated (or its get method or other object-returning methods are called). - - Here we do the same as:: - - base_result_objects = list(super(PolymorphicQuerySet, self).iterator()) - real_results = self._get_real_instances(base_result_objects) - for o in real_results: yield o - - but it requests the objects in chunks from the database, - with Polymorphic_QuerySet_objects_per_request per chunk - """ - base_iter = super(PolymorphicQuerySet, self).iterator() - - # disabled => work just like a normal queryset - if self.polymorphic_disabled: - for o in base_iter: - yield o - return - - for o in _polymorphic_iterator(self, base_iter): - yield o - def __repr__(self, *args, **kwargs): if self.model.polymorphic_query_multiline_output: result = [repr(o) for o in self.all()] diff --git a/polymorphic/query_translate.py b/polymorphic/query_translate.py index c7d7310..db184ca 100644 --- a/polymorphic/query_translate.py +++ b/polymorphic/query_translate.py @@ -5,8 +5,8 @@ PolymorphicQuerySet support functions from __future__ import absolute_import import copy -import django from functools import reduce + from django.contrib.contenttypes.models import ContentType from django.db import models from django.db.models.fields.related import ForeignObjectRel, RelatedField @@ -86,13 +86,7 @@ def translate_polymorphic_filter_definitions_in_args(queryset_model, args, using Returns: modified Q objects """ - if django.VERSION >= (1, 10): - q_objects = [copy.deepcopy(q) for q in args] - elif django.VERSION >= (1, 8): - q_objects = [q.clone() for q in args] - else: - q_objects = args # NOTE: edits existing objects in place. - return [translate_polymorphic_Q_object(queryset_model, q, using=using) for q in q_objects] + return [translate_polymorphic_Q_object(queryset_model, copy.deepcopy(q), using=using) for q in args] def _translate_polymorphic_filter_definition(queryset_model, field_path, field_val, using=DEFAULT_DB_ALIAS): diff --git a/polymorphic/tests/test_orm.py b/polymorphic/tests/test_orm.py index 63742a3..5f6186f 100644 --- a/polymorphic/tests/test_orm.py +++ b/polymorphic/tests/test_orm.py @@ -260,20 +260,11 @@ class PolymorphicTests(TestCase): self.assertEqual(show_base_manager(PlainC), " ") self.assertEqual(show_base_manager(Model2A), " ") - if django.VERSION >= (1, 10): - # The new inheritance makes all model levels polymorphic - self.assertEqual(show_base_manager(Model2B), " ") - self.assertEqual(show_base_manager(Model2C), " ") - else: - self.assertEqual(show_base_manager(Model2B), " ") - self.assertEqual(show_base_manager(Model2C), " ") + self.assertEqual(show_base_manager(Model2B), " ") + self.assertEqual(show_base_manager(Model2C), " ") self.assertEqual(show_base_manager(One2OneRelatingModel), " ") - if django.VERSION >= (1, 10): - # The new inheritance makes all model levels polymorphic - self.assertEqual(show_base_manager(One2OneRelatingModelDerived), " ") - else: - self.assertEqual(show_base_manager(One2OneRelatingModelDerived), " ") + self.assertEqual(show_base_manager(One2OneRelatingModelDerived), " ") def test_instance_default_manager(self): def show_default_manager(instance): @@ -566,17 +557,6 @@ class PolymorphicTests(TestCase): # by choice of MRO, should be MyManager from MROBase1. self.assertIs(type(MRODerived.objects), MyManager) - if django.VERSION < (1, 10, 1): - # The change for https://code.djangoproject.com/ticket/27073 - # in https://github.com/django/django/commit/d4eefc7e2af0d93283ed1c03e0af0a482982b6f0 - # removes the assignment to _default_manager - - # check for correct default manager - self.assertIs(type(MROBase1._default_manager), MyManager) - - # Django vanilla inheritance does not inherit MyManager as _default_manager here - self.assertIs(type(MROBase2._default_manager), MyManager) - def test_queryset_assignment(self): # This is just a consistency check for now, testing standard Django behavior. parent = PlainParentModelWithManager.objects.create() @@ -783,13 +763,10 @@ def qrepr(data): Ensure consistent repr() output for the QuerySet object. """ if isinstance(data, QuerySet): - if django.VERSION >= (1, 11): - return repr(data) - elif django.VERSION >= (1, 10): - # Django 1.11 still shows "'.format(data.__class__.__name__, repr(data)) + return repr(data) return repr(data)