Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Added initial cut at serialization framework, along with some basic t…
…ests 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
- Loading branch information
Showing
6 changed files
with
632 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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]) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 |
Oops, something went wrong.