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