From 26ac2656e5e0b3d01a67551910113a305d2a2820 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Mon, 5 Jan 2015 16:20:15 +0000 Subject: Pass init arguments through to serializer from pagination serializer. Closes #2355. Normally a serializer won't need these arguments on __init__, but if a user has customized __init__ they may expect them to be available. --- rest_framework/pagination.py | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) (limited to 'rest_framework') diff --git a/rest_framework/pagination.py b/rest_framework/pagination.py index f31e5fa4..9c8dda8f 100644 --- a/rest_framework/pagination.py +++ b/rest_framework/pagination.py @@ -37,16 +37,13 @@ class PreviousPageField(serializers.Field): return replace_query_param(url, self.page_field, page) -class DefaultObjectSerializer(serializers.ReadOnlyField): +class DefaultObjectSerializer(serializers.Serializer): """ If no object serializer is specified, then this serializer will be applied as the default. """ - - def __init__(self, source=None, many=None, context=None): - # Note: Swallow context and many kwargs - only required for - # eg. ModelSerializer. - super(DefaultObjectSerializer, self).__init__(source=source) + def to_representation(self, value): + return value class BasePaginationSerializer(serializers.Serializer): @@ -74,10 +71,9 @@ class BasePaginationSerializer(serializers.Serializer): list_serializer_class = serializers.ListSerializer self.fields[results_field] = list_serializer_class( - child=object_serializer(), + child=object_serializer(*args, **kwargs), source='object_list' ) - self.fields[results_field].bind(field_name=results_field, parent=self) class PaginationSerializer(BasePaginationSerializer): -- cgit v1.2.3 From 07ad0474c0cef8f8e88d299eca9dffbe6d01c10d Mon Sep 17 00:00:00 2001 From: Ryan Gaffney Date: Tue, 6 Jan 2015 14:34:36 -0800 Subject: Fix compatibility comment regarding OrderedDict --- rest_framework/compat.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'rest_framework') diff --git a/rest_framework/compat.py b/rest_framework/compat.py index ba26a3cd..b1f6f2fa 100644 --- a/rest_framework/compat.py +++ b/rest_framework/compat.py @@ -36,7 +36,7 @@ def unicode_to_repr(value): # OrderedDict only available in Python 2.7. # This will always be the case in Django 1.7 and above, as these versions # no longer support Python 2.6. -# For Django <= 1.6 and Python 2.6 fall back to OrderedDict. +# For Django <= 1.6 and Python 2.6 fall back to SortedDict. try: from collections import OrderedDict except ImportError: -- cgit v1.2.3 From b7015ea8989d67617d276829ccb6a192362ee01f Mon Sep 17 00:00:00 2001 From: Xavier Ordoquy Date: Wed, 7 Jan 2015 19:11:17 +0100 Subject: Bumped the version to 3.0.3. --- rest_framework/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'rest_framework') diff --git a/rest_framework/__init__.py b/rest_framework/__init__.py index dec89b3e..fdcebb7b 100644 --- a/rest_framework/__init__.py +++ b/rest_framework/__init__.py @@ -8,7 +8,7 @@ ______ _____ _____ _____ __ """ __title__ = 'Django REST framework' -__version__ = '3.0.2' +__version__ = '3.0.3' __author__ = 'Tom Christie' __license__ = 'BSD 2-Clause' __copyright__ = 'Copyright 2011-2015 Tom Christie' -- cgit v1.2.3 From b33a6cbff16e5a28a1a696e2ac617303da181720 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Thu, 8 Jan 2015 14:16:58 +0000 Subject: Ensure urlparse is not publically exposed in compat.py - less chance of accidental conflict. --- rest_framework/compat.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'rest_framework') diff --git a/rest_framework/compat.py b/rest_framework/compat.py index b1f6f2fa..971dee9c 100644 --- a/rest_framework/compat.py +++ b/rest_framework/compat.py @@ -10,7 +10,7 @@ import inspect from django.core.exceptions import ImproperlyConfigured from django.utils.encoding import force_text -from django.utils.six.moves.urllib import parse as urlparse +from django.utils.six.moves.urllib.parse import urlparse as _urlparse from django.conf import settings from django.utils import six import django @@ -182,7 +182,7 @@ except ImportError: class RequestFactory(DjangoRequestFactory): def generic(self, method, path, data='', content_type='application/octet-stream', **extra): - parsed = urlparse.urlparse(path) + parsed = _urlparse(path) data = force_bytes_or_smart_bytes(data, settings.DEFAULT_CHARSET) r = { 'PATH_INFO': self._get_path(parsed), -- cgit v1.2.3 From 4d9e7a53565f6301b87999e6bafdb1c2c3c2af3b Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Thu, 8 Jan 2015 15:38:27 +0000 Subject: Ammend docstring to use python2/3 compatible example. --- rest_framework/settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'rest_framework') diff --git a/rest_framework/settings.py b/rest_framework/settings.py index 33f84813..fc6dfecd 100644 --- a/rest_framework/settings.py +++ b/rest_framework/settings.py @@ -167,7 +167,7 @@ class APISettings(object): For example: from rest_framework.settings import api_settings - print api_settings.DEFAULT_RENDERER_CLASSES + print(api_settings.DEFAULT_RENDERER_CLASSES) Any setting with string import paths will be automatically resolved and return the class, rather than the string literal. -- cgit v1.2.3 From d6bff10f9829b3cef1c2773c303b172a8c7ec525 Mon Sep 17 00:00:00 2001 From: Ask Holme Date: Sat, 10 Jan 2015 18:15:21 +0100 Subject: Make FileUploadParser work with standard django API Output from parsers ends up in a Django MergeDict and they exists elements to be dicts - not None--- rest_framework/parsers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'rest_framework') diff --git a/rest_framework/parsers.py b/rest_framework/parsers.py index 3e3395c0..401856ec 100644 --- a/rest_framework/parsers.py +++ b/rest_framework/parsers.py @@ -277,7 +277,7 @@ class FileUploadParser(BaseParser): for index, handler in enumerate(upload_handlers): file_obj = handler.file_complete(counters[index]) if file_obj: - return DataAndFiles(None, {'file': file_obj}) + return DataAndFiles({}, {'file': file_obj}) raise ParseError("FileUpload parse error - " "none of upload handlers can handle the stream") -- cgit v1.2.3 From d6d08db0dd16f4a4a93b69ecf1c5948f375335b0 Mon Sep 17 00:00:00 2001 From: José Padilla Date: Sun, 11 Jan 2015 10:55:56 -0400 Subject: Fix ident format when using HTTP_X_FORWARDED_FOR If NUM_PROXIES setting is set to None, HTTP_X_FORWARDED_FOR might be used as is, which might contain spaces and cause errors on cache backends like memcached. --- rest_framework/throttling.py | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) (limited to 'rest_framework') diff --git a/rest_framework/throttling.py b/rest_framework/throttling.py index 361dbddf..7dfe2f96 100644 --- a/rest_framework/throttling.py +++ b/rest_framework/throttling.py @@ -35,7 +35,7 @@ class BaseThrottle(object): client_addr = addrs[-min(num_proxies, len(xff))] return client_addr.strip() - return xff if xff else remote_addr + return ''.join(xff.split()) if xff else remote_addr def wait(self): """ @@ -173,12 +173,6 @@ class AnonRateThrottle(SimpleRateThrottle): if request.user.is_authenticated(): return None # Only throttle unauthenticated requests. - ident = request.META.get('HTTP_X_FORWARDED_FOR') - if ident is None: - ident = request.META.get('REMOTE_ADDR') - else: - ident = ''.join(ident.split()) - return self.cache_format % { 'scope': self.scope, 'ident': self.get_ident(request) -- cgit v1.2.3 From cc13ee0577fb3de9602da634ab9c835749da49c4 Mon Sep 17 00:00:00 2001 From: José Padilla Date: Mon, 12 Jan 2015 08:12:24 -0400 Subject: Fix error when NUM_PROXIES is greater than one --- rest_framework/throttling.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'rest_framework') diff --git a/rest_framework/throttling.py b/rest_framework/throttling.py index 7dfe2f96..0f10136d 100644 --- a/rest_framework/throttling.py +++ b/rest_framework/throttling.py @@ -32,7 +32,7 @@ class BaseThrottle(object): if num_proxies == 0 or xff is None: return remote_addr addrs = xff.split(',') - client_addr = addrs[-min(num_proxies, len(xff))] + client_addr = addrs[-min(num_proxies, len(addrs))] return client_addr.strip() return ''.join(xff.split()) if xff else remote_addr -- cgit v1.2.3 From 4ce4132e08ba7764f120c71eeebdbaefee281689 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Wed, 14 Jan 2015 12:56:03 +0000 Subject: Preserve ordering on relationship drop-down choices. Closes #2408. --- rest_framework/relations.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'rest_framework') diff --git a/rest_framework/relations.py b/rest_framework/relations.py index 7b119291..aa0c2def 100644 --- a/rest_framework/relations.py +++ b/rest_framework/relations.py @@ -7,6 +7,7 @@ from django.utils import six from django.utils.encoding import smart_text from django.utils.six.moves.urllib import parse as urlparse from django.utils.translation import ugettext_lazy as _ +from rest_framework.compat import OrderedDict from rest_framework.fields import get_attribute, empty, Field from rest_framework.reverse import reverse from rest_framework.utils import html @@ -103,7 +104,7 @@ class RelatedField(Field): @property def choices(self): - return dict([ + return OrderedDict([ ( six.text_type(self.to_representation(item)), six.text_type(item) @@ -364,7 +365,7 @@ class ManyRelatedField(Field): (item, self.child_relation.to_representation(item)) for item in iterable ] - return dict([ + return OrderedDict([ ( six.text_type(item_representation), six.text_type(item) + ' - ' + six.text_type(item_representation) -- cgit v1.2.3 From 5484d570cb8214c776273b45320e7d1c73b85a34 Mon Sep 17 00:00:00 2001 From: Fabien Bochu Date: Mon, 19 Jan 2015 10:57:26 +0100 Subject: Fix timedelta JSON serialization on Python 2.6. --- rest_framework/compat.py | 8 ++++++++ rest_framework/utils/encoders.py | 4 ++-- 2 files changed, 10 insertions(+), 2 deletions(-) (limited to 'rest_framework') diff --git a/rest_framework/compat.py b/rest_framework/compat.py index 971dee9c..17814136 100644 --- a/rest_framework/compat.py +++ b/rest_framework/compat.py @@ -33,6 +33,14 @@ def unicode_to_repr(value): return value +def total_seconds(timedelta): + # TimeDelta.total_seconds() is only available in Python 2.7 + if hasattr(timedelta, 'total_seconds'): + return timedelta.total_seconds() + else: + return (timedelta.days * 86400.0) + float(timedelta.seconds) + (timedelta.microseconds / 1000000.0) + + # OrderedDict only available in Python 2.7. # This will always be the case in Django 1.7 and above, as these versions # no longer support Python 2.6. diff --git a/rest_framework/utils/encoders.py b/rest_framework/utils/encoders.py index 73cbe5d8..104343a4 100644 --- a/rest_framework/utils/encoders.py +++ b/rest_framework/utils/encoders.py @@ -6,7 +6,7 @@ from django.db.models.query import QuerySet from django.utils import six, timezone from django.utils.encoding import force_text from django.utils.functional import Promise -from rest_framework.compat import OrderedDict +from rest_framework.compat import OrderedDict, total_seconds from rest_framework.utils.serializer_helpers import ReturnDict, ReturnList import datetime import decimal @@ -41,7 +41,7 @@ class JSONEncoder(json.JSONEncoder): representation = representation[:12] return representation elif isinstance(obj, datetime.timedelta): - return six.text_type(obj.total_seconds()) + return six.text_type(total_seconds(obj)) elif isinstance(obj, decimal.Decimal): # Serializers will coerce decimals to strings by default. return float(obj) -- cgit v1.2.3 From da1db34a36f6f0cb8722acbbf7c3182e32995ae3 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Mon, 19 Jan 2015 14:18:02 +0000 Subject: Handle UUID objects in JSONEncoder. Closes #2433. --- rest_framework/utils/encoders.py | 3 +++ 1 file changed, 3 insertions(+) (limited to 'rest_framework') diff --git a/rest_framework/utils/encoders.py b/rest_framework/utils/encoders.py index 104343a4..bf753271 100644 --- a/rest_framework/utils/encoders.py +++ b/rest_framework/utils/encoders.py @@ -12,6 +12,7 @@ import datetime import decimal import types import json +import uuid class JSONEncoder(json.JSONEncoder): @@ -45,6 +46,8 @@ class JSONEncoder(json.JSONEncoder): elif isinstance(obj, decimal.Decimal): # Serializers will coerce decimals to strings by default. return float(obj) + elif isinstance(obj, uuid.UUID): + return six.text_type(obj) elif isinstance(obj, QuerySet): return tuple(obj) elif hasattr(obj, 'tolist'): -- cgit v1.2.3 From 0dffc46cb71ec868cdbb37187ba03dea7bac4e7a Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Mon, 19 Jan 2015 14:21:09 +0000 Subject: ReturnDict and ReturnList repr as standard dict/list. Closes #2421. --- rest_framework/utils/serializer_helpers.py | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'rest_framework') diff --git a/rest_framework/utils/serializer_helpers.py b/rest_framework/utils/serializer_helpers.py index 65a04d06..f9960603 100644 --- a/rest_framework/utils/serializer_helpers.py +++ b/rest_framework/utils/serializer_helpers.py @@ -16,6 +16,9 @@ class ReturnDict(OrderedDict): def copy(self): return ReturnDict(self, serializer=self.serializer) + def __repr__(self): + return dict.__repr__(self) + class ReturnList(list): """ @@ -27,6 +30,9 @@ class ReturnList(list): self.serializer = kwargs.pop('serializer') super(ReturnList, self).__init__(*args, **kwargs) + def __repr__(self): + return list.__repr__(self) + class BoundField(object): """ -- cgit v1.2.3 From af05820b1bfc60ceb7cbb35566588a732938cae2 Mon Sep 17 00:00:00 2001 From: Alexander Dutton Date: Mon, 19 Jan 2015 14:23:13 +0000 Subject: NotImplemented is not an exception `NotImplemented` is a singleton object, not an exception. You should be raising `NotImplementedError` here instead.--- rest_framework/renderers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'rest_framework') diff --git a/rest_framework/renderers.py b/rest_framework/renderers.py index 634338e9..ba6c9cc1 100644 --- a/rest_framework/renderers.py +++ b/rest_framework/renderers.py @@ -46,7 +46,7 @@ class BaseRenderer(object): render_style = 'text' def render(self, data, accepted_media_type=None, renderer_context=None): - raise NotImplemented('Renderer class requires .render() to be implemented') + raise NotImplementedError('Renderer class requires .render() to be implemented') class JSONRenderer(BaseRenderer): -- cgit v1.2.3 From 4f3c3a06cfc0ea2dfbf46da2d98546664343ce93 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Mon, 19 Jan 2015 14:41:10 +0000 Subject: Drop trailing whitespace on indented JSON output. Closes #2429. --- rest_framework/compat.py | 2 ++ rest_framework/renderers.py | 8 ++++++-- 2 files changed, 8 insertions(+), 2 deletions(-) (limited to 'rest_framework') diff --git a/rest_framework/compat.py b/rest_framework/compat.py index 7241da27..ea342994 100644 --- a/rest_framework/compat.py +++ b/rest_framework/compat.py @@ -227,6 +227,8 @@ except ImportError: if six.PY3: SHORT_SEPARATORS = (',', ':') LONG_SEPARATORS = (', ', ': ') + INDENT_SEPARATORS = (',', ': ') else: SHORT_SEPARATORS = (b',', b':') LONG_SEPARATORS = (b', ', b': ') + INDENT_SEPARATORS = (b',', b': ') diff --git a/rest_framework/renderers.py b/rest_framework/renderers.py index 4c46b049..7af03c67 100644 --- a/rest_framework/renderers.py +++ b/rest_framework/renderers.py @@ -18,7 +18,7 @@ from django.template import Context, RequestContext, loader, Template from django.test.client import encode_multipart from django.utils import six from rest_framework import exceptions, serializers, status, VERSION -from rest_framework.compat import SHORT_SEPARATORS, LONG_SEPARATORS +from rest_framework.compat import SHORT_SEPARATORS, LONG_SEPARATORS, INDENT_SEPARATORS from rest_framework.exceptions import ParseError from rest_framework.settings import api_settings from rest_framework.request import is_form_media_type, override_method @@ -87,7 +87,11 @@ class JSONRenderer(BaseRenderer): renderer_context = renderer_context or {} indent = self.get_indent(accepted_media_type, renderer_context) - separators = SHORT_SEPARATORS if (indent is None and self.compact) else LONG_SEPARATORS + + if indent is None: + separators = SHORT_SEPARATORS if self.compact else LONG_SEPARATORS + else: + separators = INDENT_SEPARATORS ret = json.dumps( data, cls=self.encoder_class, -- cgit v1.2.3 From 46a3eda08d519c6908ecd1b9c2e13e5fdb773b0c Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Mon, 19 Jan 2015 14:48:13 +0000 Subject: NotImplemented -> NotImplementedError --- rest_framework/routers.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'rest_framework') diff --git a/rest_framework/routers.py b/rest_framework/routers.py index 61f3ccab..827da034 100644 --- a/rest_framework/routers.py +++ b/rest_framework/routers.py @@ -65,13 +65,13 @@ class BaseRouter(object): If `base_name` is not specified, attempt to automatically determine it from the viewset. """ - raise NotImplemented('get_default_base_name must be overridden') + raise NotImplementedError('get_default_base_name must be overridden') def get_urls(self): """ Return a list of URL patterns, given the registered viewsets. """ - raise NotImplemented('get_urls must be overridden') + raise NotImplementedError('get_urls must be overridden') @property def urls(self): -- cgit v1.2.3 From 3cc39ffbceffc5fdbb511d9a10e7732329e8baa4 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Mon, 19 Jan 2015 15:22:38 +0000 Subject: NotImplemented -> NotImplementedError --- rest_framework/pagination.py | 6 +++--- rest_framework/versioning.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) (limited to 'rest_framework') diff --git a/rest_framework/pagination.py b/rest_framework/pagination.py index 1b7524c6..55c173df 100644 --- a/rest_framework/pagination.py +++ b/rest_framework/pagination.py @@ -132,13 +132,13 @@ class BasePagination(object): display_page_controls = False def paginate_queryset(self, queryset, request, view=None): # pragma: no cover - raise NotImplemented('paginate_queryset() must be implemented.') + raise NotImplementedError('paginate_queryset() must be implemented.') def get_paginated_response(self, data): # pragma: no cover - raise NotImplemented('get_paginated_response() must be implemented.') + raise NotImplementedError('get_paginated_response() must be implemented.') def to_html(self): # pragma: no cover - raise NotImplemented('to_html() must be implemented to display page controls.') + raise NotImplementedError('to_html() must be implemented to display page controls.') class PageNumberPagination(BasePagination): diff --git a/rest_framework/versioning.py b/rest_framework/versioning.py index e31c71e9..a07b629f 100644 --- a/rest_framework/versioning.py +++ b/rest_framework/versioning.py @@ -17,7 +17,7 @@ class BaseVersioning(object): def determine_version(self, request, *args, **kwargs): msg = '{cls}.determine_version() must be implemented.' - raise NotImplemented(msg.format( + raise NotImplementedError(msg.format( cls=self.__class__.__name__ )) -- cgit v1.2.3