Restructured django_polymorphic into a regular add-on application.
This is needed for the management commands, and also seems to be a generally good idea for future viablity as well. Also misc documentation updates.
This commit is contained in:
@@ -0,0 +1,19 @@
|
||||
"""
|
||||
polymorphic_dumpdata is just a slightly modified version
|
||||
of Django's dumpdata. In the long term, patching Django's
|
||||
dumpdata definitely is a better solution.
|
||||
|
||||
Use the Django 1.1 or 1.2 variant of dumpdata, depending of the
|
||||
Django version used.
|
||||
"""
|
||||
|
||||
import django
|
||||
|
||||
if django.VERSION[:2]==(1,1):
|
||||
from polymorphic_dumpdata_11 import Command
|
||||
|
||||
elif django.VERSION[:2]==(1,2):
|
||||
from polymorphic_dumpdata_12 import Command
|
||||
|
||||
else:
|
||||
assert False, 'Django version not supported'
|
||||
@@ -0,0 +1,94 @@
|
||||
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
from django.core.management.base import BaseCommand, CommandError
|
||||
from django.core import serializers
|
||||
from django.utils.datastructures import SortedDict
|
||||
|
||||
from optparse import make_option
|
||||
|
||||
class Command(BaseCommand):
|
||||
option_list = BaseCommand.option_list + (
|
||||
make_option('--format', default='json', dest='format',
|
||||
help='Specifies the output serialization format for fixtures.'),
|
||||
make_option('--indent', default=None, dest='indent', type='int',
|
||||
help='Specifies the indent level to use when pretty-printing output'),
|
||||
make_option('-e', '--exclude', dest='exclude',action='append', default=[],
|
||||
help='App to exclude (use multiple --exclude to exclude multiple apps).'),
|
||||
)
|
||||
help = 'Output the contents of the database as a fixture of the given format.'
|
||||
args = '[appname ...]'
|
||||
|
||||
def handle(self, *app_labels, **options):
|
||||
from django.db.models import get_app, get_apps, get_models, get_model
|
||||
|
||||
format = options.get('format','json')
|
||||
indent = options.get('indent',None)
|
||||
exclude = options.get('exclude',[])
|
||||
show_traceback = options.get('traceback', False)
|
||||
|
||||
excluded_apps = [get_app(app_label) for app_label in exclude]
|
||||
|
||||
if len(app_labels) == 0:
|
||||
app_list = SortedDict([(app, None) for app in get_apps() if app not in excluded_apps])
|
||||
else:
|
||||
app_list = SortedDict()
|
||||
for label in app_labels:
|
||||
try:
|
||||
app_label, model_label = label.split('.')
|
||||
try:
|
||||
app = get_app(app_label)
|
||||
except ImproperlyConfigured:
|
||||
raise CommandError("Unknown application: %s" % app_label)
|
||||
|
||||
model = get_model(app_label, model_label)
|
||||
if model is None:
|
||||
raise CommandError("Unknown model: %s.%s" % (app_label, model_label))
|
||||
|
||||
if app in app_list.keys():
|
||||
if app_list[app] and model not in app_list[app]:
|
||||
app_list[app].append(model)
|
||||
else:
|
||||
app_list[app] = [model]
|
||||
except ValueError:
|
||||
# This is just an app - no model qualifier
|
||||
app_label = label
|
||||
try:
|
||||
app = get_app(app_label)
|
||||
except ImproperlyConfigured:
|
||||
raise CommandError("Unknown application: %s" % app_label)
|
||||
app_list[app] = None
|
||||
|
||||
# Check that the serialization format exists; this is a shortcut to
|
||||
# avoid collating all the objects and _then_ failing.
|
||||
if format not in serializers.get_public_serializer_formats():
|
||||
raise CommandError("Unknown serialization format: %s" % format)
|
||||
|
||||
try:
|
||||
serializers.get_serializer(format)
|
||||
except KeyError:
|
||||
raise CommandError("Unknown serialization format: %s" % format)
|
||||
|
||||
objects = []
|
||||
for app, model_list in app_list.items():
|
||||
if model_list is None:
|
||||
model_list = get_models(app)
|
||||
|
||||
for model in model_list:
|
||||
if not model._meta.proxy:
|
||||
|
||||
#### patch for django_polymorphic ######################################################
|
||||
# modified for django_polymorphic compatibility:
|
||||
# do not use polymorphic queryset for serialisation
|
||||
# (as the dumpdata/serializer implementation depends
|
||||
# on non-polymorphic behavious)
|
||||
base_manager=model._default_manager
|
||||
if getattr(model,'polymorphic_model_marker',None) != None:
|
||||
base_manager=getattr(model,'base_objects',None)
|
||||
objects.extend(base_manager.all())
|
||||
|
||||
try:
|
||||
return serializers.serialize(format, objects, indent=indent)
|
||||
except Exception, e:
|
||||
if show_traceback:
|
||||
raise
|
||||
raise CommandError("Unable to serialize database: %s" % e)
|
||||
@@ -0,0 +1,175 @@
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
from django.core.management.base import BaseCommand, CommandError
|
||||
from django.core import serializers
|
||||
from django.db import connections, DEFAULT_DB_ALIAS
|
||||
from django.utils.datastructures import SortedDict
|
||||
|
||||
from optparse import make_option
|
||||
|
||||
class Command(BaseCommand):
|
||||
option_list = BaseCommand.option_list + (
|
||||
make_option('--format', default='json', dest='format',
|
||||
help='Specifies the output serialization format for fixtures.'),
|
||||
make_option('--indent', default=None, dest='indent', type='int',
|
||||
help='Specifies the indent level to use when pretty-printing output'),
|
||||
make_option('--database', action='store', dest='database',
|
||||
default=DEFAULT_DB_ALIAS, help='Nominates a specific database to load '
|
||||
'fixtures into. Defaults to the "default" database.'),
|
||||
make_option('-e', '--exclude', dest='exclude',action='append', default=[],
|
||||
help='App to exclude (use multiple --exclude to exclude multiple apps).'),
|
||||
make_option('-n', '--natural', action='store_true', dest='use_natural_keys', default=False,
|
||||
help='Use natural keys if they are available.'),
|
||||
)
|
||||
help = 'Output the contents of the database as a fixture of the given format.'
|
||||
args = '[appname ...]'
|
||||
|
||||
def handle(self, *app_labels, **options):
|
||||
from django.db.models import get_app, get_apps, get_models, get_model
|
||||
|
||||
format = options.get('format','json')
|
||||
indent = options.get('indent',None)
|
||||
using = options.get('database', DEFAULT_DB_ALIAS)
|
||||
connection = connections[using]
|
||||
exclude = options.get('exclude',[])
|
||||
show_traceback = options.get('traceback', False)
|
||||
use_natural_keys = options.get('use_natural_keys', False)
|
||||
|
||||
excluded_apps = set(get_app(app_label) for app_label in exclude)
|
||||
|
||||
if len(app_labels) == 0:
|
||||
app_list = SortedDict((app, None) for app in get_apps() if app not in excluded_apps)
|
||||
else:
|
||||
app_list = SortedDict()
|
||||
for label in app_labels:
|
||||
try:
|
||||
app_label, model_label = label.split('.')
|
||||
try:
|
||||
app = get_app(app_label)
|
||||
except ImproperlyConfigured:
|
||||
raise CommandError("Unknown application: %s" % app_label)
|
||||
|
||||
model = get_model(app_label, model_label)
|
||||
if model is None:
|
||||
raise CommandError("Unknown model: %s.%s" % (app_label, model_label))
|
||||
|
||||
if app in app_list.keys():
|
||||
if app_list[app] and model not in app_list[app]:
|
||||
app_list[app].append(model)
|
||||
else:
|
||||
app_list[app] = [model]
|
||||
except ValueError:
|
||||
# This is just an app - no model qualifier
|
||||
app_label = label
|
||||
try:
|
||||
app = get_app(app_label)
|
||||
except ImproperlyConfigured:
|
||||
raise CommandError("Unknown application: %s" % app_label)
|
||||
app_list[app] = None
|
||||
|
||||
# Check that the serialization format exists; this is a shortcut to
|
||||
# avoid collating all the objects and _then_ failing.
|
||||
if format not in serializers.get_public_serializer_formats():
|
||||
raise CommandError("Unknown serialization format: %s" % format)
|
||||
|
||||
try:
|
||||
serializers.get_serializer(format)
|
||||
except KeyError:
|
||||
raise CommandError("Unknown serialization format: %s" % format)
|
||||
|
||||
# Now collate the objects to be serialized.
|
||||
objects = []
|
||||
for model in sort_dependencies(app_list.items()):
|
||||
if not model._meta.proxy:
|
||||
|
||||
#### patch for django_polymorphic ######################################################
|
||||
# modified for django_polymorphic compatibility:
|
||||
# do not use polymorphic queryset for serialisation
|
||||
# (as the dumpdata/serializer implementation depends
|
||||
# on non-polymorphic behavious)
|
||||
base_manager=model._default_manager
|
||||
if getattr(model,'polymorphic_model_marker',None) != None:
|
||||
base_manager=getattr(model,'base_objects',None)
|
||||
objects.extend(base_manager.using(using).all())
|
||||
|
||||
try:
|
||||
return serializers.serialize(format, objects, indent=indent,
|
||||
use_natural_keys=use_natural_keys)
|
||||
except Exception, e:
|
||||
if show_traceback:
|
||||
raise
|
||||
raise CommandError("Unable to serialize database: %s" % e)
|
||||
|
||||
def sort_dependencies(app_list):
|
||||
"""Sort a list of app,modellist pairs into a single list of models.
|
||||
|
||||
The single list of models is sorted so that any model with a natural key
|
||||
is serialized before a normal model, and any model with a natural key
|
||||
dependency has it's dependencies serialized first.
|
||||
"""
|
||||
from django.db.models import get_model, get_models
|
||||
# Process the list of models, and get the list of dependencies
|
||||
model_dependencies = []
|
||||
models = set()
|
||||
for app, model_list in app_list:
|
||||
if model_list is None:
|
||||
model_list = get_models(app)
|
||||
|
||||
for model in model_list:
|
||||
models.add(model)
|
||||
# Add any explicitly defined dependencies
|
||||
if hasattr(model, 'natural_key'):
|
||||
deps = getattr(model.natural_key, 'dependencies', [])
|
||||
if deps:
|
||||
deps = [get_model(*d.split('.')) for d in deps]
|
||||
else:
|
||||
deps = []
|
||||
|
||||
# Now add a dependency for any FK or M2M relation with
|
||||
# a model that defines a natural key
|
||||
for field in model._meta.fields:
|
||||
if hasattr(field.rel, 'to'):
|
||||
rel_model = field.rel.to
|
||||
if hasattr(rel_model, 'natural_key'):
|
||||
deps.append(rel_model)
|
||||
for field in model._meta.many_to_many:
|
||||
rel_model = field.rel.to
|
||||
if hasattr(rel_model, 'natural_key'):
|
||||
deps.append(rel_model)
|
||||
model_dependencies.append((model, deps))
|
||||
|
||||
model_dependencies.reverse()
|
||||
# Now sort the models to ensure that dependencies are met. This
|
||||
# is done by repeatedly iterating over the input list of models.
|
||||
# If all the dependencies of a given model are in the final list,
|
||||
# that model is promoted to the end of the final list. This process
|
||||
# continues until the input list is empty, or we do a full iteration
|
||||
# over the input models without promoting a model to the final list.
|
||||
# If we do a full iteration without a promotion, that means there are
|
||||
# circular dependencies in the list.
|
||||
model_list = []
|
||||
while model_dependencies:
|
||||
skipped = []
|
||||
changed = False
|
||||
while model_dependencies:
|
||||
model, deps = model_dependencies.pop()
|
||||
|
||||
# If all of the models in the dependency list are either already
|
||||
# on the final model list, or not on the original serialization list,
|
||||
# then we've found another model with all it's dependencies satisfied.
|
||||
found = True
|
||||
for candidate in ((d not in models or d in model_list) for d in deps):
|
||||
if not candidate:
|
||||
found = False
|
||||
if found:
|
||||
model_list.append(model)
|
||||
changed = True
|
||||
else:
|
||||
skipped.append((model, deps))
|
||||
if not changed:
|
||||
raise CommandError("Can't resolve dependencies for %s in serialized app list." %
|
||||
', '.join('%s.%s' % (model._meta.app_label, model._meta.object_name)
|
||||
for model, deps in sorted(skipped, key=lambda obj: obj[0].__name__))
|
||||
)
|
||||
model_dependencies = skipped
|
||||
|
||||
return model_list
|
||||
@@ -0,0 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from polymorphic import PolymorphicModel, PolymorphicManager, PolymorphicQuerySet, ShowFields, ShowFieldsAndTypes
|
||||
@@ -0,0 +1,660 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Fully Polymorphic Django Models
|
||||
===============================
|
||||
|
||||
Please see the examples and documentation here:
|
||||
|
||||
http://bserve.webhop.org/wiki/django_polymorphic
|
||||
|
||||
or in the included README.rst and DOCS.rst files.
|
||||
|
||||
Copyright:
|
||||
This code and affiliated files are (C) by Bert Constantin and individual contributors.
|
||||
Please see LICENSE and AUTHORS for more information.
|
||||
"""
|
||||
|
||||
from django.db import models
|
||||
from django.db.models.base import ModelBase
|
||||
from django.db.models.query import QuerySet
|
||||
from collections import defaultdict
|
||||
from pprint import pprint
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
import sys
|
||||
|
||||
# chunk-size: maximum number of objects requested per db-request
|
||||
# by the polymorphic queryset.iterator() implementation
|
||||
Polymorphic_QuerySet_objects_per_request = 100
|
||||
|
||||
|
||||
###################################################################################
|
||||
### PolymorphicManager
|
||||
|
||||
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.
|
||||
"""
|
||||
use_for_related_fields = True
|
||||
|
||||
def __init__(self, queryset_class=None, *args, **kwrags):
|
||||
if not queryset_class: self.queryset_class = PolymorphicQuerySet
|
||||
else: self.queryset_class = queryset_class
|
||||
super(PolymorphicManager, self).__init__(*args, **kwrags)
|
||||
|
||||
def get_query_set(self):
|
||||
return self.queryset_class(self.model)
|
||||
|
||||
# Proxy all unknown method calls to the queryset, so that its members are
|
||||
# directly accessible as PolymorphicModel.objects.*
|
||||
# The advantage is that not yet known member functions of derived querysets will be proxied as well.
|
||||
# We exclude any special functions (__) from this automatic proxying.
|
||||
def __getattr__(self, name):
|
||||
if name.startswith('__'): return super(PolymorphicManager, self).__getattr__(self, name)
|
||||
return getattr(self.get_query_set(), name)
|
||||
|
||||
def __unicode__(self):
|
||||
return self.__class__.__name__ + ' (PolymorphicManager) using ' + self.queryset_class.__name__
|
||||
|
||||
|
||||
###################################################################################
|
||||
### PolymorphicQuerySet
|
||||
|
||||
class PolymorphicQuerySet(QuerySet):
|
||||
"""
|
||||
QuerySet for PolymorphicModel
|
||||
|
||||
Contains the core functionality for PolymorphicModel
|
||||
|
||||
Usually not explicitly needed, except if a custom queryset class
|
||||
is to be used.
|
||||
"""
|
||||
|
||||
def instance_of(self, *args):
|
||||
return self.filter(instance_of=args)
|
||||
|
||||
def not_instance_of(self, *args):
|
||||
return self.filter(not_instance_of=args)
|
||||
|
||||
def _filter_or_exclude(self, negate, *args, **kwargs):
|
||||
_translate_polymorphic_filter_specs_in_args(self.model, args)
|
||||
additional_args = _translate_polymorphic_filter_specs_in_kwargs(self.model, kwargs)
|
||||
return super(PolymorphicQuerySet, self)._filter_or_exclude(negate, *(list(args) + additional_args), **kwargs)
|
||||
|
||||
def _get_real_instances(self, base_result_objects):
|
||||
"""
|
||||
Polymorphic object loader
|
||||
|
||||
Does the same as:
|
||||
|
||||
return [ o.get_real_instance() for o in base_result_objects ]
|
||||
|
||||
The list base_result_objects contains the objects from the executed
|
||||
base class query. The class of all of them is self.model (our base model).
|
||||
|
||||
Some, many or all of these objects were not created and stored as
|
||||
class self.model, but as a class derived from self.model. We want to fetch
|
||||
these objects from the db so we can return them just as they were saved.
|
||||
|
||||
We identify these objects by looking at o.polymorphic_ctype, which specifies
|
||||
the real class of these objects (the class at the time they were saved).
|
||||
|
||||
First, we sort the result objects in base_result_objects for their
|
||||
subclass (from o.polymorphic_ctype), and then we execute one db query per
|
||||
subclass of objects. Finally we re-sort the resulting objects into the
|
||||
correct order and return them as a list.
|
||||
"""
|
||||
ordered_id_list = [] # list of ids of result-objects in correct order
|
||||
results = {} # polymorphic dict of result-objects, keyed with their id (no order)
|
||||
|
||||
# dict contains one entry per unique model type occurring in result,
|
||||
# in the format idlist_per_model[modelclass]=[list-of-object-ids]
|
||||
idlist_per_model = defaultdict(list)
|
||||
|
||||
# - sort base_result_object ids into idlist_per_model lists, depending on their real class;
|
||||
# - also record the correct result order in "ordered_id_list"
|
||||
# - store objects that already have the correct class into "results"
|
||||
self_model_content_type_id = ContentType.objects.get_for_model(self.model).pk
|
||||
for base_object in base_result_objects:
|
||||
ordered_id_list.append(base_object.id)
|
||||
|
||||
# this object is not a derived object and already the real instance => store it right away
|
||||
if (base_object.polymorphic_ctype_id == self_model_content_type_id):
|
||||
results[base_object.id] = base_object
|
||||
|
||||
# this object is derived and its real instance needs to be retrieved
|
||||
# => store it's id into the bin for this model type
|
||||
else:
|
||||
idlist_per_model[base_object.get_real_instance_class()].append(base_object.id)
|
||||
|
||||
# for each model in "idlist_per_model" request its objects (the full model)
|
||||
# from the db and store them in results[]
|
||||
for modelclass, idlist in idlist_per_model.items():
|
||||
qs = modelclass.base_objects.filter(id__in=idlist)
|
||||
# copy select related configuration to new qs
|
||||
# TODO: this does not seem to copy the complete sel_rel-config (field names etc.)
|
||||
self.dup_select_related(qs)
|
||||
# TODO: defer(), only() and annotate(): support for these would be around here
|
||||
for o in qs: results[o.id] = o
|
||||
|
||||
# re-create correct order and return result list
|
||||
resultlist = [ results[ordered_id] for ordered_id in ordered_id_list if ordered_id in results ]
|
||||
return resultlist
|
||||
|
||||
def iterator(self):
|
||||
"""
|
||||
This function does the same as:
|
||||
|
||||
base_result_objects=list(super(PolymorphicQuerySet, self).iterator())
|
||||
real_results=self._get_get_real_instances(base_result_objects)
|
||||
for o in real_results: yield o
|
||||
|
||||
but it requests the objects in chunks from the database,
|
||||
with Polymorphic_QuerySet_objects_per_request per chunk
|
||||
"""
|
||||
base_iter = super(PolymorphicQuerySet, self).iterator()
|
||||
|
||||
while True:
|
||||
base_result_objects = []
|
||||
reached_end = False
|
||||
|
||||
for i in range(Polymorphic_QuerySet_objects_per_request):
|
||||
try: base_result_objects.append(base_iter.next())
|
||||
except StopIteration:
|
||||
reached_end = True
|
||||
break
|
||||
|
||||
real_results = self._get_real_instances(base_result_objects)
|
||||
|
||||
for o in real_results:
|
||||
yield o
|
||||
|
||||
if reached_end: raise StopIteration
|
||||
|
||||
# these queryset functions are not yet supported
|
||||
def defer(self, *args, **kwargs): raise NotImplementedError
|
||||
def only(self, *args, **kwargs): raise NotImplementedError
|
||||
def aggregate(self, *args, **kwargs): raise NotImplementedError
|
||||
def annotate(self, *args, **kwargs): raise NotImplementedError
|
||||
|
||||
def __repr__(self):
|
||||
result = [ repr(o) for o in self.all() ]
|
||||
return '[ ' + ',\n '.join(result) + ' ]'
|
||||
|
||||
|
||||
###################################################################################
|
||||
### PolymorphicQuerySet support functions
|
||||
|
||||
# These functions implement the additional filter- and Q-object functionality.
|
||||
# They form a kind of small framework for easily adding more
|
||||
# functionality to filters and Q objects.
|
||||
# Probably a more general queryset enhancement class could be made out them.
|
||||
|
||||
def _translate_polymorphic_filter_specs_in_kwargs(queryset_model, kwargs):
|
||||
"""
|
||||
Translate the keyword argument list for PolymorphicQuerySet.filter()
|
||||
|
||||
Any kwargs with special polymorphic functionality are replaced in the kwargs
|
||||
dict with their vanilla django equivalents.
|
||||
|
||||
For some kwargs a direct replacement is not possible, as a Q object is needed
|
||||
instead to implement the required functionality. In these cases the kwarg is
|
||||
deleted from the kwargs dict and a Q object is added to the return list.
|
||||
|
||||
Modifies: kwargs dict
|
||||
Returns: a list of non-keyword-arguments (Q objects) to be added to the filter() query.
|
||||
"""
|
||||
additional_args = []
|
||||
for field_path, val in kwargs.items():
|
||||
# normal filter expression => ignore
|
||||
new_expr = _translate_polymorphic_filter_spec(queryset_model, field_path, val)
|
||||
if type(new_expr) == tuple:
|
||||
# replace kwargs element
|
||||
del(kwargs[field_path])
|
||||
kwargs[new_expr[0]] = new_expr[1]
|
||||
|
||||
elif isinstance(new_expr, models.Q):
|
||||
del(kwargs[field_path])
|
||||
additional_args.append(new_expr)
|
||||
|
||||
return additional_args
|
||||
|
||||
def _translate_polymorphic_filter_specs_in_args(queryset_model, args):
|
||||
"""
|
||||
Translate the non-keyword argument list for PolymorphicQuerySet.filter()
|
||||
|
||||
In the args list, we replace all kwargs to Q-objects that contain special
|
||||
polymorphic functionality with their vanilla django equivalents.
|
||||
We traverse the Q object tree for this (which is simple).
|
||||
|
||||
Modifies: args list
|
||||
"""
|
||||
|
||||
def tree_node_correct_field_specs(node):
|
||||
" process all children of this Q node "
|
||||
for i in range(len(node.children)):
|
||||
child = node.children[i]
|
||||
|
||||
if type(child) == tuple:
|
||||
# this Q object child is a tuple => a kwarg like Q( instance_of=ModelB )
|
||||
key, val = child
|
||||
new_expr = _translate_polymorphic_filter_spec(queryset_model, key, val)
|
||||
if new_expr:
|
||||
node.children[i] = new_expr
|
||||
else:
|
||||
# this Q object child is another Q object, recursively process this as well
|
||||
tree_node_correct_field_specs(child)
|
||||
|
||||
for q in args:
|
||||
if isinstance(q, models.Q):
|
||||
tree_node_correct_field_specs(q)
|
||||
|
||||
def _translate_polymorphic_filter_spec(queryset_model, field_path, field_val):
|
||||
"""
|
||||
Translate a keyword argument (field_path=field_val), as used for
|
||||
PolymorphicQuerySet.filter()-like functions (and Q objects).
|
||||
|
||||
A kwarg with special polymorphic functionality is translated into
|
||||
its vanilla django equivalent, which is returned, either as tuple
|
||||
(field_path, field_val) or as Q object.
|
||||
|
||||
Returns: kwarg tuple or Q object or None (if no change is required)
|
||||
"""
|
||||
|
||||
# handle instance_of expressions or alternatively,
|
||||
# if this is a normal Django filter expression, return None
|
||||
if field_path == 'instance_of':
|
||||
return _create_model_filter_Q(field_val)
|
||||
elif field_path == 'not_instance_of':
|
||||
return _create_model_filter_Q(field_val, not_instance_of=True)
|
||||
elif not '___' in field_path:
|
||||
return None #no change
|
||||
|
||||
# filter expression contains '___' (i.e. filter for polymorphic field)
|
||||
# => get the model class specified in the filter expression
|
||||
newpath = _translate_polymorphic_field_path(queryset_model, field_path)
|
||||
return (newpath, field_val)
|
||||
|
||||
|
||||
def _translate_polymorphic_field_path(queryset_model, field_path):
|
||||
"""
|
||||
Translate a field path from keyword argument, as used for
|
||||
PolymorphicQuerySet.filter()-like functions (and Q objects).
|
||||
|
||||
E.g.: ModelC___field3 is translated into modela__modelb__modelc__field3
|
||||
Returns: translated path
|
||||
"""
|
||||
classname, sep, pure_field_path = field_path.partition('___')
|
||||
assert sep == '___'
|
||||
|
||||
if '__' in classname:
|
||||
# the user has app label prepended to class name via __ => use Django's get_model function
|
||||
appname, sep, classname = classname.partition('__')
|
||||
model = models.get_model(appname, classname)
|
||||
assert model, 'PolymorphicModel: model %s (in app %s) not found!' % (model.__name__, appname)
|
||||
if not issubclass(model, queryset_model):
|
||||
e = 'PolymorphicModel: queryset filter error: "' + model.__name__ + '" is not derived from "' + queryset_model.__name__ + '"'
|
||||
raise AssertionError(e)
|
||||
|
||||
else:
|
||||
# the user has only given us the class name via __
|
||||
# => select the model from the sub models of the queryset base model
|
||||
|
||||
# function to collect all sub-models, this should be optimized (cached)
|
||||
def add_all_sub_models(model, result):
|
||||
if issubclass(model, models.Model) and model != models.Model:
|
||||
# model name is occurring twice in submodel inheritance tree => Error
|
||||
if model.__name__ in result and model != result[model.__name__]:
|
||||
e = 'PolymorphicModel: model name alone is ambiguous: %s.%s and %s.%s!\n'
|
||||
e += 'In this case, please use the syntax: applabel__ModelName___field'
|
||||
assert model, e % (
|
||||
model._meta.app_label, model.__name__,
|
||||
result[model.__name__]._meta.app_label, result[model.__name__].__name__)
|
||||
|
||||
result[model.__name__] = model
|
||||
|
||||
for b in model.__subclasses__():
|
||||
add_all_sub_models(b, result)
|
||||
|
||||
submodels = {}
|
||||
add_all_sub_models(queryset_model, submodels)
|
||||
model = submodels.get(classname, None)
|
||||
assert model, 'PolymorphicModel: model %s not found (not a subclass of %s)!' % (classname, queryset_model.__name__)
|
||||
|
||||
# create new field path for expressions, e.g. for baseclass=ModelA, myclass=ModelC
|
||||
# 'modelb__modelc" is returned
|
||||
def _create_base_path(baseclass, myclass):
|
||||
bases = myclass.__bases__
|
||||
for b in bases:
|
||||
if b == baseclass:
|
||||
return myclass.__name__.lower()
|
||||
path = _create_base_path(baseclass, b)
|
||||
if path: return path + '__' + myclass.__name__.lower()
|
||||
return ''
|
||||
|
||||
basepath = _create_base_path(queryset_model, model)
|
||||
newpath = basepath + '__' if basepath else ''
|
||||
newpath += pure_field_path
|
||||
return newpath
|
||||
|
||||
|
||||
def _create_model_filter_Q(modellist, not_instance_of=False):
|
||||
"""
|
||||
Helper function for instance_of / not_instance_of
|
||||
Creates and returns a Q object that filters for the models in modellist,
|
||||
including all subclasses of these models (as we want to do the same
|
||||
as pythons isinstance() ).
|
||||
.
|
||||
We recursively collect all __subclasses__(), create a Q filter for each,
|
||||
and or-combine these Q objects. This could be done much more
|
||||
efficiently however (regarding the resulting sql), should an optimization
|
||||
be needed.
|
||||
"""
|
||||
|
||||
if not modellist: return None
|
||||
from django.db.models import Q
|
||||
|
||||
if type(modellist) != list and type(modellist) != tuple:
|
||||
if issubclass(modellist, PolymorphicModel):
|
||||
modellist = [modellist]
|
||||
else:
|
||||
assert False, 'PolymorphicModel: instance_of expects a list of models or a single model'
|
||||
|
||||
def q_class_with_subclasses(model):
|
||||
q = Q(polymorphic_ctype=ContentType.objects.get_for_model(model))
|
||||
for subclass in model.__subclasses__():
|
||||
q = q | q_class_with_subclasses(subclass)
|
||||
return q
|
||||
|
||||
qlist = [ q_class_with_subclasses(m) for m in modellist ]
|
||||
|
||||
q_ored = reduce(lambda a, b: a | b, qlist)
|
||||
if not_instance_of: q_ored = ~q_ored
|
||||
return q_ored
|
||||
|
||||
|
||||
###################################################################################
|
||||
### PolymorphicModel meta class
|
||||
|
||||
class PolymorphicModelBase(ModelBase):
|
||||
"""
|
||||
Manager inheritance is a pretty complex topic which may need
|
||||
more thought regarding how this should be handled for polymorphic
|
||||
models.
|
||||
|
||||
In any case, we probably should propagate 'objects' and 'base_objects'
|
||||
from PolymorphicModel to every subclass. We also want to somehow
|
||||
inherit/propagate _default_manager as well, as it needs to be polymorphic.
|
||||
|
||||
The current implementation below is an experiment to solve this
|
||||
problem with a very simplistic approach: We unconditionally
|
||||
inherit/propagate any and all managers (using _copy_to_model),
|
||||
as long as they are defined on polymorphic models
|
||||
(the others are left alone).
|
||||
|
||||
Like Django ModelBase, we special-case _default_manager:
|
||||
if there are any user-defined managers, it is set to the first of these.
|
||||
|
||||
We also require that _default_manager as well as any user defined
|
||||
polymorphic managers produce querysets that are derived from
|
||||
PolymorphicQuerySet.
|
||||
"""
|
||||
|
||||
def __new__(self, model_name, bases, attrs):
|
||||
#print; print '###', model_name, '- bases:', bases
|
||||
|
||||
# create new model
|
||||
new_class = self.call_superclass_new_method(model_name, bases, attrs)
|
||||
|
||||
# create list of all managers to be inherited from the base classes
|
||||
inherited_managers = new_class.get_inherited_managers(attrs)
|
||||
|
||||
# add the managers to the new model
|
||||
for source_name, mgr_name, manager in inherited_managers:
|
||||
#print '** add inherited manager from model %s, manager %s, %s' % (source_name, mgr_name, manager.__class__.__name__)
|
||||
new_manager = manager._copy_to_model(new_class)
|
||||
new_class.add_to_class(mgr_name, new_manager)
|
||||
|
||||
# get first user defined manager; if there is one, make it the _default_manager
|
||||
user_manager = self.get_first_user_defined_manager(attrs)
|
||||
if user_manager:
|
||||
def_mgr = user_manager._copy_to_model(new_class)
|
||||
#print '## add default manager', type(def_mgr)
|
||||
new_class.add_to_class('_default_manager', def_mgr)
|
||||
new_class._default_manager._inherited = False # the default mgr was defined by the user, not inherited
|
||||
|
||||
# validate resulting default manager
|
||||
self.validate_model_manager(new_class._default_manager, model_name, '_default_manager')
|
||||
|
||||
return new_class
|
||||
|
||||
def get_inherited_managers(self, attrs):
|
||||
"""
|
||||
Return list of all managers to be inherited/propagated from the base classes;
|
||||
use correct mro, only use managers with _inherited==False,
|
||||
skip managers that are overwritten by the user with same-named class attributes (in attrs)
|
||||
"""
|
||||
add_managers = []; add_managers_keys = set()
|
||||
for base in self.__mro__[1:]:
|
||||
if not issubclass(base, models.Model): continue
|
||||
if not getattr(base, 'polymorphic_model_marker', None): continue # leave managers of non-polym. models alone
|
||||
|
||||
for key, manager in base.__dict__.items():
|
||||
if type(manager) == models.manager.ManagerDescriptor: manager = manager.manager
|
||||
if not isinstance(manager, models.Manager): continue
|
||||
if key in attrs: continue
|
||||
if key in add_managers_keys: continue # manager with that name already added, skip
|
||||
if manager._inherited: continue # inherited managers have no significance, they are just copies
|
||||
if isinstance(manager, PolymorphicManager): # validate any inherited polymorphic managers
|
||||
self.validate_model_manager(manager, self.__name__, key)
|
||||
add_managers.append((base.__name__, key, manager))
|
||||
add_managers_keys.add(key)
|
||||
return add_managers
|
||||
|
||||
@classmethod
|
||||
def get_first_user_defined_manager(self, attrs):
|
||||
mgr_list = []
|
||||
for key, val in attrs.items():
|
||||
if not isinstance(val, models.Manager): continue
|
||||
mgr_list.append((val.creation_counter, val))
|
||||
# if there are user defined managers, use first one as _default_manager
|
||||
if mgr_list: #
|
||||
_, manager = sorted(mgr_list)[0]
|
||||
return manager
|
||||
return None
|
||||
|
||||
@classmethod
|
||||
def call_superclass_new_method(self, model_name, bases, attrs):
|
||||
"""call __new__ method of super class and return the newly created class.
|
||||
Also work around a limitation in Django's ModelBase."""
|
||||
# There seems to be a general limitation in Django's app_label handling
|
||||
# regarding abstract models (in ModelBase). See issue 1 on github - TODO: propose patch for Django
|
||||
# We run into this problem if polymorphic.py is located in a top-level directory
|
||||
# which is directly in the python path. To work around this we temporarily set
|
||||
# app_label here for PolymorphicModel.
|
||||
meta = attrs.get('Meta', None)
|
||||
model_module_name = attrs['__module__']
|
||||
do_app_label_workaround = (meta
|
||||
and model_module_name == 'polymorphic'
|
||||
and model_name == 'PolymorphicModel'
|
||||
and getattr(meta, 'app_label', None) is None )
|
||||
|
||||
if do_app_label_workaround: meta.app_label = 'poly_dummy_app_label'
|
||||
new_class = super(PolymorphicModelBase, self).__new__(self, model_name, bases, attrs)
|
||||
if do_app_label_workaround: del(meta.app_label)
|
||||
return new_class
|
||||
|
||||
@classmethod
|
||||
def validate_model_manager(self, manager, model_name, manager_name):
|
||||
"""check if the manager is derived from PolymorphicManager
|
||||
and its querysets from PolymorphicQuerySet - throw AssertionError if not"""
|
||||
|
||||
if not issubclass(type(manager), PolymorphicManager):
|
||||
e = 'PolymorphicModel: "' + model_name + '.' + manager_name + '" manager is of type "' + type(manager).__name__
|
||||
e += '", but must be a subclass of PolymorphicManager'
|
||||
raise AssertionError(e)
|
||||
if not getattr(manager, 'queryset_class', None) or not issubclass(manager.queryset_class, PolymorphicQuerySet):
|
||||
e = 'PolymorphicModel: "' + model_name + '.' + manager_name + '" (PolymorphicManager) has been instantiated with a queryset class which is'
|
||||
e += ' not a subclass of PolymorphicQuerySet (which is required)'
|
||||
raise AssertionError(e)
|
||||
return manager
|
||||
|
||||
|
||||
###################################################################################
|
||||
### PolymorphicModel
|
||||
|
||||
class PolymorphicModel(models.Model):
|
||||
"""
|
||||
Abstract base class that provides polymorphic behaviour
|
||||
for any model directly or indirectly derived from it.
|
||||
|
||||
For usage instructions & examples please see documentation.
|
||||
|
||||
PolymorphicModel declares one field for internal use (polymorphic_ctype)
|
||||
and provides a polymorphic manager as the default manager
|
||||
(and as 'objects').
|
||||
|
||||
PolymorphicModel overrides the save() method.
|
||||
|
||||
If your derived class overrides save() as well, then you need
|
||||
to take care that you correctly call the save() method of
|
||||
the superclass, like:
|
||||
|
||||
super(YourClass,self).save(*args,**kwargs)
|
||||
"""
|
||||
__metaclass__ = PolymorphicModelBase
|
||||
|
||||
polymorphic_model_marker = True # for PolymorphicModelBase
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
||||
polymorphic_ctype = models.ForeignKey(ContentType, null=True, editable=False)
|
||||
|
||||
# some applications want to know the name of the fields that are added to its models
|
||||
polymorphic_internal_model_fields = [ 'polymorphic_ctype' ]
|
||||
|
||||
objects = PolymorphicManager()
|
||||
base_objects = models.Manager()
|
||||
|
||||
def pre_save_polymorphic(self):
|
||||
"""
|
||||
Normally not needed.
|
||||
This function may be called manually in special use-cases. When the object
|
||||
is saved for the first time, we store its real class in polymorphic_ctype.
|
||||
When the object later is retrieved by PolymorphicQuerySet, it uses this
|
||||
field to figure out the real class of this object
|
||||
(used by PolymorphicQuerySet._get_real_instances)
|
||||
"""
|
||||
if not self.polymorphic_ctype:
|
||||
self.polymorphic_ctype = ContentType.objects.get_for_model(self)
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
"""Overridden model save function which supports the polymorphism
|
||||
functionality (through pre_save_polymorphic)."""
|
||||
self.pre_save_polymorphic()
|
||||
return super(PolymorphicModel, self).save(*args, **kwargs)
|
||||
|
||||
def get_real_instance_class(self):
|
||||
"""Normally not needed.
|
||||
If a non-polymorphic manager (like base_objects) has been used to
|
||||
retrieve objects, then the real class/type of these objects may be
|
||||
determined using this method."""
|
||||
# the following line would be the easiest way to do this, but it produces sql queries
|
||||
#return self.polymorphic_ctype.model_class()
|
||||
# so we use the following version, which uses the CopntentType manager cache
|
||||
return ContentType.objects.get_for_id(self.polymorphic_ctype_id).model_class()
|
||||
|
||||
def get_real_instance(self):
|
||||
"""Normally not needed.
|
||||
If a non-polymorphic manager (like base_objects) has been used to
|
||||
retrieve objects, then the complete object with it's real class/type
|
||||
and all fields may be retrieved with this method.
|
||||
Each method call executes one db query (if necessary)."""
|
||||
real_model = self.get_real_instance_class()
|
||||
if real_model == self.__class__: return self
|
||||
return real_model.objects.get(id=self.id)
|
||||
|
||||
# Hack:
|
||||
# For base model back reference fields (like basemodel_ptr),
|
||||
# Django definitely must =not= use our polymorphic manager/queryset.
|
||||
# For now, we catch objects attribute access here and handle back reference fields manually.
|
||||
# This problem is triggered by delete(), like here:
|
||||
# django.db.models.base._collect_sub_objects: parent_obj = getattr(self, link.name)
|
||||
# TODO: investigate Django how this can be avoided
|
||||
def __getattribute__(self, name):
|
||||
if name != '__class__':
|
||||
#if name.endswith('_ptr_cache'): # unclear if this should be handled as well
|
||||
if name.endswith('_ptr'): name = name[:-4]
|
||||
model = self.__class__.sub_and_superclass_dict.get(name, None)
|
||||
if model:
|
||||
id = super(PolymorphicModel, self).__getattribute__('id')
|
||||
attr = model.base_objects.get(id=id)
|
||||
return attr
|
||||
return super(PolymorphicModel, self).__getattribute__(name)
|
||||
|
||||
# support for __getattribute__ hack: create sub_and_superclass_dict,
|
||||
# containing all model attribute names we need to intercept
|
||||
# (do this once here instead of in __getattribute__ every time)
|
||||
def __init__(self, *args, **kwargs):
|
||||
if not getattr(self.__class__, 'sub_and_superclass_dict', None):
|
||||
def add_all_base_models(model, result):
|
||||
if issubclass(model, models.Model) and model != models.Model:
|
||||
result[model.__name__.lower()] = model
|
||||
for b in model.__bases__:
|
||||
add_all_base_models(b, result)
|
||||
def add_all_sub_models(model, result):
|
||||
if issubclass(model, models.Model) and model != models.Model:
|
||||
result[model.__name__.lower()] = model
|
||||
for b in model.__subclasses__():
|
||||
add_all_sub_models(b, result)
|
||||
|
||||
result = {}
|
||||
add_all_base_models(self.__class__, result)
|
||||
add_all_sub_models(self.__class__, result)
|
||||
self.__class__.sub_and_superclass_dict = result
|
||||
|
||||
super(PolymorphicModel, self).__init__(*args, **kwargs)
|
||||
|
||||
def __repr__(self):
|
||||
out = self.__class__.__name__ + ': id %d, ' % (self.id or - 1); last = self._meta.fields[-1]
|
||||
for f in self._meta.fields:
|
||||
if f.name in [ 'id' ] + self.polymorphic_internal_model_fields or 'ptr' in f.name: continue
|
||||
out += f.name + ' (' + type(f).__name__ + ')'
|
||||
if f != last: out += ', '
|
||||
return '<' + out + '>'
|
||||
|
||||
|
||||
class ShowFields(object):
|
||||
""" mixin that shows the object's class, it's fields and field contents """
|
||||
def __repr__(self):
|
||||
out = 'id %d, ' % (self.id); last = self._meta.fields[-1]
|
||||
for f in self._meta.fields:
|
||||
if f.name in [ 'id' ] + self.polymorphic_internal_model_fields or 'ptr' in f.name: continue
|
||||
out += f.name
|
||||
if isinstance(f, (models.ForeignKey)):
|
||||
o = getattr(self, f.name)
|
||||
out += ': "' + ('None' if o == None else o.__class__.__name__) + '"'
|
||||
else:
|
||||
out += ': "' + getattr(self, f.name) + '"'
|
||||
if f != last: out += ', '
|
||||
return '<' + (self.__class__.__name__ + ': ') + out + '>'
|
||||
|
||||
|
||||
class ShowFieldsAndTypes(object):
|
||||
""" like ShowFields, but also show field types """
|
||||
def __repr__(self):
|
||||
out = 'id %d, ' % (self.id); last = self._meta.fields[-1]
|
||||
for f in self._meta.fields:
|
||||
if f.name in [ 'id' ] + self.polymorphic_internal_model_fields or 'ptr' in f.name: continue
|
||||
out += f.name + ' (' + type(f).__name__ + ')'
|
||||
if isinstance(f, (models.ForeignKey)):
|
||||
o = getattr(self, f.name)
|
||||
out += ': "' + ('None' if o == None else o.__class__.__name__) + '"'
|
||||
else:
|
||||
out += ': "' + getattr(self, f.name) + '"'
|
||||
if f != last: out += ', '
|
||||
return '<' + self.__class__.__name__ + ': ' + out + '>'
|
||||
|
||||
@@ -0,0 +1,234 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import settings
|
||||
|
||||
from django.test import TestCase
|
||||
from django.db.models.query import QuerySet
|
||||
from django.db.models import Q
|
||||
from django.db import models
|
||||
|
||||
from models import PolymorphicModel, PolymorphicManager, PolymorphicQuerySet, ShowFields, ShowFieldsAndTypes
|
||||
|
||||
class PlainA(models.Model):
|
||||
field1 = models.CharField(max_length=10)
|
||||
class PlainB(PlainA):
|
||||
field2 = models.CharField(max_length=10)
|
||||
class PlainC(PlainB):
|
||||
field3 = models.CharField(max_length=10)
|
||||
|
||||
class Model2A(PolymorphicModel):
|
||||
field1 = models.CharField(max_length=10)
|
||||
class Model2B(Model2A):
|
||||
field2 = models.CharField(max_length=10)
|
||||
class Model2C(Model2B):
|
||||
field3 = models.CharField(max_length=10)
|
||||
|
||||
class Base(PolymorphicModel):
|
||||
field_b = models.CharField(max_length=10)
|
||||
class ModelX(Base):
|
||||
field_x = models.CharField(max_length=10)
|
||||
class ModelY(Base):
|
||||
field_y = models.CharField(max_length=10)
|
||||
|
||||
class Enhance_Plain(models.Model):
|
||||
field_p = models.CharField(max_length=10)
|
||||
class Enhance_Base(ShowFieldsAndTypes, PolymorphicModel):
|
||||
field_b = models.CharField(max_length=10)
|
||||
class Enhance_Inherit(Enhance_Base, Enhance_Plain):
|
||||
field_i = models.CharField(max_length=10)
|
||||
|
||||
|
||||
class DiamondBase(models.Model):
|
||||
field_b = models.CharField(max_length=10)
|
||||
class DiamondX(DiamondBase):
|
||||
field_x = models.CharField(max_length=10)
|
||||
class DiamondY(DiamondBase):
|
||||
field_y = models.CharField(max_length=10)
|
||||
class DiamondXY(DiamondX, DiamondY):
|
||||
pass
|
||||
|
||||
class RelationBase(ShowFieldsAndTypes, PolymorphicModel):
|
||||
field_base = models.CharField(max_length=10)
|
||||
fk = models.ForeignKey('self', null=True)
|
||||
m2m = models.ManyToManyField('self')
|
||||
class RelationA(RelationBase):
|
||||
field_a = models.CharField(max_length=10)
|
||||
class RelationB(RelationBase):
|
||||
field_b = models.CharField(max_length=10)
|
||||
class RelationBC(RelationB):
|
||||
field_c = models.CharField(max_length=10)
|
||||
|
||||
class RelatingModel(models.Model):
|
||||
many2many = models.ManyToManyField(Model2A)
|
||||
|
||||
class MyManager(PolymorphicManager):
|
||||
def get_query_set(self):
|
||||
return super(MyManager, self).get_query_set().order_by('-field1')
|
||||
class ModelWithMyManager(ShowFieldsAndTypes, Model2A):
|
||||
objects = MyManager()
|
||||
field4 = models.CharField(max_length=10)
|
||||
|
||||
class MROBase1(PolymorphicModel):
|
||||
objects = MyManager()
|
||||
field1 = models.CharField(max_length=10) # needed as MyManager uses it
|
||||
class MROBase2(MROBase1):
|
||||
pass # Django vanilla inheritance does not inherit MyManager as _default_manager here
|
||||
class MROBase3(models.Model):
|
||||
objects = PolymorphicManager()
|
||||
class MRODerived(MROBase2, MROBase3):
|
||||
pass
|
||||
|
||||
class MgrInheritA(models.Model):
|
||||
mgrA = models.Manager()
|
||||
mgrA2 = models.Manager()
|
||||
field1 = models.CharField(max_length=10)
|
||||
class MgrInheritB(MgrInheritA):
|
||||
mgrB = models.Manager()
|
||||
field2 = models.CharField(max_length=10)
|
||||
class MgrInheritC(ShowFieldsAndTypes, MgrInheritB):
|
||||
pass
|
||||
|
||||
|
||||
class testclass(TestCase):
|
||||
def test_diamond_inheritance(self):
|
||||
# Django diamond problem
|
||||
o = DiamondXY.objects.create(field_b='b', field_x='x', field_y='y')
|
||||
print 'DiamondXY fields 1: field_b "%s", field_x "%s", field_y "%s"' % (o.field_b, o.field_x, o.field_y)
|
||||
o = DiamondXY.objects.get()
|
||||
print 'DiamondXY fields 2: field_b "%s", field_x "%s", field_y "%s"' % (o.field_b, o.field_x, o.field_y)
|
||||
if o.field_b != 'b': print '# Django model inheritance diamond problem detected'
|
||||
|
||||
__test__ = {"doctest": """
|
||||
#######################################################
|
||||
### Tests
|
||||
|
||||
>>> settings.DEBUG=True
|
||||
|
||||
### simple inheritance
|
||||
|
||||
>>> o=Model2A.objects.create(field1='A1')
|
||||
>>> o=Model2B.objects.create(field1='B1', field2='B2')
|
||||
>>> o=Model2C.objects.create(field1='C1', field2='C2', field3='C3')
|
||||
|
||||
>>> Model2A.objects.all()
|
||||
[ <Model2A: id 1, field1 (CharField)>,
|
||||
<Model2B: id 2, field1 (CharField), field2 (CharField)>,
|
||||
<Model2C: id 3, field1 (CharField), field2 (CharField), field3 (CharField)> ]
|
||||
|
||||
# manual get_real_instance()
|
||||
>>> o=Model2A.base_objects.get(field1='C1')
|
||||
>>> o.get_real_instance()
|
||||
<Model2C: id 3, field1 (CharField), field2 (CharField), field3 (CharField)>
|
||||
|
||||
### class filtering, instance_of, not_instance_of
|
||||
|
||||
>>> Model2A.objects.instance_of(Model2B)
|
||||
[ <Model2B: id 2, field1 (CharField), field2 (CharField)>,
|
||||
<Model2C: id 3, field1 (CharField), field2 (CharField), field3 (CharField)> ]
|
||||
|
||||
>>> Model2A.objects.not_instance_of(Model2B)
|
||||
[ <Model2A: id 1, field1 (CharField)> ]
|
||||
|
||||
### polymorphic filtering
|
||||
|
||||
>>> Model2A.objects.filter( Q( Model2B___field2 = 'B2' ) | Q( Model2C___field3 = 'C3' ) )
|
||||
[ <Model2B: id 2, field1 (CharField), field2 (CharField)>,
|
||||
<Model2C: id 3, field1 (CharField), field2 (CharField), field3 (CharField)> ]
|
||||
|
||||
### get & delete
|
||||
|
||||
>>> oa=Model2A.objects.get(id=2)
|
||||
>>> oa
|
||||
<Model2B: id 2, field1 (CharField), field2 (CharField)>
|
||||
|
||||
>>> oa.delete()
|
||||
>>> Model2A.objects.all()
|
||||
[ <Model2A: id 1, field1 (CharField)>,
|
||||
<Model2C: id 3, field1 (CharField), field2 (CharField), field3 (CharField)> ]
|
||||
|
||||
### queryset combining
|
||||
|
||||
>>> o=ModelX.objects.create(field_x='x')
|
||||
>>> o=ModelY.objects.create(field_y='y')
|
||||
|
||||
>>> Base.objects.instance_of(ModelX) | Base.objects.instance_of(ModelY)
|
||||
[ <ModelX: id 1, field_b (CharField), field_x (CharField)>,
|
||||
<ModelY: id 2, field_b (CharField), field_y (CharField)> ]
|
||||
|
||||
### multiple inheritance, subclassing third party models (mix PolymorphicModel with models.Model)
|
||||
|
||||
>>> o = Enhance_Base.objects.create(field_b='b-base')
|
||||
>>> o = Enhance_Inherit.objects.create(field_b='b-inherit', field_p='p', field_i='i')
|
||||
|
||||
>>> Enhance_Base.objects.all()
|
||||
[ <Enhance_Base: id 1, field_b (CharField): "b-base">,
|
||||
<Enhance_Inherit: id 2, field_b (CharField): "b-inherit", field_p (CharField): "p", field_i (CharField): "i"> ]
|
||||
|
||||
### ForeignKey, ManyToManyField
|
||||
|
||||
>>> obase=RelationBase.objects.create(field_base='base')
|
||||
>>> oa=RelationA.objects.create(field_base='A1', field_a='A2', fk=obase)
|
||||
>>> ob=RelationB.objects.create(field_base='B1', field_b='B2', fk=oa)
|
||||
>>> oc=RelationBC.objects.create(field_base='C1', field_b='C2', field_c='C3', fk=oa)
|
||||
>>> oa.m2m.add(oa); oa.m2m.add(ob)
|
||||
|
||||
>>> RelationBase.objects.all()
|
||||
[ <RelationBase: id 1, field_base (CharField): "base", fk (ForeignKey): "None">,
|
||||
<RelationA: id 2, field_base (CharField): "A1", fk (ForeignKey): "RelationBase", field_a (CharField): "A2">,
|
||||
<RelationB: id 3, field_base (CharField): "B1", fk (ForeignKey): "RelationA", field_b (CharField): "B2">,
|
||||
<RelationBC: id 4, field_base (CharField): "C1", fk (ForeignKey): "RelationA", field_b (CharField): "C2", field_c (CharField): "C3"> ]
|
||||
|
||||
>>> oa=RelationBase.objects.get(id=2)
|
||||
>>> oa.fk
|
||||
<RelationBase: id 1, field_base (CharField): "base", fk (ForeignKey): "None">
|
||||
|
||||
>>> oa.relationbase_set.all()
|
||||
[ <RelationB: id 3, field_base (CharField): "B1", fk (ForeignKey): "RelationA", field_b (CharField): "B2">,
|
||||
<RelationBC: id 4, field_base (CharField): "C1", fk (ForeignKey): "RelationA", field_b (CharField): "C2", field_c (CharField): "C3"> ]
|
||||
|
||||
>>> ob=RelationBase.objects.get(id=3)
|
||||
>>> ob.fk
|
||||
<RelationA: id 2, field_base (CharField): "A1", fk (ForeignKey): "RelationBase", field_a (CharField): "A2">
|
||||
|
||||
>>> oa=RelationA.objects.get()
|
||||
>>> oa.m2m.all()
|
||||
[ <RelationA: id 2, field_base (CharField): "A1", fk (ForeignKey): "RelationBase", field_a (CharField): "A2">,
|
||||
<RelationB: id 3, field_base (CharField): "B1", fk (ForeignKey): "RelationA", field_b (CharField): "B2"> ]
|
||||
|
||||
### user-defined manager
|
||||
|
||||
>>> o=ModelWithMyManager.objects.create(field1='D1a', field4='D4a')
|
||||
>>> o=ModelWithMyManager.objects.create(field1='D1b', field4='D4b')
|
||||
|
||||
>>> ModelWithMyManager.objects.all()
|
||||
[ <ModelWithMyManager: id 5, field1 (CharField): "D1b", field4 (CharField): "D4b">,
|
||||
<ModelWithMyManager: id 4, field1 (CharField): "D1a", field4 (CharField): "D4a"> ]
|
||||
|
||||
>>> type(ModelWithMyManager.objects)
|
||||
<class 'polymorphic.tests.MyManager'>
|
||||
>>> type(ModelWithMyManager._default_manager)
|
||||
<class 'polymorphic.tests.MyManager'>
|
||||
|
||||
### Manager Inheritance
|
||||
|
||||
>>> type(MRODerived.objects) # MRO
|
||||
<class 'polymorphic.tests.MyManager'>
|
||||
|
||||
# check for correct default manager
|
||||
>>> type(MROBase1._default_manager)
|
||||
<class 'polymorphic.tests.MyManager'>
|
||||
|
||||
# Django vanilla inheritance does not inherit MyManager as _default_manager here
|
||||
>>> type(MROBase2._default_manager)
|
||||
<class 'polymorphic.tests.MyManager'>
|
||||
|
||||
### Django model inheritance diamond problem, fails for Django 1.1
|
||||
|
||||
#>>> o=DiamondXY.objects.create(field_b='b', field_x='x', field_y='y')
|
||||
#>>> print 'DiamondXY fields 1: field_b "%s", field_x "%s", field_y "%s"' % (o.field_b, o.field_x, o.field_y)
|
||||
#DiamondXY fields 1: field_b "a", field_x "x", field_y "y"
|
||||
|
||||
>>> settings.DEBUG=False
|
||||
|
||||
"""}
|
||||
|
||||
Reference in New Issue
Block a user