summaryrefslogtreecommitdiffstats
path: root/appengine_django/serializer
diff options
context:
space:
mode:
Diffstat (limited to 'appengine_django/serializer')
-rwxr-xr-xappengine_django/serializer/__init__.py0
-rw-r--r--appengine_django/serializer/__init__.pycbin0 -> 158 bytes
-rwxr-xr-xappengine_django/serializer/python.py130
-rw-r--r--appengine_django/serializer/python.pycbin0 -> 4241 bytes
-rwxr-xr-xappengine_django/serializer/xml.py147
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
new file mode 100644
index 0000000..8dbe892
--- /dev/null
+++ b/appengine_django/serializer/__init__.pyc
Binary files differ
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
new file mode 100644
index 0000000..d9a3507
--- /dev/null
+++ b/appengine_django/serializer/python.pyc
Binary files differ
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)