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