From c9032ab07f3694f3ae7da9b0017b764248ce28c9 Mon Sep 17 00:00:00 2001 From: Jacob Kaplan-Moss Date: Thu, 29 Jun 2006 16:42:49 +0000 Subject: [PATCH] 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 --- django/core/serializers/__init__.py | 4 +- django/core/serializers/base.py | 4 +- django/core/serializers/json.py | 51 +++++++++++ django/core/serializers/python.py | 101 ++++++++++++++++++++++ django/core/serializers/xml_serializer.py | 13 ++- docs/serialization.txt | 23 ++++- tests/modeltests/serializers/models.py | 27 ++++++ 7 files changed, 211 insertions(+), 12 deletions(-) create mode 100644 django/core/serializers/json.py create mode 100644 django/core/serializers/python.py diff --git a/django/core/serializers/__init__.py b/django/core/serializers/__init__.py index 72c4407b59..75e087ee1b 100644 --- a/django/core/serializers/__init__.py +++ b/django/core/serializers/__init__.py @@ -20,7 +20,9 @@ from django.conf import settings # Built-in 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 = {} diff --git a/django/core/serializers/base.py b/django/core/serializers/base.py index 5c84861326..e939c0c6e7 100644 --- a/django/core/serializers/base.py +++ b/django/core/serializers/base.py @@ -33,7 +33,9 @@ class Serializer(object): for obj in queryset: self.start_object(obj) 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) else: self.handle_fk_field(obj, field) diff --git a/django/core/serializers/json.py b/django/core/serializers/json.py new file mode 100644 index 0000000000..dd6513db57 --- /dev/null +++ b/django/core/serializers/json.py @@ -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) \ No newline at end of file diff --git a/django/core/serializers/python.py b/django/core/serializers/python.py new file mode 100644 index 0000000000..7989e1d469 --- /dev/null +++ b/django/core/serializers/python.py @@ -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 diff --git a/django/core/serializers/xml_serializer.py b/django/core/serializers/xml_serializer.py index ab8769f237..09fff408cf 100644 --- a/django/core/serializers/xml_serializer.py +++ b/django/core/serializers/xml_serializer.py @@ -2,10 +2,11 @@ XML serializer. """ -from xml.dom import pulldom -from django.utils.xmlutils import SimplerXMLGenerator +from django.conf import settings from django.core.serializers import base from django.db import models +from django.utils.xmlutils import SimplerXMLGenerator +from xml.dom import pulldom class Serializer(base.Serializer): """ @@ -16,7 +17,7 @@ class Serializer(base.Serializer): """ 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.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 # serializer base class). None is handled specially. value = self.get_string_value(obj, field) - if value is None: - self.xml.addQuickElement("None") - else: + if value is not None: self.xml.characters(str(value)) self.xml.endElement("field") @@ -106,7 +105,7 @@ class Deserializer(base.Deserializer): def __init__(self, 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) def next(self): diff --git a/docs/serialization.txt b/docs/serialization.txt index 41954b7a0d..25199e7a50 100644 --- a/docs/serialization.txt +++ b/docs/serialization.txt @@ -78,8 +78,25 @@ 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... +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 ... diff --git a/tests/modeltests/serializers/models.py b/tests/modeltests/serializers/models.py index 8c9483beba..ccf565c365 100644 --- a/tests/modeltests/serializers/models.py +++ b/tests/modeltests/serializers/models.py @@ -91,4 +91,31 @@ API_TESTS = """ >>> Article.objects.all() [, ] +# 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() +[, , ] + +# All the serializers work the same +>>> json = serializers.serialize("json", Article.objects.all()) +>>> for obj in serializers.deserialize("json", json): +... print obj + + + +>>> 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() +[, ] + """