aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.travis.yml12
-rw-r--r--rest_framework/authentication.py2
-rw-r--r--rest_framework/compat.py383
-rw-r--r--rest_framework/fields.py4
-rw-r--r--rest_framework/routers.py2
-rw-r--r--rest_framework/runtests/settings.py5
-rw-r--r--rest_framework/runtests/urls.py2
-rw-r--r--rest_framework/serializers.py6
-rw-r--r--rest_framework/templates/rest_framework/base.html1
-rw-r--r--rest_framework/templates/rest_framework/login_base.html1
-rw-r--r--rest_framework/templatetags/rest_framework.py87
-rw-r--r--rest_framework/tests/test_authentication.py2
-rw-r--r--rest_framework/tests/test_breadcrumbs.py2
-rw-r--r--rest_framework/tests/test_filters.py3
-rw-r--r--rest_framework/tests/test_htmlrenderer.py2
-rw-r--r--rest_framework/tests/test_hyperlinkedserializers.py4
-rw-r--r--rest_framework/tests/test_relations_hyperlink.py2
-rw-r--r--rest_framework/tests/test_renderers.py3
-rw-r--r--rest_framework/tests/test_request.py2
-rw-r--r--rest_framework/tests/test_response.py2
-rw-r--r--rest_framework/tests/test_reverse.py2
-rw-r--r--rest_framework/tests/test_routers.py2
-rw-r--r--rest_framework/tests/test_testing.py2
-rw-r--r--rest_framework/tests/test_urlpatterns.py2
-rw-r--r--rest_framework/urlpatterns.py2
-rw-r--r--rest_framework/urls.py2
-rw-r--r--rest_framework/utils/encoders.py3
-rw-r--r--tox.ini22
28 files changed, 57 insertions, 507 deletions
diff --git a/.travis.yml b/.travis.yml
index 7ebe715a..456f8e9c 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 cf001a24..db5cce40 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 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<year>\d{4})-(?P<month>\d{1,2})-(?P<day>\d{1,2})$'
- )
-
- datetime_re = re.compile(
- r'(?P<year>\d{4})-(?P<month>\d{1,2})-(?P<day>\d{1,2})'
- r'[T ](?P<hour>\d{1,2}):(?P<minute>\d{1,2})'
- r'(?::(?P<second>\d{1,2})(?:\.(?P<microsecond>\d{1,6})\d{0,6})?)?'
- r'(?P<tzinfo>Z|[+-]\d{1,2}:\d{1,2})?$'
- )
-
- time_re = re.compile(
- r'(?P<hour>\d{1,2}):(?P<minute>\d{1,2})'
- r'(?::(?P<second>\d{1,2})(?:\.(?P<microsecond>\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 b3a9b0df..f340510d 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 1c7a8158..790299cc 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 be721658..12aa73e7 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 ed5baeae..dff71011 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 f1775762..9e3881a2 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 2776d550..47377d51 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 %}
<!DOCTYPE html>
<html>
diff --git a/rest_framework/templates/rest_framework/login_base.html b/rest_framework/templates/rest_framework/login_base.html
index be9a0072..be83c2f5 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 %}
<html>
diff --git a/rest_framework/templatetags/rest_framework.py b/rest_framework/templatetags/rest_framework.py
index e9c1cdd5..55f36149 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 a44813b6..e9a817c0 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 41ddf2ce..33740cbb 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 379db29d..9697c5ee 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 8957a43c..6c570dfd 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 61e613d7..ea7f70f2 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 3c4d39af..fa6b01ac 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 df6f4aa6..9d1dd77e 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 969d8024..d6363425 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 eea3c641..1c4c551c 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 690a30b1..320b125d 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 3f456fef..1c34648f 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 48b8956b..c08dd493 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 8132ec4c..e0060e69 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 d9143bb4..a62530c7 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 9c4719f1..87ec0f0a 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 7efd5417..13a85550 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 6e3b8e0a..7bd140e1 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