mirror of
https://github.com/django/django.git
synced 2025-10-27 07:36:08 +00:00
Fixed #13252 -- Added ability to serialize with natural primary keys.
Added ``--natural-foreign`` and ``--natural-primary`` options and deprecated the ``--natural`` option to the ``dumpdata`` management command. Added ``use_natural_foreign_keys`` and ``use_natural_primary_keys`` arguments and deprecated the ``use_natural_keys`` argument to ``django.core.serializers.Serializer.serialize()``. Thanks SmileyChris for the suggestion.
This commit is contained in:
@@ -1,3 +1,5 @@
|
||||
import warnings
|
||||
|
||||
from collections import OrderedDict
|
||||
from optparse import make_option
|
||||
|
||||
@@ -20,6 +22,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."),
|
||||
make_option('--pks', dest='primary_keys', help="Only dump objects with "
|
||||
@@ -40,6 +46,11 @@ class Command(BaseCommand):
|
||||
excludes = options.get('exclude')
|
||||
show_traceback = options.get('traceback')
|
||||
use_natural_keys = options.get('use_natural_keys')
|
||||
if use_natural_keys:
|
||||
warnings.warn("``--natural`` is deprecated; use ``--natural-foreign`` instead.",
|
||||
PendingDeprecationWarning)
|
||||
use_natural_foreign_keys = options.get('use_natural_foreign_keys') or use_natural_keys
|
||||
use_natural_primary_keys = options.get('use_natural_primary_keys')
|
||||
use_base_manager = options.get('use_base_manager')
|
||||
pks = options.get('primary_keys')
|
||||
|
||||
@@ -133,7 +144,9 @@ class Command(BaseCommand):
|
||||
try:
|
||||
self.stdout.ending = None
|
||||
serializers.serialize(format, get_objects(), indent=indent,
|
||||
use_natural_keys=use_natural_keys, stream=self.stdout)
|
||||
use_natural_foreign_keys=use_natural_foreign_keys,
|
||||
use_natural_primary_keys=use_natural_primary_keys,
|
||||
stream=self.stdout)
|
||||
except Exception as e:
|
||||
if show_traceback:
|
||||
raise
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
"""
|
||||
Module for abstract serializer/unserializer base classes.
|
||||
"""
|
||||
import warnings
|
||||
|
||||
from django.db import models
|
||||
from django.utils import six
|
||||
@@ -35,6 +36,11 @@ class Serializer(object):
|
||||
self.stream = options.pop("stream", six.StringIO())
|
||||
self.selected_fields = options.pop("fields", None)
|
||||
self.use_natural_keys = options.pop("use_natural_keys", False)
|
||||
if self.use_natural_keys:
|
||||
warnings.warn("``use_natural_keys`` is deprecated; use ``use_natural_foreign_keys`` instead.",
|
||||
PendingDeprecationWarning)
|
||||
self.use_natural_foreign_keys = options.pop('use_natural_foreign_keys', False) or self.use_natural_keys
|
||||
self.use_natural_primary_keys = options.pop('use_natural_primary_keys', False)
|
||||
|
||||
self.start_serialization()
|
||||
self.first = True
|
||||
@@ -169,3 +175,20 @@ 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')):
|
||||
natural_key = obj.natural_key()
|
||||
try:
|
||||
obj.pk = Model._default_manager.db_manager(db).get_by_natural_key(*natural_key).pk
|
||||
except Model.DoesNotExist:
|
||||
pass
|
||||
return obj
|
||||
|
||||
@@ -34,11 +34,14 @@ class Serializer(base.Serializer):
|
||||
self._current = None
|
||||
|
||||
def get_dump_object(self, obj):
|
||||
return {
|
||||
"pk": smart_text(obj._get_pk_val(), strings_only=True),
|
||||
data = {
|
||||
"model": smart_text(obj._meta),
|
||||
"fields": self._current
|
||||
"fields": self._current,
|
||||
}
|
||||
if not self.use_natural_primary_keys or not hasattr(obj, 'natural_key'):
|
||||
data["pk"] = smart_text(obj._get_pk_val(), strings_only=True)
|
||||
|
||||
return data
|
||||
|
||||
def handle_field(self, obj, field):
|
||||
value = field._get_val_from_obj(obj)
|
||||
@@ -51,7 +54,7 @@ class Serializer(base.Serializer):
|
||||
self._current[field.name] = field.value_to_string(obj)
|
||||
|
||||
def handle_fk_field(self, obj, 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'):
|
||||
related = getattr(obj, field.name)
|
||||
if related:
|
||||
value = related.natural_key()
|
||||
@@ -63,7 +66,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_text(value._get_pk_val(), strings_only=True)
|
||||
@@ -88,7 +91,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.get("pk", None))}
|
||||
data = {}
|
||||
if 'pk' in d:
|
||||
data[Model._meta.pk.attname] = Model._meta.pk.to_python(d.get("pk", None))
|
||||
m2m_data = {}
|
||||
model_fields = Model._meta.get_all_field_names()
|
||||
|
||||
@@ -139,7 +144,8 @@ 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):
|
||||
"""
|
||||
|
||||
@@ -46,14 +46,11 @@ 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_text(obj._meta),}
|
||||
else:
|
||||
attrs = {
|
||||
"pk": smart_text(obj._get_pk_val()),
|
||||
"model": smart_text(obj._meta),
|
||||
}
|
||||
attrs = {"model": smart_text(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:
|
||||
attrs['pk'] = smart_text(obj_pk)
|
||||
|
||||
self.xml.startElement("object", attrs)
|
||||
|
||||
@@ -91,7 +88,7 @@ class Serializer(base.Serializer):
|
||||
self._start_relational_field(field)
|
||||
related_att = getattr(obj, field.get_attname())
|
||||
if related_att is not None:
|
||||
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'):
|
||||
related = getattr(obj, field.name)
|
||||
# If related object has a natural key, use it
|
||||
related = related.natural_key()
|
||||
@@ -114,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()
|
||||
@@ -177,13 +174,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]})
|
||||
@@ -217,8 +211,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):
|
||||
"""
|
||||
|
||||
Reference in New Issue
Block a user