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 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
113 self
.xml
.startElement("field", {
115 "rel" : field
.rel
.__class
__.__name
__,
116 "to" : smart_unicode(field
.rel
.to
._meta
),
119 class Deserializer(base
.Deserializer
):
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
)
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
)
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,
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")
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]})
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")
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
_m
2m
_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
)
174 if field_node
.getElementsByTagName('None'):
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'):
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
))
213 Model
= models
.get_model(*model_identifier
.split("."))
217 raise base
.DeserializationError(
218 "<%s> node has invalid model identifier: '%s'" % \
219 (node
.nodeName
, model_identifier
))
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
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
))
236 return u
"".join(inner_text
)