django-nonrel/mongodb-engine — Issue #103: queryset update() and ListField/EmbeddedModelField
Author: gpitfield
Created: 2012-01-31 20:10:41+00:00
State: open

I think there's an issue when calling update() on a queryset if the values in update() include a ListField with an EmbeddedModelField in it. Made-up example follows. The problem seems to be that the EmbeddedModelField's pre_save() function is not called during update(), only get_db_prep_value() is called, and the latter expects the field's embedded model instances to have already been parsed into a format suitable for conversion to BSON.

Example:

class EmbeddedModel(models.Model):
a_field = models.IntegerField()

class Parent(models.Model):
some_list_field = ListField(EmbeddedModelField(EmbeddedModel))

Now, let's say we have a new value of some_list_field for an instance of Parent, but don't want to save() the Parent (because we don't want to trigger auto-updated fields, it's a really big object, etc.). So you'd like to be able to do something like this:

Parent.objects.filter(id=some_id).update(some_list_field=new_list_field_value)

This breaks in djangotoolbox fields.py line 270 (get_db_prep_value), with the error

'EmbeddedModel' object is not iterable

My fix, admittedly a raging hack, is to check the type of the values passed to get_db_prep_value, and if they haven't been parsed, to perform the same parsing that pre_save() does.

def get_db_prep_value(self, value, *args, **kwargs):
# (embedded_instance, value_list) replaced by value above
#### hack begins ####
if isinstance(value, tuple):
embedded_instance, value_list = value
else:
embedded_instance = value
value_list = []
for field in embedded_instance._meta.fields:
add = not embedded_instance._entity_exists
value = field.pre_save(embedded_instance, add)
if field.primary_key and value is None:
# exclude unset pks ({"id" : None})
continue
value_list.append((field, value))
### hack ends ###
if value_list is None:
return None
values = dict((field.column, field.get_db_prep_value(value, **kwargs))
for field, value in value_list)
if self.embedded_model is None:
values.update({'_module' : embedded_instance.__class__.__module__,
'_model' : embedded_instance.__class__.__name__})
# This instance will exist in the db very soon.
embedded_instance._entity_exists = True
return values

What I can say in favor of this is that it works. However, what I'm not at all sure of is whether it would just be better to move that parsing step out of pre_save entirely, but I'm out of my league here and in particular don't know if there are other functions expecting the parsed values returned by pre_save().

So I don't know if this is a bug exactly, or if this fix has terrible side effects I've misseed, etc, but there you go. Thoughts?