diff --git a/rest_framework/compat.py b/rest_framework/compat.py index 9ad8b0d28..b78b5c916 100644 --- a/rest_framework/compat.py +++ b/rest_framework/compat.py @@ -603,3 +603,12 @@ except ImportError: klass.__unicode__ = klass.__str__ klass.__str__ = lambda self: self.__unicode__().encode('utf-8') return klass + +if django.VERSION >= (1, 7): + try: + from collections import OrderedDict + except ImportError: + # Fall-back to OrderedDict from ordereddict module + from ordereddict import OrderedDict +else: + from django.utils.datastructures import SortedDict as OrderedDict diff --git a/rest_framework/fields.py b/rest_framework/fields.py index 6caae9242..b97c60f15 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -20,11 +20,10 @@ from django.http import QueryDict from django.forms import widgets from django.utils.encoding import is_protected_type from django.utils.translation import ugettext_lazy as _ -from django.utils.datastructures import SortedDict from rest_framework import ISO_8601 from rest_framework.compat import ( timezone, parse_date, parse_datetime, parse_time, BytesIO, six, smart_text, - force_text, is_non_str_iterable + force_text, is_non_str_iterable, OrderedDict ) from rest_framework.settings import api_settings @@ -220,7 +219,7 @@ class Field(object): return [self.to_native(item) for item in value] elif isinstance(value, dict): # Make sure we preserve field ordering, if it exists - ret = SortedDict() + ret = OrderedDict() for key, val in value.items(): ret[key] = self.to_native(val) return ret @@ -235,7 +234,7 @@ class Field(object): return {} def metadata(self): - metadata = SortedDict() + metadata = OrderedDict() metadata['type'] = self.type_label metadata['required'] = getattr(self, 'required', False) optional_attrs = ['read_only', 'label', 'help_text', diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index 43d339da0..e2e04792d 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -20,9 +20,8 @@ from django.contrib.contenttypes.generic import GenericForeignKey from django.core.paginator import Page from django.db import models from django.forms import widgets -from django.utils.datastructures import SortedDict from django.core.exceptions import ObjectDoesNotExist -from rest_framework.compat import get_concrete_model, six +from rest_framework.compat import get_concrete_model, OrderedDict, six from rest_framework.settings import api_settings @@ -105,9 +104,9 @@ class DictWithMetadata(dict): return dict(self) -class SortedDictWithMetadata(SortedDict): +class OrderedDictWithMetadata(OrderedDict): """ - A sorted dict-like object, that can have additional properties attached. + An ordered dict-like object, that can have additional properties attached. """ def __getstate__(self): """ @@ -115,7 +114,7 @@ class SortedDictWithMetadata(SortedDict): Overriden to remove the metadata from the dict, since it shouldn't be pickle and may in some instances be unpickleable. """ - return SortedDict(self).__dict__ + return OrderedDict(self).__dict__ def _is_protected_type(obj): @@ -151,7 +150,7 @@ def _get_declared_fields(bases, attrs): if hasattr(base, 'base_fields'): fields = list(base.base_fields.items()) + fields - return SortedDict(fields) + return OrderedDict(fields) class SerializerMetaclass(type): @@ -179,7 +178,7 @@ class BaseSerializer(WritableField): pass _options_class = SerializerOptions - _dict_class = SortedDictWithMetadata + _dict_class = OrderedDictWithMetadata def __init__(self, instance=None, data=None, files=None, context=None, partial=False, many=None, @@ -225,7 +224,7 @@ class BaseSerializer(WritableField): This will be the set of any explicitly declared fields, plus the set of fields returned by get_default_fields(). """ - ret = SortedDict() + ret = OrderedDict() # Get the explicitly declared fields base_fields = copy.deepcopy(self.base_fields) @@ -241,7 +240,7 @@ class BaseSerializer(WritableField): # If 'fields' is specified, use those fields, in that order. if self.opts.fields: assert isinstance(self.opts.fields, (list, tuple)), '`fields` must be a list or tuple' - new = SortedDict() + new = OrderedDict() for key in self.opts.fields: new[key] = ret[key] ret = new @@ -605,7 +604,7 @@ class BaseSerializer(WritableField): Useful for things like responding to OPTIONS requests, or generating API schemas for auto-documentation. """ - return SortedDict( + return OrderedDict( [(field_name, field.metadata()) for field_name, field in six.iteritems(self.fields)] ) @@ -664,7 +663,7 @@ class ModelSerializer(Serializer): assert cls is not None, \ "Serializer class '%s' is missing 'model' Meta option" % self.__class__.__name__ opts = get_concrete_model(cls)._meta - ret = SortedDict() + ret = OrderedDict() nested = bool(self.opts.depth) # Deal with adding the primary key field diff --git a/rest_framework/settings.py b/rest_framework/settings.py index 38753c968..b4e4f1c3f 100644 --- a/rest_framework/settings.py +++ b/rest_framework/settings.py @@ -20,7 +20,11 @@ back to the defaults. from __future__ import unicode_literals from django.conf import settings -from django.utils import importlib +try: + # Available in Python 2.7+ + import importlib +except ImportError: + from django.utils import importlib from rest_framework import ISO_8601 from rest_framework.compat import six @@ -51,7 +55,7 @@ DEFAULTS = { 'DEFAULT_CONTENT_NEGOTIATION_CLASS': 'rest_framework.negotiation.DefaultContentNegotiation', - # Genric view behavior + # Generic view behavior 'DEFAULT_MODEL_SERIALIZER_CLASS': 'rest_framework.serializers.ModelSerializer', 'DEFAULT_PAGINATION_SERIALIZER_CLASS': diff --git a/rest_framework/tests/test_fields.py b/rest_framework/tests/test_fields.py index 17d12f231..81d21993e 100644 --- a/rest_framework/tests/test_fields.py +++ b/rest_framework/tests/test_fields.py @@ -10,8 +10,8 @@ from uuid import uuid4 from django.core import validators from django.db import models from django.test import TestCase -from django.utils.datastructures import SortedDict from rest_framework import serializers +from rest_framework.compat import OrderedDict from rest_framework.tests.models import RESTFrameworkModel @@ -95,7 +95,7 @@ class BasicFieldTests(TestCase): Field should preserve dictionary ordering, if it exists. See: https://github.com/tomchristie/django-rest-framework/issues/832 """ - ret = SortedDict() + ret = OrderedDict() ret['c'] = 1 ret['b'] = 1 ret['a'] = 1 diff --git a/rest_framework/tests/test_serializer.py b/rest_framework/tests/test_serializer.py index fb2eac0ba..d0f2041a7 100644 --- a/rest_framework/tests/test_serializer.py +++ b/rest_framework/tests/test_serializer.py @@ -1274,7 +1274,7 @@ class SerializerPickleTests(TestCase): def test_pickle_inner_serializer(self): """ - Test pickling a serializer whose resulting .data (a SortedDictWithMetadata) will + Test pickling a serializer whose resulting .data (a OrderedDictWithMetadata) will have unpickleable meta data--in order to make sure metadata doesn't get pulled into the pickle. See DictWithMetadata.__getstate__ """ @@ -1289,13 +1289,13 @@ class SerializerPickleTests(TestCase): Regression test for #645. """ data = serializers.DictWithMetadata({1: 1}) - self.assertEqual(data.__getstate__(), serializers.SortedDict({1: 1})) + self.assertEqual(data.__getstate__(), serializers.OrderedDict({1: 1})) def test_serializer_data_is_pickleable(self): """ Another regression test for #645. """ - data = serializers.SortedDictWithMetadata({1: 1}) + data = serializers.OrderedDictWithMetadata({1: 1}) repr(pickle.loads(pickle.dumps(data, 0))) diff --git a/rest_framework/utils/encoders.py b/rest_framework/utils/encoders.py index e5fa41947..26d0fa3bd 100644 --- a/rest_framework/utils/encoders.py +++ b/rest_framework/utils/encoders.py @@ -3,10 +3,9 @@ Helper classes for parsers. """ from __future__ import unicode_literals from django.db.models.query import QuerySet -from django.utils.datastructures import SortedDict from django.utils.functional import Promise -from rest_framework.compat import timezone, force_text -from rest_framework.serializers import DictWithMetadata, SortedDictWithMetadata +from rest_framework.compat import timezone, force_text, OrderedDict +from rest_framework.serializers import DictWithMetadata, OrderedDictWithMetadata import datetime import decimal import types @@ -66,7 +65,7 @@ else: class SafeDumper(yaml.SafeDumper): """ Handles decimals as strings. - Handles SortedDicts as usual dicts, but preserves field order, rather + Handles OrderedDicts as usual dicts, but preserves field order, rather than the usual behaviour of sorting the keys. """ def represent_decimal(self, data): @@ -80,7 +79,7 @@ else: best_style = True if hasattr(mapping, 'items'): mapping = list(mapping.items()) - if not isinstance(mapping, SortedDict): + if not isinstance(mapping, OrderedDict): mapping.sort() for item_key, item_value in mapping: node_key = self.represent_data(item_key) @@ -100,11 +99,11 @@ else: SafeDumper.add_representer(decimal.Decimal, SafeDumper.represent_decimal) - SafeDumper.add_representer(SortedDict, + SafeDumper.add_representer(OrderedDict, yaml.representer.SafeRepresenter.represent_dict) SafeDumper.add_representer(DictWithMetadata, yaml.representer.SafeRepresenter.represent_dict) - SafeDumper.add_representer(SortedDictWithMetadata, + SafeDumper.add_representer(OrderedDictWithMetadata, yaml.representer.SafeRepresenter.represent_dict) SafeDumper.add_representer(types.GeneratorType, yaml.representer.SafeRepresenter.represent_list) diff --git a/rest_framework/views.py b/rest_framework/views.py index a2668f2c0..17d7701f1 100644 --- a/rest_framework/views.py +++ b/rest_framework/views.py @@ -5,10 +5,9 @@ from __future__ import unicode_literals from django.core.exceptions import PermissionDenied from django.http import Http404 -from django.utils.datastructures import SortedDict from django.views.decorators.csrf import csrf_exempt from rest_framework import status, exceptions -from rest_framework.compat import smart_text, HttpResponseBase, View +from rest_framework.compat import smart_text, HttpResponseBase, OrderedDict, View from rest_framework.request import Request from rest_framework.response import Response from rest_framework.settings import api_settings @@ -418,7 +417,7 @@ class APIView(View): # By default we can't provide any form-like information, however the # generic views override this implementation and add additional # information for POST and PUT methods, based on the serializer. - ret = SortedDict() + ret = OrderedDict() ret['name'] = self.get_view_name() ret['description'] = self.get_view_description() ret['renders'] = [renderer.media_type for renderer in self.renderer_classes] diff --git a/setup.py b/setup.py index 78cdb6289..cc9cc9083 100755 --- a/setup.py +++ b/setup.py @@ -51,6 +51,12 @@ if sys.argv[-1] == 'publish': print(" git push --tags") sys.exit() +requirements = [] +try: + from collections import OrderedDict +except ImportError: + # Back-port for Python < 2.7 + requirements.append('ordereddict') setup( name='djangorestframework', @@ -63,7 +69,7 @@ setup( packages=get_packages('rest_framework'), package_data=get_package_data('rest_framework'), test_suite='rest_framework.runtests.runtests.main', - install_requires=[], + install_requires=requirements, classifiers=[ 'Development Status :: 5 - Production/Stable', 'Environment :: Web Environment',