_dynamic field updates - fixed pickling and creation order
Dynamic fields are ordered based on creation and stored in _fields_ordered (#396) Fixed pickling dynamic documents `_dynamic_fields` (#387)
This commit is contained in:
		| @@ -42,6 +42,9 @@ class BaseDocument(object): | ||||
|             # Combine positional arguments with named arguments. | ||||
|             # We only want named arguments. | ||||
|             field = iter(self._fields_ordered) | ||||
|             # If its an automatic id field then skip to the first defined field | ||||
|             if self._auto_id_field: | ||||
|                 next(field) | ||||
|             for value in args: | ||||
|                 name = next(field) | ||||
|                 if name in values: | ||||
| @@ -51,6 +54,7 @@ class BaseDocument(object): | ||||
|         signals.pre_init.send(self.__class__, document=self, values=values) | ||||
|  | ||||
|         self._data = {} | ||||
|         self._dynamic_fields = SON() | ||||
|  | ||||
|         # Assign default values to instance | ||||
|         for key, field in self._fields.iteritems(): | ||||
| @@ -61,7 +65,6 @@ class BaseDocument(object): | ||||
|  | ||||
|         # Set passed values after initialisation | ||||
|         if self._dynamic: | ||||
|             self._dynamic_fields = {} | ||||
|             dynamic_data = {} | ||||
|             for key, value in values.iteritems(): | ||||
|                 if key in self._fields or key == '_id': | ||||
| @@ -116,6 +119,7 @@ class BaseDocument(object): | ||||
|                 field = DynamicField(db_field=name) | ||||
|                 field.name = name | ||||
|                 self._dynamic_fields[name] = field | ||||
|                 self._fields_ordered += (name,) | ||||
|  | ||||
|             if not name.startswith('_'): | ||||
|                 value = self.__expand_dynamic_values(name, value) | ||||
| @@ -142,7 +146,8 @@ class BaseDocument(object): | ||||
|  | ||||
|     def __getstate__(self): | ||||
|         data = {} | ||||
|         for k in ('_changed_fields', '_initialised', '_created'): | ||||
|         for k in ('_changed_fields', '_initialised', '_created', | ||||
|                   '_dynamic_fields', '_fields_ordered'): | ||||
|             if hasattr(self, k): | ||||
|                 data[k] = getattr(self, k) | ||||
|         data['_data'] = self.to_mongo() | ||||
| @@ -151,21 +156,21 @@ class BaseDocument(object): | ||||
|     def __setstate__(self, data): | ||||
|         if isinstance(data["_data"], SON): | ||||
|             data["_data"] = self.__class__._from_son(data["_data"])._data | ||||
|         for k in ('_changed_fields', '_initialised', '_created', '_data'): | ||||
|         for k in ('_changed_fields', '_initialised', '_created', '_data', | ||||
|                   '_fields_ordered', '_dynamic_fields'): | ||||
|             if k in data: | ||||
|                 setattr(self, k, data[k]) | ||||
|         for k in data.get('_dynamic_fields').keys(): | ||||
|             setattr(self, k, data["_data"].get(k)) | ||||
|  | ||||
|     def __iter__(self): | ||||
|         if 'id' in self._fields and 'id' not in self._fields_ordered: | ||||
|             return iter(('id', ) + self._fields_ordered) | ||||
|  | ||||
|         return iter(self._fields_ordered) | ||||
|  | ||||
|     def __getitem__(self, name): | ||||
|         """Dictionary-style field access, return a field's value if present. | ||||
|         """ | ||||
|         try: | ||||
|             if name in self._fields: | ||||
|             if name in self._fields_ordered: | ||||
|                 return getattr(self, name) | ||||
|         except AttributeError: | ||||
|             pass | ||||
| @@ -241,6 +246,8 @@ class BaseDocument(object): | ||||
|         for field_name in self: | ||||
|             value = self._data.get(field_name, None) | ||||
|             field = self._fields.get(field_name) | ||||
|             if field is None and self._dynamic: | ||||
|                 field = self._dynamic_fields.get(field_name) | ||||
|  | ||||
|             if value is not None: | ||||
|                 value = field.to_mongo(value) | ||||
| @@ -265,15 +272,6 @@ class BaseDocument(object): | ||||
|            not self._meta.get('allow_inheritance', ALLOW_INHERITANCE)): | ||||
|             data.pop('_cls') | ||||
|  | ||||
|         if not self._dynamic: | ||||
|             return data | ||||
|  | ||||
|         # Sort dynamic fields by key | ||||
|         dynamic_fields = sorted(self._dynamic_fields.iteritems(), | ||||
|                                 key=operator.itemgetter(0)) | ||||
|         for name, field in dynamic_fields: | ||||
|             data[name] = field.to_mongo(self._data.get(name, None)) | ||||
|  | ||||
|         return data | ||||
|  | ||||
|     def validate(self, clean=True): | ||||
| @@ -289,11 +287,8 @@ class BaseDocument(object): | ||||
|                 errors[NON_FIELD_ERRORS] = error | ||||
|  | ||||
|         # Get a list of tuples of field names and their current values | ||||
|         fields = [(field, self._data.get(name)) | ||||
|                   for name, field in self._fields.items()] | ||||
|         if self._dynamic: | ||||
|             fields += [(field, self._data.get(name)) | ||||
|                        for name, field in self._dynamic_fields.items()] | ||||
|         fields = [(self._fields.get(name, self._dynamic_fields.get(name)), | ||||
|                    self._data.get(name)) for name in self._fields_ordered] | ||||
|  | ||||
|         EmbeddedDocumentField = _import_class("EmbeddedDocumentField") | ||||
|         GenericEmbeddedDocumentField = _import_class("GenericEmbeddedDocumentField") | ||||
| @@ -406,11 +401,7 @@ class BaseDocument(object): | ||||
|                 return _changed_fields | ||||
|             inspected.add(self.id) | ||||
|  | ||||
|         field_list = self._fields.copy() | ||||
|         if self._dynamic: | ||||
|             field_list.update(self._dynamic_fields) | ||||
|  | ||||
|         for field_name in field_list: | ||||
|         for field_name in self._fields_ordered: | ||||
|  | ||||
|             db_field_name = self._db_field_map.get(field_name, field_name) | ||||
|             key = '%s.' % db_field_name | ||||
| @@ -450,7 +441,6 @@ class BaseDocument(object): | ||||
|         doc = self.to_mongo() | ||||
|  | ||||
|         set_fields = self._get_changed_fields() | ||||
|         set_data = {} | ||||
|         unset_data = {} | ||||
|         parts = [] | ||||
|         if hasattr(self, '_changed_fields'): | ||||
|   | ||||
| @@ -91,11 +91,12 @@ class DocumentMetaclass(type): | ||||
|         attrs['_fields'] = doc_fields | ||||
|         attrs['_db_field_map'] = dict([(k, getattr(v, 'db_field', k)) | ||||
|                                       for k, v in doc_fields.iteritems()]) | ||||
|         attrs['_reverse_db_field_map'] = dict( | ||||
|             (v, k) for k, v in attrs['_db_field_map'].iteritems()) | ||||
|  | ||||
|         attrs['_fields_ordered'] = tuple(i[1] for i in sorted( | ||||
|                                          (v.creation_counter, v.name) | ||||
|                                          for v in doc_fields.itervalues())) | ||||
|         attrs['_reverse_db_field_map'] = dict( | ||||
|             (v, k) for k, v in attrs['_db_field_map'].iteritems()) | ||||
|  | ||||
|         # | ||||
|         # Set document hierarchy | ||||
| @@ -358,12 +359,18 @@ class TopLevelDocumentMetaclass(DocumentMetaclass): | ||||
|                     new_class.id = field | ||||
|  | ||||
|         # Set primary key if not defined by the document | ||||
|         new_class._auto_id_field = False | ||||
|         if not new_class._meta.get('id_field'): | ||||
|             new_class._auto_id_field = True | ||||
|             new_class._meta['id_field'] = 'id' | ||||
|             new_class._fields['id'] = ObjectIdField(db_field='_id') | ||||
|             new_class._fields['id'].name = 'id' | ||||
|             new_class.id = new_class._fields['id'] | ||||
|  | ||||
|         # Prepend id field to _fields_ordered | ||||
|         if 'id' in new_class._fields and 'id' not in new_class._fields_ordered: | ||||
|             new_class._fields_ordered = ('id', ) + new_class._fields_ordered | ||||
|  | ||||
|         # Merge in exceptions with parent hierarchy | ||||
|         exceptions_to_merge = (DoesNotExist, MultipleObjectsReturned) | ||||
|         module = attrs.get('__module__') | ||||
|   | ||||
| @@ -460,11 +460,8 @@ class Document(BaseDocument): | ||||
|         else: | ||||
|             msg = "Reloaded document has been deleted" | ||||
|             raise OperationError(msg) | ||||
|         for field in self._fields: | ||||
|         for field in self._fields_ordered: | ||||
|             setattr(self, field, self._reload(field, obj[field])) | ||||
|         if self._dynamic: | ||||
|             for name in self._dynamic_fields.keys(): | ||||
|                 setattr(self, name, self._reload(name, obj._data[name])) | ||||
|         self._changed_fields = obj._changed_fields | ||||
|         self._created = False | ||||
|         return obj | ||||
|   | ||||
		Reference in New Issue
	
	Block a user