diff --git a/django/core/management.py b/django/core/management.py index 953eeca734..9ba79273f9 100644 --- a/django/core/management.py +++ b/django/core/management.py @@ -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())), diff --git a/django/core/serializers/python.py b/django/core/serializers/python.py index 1e1e6f4bec..a9baa01d5a 100644 --- a/django/core/serializers/python.py +++ b/django/core/serializers/python.py @@ -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: diff --git a/django/core/serializers/xml_serializer.py b/django/core/serializers/xml_serializer.py index 512b8c6176..8fa7bbfac9 100644 --- a/django/core/serializers/xml_serializer.py +++ b/django/core/serializers/xml_serializer.py @@ -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 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 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): """ diff --git a/django/db/backends/ado_mssql/base.py b/django/db/backends/ado_mssql/base.py index 72d2fe083e..5e916491a5 100644 --- a/django/db/backends/ado_mssql/base.py +++ b/django/db/backends/ado_mssql/base.py @@ -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 diff --git a/django/db/backends/dummy/base.py b/django/db/backends/dummy/base.py index f98afc48bb..5fa3b9c90e 100644 --- a/django/db/backends/dummy/base.py +++ b/django/db/backends/dummy/base.py @@ -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 = {} diff --git a/django/db/backends/mysql/base.py b/django/db/backends/mysql/base.py index 28c5b1c683..fb69d7d45f 100644 --- a/django/db/backends/mysql/base.py +++ b/django/db/backends/mysql/base.py @@ -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 diff --git a/django/db/backends/oracle/base.py b/django/db/backends/oracle/base.py index 3a13f39546..ca694eb66d 100644 --- a/django/db/backends/oracle/base.py +++ b/django/db/backends/oracle/base.py @@ -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 diff --git a/django/db/backends/postgresql/base.py b/django/db/backends/postgresql/base.py index 2342539a6f..8ccf98344c 100644 --- a/django/db/backends/postgresql/base.py +++ b/django/db/backends/postgresql/base.py @@ -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 diff --git a/django/db/backends/postgresql_psycopg2/base.py b/django/db/backends/postgresql_psycopg2/base.py index cfddb96bee..abe01873b2 100644 --- a/django/db/backends/postgresql_psycopg2/base.py +++ b/django/db/backends/postgresql_psycopg2/base.py @@ -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 diff --git a/django/db/backends/sqlite3/base.py b/django/db/backends/sqlite3/base.py index 891320160f..db0393782d 100644 --- a/django/db/backends/sqlite3/base.py +++ b/django/db/backends/sqlite3/base.py @@ -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 diff --git a/tests/modeltests/serializers/models.py b/tests/modeltests/serializers/models.py index e24ff537c1..e86546c6fe 100644 --- a/tests/modeltests/serializers/models.py +++ b/tests/modeltests/serializers/models.py @@ -139,4 +139,24 @@ __test__ = {'API_TESTS':""" ... print obj +# 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.categories.all() +[, ] +>>> article.author + + """}