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):
14
Serializes a QuerySet to XML.
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):
23
Start serialization -- open the XML document and the root element.
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):
31
End serialization -- end the document.
34
self.xml.endElement("django-objects")
35
self.xml.endDocument()
37
def start_object(self, obj):
39
Called as each object is handled.
41
if not hasattr(obj, "_meta"):
42
raise base.SerializationError("Non-model object (%s) encountered during serialization" % type(obj))
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):
52
Called after handling all fields for an object.
55
self.xml.endElement("object")
57
def handle_field(self, obj, field):
59
Called to handle each field on an object (except for ForeignKeys and
63
self.xml.startElement("field", {
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))
74
self.xml.addQuickElement("None")
76
self.xml.endElement("field")
78
def handle_fk_field(self, obj, field):
80
Called to handle a ForeignKey (we need to treat them slightly
81
differently from regular fields).
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()
90
# Related to remote object via other field
91
related = getattr(related, field.rel.field_name)
92
self.xml.characters(smart_unicode(related))
94
self.xml.addQuickElement("None")
95
self.xml.endElement("field")
97
def handle_m2m_field(self, obj, field):
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
if field.creates_table:
104
self._start_relational_field(field)
105
for relobj in getattr(obj, field.name).iterator():
106
self.xml.addQuickElement("object", attrs={"pk" : smart_unicode(relobj._get_pk_val())})
107
self.xml.endElement("field")
109
def _start_relational_field(self, field):
111
Helper to output the <field> element for relational fields
114
self.xml.startElement("field", {
116
"rel" : field.rel.__class__.__name__,
117
"to" : smart_unicode(field.rel.to._meta),
120
class Deserializer(base.Deserializer):
125
def __init__(self, stream_or_string, **options):
126
super(Deserializer, self).__init__(stream_or_string, **options)
127
self.event_stream = pulldom.parse(self.stream)
130
for event, node in self.event_stream:
131
if event == "START_ELEMENT" and node.nodeName == "object":
132
self.event_stream.expandNode(node)
133
return self._handle_object(node)
136
def _handle_object(self, node):
138
Convert an <object> node to a DeserializedObject.
140
# Look up the model using the model loading mechanism. If this fails,
142
Model = self._get_model_from_node(node, "model")
144
# Start building a data dictionary from the object. If the node is
145
# missing the pk attribute, bail.
146
pk = node.getAttribute("pk")
148
raise base.DeserializationError("<object> node is missing the 'pk' attribute")
150
data = {Model._meta.pk.attname : Model._meta.pk.to_python(pk)}
152
# Also start building a dict of m2m data (this is saved as
153
# {m2m_accessor_attribute : [list_of_related_objects]})
156
# Deseralize each field.
157
for field_node in node.getElementsByTagName("field"):
158
# If the field is missing the name attribute, bail (are you
159
# sensing a pattern here?)
160
field_name = field_node.getAttribute("name")
162
raise base.DeserializationError("<field> node is missing the 'name' attribute")
164
# Get the field from the Model. This will raise a
165
# FieldDoesNotExist if, well, the field doesn't exist, which will
166
# be propagated correctly.
167
field = Model._meta.get_field(field_name)
169
# As is usually the case, relation fields get the special treatment.
170
if field.rel and isinstance(field.rel, models.ManyToManyRel):
171
m2m_data[field.name] = self._handle_m2m_field_node(field_node, field)
172
elif field.rel and isinstance(field.rel, models.ManyToOneRel):
173
data[field.attname] = self._handle_fk_field_node(field_node, field)
175
if field_node.getElementsByTagName('None'):
178
value = field.to_python(getInnerText(field_node).strip())
179
data[field.name] = value
181
# Return a DeserializedObject so that the m2m data has a place to live.
182
return base.DeserializedObject(Model(**data), m2m_data)
184
def _handle_fk_field_node(self, node, field):
186
Handle a <field> node for a ForeignKey
188
# Check if there is a child node named 'None', returning None if so.
189
if node.getElementsByTagName('None'):
192
return field.rel.to._meta.get_field(field.rel.field_name).to_python(
193
getInnerText(node).strip())
195
def _handle_m2m_field_node(self, node, field):
197
Handle a <field> node for a ManyToManyField.
199
return [field.rel.to._meta.pk.to_python(
200
c.getAttribute("pk"))
201
for c in node.getElementsByTagName("object")]
203
def _get_model_from_node(self, node, attr):
205
Helper to look up a model from a <object model=...> or a <field
206
rel=... to=...> node.
208
model_identifier = node.getAttribute(attr)
209
if not model_identifier:
210
raise base.DeserializationError(
211
"<%s> node is missing the required '%s' attribute" \
212
% (node.nodeName, attr))
214
Model = models.get_model(*model_identifier.split("."))
218
raise base.DeserializationError(
219
"<%s> node has invalid model identifier: '%s'" % \
220
(node.nodeName, model_identifier))
224
def getInnerText(node):
226
Get all the inner text of a DOM node (recursively).
228
# inspired by http://mail.python.org/pipermail/xml-sig/2005-March/011022.html
230
for child in node.childNodes:
231
if child.nodeType == child.TEXT_NODE or child.nodeType == child.CDATA_SECTION_NODE:
232
inner_text.append(child.data)
233
elif child.nodeType == child.ELEMENT_NODE:
234
inner_text.extend(getInnerText(child))
237
return u"".join(inner_text)