Fixed #28324 -- Made feedgenerators write feeds with deterministically ordered attributes.

This commit is contained in:
Georg Sauthoff 2017-02-10 15:29:34 +01:00 committed by Tim Graham
parent 335a8d7895
commit d0f59054d0
3 changed files with 21 additions and 11 deletions

View File

@ -2,7 +2,6 @@
XML serializer. XML serializer.
""" """
from collections import OrderedDict
from xml.dom import pulldom from xml.dom import pulldom
from xml.sax import handler from xml.sax import handler
from xml.sax.expatreader import ExpatParser as _ExpatParser from xml.sax.expatreader import ExpatParser as _ExpatParser
@ -47,7 +46,7 @@ class Serializer(base.Serializer):
raise base.SerializationError("Non-model object (%s) encountered during serialization" % type(obj)) raise base.SerializationError("Non-model object (%s) encountered during serialization" % type(obj))
self.indent(1) self.indent(1)
attrs = OrderedDict([("model", str(obj._meta))]) attrs = {'model': str(obj._meta)}
if not self.use_natural_primary_keys or not hasattr(obj, 'natural_key'): if not self.use_natural_primary_keys or not hasattr(obj, 'natural_key'):
obj_pk = obj.pk obj_pk = obj.pk
if obj_pk is not None: if obj_pk is not None:
@ -68,10 +67,10 @@ class Serializer(base.Serializer):
ManyToManyFields). ManyToManyFields).
""" """
self.indent(2) self.indent(2)
self.xml.startElement("field", OrderedDict([ self.xml.startElement('field', {
("name", field.name), 'name': field.name,
("type", field.get_internal_type()), 'type': field.get_internal_type(),
])) })
# Get a "string version" of the object's data. # Get a "string version" of the object's data.
if getattr(obj, field.name) is not None: if getattr(obj, field.name) is not None:
@ -140,11 +139,11 @@ class Serializer(base.Serializer):
def _start_relational_field(self, field): def _start_relational_field(self, field):
"""Output the <field> element for relational fields.""" """Output the <field> element for relational fields."""
self.indent(2) self.indent(2)
self.xml.startElement("field", OrderedDict([ self.xml.startElement('field', {
("name", field.name), 'name': field.name,
("rel", field.remote_field.__class__.__name__), 'rel': field.remote_field.__class__.__name__,
("to", str(field.remote_field.model._meta)), 'to': str(field.remote_field.model._meta),
])) })
class Deserializer(base.Deserializer): class Deserializer(base.Deserializer):

View File

@ -3,6 +3,7 @@ Utilities for XML generation/parsing.
""" """
import re import re
from collections import OrderedDict
from xml.sax.saxutils import XMLGenerator from xml.sax.saxutils import XMLGenerator
@ -26,3 +27,8 @@ class SimplerXMLGenerator(XMLGenerator):
# See http://www.w3.org/International/questions/qa-controls # See http://www.w3.org/International/questions/qa-controls
raise UnserializableContentError("Control characters are not supported in XML 1.0") raise UnserializableContentError("Control characters are not supported in XML 1.0")
XMLGenerator.characters(self, content) XMLGenerator.characters(self, content)
def startElement(self, name, attrs):
# Sort attrs for a deterministic output.
sorted_attrs = OrderedDict(sorted(attrs.items())) if attrs else attrs
super().startElement(name, sorted_attrs)

View File

@ -126,6 +126,11 @@ class FeedgeneratorTest(unittest.TestCase):
feed.add_item('item_title', 'item_link', 'item_description') feed.add_item('item_title', 'item_link', 'item_description')
feed.writeString('utf-8') feed.writeString('utf-8')
def test_deterministic_attribute_order(self):
feed = feedgenerator.Atom1Feed('title', '/link/', 'desc')
feed_content = feed.writeString('utf-8')
self.assertIn('href="/link/" rel="alternate"', feed_content)
class FeedgeneratorDBTest(TestCase): class FeedgeneratorDBTest(TestCase):