Skip to content

Commit

Permalink
Fixed #3390: the serializer can now contain forward references. Thank…
Browse files Browse the repository at this point in the history
…s, Russ.

git-svn-id: http://code.djangoproject.com/svn/django/trunk@4610 bcc190cf-cafb-0310-a4f2-bffc1f526a37
  • Loading branch information
jacobian committed Feb 26, 2007
1 parent a30e3fc commit 51f39d5
Show file tree
Hide file tree
Showing 11 changed files with 60 additions and 28 deletions.
18 changes: 11 additions & 7 deletions django/core/management.py
Expand Up @@ -167,7 +167,8 @@ def _get_sql_model_create(model, known_models=set()):
if f.rel.to in known_models: if f.rel.to in known_models:
field_output.append(style.SQL_KEYWORD('REFERENCES') + ' ' + \ field_output.append(style.SQL_KEYWORD('REFERENCES') + ' ' + \
style.SQL_TABLE(backend.quote_name(f.rel.to._meta.db_table)) + ' (' + \ 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: else:
# We haven't yet created the table to which this field # We haven't yet created the table to which this field
Expand Down Expand Up @@ -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. # For MySQL, r_name must be unique in the first 64 characters.
# So we are careful with character usage here. # So we are careful with character usage here.
r_name = '%s_refs_%s_%x' % (r_col, col, abs(hash((r_table, table)))) 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_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] del pending_references[model]
return final_output return final_output


Expand All @@ -232,18 +234,20 @@ def _get_many_to_many_sql_for_model(model):
(style.SQL_FIELD(backend.quote_name('id')), (style.SQL_FIELD(backend.quote_name('id')),
style.SQL_COLTYPE(data_types['AutoField']), style.SQL_COLTYPE(data_types['AutoField']),
style.SQL_KEYWORD('NOT NULL PRIMARY KEY'))) 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_FIELD(backend.quote_name(f.m2m_column_name())),
style.SQL_COLTYPE(data_types[get_rel_data_type(opts.pk)] % opts.pk.__dict__), style.SQL_COLTYPE(data_types[get_rel_data_type(opts.pk)] % opts.pk.__dict__),
style.SQL_KEYWORD('NOT NULL REFERENCES'), style.SQL_KEYWORD('NOT NULL REFERENCES'),
style.SQL_TABLE(backend.quote_name(opts.db_table)), style.SQL_TABLE(backend.quote_name(opts.db_table)),
style.SQL_FIELD(backend.quote_name(opts.pk.column)))) style.SQL_FIELD(backend.quote_name(opts.pk.column)),
table_output.append(' %s %s %s %s (%s),' % \ 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_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_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_KEYWORD('NOT NULL REFERENCES'),
style.SQL_TABLE(backend.quote_name(f.rel.to._meta.db_table)), 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)' % \ table_output.append(' %s (%s, %s)' % \
(style.SQL_KEYWORD('UNIQUE'), (style.SQL_KEYWORD('UNIQUE'),
style.SQL_FIELD(backend.quote_name(f.m2m_column_name())), style.SQL_FIELD(backend.quote_name(f.m2m_column_name())),
Expand Down
15 changes: 7 additions & 8 deletions django/core/serializers/python.py
Expand Up @@ -67,20 +67,19 @@ def Deserializer(object_list, **options):


field = Model._meta.get_field(field_name) 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): if field.rel and isinstance(field.rel, models.ManyToManyRel):
pks = [] pks = []
for pk in field_value: for pk in field_value:
if isinstance(pk, unicode): if isinstance(pk, unicode):
pk = pk.encode(options.get("encoding", settings.DEFAULT_CHARSET)) pks.append(pk.encode(options.get("encoding", settings.DEFAULT_CHARSET)))
m2m_data[field.name] = field.rel.to._default_manager.in_bulk(field_value).values() else:
pks.append(pk)
m2m_data[field.name] = pks


# Handle FK fields # Handle FK fields
elif field.rel and isinstance(field.rel, models.ManyToOneRel) and field_value is not None: elif field.rel and isinstance(field.rel, models.ManyToOneRel):
try: data[field.attname] = field_value
data[field.name] = field.rel.to._default_manager.get(pk=field_value)
except field.rel.to.DoesNotExist:
data[field.name] = None


# Handle all other fields # Handle all other fields
else: else:
Expand Down
16 changes: 3 additions & 13 deletions django/core/serializers/xml_serializer.py
Expand Up @@ -150,7 +150,7 @@ def _handle_object(self, node):
if field.rel and isinstance(field.rel, models.ManyToManyRel): if field.rel and isinstance(field.rel, models.ManyToManyRel):
m2m_data[field.name] = self._handle_m2m_field_node(field_node) m2m_data[field.name] = self._handle_m2m_field_node(field_node)
elif field.rel and isinstance(field.rel, models.ManyToOneRel): 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: else:
value = field.to_python(getInnerText(field_node).strip().encode(self.encoding)) value = field.to_python(getInnerText(field_node).strip().encode(self.encoding))
data[field.name] = value data[field.name] = value
Expand All @@ -162,27 +162,17 @@ def _handle_fk_field_node(self, node):
""" """
Handle a <field> node for a ForeignKey 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. # Check if there is a child node named 'None', returning None if so.
if len(node.childNodes) == 1 and node.childNodes[0].nodeName == 'None': if len(node.childNodes) == 1 and node.childNodes[0].nodeName == 'None':
return None return None
else: 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): def _handle_m2m_field_node(self, node):
""" """
Handle a <field> node for a ManyToManyField Handle a <field> node for a ManyToManyField
""" """
# Load the related model return [c.getAttribute("pk").encode(self.encoding) for c in node.getElementsByTagName("object")]
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()


def _get_model_from_node(self, node, attr): def _get_model_from_node(self, node, attr):
""" """
Expand Down
3 changes: 3 additions & 0 deletions django/db/backends/ado_mssql/base.py
Expand Up @@ -125,6 +125,9 @@ def get_limit_offset_sql(limit, offset=None):
def get_random_function_sql(): def get_random_function_sql():
return "RAND()" return "RAND()"


def get_deferrable_sql():
return " DEFERRABLE INITIALLY DEFERRED"

def get_fulltext_search_sql(field_name): def get_fulltext_search_sql(field_name):
raise NotImplementedError raise NotImplementedError


Expand Down
1 change: 1 addition & 0 deletions django/db/backends/dummy/base.py
Expand Up @@ -36,6 +36,7 @@ def close(self):
get_date_trunc_sql = complain get_date_trunc_sql = complain
get_limit_offset_sql = complain get_limit_offset_sql = complain
get_random_function_sql = complain get_random_function_sql = complain
get_deferrable_sql = complain
get_fulltext_search_sql = complain get_fulltext_search_sql = complain
get_drop_foreignkey_sql = complain get_drop_foreignkey_sql = complain
OPERATOR_MAPPING = {} OPERATOR_MAPPING = {}
3 changes: 3 additions & 0 deletions django/db/backends/mysql/base.py
Expand Up @@ -174,6 +174,9 @@ def get_limit_offset_sql(limit, offset=None):
def get_random_function_sql(): def get_random_function_sql():
return "RAND()" return "RAND()"


def get_deferrable_sql():
return ""

def get_fulltext_search_sql(field_name): def get_fulltext_search_sql(field_name):
return 'MATCH (%s) AGAINST (%%s IN BOOLEAN MODE)' % field_name return 'MATCH (%s) AGAINST (%%s IN BOOLEAN MODE)' % field_name


Expand Down
3 changes: 3 additions & 0 deletions django/db/backends/oracle/base.py
Expand Up @@ -108,6 +108,9 @@ def get_limit_offset_sql(limit, offset=None):
def get_random_function_sql(): def get_random_function_sql():
return "DBMS_RANDOM.RANDOM" return "DBMS_RANDOM.RANDOM"


def get_deferrable_sql():
return " DEFERRABLE INITIALLY DEFERRED"

def get_fulltext_search_sql(field_name): def get_fulltext_search_sql(field_name):
raise NotImplementedError raise NotImplementedError


Expand Down
3 changes: 3 additions & 0 deletions django/db/backends/postgresql/base.py
Expand Up @@ -139,6 +139,9 @@ def get_limit_offset_sql(limit, offset=None):
def get_random_function_sql(): def get_random_function_sql():
return "RANDOM()" return "RANDOM()"


def get_deferrable_sql():
return " DEFERRABLE INITIALLY DEFERRED"

def get_fulltext_search_sql(field_name): def get_fulltext_search_sql(field_name):
raise NotImplementedError raise NotImplementedError


Expand Down
3 changes: 3 additions & 0 deletions django/db/backends/postgresql_psycopg2/base.py
Expand Up @@ -99,6 +99,9 @@ def get_limit_offset_sql(limit, offset=None):
def get_random_function_sql(): def get_random_function_sql():
return "RANDOM()" return "RANDOM()"


def get_deferrable_sql():
return " DEFERRABLE INITIALLY DEFERRED"

def get_fulltext_search_sql(field_name): def get_fulltext_search_sql(field_name):
raise NotImplementedError raise NotImplementedError


Expand Down
3 changes: 3 additions & 0 deletions django/db/backends/sqlite3/base.py
Expand Up @@ -139,6 +139,9 @@ def get_limit_offset_sql(limit, offset=None):
def get_random_function_sql(): def get_random_function_sql():
return "RANDOM()" return "RANDOM()"


def get_deferrable_sql():
return ""

def get_fulltext_search_sql(field_name): def get_fulltext_search_sql(field_name):
raise NotImplementedError raise NotImplementedError


Expand Down
20 changes: 20 additions & 0 deletions tests/modeltests/serializers/models.py
Expand Up @@ -139,4 +139,24 @@ def __str__(self):
... print obj ... print obj
<DeserializedObject: Profile of Joe> <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>
"""} """}

0 comments on commit 51f39d5

Please sign in to comment.