From 73978c95607f40f333ccfa3a9c202bde18e1d395 Mon Sep 17 00:00:00 2001 From: kazmiruk Date: Wed, 22 Apr 2015 13:12:12 +0700 Subject: [PATCH 01/13] change SortedDict to OrderedDict --- rest_framework/fields.py | 5 ++--- rest_framework/routers.py | 7 +++---- rest_framework/serializers.py | 27 +++++++++++++++------------ rest_framework/utils/encoders.py | 12 ++++++------ rest_framework/views.py | 4 ++-- 5 files changed, 28 insertions(+), 27 deletions(-) diff --git a/rest_framework/fields.py b/rest_framework/fields.py index a2d2d5feb..aad49ed5c 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -22,7 +22,6 @@ from django.forms import widgets from django.utils import six, timezone from django.utils.encoding import is_protected_type from django.utils.translation import ugettext_lazy as _ -from django.utils.datastructures import SortedDict from django.utils.dateparse import parse_date, parse_datetime, parse_time from rest_framework import ISO_8601 from rest_framework.compat import ( @@ -225,7 +224,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 = collections.OrderedDict() for key, val in value.items(): ret[key] = self.to_native(val) return ret @@ -240,7 +239,7 @@ class Field(object): return {} def metadata(self): - metadata = SortedDict() + metadata = collections.OrderedDict() metadata['type'] = self.type_label metadata['required'] = getattr(self, 'required', False) optional_attrs = ['read_only', 'label', 'help_text', diff --git a/rest_framework/routers.py b/rest_framework/routers.py index 169e6e8bc..9937566d2 100644 --- a/rest_framework/routers.py +++ b/rest_framework/routers.py @@ -16,11 +16,10 @@ For example, you might have a `urls.py` that looks something like this: from __future__ import unicode_literals import itertools -from collections import namedtuple +from collections import namedtuple, OrderedDict from django.conf.urls import patterns, url from django.core.exceptions import ImproperlyConfigured from django.core.urlresolvers import NoReverseMatch -from django.utils.datastructures import SortedDict from rest_framework import views from rest_framework.response import Response from rest_framework.reverse import reverse @@ -278,7 +277,7 @@ class DefaultRouter(SimpleRouter): """ Return a view to use as the API root. """ - api_root_dict = SortedDict() + api_root_dict = OrderedDict() list_name = self.routes[0].name for prefix, viewset, basename in self.registry: api_root_dict[prefix] = list_name.format(basename=basename) @@ -287,7 +286,7 @@ class DefaultRouter(SimpleRouter): _ignore_model_permissions = True def get(self, request, *args, **kwargs): - ret = SortedDict() + ret = OrderedDict() for key, url_name in api_root_dict.items(): try: ret[key] = reverse( diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index 7d85894f6..29187c92c 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -11,6 +11,7 @@ python primitives. response content is handled by parsers and renderers. """ from __future__ import unicode_literals +from collections import OrderedDict import copy import datetime import inspect @@ -21,7 +22,6 @@ from django.core.paginator import Page from django.db import models from django.forms import widgets from django.utils import six -from django.utils.datastructures import SortedDict from django.utils.functional import cached_property from django.core.exceptions import ObjectDoesNotExist from rest_framework.settings import api_settings @@ -106,7 +106,7 @@ class DictWithMetadata(dict): return dict(self) -class SortedDictWithMetadata(SortedDict): +class OrderedDictWithMetadata(OrderedDict): """ A sorted dict-like object, that can have additional properties attached. """ @@ -116,7 +116,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): @@ -152,7 +152,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): @@ -180,7 +180,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=False, @@ -229,7 +229,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) @@ -245,7 +245,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 @@ -606,7 +606,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) @@ -683,7 +683,7 @@ class ModelSerializer(Serializer): self.__class__.__name__ ) opts = cls._meta.concrete_model._meta - ret = SortedDict() + ret = OrderedDict() nested = bool(self.opts.depth) # Deal with adding the primary key field @@ -985,13 +985,16 @@ class ModelSerializer(Serializer): if field_name in attrs: m2m_data[field_name] = attrs.pop(field_name) - # Forward m2m relations - for field in meta.many_to_many + meta.virtual_fields: + def _inner_loop_code(field): if isinstance(field, GenericForeignKey): - continue + return if field.name in attrs: m2m_data[field.name] = attrs.pop(field.name) + # Forward m2m relations + _ = [_inner_loop_code(field) for field in meta.many_to_many] + _ = [_inner_loop_code(field) for field in meta.virtual_fields] + # Nested forward relations - These need to be marked so we can save # them before saving the parent model instance. for field_name in attrs.keys(): diff --git a/rest_framework/utils/encoders.py b/rest_framework/utils/encoders.py index 00ffdfbae..1e570cdde 100644 --- a/rest_framework/utils/encoders.py +++ b/rest_framework/utils/encoders.py @@ -2,12 +2,12 @@ Helper classes for parsers. """ from __future__ import unicode_literals +from collections import OrderedDict from django.utils import timezone from django.db.models.query import QuerySet -from django.utils.datastructures import SortedDict from django.utils.functional import Promise from rest_framework.compat import force_text -from rest_framework.serializers import DictWithMetadata, SortedDictWithMetadata +from rest_framework.serializers import DictWithMetadata, OrderedDictWithMetadata import datetime import decimal import types @@ -67,7 +67,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): @@ -81,7 +81,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) @@ -103,7 +103,7 @@ else: SafeDumper.represent_decimal ) SafeDumper.add_representer( - SortedDict, + OrderedDict, yaml.representer.SafeRepresenter.represent_dict ) SafeDumper.add_representer( @@ -111,7 +111,7 @@ else: yaml.representer.SafeRepresenter.represent_dict ) SafeDumper.add_representer( - SortedDictWithMetadata, + OrderedDictWithMetadata, yaml.representer.SafeRepresenter.represent_dict ) SafeDumper.add_representer( diff --git a/rest_framework/views.py b/rest_framework/views.py index 38346ab79..89b592177 100644 --- a/rest_framework/views.py +++ b/rest_framework/views.py @@ -3,9 +3,9 @@ Provides an APIView class that is the base of all views in REST framework. """ from __future__ import unicode_literals +from collections import OrderedDict 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 @@ -421,7 +421,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] From 51650f88b7d0d0782885f452466d8a3cbbb9e8ed Mon Sep 17 00:00:00 2001 From: kazmiruk Date: Wed, 22 Apr 2015 13:31:49 +0700 Subject: [PATCH 02/13] fix flake warning --- rest_framework/serializers.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index 29187c92c..ec8977a55 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -992,8 +992,8 @@ class ModelSerializer(Serializer): m2m_data[field.name] = attrs.pop(field.name) # Forward m2m relations - _ = [_inner_loop_code(field) for field in meta.many_to_many] - _ = [_inner_loop_code(field) for field in meta.virtual_fields] + [_inner_loop_code(field) for field in meta.many_to_many] + [_inner_loop_code(field) for field in meta.virtual_fields] # Nested forward relations - These need to be marked so we can save # them before saving the parent model instance. From e4e3f57321e7f32b889ccb7d1bfc354eb1cbd101 Mon Sep 17 00:00:00 2001 From: kazmiruk Date: Wed, 22 Apr 2015 13:33:58 +0700 Subject: [PATCH 03/13] fix test for OrderedDict --- tests/test_fields.py | 4 ++-- tests/test_serializer.py | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/test_fields.py b/tests/test_fields.py index 0ddbe48b5..807676230 100644 --- a/tests/test_fields.py +++ b/tests/test_fields.py @@ -3,6 +3,7 @@ General serializer field tests. """ from __future__ import unicode_literals +from collections import OrderedDict import datetime import re from decimal import Decimal @@ -10,7 +11,6 @@ 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 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/tests/test_serializer.py b/tests/test_serializer.py index e72b723f0..c0de5cf27 100644 --- a/tests/test_serializer.py +++ b/tests/test_serializer.py @@ -1297,7 +1297,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__ """ @@ -1312,13 +1312,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))) From 47c61679a58a3b01f36991023b19f4bf83201f3e Mon Sep 17 00:00:00 2001 From: kazmiruk Date: Wed, 22 Apr 2015 13:43:16 +0700 Subject: [PATCH 04/13] adds backward compatibility --- rest_framework/fields.py | 9 +++++++-- rest_framework/routers.py | 7 ++++++- rest_framework/serializers.py | 6 +++++- rest_framework/utils/encoders.py | 6 +++++- rest_framework/views.py | 6 +++++- tests/test_fields.py | 6 +++++- 6 files changed, 33 insertions(+), 7 deletions(-) diff --git a/rest_framework/fields.py b/rest_framework/fields.py index aad49ed5c..6088cdee7 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -30,6 +30,11 @@ from rest_framework.compat import ( ) from rest_framework.settings import api_settings +try: + from collections import OrderedDict +except ImportError: + from django.utils.datastructures import SortedDict as OrderedDict + def is_simple_callable(obj): """ @@ -224,7 +229,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 = collections.OrderedDict() + ret = OrderedDict() for key, val in value.items(): ret[key] = self.to_native(val) return ret @@ -239,7 +244,7 @@ class Field(object): return {} def metadata(self): - metadata = collections.OrderedDict() + 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/routers.py b/rest_framework/routers.py index 9937566d2..4ef7707bb 100644 --- a/rest_framework/routers.py +++ b/rest_framework/routers.py @@ -16,7 +16,7 @@ For example, you might have a `urls.py` that looks something like this: from __future__ import unicode_literals import itertools -from collections import namedtuple, OrderedDict +from collections import namedtuple from django.conf.urls import patterns, url from django.core.exceptions import ImproperlyConfigured from django.core.urlresolvers import NoReverseMatch @@ -25,6 +25,11 @@ from rest_framework.response import Response from rest_framework.reverse import reverse from rest_framework.urlpatterns import format_suffix_patterns +try: + from collections import OrderedDict +except ImportError: + from django.utils.datastructures import SortedDict as OrderedDict + Route = namedtuple('Route', ['url', 'mapping', 'name', 'initkwargs']) DynamicDetailRoute = namedtuple('DynamicDetailRoute', ['url', 'name', 'initkwargs']) diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index ec8977a55..d3d08f2ff 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -11,7 +11,6 @@ python primitives. response content is handled by parsers and renderers. """ from __future__ import unicode_literals -from collections import OrderedDict import copy import datetime import inspect @@ -37,6 +36,11 @@ from rest_framework.settings import api_settings from rest_framework.relations import * # NOQA from rest_framework.fields import * # NOQA +try: + from collections import OrderedDict +except ImportError: + from django.utils.datastructures import SortedDict as OrderedDict + def _resolve_model(obj): """ diff --git a/rest_framework/utils/encoders.py b/rest_framework/utils/encoders.py index 1e570cdde..c6ebdcffc 100644 --- a/rest_framework/utils/encoders.py +++ b/rest_framework/utils/encoders.py @@ -2,7 +2,6 @@ Helper classes for parsers. """ from __future__ import unicode_literals -from collections import OrderedDict from django.utils import timezone from django.db.models.query import QuerySet from django.utils.functional import Promise @@ -13,6 +12,11 @@ import decimal import types import json +try: + from collections import OrderedDict +except ImportError: + from django.utils.datastructures import SortedDict as OrderedDict + class JSONEncoder(json.JSONEncoder): """ diff --git a/rest_framework/views.py b/rest_framework/views.py index 89b592177..3852d8e15 100644 --- a/rest_framework/views.py +++ b/rest_framework/views.py @@ -3,7 +3,6 @@ Provides an APIView class that is the base of all views in REST framework. """ from __future__ import unicode_literals -from collections import OrderedDict from django.core.exceptions import PermissionDenied from django.http import Http404 from django.views.decorators.csrf import csrf_exempt @@ -14,6 +13,11 @@ from rest_framework.response import Response from rest_framework.settings import api_settings from rest_framework.utils import formatting +try: + from collections import OrderedDict +except ImportError: + from django.utils.datastructures import SortedDict as OrderedDict + def get_view_name(view_cls, suffix=None): """ diff --git a/tests/test_fields.py b/tests/test_fields.py index 807676230..261ef521c 100644 --- a/tests/test_fields.py +++ b/tests/test_fields.py @@ -3,7 +3,6 @@ General serializer field tests. """ from __future__ import unicode_literals -from collections import OrderedDict import datetime import re from decimal import Decimal @@ -14,6 +13,11 @@ from django.test import TestCase from rest_framework import serializers from tests.models import RESTFrameworkModel +try: + from collections import OrderedDict +except ImportError: + from django.utils.datastructures import SortedDict as OrderedDict + class TimestampedModel(models.Model): added = models.DateTimeField(auto_now_add=True) From b3e03cd8e2dfaefcba13ecffe03714a44bf632e3 Mon Sep 17 00:00:00 2001 From: kazmiruk Date: Wed, 22 Apr 2015 19:52:23 +0700 Subject: [PATCH 05/13] fixes broken test + importlib warnings --- rest_framework/serializers.py | 5 ++++- rest_framework/settings.py | 7 ++++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index d3d08f2ff..ceab6111f 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -16,7 +16,7 @@ import datetime import inspect import types from decimal import Decimal -from django.contrib.contenttypes.generic import GenericForeignKey +from django.contrib.contenttypes.fields import GenericForeignKey from django.core.paginator import Page from django.db import models from django.forms import widgets @@ -114,6 +114,9 @@ class OrderedDictWithMetadata(OrderedDict): """ A sorted dict-like object, that can have additional properties attached. """ + def __reduce__(self): + return self.__class__, (OrderedDict(self), ) + def __getstate__(self): """ Used by pickle (e.g., caching). diff --git a/rest_framework/settings.py b/rest_framework/settings.py index 644751f87..c7830b665 100644 --- a/rest_framework/settings.py +++ b/rest_framework/settings.py @@ -19,9 +19,14 @@ back to the defaults. """ from __future__ import unicode_literals from django.conf import settings -from django.utils import importlib, six +from django.utils import six from rest_framework import ISO_8601 +try: + import importlib +except ImportError: + from django.utils import importlib + USER_SETTINGS = getattr(settings, 'REST_FRAMEWORK', None) From 1116a534d447998b46089a0e0e699e76a97fd87f Mon Sep 17 00:00:00 2001 From: kazmiruk Date: Wed, 22 Apr 2015 20:05:10 +0700 Subject: [PATCH 06/13] add compatibility for django 1.6 --- rest_framework/serializers.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index ceab6111f..34f9f6fcc 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -16,7 +16,7 @@ import datetime import inspect import types from decimal import Decimal -from django.contrib.contenttypes.fields import GenericForeignKey +import django from django.core.paginator import Page from django.db import models from django.forms import widgets @@ -41,6 +41,11 @@ try: except ImportError: from django.utils.datastructures import SortedDict as OrderedDict +if django.VERSION >= (1, 8): + from django.contrib.contenttypes.fields import GenericForeignKey +else: + from django.contrib.contenttypes.generic import GenericForeignKey + def _resolve_model(obj): """ From 21bac85489a03b0a481347cc3268640adf9f6ce8 Mon Sep 17 00:00:00 2001 From: kazmiruk Date: Wed, 22 Apr 2015 20:52:33 +0700 Subject: [PATCH 07/13] move compatibility checks into compat.py --- rest_framework/compat.py | 10 ++++++++++ rest_framework/fields.py | 7 +------ rest_framework/routers.py | 6 +----- rest_framework/serializers.py | 11 +---------- rest_framework/utils/encoders.py | 7 +------ rest_framework/views.py | 7 +------ 6 files changed, 15 insertions(+), 33 deletions(-) diff --git a/rest_framework/compat.py b/rest_framework/compat.py index fa0f0bfb1..29036b1de 100644 --- a/rest_framework/compat.py +++ b/rest_framework/compat.py @@ -265,3 +265,13 @@ except ImportError: klass.__unicode__ = klass.__str__ klass.__str__ = lambda self: self.__unicode__().encode('utf-8') return klass + +try: + from collections import OrderedDict +except ImportError: + from django.utils.datastructures import SortedDict as OrderedDict + +if django.VERSION >= (1, 8): + from django.contrib.contenttypes.fields import GenericForeignKey +else: + from django.contrib.contenttypes.generic import GenericForeignKey diff --git a/rest_framework/fields.py b/rest_framework/fields.py index 6088cdee7..9e95e6dc2 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -25,16 +25,11 @@ from django.utils.translation import ugettext_lazy as _ from django.utils.dateparse import parse_date, parse_datetime, parse_time from rest_framework import ISO_8601 from rest_framework.compat import ( - BytesIO, smart_text, + BytesIO, smart_text, OrderedDict, force_text, is_non_str_iterable ) from rest_framework.settings import api_settings -try: - from collections import OrderedDict -except ImportError: - from django.utils.datastructures import SortedDict as OrderedDict - def is_simple_callable(obj): """ diff --git a/rest_framework/routers.py b/rest_framework/routers.py index 4ef7707bb..6e99f14de 100644 --- a/rest_framework/routers.py +++ b/rest_framework/routers.py @@ -21,15 +21,11 @@ from django.conf.urls import patterns, url from django.core.exceptions import ImproperlyConfigured from django.core.urlresolvers import NoReverseMatch from rest_framework import views +from rest_framework.compat import OrderedDict from rest_framework.response import Response from rest_framework.reverse import reverse from rest_framework.urlpatterns import format_suffix_patterns -try: - from collections import OrderedDict -except ImportError: - from django.utils.datastructures import SortedDict as OrderedDict - Route = namedtuple('Route', ['url', 'mapping', 'name', 'initkwargs']) DynamicDetailRoute = namedtuple('DynamicDetailRoute', ['url', 'name', 'initkwargs']) diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index 34f9f6fcc..0b05ace48 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -23,6 +23,7 @@ from django.forms import widgets from django.utils import six from django.utils.functional import cached_property from django.core.exceptions import ObjectDoesNotExist +from rest_framework.compat import OrderedDict, GenericForeignKey from rest_framework.settings import api_settings @@ -36,16 +37,6 @@ from rest_framework.settings import api_settings from rest_framework.relations import * # NOQA from rest_framework.fields import * # NOQA -try: - from collections import OrderedDict -except ImportError: - from django.utils.datastructures import SortedDict as OrderedDict - -if django.VERSION >= (1, 8): - from django.contrib.contenttypes.fields import GenericForeignKey -else: - from django.contrib.contenttypes.generic import GenericForeignKey - def _resolve_model(obj): """ diff --git a/rest_framework/utils/encoders.py b/rest_framework/utils/encoders.py index c6ebdcffc..c2bb60c67 100644 --- a/rest_framework/utils/encoders.py +++ b/rest_framework/utils/encoders.py @@ -5,18 +5,13 @@ from __future__ import unicode_literals from django.utils import timezone from django.db.models.query import QuerySet from django.utils.functional import Promise -from rest_framework.compat import force_text +from rest_framework.compat import force_text, OrderedDict from rest_framework.serializers import DictWithMetadata, OrderedDictWithMetadata import datetime import decimal import types import json -try: - from collections import OrderedDict -except ImportError: - from django.utils.datastructures import SortedDict as OrderedDict - class JSONEncoder(json.JSONEncoder): """ diff --git a/rest_framework/views.py b/rest_framework/views.py index 3852d8e15..526931e60 100644 --- a/rest_framework/views.py +++ b/rest_framework/views.py @@ -7,17 +7,12 @@ from django.core.exceptions import PermissionDenied from django.http import Http404 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 from rest_framework.utils import formatting -try: - from collections import OrderedDict -except ImportError: - from django.utils.datastructures import SortedDict as OrderedDict - def get_view_name(view_cls, suffix=None): """ From 18f1f5784669a026c3c68b6ab7428854a5f9d061 Mon Sep 17 00:00:00 2001 From: kazmiruk Date: Wed, 22 Apr 2015 20:58:19 +0700 Subject: [PATCH 08/13] fixes flake8 warning --- rest_framework/serializers.py | 1 - 1 file changed, 1 deletion(-) diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index 0b05ace48..14d614e65 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -16,7 +16,6 @@ import datetime import inspect import types from decimal import Decimal -import django from django.core.paginator import Page from django.db import models from django.forms import widgets From be66e15c1c14855970c9b33b9c027e67ea31d9ea Mon Sep 17 00:00:00 2001 From: kazmiruk Date: Thu, 23 Apr 2015 16:29:39 +0700 Subject: [PATCH 09/13] renaming of OrderedDict back to SortedDict + some comments --- rest_framework/compat.py | 13 +++++++++++-- rest_framework/fields.py | 6 +++--- rest_framework/routers.py | 6 +++--- rest_framework/serializers.py | 31 ++++++++++++++++++------------- rest_framework/utils/encoders.py | 12 ++++++------ rest_framework/views.py | 4 ++-- tests/test_serializer.py | 4 ++-- 7 files changed, 45 insertions(+), 31 deletions(-) diff --git a/rest_framework/compat.py b/rest_framework/compat.py index 29036b1de..8532a0172 100644 --- a/rest_framework/compat.py +++ b/rest_framework/compat.py @@ -266,11 +266,20 @@ except ImportError: klass.__str__ = lambda self: self.__unicode__().encode('utf-8') return klass +""" +SortedDict deprecated since django 1.8. There is collections.OrderedDict +which available since python 2.7 and python 3.1. There are no need of other +checks because of django 1.7+ requires python 2.7+ +""" try: - from collections import OrderedDict + from collections import OrderedDict as SortedDict except ImportError: - from django.utils.datastructures import SortedDict as OrderedDict + from django.utils.datastructures import SortedDict +""" +GenericForeignKey moves from generic to fields in django 1.9 and in 1.8 shows +deprecation warnings +""" if django.VERSION >= (1, 8): from django.contrib.contenttypes.fields import GenericForeignKey else: diff --git a/rest_framework/fields.py b/rest_framework/fields.py index 9e95e6dc2..d310c5df3 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -25,7 +25,7 @@ from django.utils.translation import ugettext_lazy as _ from django.utils.dateparse import parse_date, parse_datetime, parse_time from rest_framework import ISO_8601 from rest_framework.compat import ( - BytesIO, smart_text, OrderedDict, + BytesIO, smart_text, SortedDict, force_text, is_non_str_iterable ) from rest_framework.settings import api_settings @@ -224,7 +224,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 = OrderedDict() + ret = SortedDict() for key, val in value.items(): ret[key] = self.to_native(val) return ret @@ -239,7 +239,7 @@ class Field(object): return {} def metadata(self): - metadata = OrderedDict() + metadata = SortedDict() metadata['type'] = self.type_label metadata['required'] = getattr(self, 'required', False) optional_attrs = ['read_only', 'label', 'help_text', diff --git a/rest_framework/routers.py b/rest_framework/routers.py index 6e99f14de..9e5813b39 100644 --- a/rest_framework/routers.py +++ b/rest_framework/routers.py @@ -21,7 +21,7 @@ from django.conf.urls import patterns, url from django.core.exceptions import ImproperlyConfigured from django.core.urlresolvers import NoReverseMatch from rest_framework import views -from rest_framework.compat import OrderedDict +from rest_framework.compat import SortedDict from rest_framework.response import Response from rest_framework.reverse import reverse from rest_framework.urlpatterns import format_suffix_patterns @@ -278,7 +278,7 @@ class DefaultRouter(SimpleRouter): """ Return a view to use as the API root. """ - api_root_dict = OrderedDict() + api_root_dict = SortedDict() list_name = self.routes[0].name for prefix, viewset, basename in self.registry: api_root_dict[prefix] = list_name.format(basename=basename) @@ -287,7 +287,7 @@ class DefaultRouter(SimpleRouter): _ignore_model_permissions = True def get(self, request, *args, **kwargs): - ret = OrderedDict() + ret = SortedDict() for key, url_name in api_root_dict.items(): try: ret[key] = reverse( diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index 14d614e65..79984526d 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -22,7 +22,7 @@ from django.forms import widgets from django.utils import six from django.utils.functional import cached_property from django.core.exceptions import ObjectDoesNotExist -from rest_framework.compat import OrderedDict, GenericForeignKey +from rest_framework.compat import SortedDict, GenericForeignKey from rest_framework.settings import api_settings @@ -105,20 +105,25 @@ class DictWithMetadata(dict): return dict(self) -class OrderedDictWithMetadata(OrderedDict): +class SortedDictWithMetadata(SortedDict): """ A sorted dict-like object, that can have additional properties attached. """ def __reduce__(self): - return self.__class__, (OrderedDict(self), ) - - def __getstate__(self): """ - Used by pickle (e.g., caching). + Used by pickle (e.g., caching) if OrderedDict is used instead of SortedDict Overriden to remove the metadata from the dict, since it shouldn't be pickle and may in some instances be unpickleable. """ - return OrderedDict(self).__dict__ + return self.__class__, (SortedDict(self), ) + + def __getstate__(self): + """ + Used by pickle (e.g., caching) in 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__ def _is_protected_type(obj): @@ -154,7 +159,7 @@ def _get_declared_fields(bases, attrs): if hasattr(base, 'base_fields'): fields = list(base.base_fields.items()) + fields - return OrderedDict(fields) + return SortedDict(fields) class SerializerMetaclass(type): @@ -182,7 +187,7 @@ class BaseSerializer(WritableField): pass _options_class = SerializerOptions - _dict_class = OrderedDictWithMetadata + _dict_class = SortedDictWithMetadata def __init__(self, instance=None, data=None, files=None, context=None, partial=False, many=False, @@ -231,7 +236,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 = OrderedDict() + ret = SortedDict() # Get the explicitly declared fields base_fields = copy.deepcopy(self.base_fields) @@ -247,7 +252,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 = OrderedDict() + new = SortedDict() for key in self.opts.fields: new[key] = ret[key] ret = new @@ -608,7 +613,7 @@ class BaseSerializer(WritableField): Useful for things like responding to OPTIONS requests, or generating API schemas for auto-documentation. """ - return OrderedDict( + return SortedDict( [ (field_name, field.metadata()) for field_name, field in six.iteritems(self.fields) @@ -685,7 +690,7 @@ class ModelSerializer(Serializer): self.__class__.__name__ ) opts = cls._meta.concrete_model._meta - ret = OrderedDict() + ret = SortedDict() nested = bool(self.opts.depth) # Deal with adding the primary key field diff --git a/rest_framework/utils/encoders.py b/rest_framework/utils/encoders.py index c2bb60c67..813108b77 100644 --- a/rest_framework/utils/encoders.py +++ b/rest_framework/utils/encoders.py @@ -5,8 +5,8 @@ from __future__ import unicode_literals from django.utils import timezone from django.db.models.query import QuerySet from django.utils.functional import Promise -from rest_framework.compat import force_text, OrderedDict -from rest_framework.serializers import DictWithMetadata, OrderedDictWithMetadata +from rest_framework.compat import force_text, SortedDict +from rest_framework.serializers import DictWithMetadata, SortedDictWithMetadata import datetime import decimal import types @@ -66,7 +66,7 @@ else: class SafeDumper(yaml.SafeDumper): """ Handles decimals as strings. - Handles OrderedDicts as usual dicts, but preserves field order, rather + Handles SortedDicts as usual dicts, but preserves field order, rather than the usual behaviour of sorting the keys. """ def represent_decimal(self, data): @@ -80,7 +80,7 @@ else: best_style = True if hasattr(mapping, 'items'): mapping = list(mapping.items()) - if not isinstance(mapping, OrderedDict): + if not isinstance(mapping, SortedDict): mapping.sort() for item_key, item_value in mapping: node_key = self.represent_data(item_key) @@ -102,7 +102,7 @@ else: SafeDumper.represent_decimal ) SafeDumper.add_representer( - OrderedDict, + SortedDict, yaml.representer.SafeRepresenter.represent_dict ) SafeDumper.add_representer( @@ -110,7 +110,7 @@ else: yaml.representer.SafeRepresenter.represent_dict ) SafeDumper.add_representer( - OrderedDictWithMetadata, + SortedDictWithMetadata, yaml.representer.SafeRepresenter.represent_dict ) SafeDumper.add_representer( diff --git a/rest_framework/views.py b/rest_framework/views.py index 526931e60..fb30775b1 100644 --- a/rest_framework/views.py +++ b/rest_framework/views.py @@ -7,7 +7,7 @@ from django.core.exceptions import PermissionDenied from django.http import Http404 from django.views.decorators.csrf import csrf_exempt from rest_framework import status, exceptions -from rest_framework.compat import smart_text, HttpResponseBase, OrderedDict, View +from rest_framework.compat import smart_text, HttpResponseBase, SortedDict, View from rest_framework.request import Request from rest_framework.response import Response from rest_framework.settings import api_settings @@ -420,7 +420,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 = OrderedDict() + ret = SortedDict() 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/tests/test_serializer.py b/tests/test_serializer.py index c0de5cf27..14e88e6d2 100644 --- a/tests/test_serializer.py +++ b/tests/test_serializer.py @@ -1297,7 +1297,7 @@ class SerializerPickleTests(TestCase): def test_pickle_inner_serializer(self): """ - Test pickling a serializer whose resulting .data (a OrderedDictWithMetadata) will + Test pickling a serializer whose resulting .data (a SortedDictWithMetadata) will have unpickleable meta data--in order to make sure metadata doesn't get pulled into the pickle. See DictWithMetadata.__getstate__ """ @@ -1318,7 +1318,7 @@ class SerializerPickleTests(TestCase): """ Another regression test for #645. """ - data = serializers.OrderedDictWithMetadata({1: 1}) + data = serializers.SortedDictWithMetadata({1: 1}) repr(pickle.loads(pickle.dumps(data, 0))) From 73e433ed5c6835e413a2ebabffedb16bd36f437b Mon Sep 17 00:00:00 2001 From: kazmiruk Date: Thu, 23 Apr 2015 16:32:36 +0700 Subject: [PATCH 10/13] fixes tests for renamed SortedDict --- tests/test_fields.py | 8 ++------ tests/test_serializer.py | 2 +- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/tests/test_fields.py b/tests/test_fields.py index 261ef521c..058ad7035 100644 --- a/tests/test_fields.py +++ b/tests/test_fields.py @@ -11,13 +11,9 @@ from django.core import validators from django.db import models from django.test import TestCase from rest_framework import serializers +from rest_framework.compat import SortedDict from tests.models import RESTFrameworkModel -try: - from collections import OrderedDict -except ImportError: - from django.utils.datastructures import SortedDict as OrderedDict - class TimestampedModel(models.Model): added = models.DateTimeField(auto_now_add=True) @@ -99,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 = OrderedDict() + ret = SortedDict() ret['c'] = 1 ret['b'] = 1 ret['a'] = 1 diff --git a/tests/test_serializer.py b/tests/test_serializer.py index 14e88e6d2..e72b723f0 100644 --- a/tests/test_serializer.py +++ b/tests/test_serializer.py @@ -1312,7 +1312,7 @@ class SerializerPickleTests(TestCase): Regression test for #645. """ data = serializers.DictWithMetadata({1: 1}) - self.assertEqual(data.__getstate__(), serializers.OrderedDict({1: 1})) + self.assertEqual(data.__getstate__(), serializers.SortedDict({1: 1})) def test_serializer_data_is_pickleable(self): """ From e3522e8aef2b7dd1e93bde246672226f4243f644 Mon Sep 17 00:00:00 2001 From: kazmiruk Date: Fri, 24 Apr 2015 22:58:14 +0700 Subject: [PATCH 11/13] move importlib to compat --- rest_framework/compat.py | 8 ++++++++ rest_framework/settings.py | 6 +----- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/rest_framework/compat.py b/rest_framework/compat.py index 8532a0172..8979339c1 100644 --- a/rest_framework/compat.py +++ b/rest_framework/compat.py @@ -284,3 +284,11 @@ if django.VERSION >= (1, 8): from django.contrib.contenttypes.fields import GenericForeignKey else: from django.contrib.contenttypes.generic import GenericForeignKey + +""" +django.utils.importlib is deprecated since django 1.8 +""" +try: + import importlib +except ImportError: + from django.utils import importlib diff --git a/rest_framework/settings.py b/rest_framework/settings.py index c7830b665..e77ec754f 100644 --- a/rest_framework/settings.py +++ b/rest_framework/settings.py @@ -21,11 +21,7 @@ from __future__ import unicode_literals from django.conf import settings from django.utils import six from rest_framework import ISO_8601 - -try: - import importlib -except ImportError: - from django.utils import importlib +from rest_framework.compat import importlib USER_SETTINGS = getattr(settings, 'REST_FRAMEWORK', None) From 7d8c95141cbbccd07f28406422dec11936714588 Mon Sep 17 00:00:00 2001 From: kazmiruk Date: Mon, 27 Apr 2015 10:02:05 +0700 Subject: [PATCH 12/13] remove list\tuple changes from PR --- rest_framework/serializers.py | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index 79984526d..e85100a1e 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -992,15 +992,12 @@ class ModelSerializer(Serializer): if field_name in attrs: m2m_data[field_name] = attrs.pop(field_name) - def _inner_loop_code(field): - if isinstance(field, GenericForeignKey): - return - if field.name in attrs: - m2m_data[field.name] = attrs.pop(field.name) - # Forward m2m relations - [_inner_loop_code(field) for field in meta.many_to_many] - [_inner_loop_code(field) for field in meta.virtual_fields] + for field in meta.many_to_many + meta.virtual_fields: + if isinstance(field, GenericForeignKey): + continue + if field.name in attrs: + m2m_data[field.name] = attrs.pop(field.name) # Nested forward relations - These need to be marked so we can save # them before saving the parent model instance. From fad0848b7c544afff1cf4d91e23397fa8c9779a9 Mon Sep 17 00:00:00 2001 From: kazmiruk Date: Mon, 27 Apr 2015 10:09:43 +0700 Subject: [PATCH 13/13] fix flake8 warnings --- rest_framework/serializers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index e85100a1e..0ce54731d 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -997,7 +997,7 @@ class ModelSerializer(Serializer): if isinstance(field, GenericForeignKey): continue if field.name in attrs: - m2m_data[field.name] = attrs.pop(field.name) + m2m_data[field.name] = attrs.pop(field.name) # Nested forward relations - These need to be marked so we can save # them before saving the parent model instance.