diff --git a/django/core/management/commands/dumpdata.py b/django/core/management/commands/dumpdata.py
index 706bf60..8d8b63e 100644
--- a/django/core/management/commands/dumpdata.py
+++ b/django/core/management/commands/dumpdata.py
@@ -19,6 +19,10 @@ class Command(BaseCommand):
help='An appname or appname.ModelName to exclude (use multiple --exclude to exclude multiple apps/models).'),
make_option('-n', '--natural', action='store_true', dest='use_natural_keys', default=False,
help='Use natural keys if they are available.'),
+ make_option('--natural-foreign', action='store_true', dest='use_natural_foreign_keys', default=False,
+ help='Use natural foreign keys if they are available.'),
+ make_option('--natural-primary', action='store_true', dest='use_natural_primary_keys', default=False,
+ help='Use natural primary keys if they are available.'),
make_option('-a', '--all', action='store_true', dest='use_base_manager', default=False,
help="Use Django's base manager to dump all models stored in the database, including those that would otherwise be filtered or modified by a custom manager."),
)
@@ -37,6 +41,11 @@ class Command(BaseCommand):
excludes = options.get('exclude',[])
show_traceback = options.get('traceback', False)
use_natural_keys = options.get('use_natural_keys', False)
+ if use_natural_keys:
+ # raise pending deprecation warning.
+ pass
+ use_natural_foreign_keys = options.get('use_natural_foreign_keys', False) or use_natural_keys
+ use_natural_primary_keys = options.get('use_natural_primary_keys', False)
use_base_manager = options.get('use_base_manager', False)
excluded_apps = set()
@@ -111,7 +120,8 @@ class Command(BaseCommand):
try:
return serializers.serialize(format, objects, indent=indent,
- use_natural_keys=use_natural_keys)
+ use_natural_foreign_keys=use_natural_foreign_keys,
+ use_natural_primary_keys=use_natural_primary_keys)
except Exception, e:
if show_traceback:
raise
diff --git a/django/core/serializers/base.py b/django/core/serializers/base.py
index 6afaf21..e9b0f42 100644
--- a/django/core/serializers/base.py
+++ b/django/core/serializers/base.py
@@ -38,6 +38,11 @@ class Serializer(object):
self.stream = options.pop("stream", StringIO())
self.selected_fields = options.pop("fields", None)
self.use_natural_keys = options.pop("use_natural_keys", False)
+ if self.use_natural_keys:
+ # raise pending deprecation warning.
+ pass
+ self.use_natural_foreign_keys = options.pop('use_natural_foreign_keys', False)
+ self.use_natural_primary_keys = options.pop('use_natural_primary_keys', False)
self.start_serialization()
for obj in queryset:
@@ -174,3 +179,21 @@ class DeserializedObject(object):
# prevent a second (possibly accidental) call to save() from saving
# the m2m data twice.
self.m2m_data = None
+
+def build_instance(Model, data, db):
+ """
+ Build a model instance.
+
+ If the model instance doesn't have a primary key and the model supports
+ natural keys, try to retrieve it from the database.
+ """
+ obj = Model(**data)
+ if obj.pk is None and hasattr(Model, 'natural_key') and\
+ hasattr(Model._default_manager, 'get_by_natural_key'):
+ pk = obj.natural_key()
+ try:
+ obj.pk = Model._default_manager.db_manager(db)\
+ .get_by_natural_key(*pk).pk
+ except Model.DoesNotExist:
+ pass
+ return obj
diff --git a/django/core/serializers/python.py b/django/core/serializers/python.py
index a68ea21..7b839f0 100644
--- a/django/core/serializers/python.py
+++ b/django/core/serializers/python.py
@@ -27,11 +27,18 @@ class Serializer(base.Serializer):
self._current = {}
def end_object(self, obj):
- self.objects.append({
- "model" : smart_unicode(obj._meta),
- "pk" : smart_unicode(obj._get_pk_val(), strings_only=True),
- "fields" : self._current
- })
+ if not self.use_natural_primary_keys or not hasattr(obj, 'natural_key'):
+ data = {
+ "model": smart_unicode(obj._meta),
+ "pk": smart_unicode(obj._get_pk_val(), strings_only=True),
+ "fields": self._current,
+ }
+ else:
+ data = {
+ "model": smart_unicode(obj._meta),
+ "fields": self._current
+ }
+ self.objects.append(data)
self._current = None
def handle_field(self, obj, field):
@@ -47,7 +54,7 @@ class Serializer(base.Serializer):
def handle_fk_field(self, obj, field):
related = getattr(obj, field.name)
if related is not None:
- if self.use_natural_keys and hasattr(related, 'natural_key'):
+ if self.use_natural_foreign_keys and hasattr(related, 'natural_key'):
related = related.natural_key()
else:
if field.rel.field_name == related._meta.pk.name:
@@ -60,7 +67,7 @@ class Serializer(base.Serializer):
def handle_m2m_field(self, obj, field):
if field.rel.through._meta.auto_created:
- if self.use_natural_keys and hasattr(field.rel.to, 'natural_key'):
+ if self.use_natural_foreign_keys and hasattr(field.rel.to, 'natural_key'):
m2m_value = lambda value: value.natural_key()
else:
m2m_value = lambda value: smart_unicode(value._get_pk_val(), strings_only=True)
@@ -82,7 +89,9 @@ def Deserializer(object_list, **options):
for d in object_list:
# Look up the model and starting build a dict of data for it.
Model = _get_model(d["model"])
- data = {Model._meta.pk.attname : Model._meta.pk.to_python(d["pk"])}
+ data = {}
+ if 'pk' in d:
+ data[Model._meta.pk.attname] = Model._meta.pk.to_python(d['pk'])
m2m_data = {}
# Handle each field
@@ -127,7 +136,9 @@ def Deserializer(object_list, **options):
else:
data[field.name] = field.to_python(field_value)
- yield base.DeserializedObject(Model(**data), m2m_data)
+ obj = base.build_instance(Model, data, db)
+
+ yield base.DeserializedObject(obj, m2m_data)
def _get_model(model_identifier):
"""
diff --git a/django/core/serializers/xml_serializer.py b/django/core/serializers/xml_serializer.py
index bcf5631..22205d9 100644
--- a/django/core/serializers/xml_serializer.py
+++ b/django/core/serializers/xml_serializer.py
@@ -42,16 +42,12 @@ class Serializer(base.Serializer):
raise base.SerializationError("Non-model object (%s) encountered during serialization" % type(obj))
self.indent(1)
- obj_pk = obj._get_pk_val()
- if obj_pk is None:
- attrs = {"model": smart_unicode(obj._meta),}
- else:
- attrs = {
- "pk": smart_unicode(obj._get_pk_val()),
- "model": smart_unicode(obj._meta),
- }
-
- self.xml.startElement("object", attrs)
+ object_data = {"model": smart_unicode(obj._meta)}
+ if not self.use_natural_primary_keys or not hasattr(obj, 'natural_key'):
+ obj_pk = obj._get_pk_val()
+ if obj_pk is not None:
+ object_data['pk'] = smart_unicode(obj_pk)
+ self.xml.startElement("object", object_data)
def end_object(self, obj):
"""
@@ -87,7 +83,7 @@ class Serializer(base.Serializer):
self._start_relational_field(field)
related = getattr(obj, field.name)
if related is not None:
- if self.use_natural_keys and hasattr(related, 'natural_key'):
+ if self.use_natural_foreign_keys and hasattr(related, 'natural_key'):
# If related object has a natural key, use it
related = related.natural_key()
# Iterable natural keys are rolled out as subelements
@@ -115,7 +111,7 @@ class Serializer(base.Serializer):
"""
if field.rel.through._meta.auto_created:
self._start_relational_field(field)
- if self.use_natural_keys and hasattr(field.rel.to, 'natural_key'):
+ if self.use_natural_foreign_keys and hasattr(field.rel.to, 'natural_key'):
# If the objects in the m2m have a natural key, use it
def handle_m2m(value):
natural = value.natural_key()
@@ -173,13 +169,10 @@ class Deserializer(base.Deserializer):
Model = self._get_model_from_node(node, "model")
# Start building a data dictionary from the object.
- # If the node is missing the pk set it to None
- if node.hasAttribute("pk"):
- pk = node.getAttribute("pk")
- else:
- pk = None
-
- data = {Model._meta.pk.attname : Model._meta.pk.to_python(pk)}
+ data = {}
+ if node.hasAttribute('pk'):
+ data[Model._meta.pk.attname] = Model._meta.pk.to_python(
+ node.getAttribute('pk'))
# Also start building a dict of m2m data (this is saved as
# {m2m_accessor_attribute : [list_of_related_objects]})
@@ -210,8 +203,10 @@ class Deserializer(base.Deserializer):
value = field.to_python(getInnerText(field_node).strip())
data[field.name] = value
+ obj = base.build_instance(Model, data, self.db)
+
# Return a DeserializedObject so that the m2m data has a place to live.
- return base.DeserializedObject(Model(**data), m2m_data)
+ return base.DeserializedObject(obj, m2m_data)
def _handle_fk_field_node(self, node, field):
"""
diff --git a/docs/topics/serialization.txt b/docs/topics/serialization.txt
index f0f17b2..06ac347 100644
--- a/docs/topics/serialization.txt
+++ b/docs/topics/serialization.txt
@@ -313,6 +313,12 @@ into the primary key of an actual ``Person`` object.
fields will be effectively unique, you can still use those fields
as a natural key.
+.. versionadded:: 1.4
+
+Deserialization of objects with no primary key will always check whether the
+model's manager has a ``get_by_natural_key()`` method and if so, use it to
+populate the deserialized object's primary key.
+
Serialization of natural keys
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -335,17 +341,38 @@ Firstly, you need to add another method -- this time to the model itself::
That method should always return a natural key tuple -- in this
example, ``(first name, last name)``. Then, when you call
-``serializers.serialize()``, you provide a ``use_natural_keys=True``
-argument::
-
- >>> serializers.serialize('json', [book1, book2], indent=2, use_natural_keys=True)
-
-When ``use_natural_keys=True`` is specified, Django will use the
-``natural_key()`` method to serialize any reference to objects of the
-type that defines the method.
+``serializers.serialize()``, you provide ``use_natural_foreign_keys=True``
+or ``use_natural_primary_keys=True`` arguments::
+
+ >>> serializers.serialize('json', [book1, book2], indent=2, use_natural_foreign_keys=True, use_natural_primary_keys=True)
+
+When ``use_natural_foreign_keys=True`` is specified, Django will use the
+``natural_key()`` method to serialize any foreign key reference to objects
+of the type that defines the method.
+
+When ``use_natural_primary_keys=True``is specified, Django will not provide the
+primary key in the serialized data of this object since it can be calculated
+during deserialization::
+
+ ...
+ {
+ "model": "store.person",
+ "fields": {
+ "first_name": "Douglas",
+ "last_name": "Adams",
+ "birth_date": "1952-03-11",
+ }
+ }
+ ...
+
+This can be useful when you need to load serialized data into an existing
+database and you cannot guarantee that the serialized primary key value is not
+already in use, and do not need to ensure that deserialized objects retain the
+same primary keys.
If you are using :djadmin:`dumpdata` to generate serialized data, you
-use the `--natural` command line flag to generate natural keys.
+use the `--natural-foreign` and `--natural-primary` command line flags to
+generate natural keys.
.. note::
@@ -359,6 +386,18 @@ use the `--natural` command line flag to generate natural keys.
natural keys during serialization, but *not* be able to load those
key values, just don't define the ``get_by_natural_key()`` method.
+.. versionchanged:: 1.4
+
+Previously there was only a ``use_natural_keys`` argument for
+``serializers.serialize()`` and the `-n` or `--natural` command line flags.
+These have been deprecated in favor of the ``use_natural_foreign_keys`` and
+``use_natural_primary_keys`` arguments, and the corresponding
+`--natural-foreign` and `--natural-primary` command line flags.
+
+The original argument and command line flags remain for backwards
+compatibility, and map to the new ``use_natural_foreign_keys`` argument and
+`--natural-foreign` command line flag.
+
Dependencies during serialization
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
diff --git a/tests/modeltests/fixtures/tests.py b/tests/modeltests/fixtures/tests.py
index 31f72f0..8d1adb7 100644
--- a/tests/modeltests/fixtures/tests.py
+++ b/tests/modeltests/fixtures/tests.py
@@ -25,13 +25,15 @@ class TestCaseFixtureLoadingTests(TestCase):
class FixtureLoadingTests(TestCase):
- def _dumpdata_assert(self, args, output, format='json', natural_keys=False,
+ def _dumpdata_assert(self, args, output, format='json',
+ natural_foreign_keys=False, natural_primary_keys=False,
use_base_manager=False, exclude_list=[]):
new_io = StringIO.StringIO()
management.call_command('dumpdata', *args, **{'format':format,
'stdout':new_io,
'stderr':new_io,
- 'use_natural_keys':natural_keys,
+ 'use_natural_foreign_keys':natural_foreign_keys,
+ 'use_natural_primary_keys':natural_primary_keys,
'use_base_manager':use_base_manager,
'exclude': exclude_list})
command_output = new_io.getvalue().strip()
@@ -149,15 +151,18 @@ class FixtureLoadingTests(TestCase):
# By default, you get raw keys on dumpdata
self._dumpdata_assert(['fixtures.book'], '[{"pk": 1, "model": "fixtures.book", "fields": {"name": "Music for all ages", "authors": [3, 1]}}]')
- # But you can get natural keys if you ask for them and they are available
- self._dumpdata_assert(['fixtures.book'], '[{"pk": 1, "model": "fixtures.book", "fields": {"name": "Music for all ages", "authors": [["Artist formerly known as \\"Prince\\""], ["Django Reinhardt"]]}}]', natural_keys=True)
+ # But you can get natural foreign keys if you ask for them and they are available
+ self._dumpdata_assert(['fixtures.book'], '[{"pk": 1, "model": "fixtures.book", "fields": {"name": "Music for all ages", "authors": [["Artist formerly known as \\"Prince\\""], ["Django Reinhardt"]]}}]', natural_foreign_keys=True)
+
+ # You can also omit the primary keys for models that we can get later with natural keys.
+ self._dumpdata_assert(['fixtures.person'], '[{"fields": {"name": "Artist formerly known as \\"Prince\\""}, "model": "fixtures.person"}, {"fields": {"name": "Django Reinhardt"}, "model": "fixtures.person"}, {"fields": {"name": "Stephane Grappelli"}, "model": "fixtures.person"}]', natural_primary_keys=True)
# Dump the current contents of the database as a JSON fixture
- self._dumpdata_assert(['fixtures'], '[{"pk": 1, "model": "fixtures.category", "fields": {"description": "Latest news stories", "title": "News Stories"}}, {"pk": 5, "model": "fixtures.article", "fields": {"headline": "XML identified as leading cause of cancer", "pub_date": "2006-06-16 16:00:00"}}, {"pk": 4, "model": "fixtures.article", "fields": {"headline": "Django conquers world!", "pub_date": "2006-06-16 15:00:00"}}, {"pk": 3, "model": "fixtures.article", "fields": {"headline": "Copyright is fine the way it is", "pub_date": "2006-06-16 14:00:00"}}, {"pk": 2, "model": "fixtures.article", "fields": {"headline": "Poker on TV is great!", "pub_date": "2006-06-16 11:00:00"}}, {"pk": 1, "model": "fixtures.article", "fields": {"headline": "Python program becomes self aware", "pub_date": "2006-06-16 11:00:00"}}, {"pk": 1, "model": "fixtures.tag", "fields": {"tagged_type": ["fixtures", "article"], "name": "copyright", "tagged_id": 3}}, {"pk": 2, "model": "fixtures.tag", "fields": {"tagged_type": ["fixtures", "article"], "name": "legal", "tagged_id": 3}}, {"pk": 3, "model": "fixtures.tag", "fields": {"tagged_type": ["fixtures", "article"], "name": "django", "tagged_id": 4}}, {"pk": 4, "model": "fixtures.tag", "fields": {"tagged_type": ["fixtures", "article"], "name": "world domination", "tagged_id": 4}}, {"pk": 3, "model": "fixtures.person", "fields": {"name": "Artist formerly known as \\"Prince\\""}}, {"pk": 1, "model": "fixtures.person", "fields": {"name": "Django Reinhardt"}}, {"pk": 2, "model": "fixtures.person", "fields": {"name": "Stephane Grappelli"}}, {"pk": 1, "model": "fixtures.visa", "fields": {"person": ["Django Reinhardt"], "permissions": [["add_user", "auth", "user"], ["change_user", "auth", "user"], ["delete_user", "auth", "user"]]}}, {"pk": 2, "model": "fixtures.visa", "fields": {"person": ["Stephane Grappelli"], "permissions": [["add_user", "auth", "user"], ["delete_user", "auth", "user"]]}}, {"pk": 3, "model": "fixtures.visa", "fields": {"person": ["Artist formerly known as \\"Prince\\""], "permissions": [["change_user", "auth", "user"]]}}, {"pk": 1, "model": "fixtures.book", "fields": {"name": "Music for all ages", "authors": [["Artist formerly known as \\"Prince\\""], ["Django Reinhardt"]]}}]', natural_keys=True)
+ self._dumpdata_assert(['fixtures'], '[{"pk": 1, "model": "fixtures.category", "fields": {"description": "Latest news stories", "title": "News Stories"}}, {"pk": 5, "model": "fixtures.article", "fields": {"headline": "XML identified as leading cause of cancer", "pub_date": "2006-06-16 16:00:00"}}, {"pk": 4, "model": "fixtures.article", "fields": {"headline": "Django conquers world!", "pub_date": "2006-06-16 15:00:00"}}, {"pk": 3, "model": "fixtures.article", "fields": {"headline": "Copyright is fine the way it is", "pub_date": "2006-06-16 14:00:00"}}, {"pk": 2, "model": "fixtures.article", "fields": {"headline": "Poker on TV is great!", "pub_date": "2006-06-16 11:00:00"}}, {"pk": 1, "model": "fixtures.article", "fields": {"headline": "Python program becomes self aware", "pub_date": "2006-06-16 11:00:00"}}, {"pk": 1, "model": "fixtures.tag", "fields": {"tagged_type": ["fixtures", "article"], "name": "copyright", "tagged_id": 3}}, {"pk": 2, "model": "fixtures.tag", "fields": {"tagged_type": ["fixtures", "article"], "name": "legal", "tagged_id": 3}}, {"pk": 3, "model": "fixtures.tag", "fields": {"tagged_type": ["fixtures", "article"], "name": "django", "tagged_id": 4}}, {"pk": 4, "model": "fixtures.tag", "fields": {"tagged_type": ["fixtures", "article"], "name": "world domination", "tagged_id": 4}}, {"pk": 3, "model": "fixtures.person", "fields": {"name": "Artist formerly known as \\"Prince\\""}}, {"pk": 1, "model": "fixtures.person", "fields": {"name": "Django Reinhardt"}}, {"pk": 2, "model": "fixtures.person", "fields": {"name": "Stephane Grappelli"}}, {"pk": 1, "model": "fixtures.visa", "fields": {"person": ["Django Reinhardt"], "permissions": [["add_user", "auth", "user"], ["change_user", "auth", "user"], ["delete_user", "auth", "user"]]}}, {"pk": 2, "model": "fixtures.visa", "fields": {"person": ["Stephane Grappelli"], "permissions": [["add_user", "auth", "user"], ["delete_user", "auth", "user"]]}}, {"pk": 3, "model": "fixtures.visa", "fields": {"person": ["Artist formerly known as \\"Prince\\""], "permissions": [["change_user", "auth", "user"]]}}, {"pk": 1, "model": "fixtures.book", "fields": {"name": "Music for all ages", "authors": [["Artist formerly known as \\"Prince\\""], ["Django Reinhardt"]]}}]', natural_foreign_keys=True)
# Dump the current contents of the database as an XML fixture
self._dumpdata_assert(['fixtures'], """
-Stephane Grappelliadd_userauthuserdelete_userauthuserArtist formerly known as "Prince"change_userauthuserMusic for all agesArtist formerly known as "Prince"Django Reinhardt""", format='xml', natural_keys=True)
+News StoriesLatest news storiesXML identified as leading cause of cancer2006-06-16 16:00:00Django conquers world!2006-06-16 15:00:00Copyright is fine the way it is2006-06-16 14:00:00Poker on TV is great!2006-06-16 11:00:00Python program becomes self aware2006-06-16 11:00:00copyrightfixturesarticle3legalfixturesarticle3djangofixturesarticle4world dominationfixturesarticle4Artist formerly known as "Prince"Django ReinhardtStephane GrappelliDjango Reinhardtadd_userauthuserchange_userauthuserdelete_userauthuserStephane Grappelliadd_userauthuserdelete_userauthuserArtist formerly known as "Prince"change_userauthuserMusic for all agesArtist formerly known as "Prince"Django Reinhardt""", format='xml', natural_foreign_keys=True)
def test_dumpdata_with_excludes(self):
# Load fixture1 which has a site, two articles, and a category
@@ -289,11 +294,11 @@ class FixtureLoadingTests(TestCase):
])
# Dump the current contents of the database as a JSON fixture
- self._dumpdata_assert(['fixtures'], '[{"pk": 1, "model": "fixtures.category", "fields": {"description": "Latest news stories", "title": "News Stories"}}, {"pk": 3, "model": "fixtures.article", "fields": {"headline": "Time to reform copyright", "pub_date": "2006-06-16 13:00:00"}}, {"pk": 2, "model": "fixtures.article", "fields": {"headline": "Poker has no place on ESPN", "pub_date": "2006-06-16 12:00:00"}}, {"pk": 1, "model": "fixtures.article", "fields": {"headline": "Python program becomes self aware", "pub_date": "2006-06-16 11:00:00"}}, {"pk": 1, "model": "fixtures.tag", "fields": {"tagged_type": ["fixtures", "article"], "name": "copyright", "tagged_id": 3}}, {"pk": 2, "model": "fixtures.tag", "fields": {"tagged_type": ["fixtures", "article"], "name": "law", "tagged_id": 3}}, {"pk": 1, "model": "fixtures.person", "fields": {"name": "Django Reinhardt"}}, {"pk": 3, "model": "fixtures.person", "fields": {"name": "Prince"}}, {"pk": 2, "model": "fixtures.person", "fields": {"name": "Stephane Grappelli"}}]', natural_keys=True)
+ self._dumpdata_assert(['fixtures'], '[{"pk": 1, "model": "fixtures.category", "fields": {"description": "Latest news stories", "title": "News Stories"}}, {"pk": 3, "model": "fixtures.article", "fields": {"headline": "Time to reform copyright", "pub_date": "2006-06-16 13:00:00"}}, {"pk": 2, "model": "fixtures.article", "fields": {"headline": "Poker has no place on ESPN", "pub_date": "2006-06-16 12:00:00"}}, {"pk": 1, "model": "fixtures.article", "fields": {"headline": "Python program becomes self aware", "pub_date": "2006-06-16 11:00:00"}}, {"pk": 1, "model": "fixtures.tag", "fields": {"tagged_type": ["fixtures", "article"], "name": "copyright", "tagged_id": 3}}, {"pk": 2, "model": "fixtures.tag", "fields": {"tagged_type": ["fixtures", "article"], "name": "law", "tagged_id": 3}}, {"pk": 1, "model": "fixtures.person", "fields": {"name": "Django Reinhardt"}}, {"pk": 3, "model": "fixtures.person", "fields": {"name": "Prince"}}, {"pk": 2, "model": "fixtures.person", "fields": {"name": "Stephane Grappelli"}}]', natural_foreign_keys=True)
# Dump the current contents of the database as an XML fixture
self._dumpdata_assert(['fixtures'], """
-News StoriesLatest news storiesTime to reform copyright2006-06-16 13:00:00Poker has no place on ESPN2006-06-16 12:00:00Python program becomes self aware2006-06-16 11:00:00copyrightfixturesarticle3lawfixturesarticle3Django ReinhardtPrinceStephane Grappelli""", format='xml', natural_keys=True)
+News StoriesLatest news storiesTime to reform copyright2006-06-16 13:00:00Poker has no place on ESPN2006-06-16 12:00:00Python program becomes self aware2006-06-16 11:00:00copyrightfixturesarticle3lawfixturesarticle3Django ReinhardtPrinceStephane Grappelli""", format='xml', natural_foreign_keys=True)
class FixtureTransactionTests(TransactionTestCase):
def _dumpdata_assert(self, args, output, format='json'):
diff --git a/tests/regressiontests/fixtures_regress/tests.py b/tests/regressiontests/fixtures_regress/tests.py
index 3dc4ede..b43f4c8 100644
--- a/tests/regressiontests/fixtures_regress/tests.py
+++ b/tests/regressiontests/fixtures_regress/tests.py
@@ -451,12 +451,13 @@ class NaturalKeyFixtureTests(TestCase):
'fixtures_regress.store',
verbosity=0,
format='json',
- use_natural_keys=True,
+ use_natural_foreign_keys=True,
+ use_natural_primary_keys=True,
stdout=stdout,
)
self.assertEqual(
stdout.getvalue(),
- """[{"pk": 2, "model": "fixtures_regress.store", "fields": {"name": "Amazon"}}, {"pk": 3, "model": "fixtures_regress.store", "fields": {"name": "Borders"}}, {"pk": 4, "model": "fixtures_regress.person", "fields": {"name": "Neal Stephenson"}}, {"pk": 1, "model": "fixtures_regress.book", "fields": {"stores": [["Amazon"], ["Borders"]], "name": "Cryptonomicon", "author": ["Neal Stephenson"]}}]"""
+ """[{"fields": {"name": "Amazon"}, "model": "fixtures_regress.store"}, {"fields": {"name": "Borders"}, "model": "fixtures_regress.store"}, {"fields": {"name": "Neal Stephenson"}, "model": "fixtures_regress.person"}, {"pk": 1, "model": "fixtures_regress.book", "fields": {"stores": [["Amazon"], ["Borders"]], "name": "Cryptonomicon", "author": ["Neal Stephenson"]}}]"""
)
def test_dependency_sorting(self):
diff --git a/tests/regressiontests/serializers_regress/models.py b/tests/regressiontests/serializers_regress/models.py
index b3ae1fe..780c0cd 100644
--- a/tests/regressiontests/serializers_regress/models.py
+++ b/tests/regressiontests/serializers_regress/models.py
@@ -264,3 +264,17 @@ class LengthModel(models.Model):
def __len__(self):
return self.data
+
+#Tests for natural keys.
+class BookManager(models.Manager):
+ def get_by_natural_key(self, isbn13):
+ return self.get(isbn13=isbn13)
+
+class Book(models.Model):
+ isbn13 = models.CharField(max_length=14)
+ title = models.CharField(max_length=100)
+
+ objects = BookManager()
+
+ def natural_key(self):
+ return (self.isbn13,)
diff --git a/tests/regressiontests/serializers_regress/tests.py b/tests/regressiontests/serializers_regress/tests.py
index 90a438c..116a8f7 100644
--- a/tests/regressiontests/serializers_regress/tests.py
+++ b/tests/regressiontests/serializers_regress/tests.py
@@ -436,8 +436,36 @@ def streamTest(format, self):
self.assertEqual(string_data, stream.getvalue())
stream.close()
+def naturalKeyTest(format, self):
+ book1 = {'isbn13': '978-1590597255', 'title': 'The Definitive Guide to '
+ 'Django: Web Development Done Right'}
+ book2 = {'isbn13':'978-1590599969', 'title': 'Practical Django Projects'}
+
+ # Create the books.
+ adrian = Book.objects.create(**book1)
+ james = Book.objects.create(**book2)
+
+ # Serialize the books.
+ string_data = serializers.serialize(format, Book.objects.all(), indent=2,
+ use_natural_foreign_keys=True,
+ use_natural_primary_keys=True)
+
+ # Delete one book (to prove that the natural key generation will only
+ # restore the primary keys of books found in the database via the
+ # get_natural_key manager method).
+ james.delete()
+
+ # Deserialize and test.
+ books = list(serializers.deserialize(format, string_data))
+ self.assertEqual(len(books), 2)
+ self.assertEqual(books[0].object.title, book1['title'])
+ self.assertEqual(books[0].object.pk, adrian.pk)
+ self.assertEqual(books[1].object.title, book2['title'])
+ self.assertEqual(books[1].object.pk, None)
+
for format in serializers.get_serializer_formats():
setattr(SerializerTests, 'test_' + format + '_serializer', curry(serializerTest, format))
setattr(SerializerTests, 'test_' + format + '_serializer_fields', curry(fieldsTest, format))
+ setattr(SerializerTests, 'test_' + format + '_serializer_natural_keys', curry(naturalKeyTest, format))
if format != 'python':
setattr(SerializerTests, 'test_' + format + '_serializer_stream', curry(streamTest, format))