diff -Nru django-polymorphic-0.5.4/AUTHORS.rst django-polymorphic-0.6/AUTHORS.rst --- django-polymorphic-0.5.4/AUTHORS.rst 2014-04-09 10:11:29.000000000 +0000 +++ django-polymorphic-0.6/AUTHORS.rst 2014-10-14 14:30:18.000000000 +0000 @@ -8,14 +8,28 @@ Contributors ============= +* Abel Daniel * Adam Wentz * Andrew Ingram (contributed setup.py) -* Adam Wentz * Ben Konrath +* Bertrand Bordage +* Chad Shryock * Charles Leifer (python 2.4 compatibility) +* David Sanders +* Evan Borgstrom +* Gavin Wahl * Germán M. Bravo +* Jacob Rief * Jedediah Smith (proxy models support) +* John Furr +* Jonas Obrist +* Julian Wachholz +* Kevin Armenat +* Marius Lueck * Martin Brochhaus +* Tomas Peterka +* Vail Gold + Former authors / maintainers diff -Nru django-polymorphic-0.5.4/debian/changelog django-polymorphic-0.6/debian/changelog --- django-polymorphic-0.5.4/debian/changelog 2014-04-18 08:17:23.000000000 +0000 +++ django-polymorphic-0.6/debian/changelog 2014-10-20 09:44:39.000000000 +0000 @@ -1,3 +1,24 @@ +django-polymorphic (0.6-1) unstable; urgency=low + + * New upstream release. + * Drop django-1.7.patch as it is now applied upstream. + * Bump Standards-Version to 3.9.6. + + -- Michael Fladischer Mon, 20 Oct 2014 11:44:11 +0200 + +django-polymorphic (0.5.6-1) unstable; urgency=medium + + * New upstream release. + * Add django-1.7.patch to fix the test-suite with Django 1.7 (Closes: + #755602). + * Add dh-python to Build-Depends. + * Switch buildsystem to pybuild. + * Remove superfluous install file. + * Add Python3 support. + * Update upstream information in d/copyright. + + -- Michael Fladischer Fri, 19 Sep 2014 14:16:19 +0200 + django-polymorphic (0.5.4-1) unstable; urgency=low * Initial release (Closes: #745122). diff -Nru django-polymorphic-0.5.4/debian/control django-polymorphic-0.6/debian/control --- django-polymorphic-0.5.4/debian/control 2014-04-18 08:17:23.000000000 +0000 +++ django-polymorphic-0.6/debian/control 2014-10-20 09:44:39.000000000 +0000 @@ -4,12 +4,17 @@ Maintainer: Debian Python Modules Team Uploaders: Michael Fladischer , Build-Depends: debhelper (>= 9), + dh-python, python-all, python-django (>= 1.4), python-setuptools, - python-sphinx (>= 1.0.7+dfsg) -Standards-Version: 3.9.5 + python-sphinx (>= 1.0.7+dfsg), + python3-all, + python3-django, + python3-setuptools, +Standards-Version: 3.9.6 X-Python-Version: >= 2.6 +X-Python3-Version: >= 3.2 Homepage: https://github.com/chrisglass/django_polymorphic Vcs-Svn: svn://anonscm.debian.org/python-modules/packages/django-polymorphic/trunk/ Vcs-Browser: http://anonscm.debian.org/viewvc/python-modules/packages/django-polymorphic/trunk/ @@ -36,10 +41,35 @@ * Uses the minimum amount of queries needed to fetch the inherited models. * Disabling polymorphic behavior when needed. +Package: python3-django-polymorphic +Architecture: all +Depends: python3-django, + ${misc:Depends}, + ${python:Depends} +Suggests: python-django-polymorphic-doc +Description: Seamless Polymorphic Inheritance for Django Models (Python3 version) + Django-polymorphic simplifies using inherited models in Django projects. When a + query is made at the base model, the inherited model classes are returned. + . + Features: + * Full admin integration. + * ORM integration: + + Support for ForeignKey, ManyToManyField, OneToOneField descriptors. + + Support for proxy models. + + Filtering/ordering of inherited models (ArtProject___artist). + + Filtering model types: instance_of(...) and not_instance_of(...) + + Combining querysets of different models (qs3 = qs1 | qs2) + + Support for custom user-defined managers. + * Uses the minimum amount of queries needed to fetch the inherited models. + * Disabling polymorphic behavior when needed. + . + This package contains the Python 3 version of the library. + Package: python-django-polymorphic-doc Section: doc Architecture: all -Depends: ${misc:Depends}, ${sphinxdoc:Depends} +Depends: ${misc:Depends}, + ${sphinxdoc:Depends} Description: Seamless Polymorphic Inheritance for Django Models (Documentation) Django-polymorphic simplifies using inherited models in Django projects. When a query is made at the base model, the inherited model classes are returned. diff -Nru django-polymorphic-0.5.4/debian/copyright django-polymorphic-0.6/debian/copyright --- django-polymorphic-0.5.4/debian/copyright 2014-04-18 08:46:29.000000000 +0000 +++ django-polymorphic-0.6/debian/copyright 2014-08-31 21:18:08.000000000 +0000 @@ -1,10 +1,12 @@ Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ Upstream-Name: django-polymorphic -Upstream-Contact: Bert Constantin +Upstream-Contact: Christopher Glass Source: https://github.com/chrisglass/django_polymorphic Files: * -Copyright: 2009-1010, Bert Constantin +Copyright: 2009-2013, Bert Constantin + 2009-2014, Chris Glass + 2009-2014, Diederik van der Boor License: BSD-3-clause Files: debian/* diff -Nru django-polymorphic-0.5.4/debian/patches/django-1.7.patch django-polymorphic-0.6/debian/patches/django-1.7.patch --- django-polymorphic-0.5.4/debian/patches/django-1.7.patch 1970-01-01 00:00:00.000000000 +0000 +++ django-polymorphic-0.6/debian/patches/django-1.7.patch 2014-09-19 20:16:35.000000000 +0000 @@ -0,0 +1,334 @@ +Description: Fix tests for Django-1.7 + Taken from upstream to fix the testsuite for Django 1.7. +Author: Chad Shryock +Last-Update: 2014-09-19 +Origin: https://github.com/chrisglass/django_polymorphic/pull/102 +Bug: https://bugs.debian.org/755602 + +--- a/polymorphic/admin.py ++++ b/polymorphic/admin.py +@@ -220,8 +220,17 @@ + + + def queryset(self, request): ++ return self.get_queryset(request) ++ ++ ++ def get_queryset(self, request): + # optimize the list display. +- qs = super(PolymorphicParentModelAdmin, self).queryset(request) ++ parent_self = super(PolymorphicParentModelAdmin, self) ++ if hasattr(parent_self, 'get_queryset'): ++ qs = parent_self.get_queryset(request) ++ else: ++ qs = parent_self.queryset(request) ++ + if not self.polymorphic_list: + qs = qs.non_polymorphic() + return qs +@@ -262,7 +271,8 @@ + Expose the custom URLs for the subclasses and the URL resolver. + """ + urls = super(PolymorphicParentModelAdmin, self).get_urls() +- info = self.model._meta.app_label, self.model._meta.module_name ++ meta = self.model._meta ++ info = meta.app_label, getattr(meta, 'model_name', meta.module_name) + + # Patch the change URL so it's not a big catch-all; allowing all custom URLs to be added to the end. + # The url needs to be recreated, patching url.regex is not an option Django 1.4's LocaleRegexProvider changed it. +--- a/polymorphic/manager.py ++++ b/polymorphic/manager.py +@@ -30,9 +30,13 @@ + + super(PolymorphicManager, self).__init__(*args, **kwrags) + +- def get_query_set(self): ++ def get_queryset(self): + return self.queryset_class(self.model, using=self._db) + ++ def get_query_set(self): ++ return self.get_queryset() ++ ++ + # Proxy all unknown method calls to the queryset, so that its members are + # directly accessible as PolymorphicModel.objects.* + # The advantage of this method is that not yet known member functions of derived querysets will be proxied as well. +--- a/runtests.py ++++ b/runtests.py +@@ -18,6 +18,10 @@ + # Detect location and available modules + module_root = dirname(realpath(__file__)) + ++test_runner = 'django.test.runner.DiscoverRunner' ++if django.VERSION[:2] < (1, 6): ++ test_runner = 'django.test.simple.DjangoTestSuiteRunner' ++ + # Inline settings file + settings.configure( + DEBUG = False, # will be False anyway by DjangoTestRunner. +@@ -43,9 +47,15 @@ + 'polymorphic', + ), + SITE_ID = 3, ++ TEST_RUNNER = test_runner, ++ MIDDLEWARE_CLASSES = (), + ) + +-call_command('syncdb', verbosity=1, interactive=False) ++if django.VERSION[:2] > (1, 6): ++ django.setup() ++ call_command('migrate', verbosity=1, interactive=False) ++else: ++ call_command('syncdb', verbosity=1, interactive=False) + + + # ---- app start +--- a/polymorphic/tests.py ++++ b/polymorphic/tests.py +@@ -5,6 +5,7 @@ + from __future__ import print_function + import uuid + import re ++import django + from django.db.models.query import QuerySet + + from django.test import TestCase +@@ -66,21 +67,45 @@ + class ModelY(Base): + field_y = models.CharField(max_length=10) + +-class Enhance_Plain(models.Model): +- field_p = models.CharField(max_length=10) +-class Enhance_Base(ShowFieldTypeAndContent, PolymorphicModel): +- field_b = models.CharField(max_length=10) +-class Enhance_Inherit(Enhance_Base, Enhance_Plain): +- field_i = models.CharField(max_length=10) +- +-class DiamondBase(models.Model): +- field_b = models.CharField(max_length=10) +-class DiamondX(DiamondBase): +- field_x = models.CharField(max_length=10) +-class DiamondY(DiamondBase): +- field_y = models.CharField(max_length=10) +-class DiamondXY(DiamondX, DiamondY): +- pass ++if django.VERSION[:2] > (1, 6): ++ class Enhance_Plain(models.Model): ++ field_p = models.CharField(max_length=10) ++ class Enhance_Base(ShowFieldTypeAndContent, PolymorphicModel): ++ base_id = models.AutoField(primary_key=True) ++ field_b = models.CharField(max_length=10) ++ class Enhance_Inherit(Enhance_Base, Enhance_Plain): ++ field_i = models.CharField(max_length=10) ++ ++ class DiamondBase(models.Model): ++ field_b = models.CharField(max_length=10) ++ class DiamondX(DiamondBase): ++ x_id = models.AutoField(primary_key=True) ++ field_x = models.CharField(max_length=10) ++ class DiamondY(DiamondBase): ++ y_id = models.AutoField(primary_key=True) ++ field_y = models.CharField(max_length=10) ++ class DiamondXY(DiamondBase): ++ xy_id = models.AutoField(primary_key=True) ++ field_x = models.CharField(max_length=10) ++ field_y = models.CharField(max_length=10) ++else: ++ class Enhance_Plain(models.Model): ++ field_p = models.CharField(max_length=10) ++ class Enhance_Base(ShowFieldTypeAndContent, PolymorphicModel): ++ field_b = models.CharField(max_length=10) ++ class Enhance_Inherit(Enhance_Base, Enhance_Plain): ++ field_i = models.CharField(max_length=10) ++ ++ class DiamondBase(models.Model): ++ field_b = models.CharField(max_length=10) ++ class DiamondX(DiamondBase): ++ x_id = models.AutoField(primary_key=True) ++ field_x = models.CharField(max_length=10) ++ class DiamondY(DiamondBase): ++ y_id = models.AutoField(primary_key=True) ++ field_y = models.CharField(max_length=10) ++ class DiamondXY(DiamondX, DiamondY): ++ xy_id = models.AutoField(primary_key=True) + + class RelationBase(ShowFieldTypeAndContent, PolymorphicModel): + field_base = models.CharField(max_length=10) +@@ -110,6 +135,9 @@ + class MyManager(PolymorphicManager): + queryset_class = MyManagerQuerySet + ++ def get_queryset(self): ++ return super(MyManager, self).get_queryset().order_by('-field1') ++ + def get_query_set(self): + return super(MyManager, self).get_query_set().order_by('-field1') + +@@ -143,9 +171,12 @@ + def my_queryset_foo(self): + return self.get_query_set().my_queryset_foo() + +- def get_query_set(self): ++ def get_queryset(self): + return PlainMyManagerQuerySet(self.model, using=self._db) + ++ def get_query_set(self): ++ return self.get_queryset() ++ + class PlainParentModelWithManager(models.Model): + pass + +@@ -254,6 +285,10 @@ + The test suite + """ + def test_diamond_inheritance(self): ++ if django.VERSION[:2] > (1, 6): ++ print('') ++ print("# Django 1.7 doesn't allow multiple inheritance when two id fields exist. https://docs.djangoproject.com/en/dev/topics/db/models/#multiple-inheritance") ++ + # Django diamond problem + # https://code.djangoproject.com/ticket/10808 + o1 = DiamondXY.objects.create(field_b='b', field_x='x', field_y='y') +@@ -616,11 +651,14 @@ + Enhance_Inherit.objects.create(field_b='b-inherit', field_p='p', field_i='i') + + qs = Enhance_Base.objects.all() +- self.assertEqual(repr(qs[0]), '') +- self.assertEqual(repr(qs[1]), '') ++ if django.VERSION[:2] > (1, 6): ++ self.assertEqual(repr(qs[0]), '') ++ self.assertEqual(repr(qs[1]), '') ++ else: ++ self.assertEqual(repr(qs[0]), '') ++ self.assertEqual(repr(qs[1]), '') + self.assertEqual(len(qs), 2) + +- + def test_relation_base(self): + # ForeignKey, ManyToManyField + obase = RelationBase.objects.create(field_base='base') +--- a/docs/managers.rst ++++ b/docs/managers.rst +@@ -13,12 +13,12 @@ + from polymorphic import PolymorphicModel, PolymorphicManager + + class TimeOrderedManager(PolymorphicManager): +- def get_query_set(self): +- qs = super(TimeOrderedManager,self).get_query_set() ++ def get_queryset(self): ++ qs = super(TimeOrderedManager,self).get_queryset() + return qs.order_by('-start_date') # order the queryset + + def most_recent(self): +- qs = self.get_query_set() # get my ordered queryset ++ qs = self.get_queryset() # get my ordered queryset + return qs[:10] # limit => get ten most recent entries + + class Project(PolymorphicModel): +@@ -31,6 +31,8 @@ + 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 + ------------------- + +@@ -42,12 +44,12 @@ + from polymorphic import PolymorphicModel, PolymorphicManager + + class TimeOrderedManager(PolymorphicManager): +- def get_query_set(self): +- qs = super(TimeOrderedManager,self).get_query_set() ++ def get_queryset(self): ++ qs = super(TimeOrderedManager,self).get_queryset() + return qs.order_by('-start_date') # order the queryset + + def most_recent(self): +- qs = self.get_query_set() # get my ordered queryset ++ qs = self.get_queryset() # get my ordered queryset + return qs[:10] # limit => get ten most recent entries + + class Project(PolymorphicModel): +@@ -65,6 +67,8 @@ + 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 + ----------------------------- + +--- a/tox.ini ++++ b/tox.ini +@@ -7,12 +7,15 @@ + py27-django14, + py27-django15, + py27-django16, ++ py27-django17, + + py32-django15, + py32-django16, ++ py32-django17, + + py33-django15, + py33-django16, ++ py33-django17, + + py33-django-dev, + docs, +@@ -53,6 +56,11 @@ + deps= + django==1.6 + ++[testenv:py27-django17] ++basepython=python2.7 ++deps= ++ django==1.7 ++ + [testenv:py32-django15] + basepython=python3.2 + deps= +@@ -63,6 +71,11 @@ + deps= + django==1.6 + ++[testenv:py32-django17] ++basepython=python3.2 ++deps= ++ django==1.7 ++ + [testenv:py33-django15] + basepython=python3.3 + deps= +@@ -73,6 +86,11 @@ + deps= + django==1.6 + ++[testenv:py33-django17] ++basepython=python3.3 ++deps= ++ django==1.7 ++ + [testenv:py33-django-dev] + basepython=python3.3 + deps= +--- a/.travis.yml ++++ b/.travis.yml +@@ -8,6 +8,7 @@ + - DJANGO=django==1.4.5 + - DJANGO=django==1.5 + - DJANGO=django==1.6 ++ - DJANGO=django==1.7 + #- DJANGO=https://github.com/django/django/archive/stable/1.6.x.zip + + matrix: +@@ -16,6 +17,8 @@ + env: DJANGO=django==1.4.5 + - python: "3.2" + env: DJANGO=django==1.4.5 ++ - python: "2.6" ++ env: DJANGO=django==1.7 + + install: + - pip install $DJANGO coverage==3.6 diff -Nru django-polymorphic-0.5.4/debian/python-django-polymorphic.install django-polymorphic-0.6/debian/python-django-polymorphic.install --- django-polymorphic-0.5.4/debian/python-django-polymorphic.install 2014-04-18 08:17:23.000000000 +0000 +++ django-polymorphic-0.6/debian/python-django-polymorphic.install 1970-01-01 00:00:00.000000000 +0000 @@ -1 +0,0 @@ -usr/ diff -Nru django-polymorphic-0.5.4/debian/rules django-polymorphic-0.6/debian/rules --- django-polymorphic-0.5.4/debian/rules 2014-04-18 08:17:23.000000000 +0000 +++ django-polymorphic-0.6/debian/rules 2014-08-31 21:11:42.000000000 +0000 @@ -1,16 +1,21 @@ #!/usr/bin/make -f +# Uncomment this to turn on verbose mode. +#export DH_VERBOSE=1 + +export PYBUILD_NAME=django-polymorphic + %: - dh $@ --with python2,sphinxdoc + dh $@ --with python2,python3,sphinxdoc --buildsystem=pybuild override_dh_auto_build: - PYTHONPATH=. sphinx-build -b html -d docs/_build/.doctrees -N docs docs/_build/html dh_auto_build + PYTHONPATH=. sphinx-build -b html -d docs/_build/.doctrees -N docs docs/_build/html override_dh_auto_test: ifeq (,$(filter nocheck,$(DEB_BUILD_OPTIONS))) set -e; \ - for python in $(shell pyversions -r); do \ + for python in $(shell pyversions -r) $(shell py3versions -r); do \ PYTHONPATH="." $$python runtests.py; \ done endif diff -Nru django-polymorphic-0.5.4/docs/admin.rst django-polymorphic-0.6/docs/admin.rst --- django-polymorphic-0.5.4/docs/admin.rst 2014-04-09 10:11:29.000000000 +0000 +++ django-polymorphic-0.6/docs/admin.rst 2014-10-14 14:30:18.000000000 +0000 @@ -53,6 +53,12 @@ automatically detect the additional fields that the child model has, display those in a separate fieldset. +Polymorphic Inlines +------------------- + +To add a polymorphic child model as an Inline for another model, add a field to the inline's readonly_fields list formed by the lowercased name of the polymorphic parent model with the string "_ptr" appended to it. Otherwise, trying to save that model in the admin will raise an AttributeError with the message "can't set attribute". + + .. _admin-example: Example @@ -64,7 +70,7 @@ from django.contrib import admin from polymorphic.admin import PolymorphicParentModelAdmin, PolymorphicChildModelAdmin - from .models import ModelA, ModelB, ModelC + from .models import ModelA, ModelB, ModelC, StandardModel class ModelAChildAdmin(PolymorphicChildModelAdmin): @@ -78,9 +84,11 @@ ... ) + class ModelBAdmin(ModelAChildAdmin): # define custom features here + class ModelCAdmin(ModelBAdmin): # define custom features here @@ -93,5 +101,17 @@ (ModelC, ModelCAdmin), ) + + class ModelBInline(admin.StackedInline): + model = ModelB + fk_name = 'modelb' + readonly_fields = ['modela_ptr'] + + + class StandardModelAdmin(admin.ModelAdmin): + inlines = [ModelBInline] + + # Only the parent needs to be registered: admin.site.register(ModelA, ModelAParentAdmin) + admin.site.register(StandardModel, StandardModelAdmin) diff -Nru django-polymorphic-0.5.4/docs/changelog.rst django-polymorphic-0.6/docs/changelog.rst --- django-polymorphic-0.5.4/docs/changelog.rst 2014-04-09 10:11:29.000000000 +0000 +++ django-polymorphic-0.6/docs/changelog.rst 2014-10-14 14:30:18.000000000 +0000 @@ -1,6 +1,28 @@ Changelog ========== +Version 0.6 (2014-10-14) +------------------------ + +* Added Django 1.7 support. +* Added permission check for all child types. +* **BACKWARDS INCOMPATIBILITY:** the ``get_child_type_choices()`` method receives 2 arguments now (request, action). + If you have overwritten this method in your code, make sure the method signature is updated accordingly. + + +Version 0.5.6 (2014-07-21) +-------------------------- + +* Added ``pk_regex`` to the ``PolymorphicParentModelAdmin`` to support non-integer primary keys. +* Fixed passing ``?ct_id=`` to the add view for Django 1.6 (fixes compatibility with django-parler_). + + +Version 0.5.5 (2014-04-29) +-------------------------- + +* Fixed ``get_real_instance_class()`` for proxy models (broke in 0.5.4). + + Version 0.5.4 (2014-04-09) -------------------------- @@ -92,3 +114,4 @@ For a detailed list of it's changes, see the :doc:`archived changelog `. .. _Grappelli: http://grappelliproject.com/ +.. _django-parler: https://github.com/edoburu/django-parler diff -Nru django-polymorphic-0.5.4/docs/conf.py django-polymorphic-0.6/docs/conf.py --- django-polymorphic-0.5.4/docs/conf.py 2014-04-09 10:11:29.000000000 +0000 +++ django-polymorphic-0.6/docs/conf.py 2014-10-14 14:30:18.000000000 +0000 @@ -54,9 +54,9 @@ # built documents. # # The short X.Y version. -version = '0.5.4' +version = '0.6' # The full version, including alpha/beta/rc tags. -release = '0.5.4' +release = '0.6' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff -Nru django-polymorphic-0.5.4/docs/managers.rst django-polymorphic-0.6/docs/managers.rst --- django-polymorphic-0.5.4/docs/managers.rst 2014-04-09 10:11:29.000000000 +0000 +++ django-polymorphic-0.6/docs/managers.rst 2014-10-14 14:30:18.000000000 +0000 @@ -13,12 +13,12 @@ from polymorphic import PolymorphicModel, PolymorphicManager class TimeOrderedManager(PolymorphicManager): - def get_query_set(self): - qs = super(TimeOrderedManager,self).get_query_set() + def get_queryset(self): + qs = super(TimeOrderedManager,self).get_queryset() return qs.order_by('-start_date') # order the queryset def most_recent(self): - qs = self.get_query_set() # get my ordered queryset + qs = self.get_queryset() # get my ordered queryset return qs[:10] # limit => get ten most recent entries class Project(PolymorphicModel): @@ -31,6 +31,8 @@ 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 ------------------- @@ -42,12 +44,12 @@ from polymorphic import PolymorphicModel, PolymorphicManager class TimeOrderedManager(PolymorphicManager): - def get_query_set(self): - qs = super(TimeOrderedManager,self).get_query_set() + def get_queryset(self): + qs = super(TimeOrderedManager,self).get_queryset() return qs.order_by('-start_date') # order the queryset def most_recent(self): - qs = self.get_query_set() # get my ordered queryset + qs = self.get_queryset() # get my ordered queryset return qs[:10] # limit => get ten most recent entries class Project(PolymorphicModel): @@ -65,6 +67,8 @@ 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 -Nru django-polymorphic-0.5.4/docs/performance.rst django-polymorphic-0.6/docs/performance.rst --- django-polymorphic-0.5.4/docs/performance.rst 2014-04-09 10:11:29.000000000 +0000 +++ django-polymorphic-0.6/docs/performance.rst 2014-10-14 14:30:18.000000000 +0000 @@ -13,7 +13,7 @@ Compared to these solutions, *django-polymorphic* has the advantage that it only needs 1 SQL query *per object type*, and not *per object*. -The current implementation is does not use any custom SQL or Django DB layer +The current implementation does not use any custom SQL or Django DB layer internals - it is purely based on the standard Django ORM. Specifically, the query:: result_objects = list( ModelA.objects.filter(...) ) diff -Nru django-polymorphic-0.5.4/example/example/settings.py django-polymorphic-0.6/example/example/settings.py --- django-polymorphic-0.5.4/example/example/settings.py 2014-04-09 10:11:29.000000000 +0000 +++ django-polymorphic-0.6/example/example/settings.py 2014-10-14 14:30:18.000000000 +0000 @@ -1,3 +1,4 @@ +import django import os DEBUG = True @@ -74,6 +75,9 @@ 'pexp', # this Django app is for testing and experimentation; not needed otherwise ) +if django.VERSION >= (1,7): + TEST_RUNNER = 'django.test.runner.DiscoverRunner' # silence system checks + # Logging configuration LOGGING = { 'version': 1, diff -Nru django-polymorphic-0.5.4/.gitignore django-polymorphic-0.6/.gitignore --- django-polymorphic-0.5.4/.gitignore 2014-04-09 10:11:29.000000000 +0000 +++ django-polymorphic-0.6/.gitignore 2014-10-14 14:30:18.000000000 +0000 @@ -11,6 +11,7 @@ .idea/workspace.xml .tox/ .DS_Store +build/ dist/ docs/_build/ htmlcov/ diff -Nru django-polymorphic-0.5.4/polymorphic/admin.py django-polymorphic-0.6/polymorphic/admin.py --- django-polymorphic-0.5.4/polymorphic/admin.py 2014-04-09 10:11:29.000000000 +0000 +++ django-polymorphic-0.6/polymorphic/admin.py 2014-10-14 14:30:18.000000000 +0000 @@ -15,9 +15,19 @@ from django.template.context import RequestContext from django.utils import six from django.utils.encoding import force_text +from django.utils.http import urlencode from django.utils.safestring import mark_safe from django.utils.translation import ugettext_lazy as _ + +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 + + __all__ = ( 'PolymorphicModelChoiceForm', 'PolymorphicParentModelAdmin', 'PolymorphicChildModelAdmin', 'PolymorphicChildModelFilter' @@ -38,7 +48,7 @@ The default form for the ``add_type_form``. Can be overwritten and replaced. """ #: Define the label for the radiofield - type_label = _("Type") + type_label = _('Type') ct_id = forms.ChoiceField(label=type_label, widget=AdminRadioSelect(attrs={'class': 'radiolist'})) @@ -53,11 +63,11 @@ An admin list filter for the PolymorphicParentModelAdmin which enables filtering by its child models. """ - title = _('Content type') + title = _('Type') parameter_name = 'polymorphic_ctype' def lookups(self, request, model_admin): - return model_admin.get_child_type_choices() + return model_admin.get_child_type_choices(request, 'change') def queryset(self, request, queryset): try: @@ -103,6 +113,11 @@ add_type_template = None add_type_form = PolymorphicModelChoiceForm + #: 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+)' + def __init__(self, model, admin_site, *args, **kwargs): super(PolymorphicParentModelAdmin, self).__init__(model, admin_site, *args, **kwargs) @@ -163,12 +178,17 @@ return self.child_models - def get_child_type_choices(self): + def get_child_type_choices(self, request, action): """ - Return a list of polymorphic types which can be added. + Return a list of polymorphic types for which the user has the permission to perform the given action. """ choices = [] for model, _ in self.get_child_models(): + perm_function_name = 'has_{0}_permission'.format(action) + model_admin = self._get_real_admin_by_model(model) + perm_function = getattr(model_admin, perm_function_name) + if not perm_function(request): + continue ct = ContentType.objects.get_for_model(model, for_concrete_model=False) choices.append((ct.id, model._meta.verbose_name)) return choices @@ -206,8 +226,16 @@ raise ChildAdminNotRegistered("No child admin site was registered for a '{0}' model.".format(model_class)) - def queryset(self, request): + def get_queryset(self, request): # optimize the list display. + qs = super(PolymorphicParentModelAdmin, self).get_queryset(request) + if not self.polymorphic_list: + 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() @@ -222,6 +250,12 @@ return self.add_type_view(request) else: real_admin = self._get_real_admin_by_ct(ct_id) + # rebuild form_url, otherwise libraries below will override it. + form_url = add_preserved_filters({ + 'preserved_filters': urlencode({'ct_id': ct_id}), + 'opts': self.model._meta}, + form_url + ) return real_admin.add_view(request, form_url, extra_context) @@ -243,11 +277,12 @@ Expose the custom URLs for the subclasses and the URL resolver. """ urls = super(PolymorphicParentModelAdmin, self).get_urls() - info = self.model._meta.app_label, self.model._meta.module_name + meta = self.model._meta + info = meta.app_label, getattr(meta, 'model_name', meta.module_name) # Patch the change URL so it's not a big catch-all; allowing all custom URLs to be added to the end. # The url needs to be recreated, patching url.regex is not an option Django 1.4's LocaleRegexProvider changed it. - new_change_url = url(r'^(\d+)/$', self.admin_site.admin_view(self.change_view), name='{0}_{1}_change'.format(*info)) + new_change_url = url(r'^{0}/$'.format(self.pk_regex), self.admin_site.admin_view(self.change_view), name='{0}_{1}_change'.format(*info)) for i, oldurl in enumerate(urls): if oldurl.name == new_change_url.name: urls[i] = new_change_url @@ -306,7 +341,7 @@ if request.META['QUERY_STRING']: extra_qs = '&' + request.META['QUERY_STRING'] - choices = self.get_child_type_choices() + choices = self.get_child_type_choices(request, 'add') if len(choices) == 1: return HttpResponseRedirect('?ct_id={0}{1}'.format(choices[0][0], extra_qs)) diff -Nru django-polymorphic-0.5.4/polymorphic/manager.py django-polymorphic-0.6/polymorphic/manager.py --- django-polymorphic-0.5.4/polymorphic/manager.py 2014-04-09 10:11:29.000000000 +0000 +++ django-polymorphic-0.6/polymorphic/manager.py 2014-10-14 14:30:18.000000000 +0000 @@ -4,6 +4,7 @@ """ from __future__ import unicode_literals import warnings +import django from django.db import models from polymorphic.query import PolymorphicQuerySet @@ -30,9 +31,13 @@ super(PolymorphicManager, self).__init__(*args, **kwrags) - def get_query_set(self): + def get_queryset(self): return self.queryset_class(self.model, using=self._db) + # For Django 1.5 + if django.VERSION < (1, 7): + get_query_set = get_queryset + # Proxy all unknown method calls to the queryset, so that its members are # directly accessible as PolymorphicModel.objects.* # The advantage of this method is that not yet known member functions of derived querysets will be proxied as well. @@ -40,7 +45,7 @@ def __getattr__(self, name): if name.startswith('__'): return super(PolymorphicManager, self).__getattr__(self, name) - return getattr(self.get_query_set(), name) + return getattr(self.get_queryset(), name) def __unicode__(self): return '%s (PolymorphicManager) using %s' % (self.__class__.__name__, self.queryset_class.__name__) diff -Nru django-polymorphic-0.5.4/polymorphic/polymorphic_model.py django-polymorphic-0.6/polymorphic/polymorphic_model.py --- django-polymorphic-0.5.4/polymorphic/polymorphic_model.py 2014-04-09 10:11:29.000000000 +0000 +++ django-polymorphic-0.6/polymorphic/polymorphic_model.py 2014-10-14 14:30:18.000000000 +0000 @@ -99,7 +99,7 @@ """ # the following line would be the easiest way to do this, but it produces sql queries # return self.polymorphic_ctype.model_class() - # so we use the following version, which uses the CopntentType 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; # when the content type record still exists but no longer refers to an existing model. try: @@ -110,7 +110,9 @@ # Protect against bad imports (dumpdata without --natural) or other # issues missing with the ContentType models. - if model is not None and not issubclass(model, self.__class__): + if model is not None \ + and not issubclass(model, self.__class__) \ + and not issubclass(model, self.__class__._meta.proxy_for_model): raise RuntimeError("ContentType {0} for {1} #{2} does not point to a subclass!".format( self.polymorphic_ctype_id, model, self.pk, )) diff -Nru django-polymorphic-0.5.4/polymorphic/tests.py django-polymorphic-0.6/polymorphic/tests.py --- django-polymorphic-0.5.4/polymorphic/tests.py 2014-04-09 10:11:29.000000000 +0000 +++ django-polymorphic-0.6/polymorphic/tests.py 2014-10-14 14:30:18.000000000 +0000 @@ -5,6 +5,7 @@ from __future__ import print_function import uuid import re +import django from django.db.models.query import QuerySet from django.test import TestCase @@ -66,21 +67,45 @@ class ModelY(Base): field_y = models.CharField(max_length=10) -class Enhance_Plain(models.Model): - field_p = models.CharField(max_length=10) -class Enhance_Base(ShowFieldTypeAndContent, PolymorphicModel): - field_b = models.CharField(max_length=10) -class Enhance_Inherit(Enhance_Base, Enhance_Plain): - field_i = models.CharField(max_length=10) - -class DiamondBase(models.Model): - field_b = models.CharField(max_length=10) -class DiamondX(DiamondBase): - field_x = models.CharField(max_length=10) -class DiamondY(DiamondBase): - field_y = models.CharField(max_length=10) -class DiamondXY(DiamondX, DiamondY): - pass +if django.VERSION[:2] > (1, 6): + class Enhance_Plain(models.Model): + field_p = models.CharField(max_length=10) + class Enhance_Base(ShowFieldTypeAndContent, PolymorphicModel): + base_id = models.AutoField(primary_key=True) + field_b = models.CharField(max_length=10) + class Enhance_Inherit(Enhance_Base, Enhance_Plain): + field_i = models.CharField(max_length=10) + + class DiamondBase(models.Model): + field_b = models.CharField(max_length=10) + class DiamondX(DiamondBase): + x_id = models.AutoField(primary_key=True) + field_x = models.CharField(max_length=10) + class DiamondY(DiamondBase): + y_id = models.AutoField(primary_key=True) + field_y = models.CharField(max_length=10) + class DiamondXY(DiamondBase): + xy_id = models.AutoField(primary_key=True) + field_x = models.CharField(max_length=10) + field_y = models.CharField(max_length=10) +else: + class Enhance_Plain(models.Model): + field_p = models.CharField(max_length=10) + class Enhance_Base(ShowFieldTypeAndContent, PolymorphicModel): + field_b = models.CharField(max_length=10) + class Enhance_Inherit(Enhance_Base, Enhance_Plain): + field_i = models.CharField(max_length=10) + + class DiamondBase(models.Model): + field_b = models.CharField(max_length=10) + class DiamondX(DiamondBase): + x_id = models.AutoField(primary_key=True) + field_x = models.CharField(max_length=10) + class DiamondY(DiamondBase): + y_id = models.AutoField(primary_key=True) + field_y = models.CharField(max_length=10) + class DiamondXY(DiamondX, DiamondY): + xy_id = models.AutoField(primary_key=True) class RelationBase(ShowFieldTypeAndContent, PolymorphicModel): field_base = models.CharField(max_length=10) @@ -110,8 +135,11 @@ class MyManager(PolymorphicManager): queryset_class = MyManagerQuerySet - def get_query_set(self): - return super(MyManager, self).get_query_set().order_by('-field1') + def get_queryset(self): + return super(MyManager, self).get_queryset().order_by('-field1') + + # Django <= 1.5 compatibility + get_query_set = get_queryset class ModelWithMyManager(ShowFieldTypeAndContent, Model2A): objects = MyManager() @@ -143,9 +171,12 @@ def my_queryset_foo(self): return self.get_query_set().my_queryset_foo() - def get_query_set(self): + 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 @@ -223,6 +254,9 @@ class Meta: proxy = True +class NonProxyChild(ProxyBase): + name=models.CharField(max_length=10) + # base -> proxy -> real models class ProxiedBase(ShowFieldTypeAndContent, PolymorphicModel): name = models.CharField(max_length=10) @@ -251,6 +285,10 @@ The test suite """ def test_diamond_inheritance(self): + if django.VERSION[:2] > (1, 6): + print('') + print("# Django 1.7 doesn't allow multiple inheritance when two id fields exist. https://docs.djangoproject.com/en/dev/topics/db/models/#multiple-inheritance") + # Django diamond problem # https://code.djangoproject.com/ticket/10808 o1 = DiamondXY.objects.create(field_b='b', field_x='x', field_y='y') @@ -613,11 +651,14 @@ Enhance_Inherit.objects.create(field_b='b-inherit', field_p='p', field_i='i') qs = Enhance_Base.objects.all() - self.assertEqual(repr(qs[0]), '') - self.assertEqual(repr(qs[1]), '') + if django.VERSION[:2] > (1, 6): + self.assertEqual(repr(qs[0]), '') + self.assertEqual(repr(qs[1]), '') + else: + self.assertEqual(repr(qs[0]), '') + self.assertEqual(repr(qs[1]), '') self.assertEqual(len(qs), 2) - def test_relation_base(self): # ForeignKey, ManyToManyField obase = RelationBase.objects.create(field_base='base') @@ -716,6 +757,24 @@ self.assertIsInstance(items[0], ProxyChild) + def test_proxy_get_real_instance_class(self): + """ + The call to ``get_real_instance()`` also checks whether the returned model is of the correct type. + This unit test guards that this check is working properly. For instance, + proxy child models need to be handled separately. + """ + name="Item1" + nonproxychild = NonProxyChild.objects.create(name=name) + + pb = ProxyBase.objects.get(id=1) + self.assertEqual(pb.get_real_instance_class(), NonProxyChild) + self.assertEqual(pb.get_real_instance(), nonproxychild) + self.assertEqual(pb.name, name) + + pbm = ProxyChild.objects.get(id=1) + self.assertEqual(pbm.get_real_instance_class(), NonProxyChild) + self.assertEqual(pbm.get_real_instance(), nonproxychild) + self.assertEqual(pbm.name, name) def test_content_types_for_proxy_models(self): """Checks if ContentType is capable of returning proxy models.""" diff -Nru django-polymorphic-0.5.4/polymorphic/__version__.py django-polymorphic-0.6/polymorphic/__version__.py --- django-polymorphic-0.5.4/polymorphic/__version__.py 2014-04-09 10:11:29.000000000 +0000 +++ django-polymorphic-0.6/polymorphic/__version__.py 2014-10-14 14:30:18.000000000 +0000 @@ -11,4 +11,4 @@ 6. git commit 7. push to github """ -__version__ = "0.5.4" +__version__ = "0.6" diff -Nru django-polymorphic-0.5.4/README.rst django-polymorphic-0.6/README.rst --- django-polymorphic-0.5.4/README.rst 2014-04-09 10:11:29.000000000 +0000 +++ django-polymorphic-0.6/README.rst 2014-10-14 14:30:18.000000000 +0000 @@ -34,7 +34,7 @@ Features -------- -* Full admin integation. +* Full admin integration. * ORM integration: * support for ForeignKey, ManyToManyField, OneToOneField descriptors. diff -Nru django-polymorphic-0.5.4/runtests.py django-polymorphic-0.6/runtests.py --- django-polymorphic-0.5.4/runtests.py 2014-04-09 10:11:29.000000000 +0000 +++ django-polymorphic-0.6/runtests.py 2014-10-14 14:30:18.000000000 +0000 @@ -1,10 +1,13 @@ #!/usr/bin/env python +import django +import os +import sys +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 django.core.management import call_command from os.path import dirname, realpath -import django -import sys -import os # Give feedback on used versions @@ -14,47 +17,39 @@ os.path.dirname(os.path.abspath(django.__file__))) ) +if not settings.configured: + settings.configure( + DEBUG = True, + TEMPLATE_DEBUG = True, + DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.sqlite3', + 'NAME': ':memory:' + } + }, + TEMPLATE_LOADERS = ( + 'django.template.loaders.app_directories.Loader', + ), + TEMPLATE_CONTEXT_PROCESSORS = default_settings.TEMPLATE_CONTEXT_PROCESSORS + ( + 'django.core.context_processors.request', + ), + TEST_RUNNER = 'django.test.runner.DiscoverRunner' if django.VERSION >= (1,7) else 'django.test.simple.DjangoTestSuiteRunner', + INSTALLED_APPS = ( + 'django.contrib.auth', + 'django.contrib.contenttypes', + 'django.contrib.messages', + 'django.contrib.sites', + 'django.contrib.admin', + 'polymorphic', + ), + MIDDLEWARE_CLASSES = (), + SITE_ID = 3, + ) + + +def runtests(): + argv = sys.argv[:1] + ['test', 'polymorphic', '--traceback'] + sys.argv[1:] + execute_from_command_line(argv) -# Detect location and available modules -module_root = dirname(realpath(__file__)) - -# Inline settings file -settings.configure( - DEBUG = False, # will be False anyway by DjangoTestRunner. - TEMPLATE_DEBUG = False, - DATABASES = { - 'default': { - 'ENGINE': 'django.db.backends.sqlite3', - 'NAME': ':memory:' - } - }, - TEMPLATE_LOADERS = ( - 'django.template.loaders.app_directories.Loader', - ), - TEMPLATE_CONTEXT_PROCESSORS = default_settings.TEMPLATE_CONTEXT_PROCESSORS + ( - 'django.core.context_processors.request', - ), - INSTALLED_APPS = ( - 'django.contrib.auth', - 'django.contrib.contenttypes', - 'django.contrib.messages', - 'django.contrib.sites', - 'django.contrib.admin', - 'polymorphic', - ), - SITE_ID = 3, -) - -call_command('syncdb', verbosity=1, interactive=False) - - -# ---- app start -verbosity = 2 if '-v' in sys.argv else 1 - -from django.test.utils import get_runner -TestRunner = get_runner(settings) # DjangoTestSuiteRunner -runner = TestRunner(verbosity=verbosity, interactive=True, failfast=False) -failures = runner.run_tests(['polymorphic']) - -if failures: - sys.exit(bool(failures)) +if __name__ == '__main__': + runtests() diff -Nru django-polymorphic-0.5.4/setup.cfg django-polymorphic-0.6/setup.cfg --- django-polymorphic-0.5.4/setup.cfg 1970-01-01 00:00:00.000000000 +0000 +++ django-polymorphic-0.6/setup.cfg 2014-10-14 14:30:18.000000000 +0000 @@ -0,0 +1,3 @@ +[wheel] +# create "py2.py3-none-any.whl" package +universal = 1 diff -Nru django-polymorphic-0.5.4/tox.ini django-polymorphic-0.6/tox.ini --- django-polymorphic-0.5.4/tox.ini 2014-04-09 10:11:29.000000000 +0000 +++ django-polymorphic-0.6/tox.ini 2014-10-14 14:30:18.000000000 +0000 @@ -7,12 +7,15 @@ py27-django14, py27-django15, py27-django16, + py27-django17, py32-django15, py32-django16, + py32-django17, py33-django15, py33-django16, + py33-django17, py33-django-dev, docs, @@ -53,6 +56,11 @@ deps= django==1.6 +[testenv:py27-django17] +basepython=python2.7 +deps= + django==1.7 + [testenv:py32-django15] basepython=python3.2 deps= @@ -63,6 +71,11 @@ deps= django==1.6 +[testenv:py32-django17] +basepython=python3.2 +deps= + django==1.7 + [testenv:py33-django15] basepython=python3.3 deps= @@ -73,6 +86,11 @@ deps= django==1.6 +[testenv:py33-django17] +basepython=python3.3 +deps= + django==1.7 + [testenv:py33-django-dev] basepython=python3.3 deps= diff -Nru django-polymorphic-0.5.4/.travis.yml django-polymorphic-0.6/.travis.yml --- django-polymorphic-0.5.4/.travis.yml 2014-04-09 10:11:29.000000000 +0000 +++ django-polymorphic-0.6/.travis.yml 2014-10-14 14:30:18.000000000 +0000 @@ -8,6 +8,7 @@ - DJANGO=django==1.4.5 - DJANGO=django==1.5 - DJANGO=django==1.6 + - DJANGO=django==1.7 #- DJANGO=https://github.com/django/django/archive/stable/1.6.x.zip matrix: @@ -16,14 +17,20 @@ env: DJANGO=django==1.4.5 - python: "3.2" env: DJANGO=django==1.4.5 + - python: "2.6" + env: DJANGO=django==1.7 + install: - - "pip install $DJANGO coverage==3.6 --use-mirrors" + - pip install $DJANGO coverage==3.6 + script: - coverage run --source=polymorphic runtests.py - coverage report -m + after_success: - - "pip install coveralls==0.2 --use-mirrors" + - pip install coveralls==0.2 - coveralls + branches: only: - master