From 83f446fc475c8800298bbe5314659e82400a4709 Mon Sep 17 00:00:00 2001 From: Karol Sikora Date: Wed, 26 Nov 2014 16:06:21 +0100 Subject: [PATCH 01/32] Fixed get_component method in Field to get working with subclassess of collections.Mapping --- rest_framework/fields.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/rest_framework/fields.py b/rest_framework/fields.py index c0253f86b8..a2d2d5feba 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -10,6 +10,7 @@ import inspect import re import warnings +import collections from decimal import Decimal, DecimalException from django import forms from django.core import validators @@ -52,7 +53,7 @@ def get_component(obj, attr_name): Given an object, and an attribute name, return that attribute on the object. """ - if isinstance(obj, dict): + if isinstance(obj, collections.Mapping): val = obj.get(attr_name) else: val = getattr(obj, attr_name) From 9401eccbfab0ecd5f29414eab01e887c56e6e548 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Mon, 23 Mar 2015 11:20:09 +0000 Subject: [PATCH 02/32] Escape tab switching cookie --- rest_framework/static/rest_framework/js/default.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/rest_framework/static/rest_framework/js/default.js b/rest_framework/static/rest_framework/js/default.js index bcb1964dbe..f04e55696d 100644 --- a/rest_framework/static/rest_framework/js/default.js +++ b/rest_framework/static/rest_framework/js/default.js @@ -43,6 +43,10 @@ $('a[data-toggle="tab"]').click(function(){ var selectedTab = null; var selectedTabName = getCookie('tabstyle'); +if (selectedTabName) { + selectedTabName = selectedTabName.replace(/[^a-z-]/g, ''); +} + if (selectedTabName) { selectedTab = $('.form-switcher a[name=' + selectedTabName + ']'); } From f7fc754bc916ba41288328cc3d0434225739a4f0 Mon Sep 17 00:00:00 2001 From: Andy Freeland Date: Mon, 23 Mar 2015 12:01:54 -0400 Subject: [PATCH 03/32] Version bump and release notes update for 2.4.5 --- docs/topics/release-notes.md | 6 ++++++ rest_framework/__init__.py | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/docs/topics/release-notes.md b/docs/topics/release-notes.md index 11d12ae326..9879c4665f 100644 --- a/docs/topics/release-notes.md +++ b/docs/topics/release-notes.md @@ -40,6 +40,12 @@ You can determine your currently installed version using `pip freeze`: ## 2.4.x series +### 2.4.5 + +**Date**: 24 March 2015 + +* **Security fix**: Escape tab switching cookie name in browsable API. [Backported from 3.1.1](http://www.django-rest-framework.org/topics/release-notes/#311). + ### 2.4.4 **Date**: [3rd November 2014](https://github.com/tomchristie/django-rest-framework/issues?q=milestone%3A%222.4.4+Release%22+). diff --git a/rest_framework/__init__.py b/rest_framework/__init__.py index 15b12d9bea..5301909716 100644 --- a/rest_framework/__init__.py +++ b/rest_framework/__init__.py @@ -8,7 +8,7 @@ """ __title__ = 'Django REST framework' -__version__ = '2.4.4' +__version__ = '2.4.5' __author__ = 'Tom Christie' __license__ = 'BSD 2-Clause' __copyright__ = 'Copyright 2011-2014 Tom Christie' From 2a6f25c5f9d45cfe04e93e0421ff8982e29986f6 Mon Sep 17 00:00:00 2001 From: Andy Freeland Date: Tue, 24 Mar 2015 15:32:31 -0400 Subject: [PATCH 04/32] Pin flake8 2.4.0 and pep8 1.5.7 --- requirements-test.txt | 3 ++- tox.ini | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/requirements-test.txt b/requirements-test.txt index 411daeba2d..2880f5a987 100644 --- a/requirements-test.txt +++ b/requirements-test.txt @@ -2,7 +2,8 @@ pytest-django==2.6 pytest==2.5.2 pytest-cov==1.6 -flake8==2.2.2 +pep8==1.5.7 +flake8==2.4.0 # Optional packages markdown>=2.1.0 diff --git a/tox.ini b/tox.ini index b3f53cce28..0e17ca5116 100644 --- a/tox.ini +++ b/tox.ini @@ -15,7 +15,8 @@ setenv = [testenv:flake8] basepython = python2.7 deps = pytest==2.5.2 - flake8==2.2.2 + pep8==1.5.7 + flake8==2.4.0 commands = ./runtests.py --lintonly [testenv:py3.4-django1.7] From d1b883e1598dcf25a3104d5f4d91406074d46958 Mon Sep 17 00:00:00 2001 From: Riccardo Magliocchetti Date: Fri, 10 Apr 2015 09:59:18 +0200 Subject: [PATCH 05/32] Update 2.4 docs url Point to version 2 docs instead of the latest and greatest docs. --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 428fb8e9d1..469150e93c 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ **Awesome web-browseable Web APIs.** -**Note**: Full documentation for the project is available at [http://www.django-rest-framework.org][docs]. +**Note**: Full documentation for the project is available at [http://tomchristie.github.io/rest-framework-2-docs/][docs]. # Overview @@ -135,7 +135,7 @@ Or to create a new user: # Documentation & Support -Full documentation for the project is available at [http://www.django-rest-framework.org][docs]. +Full documentation for the project is available at [http://tomchristie.github.io/rest-framework-2-docs/][docs]. For questions and support, use the [REST framework discussion group][group], or `#restframework` on freenode IRC. From ee109c446a309382b94b62b626c385f292e28ac9 Mon Sep 17 00:00:00 2001 From: w- Date: Tue, 21 Apr 2015 18:19:33 -0700 Subject: [PATCH 06/32] increase serializer compatibility to django 1.8 i ran into this issue when using v2.4 with django v1.8. (i didn't previously read it isn't supported) It's not in the release notes but django.db.model.Options many_to_many() now returns an ImmutableList which is really just a tuple with a bunch of warnings and hooks on it. if we don't make this typecast change we get the following error TypeError: can only concatenate tuple (not "list") to tuple I'm not sure if this change is appropriate and not sure what, if any, additional tests to include with this . I attempted to test this but must be doing something wrong. Every test fails when trying to use cursor. most of the errors I get are Failed: Database access not allowed, use the "django_db" mark to enable i followed the instructions here. https://github.com/tomchristie/django-rest-framework/blob/version-2.4.x/CONTRIBUTING.md --- rest_framework/serializers.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index 7d85894f63..781c97dd81 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -986,7 +986,11 @@ def restore_object(self, attrs, instance=None): m2m_data[field_name] = attrs.pop(field_name) # Forward m2m relations - for field in meta.many_to_many + meta.virtual_fields: + if issubclass(meta.many_to_many.__class__, tuple): + temp_m2m = list(meta.many_to_many) + else: + temp_m2m = meta.many_to_many + for field in temp_m2m + meta.virtual_fields: if isinstance(field, GenericForeignKey): continue if field.name in attrs: From 5eacaecbec1b94baa332b40ccb60e275d85fcfd6 Mon Sep 17 00:00:00 2001 From: Riccardo Magliocchetti Date: Wed, 22 Apr 2015 09:37:23 +0200 Subject: [PATCH 07/32] Update 2.4 docs url for real Missed the important hunk --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 469150e93c..fd91064168 100644 --- a/README.md +++ b/README.md @@ -202,7 +202,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. [wlonk]: https://twitter.com/wlonk/status/261689665952833536 [laserllama]: https://twitter.com/laserllama/status/328688333750407168 -[docs]: http://www.django-rest-framework.org/ +[docs]: http://tomchristie.github.io/rest-framework-2-docs/ [urlobject]: https://github.com/zacharyvoase/urlobject [markdown]: http://pypi.python.org/pypi/Markdown/ [pyyaml]: http://pypi.python.org/pypi/PyYAML From 4a75a9c0cb0e2a4873e59ae596b1c41753299d28 Mon Sep 17 00:00:00 2001 From: Xavier Ordoquy Date: Wed, 22 Apr 2015 07:34:17 +0200 Subject: [PATCH 08/32] Version 2.4.5 Update release script. --- setup.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 2c56cd758f..f9c10456bc 100755 --- a/setup.py +++ b/setup.py @@ -59,8 +59,14 @@ def get_package_data(package): if sys.argv[-1] == 'publish': - os.system("python setup.py sdist upload") - os.system("python setup.py bdist_wheel upload") + if os.system("pip freeze | grep wheel"): + print("wheel not installed.\nUse `pip install wheel`.\nExiting.") + sys.exit() + if os.system("pip freeze | grep twine"): + print("twine not installed.\nUse `pip install twine`.\nExiting.") + sys.exit() + os.system("python setup.py sdist bdist_wheel") + os.system("twine upload dist/*") print("You probably want to also tag the version now:") print(" git tag -a %s -m 'version %s'" % (version, version)) print(" git push --tags") From 73978c95607f40f333ccfa3a9c202bde18e1d395 Mon Sep 17 00:00:00 2001 From: kazmiruk Date: Wed, 22 Apr 2015 13:12:12 +0700 Subject: [PATCH 09/32] 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 a2d2d5feba..aad49ed5ca 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -22,7 +22,6 @@ 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 @@ def to_native(self, value): 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 @@ def attributes(self): 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 169e6e8bc4..9937566d2b 100644 --- a/rest_framework/routers.py +++ b/rest_framework/routers.py @@ -16,11 +16,10 @@ 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 @@ def get_api_root_view(self): """ 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 APIRoot(views.APIView): _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 7d85894f63..29187c92cd 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -11,6 +11,7 @@ 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.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 @@ def __getstate__(self): return dict(self) -class SortedDictWithMetadata(SortedDict): +class OrderedDictWithMetadata(OrderedDict): """ A sorted dict-like object, that can have additional properties attached. """ @@ -116,7 +116,7 @@ def __getstate__(self): 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 Meta(object): 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 @@ def get_fields(self): 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 @@ def get_fields(self): # 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 @@ def metadata(self): 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 @@ def get_default_fields(self): 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 @@ def restore_object(self, attrs, instance=None): 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 00ffdfbae5..1e570cdde8 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 @@ def default(self, o): 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 @@ def represent_mapping(self, tag, mapping, flow_style=None): 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 @@ def represent_mapping(self, tag, mapping, flow_style=None): SafeDumper.represent_decimal ) SafeDumper.add_representer( - SortedDict, + OrderedDict, yaml.representer.SafeRepresenter.represent_dict ) SafeDumper.add_representer( @@ -111,7 +111,7 @@ def represent_mapping(self, tag, mapping, flow_style=None): 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 38346ab799..89b5921773 100644 --- a/rest_framework/views.py +++ b/rest_framework/views.py @@ -3,9 +3,9 @@ """ 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 @@ def metadata(self, request): # 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 10/32] 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 29187c92cd..ec8977a55b 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -992,8 +992,8 @@ def _inner_loop_code(field): 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 11/32] 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 0ddbe48b5b..8076762306 100644 --- a/tests/test_fields.py +++ b/tests/test_fields.py @@ -3,6 +3,7 @@ """ from __future__ import unicode_literals +from collections import OrderedDict import datetime import re from decimal import Decimal @@ -10,7 +11,6 @@ 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 @@ def test_dict_field_ordering(self): 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 e72b723f05..c0de5cf274 100644 --- a/tests/test_serializer.py +++ b/tests/test_serializer.py @@ -1297,7 +1297,7 @@ def test_pickle_simple_model_serializer_data(self): 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 @@ def test_getstate_method_should_not_return_none(self): 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 12/32] 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 aad49ed5ca..6088cdee72 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -30,6 +30,11 @@ ) 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 @@ def to_native(self, value): 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 @@ def attributes(self): 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 9937566d2b..4ef7707bbc 100644 --- a/rest_framework/routers.py +++ b/rest_framework/routers.py @@ -16,7 +16,7 @@ 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.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 ec8977a55b..d3d08f2ff7 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -11,7 +11,6 @@ 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.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 1e570cdde8..c6ebdcffca 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 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 89b5921773..3852d8e156 100644 --- a/rest_framework/views.py +++ b/rest_framework/views.py @@ -3,7 +3,6 @@ """ 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.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 8076762306..261ef521cb 100644 --- a/tests/test_fields.py +++ b/tests/test_fields.py @@ -3,7 +3,6 @@ """ from __future__ import unicode_literals -from collections import OrderedDict import datetime import re from decimal import Decimal @@ -14,6 +13,11 @@ 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 13/32] 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 d3d08f2ff7..ceab6111fa 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -16,7 +16,7 @@ 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 644751f877..c7830b6658 100644 --- a/rest_framework/settings.py +++ b/rest_framework/settings.py @@ -19,9 +19,14 @@ """ 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 14/32] 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 ceab6111fa..34f9f6fcc2 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -16,7 +16,7 @@ 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 @@ 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 15/32] 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 fa0f0bfb17..29036b1de6 100644 --- a/rest_framework/compat.py +++ b/rest_framework/compat.py @@ -265,3 +265,13 @@ def python_2_unicode_compatible(klass): 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 6088cdee72..9e95e6dc2a 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -25,16 +25,11 @@ 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 4ef7707bbc..6e99f14dec 100644 --- a/rest_framework/routers.py +++ b/rest_framework/routers.py @@ -21,15 +21,11 @@ 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 34f9f6fcc2..0b05ace488 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -23,6 +23,7 @@ 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.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 c6ebdcffca..c2bb60c672 100644 --- a/rest_framework/utils/encoders.py +++ b/rest_framework/utils/encoders.py @@ -5,18 +5,13 @@ 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 3852d8e156..526931e607 100644 --- a/rest_framework/views.py +++ b/rest_framework/views.py @@ -7,17 +7,12 @@ 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 16/32] 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 0b05ace488..14d614e65e 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -16,7 +16,6 @@ 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 17/32] 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 | 27 ++++++++++++++++----------- rest_framework/utils/encoders.py | 12 ++++++------ rest_framework/views.py | 4 ++-- tests/test_serializer.py | 4 ++-- 7 files changed, 43 insertions(+), 29 deletions(-) diff --git a/rest_framework/compat.py b/rest_framework/compat.py index 29036b1de6..8532a01725 100644 --- a/rest_framework/compat.py +++ b/rest_framework/compat.py @@ -266,11 +266,20 @@ def python_2_unicode_compatible(klass): 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 9e95e6dc2a..d310c5df36 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -25,7 +25,7 @@ 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 @@ def to_native(self, value): 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 @@ def attributes(self): 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 6e99f14dec..9e5813b398 100644 --- a/rest_framework/routers.py +++ b/rest_framework/routers.py @@ -21,7 +21,7 @@ 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 @@ def get_api_root_view(self): """ 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 APIRoot(views.APIView): _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 14d614e65e..79984526db 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -22,7 +22,7 @@ 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 @@ def __getstate__(self): 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), ) + """ + 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 self.__class__, (SortedDict(self), ) def __getstate__(self): """ - Used by pickle (e.g., caching). + 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 OrderedDict(self).__dict__ + 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 Meta(object): 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 @@ def get_fields(self): 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 @@ def get_fields(self): # 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 @@ def metadata(self): 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 @@ def get_default_fields(self): 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 c2bb60c672..813108b777 100644 --- a/rest_framework/utils/encoders.py +++ b/rest_framework/utils/encoders.py @@ -5,8 +5,8 @@ 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 @@ def default(self, o): 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 @@ def represent_mapping(self, tag, mapping, flow_style=None): 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 @@ def represent_mapping(self, tag, mapping, flow_style=None): SafeDumper.represent_decimal ) SafeDumper.add_representer( - OrderedDict, + SortedDict, yaml.representer.SafeRepresenter.represent_dict ) SafeDumper.add_representer( @@ -110,7 +110,7 @@ def represent_mapping(self, tag, mapping, flow_style=None): 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 526931e607..fb30775b19 100644 --- a/rest_framework/views.py +++ b/rest_framework/views.py @@ -7,7 +7,7 @@ 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 @@ def metadata(self, request): # 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 c0de5cf274..14e88e6d23 100644 --- a/tests/test_serializer.py +++ b/tests/test_serializer.py @@ -1297,7 +1297,7 @@ def test_pickle_simple_model_serializer_data(self): 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 @@ def test_serializer_data_is_pickleable(self): """ 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 18/32] 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 261ef521cb..058ad70355 100644 --- a/tests/test_fields.py +++ b/tests/test_fields.py @@ -11,13 +11,9 @@ 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 @@ def test_dict_field_ordering(self): 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 14e88e6d23..e72b723f05 100644 --- a/tests/test_serializer.py +++ b/tests/test_serializer.py @@ -1312,7 +1312,7 @@ def test_getstate_method_should_not_return_none(self): 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 19/32] 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 8532a01725..8979339c12 100644 --- a/rest_framework/compat.py +++ b/rest_framework/compat.py @@ -284,3 +284,11 @@ def python_2_unicode_compatible(klass): 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 c7830b6658..e77ec754fc 100644 --- a/rest_framework/settings.py +++ b/rest_framework/settings.py @@ -21,11 +21,7 @@ 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 20/32] remove list\tuple changes from PR --- rest_framework/serializers.py | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index 79984526db..e85100a1ea 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -992,15 +992,12 @@ def restore_object(self, attrs, instance=None): if field_name in attrs: m2m_data[field_name] = attrs.pop(field_name) - def _inner_loop_code(field): + # Forward m2m relations + for field in meta.many_to_many + meta.virtual_fields: if isinstance(field, GenericForeignKey): - return + continue 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] + 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 21/32] 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 e85100a1ea..0ce54731d4 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -997,7 +997,7 @@ def restore_object(self, attrs, instance=None): 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. From 86f7967a7a1d0dc1d9e02791526f6e2b8c60853f Mon Sep 17 00:00:00 2001 From: Xavier Ordoquy Date: Wed, 8 Jul 2015 08:49:59 +0200 Subject: [PATCH 22/32] Version 2.4.6 --- rest_framework/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rest_framework/__init__.py b/rest_framework/__init__.py index 5301909716..077bfb4cb8 100644 --- a/rest_framework/__init__.py +++ b/rest_framework/__init__.py @@ -8,7 +8,7 @@ """ __title__ = 'Django REST framework' -__version__ = '2.4.5' +__version__ = '2.4.6' __author__ = 'Tom Christie' __license__ = 'BSD 2-Clause' __copyright__ = 'Copyright 2011-2014 Tom Christie' From 152035aee73ae9d218a347cc966a4b9a69f4ed44 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Thu, 16 Jul 2015 16:46:07 +0100 Subject: [PATCH 23/32] Cherry picks Upgrade guardian support to 1.3. #3165 --- rest_framework/filters.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/rest_framework/filters.py b/rest_framework/filters.py index c580f9351b..2a05a8268a 100644 --- a/rest_framework/filters.py +++ b/rest_framework/filters.py @@ -188,4 +188,7 @@ def filter_queryset(self, request, queryset, view): 'model_name': get_model_name(model_cls) } permission = self.perm_format % kwargs - return guardian.shortcuts.get_objects_for_user(user, permission, queryset) + if guardian.VERSION >= (1, 3): + # Maintain behavior compatibility with versions prior to 1.3 + extra = {'accept_global_perms': False} + return guardian.shortcuts.get_objects_for_user(user, permission, queryset, **extra) From 59be95a0c0e81846833ab4acdc0938db45c49296 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Mon, 27 Jul 2015 10:20:37 +0100 Subject: [PATCH 24/32] Fix for DjangoObjectPermissionsFilter with Guardian < 1.3 --- rest_framework/filters.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rest_framework/filters.py b/rest_framework/filters.py index 2a05a8268a..18f4862e0f 100644 --- a/rest_framework/filters.py +++ b/rest_framework/filters.py @@ -191,4 +191,6 @@ def filter_queryset(self, request, queryset, view): if guardian.VERSION >= (1, 3): # Maintain behavior compatibility with versions prior to 1.3 extra = {'accept_global_perms': False} + else: + extra = {} return guardian.shortcuts.get_objects_for_user(user, permission, queryset, **extra) From 2c31f10f6e9216b62f64568898a9d4b4141e4954 Mon Sep 17 00:00:00 2001 From: Xavier Ordoquy Date: Sun, 16 Aug 2015 10:45:42 +0200 Subject: [PATCH 25/32] Release 2.4.7 --- docs/topics/release-notes.md | 9 +++++++++ rest_framework/__init__.py | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/docs/topics/release-notes.md b/docs/topics/release-notes.md index 9879c4665f..a45dddc7a3 100644 --- a/docs/topics/release-notes.md +++ b/docs/topics/release-notes.md @@ -40,6 +40,15 @@ You can determine your currently installed version using `pip freeze`: ## 2.4.x series +### 2.4.7 + +**Date**: [18 August 2015](https://github.com/tomchristie/django-rest-framework/issues?q=milestone%3A%222.4.7+Release%22+) + +* Upgrade guardian support to 1.3 + + +### 2.4.6 + ### 2.4.5 **Date**: 24 March 2015 diff --git a/rest_framework/__init__.py b/rest_framework/__init__.py index 077bfb4cb8..d128482687 100644 --- a/rest_framework/__init__.py +++ b/rest_framework/__init__.py @@ -8,7 +8,7 @@ """ __title__ = 'Django REST framework' -__version__ = '2.4.6' +__version__ = '2.4.7' __author__ = 'Tom Christie' __license__ = 'BSD 2-Clause' __copyright__ = 'Copyright 2011-2014 Tom Christie' From 647fdb51d203976a82a9cde1c7a535bd89c73135 Mon Sep 17 00:00:00 2001 From: Xavier Ordoquy Date: Tue, 18 Aug 2015 07:59:28 +0200 Subject: [PATCH 26/32] Release 2.4.8 --- docs/topics/release-notes.md | 7 +++++++ rest_framework/__init__.py | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/docs/topics/release-notes.md b/docs/topics/release-notes.md index a45dddc7a3..682d57b130 100644 --- a/docs/topics/release-notes.md +++ b/docs/topics/release-notes.md @@ -40,6 +40,13 @@ You can determine your currently installed version using `pip freeze`: ## 2.4.x series +### 2.4.8 + +**Date**: 18 August 2015 + +* Repackage 2.4.7 without pyc files. + + ### 2.4.7 **Date**: [18 August 2015](https://github.com/tomchristie/django-rest-framework/issues?q=milestone%3A%222.4.7+Release%22+) diff --git a/rest_framework/__init__.py b/rest_framework/__init__.py index d128482687..47ccda37a2 100644 --- a/rest_framework/__init__.py +++ b/rest_framework/__init__.py @@ -8,7 +8,7 @@ """ __title__ = 'Django REST framework' -__version__ = '2.4.7' +__version__ = '2.4.8' __author__ = 'Tom Christie' __license__ = 'BSD 2-Clause' __copyright__ = 'Copyright 2011-2014 Tom Christie' From 131e99baefaa935e90984246ada8b238e7208baa Mon Sep 17 00:00:00 2001 From: Sebastian Wozny Date: Mon, 31 Aug 2015 13:32:13 +0200 Subject: [PATCH 27/32] =?UTF-8?q?backport=20of=20#2492=20from=20tomchristi?= =?UTF-8?q?e=20ref=20#1850:=20=5Fclosable=5Fobjects=20as=20an=E2=80=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- rest_framework/response.py | 1 + 1 file changed, 1 insertion(+) mode change 100644 => 100755 rest_framework/response.py diff --git a/rest_framework/response.py b/rest_framework/response.py old mode 100644 new mode 100755 index 0a7d313f40..ba48648c9d --- a/rest_framework/response.py +++ b/rest_framework/response.py @@ -91,4 +91,5 @@ def __getstate__(self): for key in ('accepted_renderer', 'renderer_context', 'data'): if key in state: del state[key] + state['_closable_objects'] = [] return state From 044fefa420c1a7bec89a63117f90f4c402906093 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Hernas?= Date: Tue, 8 Mar 2016 19:54:30 +0100 Subject: [PATCH 28/32] Fixes for Django 1.9 --- rest_framework/response.py | 5 ++--- rest_framework/serializers.py | 5 ++++- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/rest_framework/response.py b/rest_framework/response.py index 0a7d313f40..3ec63f4033 100644 --- a/rest_framework/response.py +++ b/rest_framework/response.py @@ -6,10 +6,9 @@ """ from __future__ import unicode_literals import django -from django.core.handlers.wsgi import STATUS_CODE_TEXT from django.template.response import SimpleTemplateResponse from django.utils import six - +import httplib class Response(SimpleTemplateResponse): """ @@ -81,7 +80,7 @@ def status_text(self): """ # TODO: Deprecate and use a template tag instead # TODO: Status code text for RFC 6585 status codes - return STATUS_CODE_TEXT.get(self.status_code, '') + return httplib[self.status_code] def __getstate__(self): """ diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index aef815a888..c7a5de9872 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -1081,7 +1081,10 @@ def save_object(self, obj, **kwargs): self.save_object(related) else: # Reverse FK or reverse one-one - setattr(obj, accessor_name, related) + try: + setattr(obj, accessor_name, related) + except ValueError: + getattr(obj, accessor_name).add(*related, bulk=False) del(obj._related_data) From e26fe4356b453ef9b1c03000d380efcaadb936e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Hernas?= Date: Wed, 9 Mar 2016 16:03:56 +0100 Subject: [PATCH 29/32] Fixed comments --- .travis.yml | 3 ++- rest_framework/compat.py | 6 ++++++ rest_framework/response.py | 4 ++-- rest_framework/serializers.py | 8 ++++---- 4 files changed, 14 insertions(+), 7 deletions(-) diff --git a/.travis.yml b/.travis.yml index a5b6d7d918..7dec96cf16 100644 --- a/.travis.yml +++ b/.travis.yml @@ -22,7 +22,8 @@ env: - TOX_ENV=py2.6-django1.4 install: - - "pip install tox --download-cache $HOME/.pip-cache" +# Virtualenv < 14 is required to keep the Python 3.2 builds running. + - "pip install tox 'virtualenv<14' --download-cache $HOME/.pip-cache" script: - tox -e $TOX_ENV diff --git a/rest_framework/compat.py b/rest_framework/compat.py index 8979339c12..b0150f3d58 100644 --- a/rest_framework/compat.py +++ b/rest_framework/compat.py @@ -73,6 +73,12 @@ from collections import UserDict from collections import MutableMapping as DictMixin +# http responses move in Python 3 +try: + from httplib import responses +except ImportError: + from http.client import responses + # Try to import PIL in either of the two ways it can end up installed. try: from PIL import Image diff --git a/rest_framework/response.py b/rest_framework/response.py index 3ec63f4033..27098d75b2 100644 --- a/rest_framework/response.py +++ b/rest_framework/response.py @@ -8,7 +8,7 @@ import django from django.template.response import SimpleTemplateResponse from django.utils import six -import httplib +from rest_framework.compat import responses class Response(SimpleTemplateResponse): """ @@ -80,7 +80,7 @@ def status_text(self): """ # TODO: Deprecate and use a template tag instead # TODO: Status code text for RFC 6585 status codes - return httplib[self.status_code] + return responses.get(self.status_code, '') def __getstate__(self): """ diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index c7a5de9872..29df9299d4 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -1079,12 +1079,12 @@ def save_object(self, obj, **kwargs): fk_field = obj._meta.get_field_by_name(accessor_name)[0].field.name setattr(related, fk_field, obj) self.save_object(related) + elif isinstance(related, list): + # Many to One/Many + getattr(obj, accessor_name).add(*related, bulk=False) else: # Reverse FK or reverse one-one - try: - setattr(obj, accessor_name, related) - except ValueError: - getattr(obj, accessor_name).add(*related, bulk=False) + setattr(obj, accessor_name, related) del(obj._related_data) From 604b9004c64bf6487ed8a7ed65e8fc86967bde5b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Hernas?= Date: Wed, 9 Mar 2016 16:51:56 +0100 Subject: [PATCH 30/32] Fixed lint --- rest_framework/response.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rest_framework/response.py b/rest_framework/response.py index 27098d75b2..10ac4efef1 100644 --- a/rest_framework/response.py +++ b/rest_framework/response.py @@ -10,6 +10,7 @@ from django.utils import six from rest_framework.compat import responses + class Response(SimpleTemplateResponse): """ An HttpResponse that allows its data to be rendered into From a6c73c6f90230fe9d3156f1f2f9a11e0a3057966 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Hernas?= Date: Wed, 9 Mar 2016 17:39:28 +0100 Subject: [PATCH 31/32] Check for Django version --- rest_framework/serializers.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index 29df9299d4..67cf432e09 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -15,6 +15,9 @@ import datetime import inspect import types + +import django + from decimal import Decimal from django.core.paginator import Page from django.db import models @@ -1081,7 +1084,10 @@ def save_object(self, obj, **kwargs): self.save_object(related) elif isinstance(related, list): # Many to One/Many - getattr(obj, accessor_name).add(*related, bulk=False) + if django.VERSION >= (1, 9): + getattr(obj, accessor_name).add(*related, bulk=False) + else: + getattr(obj, accessor_name).add(*related) else: # Reverse FK or reverse one-one setattr(obj, accessor_name, related) From 79d7021821063b2995b8e6a7000e04f0ed27ca21 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Hernas?= Date: Thu, 17 Mar 2016 10:38:22 +0100 Subject: [PATCH 32/32] Bumped version --- rest_framework/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rest_framework/__init__.py b/rest_framework/__init__.py index 47ccda37a2..a15ed7cd49 100644 --- a/rest_framework/__init__.py +++ b/rest_framework/__init__.py @@ -8,7 +8,7 @@ """ __title__ = 'Django REST framework' -__version__ = '2.4.8' +__version__ = '2.4.9' __author__ = 'Tom Christie' __license__ = 'BSD 2-Clause' __copyright__ = 'Copyright 2011-2014 Tom Christie'