diff --git a/README.md b/README.md index 6d7a3a959..784b54c0c 100644 --- a/README.md +++ b/README.md @@ -10,9 +10,9 @@ Full documentation for the project is available at [http://www.django-rest-frame --- -**Note**: We have now released Django REST framework 3.1. For older codebases you may want to refer to the version 2.4.4 [source code](https://github.com/tomchristie/django-rest-framework/tree/version-2.4.x), and [documentation](http://tomchristie.github.io/rest-framework-2-docs/). +**Note**: We have now released Django REST framework 3.2. For older codebases you may want to refer to the version 2.4.4 [source code][2.4-code], and [documentation][2.4-docs]. -For more details see the [3.1 release notes][3.1-announcement] +For more details see the 3.2 [announcement][3.2-announcement] and [release notes][3.2-release-notes]. --- @@ -182,4 +182,7 @@ Send a description of the issue via email to [rest-framework-security@googlegrou [docs]: http://www.django-rest-framework.org/ [security-mail]: mailto:rest-framework-security@googlegroups.com -[3.1-announcement]: http://www.django-rest-framework.org/topics/3.1-announcement/ +[2.4-code]: https://github.com/tomchristie/django-rest-framework/tree/version-2.4.x +[2.4-docs]: http://tomchristie.github.io/rest-framework-2-docs/ +[3.2-announcement]: http://www.django-rest-framework.org/topics/3.2-announcement/ +[3.2-release-notes]: http://www.django-rest-framework.org/topics/release-notes/#32x-series diff --git a/docs/index.md b/docs/index.md index ec15e43dc..37f192094 100644 --- a/docs/index.md +++ b/docs/index.md @@ -12,9 +12,9 @@ --- -**Note**: This is the documentation for the **version 3.1** of REST framework. Documentation for [version 2.4](http://tomchristie.github.io/rest-framework-2-docs/) is also available. +**Note**: This is the documentation for the **version 3.2** of REST framework. Documentation for [version 2.4](http://tomchristie.github.io/rest-framework-2-docs/) is also available. -For more details see the [3.1 release notes][3.1-announcement]. +For more details see the 3.2 [announcement][3.2-announcement] and [release notes][release-notes]. --- diff --git a/docs/topics/3.2-announcement.md b/docs/topics/3.2-announcement.md index 945c1ff30..a8d8dc45d 100644 --- a/docs/topics/3.2-announcement.md +++ b/docs/topics/3.2-announcement.md @@ -74,7 +74,7 @@ The upshot is this: If you have many to many fields in your models, then make su ### List fields and allow_null -When using `allow_null` with `ListField` or a nested `mant=True` serializer the previous behavior was to allow `null` values as items in the list. The behavior is now to allow `null` values instead of the list. +When using `allow_null` with `ListField` or a nested `many=True` serializer the previous behavior was to allow `null` values as items in the list. The behavior is now to allow `null` values instead of the list. For example, take the following field: diff --git a/rest_framework/authentication.py b/rest_framework/authentication.py index 088eac4ca..da88a239b 100644 --- a/rest_framework/authentication.py +++ b/rest_framework/authentication.py @@ -5,13 +5,12 @@ from __future__ import unicode_literals import base64 -from django.contrib.auth import authenticate +from django.contrib.auth import authenticate, get_user_model from django.middleware.csrf import CsrfViewMiddleware from django.utils.translation import ugettext_lazy as _ from rest_framework import HTTP_HEADER_ENCODING, exceptions from rest_framework.authtoken.models import Token -from rest_framework.compat import get_user_model def get_authorization_header(request): @@ -89,9 +88,8 @@ class BasicAuthentication(BaseAuthentication): """ Authenticate the userid and password against username and password. """ - username_field = getattr(get_user_model(), 'USERNAME_FIELD', 'username') credentials = { - username_field: userid, + get_user_model().USERNAME_FIELD: userid, 'password': password } user = authenticate(**credentials) diff --git a/rest_framework/compat.py b/rest_framework/compat.py index d18541820..11fc9a28a 100644 --- a/rest_framework/compat.py +++ b/rest_framework/compat.py @@ -1,27 +1,22 @@ """ The `compat` module provides support for backwards compatibility with older -versions of django/python, and compatibility wrappers around optional packages. +versions of Django/Python, and compatibility wrappers around optional packages. """ # flake8: noqa from __future__ import unicode_literals -import inspect - import django from django.conf import settings -from django.core.exceptions import ImproperlyConfigured from django.db import connection, transaction -from django.forms import FilePathField as DjangoFilePathField -from django.test.client import FakePayload from django.utils import six -from django.utils.encoding import force_text -from django.utils.six.moves.urllib.parse import urlparse as _urlparse +from django.views.generic import View try: - import importlib + import importlib # Available in Python 3.1+ except ImportError: - from django.utils import importlib + from django.utils import importlib # Will be removed in Django 1.9 + def unicode_repr(instance): # Get the repr of an instance, but ensure it is a unicode string @@ -65,13 +60,6 @@ except ImportError: from django.utils.datastructures import SortedDict as OrderedDict -# HttpResponseBase only exists from 1.5 onwards -try: - from django.http.response import HttpResponseBase -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 @@ -79,16 +67,6 @@ except ImportError: postgres_fields = None -# 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 @@ -125,25 +103,6 @@ def get_model_name(model_cls): return model_cls._meta.module_name -# Support custom user models in Django 1.5+ -try: - from django.contrib.auth import get_user_model -except ImportError: - from django.contrib.auth.models import User - get_user_model = lambda: 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 DjangoView - - class View(DjangoView): - def _allowed_methods(self): - 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 @@ -154,21 +113,25 @@ else: from django.core.validators import MinLengthValidator as DjangoMinLengthValidator from django.core.validators import MaxLengthValidator as DjangoMaxLengthValidator + 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) + 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) @@ -181,6 +144,7 @@ if django.VERSION >= (1, 6): else: from django.core.validators import URLValidator as DjangoURLValidator + class URLValidator(DjangoURLValidator): def __init__(self, *args, **kwargs): self.message = kwargs.pop('message', self.message) @@ -194,6 +158,7 @@ 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) @@ -208,6 +173,7 @@ if 'patch' not in View.http_method_names: try: import markdown + def apply_markdown(text): """ Simple wrapper around :func:`markdown.markdown` to set the base level @@ -233,7 +199,6 @@ else: LONG_SEPARATORS = (b', ', b': ') INDENT_SEPARATORS = (b',', b': ') - if django.VERSION >= (1, 8): from django.db.models import DurationField from django.utils.dateparse import parse_duration diff --git a/rest_framework/routers.py b/rest_framework/routers.py index e72c4987a..b96100ec2 100644 --- a/rest_framework/routers.py +++ b/rest_framework/routers.py @@ -23,7 +23,7 @@ from django.core.exceptions import ImproperlyConfigured from django.core.urlresolvers import NoReverseMatch from rest_framework import views -from rest_framework.compat import OrderedDict, get_resolver_match +from rest_framework.compat import OrderedDict from rest_framework.response import Response from rest_framework.reverse import reverse from rest_framework.urlpatterns import format_suffix_patterns @@ -284,7 +284,7 @@ class DefaultRouter(SimpleRouter): def get(self, request, *args, **kwargs): ret = OrderedDict() - namespace = get_resolver_match(request).namespace + namespace = request.resolver_match.namespace for key, url_name in api_root_dict.items(): if namespace: url_name = namespace + ':' + url_name diff --git a/rest_framework/utils/formatting.py b/rest_framework/utils/formatting.py index 5ad974e7c..67aabd3f1 100644 --- a/rest_framework/utils/formatting.py +++ b/rest_framework/utils/formatting.py @@ -5,10 +5,11 @@ from __future__ import unicode_literals import re +from django.utils.encoding import force_text from django.utils.html import escape from django.utils.safestring import mark_safe -from rest_framework.compat import apply_markdown, force_text +from rest_framework.compat import apply_markdown def remove_trailing_string(content, trailing): diff --git a/rest_framework/utils/serializer_helpers.py b/rest_framework/utils/serializer_helpers.py index 229a46d0b..b049c7de2 100644 --- a/rest_framework/utils/serializer_helpers.py +++ b/rest_framework/utils/serializer_helpers.py @@ -2,7 +2,9 @@ from __future__ import unicode_literals import collections -from rest_framework.compat import OrderedDict, force_text, unicode_to_repr +from django.utils.encoding import force_text + +from rest_framework.compat import OrderedDict, unicode_to_repr class ReturnDict(OrderedDict): @@ -11,6 +13,7 @@ class ReturnDict(OrderedDict): Includes a backlink to the serializer instance for renderers to use if they need richer field information. """ + def __init__(self, *args, **kwargs): self.serializer = kwargs.pop('serializer') super(ReturnDict, self).__init__(*args, **kwargs) @@ -33,6 +36,7 @@ class ReturnList(list): Includes a backlink to the serializer instance for renderers to use if they need richer field information. """ + def __init__(self, *args, **kwargs): self.serializer = kwargs.pop('serializer') super(ReturnList, self).__init__(*args, **kwargs) @@ -52,6 +56,7 @@ class BoundField(object): Returned when iterating over a serializer instance, providing an API similar to Django forms and form fields. """ + def __init__(self, field, value, errors, prefix=''): self._field = field self._prefix = prefix @@ -82,6 +87,7 @@ class NestedBoundField(BoundField): in order to support nested bound fields. This class is the type of `BoundField` that is used for serializer fields. """ + def __iter__(self): for field in self.fields.values(): yield self[field.field_name] @@ -112,6 +118,7 @@ class BindingDict(collections.MutableMapping): `field.bind()` so that the `field_name` and `parent` attributes can be set correctly. """ + def __init__(self, serializer): self.serializer = serializer self.fields = OrderedDict() diff --git a/rest_framework/views.py b/rest_framework/views.py index 468737ad6..56e3c4e49 100644 --- a/rest_framework/views.py +++ b/rest_framework/views.py @@ -6,13 +6,15 @@ from __future__ import unicode_literals from django.core.exceptions import PermissionDenied from django.db import models from django.http import Http404 +from django.http.response import HttpResponseBase from django.utils import six from django.utils.encoding import smart_text from django.utils.translation import ugettext_lazy as _ from django.views.decorators.csrf import csrf_exempt +from django.views.generic import View from rest_framework import exceptions, status -from rest_framework.compat import HttpResponseBase, View, set_rollback +from rest_framework.compat import set_rollback from rest_framework.request import Request from rest_framework.response import Response from rest_framework.settings import api_settings diff --git a/tox.ini b/tox.ini index 2adfe43ed..489e8a7a6 100644 --- a/tox.ini +++ b/tox.ini @@ -4,7 +4,6 @@ addopts=--tb=short [tox] envlist = py27-{lint,docs}, - {py26,py27}-django14, {py26,py27,py32,py33,py34}-django{15,16}, {py27,py32,py33,py34}-django{17,18,master}