web/lib/django/core/serializers/xml_serializer.py
changeset 0 0d40e90630ef
child 29 cc9b7e14412b
equal deleted inserted replaced
-1:000000000000 0:0d40e90630ef
       
     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.
       
    69         if getattr(obj, field.name) is not None:
       
    70             self.xml.characters(field.value_to_string(obj))
       
    71         else:
       
    72             self.xml.addQuickElement("None")
       
    73 
       
    74         self.xml.endElement("field")
       
    75 
       
    76     def handle_fk_field(self, obj, field):
       
    77         """
       
    78         Called to handle a ForeignKey (we need to treat them slightly
       
    79         differently from regular fields).
       
    80         """
       
    81         self._start_relational_field(field)
       
    82         related = getattr(obj, field.name)
       
    83         if related is not None:
       
    84             if field.rel.field_name == related._meta.pk.name:
       
    85                 # Related to remote object via primary key
       
    86                 related = related._get_pk_val()
       
    87             else:
       
    88                 # Related to remote object via other field
       
    89                 related = getattr(related, field.rel.field_name)
       
    90             self.xml.characters(smart_unicode(related))
       
    91         else:
       
    92             self.xml.addQuickElement("None")
       
    93         self.xml.endElement("field")
       
    94 
       
    95     def handle_m2m_field(self, obj, field):
       
    96         """
       
    97         Called to handle a ManyToManyField. Related objects are only
       
    98         serialized as references to the object's PK (i.e. the related *data*
       
    99         is not dumped, just the relation).
       
   100         """
       
   101         if field.creates_table:
       
   102             self._start_relational_field(field)
       
   103             for relobj in getattr(obj, field.name).iterator():
       
   104                 self.xml.addQuickElement("object", attrs={"pk" : smart_unicode(relobj._get_pk_val())})
       
   105             self.xml.endElement("field")
       
   106 
       
   107     def _start_relational_field(self, field):
       
   108         """
       
   109         Helper to output the <field> element for relational fields
       
   110         """
       
   111         self.indent(2)
       
   112         self.xml.startElement("field", {
       
   113             "name" : field.name,
       
   114             "rel"  : field.rel.__class__.__name__,
       
   115             "to"   : smart_unicode(field.rel.to._meta),
       
   116         })
       
   117 
       
   118 class Deserializer(base.Deserializer):
       
   119     """
       
   120     Deserialize XML.
       
   121     """
       
   122 
       
   123     def __init__(self, stream_or_string, **options):
       
   124         super(Deserializer, self).__init__(stream_or_string, **options)
       
   125         self.event_stream = pulldom.parse(self.stream)
       
   126 
       
   127     def next(self):
       
   128         for event, node in self.event_stream:
       
   129             if event == "START_ELEMENT" and node.nodeName == "object":
       
   130                 self.event_stream.expandNode(node)
       
   131                 return self._handle_object(node)
       
   132         raise StopIteration
       
   133 
       
   134     def _handle_object(self, node):
       
   135         """
       
   136         Convert an <object> node to a DeserializedObject.
       
   137         """
       
   138         # Look up the model using the model loading mechanism. If this fails,
       
   139         # bail.
       
   140         Model = self._get_model_from_node(node, "model")
       
   141 
       
   142         # Start building a data dictionary from the object.  If the node is
       
   143         # missing the pk attribute, bail.
       
   144         pk = node.getAttribute("pk")
       
   145         if not pk:
       
   146             raise base.DeserializationError("<object> node is missing the 'pk' attribute")
       
   147 
       
   148         data = {Model._meta.pk.attname : Model._meta.pk.to_python(pk)}
       
   149 
       
   150         # Also start building a dict of m2m data (this is saved as
       
   151         # {m2m_accessor_attribute : [list_of_related_objects]})
       
   152         m2m_data = {}
       
   153 
       
   154         # Deseralize each field.
       
   155         for field_node in node.getElementsByTagName("field"):
       
   156             # If the field is missing the name attribute, bail (are you
       
   157             # sensing a pattern here?)
       
   158             field_name = field_node.getAttribute("name")
       
   159             if not field_name:
       
   160                 raise base.DeserializationError("<field> node is missing the 'name' attribute")
       
   161 
       
   162             # Get the field from the Model. This will raise a
       
   163             # FieldDoesNotExist if, well, the field doesn't exist, which will
       
   164             # be propagated correctly.
       
   165             field = Model._meta.get_field(field_name)
       
   166 
       
   167             # As is usually the case, relation fields get the special treatment.
       
   168             if field.rel and isinstance(field.rel, models.ManyToManyRel):
       
   169                 m2m_data[field.name] = self._handle_m2m_field_node(field_node, field)
       
   170             elif field.rel and isinstance(field.rel, models.ManyToOneRel):
       
   171                 data[field.attname] = self._handle_fk_field_node(field_node, field)
       
   172             else:
       
   173                 if field_node.getElementsByTagName('None'):
       
   174                     value = None
       
   175                 else:
       
   176                     value = field.to_python(getInnerText(field_node).strip())
       
   177                 data[field.name] = value
       
   178 
       
   179         # Return a DeserializedObject so that the m2m data has a place to live.
       
   180         return base.DeserializedObject(Model(**data), m2m_data)
       
   181 
       
   182     def _handle_fk_field_node(self, node, field):
       
   183         """
       
   184         Handle a <field> node for a ForeignKey
       
   185         """
       
   186         # Check if there is a child node named 'None', returning None if so.
       
   187         if node.getElementsByTagName('None'):
       
   188             return None
       
   189         else:
       
   190             return field.rel.to._meta.get_field(field.rel.field_name).to_python(
       
   191                        getInnerText(node).strip())
       
   192 
       
   193     def _handle_m2m_field_node(self, node, field):
       
   194         """
       
   195         Handle a <field> node for a ManyToManyField.
       
   196         """
       
   197         return [field.rel.to._meta.pk.to_python(
       
   198                     c.getAttribute("pk"))
       
   199                     for c in node.getElementsByTagName("object")]
       
   200 
       
   201     def _get_model_from_node(self, node, attr):
       
   202         """
       
   203         Helper to look up a model from a <object model=...> or a <field
       
   204         rel=... to=...> node.
       
   205         """
       
   206         model_identifier = node.getAttribute(attr)
       
   207         if not model_identifier:
       
   208             raise base.DeserializationError(
       
   209                 "<%s> node is missing the required '%s' attribute" \
       
   210                     % (node.nodeName, attr))
       
   211         try:
       
   212             Model = models.get_model(*model_identifier.split("."))
       
   213         except TypeError:
       
   214             Model = None
       
   215         if Model is None:
       
   216             raise base.DeserializationError(
       
   217                 "<%s> node has invalid model identifier: '%s'" % \
       
   218                     (node.nodeName, model_identifier))
       
   219         return Model
       
   220 
       
   221 
       
   222 def getInnerText(node):
       
   223     """
       
   224     Get all the inner text of a DOM node (recursively).
       
   225     """
       
   226     # inspired by http://mail.python.org/pipermail/xml-sig/2005-March/011022.html
       
   227     inner_text = []
       
   228     for child in node.childNodes:
       
   229         if child.nodeType == child.TEXT_NODE or child.nodeType == child.CDATA_SECTION_NODE:
       
   230             inner_text.append(child.data)
       
   231         elif child.nodeType == child.ELEMENT_NODE:
       
   232             inner_text.extend(getInnerText(child))
       
   233         else:
       
   234            pass
       
   235     return u"".join(inner_text)
       
   236