aboutsummaryrefslogtreecommitdiffstats
path: root/rest_framework
diff options
context:
space:
mode:
authorTom Christie2014-12-18 11:21:25 +0000
committerTom Christie2014-12-18 11:21:25 +0000
commitc8d88c8c8a594e3b66547a34462db4766292ea9e (patch)
tree09d1e53c9c019501b85ff8892dca4177c95a6e0b /rest_framework
parent47fe6977077ae33dfe2f8b6d04d81083b9b9f4d7 (diff)
parentd8803a35bd2dc8cbf4c892f68b48c72f24e83916 (diff)
downloaddjango-rest-framework-c8d88c8c8a594e3b66547a34462db4766292ea9e.tar.bz2
Merge branch 'master' into version-3.1
Diffstat (limited to 'rest_framework')
-rw-r--r--rest_framework/__init__.py2
-rw-r--r--rest_framework/exceptions.py4
-rw-r--r--rest_framework/fields.py12
-rw-r--r--rest_framework/generics.py8
-rw-r--r--rest_framework/relations.py18
-rw-r--r--rest_framework/renderers.py16
-rw-r--r--rest_framework/request.py15
-rw-r--r--rest_framework/serializers.py51
8 files changed, 88 insertions, 38 deletions
diff --git a/rest_framework/__init__.py b/rest_framework/__init__.py
index f30f781a..6808b74b 100644
--- a/rest_framework/__init__.py
+++ b/rest_framework/__init__.py
@@ -8,7 +8,7 @@ ______ _____ _____ _____ __
"""
__title__ = 'Django REST framework'
-__version__ = '3.0.1'
+__version__ = '3.0.2'
__author__ = 'Tom Christie'
__license__ = 'BSD 2-Clause'
__copyright__ = 'Copyright 2011-2014 Tom Christie'
diff --git a/rest_framework/exceptions.py b/rest_framework/exceptions.py
index 238934db..bcfd8961 100644
--- a/rest_framework/exceptions.py
+++ b/rest_framework/exceptions.py
@@ -5,8 +5,8 @@ In addition Django's built in 403 and 404 exceptions are handled.
(`django.http.Http404` and `django.core.exceptions.PermissionDenied`)
"""
from __future__ import unicode_literals
+from django.utils import six
from django.utils.encoding import force_text
-
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import ungettext_lazy
from rest_framework import status
@@ -66,7 +66,7 @@ class ValidationError(APIException):
self.detail = _force_text_recursive(detail)
def __str__(self):
- return str(self.detail)
+ return six.text_type(self.detail)
class ParseError(APIException):
diff --git a/rest_framework/fields.py b/rest_framework/fields.py
index f3e17b18..c40dc3fb 100644
--- a/rest_framework/fields.py
+++ b/rest_framework/fields.py
@@ -184,8 +184,11 @@ class Field(object):
self.style = {} if style is None else style
self.allow_null = allow_null
- if allow_null and self.default_empty_html is empty:
- self.default_empty_html = None
+ if self.default_empty_html is not empty:
+ if not required:
+ self.default_empty_html = empty
+ elif default is not empty:
+ self.default_empty_html = default
if validators is not None:
self.validators = validators[:]
@@ -557,6 +560,11 @@ class CharField(Field):
message = self.error_messages['min_length'].format(min_length=min_length)
self.validators.append(MinLengthValidator(min_length, message=message))
+ if self.allow_null and (not self.allow_blank) and (self.default is empty):
+ # HTML input cannot represent `None` values, so we need to
+ # forcibly coerce empty HTML values to `None` if `allow_null=True`.
+ self.default_empty_html = None
+
def run_validation(self, data=empty):
# Test for the empty string here so that it does not get validated,
# and so that subclasses do not need to handle it explicitly
diff --git a/rest_framework/generics.py b/rest_framework/generics.py
index 3d6cf168..e6db155e 100644
--- a/rest_framework/generics.py
+++ b/rest_framework/generics.py
@@ -79,16 +79,14 @@ class GenericAPIView(views.APIView):
'view': self
}
- def get_serializer(self, instance=None, data=None, many=False, partial=False):
+ def get_serializer(self, *args, **kwargs):
"""
Return the serializer instance that should be used for validating and
deserializing input, and for serializing output.
"""
serializer_class = self.get_serializer_class()
- context = self.get_serializer_context()
- return serializer_class(
- instance, data=data, many=many, partial=partial, context=context
- )
+ kwargs['context'] = self.get_serializer_context()
+ return serializer_class(*args, **kwargs)
def get_pagination_serializer(self, page):
"""
diff --git a/rest_framework/relations.py b/rest_framework/relations.py
index 892ce6c1..7b119291 100644
--- a/rest_framework/relations.py
+++ b/rest_framework/relations.py
@@ -1,13 +1,15 @@
-from django.utils.encoding import smart_text
-from rest_framework.fields import get_attribute, empty, Field
-from rest_framework.reverse import reverse
-from rest_framework.utils import html
+# coding: utf-8
+from __future__ import unicode_literals
from django.core.exceptions import ObjectDoesNotExist, ImproperlyConfigured
from django.core.urlresolvers import resolve, get_script_prefix, NoReverseMatch, Resolver404
from django.db.models.query import QuerySet
from django.utils import six
+from django.utils.encoding import smart_text
from django.utils.six.moves.urllib import parse as urlparse
from django.utils.translation import ugettext_lazy as _
+from rest_framework.fields import get_attribute, empty, Field
+from rest_framework.reverse import reverse
+from rest_framework.utils import html
class PKOnlyObject(object):
@@ -103,8 +105,8 @@ class RelatedField(Field):
def choices(self):
return dict([
(
- str(self.to_representation(item)),
- str(item)
+ six.text_type(self.to_representation(item)),
+ six.text_type(item)
)
for item in self.queryset.all()
])
@@ -364,8 +366,8 @@ class ManyRelatedField(Field):
]
return dict([
(
- str(item_representation),
- str(item) + ' - ' + str(item_representation)
+ six.text_type(item_representation),
+ six.text_type(item) + ' - ' + six.text_type(item_representation)
)
for item, item_representation in items_and_representations
])
diff --git a/rest_framework/renderers.py b/rest_framework/renderers.py
index f8d18fc6..c4de30db 100644
--- a/rest_framework/renderers.py
+++ b/rest_framework/renderers.py
@@ -435,12 +435,12 @@ class BrowsableAPIRenderer(BaseRenderer):
# serializer instance, rather than dynamically creating a new one.
if request.method == method and serializer is not None:
try:
- data = request.data
+ kwargs = {'data': request.data}
except ParseError:
- data = None
+ kwargs = {}
existing_serializer = serializer
else:
- data = None
+ kwargs = {}
existing_serializer = None
with override_method(view, request, method) as request:
@@ -460,11 +460,13 @@ class BrowsableAPIRenderer(BaseRenderer):
serializer = existing_serializer
else:
if method in ('PUT', 'PATCH'):
- serializer = view.get_serializer(instance=instance, data=data)
+ serializer = view.get_serializer(instance=instance, **kwargs)
else:
- serializer = view.get_serializer(data=data)
- if data is not None:
- serializer.is_valid()
+ serializer = view.get_serializer(**kwargs)
+
+ if hasattr(serializer, 'initial_data'):
+ serializer.is_valid()
+
form_renderer = self.form_renderer_class()
return form_renderer.render(
serializer.data,
diff --git a/rest_framework/request.py b/rest_framework/request.py
index 20e049ed..cfbbdecc 100644
--- a/rest_framework/request.py
+++ b/rest_framework/request.py
@@ -277,8 +277,12 @@ class Request(object):
Sets the user on the current request. This is necessary to maintain
compatibility with django.contrib.auth where the user property is
set in the login and logout functions.
+
+ Note that we also set the user on Django's underlying `HttpRequest`
+ instance, ensuring that it is available to any middleware in the stack.
"""
self._user = value
+ self._request.user = value
@property
def auth(self):
@@ -297,6 +301,7 @@ class Request(object):
request, such as an authentication token.
"""
self._auth = value
+ self._request.auth = value
@property
def successful_authenticator(self):
@@ -456,7 +461,7 @@ class Request(object):
if user_auth_tuple is not None:
self._authenticator = authenticator
- self._user, self._auth = user_auth_tuple
+ self.user, self.auth = user_auth_tuple
return
self._not_authenticated()
@@ -471,14 +476,14 @@ class Request(object):
self._authenticator = None
if api_settings.UNAUTHENTICATED_USER:
- self._user = api_settings.UNAUTHENTICATED_USER()
+ self.user = api_settings.UNAUTHENTICATED_USER()
else:
- self._user = None
+ self.user = None
if api_settings.UNAUTHENTICATED_TOKEN:
- self._auth = api_settings.UNAUTHENTICATED_TOKEN()
+ self.auth = api_settings.UNAUTHENTICATED_TOKEN()
else:
- self._auth = None
+ self.auth = None
def __getattr__(self, attr):
"""
diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py
index e9860a2f..8de22f4b 100644
--- a/rest_framework/serializers.py
+++ b/rest_framework/serializers.py
@@ -58,11 +58,31 @@ class BaseSerializer(Field):
"""
The BaseSerializer class provides a minimal class which may be used
for writing custom serializer implementations.
+
+ Note that we strongly restrict the ordering of operations/properties
+ that may be used on the serializer in order to enforce correct usage.
+
+ In particular, if a `data=` argument is passed then:
+
+ .is_valid() - Available.
+ .initial_data - Available.
+ .validated_data - Only available after calling `is_valid()`
+ .errors - Only available after calling `is_valid()`
+ .data - Only available after calling `is_valid()`
+
+ If a `data=` argument is not passed then:
+
+ .is_valid() - Not available.
+ .initial_data - Not available.
+ .validated_data - Not available.
+ .errors - Not available.
+ .data - Available.
"""
- def __init__(self, instance=None, data=None, **kwargs):
+ def __init__(self, instance=None, data=empty, **kwargs):
self.instance = instance
- self._initial_data = data
+ if data is not empty:
+ self.initial_data = data
self.partial = kwargs.pop('partial', False)
self._context = kwargs.pop('context', {})
kwargs.pop('many', None)
@@ -156,9 +176,14 @@ class BaseSerializer(Field):
(self.__class__.__module__, self.__class__.__name__)
)
+ assert hasattr(self, 'initial_data'), (
+ 'Cannot call `.is_valid()` as no `data=` keyword argument was'
+ 'passed when instantiating the serializer instance.'
+ )
+
if not hasattr(self, '_validated_data'):
try:
- self._validated_data = self.run_validation(self._initial_data)
+ self._validated_data = self.run_validation(self.initial_data)
except ValidationError as exc:
self._validated_data = {}
self._errors = exc.detail
@@ -172,6 +197,16 @@ class BaseSerializer(Field):
@property
def data(self):
+ if hasattr(self, 'initial_data') and not hasattr(self, '_validated_data'):
+ msg = (
+ 'When a serializer is passed a `data` keyword argument you '
+ 'must call `.is_valid()` before attempting to access the '
+ 'serialized `.data` representation.\n'
+ 'You should either call `.is_valid()` first, '
+ 'or access `.initial_data` instead.'
+ )
+ raise AssertionError(msg)
+
if not hasattr(self, '_data'):
if self.instance is not None and not getattr(self, '_errors', None):
self._data = self.to_representation(self.instance)
@@ -295,11 +330,11 @@ class Serializer(BaseSerializer):
return getattr(getattr(self, 'Meta', None), 'validators', [])
def get_initial(self):
- if self._initial_data is not None:
+ if hasattr(self, 'initial_data'):
return OrderedDict([
- (field_name, field.get_value(self._initial_data))
+ (field_name, field.get_value(self.initial_data))
for field_name, field in self.fields.items()
- if field.get_value(self._initial_data) is not empty
+ if field.get_value(self.initial_data) is not empty
and not field.read_only
])
@@ -447,8 +482,8 @@ class ListSerializer(BaseSerializer):
self.child.bind(field_name='', parent=self)
def get_initial(self):
- if self._initial_data is not None:
- return self.to_representation(self._initial_data)
+ if hasattr(self, 'initial_data'):
+ return self.to_representation(self.initial_data)
return []
def get_value(self, dictionary):