Merge remote-tracking branch 'upstream/master' into allow_extra_manager

fix_request_path_info
gilgamezh 2015-12-29 17:49:09 -03:00
commit b17aa58ad9
26 changed files with 567 additions and 335 deletions

View File

@ -202,7 +202,7 @@ Nicely Displaying Polymorphic Querysets
In order to get the output as seen in all examples here, you need to use the In order to get the output as seen in all examples here, you need to use the
:class:`~polymorphic.showfields.ShowFieldType` class mixin:: :class:`~polymorphic.showfields.ShowFieldType` class mixin::
from polymorphic import PolymorphicModel, ShowFieldType from polymorphic.showfields import PolymorphicModel, ShowFieldType
class ModelA(ShowFieldType, PolymorphicModel): class ModelA(ShowFieldType, PolymorphicModel):
field1 = models.CharField(max_length=10) field1 = models.CharField(max_length=10)

View File

@ -1,6 +1,42 @@
Changelog Changelog
========== ==========
Version 0.8.1 (2015-12-29)
--------------------------
* Fixed support for reverse relations for ``relname___field`` when the field starts with an ``_`` character.
Otherwise, the query will be interpreted as subclass lookup (``ClassName___field``).
Version 0.8 (2015-12-28)
------------------------
* Added Django 1.9 compatibility.
* Renamed ``polymorphic.manager`` => ``polymorphic.managers`` for consistentcy.
* **BACKWARDS INCOMPATIBILITY:** The import paths have changed to support Django 1.9.
Instead of ``from polymorphic import X``,
you'll have to import from the proper package. For example:
.. code-block:: python
polymorphic.models import PolymorphicModel
polymorphic.managers import PolymorphicManager, PolymorphicQuerySet
polymorphic.showfields import ShowFieldContent, ShowFieldType, ShowFieldTypeAndContent
* **BACKWARDS INCOMPATIBILITY:** Removed ``__version__.py`` in favor of a standard ``__version__`` in ``polymorphic/__init__.py``.
* **BACKWARDS INCOMPATIBILITY:** Removed automatic proxying of method calls to the queryset class.
Use the standard Django methods instead:
.. code-block:: python
# In model code:
objects = PolymorphicQuerySet.as_manager()
# For manager code:
MyCustomManager = PolymorphicManager.from_queryset(MyCustomQuerySet)
Version 0.7.2 (2015-10-01) Version 0.7.2 (2015-10-01)
-------------------------- --------------------------

View File

@ -11,7 +11,8 @@
# All configuration values have a default; values that are commented out # All configuration values have a default; values that are commented out
# serve to show the default. # serve to show the default.
import sys, os import sys
import os
# If extensions (or modules to document with autodoc) are in another directory, # If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the # add these directories to sys.path here. If the directory is relative to the
@ -54,9 +55,9 @@ copyright = u'2013, Bert Constantin, Chris Glass, Diederik van der Boor'
# built documents. # built documents.
# #
# The short X.Y version. # The short X.Y version.
version = '0.7.2' version = '0.8.1'
# The full version, including alpha/beta/rc tags. # The full version, including alpha/beta/rc tags.
release = '0.7.2' release = '0.8.1'
# The language for content autogenerated by Sphinx. Refer to documentation # The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages. # for a list of supported languages.
@ -176,21 +177,21 @@ htmlhelp_basename = 'django-polymorphicdoc'
# -- Options for LaTeX output -------------------------------------------------- # -- Options for LaTeX output --------------------------------------------------
latex_elements = { latex_elements = {
# The paper size ('letterpaper' or 'a4paper'). # The paper size ('letterpaper' or 'a4paper').
#'papersize': 'letterpaper', #'papersize': 'letterpaper',
# The font size ('10pt', '11pt' or '12pt'). # The font size ('10pt', '11pt' or '12pt').
#'pointsize': '10pt', #'pointsize': '10pt',
# Additional stuff for the LaTeX preamble. # Additional stuff for the LaTeX preamble.
#'preamble': '', #'preamble': '',
} }
# Grouping the document tree into LaTeX files. List of tuples # Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title, author, documentclass [howto/manual]). # (source start file, target name, title, author, documentclass [howto/manual]).
latex_documents = [ latex_documents = [
('index', 'django-polymorphic.tex', u'django-polymorphic Documentation', ('index', 'django-polymorphic.tex', u'django-polymorphic Documentation',
u'Bert Constantin, Chris Glass, Diederik van der Boor', 'manual'), u'Bert Constantin, Chris Glass, Diederik van der Boor', 'manual'),
] ]
# The name of an image file (relative to this directory) to place at the top of # The name of an image file (relative to this directory) to place at the top of
@ -233,9 +234,9 @@ man_pages = [
# (source start file, target name, title, author, # (source start file, target name, title, author,
# dir menu entry, description, category) # dir menu entry, description, category)
texinfo_documents = [ texinfo_documents = [
('index', 'django-polymorphic', u'django-polymorphic Documentation', ('index', 'django-polymorphic', u'django-polymorphic Documentation',
u'Bert Constantin, Chris Glass, Diederik van der Boor', 'django-polymorphic', 'One line description of project.', u'Bert Constantin, Chris Glass, Diederik van der Boor', 'django-polymorphic', 'One line description of project.',
'Miscellaneous'), 'Miscellaneous'),
] ]
# Documents to append as an appendix to all manuals. # Documents to append as an appendix to all manuals.

View File

@ -75,7 +75,7 @@ INSTALLED_APPS = (
'pexp', # this Django app is for testing and experimentation; not needed otherwise 'pexp', # this Django app is for testing and experimentation; not needed otherwise
) )
if django.VERSION >= (1,7): 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

@ -14,6 +14,7 @@ class ProjectChildAdmin(PolymorphicChildModelAdmin):
}), }),
) )
class ProjectAdmin(PolymorphicParentModelAdmin): class ProjectAdmin(PolymorphicParentModelAdmin):
base_model = Project base_model = Project
list_filter = (PolymorphicChildModelFilter,) list_filter = (PolymorphicChildModelFilter,)
@ -26,10 +27,10 @@ class ProjectAdmin(PolymorphicParentModelAdmin):
admin.site.register(Project, ProjectAdmin) admin.site.register(Project, ProjectAdmin)
class ModelAChildAdmin(PolymorphicChildModelAdmin): class ModelAChildAdmin(PolymorphicChildModelAdmin):
base_model = ModelA base_model = ModelA
class ModelAAdmin(PolymorphicParentModelAdmin): class ModelAAdmin(PolymorphicParentModelAdmin):
base_model = ModelA base_model = ModelA
list_filter = (PolymorphicChildModelFilter,) list_filter = (PolymorphicChildModelFilter,)
@ -45,6 +46,7 @@ admin.site.register(ModelA, ModelAAdmin)
class Model2AChildAdmin(PolymorphicChildModelAdmin): class Model2AChildAdmin(PolymorphicChildModelAdmin):
base_model = Model2A base_model = Model2A
class Model2AAdmin(PolymorphicParentModelAdmin): class Model2AAdmin(PolymorphicParentModelAdmin):
base_model = Model2A base_model = Model2A
list_filter = (PolymorphicChildModelFilter,) list_filter = (PolymorphicChildModelFilter,)
@ -60,6 +62,7 @@ admin.site.register(Model2A, Model2AAdmin)
class UUIDModelAChildAdmin(PolymorphicChildModelAdmin): class UUIDModelAChildAdmin(PolymorphicChildModelAdmin):
base_model = UUIDModelA base_model = UUIDModelA
class UUIDModelAAdmin(PolymorphicParentModelAdmin): class UUIDModelAAdmin(PolymorphicParentModelAdmin):
base_model = UUIDModelA base_model = UUIDModelA
list_filter = (PolymorphicChildModelFilter,) list_filter = (PolymorphicChildModelFilter,)
@ -75,6 +78,7 @@ admin.site.register(UUIDModelA, UUIDModelAAdmin)
class ProxyChildAdmin(PolymorphicChildModelAdmin): class ProxyChildAdmin(PolymorphicChildModelAdmin):
base_model = ProxyBase base_model = ProxyBase
class ProxyAdmin(PolymorphicParentModelAdmin): class ProxyAdmin(PolymorphicParentModelAdmin):
base_model = ProxyBase base_model = ProxyBase
list_filter = (PolymorphicChildModelFilter,) list_filter = (PolymorphicChildModelFilter,)

View File

@ -4,80 +4,97 @@ 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.
""" """
import uuid import uuid
import django
from django.core.management.base import NoArgsCommand from django.core.management.base import NoArgsCommand
from django.db.models import connection from django.db import connection
from pprint import pprint from pprint import pprint
import time,sys import time
import sys
from pexp.models import * from pexp.models import *
def reset_queries(): def reset_queries():
connection.queries=[] if django.VERSION < (1, 9):
connection.queries = []
else:
connection.queries_log.clear()
def show_queries(): def show_queries():
print; print 'QUERIES:',len(connection.queries); pprint(connection.queries); print; connection.queries=[] print
print 'QUERIES:', len(connection.queries)
pprint(connection.queries)
print
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() reset_queries()
for i in xrange(iterations): for i in xrange(iterations):
t1 = time.time() t1 = time.time()
x = func(*arg) x = func(*arg)
t2 = time.time() t2 = time.time()
results.append((t2-t1)*1000.0) results.append((t2 - t1) * 1000.0)
res_sum=0 res_sum = 0
for r in results: res_sum +=r for r in results:
res_sum += r
median = res_sum / len(results) 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
class Command(NoArgsCommand): class Command(NoArgsCommand):
help = "" help = ""
def handle_noargs(self, **options): def handle_noargs(self, **options):
if False: if False:
ModelA.objects.all().delete() ModelA.objects.all().delete()
a=ModelA.objects.create(field1='A1') a = ModelA.objects.create(field1='A1')
b=ModelB.objects.create(field1='B1', field2='B2') b = ModelB.objects.create(field1='B1', field2='B2')
c=ModelC.objects.create(field1='C1', field2='C2', field3='C3') c = ModelC.objects.create(field1='C1', field2='C2', field3='C3')
reset_queries() reset_queries()
print ModelC.base_objects.all(); print ModelC.base_objects.all()
show_queries() show_queries()
if False: if False:
ModelA.objects.all().delete() ModelA.objects.all().delete()
for i in xrange(1000): for i in xrange(1000):
a=ModelA.objects.create(field1=str(i%100)) a = ModelA.objects.create(field1=str(i % 100))
b=ModelB.objects.create(field1=str(i%100), field2=str(i%200)) b = ModelB.objects.create(field1=str(i % 100), field2=str(i % 200))
c=ModelC.objects.create(field1=str(i%100), field2=str(i%200), field3=str(i%300)) c = ModelC.objects.create(field1=str(i % 100), field2=str(i % 200), field3=str(i % 300))
if i%100==0: print i if i % 100 == 0:
print i
f=print_timing(poly_sql_query,iterations=1000) f = print_timing(poly_sql_query, iterations=1000)
f() f()
f=print_timing(poly_sql_query2,iterations=1000) f = print_timing(poly_sql_query2, iterations=1000)
f() f()
return return
nModelA.objects.all().delete() nModelA.objects.all().delete()
a=nModelA.objects.create(field1='A1') a = nModelA.objects.create(field1='A1')
b=nModelB.objects.create(field1='B1', field2='B2') b = nModelB.objects.create(field1='B1', field2='B2')
c=nModelC.objects.create(field1='C1', field2='C2', field3='C3') c = nModelC.objects.create(field1='C1', field2='C2', field3='C3')
qs=ModelA.objects.raw("SELECT * from pexp_modela") qs = ModelA.objects.raw("SELECT * from pexp_modela")
for o in list(qs): print o for o in list(qs):
print o
from django.db import connection, transaction from django.db import connection, transaction
from random import Random from random import Random
rnd=Random() rnd = Random()
def poly_sql_query(): def poly_sql_query():
cursor = connection.cursor() cursor = connection.cursor()
@ -90,10 +107,11 @@ def poly_sql_query():
ON pexp_modelb.modela_ptr_id = pexp_modelc.modelb_ptr_id ON pexp_modelb.modela_ptr_id = pexp_modelc.modelb_ptr_id
WHERE pexp_modela.field1=%i WHERE pexp_modela.field1=%i
ORDER BY pexp_modela.id ORDER BY pexp_modela.id
""" % rnd.randint(0,100) ) """ % rnd.randint(0, 100) )
#row=cursor.fetchone() # row=cursor.fetchone()
return return
def poly_sql_query2(): def poly_sql_query2():
cursor = connection.cursor() cursor = connection.cursor()
cursor.execute(""" cursor.execute("""
@ -101,6 +119,6 @@ def poly_sql_query2():
FROM pexp_modela FROM pexp_modela
WHERE pexp_modela.field1=%i WHERE pexp_modela.field1=%i
ORDER BY pexp_modela.id ORDER BY pexp_modela.id
""" % rnd.randint(0,100) ) """ % rnd.randint(0, 100) )
#row=cursor.fetchone() # row=cursor.fetchone()
return return

View File

@ -4,32 +4,38 @@ This module is a scratchpad for general development, testing & debugging.
""" """
from django.core.management.base import NoArgsCommand from django.core.management.base import NoArgsCommand
from django.db.models import connection from django.db import connection
from pprint import pprint from pprint import pprint
from pexp.models import * from pexp.models import *
def reset_queries(): def reset_queries():
connection.queries=[] connection.queries = []
def show_queries(): def show_queries():
print; print 'QUERIES:',len(connection.queries); pprint(connection.queries); print; connection.queries=[] print
print 'QUERIES:', len(connection.queries)
pprint(connection.queries)
print
connection.queries = []
class Command(NoArgsCommand): class Command(NoArgsCommand):
help = "" help = ""
def handle_noargs(self, **options): def handle_noargs(self, **options):
Project.objects.all().delete() Project.objects.all().delete()
a=Project.objects.create(topic="John's gathering") a = Project.objects.create(topic="John's gathering")
b=ArtProject.objects.create(topic="Sculpting with Tim", artist="T. Turner") b = ArtProject.objects.create(topic="Sculpting with Tim", artist="T. Turner")
c=ResearchProject.objects.create(topic="Swallow Aerodynamics", supervisor="Dr. Winter") c = ResearchProject.objects.create(topic="Swallow Aerodynamics", supervisor="Dr. Winter")
print Project.objects.all() print Project.objects.all()
print print
ModelA.objects.all().delete() ModelA.objects.all().delete()
a=ModelA.objects.create(field1='A1') a = ModelA.objects.create(field1='A1')
b=ModelB.objects.create(field1='B1', field2='B2') b = ModelB.objects.create(field1='B1', field2='B2')
c=ModelC.objects.create(field1='C1', field2='C2', field3='C3') c = ModelC.objects.create(field1='C1', field2='C2', field3='C3')
print ModelA.objects.all() print ModelA.objects.all()
print print

View File

@ -3,101 +3,120 @@
This module is a scratchpad for general development, testing & debugging This module is a scratchpad for general development, testing & debugging
""" """
import django
from django.core.management.base import NoArgsCommand from django.core.management.base import NoArgsCommand
from django.db.models import connection from django.db import connection
from pprint import pprint from pprint import pprint
import sys import sys
from pexp.models import * from pexp.models import *
num_objects=1000 num_objects = 1000
def reset_queries(): def reset_queries():
connection.queries=[] if django.VERSION < (1, 9):
connection.queries = []
else:
connection.queries_log.clear()
def show_queries(): def show_queries():
print; print 'QUERIES:',len(connection.queries); pprint(connection.queries); print; reset_queries() print
print 'QUERIES:', len(connection.queries)
pprint(connection.queries)
print
reset_queries()
import time import time
################################################################################### ###################################################################################
### benchmark wrappers # benchmark wrappers
def print_timing(func, message='', iterations=1): def print_timing(func, message='', iterations=1):
def wrapper(*arg): def wrapper(*arg):
results=[] results = []
reset_queries() reset_queries()
for i in xrange(iterations): for i in xrange(iterations):
t1 = time.time() t1 = time.time()
x = func(*arg) x = func(*arg)
t2 = time.time() t2 = time.time()
results.append((t2-t1)*1000.0) results.append((t2 - t1) * 1000.0)
res_sum=0 res_sum = 0
for r in results: res_sum +=r for r in results:
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
def run_vanilla_any_poly(func, iterations=1): def run_vanilla_any_poly(func, iterations=1):
f=print_timing(func,' ', iterations) f = print_timing(func, ' ', iterations)
f(nModelC) f(nModelC)
f=print_timing(func,'poly ', iterations) f = print_timing(func, 'poly ', iterations)
f(ModelC) f(ModelC)
################################################################################### ###################################################################################
### benchmarks # benchmarks
def bench_create(model): def bench_create(model):
for i in xrange(num_objects): for i in xrange(num_objects):
model.objects.create(field1='abc'+str(i), field2='abcd'+str(i), field3='abcde'+str(i)) model.objects.create(field1='abc' + str(i), field2='abcd' + str(i), field3='abcde' + str(i))
#print 'count:',model.objects.count() # print 'count:',model.objects.count()
def bench_load1(model): def bench_load1(model):
for o in model.objects.all(): for o in model.objects.all():
pass pass
def bench_load1_short(model): def bench_load1_short(model):
for i in xrange(num_objects/100): for i in xrange(num_objects / 100):
for o in model.objects.all()[:100]: for o in model.objects.all()[:100]:
pass pass
def bench_load2(model): def bench_load2(model):
for o in model.objects.all(): for o in model.objects.all():
f1=o.field1 f1 = o.field1
f2=o.field2 f2 = o.field2
f3=o.field3 f3 = o.field3
def bench_load2_short(model): def bench_load2_short(model):
for i in xrange(num_objects/100): for i in xrange(num_objects / 100):
for o in model.objects.all()[:100]: for o in model.objects.all()[:100]:
f1=o.field1 f1 = o.field1
f2=o.field2 f2 = o.field2
f3=o.field3 f3 = o.field3
def bench_delete(model): def bench_delete(model):
model.objects.all().delete() model.objects.all().delete()
################################################################################### ###################################################################################
### Command # Command
class Command(NoArgsCommand): class Command(NoArgsCommand):
help = "" help = ""
def handle_noargs(self, **options): def handle_noargs(self, **options):
func_list = [ func_list = [
( bench_delete, 1 ), (bench_delete, 1),
( bench_create, 1 ), (bench_create, 1),
( bench_load1, 5 ), (bench_load1, 5),
( bench_load1_short, 5 ), (bench_load1_short, 5),
( bench_load2, 5 ), (bench_load2, 5),
( bench_load2_short, 5 ) (bench_load2_short, 5)
] ]
for f,iterations in func_list: for f, iterations in func_list:
run_vanilla_any_poly(f,iterations=iterations) run_vanilla_any_poly(f, iterations=iterations)
print print

View File

@ -4,27 +4,31 @@ This module is a scratchpad for general development, testing & debugging
""" """
from django.core.management.base import NoArgsCommand from django.core.management.base import NoArgsCommand
from django.db.models import connection from django.db import connection
from pprint import pprint from pprint import pprint
from pexp.models import * from pexp.models import *
def reset_queries(): def reset_queries():
connection.queries=[] connection.queries = []
def show_queries(): def show_queries():
print; print 'QUERIES:',len(connection.queries); pprint(connection.queries); print; connection.queries=[] print
print 'QUERIES:', len(connection.queries)
pprint(connection.queries)
print
connection.queries = []
class Command(NoArgsCommand): class Command(NoArgsCommand):
help = "" help = ""
def handle_noargs(self, **options): def handle_noargs(self, **options):
Project.objects.all().delete() Project.objects.all().delete()
o=Project.objects.create(topic="John's gathering") o = Project.objects.create(topic="John's gathering")
o=ArtProject.objects.create(topic="Sculpting with Tim", artist="T. Turner") o = ArtProject.objects.create(topic="Sculpting with Tim", artist="T. Turner")
o=ResearchProject.objects.create(topic="Swallow Aerodynamics", supervisor="Dr. Winter") o = ResearchProject.objects.create(topic="Swallow Aerodynamics", supervisor="Dr. Winter")
print Project.objects.all() print Project.objects.all()
print print

View File

@ -6,48 +6,74 @@ from django.db import models
from polymorphic.models import PolymorphicModel from polymorphic.models import PolymorphicModel
from polymorphic.showfields import ShowFieldContent, ShowFieldType, ShowFieldTypeAndContent from polymorphic.showfields import ShowFieldContent, ShowFieldType, ShowFieldTypeAndContent
class Project(ShowFieldContent, PolymorphicModel): class Project(ShowFieldContent, PolymorphicModel):
topic = models.CharField(max_length=30) topic = models.CharField(max_length=30)
class ArtProject(Project): class ArtProject(Project):
artist = models.CharField(max_length=30) artist = models.CharField(max_length=30)
class ResearchProject(Project): class ResearchProject(Project):
supervisor = models.CharField(max_length=30) supervisor = models.CharField(max_length=30)
class ModelA(ShowFieldTypeAndContent, PolymorphicModel): class ModelA(ShowFieldTypeAndContent, PolymorphicModel):
field1 = models.CharField(max_length=10) field1 = models.CharField(max_length=10)
class ModelB(ModelA): class ModelB(ModelA):
field2 = models.CharField(max_length=10) field2 = models.CharField(max_length=10)
class ModelC(ModelB): class ModelC(ModelB):
field3 = models.CharField(max_length=10) field3 = models.CharField(max_length=10)
field4 = models.ManyToManyField(ModelB, related_name='related_c') field4 = models.ManyToManyField(ModelB, related_name='related_c')
class nModelA(models.Model): class nModelA(models.Model):
field1 = models.CharField(max_length=10) field1 = models.CharField(max_length=10)
class nModelB(nModelA): class nModelB(nModelA):
field2 = models.CharField(max_length=10) field2 = models.CharField(max_length=10)
class nModelC(nModelB): class nModelC(nModelB):
field3 = models.CharField(max_length=10) field3 = models.CharField(max_length=10)
class Model2A(PolymorphicModel): class Model2A(PolymorphicModel):
field1 = models.CharField(max_length=10) field1 = models.CharField(max_length=10)
class Model2B(Model2A): class Model2B(Model2A):
field2 = models.CharField(max_length=10) field2 = models.CharField(max_length=10)
class Model2C(Model2B): class Model2C(Model2B):
field3 = models.CharField(max_length=10) field3 = models.CharField(max_length=10)
if django.VERSION < (1,8): if django.VERSION < (1, 8):
from polymorphic.tools_for_tests import UUIDField from polymorphic.tools_for_tests import UUIDField
else: else:
from django.db.models import UUIDField from django.db.models import UUIDField
class UUIDModelA(ShowFieldTypeAndContent, PolymorphicModel): class UUIDModelA(ShowFieldTypeAndContent, PolymorphicModel):
uuid_primary_key = UUIDField(primary_key = True) uuid_primary_key = UUIDField(primary_key=True)
field1 = models.CharField(max_length=10) field1 = models.CharField(max_length=10)
class UUIDModelB(UUIDModelA): class UUIDModelB(UUIDModelA):
field2 = models.CharField(max_length=10) field2 = models.CharField(max_length=10)
class UUIDModelC(UUIDModelB): class UUIDModelC(UUIDModelB):
field3 = models.CharField(max_length=10) field3 = models.CharField(max_length=10)
class ProxyBase(PolymorphicModel): class ProxyBase(PolymorphicModel):
title = models.CharField(max_length=200) title = models.CharField(max_length=200)
@ -57,14 +83,18 @@ class ProxyBase(PolymorphicModel):
class Meta: class Meta:
ordering = ('title',) ordering = ('title',)
class ProxyA(ProxyBase): class ProxyA(ProxyBase):
class Meta: class Meta:
proxy = True proxy = True
def __unicode__(self): def __unicode__(self):
return u"<ProxyA: {0}>".format(self.title) return u"<ProxyA: {0}>".format(self.title)
class ProxyB(ProxyBase): class ProxyB(ProxyBase):
class Meta: class Meta:
proxy = True proxy = True

View File

@ -6,13 +6,12 @@ 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.
""" """
from __future__ import absolute_import # See PEP 440 (https://www.python.org/dev/peps/pep-0440/)
import django __version__ = "0.8.1"
from .showfields import ShowFieldContent, ShowFieldType, ShowFieldTypeAndContent
from .showfields import ShowFields, ShowFieldTypes, ShowFieldsAndTypes # import old names for compatibility
# Monkey-patch Django < 1.5 to allow ContentTypes for proxy models. # Monkey-patch Django < 1.5 to allow ContentTypes for proxy models.
import django
if django.VERSION[:2] < (1, 5): if django.VERSION[:2] < (1, 5):
from django.contrib.contenttypes.models import ContentTypeManager from django.contrib.contenttypes.models import ContentTypeManager
from django.utils.encoding import smart_text from django.utils.encoding import smart_text
@ -29,9 +28,9 @@ if django.VERSION[:2] < (1, 5):
ct = self._get_from_cache(opts) ct = self._get_from_cache(opts)
except KeyError: except KeyError:
ct, created = self.get_or_create( ct, created = self.get_or_create(
app_label = opts.app_label, app_label=opts.app_label,
model = opts.object_name.lower(), model=opts.object_name.lower(),
defaults = {'name': smart_text(opts.verbose_name_raw)}, defaults={'name': smart_text(opts.verbose_name_raw)},
) )
self._add_to_cache(self.db, ct) self._add_to_cache(self.db, ct)
@ -39,4 +38,3 @@ if django.VERSION[:2] < (1, 5):
ContentTypeManager.get_for_model__original = ContentTypeManager.get_for_model ContentTypeManager.get_for_model__original = ContentTypeManager.get_for_model
ContentTypeManager.get_for_model = get_for_model ContentTypeManager.get_for_model = get_for_model

View File

@ -1,5 +0,0 @@
# -*- coding: utf-8 -*-
"""
See PEP 440 (https://www.python.org/dev/peps/pep-0440/)
"""
__version__ = "0.7.2"

View File

@ -42,6 +42,7 @@ 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."
pass pass
class ChildAdminNotRegistered(RuntimeError): class ChildAdminNotRegistered(RuntimeError):
"The admin site for the model is not registered." "The admin site for the model is not registered."
pass pass
@ -122,13 +123,11 @@ class PolymorphicParentModelAdmin(admin.ModelAdmin):
#: 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 = '(\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)
self._child_admin_site = self.admin_site.__class__(name=self.admin_site.name) self._child_admin_site = self.admin_site.__class__(name=self.admin_site.name)
self._is_setup = False self._is_setup = False
def _lazy_setup(self): def _lazy_setup(self):
if self._is_setup: if self._is_setup:
return return
@ -150,7 +149,6 @@ class PolymorphicParentModelAdmin(admin.ModelAdmin):
self._child_admin_site._registry = complete_registry self._child_admin_site._registry = complete_registry
self._is_setup = True self._is_setup = True
def register_child(self, model, model_admin): def register_child(self, model, model_admin):
""" """
Register a model with admin to display. Register a model with admin to display.
@ -167,7 +165,6 @@ class PolymorphicParentModelAdmin(admin.ModelAdmin):
self._child_admin_site.register(model, model_admin) self._child_admin_site.register(model, model_admin)
def get_child_models(self): def get_child_models(self):
""" """
Return the derived model classes which this admin should handle. Return the derived model classes which this admin should handle.
@ -181,7 +178,6 @@ class PolymorphicParentModelAdmin(admin.ModelAdmin):
return self.child_models return self.child_models
def get_child_type_choices(self, request, action): def get_child_type_choices(self, request, action):
""" """
Return a list of polymorphic types for which the user has the permission to perform the given action. Return a list of polymorphic types for which the user has the permission to perform the given action.
@ -197,7 +193,6 @@ class PolymorphicParentModelAdmin(admin.ModelAdmin):
choices.append((ct.id, model._meta.verbose_name)) choices.append((ct.id, model._meta.verbose_name))
return choices return choices
def _get_real_admin(self, object_id): def _get_real_admin(self, object_id):
try: try:
obj = self.model.objects.non_polymorphic() \ obj = self.model.objects.non_polymorphic() \
@ -206,7 +201,6 @@ class PolymorphicParentModelAdmin(admin.ModelAdmin):
raise Http404 raise Http404
return self._get_real_admin_by_ct(obj['polymorphic_ctype']) return self._get_real_admin_by_ct(obj['polymorphic_ctype'])
def _get_real_admin_by_ct(self, ct_id): def _get_real_admin_by_ct(self, ct_id):
try: try:
ct = ContentType.objects.get_for_id(ct_id) ct = ContentType.objects.get_for_id(ct_id)
@ -219,7 +213,6 @@ class PolymorphicParentModelAdmin(admin.ModelAdmin):
return self._get_real_admin_by_model(model_class) return self._get_real_admin_by_model(model_class)
def _get_real_admin_by_model(self, model_class): def _get_real_admin_by_model(self, model_class):
# In case of a ?ct_id=### parameter, the view is already checked for permissions. # In case of a ?ct_id=### parameter, the view is already checked for permissions.
# Hence, make sure this is a derived object, or risk exposing other admin interfaces. # Hence, make sure this is a derived object, or risk exposing other admin interfaces.
@ -233,7 +226,6 @@ class PolymorphicParentModelAdmin(admin.ModelAdmin):
except KeyError: except KeyError:
raise ChildAdminNotRegistered("No child admin site was registered for a '{0}' model.".format(model_class)) raise ChildAdminNotRegistered("No child admin site was registered for a '{0}' model.".format(model_class))
def get_queryset(self, request): def get_queryset(self, request):
# optimize the list display. # optimize the list display.
qs = super(PolymorphicParentModelAdmin, self).get_queryset(request) qs = super(PolymorphicParentModelAdmin, self).get_queryset(request)
@ -241,7 +233,6 @@ class PolymorphicParentModelAdmin(admin.ModelAdmin):
qs = qs.non_polymorphic() qs = qs.non_polymorphic()
return qs return qs
# For Django 1.5: # For Django 1.5:
def queryset(self, request): def queryset(self, request):
qs = super(PolymorphicParentModelAdmin, self).queryset(request) qs = super(PolymorphicParentModelAdmin, self).queryset(request)
@ -249,7 +240,6 @@ class PolymorphicParentModelAdmin(admin.ModelAdmin):
qs = qs.non_polymorphic() qs = qs.non_polymorphic()
return qs 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))
@ -266,13 +256,11 @@ class PolymorphicParentModelAdmin(admin.ModelAdmin):
) )
return real_admin.add_view(request, form_url, extra_context) return real_admin.add_view(request, form_url, extra_context)
def change_view(self, request, object_id, *args, **kwargs): def change_view(self, request, object_id, *args, **kwargs):
"""Redirect the change view to the real admin.""" """Redirect the change view to the real admin."""
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)
def delete_view(self, request, object_id, extra_context=None): def delete_view(self, request, object_id, extra_context=None):
"""Redirect the delete view to the real admin.""" """Redirect the delete view to the real admin."""
real_admin = self._get_real_admin(object_id) real_admin = self._get_real_admin(object_id)
@ -331,7 +319,6 @@ class PolymorphicParentModelAdmin(admin.ModelAdmin):
return urls + custom_urls + dummy_urls return urls + custom_urls + dummy_urls
def subclass_view(self, request, path): def subclass_view(self, request, path):
""" """
Forward any request to a custom view of the real admin. Forward any request to a custom view of the real admin.
@ -350,7 +337,6 @@ class PolymorphicParentModelAdmin(admin.ModelAdmin):
ct_id = self.model.objects.values_list('polymorphic_ctype_id', flat=True).get(pk=object_id) ct_id = self.model.objects.values_list('polymorphic_ctype_id', flat=True).get(pk=object_id)
real_admin = self._get_real_admin_by_ct(ct_id) real_admin = self._get_real_admin_by_ct(ct_id)
resolver = RegexURLResolver('^', real_admin.urls) resolver = RegexURLResolver('^', real_admin.urls)
resolvermatch = resolver.resolve(path) # May raise Resolver404 resolvermatch = resolver.resolve(path) # May raise Resolver404
@ -359,7 +345,6 @@ class PolymorphicParentModelAdmin(admin.ModelAdmin):
return resolvermatch.func(request, *resolvermatch.args, **resolvermatch.kwargs) return resolvermatch.func(request, *resolvermatch.args, **resolvermatch.kwargs)
def add_type_view(self, request, form_url=''): def add_type_view(self, request, form_url=''):
""" """
Display a choice form to select which page type to add. Display a choice form to select which page type to add.
@ -402,7 +387,6 @@ class PolymorphicParentModelAdmin(admin.ModelAdmin):
} }
return self.render_add_type_form(request, context, form_url) return self.render_add_type_form(request, context, form_url)
def render_add_type_form(self, request, context, form_url=''): def render_add_type_form(self, request, context, form_url=''):
""" """
Render the page type choice form. Render the page type choice form.
@ -426,7 +410,6 @@ class PolymorphicParentModelAdmin(admin.ModelAdmin):
"admin/add_type_form.html" "admin/add_type_form.html"
], context, context_instance=context_instance) ], context, context_instance=context_instance)
@property @property
def change_list_template(self): def change_list_template(self):
opts = self.model._meta opts = self.model._meta
@ -446,7 +429,6 @@ class PolymorphicParentModelAdmin(admin.ModelAdmin):
] ]
class PolymorphicChildModelAdmin(admin.ModelAdmin): class PolymorphicChildModelAdmin(admin.ModelAdmin):
""" """
The *optional* base class for the admin interface of derived models. The *optional* base class for the admin interface of derived models.
@ -465,7 +447,6 @@ class PolymorphicChildModelAdmin(admin.ModelAdmin):
base_fieldsets = None base_fieldsets = None
extra_fieldset_title = _("Contents") # Default title for extra fieldset extra_fieldset_title = _("Contents") # Default title for extra fieldset
def get_form(self, request, obj=None, **kwargs): def get_form(self, request, obj=None, **kwargs):
# The django admin validation requires the form to have a 'class Meta: model = ..' # The django admin validation requires the form to have a 'class Meta: model = ..'
# attribute, or it will complain that the fields are missing. # attribute, or it will complain that the fields are missing.
@ -482,7 +463,6 @@ class PolymorphicChildModelAdmin(admin.ModelAdmin):
return super(PolymorphicChildModelAdmin, self).get_form(request, obj, **kwargs) return super(PolymorphicChildModelAdmin, self).get_form(request, obj, **kwargs)
@property @property
def change_form_template(self): def change_form_template(self):
opts = self.model._meta opts = self.model._meta
@ -502,7 +482,6 @@ class PolymorphicChildModelAdmin(admin.ModelAdmin):
"admin/change_form.html" "admin/change_form.html"
] ]
@property @property
def delete_confirmation_template(self): def delete_confirmation_template(self):
opts = self.model._meta opts = self.model._meta
@ -522,21 +501,18 @@ class PolymorphicChildModelAdmin(admin.ModelAdmin):
"admin/delete_confirmation.html" "admin/delete_confirmation.html"
] ]
def render_change_form(self, request, context, add=False, change=False, form_url='', obj=None): def render_change_form(self, request, context, add=False, change=False, form_url='', obj=None):
context.update({ context.update({
'base_opts': self.base_model._meta, 'base_opts': self.base_model._meta,
}) })
return super(PolymorphicChildModelAdmin, self).render_change_form(request, context, add=add, change=change, form_url=form_url, obj=obj) return super(PolymorphicChildModelAdmin, self).render_change_form(request, context, add=add, change=change, form_url=form_url, obj=obj)
def delete_view(self, request, object_id, context=None): def delete_view(self, request, object_id, context=None):
extra_context = { extra_context = {
'base_opts': self.base_model._meta, 'base_opts': self.base_model._meta,
} }
return super(PolymorphicChildModelAdmin, self).delete_view(request, object_id, extra_context) return super(PolymorphicChildModelAdmin, self).delete_view(request, object_id, extra_context)
# ---- Extra: improving the form/fieldset default display ---- # ---- Extra: improving the form/fieldset default display ----
def get_fieldsets(self, request, obj=None): def get_fieldsets(self, request, obj=None):
@ -557,7 +533,6 @@ class PolymorphicChildModelAdmin(admin.ModelAdmin):
else: else:
return self.base_fieldsets return self.base_fieldsets
def get_subclass_fields(self, request, obj=None): def get_subclass_fields(self, request, obj=None):
# Find out how many fields would really be on the form, # Find out how many fields would really be on the form,
# if it weren't restricted by declared fields. # if it weren't restricted by declared fields.

View File

@ -12,7 +12,7 @@ from django.db import models
from django.db.models.base import ModelBase from django.db.models.base import ModelBase
from django.db.models.manager import ManagerDescriptor from django.db.models.manager import ManagerDescriptor
from .manager import PolymorphicManager from .managers import PolymorphicManager
from .query import PolymorphicQuerySet from .query import PolymorphicQuerySet
# PolymorphicQuerySet Q objects (and filter()) support these additional key words. # PolymorphicQuerySet Q objects (and filter()) support these additional key words.
@ -26,7 +26,7 @@ except ImportError:
################################################################################### ###################################################################################
### PolymorphicModel meta class # PolymorphicModel meta class
class PolymorphicModelBase(ModelBase): class PolymorphicModelBase(ModelBase):
""" """
@ -53,11 +53,11 @@ class PolymorphicModelBase(ModelBase):
""" """
def __new__(self, model_name, bases, attrs): def __new__(self, model_name, bases, attrs):
#print; print '###', model_name, '- bases:', bases # print; print '###', model_name, '- bases:', bases
# Workaround compatibility issue with six.with_metaclass() and custom Django model metaclasses: # Workaround compatibility issue with six.with_metaclass() and custom Django model metaclasses:
if not attrs and model_name == 'NewBase': if not attrs and model_name == 'NewBase':
if django.VERSION < (1,5): if django.VERSION < (1, 5):
# Let Django fully ignore the class which is inserted in between. # Let Django fully ignore the class which is inserted in between.
# Django 1.5 fixed this, see https://code.djangoproject.com/ticket/19688 # Django 1.5 fixed this, see https://code.djangoproject.com/ticket/19688
attrs['__module__'] = 'django.utils.six' attrs['__module__'] = 'django.utils.six'
@ -75,7 +75,7 @@ class PolymorphicModelBase(ModelBase):
# add the managers to the new model # add the managers to the new model
for source_name, mgr_name, manager in inherited_managers: for source_name, mgr_name, manager in inherited_managers:
#print '** add inherited manager from model %s, manager %s, %s' % (source_name, mgr_name, manager.__class__.__name__) # print '** add inherited manager from model %s, manager %s, %s' % (source_name, mgr_name, manager.__class__.__name__)
new_manager = manager._copy_to_model(new_class) new_manager = manager._copy_to_model(new_class)
if mgr_name == '_default_manager': if mgr_name == '_default_manager':
new_class._default_manager = new_manager new_class._default_manager = new_manager
@ -86,7 +86,7 @@ class PolymorphicModelBase(ModelBase):
# this value is used by the related objects, restoring access to custom queryset methods on related objects. # this value is used by the related objects, restoring access to custom queryset methods on related objects.
user_manager = self.get_first_user_defined_manager(new_class) user_manager = self.get_first_user_defined_manager(new_class)
if user_manager: if user_manager:
#print '## add default manager', type(def_mgr) # print '## add default manager', type(def_mgr)
new_class._default_manager = user_manager._copy_to_model(new_class) new_class._default_manager = user_manager._copy_to_model(new_class)
new_class._default_manager._inherited = False # the default mgr was defined by the user, not inherited new_class._default_manager._inherited = False # the default mgr was defined by the user, not inherited
@ -111,7 +111,7 @@ class PolymorphicModelBase(ModelBase):
use correct mro, only use managers with _inherited==False (they are of no use), use correct mro, only use managers with _inherited==False (they are of no use),
skip managers that are overwritten by the user with same-named class attributes (in attrs) skip managers that are overwritten by the user with same-named class attributes (in attrs)
""" """
#print "** ", self.__name__ # print "** ", self.__name__
add_managers = [] add_managers = []
add_managers_keys = set() add_managers_keys = set()
for base in self.__mro__[1:]: for base in self.__mro__[1:]:
@ -147,7 +147,7 @@ class PolymorphicModelBase(ModelBase):
continue # manager with that name already added, skip continue # manager with that name already added, skip
if manager._inherited: if manager._inherited:
continue # inherited managers (on the bases) have no significance, they are just copies continue # inherited managers (on the bases) have no significance, they are just copies
#print '## {0} {1}'.format(self.__name__, key) # print '## {0} {1}'.format(self.__name__, key)
if isinstance(manager, PolymorphicManager): # validate any inherited polymorphic managers if isinstance(manager, PolymorphicManager): # validate any inherited polymorphic managers
self.validate_model_manager(manager, self.__name__, key) self.validate_model_manager(manager, self.__name__, key)
@ -175,7 +175,7 @@ class PolymorphicModelBase(ModelBase):
# if there are user defined managers, use first one as _default_manager # if there are user defined managers, use first one as _default_manager
if mgr_list: if mgr_list:
_, manager_name, manager = sorted(mgr_list)[0] _, manager_name, manager = sorted(mgr_list)[0]
#sys.stderr.write( '\n# first user defined manager for model "{model}":\n# "{mgrname}": {mgr}\n# manager model: {mgrmodel}\n\n' # sys.stderr.write( '\n# first user defined manager for model "{model}":\n# "{mgrname}": {mgr}\n# manager model: {mgrmodel}\n\n'
# .format( model=self.__name__, mgrname=manager_name, mgr=manager, mgrmodel=manager.model ) ) # .format( model=self.__name__, mgrname=manager_name, mgr=manager, mgrmodel=manager.model ) )
return manager return manager
return None return None
@ -191,9 +191,9 @@ class PolymorphicModelBase(ModelBase):
# app_label here for PolymorphicModel. # app_label here for PolymorphicModel.
meta = attrs.get('Meta', None) meta = attrs.get('Meta', None)
do_app_label_workaround = (meta do_app_label_workaround = (meta
and attrs['__module__'] == 'polymorphic' and attrs['__module__'] == 'polymorphic'
and model_name == 'PolymorphicModel' and model_name == 'PolymorphicModel'
and getattr(meta, 'app_label', None) is None) and getattr(meta, 'app_label', None) is None)
if do_app_label_workaround: if do_app_label_workaround:
meta.app_label = 'poly_dummy_app_label' meta.app_label = 'poly_dummy_app_label'
@ -241,8 +241,8 @@ class PolymorphicModelBase(ModelBase):
frm = inspect.stack()[1] # frm[1] is caller file name, frm[3] is caller function name frm = inspect.stack()[1] # frm[1] is caller file name, frm[3] is caller function name
if 'django/core/management/commands/dumpdata.py' in frm[1]: if 'django/core/management/commands/dumpdata.py' in frm[1]:
return self.base_objects return self.base_objects
#caller_mod_name = inspect.getmodule(frm[0]).__name__ # does not work with python 2.4 # caller_mod_name = inspect.getmodule(frm[0]).__name__ # does not work with python 2.4
#if caller_mod_name == 'django.core.management.commands.dumpdata': # if caller_mod_name == 'django.core.management.commands.dumpdata':
return super(PolymorphicModelBase, self).__getattribute__(name) return super(PolymorphicModelBase, self).__getattribute__(name)
# TODO: investigate Django how this can be avoided # TODO: investigate Django how this can be avoided

View File

@ -1,55 +1,2 @@
# -*- coding: utf-8 -*- # For compatibility with pre 0.8 versions
""" PolymorphicManager from .managers import PolymorphicQuerySet, PolymorphicManager
Please see README.rst or DOCS.rst or http://chrisglass.github.com/django_polymorphic/
"""
from __future__ import unicode_literals
import warnings
import django
from django.db import models
from polymorphic.query import PolymorphicQuerySet
class PolymorphicManager(models.Manager):
"""
Manager for PolymorphicModel
Usually not explicitly needed, except if a custom manager or
a custom queryset class is to be used.
"""
# Tell Django that related fields also need to use this manager:
use_for_related_fields = True
queryset_class = PolymorphicQuerySet
def __init__(self, queryset_class=None, *args, **kwrags):
# Up till polymorphic 0.4, the queryset class could be specified as parameter to __init__.
# However, this doesn't work for related managers which instantiate a new version of this class.
# Hence, for custom managers the new default is using the 'queryset_class' attribute at class level instead.
if queryset_class:
warnings.warn("Using PolymorphicManager(queryset_class=..) is deprecated; override the queryset_class attribute instead", DeprecationWarning)
# For backwards compatibility, still allow the parameter:
self.queryset_class = queryset_class
super(PolymorphicManager, self).__init__(*args, **kwrags)
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.
# We exclude any special functions (__) from this automatic proxying.
#
# NOTE: Fetching the queryset is done by calling self.all() here on purpose.
# By using .all(), the proper get_query_set()/get_queryset() will be used for each Django version.
# Django 1.4/1.5 need to use get_query_set(), because the RelatedManager overrides that.
def __getattr__(self, name):
if name.startswith('__'):
return super(PolymorphicManager, self).__getattr__(self, name)
return getattr(self.all(), name)
def __unicode__(self):
return '%s (PolymorphicManager) using %s' % (self.__class__.__name__, self.queryset_class.__name__)

View File

@ -0,0 +1,55 @@
# -*- coding: utf-8 -*-
""" PolymorphicManager
Please see README.rst or DOCS.rst or http://chrisglass.github.com/django_polymorphic/
"""
from __future__ import unicode_literals
import warnings
import django
from django.db import models
from polymorphic.query import PolymorphicQuerySet
class PolymorphicManager(models.Manager):
"""
Manager for PolymorphicModel
Usually not explicitly needed, except if a custom manager or
a custom queryset class is to be used.
"""
# Tell Django that related fields also need to use this manager:
use_for_related_fields = True
queryset_class = PolymorphicQuerySet
def __init__(self, queryset_class=None, *args, **kwrags):
# Up till polymorphic 0.4, the queryset class could be specified as parameter to __init__.
# However, this doesn't work for related managers which instantiate a new version of this class.
# Hence, for custom managers the new default is using the 'queryset_class' attribute at class level instead.
if queryset_class:
warnings.warn("Using PolymorphicManager(queryset_class=..) is deprecated; override the queryset_class attribute instead", DeprecationWarning)
# For backwards compatibility, still allow the parameter:
self.queryset_class = queryset_class
super(PolymorphicManager, self).__init__(*args, **kwrags)
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
def __unicode__(self):
return '%s (PolymorphicManager) using %s' % (self.__class__.__name__, self.queryset_class.__name__)
# Proxied methods
def non_polymorphic(self):
return self.all().non_polymorphic()
def instance_of(self, *args):
return self.all().instance_of(*args)
def not_instance_of(self, *args):
return self.all().not_instance_of(*args)
def get_real_instances(self, base_result_objects=None):
return self.all().get_real_instances(base_result_objects=base_result_objects)

View File

@ -20,11 +20,12 @@ 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
from .manager import PolymorphicManager from .managers import PolymorphicManager
from .query_translate import translate_polymorphic_Q_object from .query_translate import translate_polymorphic_Q_object
################################################################################### ###################################################################################
### PolymorphicModel # PolymorphicModel
class PolymorphicModel(six.with_metaclass(PolymorphicModelBase, models.Model)): class PolymorphicModel(six.with_metaclass(PolymorphicModelBase, models.Model)):
""" """
@ -56,7 +57,7 @@ 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)
polymorphic_ctype = models.ForeignKey(ContentType, null=True, editable=False, polymorphic_ctype = models.ForeignKey(ContentType, null=True, editable=False,
related_name='polymorphic_%(app_label)s.%(class)s_set+') 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']
@ -110,8 +111,8 @@ class PolymorphicModel(six.with_metaclass(PolymorphicModelBase, models.Model)):
# 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.
if model is not None \ if model is not None \
and not issubclass(model, self.__class__) \ and not issubclass(model, self.__class__) \
and not issubclass(model, self.__class__._meta.proxy_for_model): and not issubclass(model, self.__class__._meta.proxy_for_model):
raise RuntimeError("ContentType {0} for {1} #{2} does not point to a subclass!".format( raise RuntimeError("ContentType {0} for {1} #{2} does not point to a subclass!".format(
self.polymorphic_ctype_id, model, self.pk, self.polymorphic_ctype_id, model, self.pk,
)) ))
@ -196,31 +197,31 @@ class PolymorphicModel(six.with_metaclass(PolymorphicModelBase, models.Model)):
def add_model_if_regular(model, field_name, result): def add_model_if_regular(model, field_name, result):
if (issubclass(model, models.Model) if (issubclass(model, models.Model)
and model != models.Model and model != models.Model
and model != self.__class__ and model != self.__class__
and model != PolymorphicModel): and model != PolymorphicModel):
add_model(model, field_name, result) add_model(model, field_name, result)
def add_all_super_models(model, result): def add_all_super_models(model, result):
for super_cls, field_to_super in model._meta.parents.items(): for super_cls, field_to_super in model._meta.parents.items():
if field_to_super is not None: #if not a link to a proxy model if field_to_super is not None: # if not a link to a proxy model
field_name = field_to_super.name #the field on model can have a different name to super_cls._meta.module_name, if the field is created manually using 'parent_link' field_name = field_to_super.name # the field on model can have a different name to super_cls._meta.module_name, if the field is created manually using 'parent_link'
add_model_if_regular(super_cls, field_name, result) add_model_if_regular(super_cls, field_name, result)
add_all_super_models(super_cls, result) add_all_super_models(super_cls, result)
def add_all_sub_models(super_cls, result): def add_all_sub_models(super_cls, result):
for sub_cls in super_cls.__subclasses__(): #go through all subclasses of model for sub_cls in super_cls.__subclasses__(): # go through all subclasses of model
if super_cls in sub_cls._meta.parents: #super_cls may not be in sub_cls._meta.parents if super_cls is a proxy model if super_cls in sub_cls._meta.parents: # super_cls may not be in sub_cls._meta.parents if super_cls is a proxy model
field_to_super = sub_cls._meta.parents[super_cls] #get the field that links sub_cls to super_cls field_to_super = sub_cls._meta.parents[super_cls] # get the field that links sub_cls to super_cls
if field_to_super is not None: # if filed_to_super is not a link to a proxy model if field_to_super is not None: # if filed_to_super is not a link to a proxy model
super_to_sub_related_field = field_to_super.rel super_to_sub_related_field = field_to_super.rel
if super_to_sub_related_field.related_name is None: if super_to_sub_related_field.related_name is None:
#if related name is None the related field is the name of the subclass # if related name is None the related field is the name of the subclass
to_subclass_fieldname = sub_cls.__name__.lower() to_subclass_fieldname = sub_cls.__name__.lower()
else: else:
#otherwise use the given related name # otherwise use the given related name
to_subclass_fieldname = super_to_sub_related_field.related_name to_subclass_fieldname = super_to_sub_related_field.related_name
add_model_if_regular(sub_cls, to_subclass_fieldname, result) add_model_if_regular(sub_cls, to_subclass_fieldname, result)
result = {} result = {}

View File

@ -35,13 +35,13 @@ def transmogrify(cls, obj):
else: else:
# Run constructor, reassign values # Run constructor, reassign values
new = cls() new = cls()
for k,v in obj.__dict__.items(): for k, v in obj.__dict__.items():
new.__dict__[k] = v new.__dict__[k] = v
return new return new
################################################################################### ###################################################################################
### PolymorphicQuerySet # PolymorphicQuerySet
def _query_annotations(query): def _query_annotations(query):
try: try:
@ -72,17 +72,17 @@ class PolymorphicQuerySet(QuerySet):
new.polymorphic_disabled = self.polymorphic_disabled new.polymorphic_disabled = self.polymorphic_disabled
return new return new
if django.VERSION >= (1,7): if django.VERSION >= (1, 7):
def as_manager(cls): def as_manager(cls):
# Make sure the Django 1.7 way of creating managers works. # Make sure the Django 1.7 way of creating managers works.
from .manager 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
return manager return manager
as_manager.queryset_only = True as_manager.queryset_only = True
as_manager = classmethod(as_manager) as_manager = classmethod(as_manager)
def non_polymorphic(self, *args, **kwargs): def non_polymorphic(self):
"""switch off polymorphic behaviour for this query. """switch off polymorphic behaviour for this query.
When the queryset is evaluated, only objects of the type of the When the queryset is evaluated, only objects of the type of the
base class used for this query are returned.""" base class used for this query are returned."""
@ -160,7 +160,7 @@ class PolymorphicQuerySet(QuerySet):
# The resulting objects are required to have a unique primary key within the result set # The resulting objects are required to have a unique primary key within the result set
# (otherwise an error is thrown). # (otherwise an error is thrown).
# The "polymorphic" keyword argument is not supported anymore. # The "polymorphic" keyword argument is not supported anymore.
#def extra(self, *args, **kwargs): # def extra(self, *args, **kwargs):
def _get_real_instances(self, base_result_objects): def _get_real_instances(self, base_result_objects):
""" """
@ -333,14 +333,15 @@ class PolymorphicQuerySet(QuerySet):
def __repr__(self, *args, **kwargs): def __repr__(self, *args, **kwargs):
if self.model.polymorphic_query_multiline_output: if self.model.polymorphic_query_multiline_output:
result = [repr(o) for o in self.all()] result = [repr(o) for o in self.all()]
return '[ ' + ',\n '.join(result) + ' ]' return '[ ' + ',\n '.join(result) + ' ]'
else: else:
return super(PolymorphicQuerySet, self).__repr__(*args, **kwargs) return super(PolymorphicQuerySet, self).__repr__(*args, **kwargs)
class _p_list_class(list): class _p_list_class(list):
def __repr__(self, *args, **kwargs): def __repr__(self, *args, **kwargs):
result = [repr(o) for o in self] result = [repr(o) for o in self]
return '[ ' + ',\n '.join(result) + ' ]' return '[ ' + ',\n '.join(result) + ' ]'
def get_real_instances(self, base_result_objects=None): def get_real_instances(self, base_result_objects=None):
"same as _get_real_instances, but make sure that __repr__ for ShowField... creates correct output" "same as _get_real_instances, but make sure that __repr__ for ShowField... creates correct output"

View File

@ -4,24 +4,31 @@
""" """
from __future__ import absolute_import from __future__ import absolute_import
import django
from django.db import models 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.models import Q, FieldDoesNotExist
try: from django.db.models.fields.related import RelatedField
from django.db.models.related import RelatedObject if django.VERSION < (1, 6):
except ImportError: # There was no common base class in Django 1.5, mention all variants here.
# django.db.models.related.RelatedObject was replaced from django.db.models.fields.related import RelatedObject, ManyToOneRel, ManyToManyRel
# by django.db.models.fields.related.ForeignObjectRel in REL_FIELD_CLASSES = (RelatedField, RelatedObject, ManyToOneRel, ManyToManyRel) # Leaving GenericRel out.
# Django 1.8 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 from django.db.models.fields.related import ForeignObjectRel
RelatedObject = ForeignObjectRel REL_FIELD_CLASSES = (RelatedField, ForeignObjectRel)
from functools import reduce from functools import reduce
################################################################################### ###################################################################################
### PolymorphicQuerySet support functions # PolymorphicQuerySet support functions
# These functions implement the additional filter- and Q-object functionality. # These functions implement the additional filter- and Q-object functionality.
# They form a kind of small framework for easily adding more # They form a kind of small framework for easily adding more
@ -161,9 +168,13 @@ 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:
# rel = (field_object, model, direct, m2m) if django.VERSION >= (1, 8):
field = queryset_model._meta.get_field(classname) # This also retreives M2M relations now (including reverse foreign key relations)
if isinstance(field, RelatedObject): field = queryset_model._meta.get_field(classname)
else:
field = queryset_model._meta.get_field_by_name(classname)[0]
if isinstance(field, REL_FIELD_CLASSES):
# 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.
@ -248,7 +259,7 @@ def _create_model_filter_Q(modellist, not_instance_of=False):
q = q | q_class_with_subclasses(subclass) q = q | q_class_with_subclasses(subclass)
return q return q
qlist = [q_class_with_subclasses(m) for m in modellist] qlist = [q_class_with_subclasses(m) for m in modellist]
q_ored = reduce(lambda a, b: a | b, qlist) q_ored = reduce(lambda a, b: a | b, qlist)
if not_instance_of: if not_instance_of:

View File

@ -3,6 +3,7 @@
from django.db import models from django.db import models
from django.utils import six from django.utils import six
class ShowFieldBase(object): class ShowFieldBase(object):
""" base class for the ShowField... model mixins, does the work """ """ base class for the ShowField... model mixins, does the work """
@ -119,8 +120,8 @@ class ShowFieldBase(object):
next_new_section, _, _ = parts[i + 1] next_new_section, _, _ = parts[i + 1]
if (self.polymorphic_showfield_max_line_width if (self.polymorphic_showfield_max_line_width
and xpos + len(p) > self.polymorphic_showfield_max_line_width and xpos + len(p) > self.polymorphic_showfield_max_line_width
and possible_line_break_pos != None): and possible_line_break_pos != None):
rest = out[possible_line_break_pos:] rest = out[possible_line_break_pos:]
out = out[:possible_line_break_pos] out = out[:possible_line_break_pos]
out += '\n' + indentstr + rest out += '\n' + indentstr + rest

View File

@ -5,6 +5,7 @@ register = Library()
class BreadcrumbScope(Node): class BreadcrumbScope(Node):
def __init__(self, base_opts, nodelist): def __init__(self, base_opts, nodelist):
self.base_opts = base_opts self.base_opts = base_opts
self.nodelist = nodelist # Note, takes advantage of Node.child_nodelists self.nodelist = nodelist # Note, takes advantage of Node.child_nodelists
@ -25,7 +26,6 @@ class BreadcrumbScope(Node):
else: else:
raise TemplateSyntaxError("{0} tag expects 1 argument".format(token.contents[0])) raise TemplateSyntaxError("{0} tag expects 1 argument".format(token.contents[0]))
def render(self, context): def render(self, context):
# app_label is really hard to overwrite in the standard Django ModelAdmin. # app_label is really hard to overwrite in the standard Django ModelAdmin.
# To insert it in the template, the entire render_change_form() and delete_view() have to copied and adjusted. # To insert it in the template, the entire render_change_form() and delete_view() have to copied and adjusted.

View File

@ -14,15 +14,15 @@ except ImportError:
from django.db.models.query import QuerySet from django.db.models.query import QuerySet
from django.test import TestCase from django.test import TestCase
from django.db.models import Q,Count from django.db.models import Q, Count
from django.db import models from django.db import models
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
from django.utils import six from django.utils import six
from polymorphic.models import PolymorphicModel from polymorphic.models import PolymorphicModel
from polymorphic.manager import PolymorphicManager from polymorphic.managers import PolymorphicManager
from polymorphic.query import PolymorphicQuerySet from polymorphic.query import PolymorphicQuerySet
from polymorphic import ShowFieldContent, ShowFieldType, ShowFieldTypeAndContent from polymorphic.showfields import ShowFieldContent, ShowFieldType, ShowFieldTypeAndContent
try: try:
from django.db.models import UUIDField from django.db.models import UUIDField
except ImportError: except ImportError:
@ -32,94 +32,156 @@ except ImportError:
class PlainA(models.Model): class PlainA(models.Model):
field1 = models.CharField(max_length=10) field1 = models.CharField(max_length=10)
class PlainB(PlainA): class PlainB(PlainA):
field2 = models.CharField(max_length=10) field2 = models.CharField(max_length=10)
class PlainC(PlainB): class PlainC(PlainB):
field3 = models.CharField(max_length=10) field3 = models.CharField(max_length=10)
class Model2A(ShowFieldType, PolymorphicModel): class Model2A(ShowFieldType, PolymorphicModel):
field1 = models.CharField(max_length=10) field1 = models.CharField(max_length=10)
class Model2B(Model2A): class Model2B(Model2A):
field2 = models.CharField(max_length=10) field2 = models.CharField(max_length=10)
class Model2C(Model2B): class Model2C(Model2B):
field3 = models.CharField(max_length=10) field3 = models.CharField(max_length=10)
class Model2D(Model2C): class Model2D(Model2C):
field4 = models.CharField(max_length=10) field4 = models.CharField(max_length=10)
class ModelExtraA(ShowFieldTypeAndContent, PolymorphicModel): class ModelExtraA(ShowFieldTypeAndContent, PolymorphicModel):
field1 = models.CharField(max_length=10) field1 = models.CharField(max_length=10)
class ModelExtraB(ModelExtraA): class ModelExtraB(ModelExtraA):
field2 = models.CharField(max_length=10) field2 = models.CharField(max_length=10)
class ModelExtraC(ModelExtraB): class ModelExtraC(ModelExtraB):
field3 = models.CharField(max_length=10) field3 = models.CharField(max_length=10)
class ModelExtraExternal(models.Model): class ModelExtraExternal(models.Model):
topic = models.CharField(max_length=10) topic = models.CharField(max_length=10)
class ModelShow1(ShowFieldType,PolymorphicModel):
class ModelShow1(ShowFieldType, PolymorphicModel):
field1 = models.CharField(max_length=10) field1 = models.CharField(max_length=10)
m2m = models.ManyToManyField('self') m2m = models.ManyToManyField('self')
class ModelShow2(ShowFieldContent, PolymorphicModel): class ModelShow2(ShowFieldContent, PolymorphicModel):
field1 = models.CharField(max_length=10) field1 = models.CharField(max_length=10)
m2m = models.ManyToManyField('self') m2m = models.ManyToManyField('self')
class ModelShow3(ShowFieldTypeAndContent, PolymorphicModel): class ModelShow3(ShowFieldTypeAndContent, PolymorphicModel):
field1 = models.CharField(max_length=10) field1 = models.CharField(max_length=10)
m2m = models.ManyToManyField('self') m2m = models.ManyToManyField('self')
class ModelShow1_plain(PolymorphicModel): class ModelShow1_plain(PolymorphicModel):
field1 = models.CharField(max_length=10) field1 = models.CharField(max_length=10)
class ModelShow2_plain(ModelShow1_plain): class ModelShow2_plain(ModelShow1_plain):
field2 = models.CharField(max_length=10) field2 = models.CharField(max_length=10)
class Base(ShowFieldType, PolymorphicModel): class Base(ShowFieldType, PolymorphicModel):
field_b = models.CharField(max_length=10) field_b = models.CharField(max_length=10)
class ModelX(Base): class ModelX(Base):
field_x = models.CharField(max_length=10) field_x = models.CharField(max_length=10)
class ModelY(Base): class ModelY(Base):
field_y = models.CharField(max_length=10) field_y = models.CharField(max_length=10)
class Enhance_Plain(models.Model): class Enhance_Plain(models.Model):
field_p = models.CharField(max_length=10) field_p = models.CharField(max_length=10)
class Enhance_Base(ShowFieldTypeAndContent, PolymorphicModel): class Enhance_Base(ShowFieldTypeAndContent, PolymorphicModel):
base_id = models.AutoField(primary_key=True) base_id = models.AutoField(primary_key=True)
field_b = models.CharField(max_length=10) field_b = models.CharField(max_length=10)
class Enhance_Inherit(Enhance_Base, Enhance_Plain): class Enhance_Inherit(Enhance_Base, Enhance_Plain):
field_i = models.CharField(max_length=10) field_i = models.CharField(max_length=10)
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', null=True, related_name='relationbase_set')
m2m = models.ManyToManyField('self') m2m = models.ManyToManyField('self')
class RelationA(RelationBase): class RelationA(RelationBase):
field_a = models.CharField(max_length=10) field_a = models.CharField(max_length=10)
class RelationB(RelationBase): class RelationB(RelationBase):
field_b = models.CharField(max_length=10) field_b = models.CharField(max_length=10)
class RelationBC(RelationB): class RelationBC(RelationB):
field_c = models.CharField(max_length=10) field_c = models.CharField(max_length=10)
class RelatingModel(models.Model): class RelatingModel(models.Model):
many2many = models.ManyToManyField(Model2A) many2many = models.ManyToManyField(Model2A)
class One2OneRelatingModel(PolymorphicModel): class One2OneRelatingModel(PolymorphicModel):
one2one = models.OneToOneField(Model2A) one2one = models.OneToOneField(Model2A)
field1 = models.CharField(max_length=10) field1 = models.CharField(max_length=10)
class One2OneRelatingModelDerived(One2OneRelatingModel): class One2OneRelatingModelDerived(One2OneRelatingModel):
field2 = models.CharField(max_length=10) field2 = models.CharField(max_length=10)
class ModelUnderRelParent(PolymorphicModel):
field1 = models.CharField(max_length=10)
_private = models.CharField(max_length=10)
class ModelUnderRelChild(PolymorphicModel):
parent = models.ForeignKey(ModelUnderRelParent, related_name='children')
_private2 = models.CharField(max_length=10)
class MyManagerQuerySet(PolymorphicQuerySet): class MyManagerQuerySet(PolymorphicQuerySet):
def my_queryset_foo(self): def my_queryset_foo(self):
return self.all() # Just a method to prove the existance of the custom queryset. return self.all() # Just a method to prove the existance of the custom queryset.
class MyManager(PolymorphicManager): class MyManager(PolymorphicManager):
queryset_class = MyManagerQuerySet queryset_class = MyManagerQuerySet
def get_queryset(self): def get_queryset(self):
return super(MyManager, self).get_queryset().order_by('-field1') return super(MyManager, self).get_queryset().order_by('-field1')
def my_queryset_foo(self):
return self.all().my_queryset_foo()
# Django <= 1.5 compatibility # Django <= 1.5 compatibility
get_query_set = get_queryset get_query_set = get_queryset
class ModelWithMyManager(ShowFieldTypeAndContent, Model2A): class ModelWithMyManager(ShowFieldTypeAndContent, Model2A):
objects = MyManager() objects = MyManager()
field4 = models.CharField(max_length=10) field4 = models.CharField(max_length=10)
@ -136,23 +198,33 @@ class ModelWithMyManagerDefault(ShowFieldTypeAndContent, Model2A):
field4 = models.CharField(max_length=10) field4 = models.CharField(max_length=10)
if django.VERSION >= (1,7): 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)
class MROBase1(ShowFieldType, PolymorphicModel): class MROBase1(ShowFieldType, PolymorphicModel):
objects = MyManager() objects = MyManager()
field1 = models.CharField(max_length=10) # needed as MyManager uses it field1 = models.CharField(max_length=10) # needed as MyManager uses it
class MROBase2(MROBase1): class MROBase2(MROBase1):
pass # Django vanilla inheritance does not inherit MyManager as _default_manager here pass # Django vanilla inheritance does not inherit MyManager as _default_manager here
class MROBase3(models.Model): class MROBase3(models.Model):
objects = PolymorphicManager() objects = PolymorphicManager()
class MRODerived(MROBase2, MROBase3): class MRODerived(MROBase2, MROBase3):
pass pass
class ParentModelWithManager(PolymorphicModel): class ParentModelWithManager(PolymorphicModel):
pass pass
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, related_name='childmodel_set')
@ -160,10 +232,13 @@ class ChildModelWithManager(PolymorphicModel):
class PlainMyManagerQuerySet(QuerySet): class PlainMyManagerQuerySet(QuerySet):
def my_queryset_foo(self): def my_queryset_foo(self):
return self.all() # Just a method to prove the existance of the custom queryset. return self.all() # Just a method to prove the existance of the custom queryset.
class PlainMyManager(models.Manager): class PlainMyManager(models.Manager):
def my_queryset_foo(self): def my_queryset_foo(self):
return self.get_queryset().my_queryset_foo() return self.get_queryset().my_queryset_foo()
@ -173,9 +248,11 @@ class PlainMyManager(models.Manager):
# Django <= 1.5 compatibility # Django <= 1.5 compatibility
get_query_set = get_queryset 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, related_name='childmodel_set')
objects = PlainMyManager() objects = PlainMyManager()
@ -185,88 +262,138 @@ class MgrInheritA(models.Model):
mgrA = models.Manager() mgrA = models.Manager()
mgrA2 = models.Manager() mgrA2 = models.Manager()
field1 = models.CharField(max_length=10) field1 = models.CharField(max_length=10)
class MgrInheritB(MgrInheritA): class MgrInheritB(MgrInheritA):
mgrB = models.Manager() mgrB = models.Manager()
field2 = models.CharField(max_length=10) field2 = models.CharField(max_length=10)
class MgrInheritC(ShowFieldTypeAndContent, MgrInheritB): class MgrInheritC(ShowFieldTypeAndContent, MgrInheritB):
pass pass
class BlogBase(ShowFieldTypeAndContent, PolymorphicModel): class BlogBase(ShowFieldTypeAndContent, PolymorphicModel):
name = models.CharField(max_length=10) name = models.CharField(max_length=10)
class BlogA(BlogBase): class BlogA(BlogBase):
info = models.CharField(max_length=10) info = models.CharField(max_length=10)
class BlogB(BlogBase): class BlogB(BlogBase):
pass pass
class BlogEntry(ShowFieldTypeAndContent, PolymorphicModel): class BlogEntry(ShowFieldTypeAndContent, PolymorphicModel):
blog = models.ForeignKey(BlogA) blog = models.ForeignKey(BlogA)
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)
text = models.CharField(max_length=10) text = models.CharField(max_length=10)
class ModelFieldNameTest(ShowFieldType, PolymorphicModel): class ModelFieldNameTest(ShowFieldType, PolymorphicModel):
modelfieldnametest = models.CharField(max_length=10) modelfieldnametest = models.CharField(max_length=10)
class InitTestModel(ShowFieldType, PolymorphicModel): class InitTestModel(ShowFieldType, PolymorphicModel):
bar = models.CharField(max_length=100) bar = models.CharField(max_length=100)
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
kwargs['bar'] = self.x() kwargs['bar'] = self.x()
super(InitTestModel, self).__init__(*args, **kwargs) super(InitTestModel, self).__init__(*args, **kwargs)
class InitTestModelSubclass(InitTestModel): class InitTestModelSubclass(InitTestModel):
def x(self): def x(self):
return 'XYZ' return 'XYZ'
# models from github issue # models from github issue
class Top(PolymorphicModel): class Top(PolymorphicModel):
name = models.CharField(max_length=50) name = models.CharField(max_length=50)
class Meta: class Meta:
ordering = ('pk',) ordering = ('pk',)
class Middle(Top): class Middle(Top):
description = models.TextField() description = models.TextField()
class Bottom(Middle): class Bottom(Middle):
author = models.CharField(max_length=50) author = models.CharField(max_length=50)
class UUIDProject(ShowFieldTypeAndContent, PolymorphicModel): class UUIDProject(ShowFieldTypeAndContent, PolymorphicModel):
uuid_primary_key = UUIDField(primary_key = True, default=uuid.uuid1) uuid_primary_key = UUIDField(primary_key=True, default=uuid.uuid1)
topic = models.CharField(max_length = 30) topic = models.CharField(max_length=30)
class UUIDArtProject(UUIDProject): class UUIDArtProject(UUIDProject):
artist = models.CharField(max_length = 30) artist = models.CharField(max_length=30)
class UUIDResearchProject(UUIDProject): class UUIDResearchProject(UUIDProject):
supervisor = models.CharField(max_length = 30) supervisor = models.CharField(max_length=30)
class UUIDPlainA(models.Model): class UUIDPlainA(models.Model):
uuid_primary_key = UUIDField(primary_key = True, default=uuid.uuid1) uuid_primary_key = UUIDField(primary_key=True, default=uuid.uuid1)
field1 = models.CharField(max_length=10) field1 = models.CharField(max_length=10)
class UUIDPlainB(UUIDPlainA): class UUIDPlainB(UUIDPlainA):
field2 = models.CharField(max_length=10) field2 = models.CharField(max_length=10)
class UUIDPlainC(UUIDPlainB): class UUIDPlainC(UUIDPlainB):
field3 = models.CharField(max_length=10) field3 = models.CharField(max_length=10)
# base -> proxy # base -> proxy
class ProxyBase(PolymorphicModel): class ProxyBase(PolymorphicModel):
some_data = models.CharField(max_length=128) some_data = models.CharField(max_length=128)
class ProxyChild(ProxyBase): class ProxyChild(ProxyBase):
class Meta: class Meta:
proxy = True proxy = True
class NonProxyChild(ProxyBase): class NonProxyChild(ProxyBase):
name=models.CharField(max_length=10) name = models.CharField(max_length=10)
# base -> proxy -> real models # base -> proxy -> real models
class ProxiedBase(ShowFieldTypeAndContent, PolymorphicModel): class ProxiedBase(ShowFieldTypeAndContent, PolymorphicModel):
name = models.CharField(max_length=10) name = models.CharField(max_length=10)
class ProxyModelBase(ProxiedBase): class ProxyModelBase(ProxiedBase):
class Meta: class Meta:
proxy = True proxy = True
class ProxyModelA(ProxyModelBase): class ProxyModelA(ProxyModelBase):
field1 = models.CharField(max_length=10) field1 = models.CharField(max_length=10)
class ProxyModelB(ProxyModelBase): class ProxyModelB(ProxyModelBase):
field2 = models.CharField(max_length=10) field2 = models.CharField(max_length=10)
# test bad field name # test bad field name
#class TestBadFieldModel(ShowFieldType, PolymorphicModel): # class TestBadFieldModel(ShowFieldType, PolymorphicModel):
# instance_of = models.CharField(max_length=10) # instance_of = models.CharField(max_length=10)
# validation error: "polymorphic.relatednameclash: Accessor for field 'polymorphic_ctype' clashes # validation error: "polymorphic.relatednameclash: Accessor for field 'polymorphic_ctype' clashes
@ -275,12 +402,17 @@ class ProxyModelB(ProxyModelBase):
class RelatedNameClash(ShowFieldType, PolymorphicModel): class RelatedNameClash(ShowFieldType, PolymorphicModel):
ctype = models.ForeignKey(ContentType, null=True, editable=False) ctype = models.ForeignKey(ContentType, 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, parent_link=True, related_name='related_name_subclass')
class CustomPkBase(ShowFieldTypeAndContent, PolymorphicModel): class CustomPkBase(ShowFieldTypeAndContent, PolymorphicModel):
b = models.CharField(max_length=1) b = models.CharField(max_length=1)
class CustomPkInherit(CustomPkBase): class CustomPkInherit(CustomPkBase):
custom_id = models.AutoField(primary_key=True) custom_id = models.AutoField(primary_key=True)
i = models.CharField(max_length=1) i = models.CharField(max_length=1)
@ -335,7 +467,7 @@ class PolymorphicTests(TestCase):
# test ordering for field in one subclass only # test ordering for field in one subclass only
# MySQL and SQLite return this order # MySQL and SQLite return this order
expected1=''' expected1 = '''
[ <BlogA: id 8, name (CharField) "B5", info (CharField) "i5">, [ <BlogA: id 8, name (CharField) "B5", info (CharField) "i5">,
<BlogA: id 7, name (CharField) "B4", info (CharField) "i4">, <BlogA: id 7, name (CharField) "B4", info (CharField) "i4">,
<BlogA: id 6, name (CharField) "B3", info (CharField) "i3">, <BlogA: id 6, name (CharField) "B3", info (CharField) "i3">,
@ -346,7 +478,7 @@ class PolymorphicTests(TestCase):
<BlogB: id 4, name (CharField) "Bb3"> ]''' <BlogB: id 4, name (CharField) "Bb3"> ]'''
# PostgreSQL returns this order # PostgreSQL returns this order
expected2=''' expected2 = '''
[ <BlogB: id 2, name (CharField) "Bb1">, [ <BlogB: id 2, name (CharField) "Bb1">,
<BlogB: id 3, name (CharField) "Bb2">, <BlogB: id 3, name (CharField) "Bb2">,
<BlogB: id 4, name (CharField) "Bb3">, <BlogB: id 4, name (CharField) "Bb3">,
@ -359,7 +491,6 @@ class PolymorphicTests(TestCase):
x = '\n' + repr(BlogBase.objects.order_by('-BlogA___info')) x = '\n' + repr(BlogBase.objects.order_by('-BlogA___info'))
self.assertTrue(x == expected1 or x == expected2) self.assertTrue(x == expected1 or x == expected2)
def test_limit_choices_to(self): def test_limit_choices_to(self):
""" """
this is not really a testcase, as limit_choices_to only affects the Django admin this is not really a testcase, as limit_choices_to only affects the Django admin
@ -371,7 +502,6 @@ class PolymorphicTests(TestCase):
entry1 = BlogEntry_limit_choices_to.objects.create(blog=blog_b, text='bla2') entry1 = BlogEntry_limit_choices_to.objects.create(blog=blog_b, text='bla2')
entry2 = BlogEntry_limit_choices_to.objects.create(blog=blog_b, text='bla2') entry2 = BlogEntry_limit_choices_to.objects.create(blog=blog_b, text='bla2')
def test_primary_key_custom_field_problem(self): def test_primary_key_custom_field_problem(self):
""" """
object retrieval problem occuring with some custom primary key fields (UUIDField as test case) object retrieval problem occuring with some custom primary key fields (UUIDField as test case)
@ -389,7 +519,7 @@ class PolymorphicTests(TestCase):
self.assertIsInstance(a.uuid_primary_key, uuid.UUID) self.assertIsInstance(a.uuid_primary_key, uuid.UUID)
self.assertIsInstance(a.pk, uuid.UUID) self.assertIsInstance(a.pk, uuid.UUID)
res = re.sub(' "(.*?)..", topic',', topic', repr(qs)) res = re.sub(' "(.*?)..", topic', ', topic', repr(qs))
res_exp = """[ <UUIDProject: uuid_primary_key (UUIDField/pk), topic (CharField) "John's gathering">, res_exp = """[ <UUIDProject: uuid_primary_key (UUIDField/pk), topic (CharField) "John's gathering">,
<UUIDArtProject: uuid_primary_key (UUIDField/pk), topic (CharField) "Sculpting with Tim", artist (CharField) "T. Turner">, <UUIDArtProject: uuid_primary_key (UUIDField/pk), topic (CharField) "Sculpting with Tim", artist (CharField) "T. Turner">,
<UUIDResearchProject: uuid_primary_key (UUIDField/pk), topic (CharField) "Swallow Aerodynamics", supervisor (CharField) "Dr. Winter"> ]""" <UUIDResearchProject: uuid_primary_key (UUIDField/pk), topic (CharField) "Swallow Aerodynamics", supervisor (CharField) "Dr. Winter"> ]"""
@ -413,7 +543,6 @@ class PolymorphicTests(TestCase):
Model2C.objects.create(field1='C1', field2='C2', field3='C3') Model2C.objects.create(field1='C1', field2='C2', field3='C3')
Model2D.objects.create(field1='D1', field2='D2', field3='D3', field4='D4') Model2D.objects.create(field1='D1', field2='D2', field3='D3', field4='D4')
def test_simple_inheritance(self): def test_simple_inheritance(self):
self.create_model2abcd() self.create_model2abcd()
@ -423,14 +552,12 @@ class PolymorphicTests(TestCase):
self.assertEqual(repr(objects[2]), '<Model2C: id 3, field1 (CharField), field2 (CharField), field3 (CharField)>') self.assertEqual(repr(objects[2]), '<Model2C: id 3, field1 (CharField), field2 (CharField), field3 (CharField)>')
self.assertEqual(repr(objects[3]), '<Model2D: id 4, field1 (CharField), field2 (CharField), field3 (CharField), field4 (CharField)>') self.assertEqual(repr(objects[3]), '<Model2D: id 4, field1 (CharField), field2 (CharField), field3 (CharField), field4 (CharField)>')
def test_manual_get_real_instance(self): def test_manual_get_real_instance(self):
self.create_model2abcd() self.create_model2abcd()
o = Model2A.objects.non_polymorphic().get(field1='C1') o = Model2A.objects.non_polymorphic().get(field1='C1')
self.assertEqual(repr(o.get_real_instance()), '<Model2C: id 3, field1 (CharField), field2 (CharField), field3 (CharField)>') self.assertEqual(repr(o.get_real_instance()), '<Model2C: id 3, field1 (CharField), field2 (CharField), field3 (CharField)>')
def test_non_polymorphic(self): def test_non_polymorphic(self):
self.create_model2abcd() self.create_model2abcd()
@ -440,7 +567,6 @@ class PolymorphicTests(TestCase):
self.assertEqual(repr(objects[2]), '<Model2A: id 3, field1 (CharField)>') self.assertEqual(repr(objects[2]), '<Model2A: id 3, field1 (CharField)>')
self.assertEqual(repr(objects[3]), '<Model2A: id 4, field1 (CharField)>') self.assertEqual(repr(objects[3]), '<Model2A: id 4, field1 (CharField)>')
def test_get_real_instances(self): def test_get_real_instances(self):
self.create_model2abcd() self.create_model2abcd()
qs = Model2A.objects.all().non_polymorphic() qs = Model2A.objects.all().non_polymorphic()
@ -459,7 +585,6 @@ class PolymorphicTests(TestCase):
self.assertEqual(repr(objects[2]), '<Model2C: id 3, field1 (CharField), field2 (CharField), field3 (CharField)>') self.assertEqual(repr(objects[2]), '<Model2C: id 3, field1 (CharField), field2 (CharField), field3 (CharField)>')
self.assertEqual(repr(objects[3]), '<Model2D: id 4, field1 (CharField), field2 (CharField), field3 (CharField), field4 (CharField)>') self.assertEqual(repr(objects[3]), '<Model2D: id 4, field1 (CharField), field2 (CharField), field3 (CharField), field4 (CharField)>')
def test_translate_polymorphic_q_object(self): def test_translate_polymorphic_q_object(self):
self.create_model2abcd() self.create_model2abcd()
@ -468,7 +593,6 @@ class PolymorphicTests(TestCase):
self.assertEqual(repr(objects[0]), '<Model2C: id 3, field1 (CharField), field2 (CharField), field3 (CharField)>') self.assertEqual(repr(objects[0]), '<Model2C: id 3, field1 (CharField), field2 (CharField), field3 (CharField)>')
self.assertEqual(repr(objects[1]), '<Model2D: id 4, field1 (CharField), field2 (CharField), field3 (CharField), field4 (CharField)>') self.assertEqual(repr(objects[1]), '<Model2D: id 4, field1 (CharField), field2 (CharField), field3 (CharField), field4 (CharField)>')
def test_base_manager(self): def test_base_manager(self):
def show_base_manager(model): def show_base_manager(model):
return "{0} {1}".format( return "{0} {1}".format(
@ -480,14 +604,13 @@ class PolymorphicTests(TestCase):
self.assertEqual(show_base_manager(PlainB), "<class 'django.db.models.manager.Manager'> <class 'polymorphic.tests.PlainB'>") self.assertEqual(show_base_manager(PlainB), "<class 'django.db.models.manager.Manager'> <class 'polymorphic.tests.PlainB'>")
self.assertEqual(show_base_manager(PlainC), "<class 'django.db.models.manager.Manager'> <class 'polymorphic.tests.PlainC'>") self.assertEqual(show_base_manager(PlainC), "<class 'django.db.models.manager.Manager'> <class 'polymorphic.tests.PlainC'>")
self.assertEqual(show_base_manager(Model2A), "<class 'polymorphic.manager.PolymorphicManager'> <class 'polymorphic.tests.Model2A'>") self.assertEqual(show_base_manager(Model2A), "<class 'polymorphic.managers.PolymorphicManager'> <class 'polymorphic.tests.Model2A'>")
self.assertEqual(show_base_manager(Model2B), "<class 'django.db.models.manager.Manager'> <class 'polymorphic.tests.Model2B'>") self.assertEqual(show_base_manager(Model2B), "<class 'django.db.models.manager.Manager'> <class 'polymorphic.tests.Model2B'>")
self.assertEqual(show_base_manager(Model2C), "<class 'django.db.models.manager.Manager'> <class 'polymorphic.tests.Model2C'>") self.assertEqual(show_base_manager(Model2C), "<class 'django.db.models.manager.Manager'> <class 'polymorphic.tests.Model2C'>")
self.assertEqual(show_base_manager(One2OneRelatingModel), "<class 'polymorphic.manager.PolymorphicManager'> <class 'polymorphic.tests.One2OneRelatingModel'>") self.assertEqual(show_base_manager(One2OneRelatingModel), "<class 'polymorphic.managers.PolymorphicManager'> <class 'polymorphic.tests.One2OneRelatingModel'>")
self.assertEqual(show_base_manager(One2OneRelatingModelDerived), "<class 'django.db.models.manager.Manager'> <class 'polymorphic.tests.One2OneRelatingModelDerived'>") self.assertEqual(show_base_manager(One2OneRelatingModelDerived), "<class 'django.db.models.manager.Manager'> <class 'polymorphic.tests.One2OneRelatingModelDerived'>")
def test_instance_default_manager(self): def test_instance_default_manager(self):
def show_default_manager(instance): def show_default_manager(instance):
return "{0} {1}".format( return "{0} {1}".format(
@ -507,9 +630,9 @@ class PolymorphicTests(TestCase):
self.assertEqual(show_default_manager(plain_b), "<class 'django.db.models.manager.Manager'> <class 'polymorphic.tests.PlainB'>") self.assertEqual(show_default_manager(plain_b), "<class 'django.db.models.manager.Manager'> <class 'polymorphic.tests.PlainB'>")
self.assertEqual(show_default_manager(plain_c), "<class 'django.db.models.manager.Manager'> <class 'polymorphic.tests.PlainC'>") self.assertEqual(show_default_manager(plain_c), "<class 'django.db.models.manager.Manager'> <class 'polymorphic.tests.PlainC'>")
self.assertEqual(show_default_manager(model_2a), "<class 'polymorphic.manager.PolymorphicManager'> <class 'polymorphic.tests.Model2A'>") self.assertEqual(show_default_manager(model_2a), "<class 'polymorphic.managers.PolymorphicManager'> <class 'polymorphic.tests.Model2A'>")
self.assertEqual(show_default_manager(model_2b), "<class 'polymorphic.manager.PolymorphicManager'> <class 'polymorphic.tests.Model2B'>") self.assertEqual(show_default_manager(model_2b), "<class 'polymorphic.managers.PolymorphicManager'> <class 'polymorphic.tests.Model2B'>")
self.assertEqual(show_default_manager(model_2c), "<class 'polymorphic.manager.PolymorphicManager'> <class 'polymorphic.tests.Model2C'>") self.assertEqual(show_default_manager(model_2c), "<class 'polymorphic.managers.PolymorphicManager'> <class 'polymorphic.tests.Model2C'>")
def test_foreignkey_field(self): def test_foreignkey_field(self):
self.create_model2abcd() self.create_model2abcd()
@ -520,7 +643,6 @@ class PolymorphicTests(TestCase):
object2b = Model2B.base_objects.get(field1='C1') object2b = Model2B.base_objects.get(field1='C1')
self.assertEqual(repr(object2b.model2c), '<Model2C: id 3, field1 (CharField), field2 (CharField), field3 (CharField)>') self.assertEqual(repr(object2b.model2c), '<Model2C: id 3, field1 (CharField), field2 (CharField), field3 (CharField)>')
def test_onetoone_field(self): def test_onetoone_field(self):
self.create_model2abcd() self.create_model2abcd()
@ -534,7 +656,6 @@ class PolymorphicTests(TestCase):
self.assertEqual(repr(c.one2one), '<Model2C: id 3, field1 (CharField), field2 (CharField), field3 (CharField)>') self.assertEqual(repr(c.one2one), '<Model2C: id 3, field1 (CharField), field2 (CharField), field3 (CharField)>')
self.assertEqual(repr(a.one2onerelatingmodel), '<One2OneRelatingModelDerived: One2OneRelatingModelDerived object>') self.assertEqual(repr(a.one2onerelatingmodel), '<One2OneRelatingModelDerived: One2OneRelatingModelDerived object>')
def test_manytomany_field(self): def test_manytomany_field(self):
# Model 1 # Model 1
o = ModelShow1.objects.create(field1='abc') o = ModelShow1.objects.create(field1='abc')
@ -549,7 +670,7 @@ class PolymorphicTests(TestCase):
self.assertEqual(repr(ModelShow2.objects.all()), '[ <ModelShow2: id 1, field1 "abc", m2m 1> ]') self.assertEqual(repr(ModelShow2.objects.all()), '[ <ModelShow2: id 1, field1 "abc", m2m 1> ]')
# Model 3 # Model 3
o=ModelShow3.objects.create(field1='abc') o = ModelShow3.objects.create(field1='abc')
o.m2m.add(o) o.m2m.add(o)
o.save() o.save()
self.assertEqual(repr(ModelShow3.objects.all()), '[ <ModelShow3: id 1, field1 (CharField) "abc", m2m (ManyToManyField) 1> ]') self.assertEqual(repr(ModelShow3.objects.all()), '[ <ModelShow3: id 1, field1 (CharField) "abc", m2m (ManyToManyField) 1> ]')
@ -562,7 +683,6 @@ class PolymorphicTests(TestCase):
ModelShow2_plain.objects.create(field1='abc', field2='def') ModelShow2_plain.objects.create(field1='abc', field2='def')
self.assertEqual(repr(ModelShow1_plain.objects.all()), '[<ModelShow1_plain: ModelShow1_plain object>, <ModelShow2_plain: ModelShow2_plain object>]') self.assertEqual(repr(ModelShow1_plain.objects.all()), '[<ModelShow1_plain: ModelShow1_plain object>, <ModelShow2_plain: ModelShow2_plain object>]')
def test_extra_method(self): def test_extra_method(self):
self.create_model2abcd() self.create_model2abcd()
@ -581,7 +701,7 @@ class PolymorphicTests(TestCase):
ModelExtraExternal.objects.create(topic='extra1') ModelExtraExternal.objects.create(topic='extra1')
ModelExtraExternal.objects.create(topic='extra2') ModelExtraExternal.objects.create(topic='extra2')
ModelExtraExternal.objects.create(topic='extra3') ModelExtraExternal.objects.create(topic='extra3')
objects = ModelExtraA.objects.extra(tables=["polymorphic_modelextraexternal"], select={"topic":"polymorphic_modelextraexternal.topic"}, where=["polymorphic_modelextraa.id = polymorphic_modelextraexternal.id"]) objects = ModelExtraA.objects.extra(tables=["polymorphic_modelextraexternal"], select={"topic": "polymorphic_modelextraexternal.topic"}, where=["polymorphic_modelextraa.id = polymorphic_modelextraexternal.id"])
if six.PY3: if six.PY3:
self.assertEqual(repr(objects[0]), '<ModelExtraA: id 1, field1 (CharField) "A1" - Extra: topic (str) "extra1">') self.assertEqual(repr(objects[0]), '<ModelExtraA: id 1, field1 (CharField) "A1" - Extra: topic (str) "extra1">')
self.assertEqual(repr(objects[1]), '<ModelExtraB: id 2, field1 (CharField) "B1", field2 (CharField) "B2" - Extra: topic (str) "extra2">') self.assertEqual(repr(objects[1]), '<ModelExtraB: id 2, field1 (CharField) "B1", field2 (CharField) "B2" - Extra: topic (str) "extra2">')
@ -592,7 +712,6 @@ class PolymorphicTests(TestCase):
self.assertEqual(repr(objects[2]), '<ModelExtraC: id 3, field1 (CharField) "C1", field2 (CharField) "C2", field3 (CharField) "C3" - Extra: topic (unicode) "extra3">') self.assertEqual(repr(objects[2]), '<ModelExtraC: id 3, field1 (CharField) "C1", field2 (CharField) "C2", field3 (CharField) "C3" - Extra: topic (unicode) "extra3">')
self.assertEqual(len(objects), 3) self.assertEqual(len(objects), 3)
def test_instance_of_filter(self): def test_instance_of_filter(self):
self.create_model2abcd() self.create_model2abcd()
@ -618,15 +737,29 @@ class PolymorphicTests(TestCase):
self.assertEqual(repr(objects[0]), '<Model2A: id 1, field1 (CharField)>') self.assertEqual(repr(objects[0]), '<Model2A: id 1, field1 (CharField)>')
self.assertEqual(len(objects), 1) self.assertEqual(len(objects), 1)
def test_polymorphic___filter(self): def test_polymorphic___filter(self):
self.create_model2abcd() self.create_model2abcd()
objects = Model2A.objects.filter(Q( Model2B___field2='B2') | Q( Model2C___field3='C3')) objects = Model2A.objects.filter(Q(Model2B___field2='B2') | Q(Model2C___field3='C3'))
self.assertEqual(len(objects), 2) self.assertEqual(len(objects), 2)
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)>')
def test_polymorphic___filter_field(self):
p = ModelUnderRelParent.objects.create(_private=True, field1='AA')
ModelUnderRelChild.objects.create(parent=p, _private2=True)
# The "___" filter should also parse to "parent" -> "_private" as fallback.
objects = ModelUnderRelChild.objects.filter(parent___private=True)
self.assertEqual(len(objects), 1)
def test_polymorphic___filter_reverse_field(self):
p = ModelUnderRelParent.objects.create(_private=True, field1='BB')
ModelUnderRelChild.objects.create(parent=p, _private2=True)
# Also test for reverse relations
objects = ModelUnderRelParent.objects.filter(children___private2=True)
self.assertEqual(len(objects), 1)
def test_delete(self): def test_delete(self):
self.create_model2abcd() self.create_model2abcd()
@ -642,7 +775,6 @@ class PolymorphicTests(TestCase):
self.assertEqual(repr(objects[2]), '<Model2D: id 4, field1 (CharField), field2 (CharField), field3 (CharField), field4 (CharField)>') self.assertEqual(repr(objects[2]), '<Model2D: id 4, field1 (CharField), field2 (CharField), field3 (CharField), field4 (CharField)>')
self.assertEqual(len(objects), 3) self.assertEqual(len(objects), 3)
def test_combine_querysets(self): def test_combine_querysets(self):
ModelX.objects.create(field_x='x') ModelX.objects.create(field_x='x')
ModelY.objects.create(field_y='y') ModelY.objects.create(field_y='y')
@ -652,7 +784,6 @@ class PolymorphicTests(TestCase):
self.assertEqual(repr(qs[1]), '<ModelY: id 2, field_b (CharField), field_y (CharField)>') self.assertEqual(repr(qs[1]), '<ModelY: id 2, field_b (CharField), field_y (CharField)>')
self.assertEqual(len(qs), 2) self.assertEqual(len(qs), 2)
def test_multiple_inheritance(self): def test_multiple_inheritance(self):
# multiple inheritance, subclassing third party models (mix PolymorphicModel with models.Model) # multiple inheritance, subclassing third party models (mix PolymorphicModel with models.Model)
@ -697,7 +828,6 @@ class PolymorphicTests(TestCase):
self.assertEqual(repr(objects[1]), '<RelationB: id 3, field_base (CharField) "B1", fk (ForeignKey) RelationA, field_b (CharField) "B2", m2m (ManyToManyField) 1>') self.assertEqual(repr(objects[1]), '<RelationB: id 3, field_base (CharField) "B1", fk (ForeignKey) RelationA, field_b (CharField) "B2", m2m (ManyToManyField) 1>')
self.assertEqual(len(objects), 2) self.assertEqual(len(objects), 2)
def test_user_defined_manager(self): def test_user_defined_manager(self):
self.create_model2abcd() self.create_model2abcd()
ModelWithMyManager.objects.create(field1='D1a', field4='D4a') ModelWithMyManager.objects.create(field1='D1a', field4='D4a')
@ -737,7 +867,7 @@ 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+") @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')
@ -752,7 +882,6 @@ class PolymorphicTests(TestCase):
self.assertEqual(type(ModelWithMyManager2._default_manager).__name__, 'PolymorphicManagerFromMyManagerQuerySet') self.assertEqual(type(ModelWithMyManager2._default_manager).__name__, 'PolymorphicManagerFromMyManagerQuerySet')
self.assertIs(type(ModelWithMyManager2.base_objects), models.Manager) self.assertIs(type(ModelWithMyManager2.base_objects), models.Manager)
def test_manager_inheritance(self): def test_manager_inheritance(self):
# by choice of MRO, should be MyManager from MROBase1. # by choice of MRO, should be MyManager from MROBase1.
self.assertIs(type(MRODerived.objects), MyManager) self.assertIs(type(MRODerived.objects), MyManager)
@ -763,7 +892,6 @@ class PolymorphicTests(TestCase):
# Django vanilla inheritance does not inherit MyManager as _default_manager here # Django vanilla inheritance does not inherit MyManager as _default_manager here
self.assertIs(type(MROBase2._default_manager), MyManager) self.assertIs(type(MROBase2._default_manager), MyManager)
def test_queryset_assignment(self): def test_queryset_assignment(self):
# This is just a consistency check for now, testing standard Django behavior. # This is just a consistency check for now, testing standard Django behavior.
parent = PlainParentModelWithManager.objects.create() parent = PlainParentModelWithManager.objects.create()
@ -787,7 +915,6 @@ class PolymorphicTests(TestCase):
# A related set is created using the model's _default_manager, so does gain extra methods. # A related set is created using the model's _default_manager, so does gain extra methods.
self.assertIs(type(parent.childmodel_set.my_queryset_foo()), MyManagerQuerySet) self.assertIs(type(parent.childmodel_set.my_queryset_foo()), MyManagerQuerySet)
def test_proxy_models(self): def test_proxy_models(self):
# prepare some data # prepare some data
for data in ('bleep bloop', 'I am a', 'computer'): for data in ('bleep bloop', 'I am a', 'computer'):
@ -808,7 +935,7 @@ class PolymorphicTests(TestCase):
This unit test guards that this check is working properly. For instance, This unit test guards that this check is working properly. For instance,
proxy child models need to be handled separately. proxy child models need to be handled separately.
""" """
name="Item1" name = "Item1"
nonproxychild = NonProxyChild.objects.create(name=name) nonproxychild = NonProxyChild.objects.create(name=name)
pb = ProxyBase.objects.get(id=1) pb = ProxyBase.objects.get(id=1)
@ -829,7 +956,6 @@ class PolymorphicTests(TestCase):
ct = ContentType.objects.get_for_model(ProxyChild, for_concrete_model=False) ct = ContentType.objects.get_for_model(ProxyChild, for_concrete_model=False)
self.assertEqual(ProxyChild, ct.model_class()) self.assertEqual(ProxyChild, ct.model_class())
def test_proxy_model_inheritance(self): def test_proxy_model_inheritance(self):
""" """
Polymorphic abilities should also work when the base model is a proxy object. Polymorphic abilities should also work when the base model is a proxy object.
@ -869,7 +995,7 @@ class PolymorphicTests(TestCase):
self.assertEqual(repr(qs[1]), '<CustomPkInherit: id 2, b (CharField) "b", custom_id (AutoField/pk) 1, i (CharField) "i">') self.assertEqual(repr(qs[1]), '<CustomPkInherit: id 2, b (CharField) "b", custom_id (AutoField/pk) 1, i (CharField) "i">')
def test_fix_getattribute(self): def test_fix_getattribute(self):
### fixed issue in PolymorphicModel.__getattribute__: field name same as model name # fixed issue in PolymorphicModel.__getattribute__: field name same as model name
o = ModelFieldNameTest.objects.create(modelfieldnametest='1') o = ModelFieldNameTest.objects.create(modelfieldnametest='1')
self.assertEqual(repr(o), '<ModelFieldNameTest: id 1, modelfieldnametest (CharField)>') self.assertEqual(repr(o), '<ModelFieldNameTest: id 1, modelfieldnametest (CharField)>')
@ -883,21 +1009,20 @@ class PolymorphicTests(TestCase):
t.save() t.save()
p = ModelShow1_plain.objects.get(field1="TestParentLinkAndRelatedName") p = ModelShow1_plain.objects.get(field1="TestParentLinkAndRelatedName")
#check that p is equal to the # check that p is equal to the
self.assertIsInstance(p, TestParentLinkAndRelatedName) self.assertIsInstance(p, TestParentLinkAndRelatedName)
self.assertEqual(p, t) self.assertEqual(p, t)
#check that the accessors to parent and sublass work correctly and return the right object # check that the accessors to parent and sublass work correctly and return the right object
p = ModelShow1_plain.objects.non_polymorphic().get(field1="TestParentLinkAndRelatedName") p = ModelShow1_plain.objects.non_polymorphic().get(field1="TestParentLinkAndRelatedName")
self.assertNotEqual(p, t) #p should be Plain1 and t TestParentLinkAndRelatedName, so not equal self.assertNotEqual(p, t) # p should be Plain1 and t TestParentLinkAndRelatedName, so not equal
self.assertEqual(p, t.superclass) self.assertEqual(p, t.superclass)
self.assertEqual(p.related_name_subclass, t) self.assertEqual(p.related_name_subclass, t)
#test that we can delete the object # test that we can delete the object
t.delete() t.delete()
class RegressionTests(TestCase): class RegressionTests(TestCase):
def test_for_query_result_incomplete_with_inheritance(self): def test_for_query_result_incomplete_with_inheritance(self):
@ -918,4 +1043,3 @@ class RegressionTests(TestCase):
expected_queryset = [bottom] expected_queryset = [bottom]
self.assertQuerysetEqual(Bottom.objects.all(), [repr(r) for r in expected_queryset]) self.assertQuerysetEqual(Bottom.objects.all(), [repr(r) for r in expected_queryset])

View File

@ -63,7 +63,7 @@ class UUIDField(six.with_metaclass(models.SubfieldBase, models.CharField)):
self.namespace, self.name = namespace, name self.namespace, self.name = namespace, name
super(UUIDField, self).__init__(verbose_name=verbose_name, super(UUIDField, self).__init__(verbose_name=verbose_name,
name=name, **kwargs) name=name, **kwargs)
def create_uuid(self): def create_uuid(self):
if not self.version or self.version == 4: if not self.version or self.version == 4:
@ -103,11 +103,11 @@ class UUIDField(six.with_metaclass(models.SubfieldBase, models.CharField)):
# instance), everything works. # instance), everything works.
# #
#if not value: # if not value:
# return None # return None
#if isinstance(value, uuid.UUID): # if isinstance(value, uuid.UUID):
# return smart_text(value) # return smart_text(value)
#else: # else:
# return value # return value
def pre_save(self, model_instance, add): def pre_save(self, model_instance, add):
@ -139,6 +139,6 @@ class UUIDField(six.with_metaclass(models.SubfieldBase, models.CharField)):
defaults = { defaults = {
'form_class': forms.CharField, 'form_class': forms.CharField,
'max_length': self.max_length 'max_length': self.max_length
} }
defaults.update(kwargs) defaults.update(kwargs)
return super(UUIDField, self).formfield(**defaults) return super(UUIDField, self).formfield(**defaults)

View File

@ -19,15 +19,15 @@ sys.stderr.write('Using Django version {0} from {1}\n'.format(
if not settings.configured: if not settings.configured:
settings.configure( settings.configure(
DEBUG = True, DEBUG=True,
TEMPLATE_DEBUG = True, TEMPLATE_DEBUG=True,
DATABASES = { DATABASES={
'default': { 'default': {
'ENGINE': 'django.db.backends.sqlite3', 'ENGINE': 'django.db.backends.sqlite3',
'NAME': ':memory:' 'NAME': ':memory:'
} }
}, },
TEMPLATE_LOADERS = ( TEMPLATE_LOADERS=(
'django.template.loaders.app_directories.Loader', 'django.template.loaders.app_directories.Loader',
), ),
TEMPLATE_CONTEXT_PROCESSORS=( TEMPLATE_CONTEXT_PROCESSORS=(
@ -37,7 +37,7 @@ if not settings.configured:
'django.core.context_processors.request', 'django.core.context_processors.request',
] ]
), ),
TEST_RUNNER = 'django.test.runner.DiscoverRunner' if django.VERSION >= (1,7) else 'django.test.simple.DjangoTestSuiteRunner', TEST_RUNNER = 'django.test.runner.DiscoverRunner' if django.VERSION >= (1, 7) else 'django.test.simple.DjangoTestSuiteRunner',
INSTALLED_APPS = ( INSTALLED_APPS = (
'django.contrib.auth', 'django.contrib.auth',
'django.contrib.contenttypes', 'django.contrib.contenttypes',
@ -50,9 +50,15 @@ if not settings.configured:
SITE_ID = 3, SITE_ID = 3,
) )
DEFAULT_TEST_APPS = [
'polymorphic',
]
def runtests(): def runtests():
argv = sys.argv[:1] + ['test', 'polymorphic', '--traceback'] + sys.argv[1:] other_args = list(filter(lambda arg: arg.startswith('-'), sys.argv[1:]))
test_apps = list(filter(lambda arg: not arg.startswith('-'), sys.argv[1:])) or DEFAULT_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__':

View File

@ -21,22 +21,22 @@ def find_version(*parts):
setup( setup(
name = 'django_polymorphic', name='django_polymorphic',
version = find_version('polymorphic', '__version__.py'), version=find_version('polymorphic', '__init__.py'),
license = 'BSD', license='BSD',
description = 'Seamless Polymorphic Inheritance for Django Models', description='Seamless Polymorphic Inheritance for Django Models',
long_description = read('README.rst'), long_description=read('README.rst'),
url = 'https://github.com/chrisglass/django_polymorphic', url='https://github.com/chrisglass/django_polymorphic',
author = 'Bert Constantin', author='Bert Constantin',
author_email = 'bert.constantin@gmx.de', author_email='bert.constantin@gmx.de',
maintainer = 'Christopher Glass', maintainer='Christopher Glass',
maintainer_email = 'tribaal@gmail.com', maintainer_email='tribaal@gmail.com',
packages = find_packages(), packages=find_packages(),
package_data = { package_data={
'polymorphic': [ 'polymorphic': [
'templates/admin/polymorphic/*.html', 'templates/admin/polymorphic/*.html',
], ],

View File

@ -22,6 +22,6 @@ commands=
python runtests.py python runtests.py
[testenv:docs] [testenv:docs]
changedir=docs
deps=Sphinx deps=Sphinx
commands=sphinx-build -W -b html -d {envtmpdir}/doctrees . {envtmpdir}/html changedir = docs
commands = sphinx-build -W -b html -d {envtmpdir}/doctrees . {envtmpdir}/html