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:
Jacob Kaplan-Moss 2006-06-29 16:42:49 +00:00
parent 963d88a809
commit c9032ab07f
7 changed files with 211 additions and 12 deletions

View File

@ -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 = {}

View File

@ -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)

View File

@ -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)

View File

@ -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

View File

@ -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):

View File

@ -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 ...

View File

@ -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>]
""" """