Added a JSON serializer, a few more tests, and a couple more lines of docs.
git-svn-id: http://code.djangoproject.com/svn/django/trunk@3237 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
parent
963d88a809
commit
c9032ab07f
|
@ -21,6 +21,8 @@ from django.conf import settings
|
||||||
# Built-in serializers
|
# Built-in serializers
|
||||||
BUILTIN_SERIALIZERS = {
|
BUILTIN_SERIALIZERS = {
|
||||||
"xml" : "django.core.serializers.xml_serializer",
|
"xml" : "django.core.serializers.xml_serializer",
|
||||||
|
"python" : "django.core.serializers.python",
|
||||||
|
"json" : "django.core.serializers.json",
|
||||||
}
|
}
|
||||||
|
|
||||||
_serializers = {}
|
_serializers = {}
|
||||||
|
|
|
@ -33,7 +33,9 @@ class Serializer(object):
|
||||||
for obj in queryset:
|
for obj in queryset:
|
||||||
self.start_object(obj)
|
self.start_object(obj)
|
||||||
for field in obj._meta.fields:
|
for field in obj._meta.fields:
|
||||||
if field.rel is None:
|
if field is obj._meta.pk:
|
||||||
|
continue
|
||||||
|
elif field.rel is None:
|
||||||
self.handle_field(obj, field)
|
self.handle_field(obj, field)
|
||||||
else:
|
else:
|
||||||
self.handle_fk_field(obj, field)
|
self.handle_fk_field(obj, field)
|
||||||
|
|
|
@ -0,0 +1,51 @@
|
||||||
|
"""
|
||||||
|
Serialize data to/from JSON
|
||||||
|
"""
|
||||||
|
|
||||||
|
import datetime
|
||||||
|
from django.utils import simplejson
|
||||||
|
from django.core.serializers.python import Serializer as PythonSerializer
|
||||||
|
from django.core.serializers.python import Deserializer as PythonDeserializer
|
||||||
|
try:
|
||||||
|
from cStringIO import StringIO
|
||||||
|
except ImportError:
|
||||||
|
from StringIO import StringIO
|
||||||
|
|
||||||
|
class Serializer(PythonSerializer):
|
||||||
|
"""
|
||||||
|
Convert a queryset to JSON.
|
||||||
|
"""
|
||||||
|
def end_serialization(self):
|
||||||
|
simplejson.dump(self.objects, self.stream, cls=DateTimeAwareJSONEncoder)
|
||||||
|
|
||||||
|
def getvalue(self):
|
||||||
|
return self.stream.getvalue()
|
||||||
|
|
||||||
|
def Deserializer(stream_or_string, **options):
|
||||||
|
"""
|
||||||
|
Deserialize a stream or string of JSON data.
|
||||||
|
"""
|
||||||
|
if isinstance(stream_or_string, basestring):
|
||||||
|
stream = StringIO(stream_or_string)
|
||||||
|
else:
|
||||||
|
stream = stream_or_string
|
||||||
|
for obj in PythonDeserializer(simplejson.load(stream)):
|
||||||
|
yield obj
|
||||||
|
|
||||||
|
class DateTimeAwareJSONEncoder(simplejson.JSONEncoder):
|
||||||
|
"""
|
||||||
|
JSONEncoder subclass that knows how to encode date/time types
|
||||||
|
"""
|
||||||
|
|
||||||
|
DATE_FORMAT = "%Y-%m-%d"
|
||||||
|
TIME_FORMAT = "%H:%M:%S"
|
||||||
|
|
||||||
|
def default(self, o):
|
||||||
|
if isinstance(o, datetime.date):
|
||||||
|
return o.strftime(self.DATE_FORMAT)
|
||||||
|
elif isinstance(o, datetime.time):
|
||||||
|
return o.strftime(self.TIME_FORMAT)
|
||||||
|
elif isinstance(o, datetime.datetime):
|
||||||
|
return o.strftime("%s %s" % (self.DATE_FORMAT, self.TIME_FORMAT))
|
||||||
|
else:
|
||||||
|
return super(self, DateTimeAwareJSONEncoder).default(o)
|
|
@ -0,0 +1,101 @@
|
||||||
|
"""
|
||||||
|
A Python "serializer". Doesn't do much serializing per se -- just converts to
|
||||||
|
and from basic Python data types (lists, dicts, strings, etc.). Useful as a basis for
|
||||||
|
other serializers.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
from django.core.serializers import base
|
||||||
|
from django.db import models
|
||||||
|
|
||||||
|
class Serializer(base.Serializer):
|
||||||
|
"""
|
||||||
|
Serializes a QuerySet to basic Python objects.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def start_serialization(self):
|
||||||
|
self._current = None
|
||||||
|
self.objects = []
|
||||||
|
|
||||||
|
def end_serialization(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def start_object(self, obj):
|
||||||
|
self._current = {}
|
||||||
|
|
||||||
|
def end_object(self, obj):
|
||||||
|
self.objects.append({
|
||||||
|
"model" : str(obj._meta),
|
||||||
|
"pk" : str(obj._get_pk_val()),
|
||||||
|
"fields" : self._current
|
||||||
|
})
|
||||||
|
self._current = None
|
||||||
|
|
||||||
|
def handle_field(self, obj, field):
|
||||||
|
self._current[field.name] = getattr(obj, field.name)
|
||||||
|
|
||||||
|
def handle_fk_field(self, obj, field):
|
||||||
|
related = getattr(obj, field.name)
|
||||||
|
if related is not None:
|
||||||
|
related = related._get_pk_val()
|
||||||
|
self._current[field.name] = related
|
||||||
|
|
||||||
|
def handle_m2m_field(self, obj, field):
|
||||||
|
self._current[field.name] = [related._get_pk_val() for related in getattr(obj, field.name).iterator()]
|
||||||
|
|
||||||
|
def getvalue(self):
|
||||||
|
return self.objects
|
||||||
|
|
||||||
|
def Deserializer(object_list, **options):
|
||||||
|
"""
|
||||||
|
Deserialize simple Python objects back into Django ORM instances.
|
||||||
|
|
||||||
|
It's expected that you pass the Python objects themselves (instead of a
|
||||||
|
stream or a string) to the constructor
|
||||||
|
"""
|
||||||
|
models.get_apps()
|
||||||
|
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.name : d["pk"]}
|
||||||
|
m2m_data = {}
|
||||||
|
|
||||||
|
# Handle each field
|
||||||
|
for (field_name, field_value) in d["fields"].iteritems():
|
||||||
|
if isinstance(field_value, unicode):
|
||||||
|
field_value = field_value.encode(options.get("encoding", settings.DEFAULT_CHARSET))
|
||||||
|
|
||||||
|
field = Model._meta.get_field(field_name)
|
||||||
|
|
||||||
|
# Handle M2M relations (with in_bulk() for performance)
|
||||||
|
if field.rel and isinstance(field.rel, models.ManyToManyRel):
|
||||||
|
pks = []
|
||||||
|
for pk in field_value:
|
||||||
|
if isinstance(pk, unicode):
|
||||||
|
pk = pk.encode(options.get("encoding", settings.DEFAULT_CHARSET))
|
||||||
|
m2m_data[field.name] = field.rel.to._default_manager.in_bulk(field_value).values()
|
||||||
|
|
||||||
|
# Handle FK fields
|
||||||
|
elif field.rel and isinstance(field.rel, models.ManyToOneRel):
|
||||||
|
try:
|
||||||
|
data[field.name] = field.rel.to._default_manager.get(pk=field_value)
|
||||||
|
except RelatedModel.DoesNotExist:
|
||||||
|
data[field.name] = None
|
||||||
|
|
||||||
|
# Handle all other fields
|
||||||
|
else:
|
||||||
|
data[field.name] = field.to_python(field_value)
|
||||||
|
|
||||||
|
yield base.DeserializedObject(Model(**data), m2m_data)
|
||||||
|
|
||||||
|
def _get_model(model_identifier):
|
||||||
|
"""
|
||||||
|
Helper to look up a model from an "app_label.module_name" string.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
Model = models.get_model(*model_identifier.split("."))
|
||||||
|
except TypeError:
|
||||||
|
Model = None
|
||||||
|
if Model is None:
|
||||||
|
raise base.DeserializationError("Invalid model identifier: '%s'" % model_identifier)
|
||||||
|
return Model
|
|
@ -2,10 +2,11 @@
|
||||||
XML serializer.
|
XML serializer.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from xml.dom import pulldom
|
from django.conf import settings
|
||||||
from django.utils.xmlutils import SimplerXMLGenerator
|
|
||||||
from django.core.serializers import base
|
from django.core.serializers import base
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
from django.utils.xmlutils import SimplerXMLGenerator
|
||||||
|
from xml.dom import pulldom
|
||||||
|
|
||||||
class Serializer(base.Serializer):
|
class Serializer(base.Serializer):
|
||||||
"""
|
"""
|
||||||
|
@ -16,7 +17,7 @@ class Serializer(base.Serializer):
|
||||||
"""
|
"""
|
||||||
Start serialization -- open the XML document and the root element.
|
Start serialization -- open the XML document and the root element.
|
||||||
"""
|
"""
|
||||||
self.xml = SimplerXMLGenerator(self.stream, self.options.get("encoding", "utf-8"))
|
self.xml = SimplerXMLGenerator(self.stream, self.options.get("encoding", settings.DEFAULT_CHARSET))
|
||||||
self.xml.startDocument()
|
self.xml.startDocument()
|
||||||
self.xml.startElement("django-objects", {"version" : "1.0"})
|
self.xml.startElement("django-objects", {"version" : "1.0"})
|
||||||
|
|
||||||
|
@ -58,9 +59,7 @@ class Serializer(base.Serializer):
|
||||||
# Get a "string version" of the object's data (this is handled by the
|
# Get a "string version" of the object's data (this is handled by the
|
||||||
# serializer base class). None is handled specially.
|
# serializer base class). None is handled specially.
|
||||||
value = self.get_string_value(obj, field)
|
value = self.get_string_value(obj, field)
|
||||||
if value is None:
|
if value is not None:
|
||||||
self.xml.addQuickElement("None")
|
|
||||||
else:
|
|
||||||
self.xml.characters(str(value))
|
self.xml.characters(str(value))
|
||||||
|
|
||||||
self.xml.endElement("field")
|
self.xml.endElement("field")
|
||||||
|
@ -106,7 +105,7 @@ class Deserializer(base.Deserializer):
|
||||||
|
|
||||||
def __init__(self, stream_or_string, **options):
|
def __init__(self, stream_or_string, **options):
|
||||||
super(Deserializer, self).__init__(stream_or_string, **options)
|
super(Deserializer, self).__init__(stream_or_string, **options)
|
||||||
self.encoding = self.options.get("encoding", "utf-8")
|
self.encoding = self.options.get("encoding", settings.DEFAULT_CHARSET)
|
||||||
self.event_stream = pulldom.parse(self.stream)
|
self.event_stream = pulldom.parse(self.stream)
|
||||||
|
|
||||||
def next(self):
|
def next(self):
|
||||||
|
|
|
@ -78,8 +78,25 @@ The Django object itself can be inspected as ``deserialized_object.object``.
|
||||||
Serialization formats
|
Serialization formats
|
||||||
---------------------
|
---------------------
|
||||||
|
|
||||||
Django "ships" with a few included serializers, and there's a simple API for creating and registering your own...
|
Django "ships" with a few included serializers:
|
||||||
|
|
||||||
.. note::
|
========== ==============================================================
|
||||||
|
Identifier Information
|
||||||
|
========== ==============================================================
|
||||||
|
``xml`` Serializes to and from a simple XML dialect.
|
||||||
|
|
||||||
... which will be documented once the API is stable :)
|
``json`` Serializes to and from JSON_ (using a version of simplejson_
|
||||||
|
bundled with Django).
|
||||||
|
|
||||||
|
``python`` Translates to and from "simple" Python objects (lists, dicts,
|
||||||
|
strings, etc.). Not really all that useful on its own, but
|
||||||
|
used as a base for other serializers.
|
||||||
|
========== ==============================================================
|
||||||
|
|
||||||
|
.. _json: http://json.org/
|
||||||
|
.. _simplejson: http://undefined.org/python/#simplejson
|
||||||
|
|
||||||
|
Writing custom serializers
|
||||||
|
``````````````````````````
|
||||||
|
|
||||||
|
XXX ...
|
||||||
|
|
|
@ -91,4 +91,31 @@ API_TESTS = """
|
||||||
>>> Article.objects.all()
|
>>> Article.objects.all()
|
||||||
[<Article: Poker has no place on television>, <Article: Time to reform copyright>]
|
[<Article: Poker has no place on television>, <Article: Time to reform copyright>]
|
||||||
|
|
||||||
|
# Django also ships with a built-in JSON serializers
|
||||||
|
>>> json = serializers.serialize("json", Category.objects.filter(pk=2))
|
||||||
|
>>> json
|
||||||
|
'[{"pk": "2", "model": "serializers.category", "fields": {"name": "Music"}}]'
|
||||||
|
|
||||||
|
# You can easily create new objects by deserializing data with an empty PK
|
||||||
|
# (It's easier to demo this with JSON...)
|
||||||
|
>>> new_author_json = '[{"pk": null, "model": "serializers.author", "fields": {"name": "Bill"}}]'
|
||||||
|
>>> for obj in serializers.deserialize("json", new_author_json):
|
||||||
|
... obj.save()
|
||||||
|
>>> Author.objects.all()
|
||||||
|
[<Author: Bill>, <Author: Jane>, <Author: Joe>]
|
||||||
|
|
||||||
|
# All the serializers work the same
|
||||||
|
>>> json = serializers.serialize("json", Article.objects.all())
|
||||||
|
>>> for obj in serializers.deserialize("json", json):
|
||||||
|
... print obj
|
||||||
|
<DeserializedObject: Poker has no place on television>
|
||||||
|
<DeserializedObject: Time to reform copyright>
|
||||||
|
|
||||||
|
>>> json = json.replace("Poker has no place on television", "Just kidding; I love TV poker")
|
||||||
|
>>> for obj in serializers.deserialize("json", json):
|
||||||
|
... obj.save()
|
||||||
|
|
||||||
|
>>> Article.objects.all()
|
||||||
|
[<Article: Just kidding; I love TV poker>, <Article: Time to reform copyright>]
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
Loading…
Reference in New Issue