extra(): Re-implemented. Now is polymorphic (nearly) without restrictions.
Added test cases + docs.fix_request_path_info
parent
6c8d28cbbc
commit
19adbdaf2c
40
DOCS.rst
40
DOCS.rst
|
|
@ -262,14 +262,11 @@ About Queryset Methods
|
||||||
to select relations in derived models
|
to select relations in derived models
|
||||||
(like ``ModelA.objects.select_related('ModelC___fieldxy')`` )
|
(like ``ModelA.objects.select_related('ModelC___fieldxy')`` )
|
||||||
|
|
||||||
* ``extra()`` by default works exactly like the original version,
|
* ``extra()`` works as expected (returns polymorphic results) but
|
||||||
with the resulting queryset not being polymorphic. There is
|
currently has one restriction: The resulting objects are required to have
|
||||||
experimental support for a polymorphic extra() via the keyword
|
a unique primary key within the result set - otherwise an error is thrown
|
||||||
argument ``polymorphic=True`` (only the ``where`` and
|
(this case could be made to work, however it may be mostly unneeded)..
|
||||||
``order_by`` and ``params`` arguments of extra() should be used then).
|
The keyword-argument "polymorphic" is no longer supported.
|
||||||
The behaviour of extra() may change in the future, so it's best if you use
|
|
||||||
``base_objects=ModelA.base_objects.extra(...)`` instead if you want to
|
|
||||||
sure to get non-polymorphic behaviour.
|
|
||||||
|
|
||||||
+ ``get_real_instances(base_objects_list_or_queryset)`` allows you to turn a
|
+ ``get_real_instances(base_objects_list_or_queryset)`` allows you to turn a
|
||||||
queryset or list of base model objects efficiently into the real objects.
|
queryset or list of base model objects efficiently into the real objects.
|
||||||
|
|
@ -488,19 +485,6 @@ Restrictions & Caveats
|
||||||
for the methods of the polymorphic querysets. Please see above
|
for the methods of the polymorphic querysets. Please see above
|
||||||
for ``translate_polymorphic_Q_object``.
|
for ``translate_polymorphic_Q_object``.
|
||||||
|
|
||||||
* Django 1.1 only - the names of polymorphic models must be unique
|
|
||||||
in the whole project, even if they are in two different apps.
|
|
||||||
This results from a restriction in the Django 1.1 "related_name"
|
|
||||||
option (fixed in Django 1.2).
|
|
||||||
|
|
||||||
+ Django 1.1 only - when ContentType is used in models, Django's
|
|
||||||
seralisation or fixtures cannot be used (all polymorphic models
|
|
||||||
use ContentType). This issue seems to be resolved for Django 1.2
|
|
||||||
(changeset 11863: Fixed #7052, Added support for natural keys in serialization).
|
|
||||||
|
|
||||||
+ http://code.djangoproject.com/ticket/7052
|
|
||||||
+ http://stackoverflow.com/questions/853796/problems-with-contenttypes-when-loading-a-fixture-in-django
|
|
||||||
|
|
||||||
* A reference (``ContentType``) to the real/leaf model is stored
|
* A reference (``ContentType``) to the real/leaf model is stored
|
||||||
in the base model (the base model directly inheriting from
|
in the base model (the base model directly inheriting from
|
||||||
PolymorphicModel). You need to be aware of this when using the
|
PolymorphicModel). You need to be aware of this when using the
|
||||||
|
|
@ -510,6 +494,20 @@ Restrictions & Caveats
|
||||||
table needs to be corrected/copied too. This is of course generally
|
table needs to be corrected/copied too. This is of course generally
|
||||||
the case for any models using Django's ContentType.
|
the case for any models using Django's ContentType.
|
||||||
|
|
||||||
|
+ Django 1.1 only - the names of polymorphic models must be unique
|
||||||
|
in the whole project, even if they are in two different apps.
|
||||||
|
This results from a restriction in the Django 1.1 "related_name"
|
||||||
|
option (fixed in Django 1.2).
|
||||||
|
|
||||||
|
* Django 1.1 only - when ContentType is used in models, Django's
|
||||||
|
seralisation or fixtures cannot be used (all polymorphic models
|
||||||
|
use ContentType). This issue seems to be resolved for Django 1.2
|
||||||
|
(changeset 11863: Fixed #7052, Added support for natural keys in serialization).
|
||||||
|
|
||||||
|
+ http://code.djangoproject.com/ticket/7052
|
||||||
|
+ http://stackoverflow.com/questions/853796/problems-with-contenttypes-when-loading-a-fixture-in-django
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Project Status
|
Project Status
|
||||||
|
|
|
||||||
|
|
@ -30,11 +30,10 @@ class Command(NoArgsCommand):
|
||||||
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
|
||||||
"""
|
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ class ArtProject(Project):
|
||||||
class ResearchProject(Project):
|
class ResearchProject(Project):
|
||||||
supervisor = models.CharField(max_length=30)
|
supervisor = models.CharField(max_length=30)
|
||||||
|
|
||||||
class ModelA(ShowFieldType, 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)
|
||||||
|
|
|
||||||
|
|
@ -84,23 +84,11 @@ class PolymorphicQuerySet(QuerySet):
|
||||||
self.polymorphic_disabled = True
|
self.polymorphic_disabled = True
|
||||||
return super(PolymorphicQuerySet, self).aggregate(*args, **kwargs)
|
return super(PolymorphicQuerySet, self).aggregate(*args, **kwargs)
|
||||||
|
|
||||||
def extra(self, *args, **kwargs):
|
# Since django_polymorphic 'V1.0 beta2', extra() always returns polymorphic results.
|
||||||
"""since django_polymorphic 'V1.0 beta2' extra() returns polymorphic results by default.
|
# The resulting objects are required to have a unique primary key within the result set
|
||||||
Currently, for polymorphic queries, only the parameters 'where','order_by', 'params' are
|
# (otherwise an error is thrown).
|
||||||
supported and an error is thrown if other parameters are given.
|
# The "polymorphic" keyword argument is not supported anymore.
|
||||||
|
#def extra(self, *args, **kwargs):
|
||||||
For Django V1.1, extra() is not supported anymore (however it still works and returns
|
|
||||||
non-polymorphic results as this is needed in django.db.models.base.save_base)."""
|
|
||||||
|
|
||||||
polymorphic_by_default = not ( django_VERSION[0] <= 1 and django_VERSION[1] <= 1 )
|
|
||||||
self.polymorphic_disabled = not kwargs.pop('polymorphic',polymorphic_by_default)
|
|
||||||
if not self.polymorphic_disabled:
|
|
||||||
for key in kwargs.keys():
|
|
||||||
if key not in ['where','order_by', 'params']:
|
|
||||||
assert False,("django_polymorphic: extras() does not yet support keyword argument '%s'."
|
|
||||||
+ "You may use 'base_objects.extra()' instead - please see 'extra(' and 'get_real_instances' in DOCS.rst.") % (key,)
|
|
||||||
|
|
||||||
return super(PolymorphicQuerySet, self).extra(*args, **kwargs)
|
|
||||||
|
|
||||||
def get_real_instances(self, base_result_objects):
|
def get_real_instances(self, base_result_objects):
|
||||||
"""
|
"""
|
||||||
|
|
@ -144,6 +132,11 @@ class PolymorphicQuerySet(QuerySet):
|
||||||
self_model_content_type_id = ContentType.objects.get_for_model(self.model).pk
|
self_model_content_type_id = ContentType.objects.get_for_model(self.model).pk
|
||||||
for base_object in base_result_objects:
|
for base_object in base_result_objects:
|
||||||
ordered_id_list.append(base_object.pk)
|
ordered_id_list.append(base_object.pk)
|
||||||
|
|
||||||
|
# check if id of the result object occeres more than once - this can happen e.g. with base_objects.extra(tables=...)
|
||||||
|
assert not base_object.pk in base_result_objects_by_id, (
|
||||||
|
"django_polymorphic: result objects do not have unique primary keys - model "+unicode(self.model) )
|
||||||
|
|
||||||
base_result_objects_by_id[base_object.pk] = base_object
|
base_result_objects_by_id[base_object.pk] = base_object
|
||||||
|
|
||||||
# this object is not a derived object and already the real instance => store it right away
|
# this object is not a derived object and already the real instance => store it right away
|
||||||
|
|
@ -158,6 +151,7 @@ class PolymorphicQuerySet(QuerySet):
|
||||||
# For each model in "idlist_per_model" request its objects (the real model)
|
# For each model in "idlist_per_model" request its objects (the real model)
|
||||||
# from the db and store them in results[].
|
# from the db and store them in results[].
|
||||||
# Then we copy the annotate fields from the base objects to the real objects.
|
# Then we copy the annotate fields from the base objects to the real objects.
|
||||||
|
# Then we copy the extra() select fields from the base objects to the real objects.
|
||||||
# TODO: defer(), only(): support for these would be around here
|
# TODO: defer(), only(): support for these would be around here
|
||||||
for modelclass, idlist in idlist_per_model.items():
|
for modelclass, idlist in idlist_per_model.items():
|
||||||
qs = modelclass.base_objects.filter(id__in=idlist)
|
qs = modelclass.base_objects.filter(id__in=idlist)
|
||||||
|
|
@ -165,9 +159,15 @@ class PolymorphicQuerySet(QuerySet):
|
||||||
|
|
||||||
for o in qs:
|
for o in qs:
|
||||||
if self.query.aggregates:
|
if self.query.aggregates:
|
||||||
for anno in self.query.aggregates.keys():
|
for anno_field_name in self.query.aggregates.keys():
|
||||||
attr = getattr(base_result_objects_by_id[o.pk], anno)
|
attr = getattr(base_result_objects_by_id[o.pk], anno_field_name)
|
||||||
setattr(o, anno, attr)
|
setattr(o, anno_field_name, attr)
|
||||||
|
|
||||||
|
if self.query.extra_select:
|
||||||
|
for select_field_name in self.query.extra_select.keys():
|
||||||
|
attr = getattr(base_result_objects_by_id[o.pk], select_field_name)
|
||||||
|
setattr(o, select_field_name, attr)
|
||||||
|
|
||||||
results[o.pk] = o
|
results[o.pk] = o
|
||||||
|
|
||||||
# re-create correct order and return result list
|
# re-create correct order and return result list
|
||||||
|
|
@ -175,10 +175,17 @@ class PolymorphicQuerySet(QuerySet):
|
||||||
|
|
||||||
# set polymorphic_annotate_names in all objects (currently just used for debugging/printing)
|
# set polymorphic_annotate_names in all objects (currently just used for debugging/printing)
|
||||||
if self.query.aggregates:
|
if self.query.aggregates:
|
||||||
annotate_names=self.query.aggregates.keys() # get annotate fields list
|
annotate_names=self.query.aggregates.keys() # get annotate field list
|
||||||
for o in resultlist:
|
for o in resultlist:
|
||||||
o.polymorphic_annotate_names=annotate_names
|
o.polymorphic_annotate_names=annotate_names
|
||||||
|
|
||||||
|
# set polymorphic_extra_select_names in all objects (currently just used for debugging/printing)
|
||||||
|
if self.query.extra_select:
|
||||||
|
extra_select_names=self.query.extra_select.keys() # get extra select field list
|
||||||
|
for o in resultlist:
|
||||||
|
o.polymorphic_extra_select_names=extra_select_names
|
||||||
|
|
||||||
|
|
||||||
return resultlist
|
return resultlist
|
||||||
|
|
||||||
def iterator(self):
|
def iterator(self):
|
||||||
|
|
|
||||||
|
|
@ -40,16 +40,23 @@ class ShowFieldBase(object):
|
||||||
else:
|
else:
|
||||||
out += ': "' + unicode(o) + '"'
|
out += ': "' + unicode(o) + '"'
|
||||||
|
|
||||||
if hasattr(self,'polymorphic_annotate_names'):
|
def get_dynamic_fields(field_list, title):
|
||||||
out += ' - Ann: '
|
out = ' - '+title+': '
|
||||||
for an in self.polymorphic_annotate_names:
|
for an in field_list:
|
||||||
if an != self.polymorphic_annotate_names[0]:
|
if an != field_list[0]:
|
||||||
out += ', '
|
out += ', '
|
||||||
out += an
|
out += an
|
||||||
if self.polymorphic_showfield_type:
|
if self.polymorphic_showfield_type:
|
||||||
out += ' (' + type(getattr(self, an)).__name__ + ')'
|
out += ' (' + type(getattr(self, an)).__name__ + ')'
|
||||||
if self.polymorphic_showfield_content:
|
if self.polymorphic_showfield_content:
|
||||||
out += ': "' + unicode(getattr(self, an)) + '"'
|
out += ': "' + unicode(getattr(self, an)) + '"'
|
||||||
|
return out
|
||||||
|
|
||||||
|
if hasattr(self,'polymorphic_annotate_names'):
|
||||||
|
out+=get_dynamic_fields(self.polymorphic_annotate_names, 'Ann')
|
||||||
|
|
||||||
|
if hasattr(self,'polymorphic_extra_select_names'):
|
||||||
|
out+=get_dynamic_fields(self.polymorphic_extra_select_names, 'Extra')
|
||||||
|
|
||||||
return out+'>'
|
return out+'>'
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,15 @@ class Model2C(Model2B):
|
||||||
class Model2D(Model2C):
|
class Model2D(Model2C):
|
||||||
field4 = models.CharField(max_length=10)
|
field4 = models.CharField(max_length=10)
|
||||||
|
|
||||||
|
class ModelExtraA(ShowFieldTypeAndContent, PolymorphicModel):
|
||||||
|
field1 = models.CharField(max_length=10)
|
||||||
|
class ModelExtraB(ModelExtraA):
|
||||||
|
field2 = models.CharField(max_length=10)
|
||||||
|
class ModelExtraC(ModelExtraB):
|
||||||
|
field3 = models.CharField(max_length=10)
|
||||||
|
class ModelExtraExternal(models.Model):
|
||||||
|
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')
|
||||||
|
|
@ -360,6 +369,21 @@ __test__ = {"doctest": """
|
||||||
[ <Model2B: id 2, field1 (CharField), field2 (CharField)>,
|
[ <Model2B: id 2, field1 (CharField), field2 (CharField)>,
|
||||||
<Model2C: id 3, field1 (CharField), field2 (CharField), field3 (CharField)> ]
|
<Model2C: id 3, field1 (CharField), field2 (CharField), field3 (CharField)> ]
|
||||||
|
|
||||||
|
>>> Model2A.objects.extra(select={"select_test": "field1 = 'A1'"}, where=["field1 = 'A1' OR field1 = 'B1'"], order_by = ['-id'] )
|
||||||
|
[ <Model2B: id 2, field1 (CharField), field2 (CharField) - Extra: select_test (int)>,
|
||||||
|
<Model2A: id 1, field1 (CharField) - Extra: select_test (int)> ]
|
||||||
|
|
||||||
|
>>> o=ModelExtraA.objects.create(field1='A1')
|
||||||
|
>>> o=ModelExtraB.objects.create(field1='B1', field2='B2')
|
||||||
|
>>> o=ModelExtraC.objects.create(field1='C1', field2='C2', field3='C3')
|
||||||
|
>>> o=ModelExtraExternal.objects.create(topic='extra1')
|
||||||
|
>>> o=ModelExtraExternal.objects.create(topic='extra2')
|
||||||
|
>>> o=ModelExtraExternal.objects.create(topic='extra3')
|
||||||
|
>>> ModelExtraA.objects.extra(tables=["polymorphic_modelextraexternal"], select={"topic":"polymorphic_modelextraexternal.topic"}, where=["polymorphic_modelextraa.id = polymorphic_modelextraexternal.id"] )
|
||||||
|
[ <ModelExtraA: id 1, field1 (CharField): "A1" - Extra: topic (unicode): "extra1">,
|
||||||
|
<ModelExtraB: id 2, field1 (CharField): "B1", field2 (CharField): "B2" - Extra: topic (unicode): "extra2">,
|
||||||
|
<ModelExtraC: id 3, field1 (CharField): "C1", field2 (CharField): "C2", field3 (CharField): "C3" - Extra: topic (unicode): "extra3"> ]
|
||||||
|
|
||||||
|
|
||||||
### class filtering, instance_of, not_instance_of
|
### class filtering, instance_of, not_instance_of
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue