Added initial cut at serialization framework, along with some basic tests and a stab at some docs. This is all a bit rough right now, so expect some bumps.
git-svn-id: http://code.djangoproject.com/svn/django/trunk@3225 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
parent
414bc24e81
commit
4ea7a11659
|
@ -0,0 +1,76 @@
|
||||||
|
"""
|
||||||
|
Interfaces for serializing Django objects.
|
||||||
|
|
||||||
|
Usage::
|
||||||
|
|
||||||
|
>>> from django.core import serializers
|
||||||
|
>>> json = serializers.serialize("json", some_query_set)
|
||||||
|
>>> objects = list(serializers.deserialize("json", json))
|
||||||
|
|
||||||
|
To add your own serializers, use the SERIALIZATION_MODULES setting::
|
||||||
|
|
||||||
|
SERIALIZATION_MODULES = {
|
||||||
|
"csv" : "path.to.csv.serializer",
|
||||||
|
"txt" : "path.to.txt.serializer",
|
||||||
|
}
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
|
||||||
|
# Built-in serializers
|
||||||
|
BUILTIN_SERIALIZERS = {
|
||||||
|
"xml" : "django.core.serializers.xml_serializer",
|
||||||
|
}
|
||||||
|
|
||||||
|
_serializers = {}
|
||||||
|
|
||||||
|
def register_serializer(format, serializer_module):
|
||||||
|
"""Register a new serializer by passing in a module name."""
|
||||||
|
module = __import__(serializer_module, '', '', [''])
|
||||||
|
_serializers[format] = module
|
||||||
|
|
||||||
|
def unregister_serializer(format):
|
||||||
|
"""Unregister a given serializer"""
|
||||||
|
del _serializers[format]
|
||||||
|
|
||||||
|
def get_serializer(format):
|
||||||
|
if not _serializers:
|
||||||
|
_load_serializers()
|
||||||
|
return _serializers[format].Serializer
|
||||||
|
|
||||||
|
def get_deserializer(format):
|
||||||
|
if not _serializers:
|
||||||
|
_load_serializers()
|
||||||
|
return _serializers[format].Deserializer
|
||||||
|
|
||||||
|
def serialize(format, queryset, **options):
|
||||||
|
"""
|
||||||
|
Serialize a queryset (or any iterator that returns database objects) using
|
||||||
|
a certain serializer.
|
||||||
|
"""
|
||||||
|
s = get_serializer(format)()
|
||||||
|
s.serialize(queryset, **options)
|
||||||
|
return s.getvalue()
|
||||||
|
|
||||||
|
def deserialize(format, stream_or_string):
|
||||||
|
"""
|
||||||
|
Deserialize a stream or a string. Returns an iterator that yields ``(obj,
|
||||||
|
m2m_relation_dict)``, where ``obj`` is a instantiated -- but *unsaved* --
|
||||||
|
object, and ``m2m_relation_dict`` is a dictionary of ``{m2m_field_name :
|
||||||
|
list_of_related_objects}``.
|
||||||
|
"""
|
||||||
|
d = get_deserializer(format)
|
||||||
|
return d(stream_or_string)
|
||||||
|
|
||||||
|
def _load_serializers():
|
||||||
|
"""
|
||||||
|
Register built-in and settings-defined serializers. This is done lazily so
|
||||||
|
that user code has a chance to (e.g.) set up custom settings without
|
||||||
|
needing to be careful of import order.
|
||||||
|
"""
|
||||||
|
for format in BUILTIN_SERIALIZERS:
|
||||||
|
register_serializer(format, BUILTIN_SERIALIZERS[format])
|
||||||
|
if hasattr(settings, "SERIALIZATION_MODULES"):
|
||||||
|
for format in settings.SERIALIZATION_MODULES:
|
||||||
|
register_serializer(format, settings.SERIALIZATION_MODULES[format])
|
|
@ -0,0 +1,159 @@
|
||||||
|
"""
|
||||||
|
Module for abstract serializer/unserializer base classes.
|
||||||
|
"""
|
||||||
|
|
||||||
|
try:
|
||||||
|
from cStringIO import StringIO
|
||||||
|
except ImportError:
|
||||||
|
from StringIO import StringIO
|
||||||
|
from django.db import models
|
||||||
|
|
||||||
|
class SerializationError(Exception):
|
||||||
|
"""Something bad happened during serialization."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
class DeserializationError(Exception):
|
||||||
|
"""Something bad happened during deserialization."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
class Serializer(object):
|
||||||
|
"""
|
||||||
|
Abstract serializer base class.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def serialize(self, queryset, **options):
|
||||||
|
"""
|
||||||
|
Serialize a queryset.
|
||||||
|
"""
|
||||||
|
self.options = options
|
||||||
|
|
||||||
|
self.stream = options.get("stream", StringIO())
|
||||||
|
|
||||||
|
self.start_serialization()
|
||||||
|
for obj in queryset:
|
||||||
|
self.start_object(obj)
|
||||||
|
for field in obj._meta.fields:
|
||||||
|
if field.rel is None:
|
||||||
|
self.handle_field(obj, field)
|
||||||
|
else:
|
||||||
|
self.handle_fk_field(obj, field)
|
||||||
|
for field in obj._meta.many_to_many:
|
||||||
|
self.handle_m2m_field(obj, field)
|
||||||
|
self.end_object(obj)
|
||||||
|
self.end_serialization()
|
||||||
|
return self.getvalue()
|
||||||
|
|
||||||
|
def get_string_value(self, obj, field):
|
||||||
|
"""
|
||||||
|
Convert a field's value to a string.
|
||||||
|
"""
|
||||||
|
if isinstance(field, models.DateTimeField):
|
||||||
|
value = getattr(obj, field.name).strftime("%Y-%m-%d %H:%M:%S")
|
||||||
|
elif isinstance(field, models.FileField):
|
||||||
|
value = getattr(obj, "get_%s_url" % field.name, lambda: None)()
|
||||||
|
else:
|
||||||
|
value = field.flatten_data(follow=None, obj=obj).get(field.name, "")
|
||||||
|
return str(value)
|
||||||
|
|
||||||
|
def start_serialization(self):
|
||||||
|
"""
|
||||||
|
Called when serializing of the queryset starts.
|
||||||
|
"""
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def end_serialization(self):
|
||||||
|
"""
|
||||||
|
Called when serializing of the queryset ends.
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
def start_object(self, obj):
|
||||||
|
"""
|
||||||
|
Called when serializing of an object starts.
|
||||||
|
"""
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def end_object(self, obj):
|
||||||
|
"""
|
||||||
|
Called when serializing of an object ends.
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
def handle_field(self, obj, field):
|
||||||
|
"""
|
||||||
|
Called to handle each individual (non-relational) field on an object.
|
||||||
|
"""
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def handle_fk_field(self, obj, field):
|
||||||
|
"""
|
||||||
|
Called to handle a ForeignKey field.
|
||||||
|
"""
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def handle_m2m_field(self, obj, field):
|
||||||
|
"""
|
||||||
|
Called to handle a ManyToManyField.
|
||||||
|
"""
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def getvalue(self):
|
||||||
|
"""
|
||||||
|
Return the fully serialized queryset.
|
||||||
|
"""
|
||||||
|
return self.stream.getvalue()
|
||||||
|
|
||||||
|
class Deserializer(object):
|
||||||
|
"""
|
||||||
|
Abstract base deserializer class.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, stream_or_string, **options):
|
||||||
|
"""
|
||||||
|
Init this serializer given a stream or a string
|
||||||
|
"""
|
||||||
|
self.options = options
|
||||||
|
if isinstance(stream_or_string, basestring):
|
||||||
|
self.stream = StringIO(stream_or_string)
|
||||||
|
else:
|
||||||
|
self.stream = stream_or_string
|
||||||
|
# hack to make sure that the models have all been loaded before
|
||||||
|
# deserialization starts (otherwise subclass calls to get_model()
|
||||||
|
# and friends might fail...)
|
||||||
|
models.get_apps()
|
||||||
|
|
||||||
|
def __iter__(self):
|
||||||
|
return self
|
||||||
|
|
||||||
|
def next(self):
|
||||||
|
"""Iteration iterface -- return the next item in the stream"""
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
class DeserializedObject(object):
|
||||||
|
"""
|
||||||
|
A deserialzed model.
|
||||||
|
|
||||||
|
Basically a container for holding the pre-saved deserialized data along
|
||||||
|
with the many-to-many data saved with the object.
|
||||||
|
|
||||||
|
Call ``save()`` to save the object (with the many-to-many data) to the
|
||||||
|
database; call ``save(save_m2m=False)`` to save just the object fields
|
||||||
|
(and not touch the many-to-many stuff.)
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, obj, m2m_data=None):
|
||||||
|
self.object = obj
|
||||||
|
self.m2m_data = m2m_data
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return "<DeserializedObject: %s>" % str(self.object)
|
||||||
|
|
||||||
|
def save(self, save_m2m=True):
|
||||||
|
self.object.save()
|
||||||
|
if self.m2m_data and save_m2m:
|
||||||
|
for accessor_name, object_list in self.m2m_data.items():
|
||||||
|
setattr(self.object, accessor_name, object_list)
|
||||||
|
|
||||||
|
# prevent a second (possibly accidental) call to save() from saving
|
||||||
|
# the m2m data twice.
|
||||||
|
self.m2m_data = None
|
|
@ -0,0 +1,218 @@
|
||||||
|
"""
|
||||||
|
XML serializer.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from xml.dom import pulldom
|
||||||
|
from django.utils.xmlutils import SimplerXMLGenerator
|
||||||
|
from django.core.serializers import base
|
||||||
|
from django.db import models
|
||||||
|
|
||||||
|
class Serializer(base.Serializer):
|
||||||
|
"""
|
||||||
|
Serializes a QuerySet to XML.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def start_serialization(self):
|
||||||
|
"""
|
||||||
|
Start serialization -- open the XML document and the root element.
|
||||||
|
"""
|
||||||
|
self.xml = SimplerXMLGenerator(self.stream, self.options.get("encoding", "utf-8"))
|
||||||
|
self.xml.startDocument()
|
||||||
|
self.xml.startElement("django-objects", {"version" : "1.0"})
|
||||||
|
|
||||||
|
def end_serialization(self):
|
||||||
|
"""
|
||||||
|
End serialization -- end the document.
|
||||||
|
"""
|
||||||
|
self.xml.endElement("django-objects")
|
||||||
|
self.xml.endDocument()
|
||||||
|
|
||||||
|
def start_object(self, obj):
|
||||||
|
"""
|
||||||
|
Called as each object is handled.
|
||||||
|
"""
|
||||||
|
if not hasattr(obj, "_meta"):
|
||||||
|
raise base.SerializationError("Non-model object (%s) encountered during serialization" % type(obj))
|
||||||
|
|
||||||
|
self.xml.startElement("object", {
|
||||||
|
"pk" : str(obj._get_pk_val()),
|
||||||
|
"model" : str(obj._meta),
|
||||||
|
})
|
||||||
|
|
||||||
|
def end_object(self, obj):
|
||||||
|
"""
|
||||||
|
Called after handling all fields for an object.
|
||||||
|
"""
|
||||||
|
self.xml.endElement("object")
|
||||||
|
|
||||||
|
def handle_field(self, obj, field):
|
||||||
|
"""
|
||||||
|
Called to handle each field on an object (except for ForeignKeys and
|
||||||
|
ManyToManyFields)
|
||||||
|
"""
|
||||||
|
self.xml.startElement("field", {
|
||||||
|
"name" : field.name,
|
||||||
|
"type" : field.get_internal_type()
|
||||||
|
})
|
||||||
|
|
||||||
|
# Get a "string version" of the object's data (this is handled by the
|
||||||
|
# serializer base class). None is handled specially.
|
||||||
|
value = self.get_string_value(obj, field)
|
||||||
|
if value is None:
|
||||||
|
self.xml.addQuickElement("None")
|
||||||
|
else:
|
||||||
|
self.xml.characters(str(value))
|
||||||
|
|
||||||
|
self.xml.endElement("field")
|
||||||
|
|
||||||
|
def handle_fk_field(self, obj, field):
|
||||||
|
"""
|
||||||
|
Called to handle a ForeignKey (we need to treat them slightly
|
||||||
|
differently from regular fields).
|
||||||
|
"""
|
||||||
|
self._start_relational_field(field)
|
||||||
|
related = getattr(obj, field.name)
|
||||||
|
if related is not None:
|
||||||
|
self.xml.characters(str(related._get_pk_val()))
|
||||||
|
else:
|
||||||
|
self.xml.addQuickElement("None")
|
||||||
|
self.xml.endElement("field")
|
||||||
|
|
||||||
|
def handle_m2m_field(self, obj, field):
|
||||||
|
"""
|
||||||
|
Called to handle a ManyToManyField. Related objects are only
|
||||||
|
serialized as references to the object's PK (i.e. the related *data*
|
||||||
|
is not dumped, just the relation).
|
||||||
|
"""
|
||||||
|
self._start_relational_field(field)
|
||||||
|
for relobj in getattr(obj, field.name).iterator():
|
||||||
|
self.xml.addQuickElement("object", attrs={"pk" : str(relobj._get_pk_val())})
|
||||||
|
self.xml.endElement("field")
|
||||||
|
|
||||||
|
def _start_relational_field(self, field):
|
||||||
|
"""
|
||||||
|
Helper to output the <field> element for relational fields
|
||||||
|
"""
|
||||||
|
self.xml.startElement("field", {
|
||||||
|
"name" : field.name,
|
||||||
|
"rel" : field.rel.__class__.__name__,
|
||||||
|
"to" : str(field.rel.to._meta),
|
||||||
|
})
|
||||||
|
|
||||||
|
class Deserializer(base.Deserializer):
|
||||||
|
"""
|
||||||
|
Deserialize XML.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, stream_or_string, **options):
|
||||||
|
super(Deserializer, self).__init__(stream_or_string, **options)
|
||||||
|
self.encoding = self.options.get("encoding", "utf-8")
|
||||||
|
self.event_stream = pulldom.parse(self.stream)
|
||||||
|
|
||||||
|
def next(self):
|
||||||
|
for event, node in self.event_stream:
|
||||||
|
if event == "START_ELEMENT" and node.nodeName == "object":
|
||||||
|
self.event_stream.expandNode(node)
|
||||||
|
return self._handle_object(node)
|
||||||
|
raise StopIteration
|
||||||
|
|
||||||
|
def _handle_object(self, node):
|
||||||
|
"""
|
||||||
|
Convert an <object> node to a DeserializedObject.
|
||||||
|
"""
|
||||||
|
# Look up the model using the model loading mechanism. If this fails, bail.
|
||||||
|
Model = self._get_model_from_node(node, "model")
|
||||||
|
|
||||||
|
# Start building a data dictionary from the object. If the node is
|
||||||
|
# missing the pk attribute, bail.
|
||||||
|
pk = node.getAttribute("pk")
|
||||||
|
if not pk:
|
||||||
|
raise base.DeserializationError("<object> node is missing the 'pk' attribute")
|
||||||
|
data = {Model._meta.pk.name : pk}
|
||||||
|
|
||||||
|
# Also start building a dict of m2m data (this is saved as
|
||||||
|
# {m2m_accessor_attribute : [list_of_related_objects]})
|
||||||
|
m2m_data = {}
|
||||||
|
|
||||||
|
# Deseralize each field.
|
||||||
|
for field_node in node.getElementsByTagName("field"):
|
||||||
|
# If the field is missing the name attribute, bail (are you
|
||||||
|
# sensing a pattern here?)
|
||||||
|
field_name = field_node.getAttribute("name")
|
||||||
|
if not field_name:
|
||||||
|
raise base.DeserializationError("<field> node is missing the 'name' attribute")
|
||||||
|
|
||||||
|
# Get the field from the Model. This will raise a
|
||||||
|
# FieldDoesNotExist if, well, the field doesn't exist, which will
|
||||||
|
# be propagated correctly.
|
||||||
|
field = Model._meta.get_field(field_name)
|
||||||
|
|
||||||
|
# As is usually the case, relation fields get the special treatment.
|
||||||
|
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)
|
||||||
|
else:
|
||||||
|
value = field.to_python(getInnerText(field_node).strip().encode(self.encoding))
|
||||||
|
data[field.name] = value
|
||||||
|
|
||||||
|
# Return a DeserializedObject so that the m2m data has a place to live.
|
||||||
|
return base.DeserializedObject(Model(**data), m2m_data)
|
||||||
|
|
||||||
|
def _handle_fk_field_node(self, node):
|
||||||
|
"""
|
||||||
|
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")
|
||||||
|
return RelatedModel.objects.get(pk=getInnerText(node).strip().encode(self.encoding))
|
||||||
|
|
||||||
|
def _handle_m2m_field_node(self, node):
|
||||||
|
"""
|
||||||
|
Handle a <field> 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()
|
||||||
|
|
||||||
|
def _get_model_from_node(self, node, attr):
|
||||||
|
"""
|
||||||
|
Helper to look up a model from a <object model=...> or a <field
|
||||||
|
rel=... to=...> node.
|
||||||
|
"""
|
||||||
|
model_identifier = node.getAttribute(attr)
|
||||||
|
if not model_identifier:
|
||||||
|
raise base.DeserializationError(
|
||||||
|
"<%s> node is missing the required '%s' attribute" \
|
||||||
|
% (node.nodeName, attr))
|
||||||
|
try:
|
||||||
|
Model = models.get_model(*model_identifier.split("."))
|
||||||
|
except TypeError:
|
||||||
|
Model = None
|
||||||
|
if Model is None:
|
||||||
|
raise base.DeserializationError(
|
||||||
|
"<%s> node has invalid model identifier: '%s'" % \
|
||||||
|
(node.nodeName, model_identifier))
|
||||||
|
return Model
|
||||||
|
|
||||||
|
|
||||||
|
def getInnerText(node):
|
||||||
|
"""
|
||||||
|
Get all the inner text of a DOM node (recursively).
|
||||||
|
"""
|
||||||
|
# inspired by http://mail.python.org/pipermail/xml-sig/2005-March/011022.html
|
||||||
|
inner_text = []
|
||||||
|
for child in node.childNodes:
|
||||||
|
if child.nodeType == child.TEXT_NODE or child.nodeType == child.CDATA_SECTION_NODE:
|
||||||
|
inner_text.append(child.data)
|
||||||
|
elif child.nodeType == child.ELEMENT_NODE:
|
||||||
|
inner_text.extend(getInnerText(child))
|
||||||
|
else:
|
||||||
|
pass
|
||||||
|
return "".join(inner_text)
|
|
@ -0,0 +1,85 @@
|
||||||
|
==========================
|
||||||
|
Serializing Django objects
|
||||||
|
==========================
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
This API is currently under heavy development and may change --
|
||||||
|
perhaps drastically -- in the future.
|
||||||
|
|
||||||
|
You have been warned.
|
||||||
|
|
||||||
|
Django's serialization framework provides a mechanism for "translating" Django
|
||||||
|
objects into other formats. Usually these other formats will be text-based and
|
||||||
|
used for sending Django objects over a wire, but it's possible for a
|
||||||
|
serializer to handle any format (text-based or not).
|
||||||
|
|
||||||
|
Serializing data
|
||||||
|
----------------
|
||||||
|
|
||||||
|
At the highest level, serializing data is a very simple operation::
|
||||||
|
|
||||||
|
from django.core import serializers
|
||||||
|
data = serializers.serialize("xml", SomeModel.objects.all())
|
||||||
|
|
||||||
|
The arguments to the ``serialize`` function are the format to serialize the
|
||||||
|
data to (see `Serialization formats`_) and a QuerySet_ to serialize.
|
||||||
|
(Actually, the second argument can be any iterator that yields Django objects,
|
||||||
|
but it'll almost always be a QuerySet).
|
||||||
|
|
||||||
|
.. _QuerySet: ../db_api/#retrieving-objects
|
||||||
|
|
||||||
|
You can also use a serializer object directly::
|
||||||
|
|
||||||
|
xml_serializer = serializers.get_serializer("xml")
|
||||||
|
xml_serializer.serialize(queryset)
|
||||||
|
data = xml_serializer.getvalue()
|
||||||
|
|
||||||
|
This is useful if you want to serialize data directly to a file-like object
|
||||||
|
(which includes a HTTPResponse_)::
|
||||||
|
|
||||||
|
out = open("file.xml", "w")
|
||||||
|
xml_serializer.serialize(SomeModel.objects.all(), stream=out)
|
||||||
|
|
||||||
|
.. _HTTPResponse: ../request_response/#httpresponse-objects
|
||||||
|
|
||||||
|
Deserializing data
|
||||||
|
------------------
|
||||||
|
|
||||||
|
Deserializing data is also a fairly simple operation::
|
||||||
|
|
||||||
|
for obj in serializers.deserialize("xml", data):
|
||||||
|
do_something_with(obj)
|
||||||
|
|
||||||
|
As you can see, the ``deserialize`` function takes the same format argument as
|
||||||
|
``serialize``, a string or stream of data, and returns an iterator.
|
||||||
|
|
||||||
|
However, here it gets slightly complicated. The objects returned by the
|
||||||
|
``deserialize`` iterator *aren't* simple Django objects. Instead, they are
|
||||||
|
special ``DeserializedObject`` instances that wrap a created -- but unsaved --
|
||||||
|
object and any associated relationship data.
|
||||||
|
|
||||||
|
Calling ``DeserializedObject.save()`` saves the object to the database.
|
||||||
|
|
||||||
|
This ensures that deserializing is a non-destructive operation even if the
|
||||||
|
data in your serialized representation doesn't match what's currently in the
|
||||||
|
database. Usually, working with these ``DeserializedObject`` instances looks
|
||||||
|
something like::
|
||||||
|
|
||||||
|
for deserialized_object in serializers.deserialize("xml", data):
|
||||||
|
if object_should_be_saved(deserialized_object):
|
||||||
|
obj.save()
|
||||||
|
|
||||||
|
In other words, the usual use is to examine the deserialized objects to make
|
||||||
|
sure that they are "appropriate" for saving before doing so. Of course, if you trust your data source you could just save the object and move on.
|
||||||
|
|
||||||
|
The Django object itself can be inspected as ``deserialized_object.object``.
|
||||||
|
|
||||||
|
Serialization formats
|
||||||
|
---------------------
|
||||||
|
|
||||||
|
Django "ships" with a few included serializers, and there's a simple API for creating and registering your own...
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
... which will be documented once the API is stable :)
|
|
@ -0,0 +1,94 @@
|
||||||
|
"""
|
||||||
|
XXX. Serialization
|
||||||
|
|
||||||
|
``django.core.serializers`` provides interfaces to converting Django querysets
|
||||||
|
to and from "flat" data (i.e. strings).
|
||||||
|
"""
|
||||||
|
|
||||||
|
from django.db import models
|
||||||
|
|
||||||
|
class Category(models.Model):
|
||||||
|
name = models.CharField(maxlength=20)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
ordering = ('name',)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.name
|
||||||
|
|
||||||
|
class Author(models.Model):
|
||||||
|
name = models.CharField(maxlength=20)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
ordering = ('name',)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.name
|
||||||
|
|
||||||
|
class Article(models.Model):
|
||||||
|
author = models.ForeignKey(Author)
|
||||||
|
headline = models.CharField(maxlength=50)
|
||||||
|
pub_date = models.DateTimeField()
|
||||||
|
categories = models.ManyToManyField(Category)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
ordering = ('pub_date',)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.headline
|
||||||
|
|
||||||
|
API_TESTS = """
|
||||||
|
# Create some data:
|
||||||
|
>>> from datetime import datetime
|
||||||
|
>>> sports = Category(name="Sports")
|
||||||
|
>>> music = Category(name="Music")
|
||||||
|
>>> op_ed = Category(name="Op-Ed")
|
||||||
|
>>> sports.save(); music.save(); op_ed.save()
|
||||||
|
|
||||||
|
>>> joe = Author(name="Joe")
|
||||||
|
>>> jane = Author(name="Jane")
|
||||||
|
>>> joe.save(); jane.save()
|
||||||
|
|
||||||
|
>>> a1 = Article(
|
||||||
|
... author = jane,
|
||||||
|
... headline = "Poker has no place on ESPN",
|
||||||
|
... pub_date = datetime(2006, 6, 16, 11, 00))
|
||||||
|
>>> a2 = Article(
|
||||||
|
... author = joe,
|
||||||
|
... headline = "Time to reform copyright",
|
||||||
|
... pub_date = datetime(2006, 6, 16, 13, 00))
|
||||||
|
>>> a1.save(); a2.save()
|
||||||
|
>>> a1.categories = [sports, op_ed]
|
||||||
|
>>> a2.categories = [music, op_ed]
|
||||||
|
|
||||||
|
# Serialize a queryset to XML
|
||||||
|
>>> from django.core import serializers
|
||||||
|
>>> xml = serializers.serialize("xml", Article.objects.all())
|
||||||
|
|
||||||
|
# The output is valid XML
|
||||||
|
>>> from xml.dom import minidom
|
||||||
|
>>> dom = minidom.parseString(xml)
|
||||||
|
|
||||||
|
# Deserializing has a similar interface, except that special DeserializedObject
|
||||||
|
# instances are returned. This is because data might have changed in the
|
||||||
|
# database since the data was serialized (we'll simulate that below).
|
||||||
|
>>> for obj in serializers.deserialize("xml", xml):
|
||||||
|
... print obj
|
||||||
|
<DeserializedObject: Poker has no place on ESPN>
|
||||||
|
<DeserializedObject: Time to reform copyright>
|
||||||
|
|
||||||
|
# Deserializing data with different field values doesn't change anything in the
|
||||||
|
# database until we call save():
|
||||||
|
>>> xml = xml.replace("Poker has no place on ESPN", "Poker has no place on television")
|
||||||
|
>>> objs = list(serializers.deserialize("xml", xml))
|
||||||
|
|
||||||
|
# Even those I deserialized, the database hasn't been touched
|
||||||
|
>>> Article.objects.all()
|
||||||
|
[<Article: Poker has no place on ESPN>, <Article: Time to reform copyright>]
|
||||||
|
|
||||||
|
# But when I save, the data changes as you might except.
|
||||||
|
>>> objs[0].save()
|
||||||
|
>>> Article.objects.all()
|
||||||
|
[<Article: Poker has no place on television>, <Article: Time to reform copyright>]
|
||||||
|
|
||||||
|
"""
|
Loading…
Reference in New Issue