Merged the queryset-refactor branch into trunk.
[fdr-django.git] / django / core / serializers / xml_serializer.py
blob667faf7c5ec522d2a92cf5b199b21d9e9036a922
1 """
2 XML serializer.
3 """
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
12 class Serializer(base.Serializer):
13 """
14 Serializes a QuerySet to XML.
15 """
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)
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"})
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()
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))
44 self.indent(1)
45 self.xml.startElement("object", {
46 "pk" : smart_unicode(obj._get_pk_val()),
47 "model" : smart_unicode(obj._meta),
50 def end_object(self, obj):
51 """
52 Called after handling all fields for an object.
53 """
54 self.indent(1)
55 self.xml.endElement("object")
57 def handle_field(self, obj, field):
58 """
59 Called to handle each field on an object (except for ForeignKeys and
60 ManyToManyFields)
61 """
62 self.indent(2)
63 self.xml.startElement("field", {
64 "name" : field.name,
65 "type" : field.get_internal_type()
68 # Get a "string version" of the object's data (this is handled by the
69 # serializer base class).
70 if getattr(obj, field.name) is not None:
71 value = self.get_string_value(obj, field)
72 self.xml.characters(smart_unicode(value))
73 else:
74 self.xml.addQuickElement("None")
76 self.xml.endElement("field")
78 def handle_fk_field(self, obj, field):
79 """
80 Called to handle a ForeignKey (we need to treat them slightly
81 differently from regular fields).
82 """
83 self._start_relational_field(field)
84 related = getattr(obj, field.name)
85 if related is not None:
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")
97 def handle_m2m_field(self, obj, field):
98 """
99 Called to handle a ManyToManyField. Related objects are only
100 serialized as references to the object's PK (i.e. the related *data*
101 is not dumped, just the relation).
103 self._start_relational_field(field)
104 for relobj in getattr(obj, field.name).iterator():
105 self.xml.addQuickElement("object", attrs={"pk" : smart_unicode(relobj._get_pk_val())})
106 self.xml.endElement("field")
108 def _start_relational_field(self, field):
110 Helper to output the <field> element for relational fields
112 self.indent(2)
113 self.xml.startElement("field", {
114 "name" : field.name,
115 "rel" : field.rel.__class__.__name__,
116 "to" : smart_unicode(field.rel.to._meta),
119 class Deserializer(base.Deserializer):
121 Deserialize XML.
124 def __init__(self, stream_or_string, **options):
125 super(Deserializer, self).__init__(stream_or_string, **options)
126 self.event_stream = pulldom.parse(self.stream)
128 def next(self):
129 for event, node in self.event_stream:
130 if event == "START_ELEMENT" and node.nodeName == "object":
131 self.event_stream.expandNode(node)
132 return self._handle_object(node)
133 raise StopIteration
135 def _handle_object(self, node):
137 Convert an <object> node to a DeserializedObject.
139 # Look up the model using the model loading mechanism. If this fails,
140 # bail.
141 Model = self._get_model_from_node(node, "model")
143 # Start building a data dictionary from the object. If the node is
144 # missing the pk attribute, bail.
145 pk = node.getAttribute("pk")
146 if not pk:
147 raise base.DeserializationError("<object> node is missing the 'pk' attribute")
149 data = {Model._meta.pk.attname : Model._meta.pk.to_python(pk)}
151 # Also start building a dict of m2m data (this is saved as
152 # {m2m_accessor_attribute : [list_of_related_objects]})
153 m2m_data = {}
155 # Deseralize each field.
156 for field_node in node.getElementsByTagName("field"):
157 # If the field is missing the name attribute, bail (are you
158 # sensing a pattern here?)
159 field_name = field_node.getAttribute("name")
160 if not field_name:
161 raise base.DeserializationError("<field> node is missing the 'name' attribute")
163 # Get the field from the Model. This will raise a
164 # FieldDoesNotExist if, well, the field doesn't exist, which will
165 # be propagated correctly.
166 field = Model._meta.get_field(field_name)
168 # As is usually the case, relation fields get the special treatment.
169 if field.rel and isinstance(field.rel, models.ManyToManyRel):
170 m2m_data[field.name] = self._handle_m2m_field_node(field_node, field)
171 elif field.rel and isinstance(field.rel, models.ManyToOneRel):
172 data[field.attname] = self._handle_fk_field_node(field_node, field)
173 else:
174 if field_node.getElementsByTagName('None'):
175 value = None
176 else:
177 value = field.to_python(getInnerText(field_node).strip())
178 data[field.name] = value
180 # Return a DeserializedObject so that the m2m data has a place to live.
181 return base.DeserializedObject(Model(**data), m2m_data)
183 def _handle_fk_field_node(self, node, field):
185 Handle a <field> node for a ForeignKey
187 # Check if there is a child node named 'None', returning None if so.
188 if node.getElementsByTagName('None'):
189 return None
190 else:
191 return field.rel.to._meta.get_field(field.rel.field_name).to_python(
192 getInnerText(node).strip())
194 def _handle_m2m_field_node(self, node, field):
196 Handle a <field> node for a ManyToManyField.
198 return [field.rel.to._meta.pk.to_python(
199 c.getAttribute("pk"))
200 for c in node.getElementsByTagName("object")]
202 def _get_model_from_node(self, node, attr):
204 Helper to look up a model from a <object model=...> or a <field
205 rel=... to=...> node.
207 model_identifier = node.getAttribute(attr)
208 if not model_identifier:
209 raise base.DeserializationError(
210 "<%s> node is missing the required '%s' attribute" \
211 % (node.nodeName, attr))
212 try:
213 Model = models.get_model(*model_identifier.split("."))
214 except TypeError:
215 Model = None
216 if Model is None:
217 raise base.DeserializationError(
218 "<%s> node has invalid model identifier: '%s'" % \
219 (node.nodeName, model_identifier))
220 return Model
223 def getInnerText(node):
225 Get all the inner text of a DOM node (recursively).
227 # inspired by http://mail.python.org/pipermail/xml-sig/2005-March/011022.html
228 inner_text = []
229 for child in node.childNodes:
230 if child.nodeType == child.TEXT_NODE or child.nodeType == child.CDATA_SECTION_NODE:
231 inner_text.append(child.data)
232 elif child.nodeType == child.ELEMENT_NODE:
233 inner_text.extend(getInnerText(child))
234 else:
235 pass
236 return u"".join(inner_text)