/django/core/serializers/xml_serializer.py

https://code.google.com/p/mango-py/ · Python · 297 lines · 216 code · 21 blank · 60 comment · 44 complexity · 17dc0fce167b763d407fc894c46f7c97 MD5 · raw file

  1. """
  2. XML serializer.
  3. """
  4. from django.conf import settings
  5. from django.core.serializers import base
  6. from django.db import models, DEFAULT_DB_ALIAS
  7. from django.utils.xmlutils import SimplerXMLGenerator
  8. from django.utils.encoding import smart_unicode
  9. from xml.dom import pulldom
  10. class Serializer(base.Serializer):
  11. """
  12. Serializes a QuerySet to XML.
  13. """
  14. def indent(self, level):
  15. if self.options.get('indent', None) is not None:
  16. self.xml.ignorableWhitespace('\n' + ' ' * self.options.get('indent', None) * level)
  17. def start_serialization(self):
  18. """
  19. Start serialization -- open the XML document and the root element.
  20. """
  21. self.xml = SimplerXMLGenerator(self.stream, self.options.get("encoding", settings.DEFAULT_CHARSET))
  22. self.xml.startDocument()
  23. self.xml.startElement("django-objects", {"version" : "1.0"})
  24. def end_serialization(self):
  25. """
  26. End serialization -- end the document.
  27. """
  28. self.indent(0)
  29. self.xml.endElement("django-objects")
  30. self.xml.endDocument()
  31. def start_object(self, obj):
  32. """
  33. Called as each object is handled.
  34. """
  35. if not hasattr(obj, "_meta"):
  36. raise base.SerializationError("Non-model object (%s) encountered during serialization" % type(obj))
  37. self.indent(1)
  38. obj_pk = obj._get_pk_val()
  39. if obj_pk is None:
  40. attrs = {"model": smart_unicode(obj._meta),}
  41. else:
  42. attrs = {
  43. "pk": smart_unicode(obj._get_pk_val()),
  44. "model": smart_unicode(obj._meta),
  45. }
  46. self.xml.startElement("object", attrs)
  47. def end_object(self, obj):
  48. """
  49. Called after handling all fields for an object.
  50. """
  51. self.indent(1)
  52. self.xml.endElement("object")
  53. def handle_field(self, obj, field):
  54. """
  55. Called to handle each field on an object (except for ForeignKeys and
  56. ManyToManyFields)
  57. """
  58. self.indent(2)
  59. self.xml.startElement("field", {
  60. "name" : field.name,
  61. "type" : field.get_internal_type()
  62. })
  63. # Get a "string version" of the object's data.
  64. if getattr(obj, field.name) is not None:
  65. self.xml.characters(field.value_to_string(obj))
  66. else:
  67. self.xml.addQuickElement("None")
  68. self.xml.endElement("field")
  69. def handle_fk_field(self, obj, field):
  70. """
  71. Called to handle a ForeignKey (we need to treat them slightly
  72. differently from regular fields).
  73. """
  74. self._start_relational_field(field)
  75. related = getattr(obj, field.name)
  76. if related is not None:
  77. if self.use_natural_keys and hasattr(related, 'natural_key'):
  78. # If related object has a natural key, use it
  79. related = related.natural_key()
  80. # Iterable natural keys are rolled out as subelements
  81. for key_value in related:
  82. self.xml.startElement("natural", {})
  83. self.xml.characters(smart_unicode(key_value))
  84. self.xml.endElement("natural")
  85. else:
  86. if field.rel.field_name == related._meta.pk.name:
  87. # Related to remote object via primary key
  88. related = related._get_pk_val()
  89. else:
  90. # Related to remote object via other field
  91. related = getattr(related, field.rel.field_name)
  92. self.xml.characters(smart_unicode(related))
  93. else:
  94. self.xml.addQuickElement("None")
  95. self.xml.endElement("field")
  96. def handle_m2m_field(self, obj, field):
  97. """
  98. Called to handle a ManyToManyField. Related objects are only
  99. serialized as references to the object's PK (i.e. the related *data*
  100. is not dumped, just the relation).
  101. """
  102. if field.rel.through._meta.auto_created:
  103. self._start_relational_field(field)
  104. if self.use_natural_keys and hasattr(field.rel.to, 'natural_key'):
  105. # If the objects in the m2m have a natural key, use it
  106. def handle_m2m(value):
  107. natural = value.natural_key()
  108. # Iterable natural keys are rolled out as subelements
  109. self.xml.startElement("object", {})
  110. for key_value in natural:
  111. self.xml.startElement("natural", {})
  112. self.xml.characters(smart_unicode(key_value))
  113. self.xml.endElement("natural")
  114. self.xml.endElement("object")
  115. else:
  116. def handle_m2m(value):
  117. self.xml.addQuickElement("object", attrs={
  118. 'pk' : smart_unicode(value._get_pk_val())
  119. })
  120. for relobj in getattr(obj, field.name).iterator():
  121. handle_m2m(relobj)
  122. self.xml.endElement("field")
  123. def _start_relational_field(self, field):
  124. """
  125. Helper to output the <field> element for relational fields
  126. """
  127. self.indent(2)
  128. self.xml.startElement("field", {
  129. "name" : field.name,
  130. "rel" : field.rel.__class__.__name__,
  131. "to" : smart_unicode(field.rel.to._meta),
  132. })
  133. class Deserializer(base.Deserializer):
  134. """
  135. Deserialize XML.
  136. """
  137. def __init__(self, stream_or_string, **options):
  138. super(Deserializer, self).__init__(stream_or_string, **options)
  139. self.event_stream = pulldom.parse(self.stream)
  140. self.db = options.pop('using', DEFAULT_DB_ALIAS)
  141. def next(self):
  142. for event, node in self.event_stream:
  143. if event == "START_ELEMENT" and node.nodeName == "object":
  144. self.event_stream.expandNode(node)
  145. return self._handle_object(node)
  146. raise StopIteration
  147. def _handle_object(self, node):
  148. """
  149. Convert an <object> node to a DeserializedObject.
  150. """
  151. # Look up the model using the model loading mechanism. If this fails,
  152. # bail.
  153. Model = self._get_model_from_node(node, "model")
  154. # Start building a data dictionary from the object.
  155. # If the node is missing the pk set it to None
  156. if node.hasAttribute("pk"):
  157. pk = node.getAttribute("pk")
  158. else:
  159. pk = None
  160. data = {Model._meta.pk.attname : Model._meta.pk.to_python(pk)}
  161. # Also start building a dict of m2m data (this is saved as
  162. # {m2m_accessor_attribute : [list_of_related_objects]})
  163. m2m_data = {}
  164. # Deseralize each field.
  165. for field_node in node.getElementsByTagName("field"):
  166. # If the field is missing the name attribute, bail (are you
  167. # sensing a pattern here?)
  168. field_name = field_node.getAttribute("name")
  169. if not field_name:
  170. raise base.DeserializationError("<field> node is missing the 'name' attribute")
  171. # Get the field from the Model. This will raise a
  172. # FieldDoesNotExist if, well, the field doesn't exist, which will
  173. # be propagated correctly.
  174. field = Model._meta.get_field(field_name)
  175. # As is usually the case, relation fields get the special treatment.
  176. if field.rel and isinstance(field.rel, models.ManyToManyRel):
  177. m2m_data[field.name] = self._handle_m2m_field_node(field_node, field)
  178. elif field.rel and isinstance(field.rel, models.ManyToOneRel):
  179. data[field.attname] = self._handle_fk_field_node(field_node, field)
  180. else:
  181. if field_node.getElementsByTagName('None'):
  182. value = None
  183. else:
  184. value = field.to_python(getInnerText(field_node).strip())
  185. data[field.name] = value
  186. # Return a DeserializedObject so that the m2m data has a place to live.
  187. return base.DeserializedObject(Model(**data), m2m_data)
  188. def _handle_fk_field_node(self, node, field):
  189. """
  190. Handle a <field> node for a ForeignKey
  191. """
  192. # Check if there is a child node named 'None', returning None if so.
  193. if node.getElementsByTagName('None'):
  194. return None
  195. else:
  196. if hasattr(field.rel.to._default_manager, 'get_by_natural_key'):
  197. keys = node.getElementsByTagName('natural')
  198. if keys:
  199. # If there are 'natural' subelements, it must be a natural key
  200. field_value = [getInnerText(k).strip() for k in keys]
  201. obj = field.rel.to._default_manager.db_manager(self.db).get_by_natural_key(*field_value)
  202. obj_pk = getattr(obj, field.rel.field_name)
  203. # If this is a natural foreign key to an object that
  204. # has a FK/O2O as the foreign key, use the FK value
  205. if field.rel.to._meta.pk.rel:
  206. obj_pk = obj_pk.pk
  207. else:
  208. # Otherwise, treat like a normal PK
  209. field_value = getInnerText(node).strip()
  210. obj_pk = field.rel.to._meta.get_field(field.rel.field_name).to_python(field_value)
  211. return obj_pk
  212. else:
  213. field_value = getInnerText(node).strip()
  214. return field.rel.to._meta.get_field(field.rel.field_name).to_python(field_value)
  215. def _handle_m2m_field_node(self, node, field):
  216. """
  217. Handle a <field> node for a ManyToManyField.
  218. """
  219. if hasattr(field.rel.to._default_manager, 'get_by_natural_key'):
  220. def m2m_convert(n):
  221. keys = n.getElementsByTagName('natural')
  222. if keys:
  223. # If there are 'natural' subelements, it must be a natural key
  224. field_value = [getInnerText(k).strip() for k in keys]
  225. obj_pk = field.rel.to._default_manager.db_manager(self.db).get_by_natural_key(*field_value).pk
  226. else:
  227. # Otherwise, treat like a normal PK value.
  228. obj_pk = field.rel.to._meta.pk.to_python(n.getAttribute('pk'))
  229. return obj_pk
  230. else:
  231. m2m_convert = lambda n: field.rel.to._meta.pk.to_python(n.getAttribute('pk'))
  232. return [m2m_convert(c) for c in node.getElementsByTagName("object")]
  233. def _get_model_from_node(self, node, attr):
  234. """
  235. Helper to look up a model from a <object model=...> or a <field
  236. rel=... to=...> node.
  237. """
  238. model_identifier = node.getAttribute(attr)
  239. if not model_identifier:
  240. raise base.DeserializationError(
  241. "<%s> node is missing the required '%s' attribute" \
  242. % (node.nodeName, attr))
  243. try:
  244. Model = models.get_model(*model_identifier.split("."))
  245. except TypeError:
  246. Model = None
  247. if Model is None:
  248. raise base.DeserializationError(
  249. "<%s> node has invalid model identifier: '%s'" % \
  250. (node.nodeName, model_identifier))
  251. return Model
  252. def getInnerText(node):
  253. """
  254. Get all the inner text of a DOM node (recursively).
  255. """
  256. # inspired by http://mail.python.org/pipermail/xml-sig/2005-March/011022.html
  257. inner_text = []
  258. for child in node.childNodes:
  259. if child.nodeType == child.TEXT_NODE or child.nodeType == child.CDATA_SECTION_NODE:
  260. inner_text.append(child.data)
  261. elif child.nodeType == child.ELEMENT_NODE:
  262. inner_text.extend(getInnerText(child))
  263. else:
  264. pass
  265. return u"".join(inner_text)