extra(): Re-implemented. Now is polymorphic (nearly) without restrictions.

Added test cases + docs.
This commit is contained in:
Bert Constantin
2010-10-23 10:58:44 +02:00
parent 6c8d28cbbc
commit 19adbdaf2c
6 changed files with 84 additions and 49 deletions
+28 -21
View File
@@ -84,23 +84,11 @@ class PolymorphicQuerySet(QuerySet):
self.polymorphic_disabled = True
return super(PolymorphicQuerySet, self).aggregate(*args, **kwargs)
def extra(self, *args, **kwargs):
"""since django_polymorphic 'V1.0 beta2' extra() returns polymorphic results by default.
Currently, for polymorphic queries, only the parameters 'where','order_by', 'params' are
supported and an error is thrown if other parameters are given.
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)
# Since django_polymorphic 'V1.0 beta2', extra() always returns polymorphic results.
# The resulting objects are required to have a unique primary key within the result set
# (otherwise an error is thrown).
# The "polymorphic" keyword argument is not supported anymore.
#def extra(self, *args, **kwargs):
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
for base_object in base_result_objects:
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
# 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)
# 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 extra() select fields from the base objects to the real objects.
# TODO: defer(), only(): support for these would be around here
for modelclass, idlist in idlist_per_model.items():
qs = modelclass.base_objects.filter(id__in=idlist)
@@ -165,9 +159,15 @@ class PolymorphicQuerySet(QuerySet):
for o in qs:
if self.query.aggregates:
for anno in self.query.aggregates.keys():
attr = getattr(base_result_objects_by_id[o.pk], anno)
setattr(o, anno, attr)
for anno_field_name in self.query.aggregates.keys():
attr = getattr(base_result_objects_by_id[o.pk], anno_field_name)
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
# 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)
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:
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
def iterator(self):
+11 -4
View File
@@ -40,16 +40,23 @@ class ShowFieldBase(object):
else:
out += ': "' + unicode(o) + '"'
if hasattr(self,'polymorphic_annotate_names'):
out += ' - Ann: '
for an in self.polymorphic_annotate_names:
if an != self.polymorphic_annotate_names[0]:
def get_dynamic_fields(field_list, title):
out = ' - '+title+': '
for an in field_list:
if an != field_list[0]:
out += ', '
out += an
if self.polymorphic_showfield_type:
out += ' (' + type(getattr(self, an)).__name__ + ')'
if self.polymorphic_showfield_content:
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+'>'
+24
View File
@@ -33,6 +33,15 @@ class Model2C(Model2B):
class Model2D(Model2C):
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):
field1 = models.CharField(max_length=10)
m2m = models.ManyToManyField('self')
@@ -360,6 +369,21 @@ __test__ = {"doctest": """
[ <Model2B: id 2, field1 (CharField), field2 (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