From e441f85109e64345a12e65062fc0e51c5787e67f Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Wed, 25 Sep 2013 10:30:04 +0100 Subject: [PATCH] Drop 1.3 support --- .travis.yml | 12 +- rest_framework/authentication.py | 2 +- rest_framework/compat.py | 381 +----------------- rest_framework/fields.py | 4 +- rest_framework/routers.py | 2 +- rest_framework/runtests/settings.py | 5 +- rest_framework/runtests/urls.py | 2 +- rest_framework/serializers.py | 6 +- .../templates/rest_framework/base.html | 1 + .../templates/rest_framework/login_base.html | 1 + rest_framework/templatetags/rest_framework.py | 87 +--- rest_framework/tests/test_authentication.py | 2 +- rest_framework/tests/test_breadcrumbs.py | 2 +- rest_framework/tests/test_filters.py | 3 +- rest_framework/tests/test_htmlrenderer.py | 2 +- .../tests/test_hyperlinkedserializers.py | 4 +- .../tests/test_relations_hyperlink.py | 2 +- rest_framework/tests/test_renderers.py | 3 +- rest_framework/tests/test_request.py | 2 +- rest_framework/tests/test_response.py | 2 +- rest_framework/tests/test_reverse.py | 2 +- rest_framework/tests/test_routers.py | 2 +- rest_framework/tests/test_testing.py | 2 +- rest_framework/tests/test_urlpatterns.py | 2 +- rest_framework/urlpatterns.py | 2 +- rest_framework/urls.py | 2 +- rest_framework/utils/encoders.py | 3 +- tox.ini | 22 +- 28 files changed, 56 insertions(+), 506 deletions(-) diff --git a/.travis.yml b/.travis.yml index 7ebe715a0..456f8e9c0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,18 +10,15 @@ env: - DJANGO="https://www.djangoproject.com/download/1.6a1/tarball/" - DJANGO="django==1.5.1 --use-mirrors" - DJANGO="django==1.4.5 --use-mirrors" - - DJANGO="django==1.3.7 --use-mirrors" install: - pip install $DJANGO - - pip install defusedxml==0.3 + - pip install defusedxml==0.3 --use-mirrors + - pip install django-filter==0.6 --use-mirrors - "if [[ ${TRAVIS_PYTHON_VERSION::1} != '3' ]]; then pip install oauth2==1.5.211 --use-mirrors; fi" - "if [[ ${TRAVIS_PYTHON_VERSION::1} != '3' ]]; then pip install django-oauth-plus==2.0 --use-mirrors; fi" - "if [[ ${TRAVIS_PYTHON_VERSION::1} != '3' ]]; then pip install django-oauth2-provider==0.2.4 --use-mirrors; fi" - "if [[ ${TRAVIS_PYTHON_VERSION::1} != '3' ]]; then pip install django-guardian==1.1.1 --use-mirrors; fi" - - "if [[ ${DJANGO::11} == 'django==1.3' ]]; then pip install django-filter==0.5.4 --use-mirrors; fi" - - "if [[ ${DJANGO::11} == 'django==1.3' ]]; then pip install six --use-mirrors; fi" - - "if [[ ${DJANGO::11} != 'django==1.3' ]]; then pip install django-filter==0.6 --use-mirrors; fi" - export PYTHONPATH=. script: @@ -31,10 +28,5 @@ matrix: exclude: - python: "3.2" env: DJANGO="django==1.4.5 --use-mirrors" - - python: "3.2" - env: DJANGO="django==1.3.7 --use-mirrors" - python: "3.3" env: DJANGO="django==1.4.5 --use-mirrors" - - python: "3.3" - env: DJANGO="django==1.3.7 --use-mirrors" - diff --git a/rest_framework/authentication.py b/rest_framework/authentication.py index cf001a24d..db5cce40d 100644 --- a/rest_framework/authentication.py +++ b/rest_framework/authentication.py @@ -6,8 +6,8 @@ import base64 from django.contrib.auth import authenticate from django.core.exceptions import ImproperlyConfigured +from django.middleware.csrf import CsrfViewMiddleware from rest_framework import exceptions, HTTP_HEADER_ENCODING -from rest_framework.compat import CsrfViewMiddleware from rest_framework.compat import oauth, oauth_provider, oauth_provider_store from rest_framework.compat import oauth2_provider, provider_now from rest_framework.authtoken.models import Token diff --git a/rest_framework/compat.py b/rest_framework/compat.py index 1238f043a..f048b10ad 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 + from django.views.generic import View as DjangoView - 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 + 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: diff --git a/rest_framework/fields.py b/rest_framework/fields.py index b3a9b0dfe..f340510d3 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -18,12 +18,14 @@ from django.conf import settings from django.db.models.fields import BLANK_CHOICE_DASH from django.http import QueryDict from django.forms import widgets +from django.utils import 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 ( - timezone, parse_date, parse_datetime, parse_time, BytesIO, six, smart_text, + BytesIO, six, smart_text, force_text, is_non_str_iterable ) from rest_framework.settings import api_settings diff --git a/rest_framework/routers.py b/rest_framework/routers.py index 1c7a81585..790299cc3 100644 --- a/rest_framework/routers.py +++ b/rest_framework/routers.py @@ -17,9 +17,9 @@ from __future__ import unicode_literals import itertools from collections import namedtuple +from django.conf.urls import patterns, url from django.core.exceptions import ImproperlyConfigured from rest_framework import views -from rest_framework.compat import patterns, url from rest_framework.response import Response from rest_framework.reverse import reverse from rest_framework.urlpatterns import format_suffix_patterns diff --git a/rest_framework/runtests/settings.py b/rest_framework/runtests/settings.py index be7216580..12aa73e7b 100644 --- a/rest_framework/runtests/settings.py +++ b/rest_framework/runtests/settings.py @@ -93,10 +93,7 @@ INSTALLED_APPS = ( 'django.contrib.sessions', 'django.contrib.sites', 'django.contrib.messages', - # Uncomment the next line to enable the admin: - # 'django.contrib.admin', - # Uncomment the next line to enable admin documentation: - # 'django.contrib.admindocs', + 'django.contrib.staticfiles', 'rest_framework', 'rest_framework.authtoken', 'rest_framework.tests', diff --git a/rest_framework/runtests/urls.py b/rest_framework/runtests/urls.py index ed5baeae6..dff710115 100644 --- a/rest_framework/runtests/urls.py +++ b/rest_framework/runtests/urls.py @@ -1,7 +1,7 @@ """ Blank URLConf just to keep runtests.py happy. """ -from rest_framework.compat import patterns +from django.conf.urls import patterns urlpatterns = patterns('', ) diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index f17757625..9e3881a2c 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -18,7 +18,7 @@ from decimal import Decimal from django.db import models from django.forms import widgets from django.utils.datastructures import SortedDict -from rest_framework.compat import get_concrete_model, six +from rest_framework.compat import six # Note: We do the following so that users of the framework can use this style: # @@ -575,7 +575,7 @@ class ModelSerializer(Serializer): cls = self.opts.model assert cls is not None, \ "Serializer class '%s' is missing 'model' Meta option" % self.__class__.__name__ - opts = get_concrete_model(cls)._meta + opts = cls._meta.concrete_model._meta ret = SortedDict() nested = bool(self.opts.depth) @@ -784,7 +784,7 @@ class ModelSerializer(Serializer): Return a list of field names to exclude from model validation. """ cls = self.opts.model - opts = get_concrete_model(cls)._meta + opts = cls._meta.concrete_model._meta exclusions = [field.name for field in opts.fields + opts.many_to_many] for field_name, field in self.fields.items(): diff --git a/rest_framework/templates/rest_framework/base.html b/rest_framework/templates/rest_framework/base.html index 2776d5500..47377d51f 100644 --- a/rest_framework/templates/rest_framework/base.html +++ b/rest_framework/templates/rest_framework/base.html @@ -1,4 +1,5 @@ {% load url from future %} +{% load staticfiles %} {% load rest_framework %} diff --git a/rest_framework/templates/rest_framework/login_base.html b/rest_framework/templates/rest_framework/login_base.html index be9a0072a..be83c2f53 100644 --- a/rest_framework/templates/rest_framework/login_base.html +++ b/rest_framework/templates/rest_framework/login_base.html @@ -1,4 +1,5 @@ {% load url from future %} +{% load staticfiles %} {% load rest_framework %} diff --git a/rest_framework/templatetags/rest_framework.py b/rest_framework/templatetags/rest_framework.py index e9c1cdd54..55f361490 100644 --- a/rest_framework/templatetags/rest_framework.py +++ b/rest_framework/templatetags/rest_framework.py @@ -2,97 +2,14 @@ from __future__ import unicode_literals, absolute_import from django import template from django.core.urlresolvers import reverse, NoReverseMatch from django.http import QueryDict -from django.utils.html import escape +from django.utils.html import escape, smart_urlquote from django.utils.safestring import SafeData, mark_safe -from rest_framework.compat import urlparse, force_text, six, smart_urlquote +from rest_framework.compat import urlparse, force_text, six import re, string register = template.Library() -# Note we don't use 'load staticfiles', because we need a 1.3 compatible -# version, so instead we include the `static` template tag ourselves. - -# When 1.3 becomes unsupported by REST framework, we can instead start to -# use the {% load staticfiles %} tag, remove the following code, -# and add a dependency that `django.contrib.staticfiles` must be installed. - -# Note: We can't put this into the `compat` module because the compat import -# from rest_framework.compat import ... -# conflicts with this rest_framework template tag module. - -try: # Django 1.5+ - from django.contrib.staticfiles.templatetags.staticfiles import StaticFilesNode - - @register.tag('static') - def do_static(parser, token): - return StaticFilesNode.handle_token(parser, token) - -except ImportError: - try: # Django 1.4 - from django.contrib.staticfiles.storage import staticfiles_storage - - @register.simple_tag - def static(path): - """ - A template tag that returns the URL to a file - using staticfiles' storage backend - """ - return staticfiles_storage.url(path) - - except ImportError: # Django 1.3 - from urlparse import urljoin - from django import template - from django.templatetags.static import PrefixNode - - class StaticNode(template.Node): - def __init__(self, varname=None, path=None): - if path is None: - raise template.TemplateSyntaxError( - "Static template nodes must be given a path to return.") - self.path = path - self.varname = varname - - def url(self, context): - path = self.path.resolve(context) - return self.handle_simple(path) - - def render(self, context): - url = self.url(context) - if self.varname is None: - return url - context[self.varname] = url - return '' - - @classmethod - def handle_simple(cls, path): - return urljoin(PrefixNode.handle_simple("STATIC_URL"), path) - - @classmethod - def handle_token(cls, parser, token): - """ - Class method to parse prefix node and return a Node. - """ - bits = token.split_contents() - - if len(bits) < 2: - raise template.TemplateSyntaxError( - "'%s' takes at least one argument (path to file)" % bits[0]) - - path = parser.compile_filter(bits[1]) - - if len(bits) >= 2 and bits[-2] == 'as': - varname = bits[3] - else: - varname = None - - return cls(varname, path) - - @register.tag('static') - def do_static_13(parser, token): - return StaticNode.handle_token(parser, token) - - def replace_query_param(url, key, val): """ Given a URL and a key/val pair, set or replace an item in the query diff --git a/rest_framework/tests/test_authentication.py b/rest_framework/tests/test_authentication.py index a44813b69..e9a817c0f 100644 --- a/rest_framework/tests/test_authentication.py +++ b/rest_framework/tests/test_authentication.py @@ -1,4 +1,5 @@ from __future__ import unicode_literals +from django.conf.urls import patterns, url, include from django.contrib.auth.models import User from django.http import HttpResponse from django.test import TestCase @@ -18,7 +19,6 @@ from rest_framework.authentication import ( OAuth2Authentication ) from rest_framework.authtoken.models import Token -from rest_framework.compat import patterns, url, include from rest_framework.compat import oauth2_provider, oauth2_provider_models, oauth2_provider_scope from rest_framework.compat import oauth, oauth_provider from rest_framework.test import APIRequestFactory, APIClient diff --git a/rest_framework/tests/test_breadcrumbs.py b/rest_framework/tests/test_breadcrumbs.py index 41ddf2cea..33740cbb2 100644 --- a/rest_framework/tests/test_breadcrumbs.py +++ b/rest_framework/tests/test_breadcrumbs.py @@ -1,6 +1,6 @@ from __future__ import unicode_literals +from django.conf.urls import patterns, url from django.test import TestCase -from rest_framework.compat import patterns, url from rest_framework.utils.breadcrumbs import get_breadcrumbs from rest_framework.views import APIView diff --git a/rest_framework/tests/test_filters.py b/rest_framework/tests/test_filters.py index 379db29d8..9697c5eef 100644 --- a/rest_framework/tests/test_filters.py +++ b/rest_framework/tests/test_filters.py @@ -1,12 +1,13 @@ from __future__ import unicode_literals import datetime from decimal import Decimal +from django.conf.urls import patterns, url from django.db import models from django.core.urlresolvers import reverse from django.test import TestCase from django.utils import unittest from rest_framework import generics, serializers, status, filters -from rest_framework.compat import django_filters, patterns, url +from rest_framework.compat import django_filters from rest_framework.test import APIRequestFactory from rest_framework.tests.models import BasicModel diff --git a/rest_framework/tests/test_htmlrenderer.py b/rest_framework/tests/test_htmlrenderer.py index 8957a43c7..6c570dfdc 100644 --- a/rest_framework/tests/test_htmlrenderer.py +++ b/rest_framework/tests/test_htmlrenderer.py @@ -1,11 +1,11 @@ from __future__ import unicode_literals from django.core.exceptions import PermissionDenied +from django.conf.urls import patterns, url from django.http import Http404 from django.test import TestCase from django.template import TemplateDoesNotExist, Template import django.template.loader from rest_framework import status -from rest_framework.compat import patterns, url from rest_framework.decorators import api_view, renderer_classes from rest_framework.renderers import TemplateHTMLRenderer from rest_framework.response import Response diff --git a/rest_framework/tests/test_hyperlinkedserializers.py b/rest_framework/tests/test_hyperlinkedserializers.py index 61e613d75..ea7f70f2f 100644 --- a/rest_framework/tests/test_hyperlinkedserializers.py +++ b/rest_framework/tests/test_hyperlinkedserializers.py @@ -1,8 +1,8 @@ from __future__ import unicode_literals import json +from django.conf.urls import patterns, url from django.test import TestCase from rest_framework import generics, status, serializers -from rest_framework.compat import patterns, url from rest_framework.test import APIRequestFactory from rest_framework.tests.models import ( Anchor, BasicModel, ManyToManyModel, BlogPost, BlogPostComment, @@ -24,7 +24,7 @@ class BlogPostCommentSerializer(serializers.ModelSerializer): class PhotoSerializer(serializers.Serializer): description = serializers.CharField() - album_url = serializers.HyperlinkedRelatedField(source='album', view_name='album-detail', queryset=Album.objects.all(), lookup_field='title', slug_url_kwarg='title') + album_url = serializers.HyperlinkedRelatedField(source='album', view_name='album-detail', queryset=Album.objects.all(), lookup_field='title') def restore_object(self, attrs, instance=None): return Photo(**attrs) diff --git a/rest_framework/tests/test_relations_hyperlink.py b/rest_framework/tests/test_relations_hyperlink.py index 3c4d39af6..fa6b01aca 100644 --- a/rest_framework/tests/test_relations_hyperlink.py +++ b/rest_framework/tests/test_relations_hyperlink.py @@ -1,7 +1,7 @@ from __future__ import unicode_literals +from django.conf.urls import patterns, url from django.test import TestCase from rest_framework import serializers -from rest_framework.compat import patterns, url from rest_framework.test import APIRequestFactory from rest_framework.tests.models import ( BlogPost, diff --git a/rest_framework/tests/test_renderers.py b/rest_framework/tests/test_renderers.py index df6f4aa63..9d1dd77e5 100644 --- a/rest_framework/tests/test_renderers.py +++ b/rest_framework/tests/test_renderers.py @@ -2,12 +2,13 @@ from __future__ import unicode_literals from decimal import Decimal +from django.conf.urls import patterns, url, include from django.core.cache import cache from django.test import TestCase from django.utils import unittest from django.utils.translation import ugettext_lazy as _ from rest_framework import status, permissions -from rest_framework.compat import yaml, etree, patterns, url, include, six, StringIO +from rest_framework.compat import yaml, etree, six, StringIO from rest_framework.response import Response from rest_framework.views import APIView from rest_framework.renderers import BaseRenderer, JSONRenderer, YAMLRenderer, \ diff --git a/rest_framework/tests/test_request.py b/rest_framework/tests/test_request.py index 969d8024a..d63634258 100644 --- a/rest_framework/tests/test_request.py +++ b/rest_framework/tests/test_request.py @@ -2,13 +2,13 @@ Tests for content parsing, and form-overloaded content parsing. """ from __future__ import unicode_literals +from django.conf.urls import patterns from django.contrib.auth.models import User from django.contrib.auth import authenticate, login, logout from django.contrib.sessions.middleware import SessionMiddleware from django.test import TestCase from rest_framework import status from rest_framework.authentication import SessionAuthentication -from rest_framework.compat import patterns from rest_framework.parsers import ( BaseParser, FormParser, diff --git a/rest_framework/tests/test_response.py b/rest_framework/tests/test_response.py index eea3c6418..1c4c551cf 100644 --- a/rest_framework/tests/test_response.py +++ b/rest_framework/tests/test_response.py @@ -1,7 +1,7 @@ from __future__ import unicode_literals +from django.conf.urls import patterns, url, include from django.test import TestCase from rest_framework.tests.models import BasicModel, BasicModelSerializer -from rest_framework.compat import patterns, url, include from rest_framework.response import Response from rest_framework.views import APIView from rest_framework import generics diff --git a/rest_framework/tests/test_reverse.py b/rest_framework/tests/test_reverse.py index 690a30b11..320b125d2 100644 --- a/rest_framework/tests/test_reverse.py +++ b/rest_framework/tests/test_reverse.py @@ -1,6 +1,6 @@ from __future__ import unicode_literals +from django.conf.urls import patterns, url from django.test import TestCase -from rest_framework.compat import patterns, url from rest_framework.reverse import reverse from rest_framework.test import APIRequestFactory diff --git a/rest_framework/tests/test_routers.py b/rest_framework/tests/test_routers.py index 3f456fefd..1c34648f4 100644 --- a/rest_framework/tests/test_routers.py +++ b/rest_framework/tests/test_routers.py @@ -1,9 +1,9 @@ from __future__ import unicode_literals +from django.conf.urls import patterns, url, include from django.db import models from django.test import TestCase from django.core.exceptions import ImproperlyConfigured from rest_framework import serializers, viewsets, permissions -from rest_framework.compat import include, patterns, url from rest_framework.decorators import detail_route, list_route from rest_framework.response import Response from rest_framework.routers import SimpleRouter, DefaultRouter diff --git a/rest_framework/tests/test_testing.py b/rest_framework/tests/test_testing.py index 48b8956b5..c08dd4932 100644 --- a/rest_framework/tests/test_testing.py +++ b/rest_framework/tests/test_testing.py @@ -1,9 +1,9 @@ # -- coding: utf-8 -- from __future__ import unicode_literals +from django.conf.urls import patterns, url from django.contrib.auth.models import User from django.test import TestCase -from rest_framework.compat import patterns, url from rest_framework.decorators import api_view from rest_framework.response import Response from rest_framework.test import APIClient, APIRequestFactory, force_authenticate diff --git a/rest_framework/tests/test_urlpatterns.py b/rest_framework/tests/test_urlpatterns.py index 8132ec4c8..e0060e690 100644 --- a/rest_framework/tests/test_urlpatterns.py +++ b/rest_framework/tests/test_urlpatterns.py @@ -1,9 +1,9 @@ from __future__ import unicode_literals from collections import namedtuple +from django.conf.urls import patterns, url, include from django.core import urlresolvers from django.test import TestCase from rest_framework.test import APIRequestFactory -from rest_framework.compat import patterns, url, include from rest_framework.urlpatterns import format_suffix_patterns diff --git a/rest_framework/urlpatterns.py b/rest_framework/urlpatterns.py index d9143bb4c..a62530c74 100644 --- a/rest_framework/urlpatterns.py +++ b/rest_framework/urlpatterns.py @@ -1,6 +1,6 @@ from __future__ import unicode_literals +from django.conf.urls import url, include from django.core.urlresolvers import RegexURLResolver -from rest_framework.compat import url, include from rest_framework.settings import api_settings diff --git a/rest_framework/urls.py b/rest_framework/urls.py index 9c4719f1d..87ec0f0ae 100644 --- a/rest_framework/urls.py +++ b/rest_framework/urls.py @@ -13,7 +13,7 @@ your authentication settings include `SessionAuthentication`. ) """ from __future__ import unicode_literals -from rest_framework.compat import patterns, url +from django.conf.urls import patterns, url template_name = {'template_name': 'rest_framework/login.html'} diff --git a/rest_framework/utils/encoders.py b/rest_framework/utils/encoders.py index 7efd5417b..13a855509 100644 --- a/rest_framework/utils/encoders.py +++ b/rest_framework/utils/encoders.py @@ -2,9 +2,10 @@ Helper classes for parsers. """ from __future__ import unicode_literals +from django.utils import timezone from django.utils.datastructures import SortedDict from django.utils.functional import Promise -from rest_framework.compat import timezone, force_text +from rest_framework.compat import force_text from rest_framework.serializers import DictWithMetadata, SortedDictWithMetadata import datetime import decimal diff --git a/tox.ini b/tox.ini index 6e3b8e0a8..7bd140e1c 100644 --- a/tox.ini +++ b/tox.ini @@ -1,6 +1,6 @@ [tox] downloadcache = {toxworkdir}/cache/ -envlist = py3.3-django1.6,py3.2-django1.6,py2.7-django1.6,py2.6-django1.6,py3.3-django1.5,py3.2-django1.5,py2.7-django1.5,py2.6-django1.5,py2.7-django1.4,py2.6-django1.4,py2.7-django1.3,py2.6-django1.3 +envlist = py3.3-django1.6,py3.2-django1.6,py2.7-django1.6,py2.6-django1.6,py3.3-django1.5,py3.2-django1.5,py2.7-django1.5,py2.6-django1.5,py2.7-django1.4,py2.6-django1.4 [testenv] commands = {envpython} rest_framework/runtests/runtests.py @@ -88,23 +88,3 @@ deps = django==1.4.3 oauth2==1.5.211 django-oauth2-provider==0.2.3 django-guardian==1.1.1 - -[testenv:py2.7-django1.3] -basepython = python2.7 -deps = django==1.3.5 - django-filter==0.5.4 - defusedxml==0.3 - django-oauth-plus==2.0 - oauth2==1.5.211 - django-oauth2-provider==0.2.3 - django-guardian==1.1.1 - -[testenv:py2.6-django1.3] -basepython = python2.6 -deps = django==1.3.5 - django-filter==0.5.4 - defusedxml==0.3 - django-oauth-plus==2.0 - oauth2==1.5.211 - django-oauth2-provider==0.2.3 - django-guardian==1.1.1