Merge pull request #298 from jleclanche/cleanup/django18

Drop Django < 1.8 compatibility
fix_request_path_info
Diederik van der Boor 2017-07-10 10:55:16 +02:00 committed by GitHub
commit e5d21d7b4e
30 changed files with 218 additions and 654 deletions

View File

@ -2,20 +2,12 @@ sudo: false
language: python language: python
cache: pip cache: pip
python: python:
- "2.6"
- "2.7" - "2.7"
- "3.2"
- "3.3"
- "3.4" - "3.4"
- "3.5" - "3.5"
- "3.6" - "3.6"
env: env:
- TOXENV="django14"
- TOXENV="django15"
- TOXENV="django16"
- TOXENV="django17"
- TOXENV="django18" - TOXENV="django18"
- TOXENV="django19"
- TOXENV="django110" - TOXENV="django110"
- TOXENV="django111" - TOXENV="django111"
- TOXENV="djangodev" - TOXENV="djangodev"
@ -23,77 +15,19 @@ env:
matrix: matrix:
fast_finish: true fast_finish: true
exclude: 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" - python: "3.6"
env: TOXENV="django18" env: TOXENV="django18"
- python: "3.6"
env: TOXENV="django19"
- python: "3.6" - python: "3.6"
env: TOXENV="django110" 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" - python: "3.4"
env: TOXENV="django110" env: TOXENV="django110"
- python: "3.4" - python: "3.4"
env: TOXENV="django111" 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" - python: "2.7"
env: TOXENV="djangodev" 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: allow_failures:
- env: TOXENV="djangodev" - env: TOXENV="djangodev"

View File

@ -22,6 +22,7 @@ Contributors
* Hugo Osvaldo Barrera * Hugo Osvaldo Barrera
* Jacob Rief * Jacob Rief
* Jedediah Smith (proxy models support) * Jedediah Smith (proxy models support)
* Jerome Leclanche
* John Furr * John Furr
* Jonas Obrist * Jonas Obrist
* Julian Wachholz * Julian Wachholz

View File

@ -1,4 +0,0 @@
# for readthedocs
# Remaining requirements are picked up from setup.py
Django==1.11
django-extra-views==0.9.0

View File

@ -2,22 +2,16 @@
Automatically mention all model fields as parameters in the model construction. Automatically mention all model fields as parameters in the model construction.
Based on http://djangosnippets.org/snippets/2533/ 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 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): 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. 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 inspect.isclass(obj) and issubclass(obj, models.Model):
if django.VERSION >= (1,8):
model_fields = obj._meta.get_fields() model_fields = obj._meta.get_fields()
elif django.VERSION >= (1,6):
model_fields = obj._meta.fields
else:
model_fields = obj._meta._fields()
for field in model_fields: for field in model_fields:
help_text = strip_tags(force_text(field.help_text)) 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 the extended docstring
return lines return lines
# Allow this module to be used as sphinx extension: # Allow this module to be used as sphinx extension:
def setup(app): def setup(app):
# Generate docstrings for Django model fields # Generate docstrings for Django model fields
# Register the docstring processor with sphinx # Register the docstring processor with sphinx
app.connect('autodoc-process-docstring', improve_model_docstring) app.connect('autodoc-process-docstring', improve_model_docstring)

View File

@ -23,7 +23,6 @@ sys.path.insert(0, os.path.abspath('_ext'))
sys.path.insert(0, os.path.abspath('..')) sys.path.insert(0, os.path.abspath('..'))
os.environ['DJANGO_SETTINGS_MODULE'] = 'djangodummy.settings' os.environ['DJANGO_SETTINGS_MODULE'] = 'djangodummy.settings'
if django.VERSION >= (1, 8):
django.setup() django.setup()
# -- General configuration ----------------------------------------------------- # -- General configuration -----------------------------------------------------

View File

@ -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 related objects. It must not filter objects and it's safest to use
the plain ``PolymorphicManager`` here. the plain ``PolymorphicManager`` here.
Note that get_query_set is deprecated in Django 1.8 and creates warnings in Django 1.7.
Manager Inheritance Manager Inheritance
------------------- -------------------
@ -69,8 +67,6 @@ regarding their start time and ``ArtProject.objects_ordered.most_recent()``
will return the ten most recent art projects. 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 Using a Custom Queryset Class
----------------------------- -----------------------------

View File

@ -61,6 +61,7 @@ It's recommended to let ``makemigrations`` create the migration file,
and include the ``RunPython`` manually before running the migration. and include the ``RunPython`` manually before running the migration.
.. versionadded:: 1.1 .. versionadded:: 1.1
When the model is created elsewhere, you can also use When the model is created elsewhere, you can also use
the :func:`polymorphic.utils.reset_polymorphic_ctype` function: the :func:`polymorphic.utils.reset_polymorphic_ctype` function:

View File

@ -1,4 +1,3 @@
import django
import os import os
DEBUG = True DEBUG = True
@ -67,7 +66,6 @@ INSTALLED_APPS = (
'django.contrib.admin', 'django.contrib.admin',
'django.contrib.contenttypes', 'django.contrib.contenttypes',
'django.contrib.sessions', 'django.contrib.sessions',
#'django.contrib.sites',
'django.contrib.messages', 'django.contrib.messages',
'django.contrib.staticfiles', 'django.contrib.staticfiles',
@ -76,7 +74,6 @@ INSTALLED_APPS = (
'orders', '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 configuration

View File

@ -3,34 +3,32 @@
This module is a scratchpad for general development, testing & debugging This module is a scratchpad for general development, testing & debugging
Well, even more so than pcmd.py. You best ignore p2cmd.py. 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 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 * from pexp.models import *
def reset_queries(): rnd = Random()
if django.VERSION < (1, 8):
connection.queries = []
else:
connection.queries_log.clear()
def show_queries(): def show_queries():
print print()
print 'QUERIES:', len(connection.queries) print("QUERIES:", len(connection.queries))
pprint(connection.queries) pprint(connection.queries)
print print()
connection.queries = [] connection.queries = []
def print_timing(func, message='', iterations=1): def print_timing(func, message='', iterations=1):
def wrapper(*arg): def wrapper(*arg):
results = [] results = []
reset_queries() connection.queries_log.clear()
for i in xrange(iterations): for i in range(iterations):
t1 = time.time() t1 = time.time()
x = func(*arg) x = func(*arg)
t2 = time.time() t2 = time.time()
@ -38,13 +36,12 @@ def print_timing(func, message='', iterations=1):
res_sum = 0 res_sum = 0
for r in results: for r in results:
res_sum += r 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, message, func.func_name,
res_sum, res_sum,
len(connection.queries), len(connection.queries),
iterations iterations
) ))
sys.stdout.flush() sys.stdout.flush()
return wrapper return wrapper
@ -58,18 +55,18 @@ class Command(NoArgsCommand):
a = TestModelA.objects.create(field1='A1') a = TestModelA.objects.create(field1='A1')
b = TestModelB.objects.create(field1='B1', field2='B2') b = TestModelB.objects.create(field1='B1', field2='B2')
c = TestModelC.objects.create(field1='C1', field2='C2', field3='C3') c = TestModelC.objects.create(field1='C1', field2='C2', field3='C3')
reset_queries() connection.queries_log.clear()
print TestModelC.base_objects.all() print(TestModelC.base_objects.all())
show_queries() show_queries()
if False: if False:
TestModelA.objects.all().delete() TestModelA.objects.all().delete()
for i in xrange(1000): for i in range(1000):
a = TestModelA.objects.create(field1=str(i % 100)) a = TestModelA.objects.create(field1=str(i % 100))
b = TestModelB.objects.create(field1=str(i % 100), field2=str(i % 200)) 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)) c = TestModelC.objects.create(field1=str(i % 100), field2=str(i % 200), field3=str(i % 300))
if i % 100 == 0: if i % 100 == 0:
print i print(i)
f = print_timing(poly_sql_query, iterations=1000) f = print_timing(poly_sql_query, iterations=1000)
f() f()
@ -85,11 +82,7 @@ class Command(NoArgsCommand):
c = NormalModelC.objects.create(field1='C1', field2='C2', field3='C3') c = NormalModelC.objects.create(field1='C1', field2='C2', field3='C3')
qs = TestModelA.objects.raw("SELECT * from pexp_testmodela") qs = TestModelA.objects.raw("SELECT * from pexp_testmodela")
for o in list(qs): for o in list(qs):
print o print(o)
from django.db import connection, transaction
from random import Random
rnd = Random()
def poly_sql_query(): def poly_sql_query():

View File

@ -3,31 +3,26 @@
This module is a scratchpad for general development, testing & debugging This module is a scratchpad for general development, testing & debugging
""" """
import time
import sys
from django.core.management.base import NoArgsCommand from django.core.management.base import NoArgsCommand
from django.db import connection from django.db import connection
from pprint import pprint from pprint import pprint
import sys
from pexp.models import * from pexp.models import *
num_objects = 1000 num_objects = 1000
def reset_queries(): def show_queries():
if django.VERSION < (1, 8): print()
connection.queries = [] print("QUERIES:", len(connection.queries))
else: pprint(connection.queries)
print()
connection.queries_log.clear() connection.queries_log.clear()
def show_queries():
print
print 'QUERIES:', len(connection.queries)
pprint(connection.queries)
print
reset_queries()
import time
################################################################################### ###################################################################################
# benchmark wrappers # benchmark wrappers
@ -35,8 +30,8 @@ import time
def print_timing(func, message='', iterations=1): def print_timing(func, message='', iterations=1):
def wrapper(*arg): def wrapper(*arg):
results = [] results = []
reset_queries() connection.queries_log.clear()
for i in xrange(iterations): for i in range(iterations):
t1 = time.time() t1 = time.time()
x = func(*arg) x = func(*arg)
t2 = time.time() t2 = time.time()
@ -45,11 +40,11 @@ def print_timing(func, message='', iterations=1):
for r in results: for r in results:
res_sum += r res_sum += r
median = res_sum / len(results) median = res_sum / len(results)
print '%s%-19s: %.0f ms, %i queries' % ( print("%s%-19s: %.0f ms, %i queries" % (
message, func.func_name, message, func.func_name,
median, median,
len(connection.queries) / len(results) len(connection.queries) / len(results)
) ))
sys.stdout.flush() sys.stdout.flush()
return wrapper return wrapper

View File

@ -1,6 +1,5 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
import django
from django.db import models from django.db import models
from polymorphic.models import PolymorphicModel from polymorphic.models import PolymorphicModel
@ -20,15 +19,9 @@ class ResearchProject(Project):
supervisor = models.CharField(max_length=30) 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): class UUIDModelA(ShowFieldTypeAndContent, PolymorphicModel):
"""UUID as primary key example""" """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) field1 = models.CharField(max_length=10)

View File

@ -6,36 +6,6 @@ Copyright:
This code and affiliated files are (C) by Bert Constantin and individual contributors. This code and affiliated files are (C) by Bert Constantin and individual contributors.
Please see LICENSE and AUTHORS for more information. Please see LICENSE and AUTHORS for more information.
""" """
import django
# See PEP 440 (https://www.python.org/dev/peps/pep-0440/) # See PEP 440 (https://www.python.org/dev/peps/pep-0440/)
__version__ = "1.2" __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

View File

@ -1,14 +1,10 @@
from django.contrib.contenttypes.admin import GenericInlineModelAdmin
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
from django.utils.functional import cached_property from django.utils.functional import cached_property
from polymorphic.formsets import polymorphic_child_forms_factory, BaseGenericPolymorphicInlineFormSet, GenericPolymorphicFormSetChild from polymorphic.formsets import polymorphic_child_forms_factory, BaseGenericPolymorphicInlineFormSet, GenericPolymorphicFormSetChild
from .inlines import PolymorphicInlineModelAdmin 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): class GenericPolymorphicInlineModelAdmin(PolymorphicInlineModelAdmin, GenericInlineModelAdmin):
""" """

View File

@ -6,6 +6,7 @@ Each row in the inline can correspond with a different subclass.
from functools import partial from functools import partial
from django.contrib.admin.options import InlineModelAdmin from django.contrib.admin.options import InlineModelAdmin
from django.contrib.admin.utils import flatten_fieldsets
from django.core.exceptions import ImproperlyConfigured from django.core.exceptions import ImproperlyConfigured
from django.forms import Media 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 polymorphic.formsets.utils import add_media
from .helpers import PolymorphicInlineSupportMixin 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): class PolymorphicInlineModelAdmin(InlineModelAdmin):
""" """

View File

@ -1,19 +1,18 @@
""" """
The parent admin displays the list view of the base model. The parent admin displays the list view of the base model.
""" """
import sys
import warnings import warnings
import django import django
from django.conf.urls import url from django.conf.urls import url
from django.contrib import admin from django.contrib import admin
from django.contrib.admin.helpers import AdminErrorList, AdminForm 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.contrib.contenttypes.models import ContentType
from django.core.exceptions import PermissionDenied from django.core.exceptions import PermissionDenied
from django.core.urlresolvers import RegexURLResolver from django.core.urlresolvers import RegexURLResolver
from django.http import Http404, HttpResponseRedirect from django.http import Http404, HttpResponseRedirect
from django.shortcuts import render_to_response from django.template.response import TemplateResponse
from django.template.context import RequestContext
from django.utils.encoding import force_text from django.utils.encoding import force_text
from django.utils.http import urlencode from django.utils.http import urlencode
from django.utils.safestring import mark_safe from django.utils.safestring import mark_safe
@ -21,16 +20,6 @@ from django.utils.translation import ugettext_lazy as _
from .forms import PolymorphicModelChoiceForm 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): class RegistrationClosed(RuntimeError):
"The admin model can't be registered anymore at this point." "The admin model can't be registered anymore at this point."
@ -72,7 +61,7 @@ class PolymorphicParentModelAdmin(admin.ModelAdmin):
#: The regular expression to filter the primary key in the URL. #: The regular expression to filter the primary key in the URL.
#: This accepts only numbers as defensive measure against catch-all URLs. #: This accepts only numbers as defensive measure against catch-all URLs.
#: If your primary key consists of string values, update this regular expression. #: 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): def __init__(self, model, admin_site, *args, **kwargs):
super(PolymorphicParentModelAdmin, self).__init__(model, admin_site, *args, **kwargs) super(PolymorphicParentModelAdmin, self).__init__(model, admin_site, *args, **kwargs)
@ -210,13 +199,6 @@ class PolymorphicParentModelAdmin(admin.ModelAdmin):
qs = qs.non_polymorphic() qs = qs.non_polymorphic()
return qs 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): def add_view(self, request, form_url='', extra_context=None):
"""Redirect the add view to the real admin.""" """Redirect the add view to the real admin."""
ct_id = int(request.GET.get('ct_id', 0)) ct_id = int(request.GET.get('ct_id', 0))
@ -238,7 +220,6 @@ class PolymorphicParentModelAdmin(admin.ModelAdmin):
real_admin = self._get_real_admin(object_id) real_admin = self._get_real_admin(object_id)
return real_admin.change_view(request, object_id, *args, **kwargs) 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): 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. # 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 # As it's directly called by django-reversion, this method is also overwritten to make sure it
@ -334,9 +315,9 @@ class PolymorphicParentModelAdmin(admin.ModelAdmin):
try: try:
pos = path.find('/') pos = path.find('/')
if pos == -1: if pos == -1:
object_id = long(path) object_id = int(path)
else: else:
object_id = long(path[0:pos]) object_id = int(path[0:pos])
except ValueError: except ValueError:
raise Http404("No ct_id parameter, unable to find admin subclass for path '{0}'.".format(path)) 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, 'add': True,
'save_on_top': self.save_on_top, '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 [ templates = self.add_type_template or [
"admin/%s/%s/add_type_form.html" % (app_label, opts.object_name.lower()), "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" "admin/add_type_form.html"
] ]
if django.VERSION >= (1, 8):
from django.template.response import TemplateResponse
request.current_app = self.admin_site.name request.current_app = self.admin_site.name
return TemplateResponse(request, templates, context) 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)
@property @property
def change_list_template(self): def change_list_template(self):
@ -445,7 +419,4 @@ class PolymorphicParentModelAdmin(admin.ModelAdmin):
def _get_opt(model): def _get_opt(model):
try: return model._meta.app_label, model._meta.model_name
return model._meta.app_label, model._meta.model_name # Django 1.7 format
except AttributeError:
return model._meta.app_label, model._meta.module_name

View File

@ -1,6 +1,6 @@
# -*- coding: utf-8 -*- # -*- 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 from __future__ import absolute_import
@ -132,6 +132,17 @@ class PolymorphicModelBase(ModelBase):
if type(manager) == models.manager.ManagerDescriptor: if type(manager) == models.manager.ManagerDescriptor:
manager = manager.manager 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: if AbstractManagerDescriptor is not None:
# Django 1.4 unconditionally assigned managers to a model. As of Django 1.5 however, # 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. # the abstract models don't get any managers, only a AbstractManagerDescriptor as substitute.

View File

@ -1,15 +1,11 @@
import django import django
from django.contrib.contenttypes.forms import BaseGenericInlineFormSet, generic_inlineformset_factory
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
from django.db import models from django.db import models
from django.forms.models import ModelForm from django.forms.models import ModelForm
from .models import BasePolymorphicModelFormSet, polymorphic_child_forms_factory, PolymorphicFormSetChild 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): class GenericPolymorphicFormSetChild(PolymorphicFormSetChild):
""" """

View File

@ -1,3 +1,4 @@
from collections import OrderedDict
import django import django
from django import forms from django import forms
@ -7,11 +8,6 @@ from django.forms.models import ModelForm, BaseModelFormSet, BaseInlineFormSet,
from django.utils.functional import cached_property from django.utils.functional import cached_property
from .utils import add_media 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): class PolymorphicFormSetChild(object):
""" """

View File

@ -4,14 +4,9 @@ The manager class for use in the models.
""" """
from __future__ import unicode_literals from __future__ import unicode_literals
import warnings import warnings
import django
from django.db import models from django.db import models
from polymorphic.query import PolymorphicQuerySet
try:
from django.utils.six import python_2_unicode_compatible from django.utils.six import python_2_unicode_compatible
except ImportError: from polymorphic.query import PolymorphicQuerySet
from django.utils.encoding import python_2_unicode_compatible # Django 1.5
__all__ = ( __all__ = (
@ -32,6 +27,12 @@ class PolymorphicManager(models.Manager):
use_for_related_fields = True use_for_related_fields = True
queryset_class = PolymorphicQuerySet 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): def __init__(self, queryset_class=None, *args, **kwrags):
# Up till polymorphic 0.4, the queryset class could be specified as parameter to __init__. # 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. # 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) super(PolymorphicManager, self).__init__(*args, **kwrags)
def get_queryset(self): def get_queryset(self):
if django.VERSION >= (1, 7):
qs = self.queryset_class(self.model, using=self._db, hints=self._hints) qs = self.queryset_class(self.model, using=self._db, hints=self._hints)
else:
qs = self.queryset_class(self.model, using=self._db)
if self.model._meta.proxy: if self.model._meta.proxy:
qs = qs.instance_of(self.model) qs = qs.instance_of(self.model)
return qs return qs
# For Django 1.5
if django.VERSION < (1, 7):
get_query_set = get_queryset
def __str__(self): def __str__(self):
return '%s (PolymorphicManager) using %s' % (self.__class__.__name__, self.queryset_class.__name__) 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): def get_real_instances(self, base_result_objects=None):
return self.all().get_real_instances(base_result_objects=base_result_objects) 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

View File

@ -4,9 +4,9 @@ Seamless Polymorphic Inheritance for Django Models
""" """
from __future__ import absolute_import from __future__ import absolute_import
from django.contrib.contenttypes.models import ContentType
from django.db import models from django.db import models
from django.db.utils import DEFAULT_DB_ALIAS from django.db.utils import DEFAULT_DB_ALIAS
from django.contrib.contenttypes.models import ContentType
from django.utils import six from django.utils import six
from .base import PolymorphicModelBase from .base import PolymorphicModelBase
@ -37,8 +37,10 @@ class PolymorphicModel(six.with_metaclass(PolymorphicModelBase, models.Model)):
# avoid ContentType related field accessor clash (an error emitted by model validation) # 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. #: 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, polymorphic_ctype = models.ForeignKey(
related_name='polymorphic_%(app_label)s.%(class)s_set+') 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 # some applications want to know the name of the fields that are added to its models
polymorphic_internal_model_fields = ['polymorphic_ctype'] polymorphic_internal_model_fields = ['polymorphic_ctype']
@ -85,11 +87,7 @@ class PolymorphicModel(six.with_metaclass(PolymorphicModelBase, models.Model)):
# so we use the following version, which uses the ContentType manager cache. # so we use the following version, which uses the ContentType manager cache.
# Note that model_class() can return None for stale content types; # 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. # 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() 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
# Protect against bad imports (dumpdata without --natural) or other # Protect against bad imports (dumpdata without --natural) or other
# issues missing with the ContentType models. # issues missing with the ContentType models.

View File

@ -1,6 +1,6 @@
# -*- coding: utf-8 -*- # -*- 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 from __future__ import absolute_import
@ -8,21 +8,16 @@ import copy
from collections import defaultdict from collections import defaultdict
import django import django
from django.db.models.query import QuerySet, Q
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
from django.db.models.query import Q, QuerySet
from django.utils import six 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_filter_definitions_in_kwargs, translate_polymorphic_filter_definitions_in_args
from .query_translate import translate_polymorphic_field_path, translate_polymorphic_Q_object from .query_translate import translate_polymorphic_field_path, translate_polymorphic_Q_object
# chunk-size: maximum number of objects requested per db-request # chunk-size: maximum number of objects requested per db-request
# by the PolymorphicModelIterable; we use the same chunk size as Django # by the polymorphic queryset.iterator() implementation
try: Polymorphic_QuerySet_objects_per_request = 100
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
def _polymorphic_iterator(queryset, base_iter): def _polymorphic_iterator(queryset, base_iter):
@ -97,14 +92,6 @@ def transmogrify(cls, obj):
################################################################################### ###################################################################################
# PolymorphicQuerySet # PolymorphicQuerySet
def _query_annotations(query):
try:
return query.annotations
except AttributeError:
# Django < 1.8
return query.aggregates
class PolymorphicQuerySet(QuerySet): class PolymorphicQuerySet(QuerySet):
""" """
QuerySet for PolymorphicModel QuerySet for PolymorphicModel
@ -138,9 +125,7 @@ class PolymorphicQuerySet(QuerySet):
self.polymorphic_deferred_loading[1]) self.polymorphic_deferred_loading[1])
return new return new
if django.VERSION >= (1, 7):
def as_manager(cls): def as_manager(cls):
# Make sure the Django 1.7 way of creating managers works.
from .managers import PolymorphicManager from .managers import PolymorphicManager
manager = PolymorphicManager.from_queryset(cls)() manager = PolymorphicManager.from_queryset(cls)()
manager._built_with_as_manager = True manager._built_with_as_manager = True
@ -244,15 +229,8 @@ class PolymorphicQuerySet(QuerySet):
Modifies kwargs if needed (these are Aggregate objects, we translate the lookup member variable)""" 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' ___lookup_assert_msg = 'PolymorphicModel: annotate()/aggregate(): ___ model lookup supported for keyword arguments only'
if django.VERSION < (1, 8):
def patch_lookup(a): def patch_lookup(a):
a.lookup = translate_polymorphic_field_path(self.model, a.lookup) # The field on which the aggregate operates is
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. # stored inside a complex query expression.
if isinstance(a, Q): if isinstance(a, Q):
translate_polymorphic_Q_object(self.model, a) translate_polymorphic_Q_object(self.model, a)
@ -438,8 +416,8 @@ class PolymorphicQuerySet(QuerySet):
if real_class != real_concrete_class: if real_class != real_concrete_class:
real_object = transmogrify(real_class, real_object) real_object = transmogrify(real_class, real_object)
if _query_annotations(self.query): if self.query.annotations:
for anno_field_name in six.iterkeys(_query_annotations(self.query)): for anno_field_name in six.iterkeys(self.query.annotations):
attr = getattr(base_result_objects_by_id[o_pk], anno_field_name) attr = getattr(base_result_objects_by_id[o_pk], anno_field_name)
setattr(real_object, anno_field_name, attr) 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] 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) # set polymorphic_annotate_names in all objects (currently just used for debugging/printing)
if _query_annotations(self.query): if self.query.annotations:
annotate_names = list(six.iterkeys(_query_annotations(self.query))) # get annotate field list annotate_names = list(six.iterkeys(self.query.annotations)) # get annotate field list
for real_object in resultlist: for real_object in resultlist:
real_object.polymorphic_annotate_names = annotate_names real_object.polymorphic_annotate_names = annotate_names

View File

@ -1,32 +1,18 @@
# -*- coding: utf-8 -*- # -*- 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 __future__ import absolute_import
import copy import copy
import django import django
from functools import reduce from functools import reduce
from django.db import models
from django.contrib.contenttypes.models import ContentType 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.db.utils import DEFAULT_DB_ALIAS
from django.utils import six 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 # PolymorphicQuerySet support functions
@ -102,7 +88,7 @@ def translate_polymorphic_filter_definitions_in_args(queryset_model, args, using
""" """
if django.VERSION >= (1, 10): if django.VERSION >= (1, 10):
q_objects = [copy.deepcopy(q) for q in args] 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] q_objects = [q.clone() for q in args]
else: else:
q_objects = args # NOTE: edits existing objects in place. 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 _) # Test whether it's actually a regular relation__ _fieldname (the field starting with an _)
# so no tripple ClassName___field was intended. # so no tripple ClassName___field was intended.
try: try:
if django.VERSION >= (1, 8):
# This also retreives M2M relations now (including reverse foreign key relations) # This also retreives M2M relations now (including reverse foreign key relations)
field = queryset_model._meta.get_field(classname) field = queryset_model._meta.get_field(classname)
else:
field = queryset_model._meta.get_field_by_name(classname)[0]
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 # 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. # 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. return field_path # No exception raised, field does exist.
except FieldDoesNotExist: except models.FieldDoesNotExist:
pass pass
# function to collect all sub-models, this should be optimized (cached) # 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' assert False, 'PolymorphicModel: instance_of expects a list of (polymorphic) models or a single (polymorphic) model'
def q_class_with_subclasses(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__(): for subclass in model.__subclasses__():
q = q | q_class_with_subclasses(subclass) q = q | q_class_with_subclasses(subclass)
return q return q

View File

@ -1,13 +1,10 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
import django
import re import re
from django.db import models from django.db import models
from django.utils import six from django.utils import six
try:
from django.utils.six import python_2_unicode_compatible 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_.*') RE_DEFERRED = re.compile('_Deferred_.*')
@ -157,11 +154,6 @@ class ShowFieldBase(object):
return '<' + out + '>' 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): class ShowFieldType(ShowFieldBase):
""" model mixin that shows the object's class and it's field types """ """ model mixin that shows the object's class and it's field types """

View File

@ -1,22 +1,16 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
import django
import uuid import uuid
from django.contrib.contenttypes.models import ContentType
from django.db import models from django.db import models
from django.db.models.query import QuerySet from django.db.models.query import QuerySet
from django.contrib.contenttypes.models import ContentType
from polymorphic.managers import PolymorphicManager from polymorphic.managers import PolymorphicManager
from polymorphic.models import PolymorphicModel from polymorphic.models import PolymorphicModel
from polymorphic.query import PolymorphicQuerySet from polymorphic.query import PolymorphicQuerySet
from polymorphic.showfields import ShowFieldContent, ShowFieldType, ShowFieldTypeAndContent 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): class PlainA(models.Model):
field1 = models.CharField(max_length=10) field1 = models.CharField(max_length=10)
@ -113,7 +107,7 @@ class Enhance_Inherit(Enhance_Base, Enhance_Plain):
class RelationBase(ShowFieldTypeAndContent, PolymorphicModel): class RelationBase(ShowFieldTypeAndContent, PolymorphicModel):
field_base = models.CharField(max_length=10) 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') m2m = models.ManyToManyField('self')
@ -134,7 +128,7 @@ class RelatingModel(models.Model):
class One2OneRelatingModel(PolymorphicModel): class One2OneRelatingModel(PolymorphicModel):
one2one = models.OneToOneField(Model2A) one2one = models.OneToOneField(Model2A, on_delete=models.CASCADE)
field1 = models.CharField(max_length=10) field1 = models.CharField(max_length=10)
@ -148,7 +142,7 @@ class ModelUnderRelParent(PolymorphicModel):
class ModelUnderRelChild(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) _private2 = models.CharField(max_length=10)
@ -167,9 +161,6 @@ class MyManager(PolymorphicManager):
def my_queryset_foo(self): def my_queryset_foo(self):
return self.all().my_queryset_foo() return self.all().my_queryset_foo()
# Django <= 1.5 compatibility
get_query_set = get_queryset
class ModelWithMyManager(ShowFieldTypeAndContent, Model2A): class ModelWithMyManager(ShowFieldTypeAndContent, Model2A):
objects = MyManager() objects = MyManager()
@ -188,7 +179,6 @@ class ModelWithMyManagerDefault(ShowFieldTypeAndContent, Model2A):
field4 = models.CharField(max_length=10) field4 = models.CharField(max_length=10)
if django.VERSION >= (1, 7):
class ModelWithMyManager2(ShowFieldTypeAndContent, Model2A): class ModelWithMyManager2(ShowFieldTypeAndContent, Model2A):
objects = MyManagerQuerySet.as_manager() objects = MyManagerQuerySet.as_manager()
field4 = models.CharField(max_length=10) field4 = models.CharField(max_length=10)
@ -218,7 +208,7 @@ class ParentModelWithManager(PolymorphicModel):
class ChildModelWithManager(PolymorphicModel): class ChildModelWithManager(PolymorphicModel):
# Also test whether foreign keys receive the manager: # 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() objects = MyManager()
@ -236,16 +226,13 @@ class PlainMyManager(models.Manager):
def get_queryset(self): def get_queryset(self):
return PlainMyManagerQuerySet(self.model, using=self._db) return PlainMyManagerQuerySet(self.model, using=self._db)
# Django <= 1.5 compatibility
get_query_set = get_queryset
class PlainParentModelWithManager(models.Model): class PlainParentModelWithManager(models.Model):
pass pass
class PlainChildModelWithManager(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() objects = PlainMyManager()
@ -277,12 +264,12 @@ class BlogB(BlogBase):
class BlogEntry(ShowFieldTypeAndContent, PolymorphicModel): class BlogEntry(ShowFieldTypeAndContent, PolymorphicModel):
blog = models.ForeignKey(BlogA) blog = models.ForeignKey(BlogA, on_delete=models.CASCADE)
text = models.CharField(max_length=10) text = models.CharField(max_length=10)
class BlogEntry_limit_choices_to(ShowFieldTypeAndContent, PolymorphicModel): 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) text = models.CharField(max_length=10)
@ -319,7 +306,7 @@ class Bottom(Middle):
class UUIDProject(ShowFieldTypeAndContent, PolymorphicModel): 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) topic = models.CharField(max_length=30)
@ -332,7 +319,7 @@ class UUIDResearchProject(UUIDProject):
class UUIDPlainA(models.Model): 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) field1 = models.CharField(max_length=10)
@ -388,13 +375,15 @@ class ProxyModelB(ProxyModelBase):
# with related field 'ContentType.relatednameclash_set'." (reported by Andrew Ingram) # with related field 'ContentType.relatednameclash_set'." (reported by Andrew Ingram)
# fixed with related_name # fixed with related_name
class RelatedNameClash(ShowFieldType, PolymorphicModel): 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 with a parent_link to superclass, and a related_name back to subclass
class TestParentLinkAndRelatedName(ModelShow1_plain): 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): class CustomPkBase(ShowFieldTypeAndContent, PolymorphicModel):

View File

@ -1,25 +1,15 @@
from __future__ import print_function from __future__ import print_function
import django
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
from django.test import TestCase
from django.db.models import Q from django.db.models import Q
from django.test import TestCase
from polymorphic.tests import * # all models 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): class MultipleDatabasesTests(TestCase):
multi_db = True multi_db = True
@skipIf(django.VERSION < (1, 5,), "This test needs Django >=1.5")
def test_save_to_non_default_database(self): def test_save_to_non_default_database(self):
Model2A.objects.db_manager('secondary').create(field1='A1') Model2A.objects.db_manager('secondary').create(field1='A1')
Model2C(field1='C1', field2='C2', field3='C3').save(using='secondary') Model2C(field1='C1', field2='C2', field3='C3').save(using='secondary')

View File

@ -1,22 +1,11 @@
from __future__ import print_function
import re import re
import django import django
from django.db.models import Case, Count, Q, When
from django.test import TestCase from django.test import TestCase
from django.db.models import Q, Count
from django.utils import six from django.utils import six
from polymorphic.tests import * # all models
from polymorphic.contrib.guardian import get_polymorphic_base_content_type from polymorphic.contrib.guardian import get_polymorphic_base_content_type
from polymorphic.tests import * # all models
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
class PolymorphicTests(TestCase): class PolymorphicTests(TestCase):
@ -188,11 +177,6 @@ class PolymorphicTests(TestCase):
'<Model2D: id 4, field1 (CharField), field2 (CharField), field3 (CharField), field4 (CharField), ' '<Model2D: id 4, field1 (CharField), field2 (CharField), field3 (CharField), field4 (CharField), '
'deferred[field2,field3,field4,model2a_ptr_id,model2b_ptr_id]>') 'deferred[field2,field3,field4,model2a_ptr_id,model2b_ptr_id]>')
# A bug in Django 1.4 prevents using defer across reverse relations
# <https://code.djangoproject.com/ticket/14694>. 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): def test_defer_related_fields(self):
self.create_model2abcd() self.create_model2abcd()
@ -424,7 +408,6 @@ class PolymorphicTests(TestCase):
self.assertEqual(repr(objects[0]), '<Model2B: id 2, field1 (CharField), field2 (CharField)>') self.assertEqual(repr(objects[0]), '<Model2B: id 2, field1 (CharField), field2 (CharField)>')
self.assertEqual(repr(objects[1]), '<Model2C: id 3, field1 (CharField), field2 (CharField), field3 (CharField)>') self.assertEqual(repr(objects[1]), '<Model2C: id 3, field1 (CharField), field2 (CharField), field3 (CharField)>')
@skipIf(django.VERSION < (1, 6), "Django 1.4 and 1.5 don't support q.clone()")
def test_query_filter_exclude_is_immutable(self): def test_query_filter_exclude_is_immutable(self):
# given # given
q_to_reuse = Q(Model2B___field2='something') 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._default_manager), MyManager)
self.assertIs(type(ModelWithMyManagerDefault.base_objects), models.Manager) 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): def test_user_defined_queryset_as_manager(self):
self.create_model2abcd() self.create_model2abcd()
ModelWithMyManager2.objects.create(field1='D1a', field4='D4a') ModelWithMyManager2.objects.create(field1='D1a', field4='D4a')
@ -751,7 +733,6 @@ class PolymorphicTests(TestCase):
lambda: Model2A.objects.aggregate(Count('Model2B___field2')) lambda: Model2A.objects.aggregate(Count('Model2B___field2'))
) )
@skipIf(django.VERSION < (1, 8,), "This test needs Django >=1.8")
def test_polymorphic__complex_aggregate(self): def test_polymorphic__complex_aggregate(self):
""" test (complex expression on) aggregate (should work for annotate either) """ """ 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'): with self.assertRaisesMessage(AssertionError, 'PolymorphicModel: annotate()/aggregate(): ___ model lookup supported for keyword arguments only'):
Model2A.objects.aggregate(ComplexAgg('Model2B___field2')) Model2A.objects.aggregate(ComplexAgg('Model2B___field2'))
@skipIf(django.VERSION < (1, 8,), "This test needs Django >=1.8")
def test_polymorphic__expressions(self): def test_polymorphic__expressions(self):
from django.db.models.functions import Concat from django.db.models.functions import Concat

View File

@ -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)

View File

@ -1,12 +1,10 @@
#!/usr/bin/env python #!/usr/bin/env python
import os
import sys import sys
import django from os.path import abspath, dirname
import django
from django.conf import settings from django.conf import settings
from django.core.management import execute_from_command_line 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 # 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 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( settings.configure(
DEBUG=False, DEBUG=False,
DATABASES={ DATABASES={
@ -65,7 +27,7 @@ if not settings.configured:
'NAME': ':memory:' 'NAME': ':memory:'
} }
}, },
TEST_RUNNER = 'django.test.runner.DiscoverRunner' if django.VERSION >= (1, 7) else 'django.test.simple.DjangoTestSuiteRunner', TEST_RUNNER="django.test.runner.DiscoverRunner",
INSTALLED_APPS=( INSTALLED_APPS=(
'django.contrib.auth', 'django.contrib.auth',
'django.contrib.contenttypes', 'django.contrib.contenttypes',
@ -76,9 +38,29 @@ if not settings.configured:
), ),
MIDDLEWARE_CLASSES=(), MIDDLEWARE_CLASSES=(),
SITE_ID=3, SITE_ID=3,
**template_settings 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 = [ DEFAULT_TEST_APPS = [
'polymorphic', 'polymorphic',
] ]
@ -90,5 +72,6 @@ def runtests():
argv = sys.argv[:1] + ['test', '--traceback'] + other_args + test_apps argv = sys.argv[:1] + ['test', '--traceback'] + other_args + test_apps
execute_from_command_line(argv) execute_from_command_line(argv)
if __name__ == '__main__': if __name__ == '__main__':
runtests() runtests()

View File

@ -1,10 +1,9 @@
#!/usr/bin/env python #!/usr/bin/env python
from setuptools import setup, find_packages
from os import path
import codecs import codecs
import os
import re import re
import sys from os import path
from setuptools import find_packages, setup
def read(*parts): def read(*parts):
@ -47,18 +46,14 @@ setup(
'License :: OSI Approved :: BSD License', 'License :: OSI Approved :: BSD License',
'Operating System :: OS Independent', 'Operating System :: OS Independent',
'Programming Language :: Python', 'Programming Language :: Python',
'Programming Language :: Python :: 2.6',
'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 2.7',
'Programming Language :: Python :: 3.2',
'Programming Language :: Python :: 3.3',
'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.4',
'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.5',
'Programming Language :: Python :: 3.6',
'Framework :: Django', '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.8',
'Framework :: Django :: 1.10',
'Framework :: Django :: 1.11',
'Topic :: Software Development :: Libraries :: Python Modules', 'Topic :: Software Development :: Libraries :: Python Modules',
] ]
) )

26
tox.ini
View File

@ -1,23 +1,17 @@
[tox] [tox]
envlist = envlist =
py26-django{14,15,16}, py27-django{18,110,111}
py27-django{14,15,16,17,18,19,110,111}, py34-django{18,110,111}
py32-django{15,16,17,18}, py35-django{18,110,111,dev}
py33-django{15,16,17,18}, py36-django{111,dev}
py34-django{15,16,17,18,19,110,111}, docs
py35-django{18,19,110,111,dev},
py36-django{111,dev},
docs,
[testenv] [testenv]
setenv =
PYTHONWARNINGS = all
deps = deps =
coverage == 3.6 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 django18: Django >= 1.8, < 1.9
django19: Django >= 1.9, < 1.10
django110: Django >= 1.10, < 1.11 django110: Django >= 1.10, < 1.11
django111: Django >= 1.11, < 1.12 django111: Django >= 1.11, < 1.12
djangodev: https://github.com/django/django/tarball/master djangodev: https://github.com/django/django/tarball/master
@ -25,6 +19,10 @@ commands=
coverage run --source polymorphic runtests.py coverage run --source polymorphic runtests.py
[testenv:docs] [testenv:docs]
deps=Sphinx deps =
Sphinx
sphinx_rtd_theme
Django
django-extra-views
changedir = docs changedir = docs
commands = sphinx-build -W -b html -d {envtmpdir}/doctrees . {envtmpdir}/html commands = sphinx-build -W -b html -d {envtmpdir}/doctrees . {envtmpdir}/html