/appengine_django/serializer/xml.py

http://google-app-engine-django.googlecode.com/ · Python · 157 lines · 100 code · 18 blank · 39 comment · 0 complexity · e1f12c024228941b952068f90f915c70 MD5 · raw file

  1. #!/usr/bin/python2.4
  2. #
  3. # Copyright 2008 Google Inc.
  4. #
  5. # Licensed under the Apache License, Version 2.0 (the "License");
  6. # you may not use this file except in compliance with the License.
  7. # You may obtain a copy of the License at
  8. #
  9. # http://www.apache.org/licenses/LICENSE-2.0
  10. #
  11. # Unless required by applicable law or agreed to in writing, software
  12. # distributed under the License is distributed on an "AS IS" BASIS,
  13. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. # See the License for the specific language governing permissions and
  15. # limitations under the License.
  16. """
  17. Replaces the default Django XML serializer with one that uses the built in
  18. ToXml method for each entity.
  19. """
  20. from datetime import datetime
  21. import re
  22. from django.conf import settings
  23. from django.core.serializers import base
  24. from django.core.serializers import xml_serializer
  25. from django.db import models
  26. from google.appengine.api import datastore_types
  27. from google.appengine.ext import db
  28. from python import FakeParent
  29. from python import parse_datetime_with_microseconds
  30. getInnerText = xml_serializer.getInnerText
  31. class Serializer(xml_serializer.Serializer):
  32. """A Django Serializer class to convert datastore models to XML.
  33. This class relies on the ToXml method of the entity behind each model to do
  34. the hard work.
  35. """
  36. def __init__(self, *args, **kwargs):
  37. super(Serializer, self).__init__(*args, **kwargs)
  38. self._objects = []
  39. def handle_field(self, obj, field):
  40. """Fields are not handled individually."""
  41. pass
  42. def handle_fk_field(self, obj, field):
  43. """Fields are not handled individually."""
  44. pass
  45. def start_object(self, obj):
  46. """Nothing needs to be done to start an object."""
  47. pass
  48. def end_object(self, obj):
  49. """Serialize the object to XML and add to the list of objects to output.
  50. The output of ToXml is manipulated to replace the datastore model name in
  51. the "kind" tag with the Django model name (which includes the Django
  52. application name) to make importing easier.
  53. """
  54. xml = obj._entity.ToXml()
  55. xml = xml.replace(u"""kind="%s" """ % obj._entity.kind(),
  56. u"""kind="%s" """ % unicode(obj._meta))
  57. self._objects.append(xml)
  58. def getvalue(self):
  59. """Wrap the serialized objects with XML headers and return."""
  60. str = u"""<?xml version="1.0" encoding="utf-8"?>\n"""
  61. str += u"""<django-objects version="1.0">\n"""
  62. str += u"".join(self._objects)
  63. str += u"""</django-objects>"""
  64. return str
  65. class Deserializer(xml_serializer.Deserializer):
  66. """A Django Deserializer class to convert XML to Django objects.
  67. This is a fairly manualy and simplistic XML parser, it supports just enough
  68. functionality to read the keys and fields for an entity from the XML file and
  69. construct a model object.
  70. """
  71. def next(self):
  72. """Replacement next method to look for 'entity'.
  73. The default next implementation exepects 'object' nodes which is not
  74. what the entity's ToXml output provides.
  75. """
  76. for event, node in self.event_stream:
  77. if event == "START_ELEMENT" and node.nodeName == "entity":
  78. self.event_stream.expandNode(node)
  79. return self._handle_object(node)
  80. raise StopIteration
  81. def _handle_object(self, node):
  82. """Convert an <entity> node to a DeserializedObject"""
  83. Model = self._get_model_from_node(node, "kind")
  84. data = {}
  85. key = db.Key(node.getAttribute("key"))
  86. if key.name():
  87. data["key_name"] = key.name()
  88. parent = None
  89. if key.parent():
  90. parent = FakeParent(key.parent())
  91. m2m_data = {}
  92. # Deseralize each field.
  93. for field_node in node.getElementsByTagName("property"):
  94. # If the field is missing the name attribute, bail (are you
  95. # sensing a pattern here?)
  96. field_name = field_node.getAttribute("name")
  97. if not field_name:
  98. raise base.DeserializationError("<field> node is missing the 'name' "
  99. "attribute")
  100. field = Model.properties()[field_name]
  101. field_value = getInnerText(field_node).strip()
  102. if isinstance(field, db.Reference):
  103. m = re.match("tag:.*\[(.*)\]", field_value)
  104. if not m:
  105. raise base.DeserializationError(u"Invalid reference value: '%s'" %
  106. field_value)
  107. key = m.group(1)
  108. key_obj = db.Key(key)
  109. if not key_obj.name():
  110. raise base.DeserializationError(u"Cannot load Reference with "
  111. "unnamed key: '%s'" % field_value)
  112. data[field.name] = key_obj
  113. else:
  114. format = '%Y-%m-%d %H:%M:%S'
  115. if isinstance(field, db.DateProperty):
  116. field_value = datetime.strptime(field_value, format).date()
  117. elif isinstance(field, db.TimeProperty):
  118. field_value = parse_datetime_with_microseconds(field_value,
  119. format).time()
  120. elif isinstance(field, db.DateTimeProperty):
  121. field_value = parse_datetime_with_microseconds(field_value, format)
  122. data[field.name] = field.validate(field_value)
  123. # Create the new model instance with all it's data, but no parent.
  124. object = Model(**data)
  125. # Now add the parent into the hidden attribute, bypassing the type checks
  126. # in the Model's __init__ routine.
  127. object._parent = parent
  128. # When the deserialized object is saved our replacement DeserializedObject
  129. # class will set object._parent to force the real parent model to be loaded
  130. # the first time it is referenced.
  131. return base.DeserializedObject(object, m2m_data)