From df0041dff6372727f8b53182824ae1b091a60736 Mon Sep 17 00:00:00 2001 From: Jerome Leclanche Date: Fri, 19 May 2017 08:32:24 +0300 Subject: [PATCH 1/9] Stop running tests on unmaintained versions of Python/Django --- .travis.yml | 66 ----------------------------------------------------- tox.ini | 24 +++++++------------ 2 files changed, 8 insertions(+), 82 deletions(-) diff --git a/.travis.yml b/.travis.yml index 59fbfa2..a6f737b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,20 +2,12 @@ sudo: false language: python cache: pip python: - - "2.6" - "2.7" - - "3.2" - - "3.3" - "3.4" - "3.5" - "3.6" env: - - TOXENV="django14" - - TOXENV="django15" - - TOXENV="django16" - - TOXENV="django17" - TOXENV="django18" - - TOXENV="django19" - TOXENV="django110" - TOXENV="django111" - TOXENV="djangodev" @@ -23,77 +15,19 @@ env: matrix: fast_finish: true exclude: - - python: "3.6" - env: TOXENV="django14" - - python: "3.6" - env: TOXENV="django15" - - python: "3.6" - env: TOXENV="django16" - - python: "3.6" - env: TOXENV="django17" - python: "3.6" env: TOXENV="django18" - - python: "3.6" - env: TOXENV="django19" - python: "3.6" env: TOXENV="django110" - - python: "3.5" - env: TOXENV="django14" - - python: "3.5" - env: TOXENV="django15" - - python: "3.5" - env: TOXENV="django16" - - python: "3.5" - env: TOXENV="django17" - - - python: "3.4" - env: TOXENV="django14" - - python: "3.4" - env: TOXENV="django19" - python: "3.4" env: TOXENV="django110" - python: "3.4" env: TOXENV="django111" - - python: "3.3" - env: TOXENV="django14" - - python: "3.3" - env: TOXENV="django19" - - python: "3.3" - env: TOXENV="django110" - - python: "3.3" - env: TOXENV="django111" - - python: "3.3" - env: TOXENV="djangodev" - - - python: "3.2" - env: TOXENV="django14" - - python: "3.2" - env: TOXENV="django19" - - python: "3.2" - env: TOXENV="django110" - - python: "3.2" - env: TOXENV="django111" - - python: "3.2" - env: TOXENV="djangodev" - - python: "2.7" env: TOXENV="djangodev" - - python: "2.6" - env: TOXENV="django17" - - python: "2.6" - env: TOXENV="django18" - - python: "2.6" - env: TOXENV="django19" - - python: "2.6" - env: TOXENV="django110" - - python: "2.6" - env: TOXENV="django111" - - python: "2.6" - env: TOXENV="djangodev" - allow_failures: - env: TOXENV="djangodev" diff --git a/tox.ini b/tox.ini index cff1ea7..5c71b57 100644 --- a/tox.ini +++ b/tox.ini @@ -1,30 +1,22 @@ [tox] -envlist= - py26-django{14,15,16}, - py27-django{14,15,16,17,18,19,110,111}, - py32-django{15,16,17,18}, - py33-django{15,16,17,18}, - py34-django{15,16,17,18,19,110,111}, - py35-django{18,19,110,111,dev}, - py36-django{111,dev}, - docs, +envlist = + py27-django{18,110,111} + py34-django{18,110,111} + py35-django{18,110,111,dev} + py36-django{111,dev} + docs [testenv] deps = coverage == 3.6 - django14: Django >= 1.4, < 1.5 - django15: Django >= 1.5, < 1.6 - django16: Django >= 1.6, < 1.7 - django17: Django >= 1.7, < 1.8 django18: Django >= 1.8, < 1.9 - django19: Django >= 1.9, < 1.10 django110: Django >= 1.10, < 1.11 django111: Django >= 1.11, < 1.12 djangodev: https://github.com/django/django/tarball/master -commands= +commands = coverage run --source polymorphic runtests.py [testenv:docs] -deps=Sphinx +deps = Sphinx changedir = docs commands = sphinx-build -W -b html -d {envtmpdir}/doctrees . {envtmpdir}/html From 298460c4cf8c8518c7b985524d9fa4474c7e64c0 Mon Sep 17 00:00:00 2001 From: Jerome Leclanche Date: Fri, 19 May 2017 09:12:33 +0300 Subject: [PATCH 2/9] Drop support for Django <1.8, Python <2.7 --- docs/_ext/djangoext/docstrings.py | 14 +- docs/conf.py | 3 +- docs/managers.rst | 4 - example/example/settings.py | 5 +- example/pexp/management/commands/p2cmd.py | 47 +++--- example/pexp/management/commands/polybench.py | 29 ++-- example/pexp/models.py | 9 +- polymorphic/__init__.py | 30 ---- polymorphic/admin/generic.py | 6 +- polymorphic/admin/inlines.py | 6 +- polymorphic/admin/parentadmin.py | 63 +++----- polymorphic/base.py | 11 ++ polymorphic/formsets/generic.py | 6 +- polymorphic/formsets/models.py | 6 +- polymorphic/managers.py | 29 +--- polymorphic/models.py | 8 +- polymorphic/query.py | 112 ++++++-------- polymorphic/query_translate.py | 33 +--- polymorphic/showfields.py | 12 +- polymorphic/tests/__init__.py | 27 +--- polymorphic/tests/test_multidb.py | 12 +- polymorphic/tests/test_orm.py | 24 +-- polymorphic/tools_for_tests.py | 144 ------------------ runtests.py | 73 ++++----- setup.py | 17 +-- 25 files changed, 180 insertions(+), 550 deletions(-) delete mode 100644 polymorphic/tools_for_tests.py diff --git a/docs/_ext/djangoext/docstrings.py b/docs/_ext/djangoext/docstrings.py index b626b73..d52abbb 100644 --- a/docs/_ext/djangoext/docstrings.py +++ b/docs/_ext/djangoext/docstrings.py @@ -2,22 +2,16 @@ Automatically mention all model fields as parameters in the model construction. Based on http://djangosnippets.org/snippets/2533/ """ -import django -from django.utils.html import strip_tags -from django.utils.encoding import force_text import inspect +from django.utils.encoding import force_text +from django.utils.html import strip_tags def improve_model_docstring(app, what, name, obj, options, lines): from django.db import models # must be inside the function, to allow settings initialization first. if inspect.isclass(obj) and issubclass(obj, models.Model): - if django.VERSION >= (1,8): - model_fields = obj._meta.get_fields() - elif django.VERSION >= (1,6): - model_fields = obj._meta.fields - else: - model_fields = obj._meta._fields() + model_fields = obj._meta.get_fields() for field in model_fields: help_text = strip_tags(force_text(field.help_text)) @@ -39,9 +33,9 @@ def improve_model_docstring(app, what, name, obj, options, lines): # Return the extended docstring return lines + # Allow this module to be used as sphinx extension: def setup(app): # Generate docstrings for Django model fields # Register the docstring processor with sphinx app.connect('autodoc-process-docstring', improve_model_docstring) - diff --git a/docs/conf.py b/docs/conf.py index 5221d1b..88e93c2 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -23,8 +23,7 @@ sys.path.insert(0, os.path.abspath('_ext')) sys.path.insert(0, os.path.abspath('..')) os.environ['DJANGO_SETTINGS_MODULE'] = 'djangodummy.settings' -if django.VERSION >= (1, 8): - django.setup() +django.setup() # -- General configuration ----------------------------------------------------- diff --git a/docs/managers.rst b/docs/managers.rst index 9ed04f7..8790593 100644 --- a/docs/managers.rst +++ b/docs/managers.rst @@ -32,8 +32,6 @@ Django as automatic manager for several purposes, including accessing related objects. It must not filter objects and it's safest to use the plain ``PolymorphicManager`` here. - Note that get_query_set is deprecated in Django 1.8 and creates warnings in Django 1.7. - Manager Inheritance ------------------- @@ -69,8 +67,6 @@ regarding their start time and ``ArtProject.objects_ordered.most_recent()`` will return the ten most recent art projects. . - Note that get_query_set is deprecated in Django 1.8 and creates warnings in Django 1.7. - Using a Custom Queryset Class ----------------------------- diff --git a/example/example/settings.py b/example/example/settings.py index 8679620..abb389b 100644 --- a/example/example/settings.py +++ b/example/example/settings.py @@ -1,4 +1,3 @@ -import django import os DEBUG = True @@ -67,7 +66,6 @@ INSTALLED_APPS = ( 'django.contrib.admin', 'django.contrib.contenttypes', 'django.contrib.sessions', - #'django.contrib.sites', 'django.contrib.messages', 'django.contrib.staticfiles', @@ -76,8 +74,7 @@ INSTALLED_APPS = ( 'orders', ) -if django.VERSION >= (1, 7): - TEST_RUNNER = 'django.test.runner.DiscoverRunner' # silence system checks +TEST_RUNNER = 'django.test.runner.DiscoverRunner' # silence system checks # Logging configuration LOGGING = { diff --git a/example/pexp/management/commands/p2cmd.py b/example/pexp/management/commands/p2cmd.py index abb7608..0f8e0dc 100644 --- a/example/pexp/management/commands/p2cmd.py +++ b/example/pexp/management/commands/p2cmd.py @@ -3,34 +3,32 @@ This module is a scratchpad for general development, testing & debugging Well, even more so than pcmd.py. You best ignore p2cmd.py. """ -from django.core.management.base import NoArgsCommand -from pprint import pprint -import time import sys +import time +from pprint import pprint +from random import Random +from django.core.management.base import NoArgsCommand +from django.db import connection from pexp.models import * -def reset_queries(): - if django.VERSION < (1, 8): - connection.queries = [] - else: - connection.queries_log.clear() +rnd = Random() def show_queries(): - print - print 'QUERIES:', len(connection.queries) + print() + print("QUERIES:", len(connection.queries)) pprint(connection.queries) - print + print() connection.queries = [] def print_timing(func, message='', iterations=1): def wrapper(*arg): results = [] - reset_queries() - for i in xrange(iterations): + connection.queries_log.clear() + for i in range(iterations): t1 = time.time() x = func(*arg) t2 = time.time() @@ -38,13 +36,12 @@ def print_timing(func, message='', iterations=1): res_sum = 0 for r in results: res_sum += r - median = res_sum / len(results) - print '%s%-19s: %.4f ms, %i queries (%i times)' % ( + print("%s%-19s: %.4f ms, %i queries (%i times)" % ( message, func.func_name, res_sum, len(connection.queries), iterations - ) + )) sys.stdout.flush() return wrapper @@ -58,18 +55,18 @@ class Command(NoArgsCommand): a = TestModelA.objects.create(field1='A1') b = TestModelB.objects.create(field1='B1', field2='B2') c = TestModelC.objects.create(field1='C1', field2='C2', field3='C3') - reset_queries() - print TestModelC.base_objects.all() + connection.queries_log.clear() + print(TestModelC.base_objects.all()) show_queries() if False: TestModelA.objects.all().delete() - for i in xrange(1000): + for i in range(1000): a = TestModelA.objects.create(field1=str(i % 100)) b = TestModelB.objects.create(field1=str(i % 100), field2=str(i % 200)) c = TestModelC.objects.create(field1=str(i % 100), field2=str(i % 200), field3=str(i % 300)) if i % 100 == 0: - print i + print(i) f = print_timing(poly_sql_query, iterations=1000) f() @@ -85,11 +82,7 @@ class Command(NoArgsCommand): c = NormalModelC.objects.create(field1='C1', field2='C2', field3='C3') qs = TestModelA.objects.raw("SELECT * from pexp_testmodela") for o in list(qs): - print o - -from django.db import connection, transaction -from random import Random -rnd = Random() + print(o) def poly_sql_query(): @@ -103,7 +96,7 @@ def poly_sql_query(): ON pexp_testmodelb.testmodela_ptr_id = pexp_testmodelc.testmodelb_ptr_id WHERE pexp_testmodela.field1=%i ORDER BY pexp_testmodela.id - """ % rnd.randint(0, 100) ) + """ % rnd.randint(0, 100)) # row=cursor.fetchone() return @@ -115,6 +108,6 @@ def poly_sql_query2(): FROM pexp_testmodela WHERE pexp_testmodela.field1=%i ORDER BY pexp_testmodela.id - """ % rnd.randint(0, 100) ) + """ % rnd.randint(0, 100)) # row=cursor.fetchone() return diff --git a/example/pexp/management/commands/polybench.py b/example/pexp/management/commands/polybench.py index 8ca5913..c0b7efb 100644 --- a/example/pexp/management/commands/polybench.py +++ b/example/pexp/management/commands/polybench.py @@ -3,30 +3,25 @@ This module is a scratchpad for general development, testing & debugging """ +import time +import sys + from django.core.management.base import NoArgsCommand from django.db import connection from pprint import pprint -import sys from pexp.models import * + num_objects = 1000 -def reset_queries(): - if django.VERSION < (1, 8): - connection.queries = [] - else: - connection.queries_log.clear() - - def show_queries(): - print - print 'QUERIES:', len(connection.queries) + print() + print("QUERIES:", len(connection.queries)) pprint(connection.queries) - print - reset_queries() + print() + connection.queries_log.clear() -import time ################################################################################### # benchmark wrappers @@ -35,8 +30,8 @@ import time def print_timing(func, message='', iterations=1): def wrapper(*arg): results = [] - reset_queries() - for i in xrange(iterations): + connection.queries_log.clear() + for i in range(iterations): t1 = time.time() x = func(*arg) t2 = time.time() @@ -45,11 +40,11 @@ def print_timing(func, message='', iterations=1): for r in results: res_sum += r median = res_sum / len(results) - print '%s%-19s: %.0f ms, %i queries' % ( + print("%s%-19s: %.0f ms, %i queries" % ( message, func.func_name, median, len(connection.queries) / len(results) - ) + )) sys.stdout.flush() return wrapper diff --git a/example/pexp/models.py b/example/pexp/models.py index f49e9a1..26b4768 100644 --- a/example/pexp/models.py +++ b/example/pexp/models.py @@ -1,6 +1,5 @@ # -*- coding: utf-8 -*- -import django from django.db import models from polymorphic.models import PolymorphicModel @@ -20,15 +19,9 @@ class ResearchProject(Project): supervisor = models.CharField(max_length=30) -if django.VERSION < (1, 8): - from polymorphic.tools_for_tests import UUIDField -else: - from django.db.models import UUIDField - - class UUIDModelA(ShowFieldTypeAndContent, PolymorphicModel): """UUID as primary key example""" - uuid_primary_key = UUIDField(primary_key=True) + uuid_primary_key = models.UUIDField(primary_key=True) field1 = models.CharField(max_length=10) diff --git a/polymorphic/__init__.py b/polymorphic/__init__.py index 6e75907..4b64b0d 100644 --- a/polymorphic/__init__.py +++ b/polymorphic/__init__.py @@ -6,36 +6,6 @@ Copyright: This code and affiliated files are (C) by Bert Constantin and individual contributors. Please see LICENSE and AUTHORS for more information. """ -import django # See PEP 440 (https://www.python.org/dev/peps/pep-0440/) __version__ = "1.2" - - -# Monkey-patch Django < 1.5 to allow ContentTypes for proxy models. -if django.VERSION[:2] < (1, 5): - from django.contrib.contenttypes.models import ContentTypeManager - from django.utils.encoding import smart_text - - def get_for_model(self, model, for_concrete_model=True): - if for_concrete_model: - model = model._meta.concrete_model - elif model._deferred: - model = model._meta.proxy_for_model - - opts = model._meta - - try: - ct = self._get_from_cache(opts) - except KeyError: - ct, created = self.get_or_create( - app_label=opts.app_label, - model=opts.object_name.lower(), - defaults={'name': smart_text(opts.verbose_name_raw)}, - ) - self._add_to_cache(self.db, ct) - - return ct - - ContentTypeManager.get_for_model__original = ContentTypeManager.get_for_model - ContentTypeManager.get_for_model = get_for_model diff --git a/polymorphic/admin/generic.py b/polymorphic/admin/generic.py index 4a121af..bd3453f 100644 --- a/polymorphic/admin/generic.py +++ b/polymorphic/admin/generic.py @@ -1,14 +1,10 @@ +from django.contrib.contenttypes.admin import GenericInlineModelAdmin from django.contrib.contenttypes.models import ContentType from django.utils.functional import cached_property from polymorphic.formsets import polymorphic_child_forms_factory, BaseGenericPolymorphicInlineFormSet, GenericPolymorphicFormSetChild from .inlines import PolymorphicInlineModelAdmin -try: - from django.contrib.contenttypes.admin import GenericInlineModelAdmin # Django 1.7+ -except ImportError: - from django.contrib.contenttypes.generic import GenericInlineModelAdmin - class GenericPolymorphicInlineModelAdmin(PolymorphicInlineModelAdmin, GenericInlineModelAdmin): """ diff --git a/polymorphic/admin/inlines.py b/polymorphic/admin/inlines.py index 7e163f4..deee7bb 100644 --- a/polymorphic/admin/inlines.py +++ b/polymorphic/admin/inlines.py @@ -6,6 +6,7 @@ Each row in the inline can correspond with a different subclass. from functools import partial from django.contrib.admin.options import InlineModelAdmin +from django.contrib.admin.utils import flatten_fieldsets from django.core.exceptions import ImproperlyConfigured from django.forms import Media @@ -13,11 +14,6 @@ from polymorphic.formsets import polymorphic_child_forms_factory, BasePolymorphi from polymorphic.formsets.utils import add_media from .helpers import PolymorphicInlineSupportMixin -try: - from django.contrib.admin.utils import flatten_fieldsets # Django 1.7+ -except ImportError: - from django.contrib.admin.util import flatten_fieldsets - class PolymorphicInlineModelAdmin(InlineModelAdmin): """ diff --git a/polymorphic/admin/parentadmin.py b/polymorphic/admin/parentadmin.py index 0169225..dd37e40 100644 --- a/polymorphic/admin/parentadmin.py +++ b/polymorphic/admin/parentadmin.py @@ -1,19 +1,18 @@ """ 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 +from django.contrib.admin.templatetags.admin_urls import add_preserved_filters from django.contrib.contenttypes.models import ContentType from django.core.exceptions import PermissionDenied from django.core.urlresolvers import RegexURLResolver from django.http import Http404, HttpResponseRedirect -from django.shortcuts import render_to_response -from django.template.context import RequestContext +from django.template.response import TemplateResponse from django.utils.encoding import force_text from django.utils.http import urlencode from django.utils.safestring import mark_safe @@ -21,16 +20,6 @@ from django.utils.translation import ugettext_lazy as _ from .forms import PolymorphicModelChoiceForm -try: - # Django 1.6 implements this - from django.contrib.admin.templatetags.admin_urls import add_preserved_filters -except ImportError: - def add_preserved_filters(context, form_url): - return form_url - -if sys.version_info[0] >= 3: - long = int - class RegistrationClosed(RuntimeError): "The admin model can't be registered anymore at this point." @@ -210,13 +199,6 @@ class PolymorphicParentModelAdmin(admin.ModelAdmin): qs = qs.non_polymorphic() return qs - # For Django 1.5: - def queryset(self, request): - qs = super(PolymorphicParentModelAdmin, self).queryset(request) - if not self.polymorphic_list: - qs = qs.non_polymorphic() - return qs - def add_view(self, request, form_url='', extra_context=None): """Redirect the add view to the real admin.""" ct_id = int(request.GET.get('ct_id', 0)) @@ -238,17 +220,16 @@ class PolymorphicParentModelAdmin(admin.ModelAdmin): real_admin = self._get_real_admin(object_id) return real_admin.change_view(request, object_id, *args, **kwargs) - if django.VERSION >= (1, 7): - def changeform_view(self, request, object_id=None, *args, **kwargs): - # The `changeform_view` is available as of Django 1.7, combining the add_view and change_view. - # As it's directly called by django-reversion, this method is also overwritten to make sure it - # also redirects to the child admin. - if object_id: - real_admin = self._get_real_admin(object_id) - return real_admin.changeform_view(request, object_id, *args, **kwargs) - else: - # Add view. As it should already be handled via `add_view`, this means something custom is done here! - return super(PolymorphicParentModelAdmin, self).changeform_view(request, object_id, *args, **kwargs) + def changeform_view(self, request, object_id=None, *args, **kwargs): + # The `changeform_view` is available as of Django 1.7, combining the add_view and change_view. + # As it's directly called by django-reversion, this method is also overwritten to make sure it + # also redirects to the child admin. + if object_id: + real_admin = self._get_real_admin(object_id) + return real_admin.changeform_view(request, object_id, *args, **kwargs) + else: + # Add view. As it should already be handled via `add_view`, this means something custom is done here! + return super(PolymorphicParentModelAdmin, self).changeform_view(request, object_id, *args, **kwargs) def history_view(self, request, object_id, extra_context=None): """Redirect the history view to the real admin.""" @@ -334,9 +315,9 @@ class PolymorphicParentModelAdmin(admin.ModelAdmin): try: pos = path.find('/') if pos == -1: - object_id = long(path) + object_id = int(path) else: - object_id = long(path[0:pos]) + object_id = int(path[0:pos]) except ValueError: raise Http404("No ct_id parameter, unable to find admin subclass for path '{0}'.".format(path)) @@ -407,8 +388,6 @@ class PolymorphicParentModelAdmin(admin.ModelAdmin): 'add': True, 'save_on_top': self.save_on_top, }) - if hasattr(self.admin_site, 'root_path'): - context['root_path'] = self.admin_site.root_path # Django < 1.4 templates = self.add_type_template or [ "admin/%s/%s/add_type_form.html" % (app_label, opts.object_name.lower()), @@ -417,13 +396,8 @@ class PolymorphicParentModelAdmin(admin.ModelAdmin): "admin/add_type_form.html" ] - if django.VERSION >= (1, 8): - from django.template.response import TemplateResponse - request.current_app = self.admin_site.name - return TemplateResponse(request, templates, context) - else: - context_instance = RequestContext(request, current_app=self.admin_site.name) - return render_to_response(templates, context, context_instance=context_instance) + request.current_app = self.admin_site.name + return TemplateResponse(request, templates, context) @property def change_list_template(self): @@ -445,7 +419,4 @@ class PolymorphicParentModelAdmin(admin.ModelAdmin): def _get_opt(model): - try: - return model._meta.app_label, model._meta.model_name # Django 1.7 format - except AttributeError: - return model._meta.app_label, model._meta.module_name + return model._meta.app_label, model._meta.model_name diff --git a/polymorphic/base.py b/polymorphic/base.py index c42ce89..3abe7f0 100644 --- a/polymorphic/base.py +++ b/polymorphic/base.py @@ -132,6 +132,17 @@ class PolymorphicModelBase(ModelBase): 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. diff --git a/polymorphic/formsets/generic.py b/polymorphic/formsets/generic.py index 90c0ec1..c085757 100644 --- a/polymorphic/formsets/generic.py +++ b/polymorphic/formsets/generic.py @@ -1,15 +1,11 @@ import django +from django.contrib.contenttypes.forms import BaseGenericInlineFormSet, generic_inlineformset_factory from django.contrib.contenttypes.models import ContentType from django.db import models from django.forms.models import ModelForm from .models import BasePolymorphicModelFormSet, polymorphic_child_forms_factory, PolymorphicFormSetChild -try: - from django.contrib.contenttypes.forms import BaseGenericInlineFormSet, generic_inlineformset_factory # Django 1.7+ -except ImportError: - from django.contrib.contenttypes.generic import BaseGenericInlineFormSet, generic_inlineformset_factory - class GenericPolymorphicFormSetChild(PolymorphicFormSetChild): """ diff --git a/polymorphic/formsets/models.py b/polymorphic/formsets/models.py index d715e72..b13adf8 100644 --- a/polymorphic/formsets/models.py +++ b/polymorphic/formsets/models.py @@ -1,3 +1,4 @@ +from collections import OrderedDict import django from django import forms @@ -7,11 +8,6 @@ from django.forms.models import ModelForm, BaseModelFormSet, BaseInlineFormSet, from django.utils.functional import cached_property from .utils import add_media -try: - from collections import OrderedDict -except ImportError: - from django.utils.datastructures import SortedDict as OrderedDict # Python 2.6 - class PolymorphicFormSetChild(object): """ diff --git a/polymorphic/managers.py b/polymorphic/managers.py index d2e677e..d743eec 100644 --- a/polymorphic/managers.py +++ b/polymorphic/managers.py @@ -4,15 +4,10 @@ The manager class for use in the models. """ from __future__ import unicode_literals import warnings -import django from django.db import models +from django.utils.six import python_2_unicode_compatible from polymorphic.query import PolymorphicQuerySet -try: - from django.utils.six import python_2_unicode_compatible -except ImportError: - from django.utils.encoding import python_2_unicode_compatible # Django 1.5 - __all__ = ( 'PolymorphicManager', @@ -32,6 +27,12 @@ class PolymorphicManager(models.Manager): use_for_related_fields = True queryset_class = PolymorphicQuerySet + @classmethod + def from_queryset(cls, queryset_class, class_name=None): + manager = super(PolymorphicManager, cls).from_queryset(queryset_class, class_name=class_name) + manager.queryset_class = queryset_class # also set our version, Django uses _queryset_class + return manager + def __init__(self, queryset_class=None, *args, **kwrags): # Up till polymorphic 0.4, the queryset class could be specified as parameter to __init__. # However, this doesn't work for related managers which instantiate a new version of this class. @@ -44,18 +45,11 @@ class PolymorphicManager(models.Manager): super(PolymorphicManager, self).__init__(*args, **kwrags) def get_queryset(self): - if django.VERSION >= (1, 7): - qs = self.queryset_class(self.model, using=self._db, hints=self._hints) - else: - qs = self.queryset_class(self.model, using=self._db) + qs = self.queryset_class(self.model, using=self._db, hints=self._hints) if self.model._meta.proxy: qs = qs.instance_of(self.model) return qs - # For Django 1.5 - if django.VERSION < (1, 7): - get_query_set = get_queryset - def __str__(self): return '%s (PolymorphicManager) using %s' % (self.__class__.__name__, self.queryset_class.__name__) @@ -71,10 +65,3 @@ class PolymorphicManager(models.Manager): def get_real_instances(self, base_result_objects=None): return self.all().get_real_instances(base_result_objects=base_result_objects) - - if django.VERSION >= (1, 7): - @classmethod - def from_queryset(cls, queryset_class, class_name=None): - manager = super(PolymorphicManager, cls).from_queryset(queryset_class, class_name=class_name) - manager.queryset_class = queryset_class # also set our version, Django uses _queryset_class - return manager diff --git a/polymorphic/models.py b/polymorphic/models.py index 4c400a9..4271f14 100644 --- a/polymorphic/models.py +++ b/polymorphic/models.py @@ -4,9 +4,9 @@ Seamless Polymorphic Inheritance for Django Models """ from __future__ import absolute_import +from django.contrib.contenttypes.models import ContentType from django.db import models from django.db.utils import DEFAULT_DB_ALIAS -from django.contrib.contenttypes.models import ContentType from django.utils import six from .base import PolymorphicModelBase @@ -85,11 +85,7 @@ class PolymorphicModel(six.with_metaclass(PolymorphicModelBase, models.Model)): # so we use the following version, which uses the ContentType manager cache. # Note that model_class() can return None for stale content types; # when the content type record still exists but no longer refers to an existing model. - try: - model = ContentType.objects.db_manager(self._state.db).get_for_id(self.polymorphic_ctype_id).model_class() - except AttributeError: - # Django <1.6 workaround - return None + model = ContentType.objects.db_manager(self._state.db).get_for_id(self.polymorphic_ctype_id).model_class() # Protect against bad imports (dumpdata without --natural) or other # issues missing with the ContentType models. diff --git a/polymorphic/query.py b/polymorphic/query.py index 8643308..b520c00 100644 --- a/polymorphic/query.py +++ b/polymorphic/query.py @@ -8,21 +8,16 @@ import copy from collections import defaultdict import django -from django.db.models.query import QuerySet, Q from django.contrib.contenttypes.models import ContentType +from django.db.models.query import Q, QuerySet from django.utils import six from .query_translate import translate_polymorphic_filter_definitions_in_kwargs, translate_polymorphic_filter_definitions_in_args from .query_translate import translate_polymorphic_field_path, translate_polymorphic_Q_object # chunk-size: maximum number of objects requested per db-request -# by the PolymorphicModelIterable; we use the same chunk size as Django -try: - from django.db.models.query import CHUNK_SIZE # this is 100 for Django 1.1/1.2 -except ImportError: - # CHUNK_SIZE was removed in Django 1.6 - CHUNK_SIZE = 100 -Polymorphic_QuerySet_objects_per_request = CHUNK_SIZE +# by the polymorphic queryset.iterator() implementation +Polymorphic_QuerySet_objects_per_request = 100 def _polymorphic_iterator(queryset, base_iter): @@ -97,14 +92,6 @@ def transmogrify(cls, obj): ################################################################################### # PolymorphicQuerySet -def _query_annotations(query): - try: - return query.annotations - except AttributeError: - # Django < 1.8 - return query.aggregates - - class PolymorphicQuerySet(QuerySet): """ QuerySet for PolymorphicModel @@ -138,15 +125,13 @@ class PolymorphicQuerySet(QuerySet): self.polymorphic_deferred_loading[1]) return new - if django.VERSION >= (1, 7): - def as_manager(cls): - # Make sure the Django 1.7 way of creating managers works. - from .managers import PolymorphicManager - manager = PolymorphicManager.from_queryset(cls)() - manager._built_with_as_manager = True - return manager - as_manager.queryset_only = True - as_manager = classmethod(as_manager) + def as_manager(cls): + from .managers import PolymorphicManager + manager = PolymorphicManager.from_queryset(cls)() + manager._built_with_as_manager = True + return manager + as_manager.queryset_only = True + as_manager = classmethod(as_manager) def non_polymorphic(self): """switch off polymorphic behaviour for this query. @@ -244,47 +229,40 @@ class PolymorphicQuerySet(QuerySet): Modifies kwargs if needed (these are Aggregate objects, we translate the lookup member variable)""" ___lookup_assert_msg = 'PolymorphicModel: annotate()/aggregate(): ___ model lookup supported for keyword arguments only' - if django.VERSION < (1, 8): - def patch_lookup(a): - a.lookup = translate_polymorphic_field_path(self.model, a.lookup) + def patch_lookup(a): + # The field on which the aggregate operates is + # stored inside a complex query expression. + if isinstance(a, Q): + translate_polymorphic_Q_object(self.model, a) + elif hasattr(a, 'get_source_expressions'): + for source_expression in a.get_source_expressions(): + if source_expression is not None: + patch_lookup(source_expression) + else: + a.name = translate_polymorphic_field_path(self.model, a.name) - def test___lookup(a): - assert '___' not in a.lookup, ___lookup_assert_msg - else: - def patch_lookup(a): - # With Django > 1.8, the field on which the aggregate operates is - # stored inside a complex query expression. - if isinstance(a, Q): - translate_polymorphic_Q_object(self.model, a) - elif hasattr(a, 'get_source_expressions'): - for source_expression in a.get_source_expressions(): - if source_expression is not None: - patch_lookup(source_expression) - else: - a.name = translate_polymorphic_field_path(self.model, a.name) + def test___lookup(a): + """ *args might be complex expressions too in django 1.8 so + the testing for a '___' is rather complex on this one """ + if isinstance(a, Q): + def tree_node_test___lookup(my_model, node): + " process all children of this Q node " + for i in range(len(node.children)): + child = node.children[i] - def test___lookup(a): - """ *args might be complex expressions too in django 1.8 so - the testing for a '___' is rather complex on this one """ - if isinstance(a, Q): - def tree_node_test___lookup(my_model, node): - " process all children of this Q node " - for i in range(len(node.children)): - child = node.children[i] + if type(child) == tuple: + # this Q object child is a tuple => a kwarg like Q( instance_of=ModelB ) + assert '___' not in child[0], ___lookup_assert_msg + else: + # this Q object child is another Q object, recursively process this as well + tree_node_test___lookup(my_model, child) - if type(child) == tuple: - # this Q object child is a tuple => a kwarg like Q( instance_of=ModelB ) - assert '___' not in child[0], ___lookup_assert_msg - else: - # this Q object child is another Q object, recursively process this as well - tree_node_test___lookup(my_model, child) - - tree_node_test___lookup(self.model, a) - elif hasattr(a, 'get_source_expressions'): - for source_expression in a.get_source_expressions(): - test___lookup(source_expression) - else: - assert '___' not in a.name, ___lookup_assert_msg + tree_node_test___lookup(self.model, a) + elif hasattr(a, 'get_source_expressions'): + for source_expression in a.get_source_expressions(): + test___lookup(source_expression) + else: + assert '___' not in a.name, ___lookup_assert_msg for a in args: test___lookup(a) @@ -438,8 +416,8 @@ class PolymorphicQuerySet(QuerySet): if real_class != real_concrete_class: real_object = transmogrify(real_class, real_object) - if _query_annotations(self.query): - for anno_field_name in six.iterkeys(_query_annotations(self.query)): + if self.query.annotations: + for anno_field_name in six.iterkeys(self.query.annotations): attr = getattr(base_result_objects_by_id[o_pk], anno_field_name) setattr(real_object, anno_field_name, attr) @@ -454,8 +432,8 @@ class PolymorphicQuerySet(QuerySet): resultlist = [results[ordered_id] for ordered_id in ordered_id_list if ordered_id in results] # set polymorphic_annotate_names in all objects (currently just used for debugging/printing) - if _query_annotations(self.query): - annotate_names = list(six.iterkeys(_query_annotations(self.query))) # get annotate field list + if self.query.annotations: + annotate_names = list(six.iterkeys(self.query.annotations)) # get annotate field list for real_object in resultlist: real_object.polymorphic_annotate_names = annotate_names diff --git a/polymorphic/query_translate.py b/polymorphic/query_translate.py index 9c69ba8..b98f8d1 100644 --- a/polymorphic/query_translate.py +++ b/polymorphic/query_translate.py @@ -7,26 +7,12 @@ from __future__ import absolute_import import copy import django from functools import reduce -from django.db import models from django.contrib.contenttypes.models import ContentType -from django.db.models import Q, FieldDoesNotExist +from django.db import models +from django.db.models.fields.related import ForeignObjectRel, RelatedField from django.db.utils import DEFAULT_DB_ALIAS from django.utils import six -from django.db.models.fields.related import RelatedField -if django.VERSION < (1, 6): - # There was no common base class in Django 1.5, mention all variants here. - from django.db.models.fields.related import RelatedObject, ManyToOneRel, ManyToManyRel - REL_FIELD_CLASSES = (RelatedField, RelatedObject, ManyToOneRel, ManyToManyRel) # Leaving GenericRel out. -elif django.VERSION < (1, 8): - # As of Django 1.6 there is a ForeignObjectRel. - from django.db.models.fields.related import ForeignObjectRel, RelatedObject - REL_FIELD_CLASSES = (RelatedField, ForeignObjectRel, RelatedObject) -else: - # As of Django 1.8 the base class serves everything. RelatedObject is gone. - from django.db.models.fields.related import ForeignObjectRel - REL_FIELD_CLASSES = (RelatedField, ForeignObjectRel) - ################################################################################### # PolymorphicQuerySet support functions @@ -102,7 +88,7 @@ def translate_polymorphic_filter_definitions_in_args(queryset_model, args, using """ if django.VERSION >= (1, 10): q_objects = [copy.deepcopy(q) for q in args] - elif django.VERSION >= (1, 6): + elif django.VERSION >= (1, 8): q_objects = [q.clone() for q in args] else: q_objects = args # NOTE: edits existing objects in place. @@ -175,17 +161,14 @@ def translate_polymorphic_field_path(queryset_model, field_path): # Test whether it's actually a regular relation__ _fieldname (the field starting with an _) # so no tripple ClassName___field was intended. try: - if django.VERSION >= (1, 8): - # This also retreives M2M relations now (including reverse foreign key relations) - field = queryset_model._meta.get_field(classname) - else: - field = queryset_model._meta.get_field_by_name(classname)[0] + # This also retreives M2M relations now (including reverse foreign key relations) + field = queryset_model._meta.get_field(classname) - if isinstance(field, REL_FIELD_CLASSES): + if isinstance(field, (RelatedField, ForeignObjectRel)): # Can also test whether the field exists in the related object to avoid ambiguity between # class names and field names, but that never happens when your class names are in CamelCase. return field_path # No exception raised, field does exist. - except FieldDoesNotExist: + except models.FieldDoesNotExist: pass # function to collect all sub-models, this should be optimized (cached) @@ -261,7 +244,7 @@ def _create_model_filter_Q(modellist, not_instance_of=False, using=DEFAULT_DB_AL assert False, 'PolymorphicModel: instance_of expects a list of (polymorphic) models or a single (polymorphic) model' def q_class_with_subclasses(model): - q = Q(polymorphic_ctype=ContentType.objects.db_manager(using).get_for_model(model, for_concrete_model=False)) + q = models.Q(polymorphic_ctype=ContentType.objects.db_manager(using).get_for_model(model, for_concrete_model=False)) for subclass in model.__subclasses__(): q = q | q_class_with_subclasses(subclass) return q diff --git a/polymorphic/showfields.py b/polymorphic/showfields.py index 1d4cfe2..7d62427 100644 --- a/polymorphic/showfields.py +++ b/polymorphic/showfields.py @@ -1,13 +1,10 @@ # -*- coding: utf-8 -*- -import django import re + from django.db import models from django.utils import six +from django.utils.six import python_2_unicode_compatible -try: - from django.utils.six import python_2_unicode_compatible -except ImportError: - from django.utils.encoding import python_2_unicode_compatible # Django 1.5 RE_DEFERRED = re.compile('_Deferred_.*') @@ -157,11 +154,6 @@ class ShowFieldBase(object): return '<' + out + '>' - if django.VERSION < (1, 8): - def get_deferred_fields(self): - from django.db.models.query_utils import DeferredAttribute - return set(attr for attr, value in self.__class__.__dict__.items() if isinstance(value, DeferredAttribute)) - class ShowFieldType(ShowFieldBase): """ model mixin that shows the object's class and it's field types """ diff --git a/polymorphic/tests/__init__.py b/polymorphic/tests/__init__.py index bfee088..bac18e9 100644 --- a/polymorphic/tests/__init__.py +++ b/polymorphic/tests/__init__.py @@ -1,22 +1,16 @@ # -*- coding: utf-8 -*- -import django import uuid + +from django.contrib.contenttypes.models import ContentType from django.db import models from django.db.models.query import QuerySet -from django.contrib.contenttypes.models import ContentType from polymorphic.managers import PolymorphicManager from polymorphic.models import PolymorphicModel from polymorphic.query import PolymorphicQuerySet from polymorphic.showfields import ShowFieldContent, ShowFieldType, ShowFieldTypeAndContent -try: - from django.db.models import UUIDField -except ImportError: - # django<1.8 - from polymorphic.tools_for_tests import UUIDField - class PlainA(models.Model): field1 = models.CharField(max_length=10) @@ -167,9 +161,6 @@ class MyManager(PolymorphicManager): def my_queryset_foo(self): return self.all().my_queryset_foo() - # Django <= 1.5 compatibility - get_query_set = get_queryset - class ModelWithMyManager(ShowFieldTypeAndContent, Model2A): objects = MyManager() @@ -188,10 +179,9 @@ class ModelWithMyManagerDefault(ShowFieldTypeAndContent, Model2A): field4 = models.CharField(max_length=10) -if django.VERSION >= (1, 7): - class ModelWithMyManager2(ShowFieldTypeAndContent, Model2A): - objects = MyManagerQuerySet.as_manager() - field4 = models.CharField(max_length=10) +class ModelWithMyManager2(ShowFieldTypeAndContent, Model2A): + objects = MyManagerQuerySet.as_manager() + field4 = models.CharField(max_length=10) class MROBase1(ShowFieldType, PolymorphicModel): @@ -236,9 +226,6 @@ class PlainMyManager(models.Manager): def get_queryset(self): return PlainMyManagerQuerySet(self.model, using=self._db) - # Django <= 1.5 compatibility - get_query_set = get_queryset - class PlainParentModelWithManager(models.Model): pass @@ -319,7 +306,7 @@ class Bottom(Middle): class UUIDProject(ShowFieldTypeAndContent, PolymorphicModel): - uuid_primary_key = UUIDField(primary_key=True, default=uuid.uuid1) + uuid_primary_key = models.UUIDField(primary_key=True, default=uuid.uuid1) topic = models.CharField(max_length=30) @@ -332,7 +319,7 @@ class UUIDResearchProject(UUIDProject): class UUIDPlainA(models.Model): - uuid_primary_key = UUIDField(primary_key=True, default=uuid.uuid1) + uuid_primary_key = models.UUIDField(primary_key=True, default=uuid.uuid1) field1 = models.CharField(max_length=10) diff --git a/polymorphic/tests/test_multidb.py b/polymorphic/tests/test_multidb.py index 9d23799..3636808 100644 --- a/polymorphic/tests/test_multidb.py +++ b/polymorphic/tests/test_multidb.py @@ -1,25 +1,15 @@ from __future__ import print_function -import django from django.contrib.contenttypes.models import ContentType - -from django.test import TestCase from django.db.models import Q +from django.test import TestCase from polymorphic.tests import * # all models -try: - from unittest import skipIf -except ImportError: - # python<2.7 - from django.utils.unittest import skipIf - - class MultipleDatabasesTests(TestCase): multi_db = True - @skipIf(django.VERSION < (1, 5,), "This test needs Django >=1.5") def test_save_to_non_default_database(self): Model2A.objects.db_manager('secondary').create(field1='A1') Model2C(field1='C1', field2='C2', field3='C3').save(using='secondary') diff --git a/polymorphic/tests/test_orm.py b/polymorphic/tests/test_orm.py index 7dff603..3489783 100644 --- a/polymorphic/tests/test_orm.py +++ b/polymorphic/tests/test_orm.py @@ -1,22 +1,11 @@ -from __future__ import print_function import re import django +from django.db.models import Case, Count, Q, When from django.test import TestCase -from django.db.models import Q, Count from django.utils import six -from polymorphic.tests import * # all models - from polymorphic.contrib.guardian import get_polymorphic_base_content_type - -try: - from unittest import skipIf -except ImportError: - # python<2.7 - from django.utils.unittest import skipIf - -if django.VERSION >= (1, 8): - from django.db.models import Case, When +from polymorphic.tests import * # all models class PolymorphicTests(TestCase): @@ -188,11 +177,6 @@ class PolymorphicTests(TestCase): '') - # A bug in Django 1.4 prevents using defer across reverse relations - # . Since polymorphic - # uses reverse relations to traverse down model inheritance, deferring - # fields in child models will not work in Django 1.4. - @skipIf(django.VERSION < (1, 5), "Django 1.4 does not support defer on related fields") def test_defer_related_fields(self): self.create_model2abcd() @@ -424,7 +408,6 @@ class PolymorphicTests(TestCase): self.assertEqual(repr(objects[0]), '') self.assertEqual(repr(objects[1]), '') - @skipIf(django.VERSION < (1, 6), "Django 1.4 and 1.5 don't support q.clone()") def test_query_filter_exclude_is_immutable(self): # given q_to_reuse = Q(Model2B___field2='something') @@ -564,7 +547,6 @@ class PolymorphicTests(TestCase): self.assertIs(type(ModelWithMyManagerDefault._default_manager), MyManager) self.assertIs(type(ModelWithMyManagerDefault.base_objects), models.Manager) - @skipIf(django.VERSION < (1, 7), "This test needs Django 1.7+") def test_user_defined_queryset_as_manager(self): self.create_model2abcd() ModelWithMyManager2.objects.create(field1='D1a', field4='D4a') @@ -751,7 +733,6 @@ class PolymorphicTests(TestCase): lambda: Model2A.objects.aggregate(Count('Model2B___field2')) ) - @skipIf(django.VERSION < (1, 8,), "This test needs Django >=1.8") def test_polymorphic__complex_aggregate(self): """ test (complex expression on) aggregate (should work for annotate either) """ @@ -777,7 +758,6 @@ class PolymorphicTests(TestCase): with self.assertRaisesMessage(AssertionError, 'PolymorphicModel: annotate()/aggregate(): ___ model lookup supported for keyword arguments only'): Model2A.objects.aggregate(ComplexAgg('Model2B___field2')) - @skipIf(django.VERSION < (1, 8,), "This test needs Django >=1.8") def test_polymorphic__expressions(self): from django.db.models.functions import Concat diff --git a/polymorphic/tools_for_tests.py b/polymorphic/tools_for_tests.py deleted file mode 100644 index 9a60b60..0000000 --- a/polymorphic/tools_for_tests.py +++ /dev/null @@ -1,144 +0,0 @@ -# Compatibility module for Django < 1.8 -import uuid - -from django import forms -from django.db import models -from django.utils.encoding import smart_text -from django.utils import six - - -class UUIDVersionError(Exception): - pass - - -class UUIDField(six.with_metaclass(models.SubfieldBase, models.CharField)): - """Encode and stores a Python uuid.UUID in a manner that is appropriate - for the given datatabase that we are using. - - For sqlite3 or MySQL we save it as a 36-character string value - For PostgreSQL we save it as a uuid field - - This class supports type 1, 2, 4, and 5 UUID's. - """ - - _CREATE_COLUMN_TYPES = { - 'postgresql_psycopg2': 'uuid', - 'postgresql': 'uuid' - } - - def __init__(self, verbose_name=None, name=None, auto=True, version=1, node=None, clock_seq=None, namespace=None, **kwargs): - """Contruct a UUIDField. - - @param verbose_name: Optional verbose name to use in place of what - Django would assign. - @param name: Override Django's name assignment - @param auto: If True, create a UUID value if one is not specified. - @param version: By default we create a version 1 UUID. - @param node: Used for version 1 UUID's. If not supplied, then the uuid.getnode() function is called to obtain it. This can be slow. - @param clock_seq: Used for version 1 UUID's. If not supplied a random 14-bit sequence number is chosen - @param namespace: Required for version 3 and version 5 UUID's. - @param name: Required for version4 and version 5 UUID's. - - See Also: - - Python Library Reference, section 18.16 for more information. - - RFC 4122, "A Universally Unique IDentifier (UUID) URN Namespace" - - If you want to use one of these as a primary key for a Django - model, do this:: - id = UUIDField(primary_key=True) - This will currently I{not} work with Jython because PostgreSQL support - in Jython is not working for uuid column types. - """ - self.max_length = 36 - kwargs['max_length'] = self.max_length - if auto: - kwargs['blank'] = True - kwargs.setdefault('editable', False) - - self.auto = auto - self.version = version - if version == 1: - self.node, self.clock_seq = node, clock_seq - elif version == 3 or version == 5: - self.namespace, self.name = namespace, name - - super(UUIDField, self).__init__(verbose_name=verbose_name, - name=name, **kwargs) - - def create_uuid(self): - if not self.version or self.version == 4: - return uuid.uuid4() - elif self.version == 1: - return uuid.uuid1(self.node, self.clock_seq) - elif self.version == 2: - raise UUIDVersionError("UUID version 2 is not supported.") - elif self.version == 3: - return uuid.uuid3(self.namespace, self.name) - elif self.version == 5: - return uuid.uuid5(self.namespace, self.name) - else: - raise UUIDVersionError("UUID version %s is not valid." % self.version) - - def db_type(self, connection): - from django.conf import settings - full_database_type = settings.DATABASES['default']['ENGINE'] - database_type = full_database_type.split('.')[-1] - return UUIDField._CREATE_COLUMN_TYPES.get(database_type, "char(%s)" % self.max_length) - - def to_python(self, value): - """Return a uuid.UUID instance from the value returned by the database.""" - # - # This is the proper way... But this doesn't work correctly when - # working with an inherited model - # - if not value: - return None - if isinstance(value, uuid.UUID): - return value - # attempt to parse a UUID - return uuid.UUID(smart_text(value)) - - # - # If I do the following (returning a String instead of a UUID - # instance), everything works. - # - - # if not value: - # return None - # if isinstance(value, uuid.UUID): - # return smart_text(value) - # else: - # return value - - def pre_save(self, model_instance, add): - if self.auto and add: - value = self.create_uuid() - setattr(model_instance, self.attname, value) - else: - value = super(UUIDField, self).pre_save(model_instance, add) - if self.auto and not value: - value = self.create_uuid() - setattr(model_instance, self.attname, value) - return value - - def get_db_prep_value(self, value, connection, prepared): - """Casts uuid.UUID values into the format expected by the back end for use in queries""" - if isinstance(value, uuid.UUID): - return smart_text(value) - return value - - def value_to_string(self, obj): - val = self._get_val_from_obj(obj) - if val is None: - data = '' - else: - data = smart_text(val) - return data - - def formfield(self, **kwargs): - defaults = { - 'form_class': forms.CharField, - 'max_length': self.max_length - } - defaults.update(kwargs) - return super(UUIDField, self).formfield(**defaults) diff --git a/runtests.py b/runtests.py index 2b73f66..0e76c03 100755 --- a/runtests.py +++ b/runtests.py @@ -1,12 +1,10 @@ #!/usr/bin/env python -import os import sys -import django +from os.path import abspath, dirname +import django from django.conf import settings from django.core.management import execute_from_command_line -from django.conf import settings, global_settings as default_settings -from os.path import dirname, realpath, abspath # Give feedback on used versions @@ -17,42 +15,6 @@ sys.stderr.write('Using Django version {0} from {1}\n'.format( ) if not settings.configured: - if django.VERSION >= (1, 8): - template_settings = dict( - TEMPLATES = [ - { - 'BACKEND': 'django.template.backends.django.DjangoTemplates', - 'DIRS': (), - 'OPTIONS': { - 'loaders': ( - 'django.template.loaders.filesystem.Loader', - 'django.template.loaders.app_directories.Loader', - ), - 'context_processors': ( - 'django.template.context_processors.debug', - 'django.template.context_processors.i18n', - 'django.template.context_processors.media', - 'django.template.context_processors.request', - 'django.template.context_processors.static', - 'django.contrib.messages.context_processors.messages', - 'django.contrib.auth.context_processors.auth', - ), - }, - }, - ] - ) - else: - template_settings = dict( - TEMPLATE_LOADERS = ( - 'django.template.loaders.app_directories.Loader', - 'django.template.loaders.filesystem.Loader', - ), - TEMPLATE_CONTEXT_PROCESSORS = list(default_settings.TEMPLATE_CONTEXT_PROCESSORS) + [ - 'django.contrib.messages.context_processors.messages', - 'django.core.context_processors.request', - ], - ) - settings.configure( DEBUG=False, DATABASES={ @@ -65,8 +27,8 @@ if not settings.configured: 'NAME': ':memory:' } }, - TEST_RUNNER = 'django.test.runner.DiscoverRunner' if django.VERSION >= (1, 7) else 'django.test.simple.DjangoTestSuiteRunner', - INSTALLED_APPS = ( + TEST_RUNNER="django.test.runner.DiscoverRunner", + INSTALLED_APPS=( 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.messages', @@ -74,11 +36,31 @@ if not settings.configured: 'django.contrib.admin', 'polymorphic', ), - MIDDLEWARE_CLASSES = (), - SITE_ID = 3, - **template_settings + MIDDLEWARE_CLASSES=(), + SITE_ID=3, + TEMPLATES=[{ + "BACKEND": "django.template.backends.django.DjangoTemplates", + "DIRS": (), + "OPTIONS": { + "loaders": ( + "django.template.loaders.filesystem.Loader", + "django.template.loaders.app_directories.Loader", + ), + "context_processors": ( + "django.template.context_processors.debug", + "django.template.context_processors.i18n", + "django.template.context_processors.media", + "django.template.context_processors.request", + "django.template.context_processors.static", + "django.contrib.messages.context_processors.messages", + "django.contrib.auth.context_processors.auth", + ), + }, + }, + ] ) + DEFAULT_TEST_APPS = [ 'polymorphic', ] @@ -90,5 +72,6 @@ def runtests(): argv = sys.argv[:1] + ['test', '--traceback'] + other_args + test_apps execute_from_command_line(argv) + if __name__ == '__main__': runtests() diff --git a/setup.py b/setup.py index 3f0f9cb..7c0a4f5 100755 --- a/setup.py +++ b/setup.py @@ -1,10 +1,9 @@ #!/usr/bin/env python -from setuptools import setup, find_packages -from os import path import codecs -import os import re -import sys +from os import path + +from setuptools import find_packages, setup def read(*parts): @@ -47,18 +46,14 @@ setup( 'License :: OSI Approved :: BSD License', 'Operating System :: OS Independent', 'Programming Language :: Python', - 'Programming Language :: Python :: 2.6', 'Programming Language :: Python :: 2.7', - 'Programming Language :: Python :: 3.2', - 'Programming Language :: Python :: 3.3', 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', + 'Programming Language :: Python :: 3.6', 'Framework :: Django', - 'Framework :: Django :: 1.4', - 'Framework :: Django :: 1.5', - 'Framework :: Django :: 1.6', - 'Framework :: Django :: 1.7', 'Framework :: Django :: 1.8', + 'Framework :: Django :: 1.10', + 'Framework :: Django :: 1.11', 'Topic :: Software Development :: Libraries :: Python Modules', ] ) From 1e7237986cbb26d67bf8b0507d170efdf7f84bc0 Mon Sep 17 00:00:00 2001 From: Jerome Leclanche Date: Fri, 19 May 2017 10:02:03 +0300 Subject: [PATCH 3/9] Specify on_delete argument for all related fields that need it --- polymorphic/models.py | 6 ++++-- polymorphic/tests/__init__.py | 20 +++++++++++--------- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/polymorphic/models.py b/polymorphic/models.py index 4271f14..f0af431 100644 --- a/polymorphic/models.py +++ b/polymorphic/models.py @@ -37,8 +37,10 @@ class PolymorphicModel(six.with_metaclass(PolymorphicModelBase, models.Model)): # avoid ContentType related field accessor clash (an error emitted by model validation) #: The model field that stores the :class:`~django.contrib.contenttypes.models.ContentType` reference to the actual class. - polymorphic_ctype = models.ForeignKey(ContentType, null=True, editable=False, - related_name='polymorphic_%(app_label)s.%(class)s_set+') + polymorphic_ctype = models.ForeignKey( + ContentType, null=True, editable=False, on_delete=models.CASCADE, + related_name='polymorphic_%(app_label)s.%(class)s_set+' + ) # some applications want to know the name of the fields that are added to its models polymorphic_internal_model_fields = ['polymorphic_ctype'] diff --git a/polymorphic/tests/__init__.py b/polymorphic/tests/__init__.py index bac18e9..b16a917 100644 --- a/polymorphic/tests/__init__.py +++ b/polymorphic/tests/__init__.py @@ -107,7 +107,7 @@ class Enhance_Inherit(Enhance_Base, Enhance_Plain): class RelationBase(ShowFieldTypeAndContent, PolymorphicModel): field_base = models.CharField(max_length=10) - fk = models.ForeignKey('self', null=True, related_name='relationbase_set') + fk = models.ForeignKey('self', on_delete=models.CASCADE, null=True, related_name='relationbase_set') m2m = models.ManyToManyField('self') @@ -128,7 +128,7 @@ class RelatingModel(models.Model): class One2OneRelatingModel(PolymorphicModel): - one2one = models.OneToOneField(Model2A) + one2one = models.OneToOneField(Model2A, on_delete=models.CASCADE) field1 = models.CharField(max_length=10) @@ -142,7 +142,7 @@ class ModelUnderRelParent(PolymorphicModel): class ModelUnderRelChild(PolymorphicModel): - parent = models.ForeignKey(ModelUnderRelParent, related_name='children') + parent = models.ForeignKey(ModelUnderRelParent, on_delete=models.CASCADE, related_name='children') _private2 = models.CharField(max_length=10) @@ -208,7 +208,7 @@ class ParentModelWithManager(PolymorphicModel): class ChildModelWithManager(PolymorphicModel): # Also test whether foreign keys receive the manager: - fk = models.ForeignKey(ParentModelWithManager, related_name='childmodel_set') + fk = models.ForeignKey(ParentModelWithManager, on_delete=models.CASCADE, related_name='childmodel_set') objects = MyManager() @@ -232,7 +232,7 @@ class PlainParentModelWithManager(models.Model): class PlainChildModelWithManager(models.Model): - fk = models.ForeignKey(PlainParentModelWithManager, related_name='childmodel_set') + fk = models.ForeignKey(PlainParentModelWithManager, on_delete=models.CASCADE, related_name='childmodel_set') objects = PlainMyManager() @@ -264,12 +264,12 @@ class BlogB(BlogBase): class BlogEntry(ShowFieldTypeAndContent, PolymorphicModel): - blog = models.ForeignKey(BlogA) + blog = models.ForeignKey(BlogA, on_delete=models.CASCADE) text = models.CharField(max_length=10) class BlogEntry_limit_choices_to(ShowFieldTypeAndContent, PolymorphicModel): - blog = models.ForeignKey(BlogBase) + blog = models.ForeignKey(BlogBase, on_delete=models.CASCADE) text = models.CharField(max_length=10) @@ -375,13 +375,15 @@ class ProxyModelB(ProxyModelBase): # with related field 'ContentType.relatednameclash_set'." (reported by Andrew Ingram) # fixed with related_name class RelatedNameClash(ShowFieldType, PolymorphicModel): - ctype = models.ForeignKey(ContentType, null=True, editable=False) + ctype = models.ForeignKey(ContentType, on_delete=models.CASCADE, null=True, editable=False) # class with a parent_link to superclass, and a related_name back to subclass class TestParentLinkAndRelatedName(ModelShow1_plain): - superclass = models.OneToOneField(ModelShow1_plain, parent_link=True, related_name='related_name_subclass') + superclass = models.OneToOneField( + ModelShow1_plain, on_delete=models.CASCADE, parent_link=True, related_name='related_name_subclass' + ) class CustomPkBase(ShowFieldTypeAndContent, PolymorphicModel): From 66124a59eb6bb835115553863eb4c82bd522bc67 Mon Sep 17 00:00:00 2001 From: Jerome Leclanche Date: Mon, 22 May 2017 14:06:16 +0300 Subject: [PATCH 4/9] docs: Move dependencies to tox.ini docs env --- docs/_ext/djangodummy/requirements.txt | 4 ---- tox.ini | 6 +++++- 2 files changed, 5 insertions(+), 5 deletions(-) delete mode 100644 docs/_ext/djangodummy/requirements.txt diff --git a/docs/_ext/djangodummy/requirements.txt b/docs/_ext/djangodummy/requirements.txt deleted file mode 100644 index b182c33..0000000 --- a/docs/_ext/djangodummy/requirements.txt +++ /dev/null @@ -1,4 +0,0 @@ -# for readthedocs -# Remaining requirements are picked up from setup.py -Django==1.11 -django-extra-views==0.9.0 diff --git a/tox.ini b/tox.ini index 5c71b57..0a2a534 100644 --- a/tox.ini +++ b/tox.ini @@ -17,6 +17,10 @@ commands = coverage run --source polymorphic runtests.py [testenv:docs] -deps = Sphinx +deps = + Sphinx + sphinx_rtd_theme + Django + django-extra-views changedir = docs commands = sphinx-build -W -b html -d {envtmpdir}/doctrees . {envtmpdir}/html From b2b20bed55ef45fd090467c05ff8f81a56404783 Mon Sep 17 00:00:00 2001 From: Jerome Leclanche Date: Mon, 22 May 2017 14:11:43 +0300 Subject: [PATCH 5/9] docs: Fix a missing newline --- docs/migrating.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/migrating.rst b/docs/migrating.rst index b6f7d5c..866da3b 100644 --- a/docs/migrating.rst +++ b/docs/migrating.rst @@ -61,6 +61,7 @@ It's recommended to let ``makemigrations`` create the migration file, and include the ``RunPython`` manually before running the migration. .. versionadded:: 1.1 + When the model is created elsewhere, you can also use the :func:`polymorphic.utils.reset_polymorphic_ctype` function: From 0a495cb485ba5bb8bb81dc95fa760b10957b6175 Mon Sep 17 00:00:00 2001 From: Jerome Leclanche Date: Sun, 4 Jun 2017 14:32:59 +0300 Subject: [PATCH 6/9] Fix PolymorphicParentModelAdmin.pk_regex escape --- polymorphic/admin/parentadmin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/polymorphic/admin/parentadmin.py b/polymorphic/admin/parentadmin.py index dd37e40..d156ca2 100644 --- a/polymorphic/admin/parentadmin.py +++ b/polymorphic/admin/parentadmin.py @@ -61,7 +61,7 @@ class PolymorphicParentModelAdmin(admin.ModelAdmin): #: The regular expression to filter the primary key in the URL. #: This accepts only numbers as defensive measure against catch-all URLs. #: If your primary key consists of string values, update this regular expression. - pk_regex = '(\d+|__fk__)' + pk_regex = r"(\d+|__fk__)" def __init__(self, model, admin_site, *args, **kwargs): super(PolymorphicParentModelAdmin, self).__init__(model, admin_site, *args, **kwargs) From 87979a666095e4bda63b12078d7057e3c844c218 Mon Sep 17 00:00:00 2001 From: Jerome Leclanche Date: Thu, 1 Jun 2017 12:18:02 +0300 Subject: [PATCH 7/9] Remove outdated 404 url from docstrings --- polymorphic/base.py | 4 ++-- polymorphic/query.py | 4 ++-- polymorphic/query_translate.py | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/polymorphic/base.py b/polymorphic/base.py index 3abe7f0..b6a5a74 100644 --- a/polymorphic/base.py +++ b/polymorphic/base.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -""" PolymorphicModel Meta Class - Please see README.rst or DOCS.rst or http://chrisglass.github.com/django_polymorphic/ +""" +PolymorphicModel Meta Class """ from __future__ import absolute_import diff --git a/polymorphic/query.py b/polymorphic/query.py index b520c00..3d881d8 100644 --- a/polymorphic/query.py +++ b/polymorphic/query.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -""" QuerySet for PolymorphicModel - Please see README.rst or DOCS.rst or http://chrisglass.github.com/django_polymorphic/ +""" +QuerySet for PolymorphicModel """ from __future__ import absolute_import diff --git a/polymorphic/query_translate.py b/polymorphic/query_translate.py index b98f8d1..c7d7310 100644 --- a/polymorphic/query_translate.py +++ b/polymorphic/query_translate.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -""" PolymorphicQuerySet support functions - Please see README.rst or DOCS.rst or http://chrisglass.github.com/django_polymorphic/ +""" +PolymorphicQuerySet support functions """ from __future__ import absolute_import From 2b4aeaec789d6553f8a33acf5b5eeca79915fab4 Mon Sep 17 00:00:00 2001 From: Jerome Leclanche Date: Thu, 1 Jun 2017 11:33:30 +0300 Subject: [PATCH 8/9] Set PYTHONWARNINGS=all when testing --- tox.ini | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tox.ini b/tox.ini index 0a2a534..9f14681 100644 --- a/tox.ini +++ b/tox.ini @@ -7,6 +7,8 @@ envlist = docs [testenv] +setenv = + PYTHONWARNINGS = all deps = coverage == 3.6 django18: Django >= 1.8, < 1.9 From 452b7cb69ebb545d4fd87aadc4a4d7ddb163f363 Mon Sep 17 00:00:00 2001 From: Jerome Leclanche Date: Thu, 1 Jun 2017 11:40:25 +0300 Subject: [PATCH 9/9] Add myself to AUTHORS --- AUTHORS.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/AUTHORS.rst b/AUTHORS.rst index 5e562ce..ee00aa9 100644 --- a/AUTHORS.rst +++ b/AUTHORS.rst @@ -22,6 +22,7 @@ Contributors * Hugo Osvaldo Barrera * Jacob Rief * Jedediah Smith (proxy models support) +* Jerome Leclanche * John Furr * Jonas Obrist * Julian Wachholz