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:
parent
a30e3fca48
commit
51f39d59bd
|
@ -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
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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())),
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -150,7 +150,7 @@ class Deserializer(base.Deserializer):
|
||||||
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
|
||||||
|
@ -162,27 +162,17 @@ class Deserializer(base.Deserializer):
|
||||||
"""
|
"""
|
||||||
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):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -36,6 +36,7 @@ get_date_extract_sql = complain
|
||||||
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 = {}
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -139,4 +139,24 @@ __test__ = {'API_TESTS':"""
|
||||||
... 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>
|
||||||
|
|
||||||
"""}
|
"""}
|
||||||
|
|
Loading…
Reference in New Issue