~canonical-django/canonical-django/project-template

« back to all changes in this revision

Viewing changes to trunk/python-packages/django/core/serializers/xml_serializer.py

  • Committer: Matthew Nuzum
  • Date: 2008-11-13 05:46:03 UTC
  • Revision ID: matthew.nuzum@canonical.com-20081113054603-v0kvr6z6xyexvqt3
adding to version control

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
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
            "model" : smart_unicode(obj._meta),
 
48
        })
 
49
 
 
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")
 
56
 
 
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()
 
66
        })
 
67
 
 
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")
 
75
 
 
76
        self.xml.endElement("field")
 
77
 
 
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")
 
96
 
 
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).
 
102
        """
 
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")
 
108
 
 
109
    def _start_relational_field(self, field):
 
110
        """
 
111
        Helper to output the <field> element for relational fields
 
112
        """
 
113
        self.indent(2)
 
114
        self.xml.startElement("field", {
 
115
            "name" : field.name,
 
116
            "rel"  : field.rel.__class__.__name__,
 
117
            "to"   : smart_unicode(field.rel.to._meta),
 
118
        })
 
119
 
 
120
class Deserializer(base.Deserializer):
 
121
    """
 
122
    Deserialize XML.
 
123
    """
 
124
 
 
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)
 
128
 
 
129
    def next(self):
 
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)
 
134
        raise StopIteration
 
135
 
 
136
    def _handle_object(self, node):
 
137
        """
 
138
        Convert an <object> node to a DeserializedObject.
 
139
        """
 
140
        # Look up the model using the model loading mechanism. If this fails,
 
141
        # bail.
 
142
        Model = self._get_model_from_node(node, "model")
 
143
 
 
144
        # Start building a data dictionary from the object.  If the node is
 
145
        # missing the pk attribute, bail.
 
146
        pk = node.getAttribute("pk")
 
147
        if not pk:
 
148
            raise base.DeserializationError("<object> node is missing the 'pk' attribute")
 
149
 
 
150
        data = {Model._meta.pk.attname : Model._meta.pk.to_python(pk)}
 
151
 
 
152
        # Also start building a dict of m2m data (this is saved as
 
153
        # {m2m_accessor_attribute : [list_of_related_objects]})
 
154
        m2m_data = {}
 
155
 
 
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")
 
161
            if not field_name:
 
162
                raise base.DeserializationError("<field> node is missing the 'name' attribute")
 
163
 
 
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)
 
168
 
 
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)
 
174
            else:
 
175
                if field_node.getElementsByTagName('None'):
 
176
                    value = None
 
177
                else:
 
178
                    value = field.to_python(getInnerText(field_node).strip())
 
179
                data[field.name] = value
 
180
 
 
181
        # Return a DeserializedObject so that the m2m data has a place to live.
 
182
        return base.DeserializedObject(Model(**data), m2m_data)
 
183
 
 
184
    def _handle_fk_field_node(self, node, field):
 
185
        """
 
186
        Handle a <field> node for a ForeignKey
 
187
        """
 
188
        # Check if there is a child node named 'None', returning None if so.
 
189
        if node.getElementsByTagName('None'):
 
190
            return None
 
191
        else:
 
192
            return field.rel.to._meta.get_field(field.rel.field_name).to_python(
 
193
                       getInnerText(node).strip())
 
194
 
 
195
    def _handle_m2m_field_node(self, node, field):
 
196
        """
 
197
        Handle a <field> node for a ManyToManyField.
 
198
        """
 
199
        return [field.rel.to._meta.pk.to_python(
 
200
                    c.getAttribute("pk"))
 
201
                    for c in node.getElementsByTagName("object")]
 
202
 
 
203
    def _get_model_from_node(self, node, attr):
 
204
        """
 
205
        Helper to look up a model from a <object model=...> or a <field
 
206
        rel=... to=...> node.
 
207
        """
 
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))
 
213
        try:
 
214
            Model = models.get_model(*model_identifier.split("."))
 
215
        except TypeError:
 
216
            Model = None
 
217
        if Model is None:
 
218
            raise base.DeserializationError(
 
219
                "<%s> node has invalid model identifier: '%s'" % \
 
220
                    (node.nodeName, model_identifier))
 
221
        return Model
 
222
 
 
223
 
 
224
def getInnerText(node):
 
225
    """
 
226
    Get all the inner text of a DOM node (recursively).
 
227
    """
 
228
    # inspired by http://mail.python.org/pipermail/xml-sig/2005-March/011022.html
 
229
    inner_text = []
 
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))
 
235
        else:
 
236
           pass
 
237
    return u"".join(inner_text)
 
238