diff options
Diffstat (limited to 'appengine_django/serializer')
| -rwxr-xr-x | appengine_django/serializer/__init__.py | 0 | ||||
| -rw-r--r-- | appengine_django/serializer/__init__.pyc | bin | 0 -> 158 bytes | |||
| -rwxr-xr-x | appengine_django/serializer/python.py | 130 | ||||
| -rw-r--r-- | appengine_django/serializer/python.pyc | bin | 0 -> 4241 bytes | |||
| -rwxr-xr-x | appengine_django/serializer/xml.py | 147 | 
5 files changed, 277 insertions, 0 deletions
diff --git a/appengine_django/serializer/__init__.py b/appengine_django/serializer/__init__.py new file mode 100755 index 0000000..e69de29 --- /dev/null +++ b/appengine_django/serializer/__init__.py diff --git a/appengine_django/serializer/__init__.pyc b/appengine_django/serializer/__init__.pyc Binary files differnew file mode 100644 index 0000000..8dbe892 --- /dev/null +++ b/appengine_django/serializer/__init__.pyc diff --git a/appengine_django/serializer/python.py b/appengine_django/serializer/python.py new file mode 100755 index 0000000..bce16e7 --- /dev/null +++ b/appengine_django/serializer/python.py @@ -0,0 +1,130 @@ +#!/usr/bin/python2.4 +# +# Copyright 2008 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +#     http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +""" +A Python "serializer", based on the default Django python serializer. + +The only customisation is in the deserialization process which needs to take +special care to resolve the name and parent attributes of the key for each +entity and also recreate the keys for any references appropriately. +""" + + +from django.conf import settings +from django.core.serializers import base +from django.core.serializers import python +from django.db import models + +from google.appengine.api import datastore_types +from google.appengine.ext import db + +from django.utils.encoding import smart_unicode + +Serializer = python.Serializer + + +class FakeParent(object): +  """Fake parent 'model' like object. + +  This class exists to allow a parent object to be provided to a new model +  without having to load the parent instance itself. +  """ + +  def __init__(self, parent_key): +    self._entity = parent_key + + +def Deserializer(object_list, **options): +  """Deserialize simple Python objects back into Model 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 = python._get_model(d["model"]) +    data = {} +    key = resolve_key(Model._meta.module_name, d["pk"]) +    if key.name(): +      data["key_name"] = key.name() +    parent = None +    if key.parent(): +      parent = FakeParent(key.parent()) +    m2m_data = {} + +    # Handle each field +    for (field_name, field_value) in d["fields"].iteritems(): +      if isinstance(field_value, str): +        field_value = smart_unicode( +            field_value, options.get("encoding", +                                     settings.DEFAULT_CHARSET), +            strings_only=True) +      field = Model.properties()[field_name] + +      if isinstance(field, db.Reference): +        # Resolve foreign key references. +        data[field.name] = resolve_key(Model._meta.module_name, field_value) +        if not data[field.name].name(): +          raise base.DeserializationError(u"Cannot load Reference with " +                                          "unnamed key: '%s'" % field_value) +      else: +        data[field.name] = field.validate(field_value) +    # Create the new model instance with all it's data, but no parent. +    object = Model(**data) +    # Now add the parent into the hidden attribute, bypassing the type checks +    # in the Model's __init__ routine. +    object._parent = parent +    # When the deserialized object is saved our replacement DeserializedObject +    # class will set object._parent to force the real parent model to be loaded +    # the first time it is referenced. +    yield base.DeserializedObject(object, m2m_data) + + +def resolve_key(model, key_data): +  """Creates a Key instance from a some data. + +  Args: +    model: The name of the model this key is being resolved for. Only used in +      the fourth case below (a plain key_name string). +    key_data: The data to create a key instance from. May be in four formats: +      * The str() output of a key instance. Eg. A base64 encoded string. +      * The repr() output of a key instance. Eg. A string for eval(). +      * A list of arguments to pass to db.Key.from_path. +      * A single string value, being the key_name of the instance. When this +        format is used the resulting key has no parent, and is for the model +        named in the model parameter. + +  Returns: +    An instance of db.Key. If the data cannot be used to create a Key instance +    an error will be raised. +  """ +  if isinstance(key_data, list): +    # The key_data is a from_path sequence. +    return db.Key.from_path(*key_data) +  elif isinstance(key_data, basestring): +    if key_data.find("from_path") != -1: +      # key_data is encoded in repr(key) format +      return eval(key_data) +    else: +      try: +        # key_data encoded a str(key) format +        return db.Key(key_data) +      except datastore_types.datastore_errors.BadKeyError, e: +        # Final try, assume it's a plain key name for the model. +        return db.Key.from_path(model, key_data) +  else: +    raise base.DeserializationError(u"Invalid key data: '%s'" % key_data) diff --git a/appengine_django/serializer/python.pyc b/appengine_django/serializer/python.pyc Binary files differnew file mode 100644 index 0000000..d9a3507 --- /dev/null +++ b/appengine_django/serializer/python.pyc diff --git a/appengine_django/serializer/xml.py b/appengine_django/serializer/xml.py new file mode 100755 index 0000000..f67588a --- /dev/null +++ b/appengine_django/serializer/xml.py @@ -0,0 +1,147 @@ +#!/usr/bin/python2.4 +# +# Copyright 2008 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +#     http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +""" +Replaces the default Django XML serializer with one that uses the built in +ToXml method for each entity. +""" + +import re + +from django.conf import settings +from django.core.serializers import base +from django.core.serializers import xml_serializer +from django.db import models + +from google.appengine.api import datastore_types +from google.appengine.ext import db + +from python import FakeParent + +getInnerText = xml_serializer.getInnerText + + +class Serializer(xml_serializer.Serializer): +  """A Django Serializer class to convert datastore models to XML. + +  This class relies on the ToXml method of the entity behind each model to do +  the hard work. +  """ + +  def __init__(self, *args, **kwargs): +    super(Serializer, self).__init__(*args, **kwargs) +    self._objects = [] + +  def handle_field(self, obj, field): +    """Fields are not handled individually.""" +    pass + +  def handle_fk_field(self, obj, field): +    """Fields are not handled individually.""" +    pass + +  def start_object(self, obj): +    """Nothing needs to be done to start an object.""" +    pass + +  def end_object(self, obj): +    """Serialize the object to XML and add to the list of objects to output. + +    The output of ToXml is manipulated to replace the datastore model name in +    the "kind" tag with the Django model name (which includes the Django +    application name) to make importing easier. +    """ +    xml = obj._entity.ToXml() +    xml = xml.replace(u"""kind="%s" """ % obj._entity.kind(), +                      u"""kind="%s" """ % unicode(obj._meta)) +    self._objects.append(xml) + +  def getvalue(self): +    """Wrap the serialized objects with XML headers and return.""" +    str = u"""<?xml version="1.0" encoding="utf-8"?>\n""" +    str += u"""<django-objects version="1.0">\n""" +    str += u"".join(self._objects) +    str += u"""</django-objects>""" +    return str + + +class Deserializer(xml_serializer.Deserializer): +  """A Django Deserializer class to convert XML to Django objects. + +  This is a fairly manualy and simplistic XML parser, it supports just enough +  functionality to read the keys and fields for an entity from the XML file and +  construct a model object. +  """ + +  def next(self): +    """Replacement next method to look for 'entity'. + +    The default next implementation exepects 'object' nodes which is not +    what the entity's ToXml output provides. +    """ +    for event, node in self.event_stream: +      if event == "START_ELEMENT" and node.nodeName == "entity": +        self.event_stream.expandNode(node) +        return self._handle_object(node) +    raise StopIteration + +  def _handle_object(self, node): +    """Convert an <entity> node to a DeserializedObject""" +    Model = self._get_model_from_node(node, "kind") +    data = {} +    key = db.Key(node.getAttribute("key")) +    if key.name(): +      data["key_name"] = key.name() +    parent = None +    if key.parent(): +      parent = FakeParent(key.parent()) +    m2m_data = {} + +    # Deseralize each field. +    for field_node in node.getElementsByTagName("property"): +      # If the field is missing the name attribute, bail (are you +      # sensing a pattern here?) +      field_name = field_node.getAttribute("name") +      if not field_name: +          raise base.DeserializationError("<field> node is missing the 'name' " +                                          "attribute") +      field = Model.properties()[field_name] +      field_value = getInnerText(field_node).strip() + +      if isinstance(field, db.Reference): +        m = re.match("tag:.*\[(.*)\]", field_value) +        if not m: +          raise base.DeserializationError(u"Invalid reference value: '%s'" % +                                          field_value) +        key = m.group(1) +        key_obj = db.Key(key) +        if not key_obj.name(): +          raise base.DeserializationError(u"Cannot load Reference with " +                                          "unnamed key: '%s'" % field_value) +        data[field.name] = key_obj +      else: +        data[field.name] = field.validate(field_value) + +    # Create the new model instance with all it's data, but no parent. +    object = Model(**data) +    # Now add the parent into the hidden attribute, bypassing the type checks +    # in the Model's __init__ routine. +    object._parent = parent +    # When the deserialized object is saved our replacement DeserializedObject +    # class will set object._parent to force the real parent model to be loaded +    # the first time it is referenced. +    return base.DeserializedObject(object, m2m_data)  | 
