mirror of
				https://github.com/django/django.git
				synced 2025-10-31 09:41:08 +00:00 
			
		
		
		
	Fixed #3390: the serializer can now contain forward references. Thanks, Russ.
git-svn-id: http://code.djangoproject.com/svn/django/trunk@4610 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
		| @@ -167,7 +167,8 @@ def _get_sql_model_create(model, known_models=set()): | ||||
|                 if f.rel.to in known_models: | ||||
|                     field_output.append(style.SQL_KEYWORD('REFERENCES') + ' ' + \ | ||||
|                         style.SQL_TABLE(backend.quote_name(f.rel.to._meta.db_table)) + ' (' + \ | ||||
|                         style.SQL_FIELD(backend.quote_name(f.rel.to._meta.get_field(f.rel.field_name).column)) + ')' | ||||
|                         style.SQL_FIELD(backend.quote_name(f.rel.to._meta.get_field(f.rel.field_name).column)) + ')' +  | ||||
|                         backend.get_deferrable_sql() | ||||
|                     ) | ||||
|                 else: | ||||
|                     # We haven't yet created the table to which this field | ||||
| @@ -210,9 +211,10 @@ def _get_sql_for_pending_references(model, pending_references): | ||||
|                 # For MySQL, r_name must be unique in the first 64 characters. | ||||
|                 # So we are careful with character usage here. | ||||
|                 r_name = '%s_refs_%s_%x' % (r_col, col, abs(hash((r_table, table)))) | ||||
|                 final_output.append(style.SQL_KEYWORD('ALTER TABLE') + ' %s ADD CONSTRAINT %s FOREIGN KEY (%s) REFERENCES %s (%s);' % \ | ||||
|                 final_output.append(style.SQL_KEYWORD('ALTER TABLE') + ' %s ADD CONSTRAINT %s FOREIGN KEY (%s) REFERENCES %s (%s)%s;' % \ | ||||
|                     (backend.quote_name(r_table), r_name, | ||||
|                     backend.quote_name(r_col), backend.quote_name(table), backend.quote_name(col))) | ||||
|                     backend.quote_name(r_col), backend.quote_name(table), backend.quote_name(col),  | ||||
|                     backend.get_deferrable_sql())) | ||||
|             del pending_references[model] | ||||
|     return final_output | ||||
|  | ||||
| @@ -232,18 +234,20 @@ def _get_many_to_many_sql_for_model(model): | ||||
|                 (style.SQL_FIELD(backend.quote_name('id')), | ||||
|                 style.SQL_COLTYPE(data_types['AutoField']), | ||||
|                 style.SQL_KEYWORD('NOT NULL PRIMARY KEY'))) | ||||
|             table_output.append('    %s %s %s %s (%s),' % \ | ||||
|             table_output.append('    %s %s %s %s (%s)%s,' % \ | ||||
|                 (style.SQL_FIELD(backend.quote_name(f.m2m_column_name())), | ||||
|                 style.SQL_COLTYPE(data_types[get_rel_data_type(opts.pk)] % opts.pk.__dict__), | ||||
|                 style.SQL_KEYWORD('NOT NULL REFERENCES'), | ||||
|                 style.SQL_TABLE(backend.quote_name(opts.db_table)), | ||||
|                 style.SQL_FIELD(backend.quote_name(opts.pk.column)))) | ||||
|             table_output.append('    %s %s %s %s (%s),' % \ | ||||
|                 style.SQL_FIELD(backend.quote_name(opts.pk.column)), | ||||
|                 backend.get_deferrable_sql())) | ||||
|             table_output.append('    %s %s %s %s (%s)%s,' % \ | ||||
|                 (style.SQL_FIELD(backend.quote_name(f.m2m_reverse_name())), | ||||
|                 style.SQL_COLTYPE(data_types[get_rel_data_type(f.rel.to._meta.pk)] % f.rel.to._meta.pk.__dict__), | ||||
|                 style.SQL_KEYWORD('NOT NULL REFERENCES'), | ||||
|                 style.SQL_TABLE(backend.quote_name(f.rel.to._meta.db_table)), | ||||
|                 style.SQL_FIELD(backend.quote_name(f.rel.to._meta.pk.column)))) | ||||
|                 style.SQL_FIELD(backend.quote_name(f.rel.to._meta.pk.column)), | ||||
|                 backend.get_deferrable_sql())) | ||||
|             table_output.append('    %s (%s, %s)' % \ | ||||
|                 (style.SQL_KEYWORD('UNIQUE'), | ||||
|                 style.SQL_FIELD(backend.quote_name(f.m2m_column_name())), | ||||
|   | ||||
| @@ -67,20 +67,19 @@ def Deserializer(object_list, **options): | ||||
|                  | ||||
|             field = Model._meta.get_field(field_name) | ||||
|              | ||||
|             # Handle M2M relations (with in_bulk() for performance) | ||||
|             # Handle M2M relations | ||||
|             if field.rel and isinstance(field.rel, models.ManyToManyRel): | ||||
|                 pks = [] | ||||
|                 for pk in field_value: | ||||
|                     if isinstance(pk, unicode): | ||||
|                         pk = pk.encode(options.get("encoding", settings.DEFAULT_CHARSET)) | ||||
|                 m2m_data[field.name] = field.rel.to._default_manager.in_bulk(field_value).values() | ||||
|                         pks.append(pk.encode(options.get("encoding", settings.DEFAULT_CHARSET))) | ||||
|                     else: | ||||
|                         pks.append(pk) | ||||
|                 m2m_data[field.name] = pks | ||||
|                  | ||||
|             # Handle FK fields | ||||
|             elif field.rel and isinstance(field.rel, models.ManyToOneRel) and field_value is not None: | ||||
|                 try: | ||||
|                     data[field.name] = field.rel.to._default_manager.get(pk=field_value) | ||||
|                 except field.rel.to.DoesNotExist: | ||||
|                     data[field.name] = None | ||||
|             elif field.rel and isinstance(field.rel, models.ManyToOneRel): | ||||
|                 data[field.attname] = field_value | ||||
|                      | ||||
|             # Handle all other fields | ||||
|             else: | ||||
|   | ||||
| @@ -150,7 +150,7 @@ class Deserializer(base.Deserializer): | ||||
|             if field.rel and isinstance(field.rel, models.ManyToManyRel): | ||||
|                 m2m_data[field.name] = self._handle_m2m_field_node(field_node) | ||||
|             elif field.rel and isinstance(field.rel, models.ManyToOneRel): | ||||
|                 data[field.name] = self._handle_fk_field_node(field_node) | ||||
|                 data[field.attname] = self._handle_fk_field_node(field_node) | ||||
|             else: | ||||
|                 value = field.to_python(getInnerText(field_node).strip().encode(self.encoding)) | ||||
|                 data[field.name] = value | ||||
| @@ -162,27 +162,17 @@ class Deserializer(base.Deserializer): | ||||
|         """ | ||||
|         Handle a <field> node for a ForeignKey | ||||
|         """ | ||||
|         # Try to set the foreign key by looking up the foreign related object. | ||||
|         # If it doesn't exist, set the field to None (which might trigger  | ||||
|         # validation error, but that's expected). | ||||
|         RelatedModel = self._get_model_from_node(node, "to") | ||||
|         # Check if there is a child node named 'None', returning None if so. | ||||
|         if len(node.childNodes) == 1 and node.childNodes[0].nodeName == 'None': | ||||
|             return None | ||||
|         else: | ||||
|             return RelatedModel.objects.get(pk=getInnerText(node).strip().encode(self.encoding)) | ||||
|             return getInnerText(node).strip().encode(self.encoding) | ||||
|          | ||||
|     def _handle_m2m_field_node(self, node): | ||||
|         """ | ||||
|         Handle a <field> node for a ManyToManyField | ||||
|         """ | ||||
|         # Load the related model | ||||
|         RelatedModel = self._get_model_from_node(node, "to") | ||||
|          | ||||
|         # Look up all the related objects. Using the in_bulk() lookup ensures | ||||
|         # that missing related objects don't cause an exception | ||||
|         related_ids = [c.getAttribute("pk").encode(self.encoding) for c in node.getElementsByTagName("object")] | ||||
|         return RelatedModel._default_manager.in_bulk(related_ids).values() | ||||
|         return [c.getAttribute("pk").encode(self.encoding) for c in node.getElementsByTagName("object")] | ||||
|      | ||||
|     def _get_model_from_node(self, node, attr): | ||||
|         """ | ||||
|   | ||||
| @@ -125,6 +125,9 @@ def get_limit_offset_sql(limit, offset=None): | ||||
| def get_random_function_sql(): | ||||
|     return "RAND()" | ||||
|  | ||||
| def get_deferrable_sql(): | ||||
|     return " DEFERRABLE INITIALLY DEFERRED" | ||||
|  | ||||
| def get_fulltext_search_sql(field_name): | ||||
|     raise NotImplementedError | ||||
|  | ||||
|   | ||||
| @@ -36,6 +36,7 @@ get_date_extract_sql = complain | ||||
| get_date_trunc_sql = complain | ||||
| get_limit_offset_sql = complain | ||||
| get_random_function_sql = complain | ||||
| get_deferrable_sql = complain | ||||
| get_fulltext_search_sql = complain | ||||
| get_drop_foreignkey_sql = complain | ||||
| OPERATOR_MAPPING = {} | ||||
|   | ||||
| @@ -174,6 +174,9 @@ def get_limit_offset_sql(limit, offset=None): | ||||
| def get_random_function_sql(): | ||||
|     return "RAND()" | ||||
|  | ||||
| def get_deferrable_sql(): | ||||
|     return "" | ||||
|  | ||||
| def get_fulltext_search_sql(field_name): | ||||
|     return 'MATCH (%s) AGAINST (%%s IN BOOLEAN MODE)' % field_name | ||||
|  | ||||
|   | ||||
| @@ -108,6 +108,9 @@ def get_limit_offset_sql(limit, offset=None): | ||||
| def get_random_function_sql(): | ||||
|     return "DBMS_RANDOM.RANDOM" | ||||
|  | ||||
| def get_deferrable_sql(): | ||||
|     return " DEFERRABLE INITIALLY DEFERRED" | ||||
|  | ||||
| def get_fulltext_search_sql(field_name): | ||||
|     raise NotImplementedError | ||||
|  | ||||
|   | ||||
| @@ -139,6 +139,9 @@ def get_limit_offset_sql(limit, offset=None): | ||||
| def get_random_function_sql(): | ||||
|     return "RANDOM()" | ||||
|  | ||||
| def get_deferrable_sql(): | ||||
|     return " DEFERRABLE INITIALLY DEFERRED" | ||||
|      | ||||
| def get_fulltext_search_sql(field_name): | ||||
|     raise NotImplementedError | ||||
|  | ||||
|   | ||||
| @@ -99,6 +99,9 @@ def get_limit_offset_sql(limit, offset=None): | ||||
| def get_random_function_sql(): | ||||
|     return "RANDOM()" | ||||
|  | ||||
| def get_deferrable_sql(): | ||||
|     return " DEFERRABLE INITIALLY DEFERRED" | ||||
|  | ||||
| def get_fulltext_search_sql(field_name): | ||||
|     raise NotImplementedError | ||||
|  | ||||
|   | ||||
| @@ -139,6 +139,9 @@ def get_limit_offset_sql(limit, offset=None): | ||||
| def get_random_function_sql(): | ||||
|     return "RANDOM()" | ||||
|  | ||||
| def get_deferrable_sql(): | ||||
|     return "" | ||||
|  | ||||
| def get_fulltext_search_sql(field_name): | ||||
|     raise NotImplementedError | ||||
|  | ||||
|   | ||||
| @@ -139,4 +139,24 @@ __test__ = {'API_TESTS':""" | ||||
| ...     print obj | ||||
| <DeserializedObject: Profile of Joe> | ||||
|  | ||||
| # Objects ids can be referenced before they are defined in the serialization data | ||||
| # However, the deserialization process will need to be contained within a transaction | ||||
| >>> json = '[{"pk": "3", "model": "serializers.article", "fields": {"headline": "Forward references pose no problem", "pub_date": "2006-06-16 15:00:00", "categories": [4, 1], "author": 4}}, {"pk": "4", "model": "serializers.category", "fields": {"name": "Reference"}}, {"pk": "4", "model": "serializers.author", "fields": {"name": "Agnes"}}]' | ||||
| >>> from django.db import transaction | ||||
| >>> transaction.enter_transaction_management() | ||||
| >>> transaction.managed(True) | ||||
| >>> for obj in serializers.deserialize("json", json): | ||||
| ...     obj.save() | ||||
|  | ||||
| >>> transaction.commit() | ||||
| >>> transaction.leave_transaction_management() | ||||
|  | ||||
| >>> article = Article.objects.get(pk=3) | ||||
| >>> article | ||||
| <Article: Forward references pose no problem> | ||||
| >>> article.categories.all() | ||||
| [<Category: Reference>, <Category: Sports>] | ||||
| >>> article.author | ||||
| <Author: Agnes> | ||||
|  | ||||
| """} | ||||
|   | ||||
		Reference in New Issue
	
	Block a user