app/django/core/serializers/xml_serializer.py
changeset 54 03e267d67478
child 323 ff1a9aa48cfd
equal deleted inserted replaced
53:57b4279d8c4e 54:03e267d67478
       
     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         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")
       
   107 
       
   108     def _start_relational_field(self, field):
       
   109         """
       
   110         Helper to output the <field> element for relational fields
       
   111         """
       
   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),
       
   117         })
       
   118 
       
   119 class Deserializer(base.Deserializer):
       
   120     """
       
   121     Deserialize XML.
       
   122     """
       
   123 
       
   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)
       
   127 
       
   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
       
   134 
       
   135     def _handle_object(self, node):
       
   136         """
       
   137         Convert an <object> node to a DeserializedObject.
       
   138         """
       
   139         # Look up the model using the model loading mechanism. If this fails,
       
   140         # bail.
       
   141         Model = self._get_model_from_node(node, "model")
       
   142 
       
   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")
       
   148 
       
   149         data = {Model._meta.pk.attname : Model._meta.pk.to_python(pk)}
       
   150 
       
   151         # Also start building a dict of m2m data (this is saved as
       
   152         # {m2m_accessor_attribute : [list_of_related_objects]})
       
   153         m2m_data = {}
       
   154 
       
   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")
       
   162 
       
   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)
       
   167 
       
   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
       
   179 
       
   180         # Return a DeserializedObject so that the m2m data has a place to live.
       
   181         return base.DeserializedObject(Model(**data), m2m_data)
       
   182 
       
   183     def _handle_fk_field_node(self, node, field):
       
   184         """
       
   185         Handle a <field> node for a ForeignKey
       
   186         """
       
   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())
       
   193 
       
   194     def _handle_m2m_field_node(self, node, field):
       
   195         """
       
   196         Handle a <field> node for a ManyToManyField.
       
   197         """
       
   198         return [field.rel.to._meta.pk.to_python(
       
   199                     c.getAttribute("pk"))
       
   200                     for c in node.getElementsByTagName("object")]
       
   201 
       
   202     def _get_model_from_node(self, node, attr):
       
   203         """
       
   204         Helper to look up a model from a <object model=...> or a <field
       
   205         rel=... to=...> node.
       
   206         """
       
   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
       
   221 
       
   222 
       
   223 def getInnerText(node):
       
   224     """
       
   225     Get all the inner text of a DOM node (recursively).
       
   226     """
       
   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)
       
   237