From 3fcc01273c5efef26d911e50c02a4a43f89b34eb Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Thu, 27 Jun 2013 20:29:52 +0100 Subject: Remove deprecated code --- rest_framework/compat.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'rest_framework/compat.py') diff --git a/rest_framework/compat.py b/rest_framework/compat.py index b748dcc5..161fffa8 100644 --- a/rest_framework/compat.py +++ b/rest_framework/compat.py @@ -494,11 +494,14 @@ try: if provider_version in ('0.2.3', '0.2.4'): # 0.2.3 and 0.2.4 are supported version that do not support # timezone aware datetimes - from datetime.datetime import now as provider_now + import datetime + provider_now = datetime.datetime.now else: # Any other supported version does use timezone aware datetimes from django.utils.timezone import now as provider_now except ImportError: + import traceback + traceback.print_exc() oauth2_provider = None oauth2_provider_models = None oauth2_provider_forms = None -- cgit v1.2.3 From 44ceef841543877a700c3fb4a0f84dfecbad0cbb Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Wed, 21 Aug 2013 21:30:25 +0100 Subject: Updating 2.4.0 release notes --- rest_framework/compat.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'rest_framework/compat.py') diff --git a/rest_framework/compat.py b/rest_framework/compat.py index baee3a9c..178a697f 100644 --- a/rest_framework/compat.py +++ b/rest_framework/compat.py @@ -14,7 +14,7 @@ from django.conf import settings try: from django.utils import six except ImportError: - from rest_framework import six + import six # location of patterns, url, include changes in 1.4 onwards try: -- cgit v1.2.3 From f631f55f8ebdf3d4e478aa5ca435ad36e86bee0f Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Wed, 21 Aug 2013 21:35:17 +0100 Subject: Tweak comment --- rest_framework/compat.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'rest_framework/compat.py') diff --git a/rest_framework/compat.py b/rest_framework/compat.py index 178a697f..66be96a6 100644 --- a/rest_framework/compat.py +++ b/rest_framework/compat.py @@ -10,7 +10,7 @@ import django from django.core.exceptions import ImproperlyConfigured from django.conf import settings -# Try to import six from Django, fallback to included `six`. +# Try to import six from Django, fallback to external `six` package. try: from django.utils import six except ImportError: -- cgit v1.2.3 From 4a9dcfa76089143bbeb5cd43fa3a406365d89e96 Mon Sep 17 00:00:00 2001 From: bwreilly Date: Fri, 6 Sep 2013 11:01:31 -0500 Subject: added guardian as optional requirement, stubbed out object-level permission class --- rest_framework/compat.py | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'rest_framework/compat.py') diff --git a/rest_framework/compat.py b/rest_framework/compat.py index 6f7447ad..b9d1dae6 100644 --- a/rest_framework/compat.py +++ b/rest_framework/compat.py @@ -47,6 +47,12 @@ try: except ImportError: django_filters = None +# guardian is optional +try: + import guardian +except ImportError: + guardian = None + # cStringIO only if it's available, otherwise StringIO try: -- cgit v1.2.3 From abbe9213f98b5e1d3b53db2c1711d9221c5b257f Mon Sep 17 00:00:00 2001 From: Markus Kaiserswerth Date: Mon, 23 Sep 2013 17:48:25 +0200 Subject: Address pending deprecation of Model._meta.module_name in Django 1.6 --- rest_framework/compat.py | 8 ++++++++ 1 file changed, 8 insertions(+) (limited to 'rest_framework/compat.py') diff --git a/rest_framework/compat.py b/rest_framework/compat.py index b9d1dae6..581e29fc 100644 --- a/rest_framework/compat.py +++ b/rest_framework/compat.py @@ -80,6 +80,14 @@ except ImportError: Image = None +def get_model_name(model_cls): + try: + return model_cls._meta.model_name + except AttributeError: + # < 1.6 used module_name instead of model_name + return model_cls._meta.module_name + + def get_concrete_model(model_cls): try: return model_cls._meta.concrete_model -- cgit v1.2.3 From e441f85109e64345a12e65062fc0e51c5787e67f Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Wed, 25 Sep 2013 10:30:04 +0100 Subject: Drop 1.3 support --- rest_framework/compat.py | 383 +++-------------------------------------------- 1 file changed, 20 insertions(+), 363 deletions(-) (limited to 'rest_framework/compat.py') diff --git a/rest_framework/compat.py b/rest_framework/compat.py index 1238f043..f048b10a 100644 --- a/rest_framework/compat.py +++ b/rest_framework/compat.py @@ -5,25 +5,19 @@ versions of django/python, and compatibility wrappers around optional packages. # flake8: noqa from __future__ import unicode_literals - import django from django.core.exceptions import ImproperlyConfigured from django.conf import settings + # Try to import six from Django, fallback to external `six` package. try: from django.utils import six except ImportError: import six -# location of patterns, url, include changes in 1.4 onwards -try: - from django.conf.urls import patterns, url, include -except ImportError: - from django.conf.urls.defaults import patterns, url, include - -# Handle django.utils.encoding rename: -# smart_unicode -> smart_text +# Handle django.utils.encoding rename in 1.5 onwards. +# smart_unicode -> smart_text # force_unicode -> force_text try: from django.utils.encoding import smart_text @@ -41,13 +35,15 @@ try: except ImportError: from django.http import HttpResponse as HttpResponseBase + # django-filter is optional try: import django_filters except ImportError: django_filters = None -# guardian is optional + +# django-guardian is optional try: import guardian except ImportError: @@ -80,14 +76,6 @@ except ImportError: Image = None -def get_concrete_model(model_cls): - try: - return model_cls._meta.concrete_model - except AttributeError: - # 1.3 does not include concrete model - return model_cls - - # Django 1.5 add support for custom auth user model if django.VERSION >= (1, 5): AUTH_USER_MODEL = settings.AUTH_USER_MODEL @@ -95,46 +83,13 @@ else: AUTH_USER_MODEL = 'auth.User' +# View._allowed_methods only present from 1.5 onwards if django.VERSION >= (1, 5): from django.views.generic import View else: - from django.views.generic import View as _View - from django.utils.decorators import classonlymethod - from django.utils.functional import update_wrapper - - class View(_View): - # 1.3 does not include head method in base View class - # See: https://code.djangoproject.com/ticket/15668 - @classonlymethod - def as_view(cls, **initkwargs): - """ - Main entry point for a request-response process. - """ - # sanitize keyword arguments - for key in initkwargs: - if key in cls.http_method_names: - raise TypeError("You tried to pass in the %s method name as a " - "keyword argument to %s(). Don't do that." - % (key, cls.__name__)) - if not hasattr(cls, key): - raise TypeError("%s() received an invalid keyword %r" % ( - cls.__name__, key)) - - def view(request, *args, **kwargs): - self = cls(**initkwargs) - if hasattr(self, 'get') and not hasattr(self, 'head'): - self.head = self.get - return self.dispatch(request, *args, **kwargs) - - # take name and docstring from class - update_wrapper(view, cls, updated=()) - - # and possible attributes set by decorators - # like csrf_exempt from dispatch - update_wrapper(view, cls.dispatch, assigned=()) - return view - - # _allowed_methods only present from 1.5 onwards + from django.views.generic import View as DjangoView + + class View(DjangoView): def _allowed_methods(self): return [m.upper() for m in self.http_method_names if hasattr(self, m)] @@ -144,316 +99,16 @@ if 'patch' not in View.http_method_names: View.http_method_names = View.http_method_names + ['patch'] -# PUT, DELETE do not require CSRF until 1.4. They should. Make it better. -if django.VERSION >= (1, 4): - from django.middleware.csrf import CsrfViewMiddleware -else: - import hashlib - import re - import random - import logging - - from django.conf import settings - from django.core.urlresolvers import get_callable - - try: - from logging import NullHandler - except ImportError: - class NullHandler(logging.Handler): - def emit(self, record): - pass - - logger = logging.getLogger('django.request') - - if not logger.handlers: - logger.addHandler(NullHandler()) - - def same_origin(url1, url2): - """ - Checks if two URLs are 'same-origin' - """ - p1, p2 = urlparse.urlparse(url1), urlparse.urlparse(url2) - return p1[0:2] == p2[0:2] - - def constant_time_compare(val1, val2): - """ - Returns True if the two strings are equal, False otherwise. - - The time taken is independent of the number of characters that match. - """ - if len(val1) != len(val2): - return False - result = 0 - for x, y in zip(val1, val2): - result |= ord(x) ^ ord(y) - return result == 0 - - # Use the system (hardware-based) random number generator if it exists. - if hasattr(random, 'SystemRandom'): - randrange = random.SystemRandom().randrange - else: - randrange = random.randrange - - _MAX_CSRF_KEY = 18446744073709551616 # 2 << 63 - - REASON_NO_REFERER = "Referer checking failed - no Referer." - REASON_BAD_REFERER = "Referer checking failed - %s does not match %s." - REASON_NO_CSRF_COOKIE = "CSRF cookie not set." - REASON_BAD_TOKEN = "CSRF token missing or incorrect." - - def _get_failure_view(): - """ - Returns the view to be used for CSRF rejections - """ - return get_callable(settings.CSRF_FAILURE_VIEW) - - def _get_new_csrf_key(): - return hashlib.md5("%s%s" % (randrange(0, _MAX_CSRF_KEY), settings.SECRET_KEY)).hexdigest() - - def get_token(request): - """ - Returns the the CSRF token required for a POST form. The token is an - alphanumeric value. - - A side effect of calling this function is to make the the csrf_protect - decorator and the CsrfViewMiddleware add a CSRF cookie and a 'Vary: Cookie' - header to the outgoing response. For this reason, you may need to use this - function lazily, as is done by the csrf context processor. - """ - request.META["CSRF_COOKIE_USED"] = True - return request.META.get("CSRF_COOKIE", None) - - def _sanitize_token(token): - # Allow only alphanum, and ensure we return a 'str' for the sake of the post - # processing middleware. - token = re.sub('[^a-zA-Z0-9]', '', str(token.decode('ascii', 'ignore'))) - if token == "": - # In case the cookie has been truncated to nothing at some point. - return _get_new_csrf_key() - else: - return token - - class CsrfViewMiddleware(object): - """ - Middleware that requires a present and correct csrfmiddlewaretoken - for POST requests that have a CSRF cookie, and sets an outgoing - CSRF cookie. - - This middleware should be used in conjunction with the csrf_token template - tag. - """ - # The _accept and _reject methods currently only exist for the sake of the - # requires_csrf_token decorator. - def _accept(self, request): - # Avoid checking the request twice by adding a custom attribute to - # request. This will be relevant when both decorator and middleware - # are used. - request.csrf_processing_done = True - return None - - def _reject(self, request, reason): - return _get_failure_view()(request, reason=reason) - - def process_view(self, request, callback, callback_args, callback_kwargs): - - if getattr(request, 'csrf_processing_done', False): - return None - - try: - csrf_token = _sanitize_token(request.COOKIES[settings.CSRF_COOKIE_NAME]) - # Use same token next time - request.META['CSRF_COOKIE'] = csrf_token - except KeyError: - csrf_token = None - # Generate token and store it in the request, so it's available to the view. - request.META["CSRF_COOKIE"] = _get_new_csrf_key() - - # Wait until request.META["CSRF_COOKIE"] has been manipulated before - # bailing out, so that get_token still works - if getattr(callback, 'csrf_exempt', False): - return None - - # Assume that anything not defined as 'safe' by RC2616 needs protection. - if request.method not in ('GET', 'HEAD', 'OPTIONS', 'TRACE'): - if getattr(request, '_dont_enforce_csrf_checks', False): - # Mechanism to turn off CSRF checks for test suite. It comes after - # the creation of CSRF cookies, so that everything else continues to - # work exactly the same (e.g. cookies are sent etc), but before the - # any branches that call reject() - return self._accept(request) - - if request.is_secure(): - # Suppose user visits http://example.com/ - # An active network attacker,(man-in-the-middle, MITM) sends a - # POST form which targets https://example.com/detonate-bomb/ and - # submits it via javascript. - # - # The attacker will need to provide a CSRF cookie and token, but - # that is no problem for a MITM and the session independent - # nonce we are using. So the MITM can circumvent the CSRF - # protection. This is true for any HTTP connection, but anyone - # using HTTPS expects better! For this reason, for - # https://example.com/ we need additional protection that treats - # http://example.com/ as completely untrusted. Under HTTPS, - # Barth et al. found that the Referer header is missing for - # same-domain requests in only about 0.2% of cases or less, so - # we can use strict Referer checking. - referer = request.META.get('HTTP_REFERER') - if referer is None: - logger.warning('Forbidden (%s): %s' % (REASON_NO_REFERER, request.path), - extra={ - 'status_code': 403, - 'request': request, - } - ) - return self._reject(request, REASON_NO_REFERER) - - # Note that request.get_host() includes the port - good_referer = 'https://%s/' % request.get_host() - if not same_origin(referer, good_referer): - reason = REASON_BAD_REFERER % (referer, good_referer) - logger.warning('Forbidden (%s): %s' % (reason, request.path), - extra={ - 'status_code': 403, - 'request': request, - } - ) - return self._reject(request, reason) - - if csrf_token is None: - # No CSRF cookie. For POST requests, we insist on a CSRF cookie, - # and in this way we can avoid all CSRF attacks, including login - # CSRF. - logger.warning('Forbidden (%s): %s' % (REASON_NO_CSRF_COOKIE, request.path), - extra={ - 'status_code': 403, - 'request': request, - } - ) - return self._reject(request, REASON_NO_CSRF_COOKIE) - - # check non-cookie token for match - request_csrf_token = "" - if request.method == "POST": - request_csrf_token = request.POST.get('csrfmiddlewaretoken', '') - - if request_csrf_token == "": - # Fall back to X-CSRFToken, to make things easier for AJAX, - # and possible for PUT/DELETE - request_csrf_token = request.META.get('HTTP_X_CSRFTOKEN', '') - - if not constant_time_compare(request_csrf_token, csrf_token): - logger.warning('Forbidden (%s): %s' % (REASON_BAD_TOKEN, request.path), - extra={ - 'status_code': 403, - 'request': request, - } - ) - return self._reject(request, REASON_BAD_TOKEN) - - return self._accept(request) - -# timezone support is new in Django 1.4 -try: - from django.utils import timezone -except ImportError: - timezone = None - -# dateparse is ALSO new in Django 1.4 -try: - from django.utils.dateparse import parse_date, parse_datetime, parse_time -except ImportError: - import datetime - import re - - date_re = re.compile( - r'(?P\d{4})-(?P\d{1,2})-(?P\d{1,2})$' - ) - - datetime_re = re.compile( - r'(?P\d{4})-(?P\d{1,2})-(?P\d{1,2})' - r'[T ](?P\d{1,2}):(?P\d{1,2})' - r'(?::(?P\d{1,2})(?:\.(?P\d{1,6})\d{0,6})?)?' - r'(?PZ|[+-]\d{1,2}:\d{1,2})?$' - ) - - time_re = re.compile( - r'(?P\d{1,2}):(?P\d{1,2})' - r'(?::(?P\d{1,2})(?:\.(?P\d{1,6})\d{0,6})?)?' - ) - - def parse_date(value): - match = date_re.match(value) - if match: - kw = dict((k, int(v)) for k, v in match.groupdict().iteritems()) - return datetime.date(**kw) - - def parse_time(value): - match = time_re.match(value) - if match: - kw = match.groupdict() - if kw['microsecond']: - kw['microsecond'] = kw['microsecond'].ljust(6, '0') - kw = dict((k, int(v)) for k, v in kw.iteritems() if v is not None) - return datetime.time(**kw) - - def parse_datetime(value): - """Parse datetime, but w/o the timezone awareness in 1.4""" - match = datetime_re.match(value) - if match: - kw = match.groupdict() - if kw['microsecond']: - kw['microsecond'] = kw['microsecond'].ljust(6, '0') - kw = dict((k, int(v)) for k, v in kw.iteritems() if v is not None) - return datetime.datetime(**kw) - - -# smart_urlquote is new on Django 1.4 -try: - from django.utils.html import smart_urlquote -except ImportError: - import re - from django.utils.encoding import smart_str - try: - from urllib.parse import quote, urlsplit, urlunsplit - except ImportError: # Python 2 - from urllib import quote - from urlparse import urlsplit, urlunsplit - - unquoted_percents_re = re.compile(r'%(?![0-9A-Fa-f]{2})') - - def smart_urlquote(url): - "Quotes a URL if it isn't already quoted." - # Handle IDN before quoting. - scheme, netloc, path, query, fragment = urlsplit(url) - try: - netloc = netloc.encode('idna').decode('ascii') # IDN -> ACE - except UnicodeError: # invalid domain part - pass - else: - url = urlunsplit((scheme, netloc, path, query, fragment)) - - # An URL is considered unquoted if it contains no % characters or - # contains a % not followed by two hexadecimal digits. See #9655. - if '%' not in url or unquoted_percents_re.search(url): - # See http://bugs.python.org/issue2637 - url = quote(smart_str(url), safe=b'!*\'();:@&=+$,/?#[]~') - - return force_text(url) - - -# RequestFactory only provide `generic` from 1.5 onwards - +# RequestFactory only provides `generic` from 1.5 onwards from django.test.client import RequestFactory as DjangoRequestFactory from django.test.client import FakePayload try: # In 1.5 the test client uses force_bytes from django.utils.encoding import force_bytes_or_smart_bytes except ImportError: - # In 1.3 and 1.4 the test client just uses smart_str + # In 1.4 the test client just uses smart_str from django.utils.encoding import smart_str as force_bytes_or_smart_bytes - class RequestFactory(DjangoRequestFactory): def generic(self, method, path, data='', content_type='application/octet-stream', **extra): @@ -478,6 +133,7 @@ class RequestFactory(DjangoRequestFactory): r.update(extra) return self.request(**r) + # Markdown is optional try: import markdown @@ -492,7 +148,6 @@ try: safe_mode = False md = markdown.Markdown(extensions=extensions, safe_mode=safe_mode) return md.convert(text) - except ImportError: apply_markdown = None @@ -510,14 +165,16 @@ try: except ImportError: etree = None -# OAuth is optional + +# OAuth2 is optional try: # Note: The `oauth2` package actually provides oauth1.0a support. Urg. import oauth2 as oauth except ImportError: oauth = None -# OAuth is optional + +# OAuthProvider is optional try: import oauth_provider from oauth_provider.store import store as oauth_provider_store @@ -525,6 +182,7 @@ except (ImportError, ImproperlyConfigured): oauth_provider = None oauth_provider_store = None + # OAuth 2 support is optional try: import provider.oauth2 as oauth2_provider @@ -542,8 +200,6 @@ try: # Any other supported version does use timezone aware datetimes from django.utils.timezone import now as provider_now except ImportError: - import traceback - traceback.print_exc() oauth2_provider = None oauth2_provider_models = None oauth2_provider_forms = None @@ -551,7 +207,8 @@ except ImportError: oauth2_constants = None provider_now = None -# Handle lazy strings + +# Handle lazy strings across Py2/Py3 from django.utils.functional import Promise if six.PY3: -- cgit v1.2.3 From 1bd8fe415296739521fd2e75c0b604cbf3dd3a83 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Wed, 25 Sep 2013 10:36:08 +0100 Subject: Whitespace fix --- rest_framework/compat.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'rest_framework/compat.py') diff --git a/rest_framework/compat.py b/rest_framework/compat.py index f048b10a..efd2581f 100644 --- a/rest_framework/compat.py +++ b/rest_framework/compat.py @@ -17,7 +17,7 @@ except ImportError: import six # Handle django.utils.encoding rename in 1.5 onwards. -# smart_unicode -> smart_text +# smart_unicode -> smart_text # force_unicode -> force_text try: from django.utils.encoding import smart_text -- cgit v1.2.3 From ddd17c69e7abdd70448fa0f2f2a807d600b3391d Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Mon, 9 Dec 2013 09:24:10 +0000 Subject: Fix compat issues for #1231 --- rest_framework/compat.py | 7 +++++++ 1 file changed, 7 insertions(+) (limited to 'rest_framework/compat.py') diff --git a/rest_framework/compat.py b/rest_framework/compat.py index 581e29fc..05bd99e0 100644 --- a/rest_framework/compat.py +++ b/rest_framework/compat.py @@ -69,6 +69,13 @@ try: except ImportError: import urlparse +# UserDict moves in Python 3 +try: + from UserDict import UserDict + from UserDict import DictMixin +except ImportError: + from collections import UserDict + from collections import MutableMapping as DictMixin # Try to import PIL in either of the two ways it can end up installed. try: -- cgit v1.2.3 From a87c55a93a7ca380bc49425cc6df00f4eba99aa2 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Fri, 13 Dec 2013 21:57:07 +0000 Subject: Compat fixes for django-oauth-plus versions 2.0-2.2.1 --- rest_framework/compat.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) (limited to 'rest_framework/compat.py') diff --git a/rest_framework/compat.py b/rest_framework/compat.py index 05bd99e0..88211bec 100644 --- a/rest_framework/compat.py +++ b/rest_framework/compat.py @@ -7,6 +7,7 @@ versions of django/python, and compatibility wrappers around optional packages. from __future__ import unicode_literals import django +import inspect from django.core.exceptions import ImproperlyConfigured from django.conf import settings @@ -536,9 +537,23 @@ except ImportError: try: import oauth_provider from oauth_provider.store import store as oauth_provider_store + + # check_nonce's calling signature in django-oauth-plus changes sometime + # between versions 2.0 and 2.2.1 + def check_nonce(request, oauth_request, oauth_nonce, oauth_timestamp): + check_nonce_args = inspect.getargspec(oauth_provider_store.check_nonce).args + if 'timestamp' in check_nonce_args: + return oauth_provider_store.check_nonce( + request, oauth_request, oauth_nonce, oauth_timestamp + ) + return oauth_provider_store.check_nonce( + request, oauth_request, oauth_nonce + ) + except (ImportError, ImproperlyConfigured): oauth_provider = None oauth_provider_store = None + check_nonce = None # OAuth 2 support is optional try: -- cgit v1.2.3 From fc2dee844ab0ca77928f296f13777bf01d94e6fd Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Mon, 16 Dec 2013 08:59:10 +0000 Subject: Don't import compat.py from authtoken.models. Closes #1297 --- rest_framework/compat.py | 7 ------- 1 file changed, 7 deletions(-) (limited to 'rest_framework/compat.py') diff --git a/rest_framework/compat.py b/rest_framework/compat.py index 88211bec..b69749fe 100644 --- a/rest_framework/compat.py +++ b/rest_framework/compat.py @@ -104,13 +104,6 @@ def get_concrete_model(model_cls): return model_cls -# Django 1.5 add support for custom auth user model -if django.VERSION >= (1, 5): - AUTH_USER_MODEL = settings.AUTH_USER_MODEL -else: - AUTH_USER_MODEL = 'auth.User' - - if django.VERSION >= (1, 5): from django.views.generic import View else: -- cgit v1.2.3 From 74fec7eeb4e7e2e593ed5e2213020024264681ce Mon Sep 17 00:00:00 2001 From: Ian Foote Date: Tue, 28 Jan 2014 14:30:46 +0000 Subject: Import force_bytes on django >= 1.5 --- rest_framework/compat.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'rest_framework/compat.py') diff --git a/rest_framework/compat.py b/rest_framework/compat.py index b69749fe..d283e2f5 100644 --- a/rest_framework/compat.py +++ b/rest_framework/compat.py @@ -457,7 +457,7 @@ from django.test.client import RequestFactory as DjangoRequestFactory from django.test.client import FakePayload try: # In 1.5 the test client uses force_bytes - from django.utils.encoding import force_bytes_or_smart_bytes + from django.utils.encoding import force_bytes as force_bytes_or_smart_bytes except ImportError: # In 1.3 and 1.4 the test client just uses smart_str from django.utils.encoding import smart_str as force_bytes_or_smart_bytes -- cgit v1.2.3 From 5ae94547bc08ade94c3f1df2223c0b8261cae59f Mon Sep 17 00:00:00 2001 From: Xavier Ordoquy Date: Tue, 18 Feb 2014 11:42:17 +0100 Subject: Moved the python_2_unicode_compatible into compat module. --- rest_framework/compat.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) (limited to 'rest_framework/compat.py') diff --git a/rest_framework/compat.py b/rest_framework/compat.py index b69749fe..36f5653a 100644 --- a/rest_framework/compat.py +++ b/rest_framework/compat.py @@ -584,3 +584,23 @@ if six.PY3: else: def is_non_str_iterable(obj): return hasattr(obj, '__iter__') + + +try: + from django.utils.encoding import python_2_unicode_compatible +except ImportError: + def python_2_unicode_compatible(klass): + """ + A decorator that defines __unicode__ and __str__ methods under Python 2. + Under Python 3 it does nothing. + + To support Python 2 and 3 with a single code base, define a __str__ method + returning text and apply this decorator to the class. + """ + if '__str__' not in klass.__dict__: + raise ValueError("@python_2_unicode_compatible cannot be applied " + "to %s because it doesn't define __str__()." % + klass.__name__) + klass.__unicode__ = klass.__str__ + klass.__str__ = lambda self: self.__unicode__().encode('utf-8') + return klass -- cgit v1.2.3 From e0682e9298092721c0d3eb358ce4be8039e7ccf6 Mon Sep 17 00:00:00 2001 From: Eric Buehl Date: Wed, 5 Mar 2014 17:15:52 +0000 Subject: don't implicitly import provider.oauth2 --- rest_framework/compat.py | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) (limited to 'rest_framework/compat.py') diff --git a/rest_framework/compat.py b/rest_framework/compat.py index 3089b7fb..f60a180d 100644 --- a/rest_framework/compat.py +++ b/rest_framework/compat.py @@ -550,13 +550,8 @@ except (ImportError, ImproperlyConfigured): # OAuth 2 support is optional try: - import provider.oauth2 as oauth2_provider - from provider.oauth2 import models as oauth2_provider_models - from provider.oauth2 import forms as oauth2_provider_forms - from provider import scope as oauth2_provider_scope - from provider import constants as oauth2_constants - from provider import __version__ as provider_version - if provider_version in ('0.2.3', '0.2.4'): + import provider as oauth2_provider + if oauth2_provider.__version__ in ('0.2.3', '0.2.4'): # 0.2.3 and 0.2.4 are supported version that do not support # timezone aware datetimes import datetime @@ -566,10 +561,6 @@ try: from django.utils.timezone import now as provider_now except ImportError: oauth2_provider = None - oauth2_provider_models = None - oauth2_provider_forms = None - oauth2_provider_scope = None - oauth2_constants = None provider_now = None # Handle lazy strings -- cgit v1.2.3 From 34887ed75625a58d00c986b3ea5526877f4724b2 Mon Sep 17 00:00:00 2001 From: Eric Buehl Date: Thu, 6 Mar 2014 20:19:21 +0000 Subject: it's safe to import scope and constants --- rest_framework/compat.py | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'rest_framework/compat.py') diff --git a/rest_framework/compat.py b/rest_framework/compat.py index f60a180d..d155f554 100644 --- a/rest_framework/compat.py +++ b/rest_framework/compat.py @@ -551,6 +551,8 @@ except (ImportError, ImproperlyConfigured): # OAuth 2 support is optional try: import provider as oauth2_provider + from provider import scope as oauth2_provider_scope + from provider import constants as oauth2_constants if oauth2_provider.__version__ in ('0.2.3', '0.2.4'): # 0.2.3 and 0.2.4 are supported version that do not support # timezone aware datetimes @@ -561,6 +563,8 @@ try: from django.utils.timezone import now as provider_now except ImportError: oauth2_provider = None + oauth2_provider_scope = None + oauth2_constants = None provider_now = None # Handle lazy strings -- cgit v1.2.3 From e8ec81f5e985f9cc9f524f77ec23013be918b990 Mon Sep 17 00:00:00 2001 From: Xavier Ordoquy Date: Sun, 8 Jun 2014 09:03:21 +0200 Subject: Fixed #1624 (thanks @abraithwaite) --- rest_framework/compat.py | 1 + 1 file changed, 1 insertion(+) (limited to 'rest_framework/compat.py') diff --git a/rest_framework/compat.py b/rest_framework/compat.py index d155f554..fdf12448 100644 --- a/rest_framework/compat.py +++ b/rest_framework/compat.py @@ -51,6 +51,7 @@ except ImportError: # guardian is optional try: import guardian + import guardian.shortcuts # Fixes #1624 except ImportError: guardian = None -- cgit v1.2.3 From fe048dc4fbf064b11d7247061c931bb1038cc774 Mon Sep 17 00:00:00 2001 From: Xavier Ordoquy Date: Mon, 28 Jul 2014 07:37:30 +0200 Subject: Fix #1712 (issue when django-guardian is installed but not configured/used) --- rest_framework/compat.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) (limited to 'rest_framework/compat.py') diff --git a/rest_framework/compat.py b/rest_framework/compat.py index fdf12448..9ad8b0d2 100644 --- a/rest_framework/compat.py +++ b/rest_framework/compat.py @@ -48,12 +48,15 @@ try: except ImportError: django_filters = None -# guardian is optional -try: - import guardian - import guardian.shortcuts # Fixes #1624 -except ImportError: - guardian = None +# Django-guardian is optional. Import only if guardian is in INSTALLED_APPS +# Fixes (#1712). We keep the try/except for the test suite. +guardian = None +if 'guardian' in settings.INSTALLED_APPS: + try: + import guardian + import guardian.shortcuts # Fixes #1624 + except ImportError: + pass # cStringIO only if it's available, otherwise StringIO -- cgit v1.2.3 From 63d02dbea855a060ec4cdb194497188e2f40cb66 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Tue, 19 Aug 2014 17:06:55 +0100 Subject: Drop six from compat. 1.4.2 is now the lowest supported version. --- rest_framework/compat.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) (limited to 'rest_framework/compat.py') diff --git a/rest_framework/compat.py b/rest_framework/compat.py index 4b16a8ca..fa0f0bfb 100644 --- a/rest_framework/compat.py +++ b/rest_framework/compat.py @@ -9,14 +9,9 @@ import django import inspect from django.core.exceptions import ImproperlyConfigured from django.conf import settings +from django.utils import six -# Try to import six from Django, fallback to external `six` package. -try: - from django.utils import six -except ImportError: - import six - # Handle django.utils.encoding rename in 1.5 onwards. # smart_unicode -> smart_text # force_unicode -> force_text -- cgit v1.2.3 From baa518cd890103173dd18857c609432bd47c6be4 Mon Sep 17 00:00:00 2001 From: Jharrod LaFon Date: Fri, 5 Sep 2014 15:30:01 -0700 Subject: Moved OAuth support out of DRF and into a separate package, per #1767 --- rest_framework/compat.py | 51 ------------------------------------------------ 1 file changed, 51 deletions(-) (limited to 'rest_framework/compat.py') diff --git a/rest_framework/compat.py b/rest_framework/compat.py index fa0f0bfb..bc5719ef 100644 --- a/rest_framework/compat.py +++ b/rest_framework/compat.py @@ -182,57 +182,6 @@ except ImportError: etree = None -# OAuth2 is optional -try: - # Note: The `oauth2` package actually provides oauth1.0a support. Urg. - import oauth2 as oauth -except ImportError: - oauth = None - - -# OAuthProvider is optional -try: - import oauth_provider - from oauth_provider.store import store as oauth_provider_store - - # check_nonce's calling signature in django-oauth-plus changes sometime - # between versions 2.0 and 2.2.1 - def check_nonce(request, oauth_request, oauth_nonce, oauth_timestamp): - check_nonce_args = inspect.getargspec(oauth_provider_store.check_nonce).args - if 'timestamp' in check_nonce_args: - return oauth_provider_store.check_nonce( - request, oauth_request, oauth_nonce, oauth_timestamp - ) - return oauth_provider_store.check_nonce( - request, oauth_request, oauth_nonce - ) - -except (ImportError, ImproperlyConfigured): - oauth_provider = None - oauth_provider_store = None - check_nonce = None - - -# OAuth 2 support is optional -try: - import provider as oauth2_provider - from provider import scope as oauth2_provider_scope - from provider import constants as oauth2_constants - if oauth2_provider.__version__ in ('0.2.3', '0.2.4'): - # 0.2.3 and 0.2.4 are supported version that do not support - # timezone aware datetimes - import datetime - provider_now = datetime.datetime.now - else: - # Any other supported version does use timezone aware datetimes - from django.utils.timezone import now as provider_now -except ImportError: - oauth2_provider = None - oauth2_provider_scope = None - oauth2_constants = None - provider_now = None - - # Handle lazy strings across Py2/Py3 from django.utils.functional import Promise -- cgit v1.2.3 From 01c8c0cad977fc0787dbfc78bd34f4fd37e613f4 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Wed, 10 Sep 2014 13:52:16 +0100 Subject: Added help_text argument to fields --- rest_framework/compat.py | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) (limited to 'rest_framework/compat.py') diff --git a/rest_framework/compat.py b/rest_framework/compat.py index fa0f0bfb..70b38df9 100644 --- a/rest_framework/compat.py +++ b/rest_framework/compat.py @@ -39,6 +39,17 @@ except ImportError: django_filters = None +if django.VERSION >= (1, 6): + def clean_manytomany_helptext(text): + return text +else: + # Up to version 1.5 many to many fields automatically suffix + # the `help_text` attribute with hardcoded text. + def clean_manytomany_helptext(text): + if text.endswith(' Hold down "Control", or "Command" on a Mac, to select more than one.'): + text = text[:-69] + return text + # Django-guardian is optional. Import only if guardian is in INSTALLED_APPS # Fixes (#1712). We keep the try/except for the test suite. guardian = None @@ -99,18 +110,8 @@ def get_concrete_model(model_cls): return model_cls -# View._allowed_methods only present from 1.5 onwards -if django.VERSION >= (1, 5): - from django.views.generic import View -else: - from django.views.generic import View as DjangoView - - class View(DjangoView): - def _allowed_methods(self): - return [m.upper() for m in self.http_method_names if hasattr(self, m)] - - # PATCH method is not implemented by Django +from django.views.generic import View if 'patch' not in View.http_method_names: View.http_method_names = View.http_method_names + ['patch'] -- cgit v1.2.3 From 80ba0473473501968154c5cc5dd5922e53d96a70 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Wed, 10 Sep 2014 16:57:22 +0100 Subject: Compat fixes --- rest_framework/compat.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) (limited to 'rest_framework/compat.py') diff --git a/rest_framework/compat.py b/rest_framework/compat.py index 70b38df9..7c05bed9 100644 --- a/rest_framework/compat.py +++ b/rest_framework/compat.py @@ -110,8 +110,18 @@ def get_concrete_model(model_cls): return model_cls +# View._allowed_methods only present from 1.5 onwards +if django.VERSION >= (1, 5): + from django.views.generic import View +else: + from django.views.generic import View as DjangoView + + class View(DjangoView): + def _allowed_methods(self): + return [m.upper() for m in self.http_method_names if hasattr(self, m)] + + # PATCH method is not implemented by Django -from django.views.generic import View if 'patch' not in View.http_method_names: View.http_method_names = View.http_method_names + ['patch'] -- cgit v1.2.3 From c54f394904c3f93211b8aa073de4e9e50110f831 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Mon, 22 Sep 2014 13:57:45 +0100 Subject: Ensure 'messages' in fields are respected in preference to default validator messages --- rest_framework/compat.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) (limited to 'rest_framework/compat.py') diff --git a/rest_framework/compat.py b/rest_framework/compat.py index 7c05bed9..2b4ddb02 100644 --- a/rest_framework/compat.py +++ b/rest_framework/compat.py @@ -121,6 +121,25 @@ else: return [m.upper() for m in self.http_method_names if hasattr(self, m)] + +# MinValueValidator and MaxValueValidator only accept `message` in 1.8+ +if django.VERSION >= (1, 8): + from django.core.validators import MinValueValidator, MaxValueValidator +else: + from django.core.validators import MinValueValidator as DjangoMinValueValidator + from django.core.validators import MaxValueValidator as DjangoMaxValueValidator + + class MinValueValidator(DjangoMinValueValidator): + def __init__(self, *args, **kwargs): + self.message = kwargs.pop('message', self.message) + super(MinValueValidator, self).__init__(*args, **kwargs) + + class MaxValueValidator(DjangoMaxValueValidator): + def __init__(self, *args, **kwargs): + self.message = kwargs.pop('message', self.message) + super(MaxValueValidator, self).__init__(*args, **kwargs) + + # PATCH method is not implemented by Django if 'patch' not in View.http_method_names: View.http_method_names = View.http_method_names + ['patch'] -- cgit v1.2.3 From e5f0a97595ff9280c7876fc917f6feb27b5ea95d Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Mon, 22 Sep 2014 16:45:06 +0100 Subject: More compat fixes --- rest_framework/compat.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) (limited to 'rest_framework/compat.py') diff --git a/rest_framework/compat.py b/rest_framework/compat.py index 2b4ddb02..7303c32a 100644 --- a/rest_framework/compat.py +++ b/rest_framework/compat.py @@ -139,6 +139,29 @@ else: self.message = kwargs.pop('message', self.message) super(MaxValueValidator, self).__init__(*args, **kwargs) +# URLValidator only accept `message` in 1.6+ +if django.VERSION >= (1, 6): + from django.core.validators import URLValidator +else: + from django.core.validators import URLValidator as DjangoURLValidator + + class URLValidator(DjangoURLValidator): + def __init__(self, *args, **kwargs): + self.message = kwargs.pop('message', self.message) + super(URLValidator, self).__init__(*args, **kwargs) + + +# EmailValidator requires explicit regex prior to 1.6+ +if django.VERSION >= (1, 6): + from django.core.validators import EmailValidator +else: + from django.core.validators import EmailValidator as DjangoEmailValidator + from django.core.validators import email_re + + class EmailValidator(DjangoEmailValidator): + def __init__(self, *args, **kwargs): + super(EmailValidator, self).__init__(email_re, *args, **kwargs) + # PATCH method is not implemented by Django if 'patch' not in View.http_method_names: -- cgit v1.2.3 From dee3f78cb688b1bee892ef78d6eec23ccf67a80e Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Fri, 26 Sep 2014 17:06:20 +0100 Subject: FileField and ImageField --- rest_framework/compat.py | 9 --------- 1 file changed, 9 deletions(-) (limited to 'rest_framework/compat.py') diff --git a/rest_framework/compat.py b/rest_framework/compat.py index 7303c32a..89af9b48 100644 --- a/rest_framework/compat.py +++ b/rest_framework/compat.py @@ -84,15 +84,6 @@ except ImportError: from collections import UserDict from collections import MutableMapping as DictMixin -# Try to import PIL in either of the two ways it can end up installed. -try: - from PIL import Image -except ImportError: - try: - import Image - except ImportError: - Image = None - def get_model_name(model_cls): try: -- cgit v1.2.3 From 381771731f48c75e7d5951e353049cceec386512 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Wed, 1 Oct 2014 13:09:14 +0100 Subject: Use six.text_type instead of str everywhere --- rest_framework/compat.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) (limited to 'rest_framework/compat.py') diff --git a/rest_framework/compat.py b/rest_framework/compat.py index 89af9b48..3993cee6 100644 --- a/rest_framework/compat.py +++ b/rest_framework/compat.py @@ -5,11 +5,12 @@ versions of django/python, and compatibility wrappers around optional packages. # flake8: noqa from __future__ import unicode_literals -import django -import inspect + from django.core.exceptions import ImproperlyConfigured from django.conf import settings from django.utils import six +import django +import inspect # Handle django.utils.encoding rename in 1.5 onwards. @@ -177,12 +178,12 @@ class RequestFactory(DjangoRequestFactory): r = { 'PATH_INFO': self._get_path(parsed), 'QUERY_STRING': force_text(parsed[4]), - 'REQUEST_METHOD': str(method), + 'REQUEST_METHOD': six.text_type(method), } if data: r.update({ 'CONTENT_LENGTH': len(data), - 'CONTENT_TYPE': str(content_type), + 'CONTENT_TYPE': six.text_type(content_type), 'wsgi.input': FakePayload(data), }) elif django.VERSION <= (1, 4): -- cgit v1.2.3 From dfab9af294972720f59890967cd9ae1a6c0796b6 Mon Sep 17 00:00:00 2001 From: Craig de Stigter Date: Fri, 3 Oct 2014 08:41:18 +1300 Subject: Minor: fix spelling and grammar, mostly in 3.0 announcement --- rest_framework/compat.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'rest_framework/compat.py') diff --git a/rest_framework/compat.py b/rest_framework/compat.py index 3993cee6..e4e69580 100644 --- a/rest_framework/compat.py +++ b/rest_framework/compat.py @@ -131,7 +131,7 @@ else: self.message = kwargs.pop('message', self.message) super(MaxValueValidator, self).__init__(*args, **kwargs) -# URLValidator only accept `message` in 1.6+ +# URLValidator only accepts `message` in 1.6+ if django.VERSION >= (1, 6): from django.core.validators import URLValidator else: -- cgit v1.2.3 From 5d247a65c89594a7ab5ce2333612f23eadc6828d Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Thu, 9 Oct 2014 15:11:19 +0100 Subject: First pass on nested serializers in HTML --- rest_framework/compat.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) (limited to 'rest_framework/compat.py') diff --git a/rest_framework/compat.py b/rest_framework/compat.py index e4e69580..4ab23a4d 100644 --- a/rest_framework/compat.py +++ b/rest_framework/compat.py @@ -114,12 +114,15 @@ else: -# MinValueValidator and MaxValueValidator only accept `message` in 1.8+ +# MinValueValidator, MaxValueValidator et al. only accept `message` in 1.8+ if django.VERSION >= (1, 8): from django.core.validators import MinValueValidator, MaxValueValidator + from django.core.validators import MinLengthValidator, MaxLengthValidator else: from django.core.validators import MinValueValidator as DjangoMinValueValidator from django.core.validators import MaxValueValidator as DjangoMaxValueValidator + from django.core.validators import MinLengthValidator as DjangoMinLengthValidator + from django.core.validators import MaxLengthValidator as DjangoMaxLengthValidator class MinValueValidator(DjangoMinValueValidator): def __init__(self, *args, **kwargs): @@ -131,6 +134,17 @@ else: self.message = kwargs.pop('message', self.message) super(MaxValueValidator, self).__init__(*args, **kwargs) + class MinLengthValidator(DjangoMinLengthValidator): + def __init__(self, *args, **kwargs): + self.message = kwargs.pop('message', self.message) + super(MinLengthValidator, self).__init__(*args, **kwargs) + + class MaxLengthValidator(DjangoMaxLengthValidator): + def __init__(self, *args, **kwargs): + self.message = kwargs.pop('message', self.message) + super(MaxLengthValidator, self).__init__(*args, **kwargs) + + # URLValidator only accepts `message` in 1.6+ if django.VERSION >= (1, 6): from django.core.validators import URLValidator -- cgit v1.2.3 From cd40dcb06500424ef9016913a1a89db0deed8a1f Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Thu, 30 Oct 2014 16:53:12 +0000 Subject: Ensure json.dumps(separators=...) works on both 2.x and 3.x --- rest_framework/compat.py | 9 +++++++++ 1 file changed, 9 insertions(+) (limited to 'rest_framework/compat.py') diff --git a/rest_framework/compat.py b/rest_framework/compat.py index 4ab23a4d..0201bd83 100644 --- a/rest_framework/compat.py +++ b/rest_framework/compat.py @@ -291,6 +291,15 @@ except ImportError: oauth2_constants = None provider_now = None +# `seperators` argument to `json.dumps()` differs between 2.x and 3.x +# See: http://bugs.python.org/issue22767 +if six.PY3: + SHORT_SEPARATORS = (',', ':') + LONG_SEPARATORS = (', ', ': ') +else: + SHORT_SEPARATORS = (b',', b':') + LONG_SEPARATORS = (b', ', b': ') + # Handle lazy strings across Py2/Py3 from django.utils.functional import Promise -- cgit v1.2.3 From 4e001dbb7ac0bc13d6d5fbb4524e905184610aa2 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Thu, 6 Nov 2014 12:00:30 +0000 Subject: Drop usage of SortedDict. Closes #2027. --- rest_framework/compat.py | 10 ++++++++++ 1 file changed, 10 insertions(+) (limited to 'rest_framework/compat.py') diff --git a/rest_framework/compat.py b/rest_framework/compat.py index 0201bd83..5bd85e74 100644 --- a/rest_framework/compat.py +++ b/rest_framework/compat.py @@ -26,6 +26,16 @@ except ImportError: from django.utils.encoding import force_unicode as force_text +# 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. +try: + from collections import OrderedDict +except: + from django.utils.datastructures import SortedDict as OrderedDict + + # HttpResponseBase only exists from 1.5 onwards try: from django.http.response import HttpResponseBase -- cgit v1.2.3 From 731c8421afe3093a78cdabb9c3cc28fa52cd1c8e Mon Sep 17 00:00:00 2001 From: José Padilla Date: Sat, 29 Nov 2014 14:43:05 -0400 Subject: Remove YAML support from core --- rest_framework/compat.py | 7 ------- 1 file changed, 7 deletions(-) (limited to 'rest_framework/compat.py') diff --git a/rest_framework/compat.py b/rest_framework/compat.py index 5bd85e74..52db9625 100644 --- a/rest_framework/compat.py +++ b/rest_framework/compat.py @@ -237,13 +237,6 @@ except ImportError: apply_markdown = None -# Yaml is optional -try: - import yaml -except ImportError: - yaml = None - - # XML is optional try: import defusedxml.ElementTree as etree -- cgit v1.2.3 From 7f9dc736728baf92a3198a7f90bd302fff240373 Mon Sep 17 00:00:00 2001 From: José Padilla Date: Sat, 29 Nov 2014 14:50:51 -0400 Subject: Remove XML support from core --- rest_framework/compat.py | 7 ------- 1 file changed, 7 deletions(-) (limited to 'rest_framework/compat.py') diff --git a/rest_framework/compat.py b/rest_framework/compat.py index 5bd85e74..899dd2b4 100644 --- a/rest_framework/compat.py +++ b/rest_framework/compat.py @@ -244,13 +244,6 @@ except ImportError: yaml = None -# XML is optional -try: - import defusedxml.ElementTree as etree -except ImportError: - etree = None - - # OAuth2 is optional try: # Note: The `oauth2` package actually provides oauth1.0a support. Urg. -- cgit v1.2.3 From 09e59f268619927dc22f15fed97c3ceac05ea306 Mon Sep 17 00:00:00 2001 From: Tymur Maryokhin Date: Thu, 4 Dec 2014 02:50:25 +0100 Subject: Removed custom python_2_unicode_compatible. Closes #2183 --- rest_framework/compat.py | 42 ++++++++++++------------------------------ 1 file changed, 12 insertions(+), 30 deletions(-) (limited to 'rest_framework/compat.py') diff --git a/rest_framework/compat.py b/rest_framework/compat.py index 5bd85e74..eefaf22e 100644 --- a/rest_framework/compat.py +++ b/rest_framework/compat.py @@ -6,11 +6,12 @@ versions of django/python, and compatibility wrappers around optional packages. # flake8: noqa from __future__ import unicode_literals +import inspect + from django.core.exceptions import ImproperlyConfigured from django.conf import settings from django.utils import six import django -import inspect # Handle django.utils.encoding rename in 1.5 onwards. @@ -49,7 +50,6 @@ try: except ImportError: django_filters = None - if django.VERSION >= (1, 6): def clean_manytomany_helptext(text): return text @@ -123,7 +123,6 @@ else: return [m.upper() for m in self.http_method_names if hasattr(self, m)] - # MinValueValidator, MaxValueValidator et al. only accept `message` in 1.8+ if django.VERSION >= (1, 8): from django.core.validators import MinValueValidator, MaxValueValidator @@ -187,6 +186,7 @@ if 'patch' not in View.http_method_names: # RequestFactory only provides `generic` from 1.5 onwards from django.test.client import RequestFactory as DjangoRequestFactory from django.test.client import FakePayload + try: # In 1.5 the test client uses force_bytes from django.utils.encoding import force_bytes as force_bytes_or_smart_bytes @@ -194,21 +194,22 @@ except ImportError: # In 1.4 the test client just uses smart_str from django.utils.encoding import smart_str as force_bytes_or_smart_bytes + class RequestFactory(DjangoRequestFactory): def generic(self, method, path, data='', content_type='application/octet-stream', **extra): parsed = urlparse.urlparse(path) data = force_bytes_or_smart_bytes(data, settings.DEFAULT_CHARSET) r = { - 'PATH_INFO': self._get_path(parsed), - 'QUERY_STRING': force_text(parsed[4]), + 'PATH_INFO': self._get_path(parsed), + 'QUERY_STRING': force_text(parsed[4]), 'REQUEST_METHOD': six.text_type(method), } if data: r.update({ 'CONTENT_LENGTH': len(data), - 'CONTENT_TYPE': six.text_type(content_type), - 'wsgi.input': FakePayload(data), + 'CONTENT_TYPE': six.text_type(content_type), + 'wsgi.input': FakePayload(data), }) elif django.VERSION <= (1, 4): # For 1.3 we need an empty WSGI payload @@ -287,10 +288,12 @@ try: import provider as oauth2_provider from provider import scope as oauth2_provider_scope from provider import constants as oauth2_constants + if oauth2_provider.__version__ in ('0.2.3', '0.2.4'): # 0.2.3 and 0.2.4 are supported version that do not support # timezone aware datetimes import datetime + provider_now = datetime.datetime.now else: # Any other supported version does use timezone aware datetimes @@ -301,7 +304,7 @@ except ImportError: oauth2_constants = None provider_now = None -# `seperators` argument to `json.dumps()` differs between 2.x and 3.x +# `separators` argument to `json.dumps()` differs between 2.x and 3.x # See: http://bugs.python.org/issue22767 if six.PY3: SHORT_SEPARATORS = (',', ':') @@ -316,30 +319,9 @@ from django.utils.functional import Promise if six.PY3: def is_non_str_iterable(obj): - if (isinstance(obj, str) or - (isinstance(obj, Promise) and obj._delegate_text)): + if isinstance(obj, str) or (isinstance(obj, Promise) and obj._delegate_text): return False return hasattr(obj, '__iter__') else: def is_non_str_iterable(obj): return hasattr(obj, '__iter__') - - -try: - from django.utils.encoding import python_2_unicode_compatible -except ImportError: - def python_2_unicode_compatible(klass): - """ - A decorator that defines __unicode__ and __str__ methods under Python 2. - Under Python 3 it does nothing. - - To support Python 2 and 3 with a single code base, define a __str__ method - returning text and apply this decorator to the class. - """ - if '__str__' not in klass.__dict__: - raise ValueError("@python_2_unicode_compatible cannot be applied " - "to %s because it doesn't define __str__()." % - klass.__name__) - klass.__unicode__ = klass.__str__ - klass.__str__ = lambda self: self.__unicode__().encode('utf-8') - return klass -- cgit v1.2.3 From d54c67d79d0f5661ef33d860efe80a4272e22b3e Mon Sep 17 00:00:00 2001 From: Tymur Maryokhin Date: Thu, 4 Dec 2014 03:11:42 +0100 Subject: Removed custom StringIO, force_text, smart_text compat --- rest_framework/compat.py | 34 +++------------------------------- 1 file changed, 3 insertions(+), 31 deletions(-) (limited to 'rest_framework/compat.py') diff --git a/rest_framework/compat.py b/rest_framework/compat.py index eefaf22e..91723b45 100644 --- a/rest_framework/compat.py +++ b/rest_framework/compat.py @@ -9,31 +9,19 @@ from __future__ import unicode_literals import inspect from django.core.exceptions import ImproperlyConfigured +from django.utils.encoding import force_text from django.conf import settings from django.utils import six import django -# Handle django.utils.encoding rename in 1.5 onwards. -# smart_unicode -> smart_text -# force_unicode -> force_text -try: - from django.utils.encoding import smart_text -except ImportError: - from django.utils.encoding import smart_unicode as smart_text -try: - from django.utils.encoding import force_text -except ImportError: - from django.utils.encoding import force_unicode as force_text - - # 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. try: from collections import OrderedDict -except: +except ImportError: from django.utils.datastructures import SortedDict as OrderedDict @@ -72,21 +60,13 @@ if 'guardian' in settings.INSTALLED_APPS: pass -# cStringIO only if it's available, otherwise StringIO -try: - import cStringIO.StringIO as StringIO -except ImportError: - StringIO = six.StringIO - -BytesIO = six.BytesIO - - # urlparse compat import (Required because it changed in python 3.x) try: from urllib import parse as urlparse except ImportError: import urlparse + # UserDict moves in Python 3 try: from UserDict import UserDict @@ -104,14 +84,6 @@ def get_model_name(model_cls): return model_cls._meta.module_name -def get_concrete_model(model_cls): - try: - return model_cls._meta.concrete_model - except AttributeError: - # 1.3 does not include concrete model - return model_cls - - # View._allowed_methods only present from 1.5 onwards if django.VERSION >= (1, 5): from django.views.generic import View -- cgit v1.2.3 From 95af92ca01435c32f4374d26eb448f964d77c81a Mon Sep 17 00:00:00 2001 From: Tymur Maryokhin Date: Thu, 4 Dec 2014 15:47:42 +0100 Subject: Removed custom urlparse compat --- rest_framework/compat.py | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) (limited to 'rest_framework/compat.py') diff --git a/rest_framework/compat.py b/rest_framework/compat.py index 91723b45..c58aa4ce 100644 --- a/rest_framework/compat.py +++ b/rest_framework/compat.py @@ -10,6 +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.conf import settings from django.utils import six import django @@ -60,13 +61,6 @@ if 'guardian' in settings.INSTALLED_APPS: pass -# urlparse compat import (Required because it changed in python 3.x) -try: - from urllib import parse as urlparse -except ImportError: - import urlparse - - # UserDict moves in Python 3 try: from UserDict import UserDict -- cgit v1.2.3 From 001884a1927f6ceaa60dcc2a4df3422f94455311 Mon Sep 17 00:00:00 2001 From: Tymur Maryokhin Date: Thu, 4 Dec 2014 16:06:43 +0100 Subject: Removed unused compat code --- rest_framework/compat.py | 27 --------------------------- 1 file changed, 27 deletions(-) (limited to 'rest_framework/compat.py') diff --git a/rest_framework/compat.py b/rest_framework/compat.py index c58aa4ce..71520b92 100644 --- a/rest_framework/compat.py +++ b/rest_framework/compat.py @@ -61,15 +61,6 @@ if 'guardian' in settings.INSTALLED_APPS: pass -# UserDict moves in Python 3 -try: - from UserDict import UserDict - from UserDict import DictMixin -except ImportError: - from collections import UserDict - from collections import MutableMapping as DictMixin - - def get_model_name(model_cls): try: return model_cls._meta.model_name @@ -177,11 +168,6 @@ class RequestFactory(DjangoRequestFactory): 'CONTENT_TYPE': six.text_type(content_type), 'wsgi.input': FakePayload(data), }) - elif django.VERSION <= (1, 4): - # For 1.3 we need an empty WSGI payload - r.update({ - 'wsgi.input': FakePayload('') - }) r.update(extra) return self.request(**r) @@ -278,16 +264,3 @@ if six.PY3: else: SHORT_SEPARATORS = (b',', b':') LONG_SEPARATORS = (b', ', b': ') - - -# Handle lazy strings across Py2/Py3 -from django.utils.functional import Promise - -if six.PY3: - def is_non_str_iterable(obj): - if isinstance(obj, str) or (isinstance(obj, Promise) and obj._delegate_text): - return False - return hasattr(obj, '__iter__') -else: - def is_non_str_iterable(obj): - return hasattr(obj, '__iter__') -- cgit v1.2.3 From 72e08a3e8b6427cb93f0f98b42724e31e5b3d8f9 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Mon, 15 Dec 2014 11:55:17 +0000 Subject: Use unicode internally everywhere for 'repr' --- rest_framework/compat.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) (limited to 'rest_framework/compat.py') diff --git a/rest_framework/compat.py b/rest_framework/compat.py index 71520b92..69fdd793 100644 --- a/rest_framework/compat.py +++ b/rest_framework/compat.py @@ -16,6 +16,23 @@ from django.utils import six import django +def unicode_repr(instance): + # Get the repr of an instance, but ensure it is a unicode string + # on both python 3 (already the case) and 2 (not the case). + if six.PY2: + repr(instance).decode('utf-8') + return repr(instance) + + +def unicode_to_repr(value): + # Coerce a unicode string to the correct repr return type, depending on + # the Python version. We wrap all our `__repr__` implementations with + # this and then use unicode throughout internally. + if six.PY2: + return value.encode('utf-8') + return 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. -- cgit v1.2.3 From fe9647ce92b61b57dc64604241352bf269d65af7 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Tue, 16 Dec 2014 16:37:32 +0000 Subject: AcceptHeaderVersioning to return unicode strings. --- rest_framework/compat.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) (limited to 'rest_framework/compat.py') diff --git a/rest_framework/compat.py b/rest_framework/compat.py index c5242343..3c8fb0da 100644 --- a/rest_framework/compat.py +++ b/rest_framework/compat.py @@ -5,15 +5,13 @@ versions of django/python, and compatibility wrappers around optional packages. # flake8: noqa from __future__ import unicode_literals - -import inspect - from django.core.exceptions import ImproperlyConfigured +from django.conf import settings from django.utils.encoding import force_text from django.utils.six.moves.urllib import parse as urlparse -from django.conf import settings from django.utils import six import django +import inspect def unicode_repr(instance): @@ -33,6 +31,13 @@ def unicode_to_repr(value): return value +def unicode_http_header(value): + # Coerce HTTP header value to unicode. + if isinstance(value, six.binary_type): + return value.decode('iso-8859-1') + return 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. -- cgit v1.2.3 From efa5942ce1c5d2286fd91994b52fb73a5690426c Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Sun, 28 Dec 2014 12:02:52 +0000 Subject: Support namespaced router URLs with DefaultRouter. --- rest_framework/compat.py | 10 ++++++++++ 1 file changed, 10 insertions(+) (limited to 'rest_framework/compat.py') diff --git a/rest_framework/compat.py b/rest_framework/compat.py index 69fdd793..ba26a3cd 100644 --- a/rest_framework/compat.py +++ b/rest_framework/compat.py @@ -50,6 +50,16 @@ except ImportError: from django.http import HttpResponse as HttpResponseBase +# request only provides `resolver_match` from 1.5 onwards. +def get_resolver_match(request): + try: + return request.resolver_match + except AttributeError: + # Django < 1.5 + from django.core.urlresolvers import resolve + return resolve(request.path_info) + + # django-filter is optional try: import django_filters -- 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/compat.py') 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 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/compat.py') 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 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 ++++++++ 1 file changed, 8 insertions(+) (limited to 'rest_framework/compat.py') 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. -- 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 ++ 1 file changed, 2 insertions(+) (limited to 'rest_framework/compat.py') 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': ') -- cgit v1.2.3 From da6ef3d0b0f3a8e688524bbd446d4350a74fd05a Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Wed, 21 Jan 2015 13:03:37 +0000 Subject: Allow missing fields option for inherited serializers. Closes #2388. --- rest_framework/compat.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'rest_framework/compat.py') diff --git a/rest_framework/compat.py b/rest_framework/compat.py index 17814136..766afaec 100644 --- a/rest_framework/compat.py +++ b/rest_framework/compat.py @@ -20,7 +20,7 @@ def unicode_repr(instance): # Get the repr of an instance, but ensure it is a unicode string # on both python 3 (already the case) and 2 (not the case). if six.PY2: - repr(instance).decode('utf-8') + return repr(instance).decode('utf-8') return repr(instance) -- cgit v1.2.3 From 35f6a8246299d31ecce4f791f9527bf34cebe6e2 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Fri, 23 Jan 2015 16:27:23 +0000 Subject: Added DictField and support for HStoreField. --- rest_framework/compat.py | 7 +++++++ 1 file changed, 7 insertions(+) (limited to 'rest_framework/compat.py') diff --git a/rest_framework/compat.py b/rest_framework/compat.py index 766afaec..36413394 100644 --- a/rest_framework/compat.py +++ b/rest_framework/compat.py @@ -58,6 +58,13 @@ except ImportError: from django.http import HttpResponse as HttpResponseBase +# contrib.postgres only supported from 1.8 onwards. +try: + from django.contrib.postgres import fields as postgres_fields +except ImportError: + postgres_fields = None + + # request only provides `resolver_match` from 1.5 onwards. def get_resolver_match(request): try: -- cgit v1.2.3 From c5eb5b22018e55bffe080bb3f14e34ab6b493073 Mon Sep 17 00:00:00 2001 From: Carlton Gibson Date: Tue, 17 Feb 2015 11:55:15 +0100 Subject: Move `importlib` fallback into compat. --- rest_framework/compat.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'rest_framework/compat.py') diff --git a/rest_framework/compat.py b/rest_framework/compat.py index 50f37014..c6a4a869 100644 --- a/rest_framework/compat.py +++ b/rest_framework/compat.py @@ -12,7 +12,10 @@ from django.utils.six.moves.urllib.parse import urlparse as _urlparse from django.utils import six import django import inspect - +try: + import importlib +except ImportError: + from django.utils import importlib def unicode_repr(instance): # Get the repr of an instance, but ensure it is a unicode string -- cgit v1.2.3