Commit c9032ab0 authored by Jacob Kaplan-Moss's avatar Jacob Kaplan-Moss
Browse files

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
parent 963d88a8
Loading
Loading
Loading
Loading
+3 −1
Original line number Diff line number Diff line
@@ -21,6 +21,8 @@ from django.conf import settings
# Built-in serializers
BUILTIN_SERIALIZERS = {
    "xml"    : "django.core.serializers.xml_serializer",
    "python" : "django.core.serializers.python",
    "json"   : "django.core.serializers.json",
}

_serializers = {}
+3 −1
Original line number Diff line number Diff line
@@ -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)
+51 −0
Original line number Diff line number Diff line
"""
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
+101 −0
Original line number Diff line number Diff line
"""
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
+6 −7
Original line number Diff line number Diff line
@@ -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):
Loading