web/lib/django/core/serializers/xml_serializer.py
changeset 38 77b6da96e6f1
equal deleted inserted replaced
37:8d941af65caf 38:77b6da96e6f1
       
     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, DEFAULT_DB_ALIAS
       
     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 self.use_natural_keys and hasattr(related, 'natural_key'):
       
    85                 # If related object has a natural key, use it
       
    86                 related = related.natural_key()
       
    87                 # Iterable natural keys are rolled out as subelements
       
    88                 for key_value in related:
       
    89                     self.xml.startElement("natural", {})
       
    90                     self.xml.characters(smart_unicode(key_value))
       
    91                     self.xml.endElement("natural")
       
    92             else:
       
    93                 if field.rel.field_name == related._meta.pk.name:
       
    94                     # Related to remote object via primary key
       
    95                     related = related._get_pk_val()
       
    96                 else:
       
    97                     # Related to remote object via other field
       
    98                     related = getattr(related, field.rel.field_name)
       
    99                 self.xml.characters(smart_unicode(related))
       
   100         else:
       
   101             self.xml.addQuickElement("None")
       
   102         self.xml.endElement("field")
       
   103 
       
   104     def handle_m2m_field(self, obj, field):
       
   105         """
       
   106         Called to handle a ManyToManyField. Related objects are only
       
   107         serialized as references to the object's PK (i.e. the related *data*
       
   108         is not dumped, just the relation).
       
   109         """
       
   110         if field.rel.through._meta.auto_created:
       
   111             self._start_relational_field(field)
       
   112             if self.use_natural_keys and hasattr(field.rel.to, 'natural_key'):
       
   113                 # If the objects in the m2m have a natural key, use it
       
   114                 def handle_m2m(value):
       
   115                     natural = value.natural_key()
       
   116                     # Iterable natural keys are rolled out as subelements
       
   117                     self.xml.startElement("object", {})
       
   118                     for key_value in natural:
       
   119                         self.xml.startElement("natural", {})
       
   120                         self.xml.characters(smart_unicode(key_value))
       
   121                         self.xml.endElement("natural")
       
   122                     self.xml.endElement("object")
       
   123             else:
       
   124                 def handle_m2m(value):
       
   125                     self.xml.addQuickElement("object", attrs={
       
   126                         'pk' : smart_unicode(value._get_pk_val())
       
   127                     })
       
   128             for relobj in getattr(obj, field.name).iterator():
       
   129                 handle_m2m(relobj)
       
   130 
       
   131             self.xml.endElement("field")
       
   132 
       
   133     def _start_relational_field(self, field):
       
   134         """
       
   135         Helper to output the <field> element for relational fields
       
   136         """
       
   137         self.indent(2)
       
   138         self.xml.startElement("field", {
       
   139             "name" : field.name,
       
   140             "rel"  : field.rel.__class__.__name__,
       
   141             "to"   : smart_unicode(field.rel.to._meta),
       
   142         })
       
   143 
       
   144 class Deserializer(base.Deserializer):
       
   145     """
       
   146     Deserialize XML.
       
   147     """
       
   148 
       
   149     def __init__(self, stream_or_string, **options):
       
   150         super(Deserializer, self).__init__(stream_or_string, **options)
       
   151         self.event_stream = pulldom.parse(self.stream)
       
   152         self.db = options.pop('using', DEFAULT_DB_ALIAS)
       
   153 
       
   154     def next(self):
       
   155         for event, node in self.event_stream:
       
   156             if event == "START_ELEMENT" and node.nodeName == "object":
       
   157                 self.event_stream.expandNode(node)
       
   158                 return self._handle_object(node)
       
   159         raise StopIteration
       
   160 
       
   161     def _handle_object(self, node):
       
   162         """
       
   163         Convert an <object> node to a DeserializedObject.
       
   164         """
       
   165         # Look up the model using the model loading mechanism. If this fails,
       
   166         # bail.
       
   167         Model = self._get_model_from_node(node, "model")
       
   168 
       
   169         # Start building a data dictionary from the object.  If the node is
       
   170         # missing the pk attribute, bail.
       
   171         pk = node.getAttribute("pk")
       
   172         if not pk:
       
   173             raise base.DeserializationError("<object> node is missing the 'pk' attribute")
       
   174 
       
   175         data = {Model._meta.pk.attname : Model._meta.pk.to_python(pk)}
       
   176 
       
   177         # Also start building a dict of m2m data (this is saved as
       
   178         # {m2m_accessor_attribute : [list_of_related_objects]})
       
   179         m2m_data = {}
       
   180 
       
   181         # Deseralize each field.
       
   182         for field_node in node.getElementsByTagName("field"):
       
   183             # If the field is missing the name attribute, bail (are you
       
   184             # sensing a pattern here?)
       
   185             field_name = field_node.getAttribute("name")
       
   186             if not field_name:
       
   187                 raise base.DeserializationError("<field> node is missing the 'name' attribute")
       
   188 
       
   189             # Get the field from the Model. This will raise a
       
   190             # FieldDoesNotExist if, well, the field doesn't exist, which will
       
   191             # be propagated correctly.
       
   192             field = Model._meta.get_field(field_name)
       
   193 
       
   194             # As is usually the case, relation fields get the special treatment.
       
   195             if field.rel and isinstance(field.rel, models.ManyToManyRel):
       
   196                 m2m_data[field.name] = self._handle_m2m_field_node(field_node, field)
       
   197             elif field.rel and isinstance(field.rel, models.ManyToOneRel):
       
   198                 data[field.attname] = self._handle_fk_field_node(field_node, field)
       
   199             else:
       
   200                 if field_node.getElementsByTagName('None'):
       
   201                     value = None
       
   202                 else:
       
   203                     value = field.to_python(getInnerText(field_node).strip())
       
   204                 data[field.name] = value
       
   205 
       
   206         # Return a DeserializedObject so that the m2m data has a place to live.
       
   207         return base.DeserializedObject(Model(**data), m2m_data)
       
   208 
       
   209     def _handle_fk_field_node(self, node, field):
       
   210         """
       
   211         Handle a <field> node for a ForeignKey
       
   212         """
       
   213         # Check if there is a child node named 'None', returning None if so.
       
   214         if node.getElementsByTagName('None'):
       
   215             return None
       
   216         else:
       
   217             if hasattr(field.rel.to._default_manager, 'get_by_natural_key'):
       
   218                 keys = node.getElementsByTagName('natural')
       
   219                 if keys:
       
   220                     # If there are 'natural' subelements, it must be a natural key
       
   221                     field_value = [getInnerText(k).strip() for k in keys]
       
   222                     obj = field.rel.to._default_manager.db_manager(self.db).get_by_natural_key(*field_value)
       
   223                     obj_pk = getattr(obj, field.rel.field_name)
       
   224                     # If this is a natural foreign key to an object that
       
   225                     # has a FK/O2O as the foreign key, use the FK value
       
   226                     if field.rel.to._meta.pk.rel:
       
   227                         obj_pk = obj_pk.pk
       
   228                 else:
       
   229                     # Otherwise, treat like a normal PK
       
   230                     field_value = getInnerText(node).strip()
       
   231                     obj_pk = field.rel.to._meta.get_field(field.rel.field_name).to_python(field_value)
       
   232                 return obj_pk
       
   233             else:
       
   234                 field_value = getInnerText(node).strip()
       
   235                 return field.rel.to._meta.get_field(field.rel.field_name).to_python(field_value)
       
   236 
       
   237     def _handle_m2m_field_node(self, node, field):
       
   238         """
       
   239         Handle a <field> node for a ManyToManyField.
       
   240         """
       
   241         if hasattr(field.rel.to._default_manager, 'get_by_natural_key'):
       
   242             def m2m_convert(n):
       
   243                 keys = n.getElementsByTagName('natural')
       
   244                 if keys:
       
   245                     # If there are 'natural' subelements, it must be a natural key
       
   246                     field_value = [getInnerText(k).strip() for k in keys]
       
   247                     obj_pk = field.rel.to._default_manager.db_manager(self.db).get_by_natural_key(*field_value).pk
       
   248                 else:
       
   249                     # Otherwise, treat like a normal PK value.
       
   250                     obj_pk = field.rel.to._meta.pk.to_python(n.getAttribute('pk'))
       
   251                 return obj_pk
       
   252         else:
       
   253             m2m_convert = lambda n: field.rel.to._meta.pk.to_python(n.getAttribute('pk'))
       
   254         return [m2m_convert(c) for c in node.getElementsByTagName("object")]
       
   255 
       
   256     def _get_model_from_node(self, node, attr):
       
   257         """
       
   258         Helper to look up a model from a <object model=...> or a <field
       
   259         rel=... to=...> node.
       
   260         """
       
   261         model_identifier = node.getAttribute(attr)
       
   262         if not model_identifier:
       
   263             raise base.DeserializationError(
       
   264                 "<%s> node is missing the required '%s' attribute" \
       
   265                     % (node.nodeName, attr))
       
   266         try:
       
   267             Model = models.get_model(*model_identifier.split("."))
       
   268         except TypeError:
       
   269             Model = None
       
   270         if Model is None:
       
   271             raise base.DeserializationError(
       
   272                 "<%s> node has invalid model identifier: '%s'" % \
       
   273                     (node.nodeName, model_identifier))
       
   274         return Model
       
   275 
       
   276 
       
   277 def getInnerText(node):
       
   278     """
       
   279     Get all the inner text of a DOM node (recursively).
       
   280     """
       
   281     # inspired by http://mail.python.org/pipermail/xml-sig/2005-March/011022.html
       
   282     inner_text = []
       
   283     for child in node.childNodes:
       
   284         if child.nodeType == child.TEXT_NODE or child.nodeType == child.CDATA_SECTION_NODE:
       
   285             inner_text.append(child.data)
       
   286         elif child.nodeType == child.ELEMENT_NODE:
       
   287             inner_text.extend(getInnerText(child))
       
   288         else:
       
   289            pass
       
   290     return u"".join(inner_text)