aboutsummaryrefslogtreecommitdiffstats
path: root/rest_framework
diff options
context:
space:
mode:
authorTom Christie2014-09-26 10:46:52 +0100
committerTom Christie2014-09-26 10:46:52 +0100
commit2859eaf524bca23f27e666d24a0b63ba61698a76 (patch)
tree8acd1be737f9052577e130608a764d6197551d93 /rest_framework
parent417fe1b675bd1d42518fb89a6f81547caef5b735 (diff)
downloaddjango-rest-framework-2859eaf524bca23f27e666d24a0b63ba61698a76.tar.bz2
request.data attribute
Diffstat (limited to 'rest_framework')
-rw-r--r--rest_framework/authtoken/views.py2
-rw-r--r--rest_framework/fields.py51
-rw-r--r--rest_framework/filters.py6
-rw-r--r--rest_framework/generics.py4
-rw-r--r--rest_framework/mixins.py6
-rw-r--r--rest_framework/negotiation.py4
-rw-r--r--rest_framework/renderers.py4
-rw-r--r--rest_framework/request.py37
-rw-r--r--rest_framework/serializers.py21
9 files changed, 86 insertions, 49 deletions
diff --git a/rest_framework/authtoken/views.py b/rest_framework/authtoken/views.py
index 94e6f061..103abb27 100644
--- a/rest_framework/authtoken/views.py
+++ b/rest_framework/authtoken/views.py
@@ -16,7 +16,7 @@ class ObtainAuthToken(APIView):
model = Token
def post(self, request):
- serializer = self.serializer_class(data=request.DATA)
+ serializer = self.serializer_class(data=request.data)
if serializer.is_valid():
user = serializer.validated_data['user']
token, created = Token.objects.get_or_create(user=user)
diff --git a/rest_framework/fields.py b/rest_framework/fields.py
index 032bfd04..ec07a413 100644
--- a/rest_framework/fields.py
+++ b/rest_framework/fields.py
@@ -56,7 +56,7 @@ def get_attribute(instance, attrs):
except AttributeError as exc:
try:
return instance[attr]
- except (KeyError, TypeError):
+ except (KeyError, TypeError, AttributeError):
raise exc
return instance
@@ -90,6 +90,7 @@ NOT_READ_ONLY_WRITE_ONLY = 'May not set both `read_only` and `write_only`'
NOT_READ_ONLY_REQUIRED = 'May not set both `read_only` and `required`'
NOT_READ_ONLY_DEFAULT = 'May not set both `read_only` and `default`'
NOT_REQUIRED_DEFAULT = 'May not set both `required` and `default`'
+USE_READONLYFIELD = 'Field(read_only=True) should be ReadOnlyField'
MISSING_ERROR_MESSAGE = (
'ValidationError raised by `{class_name}`, but error key `{key}` does '
'not exist in the `error_messages` dictionary.'
@@ -105,9 +106,10 @@ class Field(object):
}
default_validators = []
default_empty_html = None
+ initial = None
def __init__(self, read_only=False, write_only=False,
- required=None, default=empty, initial=None, source=None,
+ required=None, default=empty, initial=empty, source=None,
label=None, help_text=None, style=None,
error_messages=None, validators=[], allow_null=False):
self._creation_counter = Field._creation_counter
@@ -122,13 +124,14 @@ class Field(object):
assert not (read_only and required), NOT_READ_ONLY_REQUIRED
assert not (read_only and default is not empty), NOT_READ_ONLY_DEFAULT
assert not (required and default is not empty), NOT_REQUIRED_DEFAULT
+ assert not (read_only and self.__class__ == Field), USE_READONLYFIELD
self.read_only = read_only
self.write_only = write_only
self.required = required
self.default = default
self.source = source
- self.initial = initial
+ self.initial = self.initial if (initial is empty) else initial
self.label = label
self.help_text = help_text
self.style = {} if style is None else style
@@ -146,24 +149,10 @@ class Field(object):
messages.update(error_messages or {})
self.error_messages = messages
- def __new__(cls, *args, **kwargs):
- """
- When a field is instantiated, we store the arguments that were used,
- so that we can present a helpful representation of the object.
- """
- instance = super(Field, cls).__new__(cls)
- instance._args = args
- instance._kwargs = kwargs
- return instance
-
- def __deepcopy__(self, memo):
- args = copy.deepcopy(self._args)
- kwargs = copy.deepcopy(self._kwargs)
- return self.__class__(*args, **kwargs)
-
def bind(self, field_name, parent):
"""
- Setup the context for the field instance.
+ Initializes the field name and parent for the field instance.
+ Called when a field is added to the parent serializer instance.
"""
# In order to enforce a consistent style, we error if a redundant
@@ -244,9 +233,9 @@ class Field(object):
validated data.
"""
if data is empty:
+ if getattr(self.root, 'partial', False):
+ raise SkipField()
if self.required:
- if getattr(self.root, 'partial', False):
- raise SkipField()
self.fail('required')
return self.get_default()
@@ -314,6 +303,25 @@ class Field(object):
"""
return getattr(self.root, '_context', {})
+ def __new__(cls, *args, **kwargs):
+ """
+ When a field is instantiated, we store the arguments that were used,
+ so that we can present a helpful representation of the object.
+ """
+ instance = super(Field, cls).__new__(cls)
+ instance._args = args
+ instance._kwargs = kwargs
+ return instance
+
+ def __deepcopy__(self, memo):
+ """
+ When cloning fields we instantiate using the arguments it was
+ originally created with, rather than copying the complete state.
+ """
+ args = copy.deepcopy(self._args)
+ kwargs = copy.deepcopy(self._kwargs)
+ return self.__class__(*args, **kwargs)
+
def __repr__(self):
"""
Fields are represented using their initial calling arguments.
@@ -358,6 +366,7 @@ class NullBooleanField(Field):
'invalid': _('`{input}` is not a valid boolean.')
}
default_empty_html = None
+ initial = None
TRUE_VALUES = set(('t', 'T', 'true', 'True', 'TRUE', '1', 1, True))
FALSE_VALUES = set(('f', 'F', 'false', 'False', 'FALSE', '0', 0, 0.0, False))
NULL_VALUES = set(('n', 'N', 'null', 'Null', 'NULL', '', None))
diff --git a/rest_framework/filters.py b/rest_framework/filters.py
index c580f935..085dfe65 100644
--- a/rest_framework/filters.py
+++ b/rest_framework/filters.py
@@ -64,7 +64,7 @@ class DjangoFilterBackend(BaseFilterBackend):
filter_class = self.get_filter_class(view, queryset)
if filter_class:
- return filter_class(request.QUERY_PARAMS, queryset=queryset).qs
+ return filter_class(request.query_params, queryset=queryset).qs
return queryset
@@ -78,7 +78,7 @@ class SearchFilter(BaseFilterBackend):
Search terms are set by a ?search=... query parameter,
and may be comma and/or whitespace delimited.
"""
- params = request.QUERY_PARAMS.get(self.search_param, '')
+ params = request.query_params.get(self.search_param, '')
return params.replace(',', ' ').split()
def construct_search(self, field_name):
@@ -121,7 +121,7 @@ class OrderingFilter(BaseFilterBackend):
the `ordering_param` value on the OrderingFilter or by
specifying an `ORDERING_PARAM` value in the API settings.
"""
- params = request.QUERY_PARAMS.get(self.ordering_param)
+ params = request.query_params.get(self.ordering_param)
if params:
return [param.strip() for param in params.split(',')]
diff --git a/rest_framework/generics.py b/rest_framework/generics.py
index f49b0a43..cf903dab 100644
--- a/rest_framework/generics.py
+++ b/rest_framework/generics.py
@@ -112,7 +112,7 @@ class GenericAPIView(views.APIView):
paginator = self.paginator_class(queryset, page_size)
page_kwarg = self.kwargs.get(self.page_kwarg)
- page_query_param = self.request.QUERY_PARAMS.get(self.page_kwarg)
+ page_query_param = self.request.query_params.get(self.page_kwarg)
page = page_kwarg or page_query_param or 1
try:
page_number = paginator.validate_number(page)
@@ -166,7 +166,7 @@ class GenericAPIView(views.APIView):
if self.paginate_by_param:
try:
return strict_positive_int(
- self.request.QUERY_PARAMS[self.paginate_by_param],
+ self.request.query_params[self.paginate_by_param],
cutoff=self.max_paginate_by
)
except (KeyError, ValueError):
diff --git a/rest_framework/mixins.py b/rest_framework/mixins.py
index 14a6b44b..04b7a763 100644
--- a/rest_framework/mixins.py
+++ b/rest_framework/mixins.py
@@ -18,7 +18,7 @@ class CreateModelMixin(object):
Create a model instance.
"""
def create(self, request, *args, **kwargs):
- serializer = self.get_serializer(data=request.DATA)
+ serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
serializer.save()
headers = self.get_success_headers(serializer.data)
@@ -62,7 +62,7 @@ class UpdateModelMixin(object):
def update(self, request, *args, **kwargs):
partial = kwargs.pop('partial', False)
instance = self.get_object()
- serializer = self.get_serializer(instance, data=request.DATA, partial=partial)
+ serializer = self.get_serializer(instance, data=request.data, partial=partial)
serializer.is_valid(raise_exception=True)
serializer.save()
return Response(serializer.data)
@@ -95,7 +95,7 @@ class AllowPUTAsCreateMixin(object):
def update(self, request, *args, **kwargs):
partial = kwargs.pop('partial', False)
instance = self.get_object_or_none()
- serializer = self.get_serializer(instance, data=request.DATA, partial=partial)
+ serializer = self.get_serializer(instance, data=request.data, partial=partial)
serializer.is_valid(raise_exception=True)
if instance is None:
diff --git a/rest_framework/negotiation.py b/rest_framework/negotiation.py
index ca7b5397..1838130a 100644
--- a/rest_framework/negotiation.py
+++ b/rest_framework/negotiation.py
@@ -38,7 +38,7 @@ class DefaultContentNegotiation(BaseContentNegotiation):
"""
# Allow URL style format override. eg. "?format=json
format_query_param = self.settings.URL_FORMAT_OVERRIDE
- format = format_suffix or request.QUERY_PARAMS.get(format_query_param)
+ format = format_suffix or request.query_params.get(format_query_param)
if format:
renderers = self.filter_renderers(renderers, format)
@@ -87,5 +87,5 @@ class DefaultContentNegotiation(BaseContentNegotiation):
Allows URL style accept override. eg. "?accept=application/json"
"""
header = request.META.get('HTTP_ACCEPT', '*/*')
- header = request.QUERY_PARAMS.get(self.settings.URL_ACCEPT_OVERRIDE, header)
+ header = request.query_params.get(self.settings.URL_ACCEPT_OVERRIDE, header)
return [token.strip() for token in header.split(',')]
diff --git a/rest_framework/renderers.py b/rest_framework/renderers.py
index 3bf03e62..225f9fe8 100644
--- a/rest_framework/renderers.py
+++ b/rest_framework/renderers.py
@@ -120,7 +120,7 @@ class JSONPRenderer(JSONRenderer):
Determine the name of the callback to wrap around the json output.
"""
request = renderer_context.get('request', None)
- params = request and request.QUERY_PARAMS or {}
+ params = request and request.query_params or {}
return params.get(self.callback_parameter, self.default_callback)
def render(self, data, accepted_media_type=None, renderer_context=None):
@@ -426,7 +426,7 @@ class BrowsableAPIRenderer(BaseRenderer):
"""
if request.method == method:
try:
- data = request.DATA
+ data = request.data
# files = request.FILES
except ParseError:
data = None
diff --git a/rest_framework/request.py b/rest_framework/request.py
index 27532661..d80baa70 100644
--- a/rest_framework/request.py
+++ b/rest_framework/request.py
@@ -4,7 +4,7 @@ The Request class is used as a wrapper around the standard request object.
The wrapped request then offers a richer API, in particular :
- content automatically parsed according to `Content-Type` header,
- and available as `request.DATA`
+ and available as `request.data`
- full support of PUT method, including support for file uploads
- form overloading of HTTP method, content type and content
"""
@@ -13,6 +13,7 @@ from django.conf import settings
from django.http import QueryDict
from django.http.multipartparser import parse_header
from django.utils.datastructures import MultiValueDict
+from django.utils.datastructures import MergeDict as DjangoMergeDict
from rest_framework import HTTP_HEADER_ENCODING
from rest_framework import exceptions
from rest_framework.compat import BytesIO
@@ -58,6 +59,15 @@ class override_method(object):
self.view.action = self.action
+class MergeDict(DjangoMergeDict, dict):
+ """
+ Using this as a workaround until the parsers API is properly
+ addressed in 3.1.
+ """
+ def __init__(self, *dicts):
+ self.dicts = dicts
+
+
class Empty(object):
"""
Placeholder for unset attributes.
@@ -82,6 +92,7 @@ def clone_request(request, method):
parser_context=request.parser_context)
ret._data = request._data
ret._files = request._files
+ ret._full_data = request._full_data
ret._content_type = request._content_type
ret._stream = request._stream
ret._method = method
@@ -133,6 +144,7 @@ class Request(object):
self.parser_context = parser_context
self._data = Empty
self._files = Empty
+ self._full_data = Empty
self._method = Empty
self._content_type = Empty
self._stream = Empty
@@ -186,13 +198,26 @@ class Request(object):
return self._stream
@property
- def QUERY_PARAMS(self):
+ def query_params(self):
"""
More semantically correct name for request.GET.
"""
return self._request.GET
@property
+ def QUERY_PARAMS(self):
+ """
+ Synonym for `.query_params`, for backwards compatibility.
+ """
+ return self._request.GET
+
+ @property
+ def data(self):
+ if not _hasattr(self, '_full_data'):
+ self._load_data_and_files()
+ return self._full_data
+
+ @property
def DATA(self):
"""
Parses the request body and returns the data.
@@ -272,6 +297,10 @@ class Request(object):
if not _hasattr(self, '_data'):
self._data, self._files = self._parse()
+ if self._files:
+ self._full_data = MergeDict(self._data, self._files)
+ else:
+ self._full_data = self._data
def _load_method_and_content_type(self):
"""
@@ -333,6 +362,7 @@ class Request(object):
# At this point we're committed to parsing the request as form data.
self._data = self._request.POST
self._files = self._request.FILES
+ self._full_data = MergeDict(self._data, self._files)
# Method overloading - change the method and remove the param from the content.
if (
@@ -350,7 +380,7 @@ class Request(object):
):
self._content_type = self._data[self._CONTENTTYPE_PARAM]
self._stream = BytesIO(self._data[self._CONTENT_PARAM].encode(self.parser_context['encoding']))
- self._data, self._files = (Empty, Empty)
+ self._data, self._files, self._full_data = (Empty, Empty, Empty)
def _parse(self):
"""
@@ -380,6 +410,7 @@ class Request(object):
# logging the request or similar.
self._data = QueryDict('', encoding=self._request._encoding)
self._files = MultiValueDict()
+ self._full_data = self._data
raise
# Parser classes may return the raw data, or a
diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py
index b6a1898c..a2b878ec 100644
--- a/rest_framework/serializers.py
+++ b/rest_framework/serializers.py
@@ -57,21 +57,24 @@ class BaseSerializer(Field):
def to_representation(self, instance):
raise NotImplementedError('`to_representation()` must be implemented.')
- def update(self, instance, attrs):
+ def update(self, instance, validated_data):
raise NotImplementedError('`update()` must be implemented.')
- def create(self, attrs):
+ def create(self, validated_data):
raise NotImplementedError('`create()` must be implemented.')
def save(self, extras=None):
- attrs = self.validated_data
+ validated_data = self.validated_data
if extras is not None:
- attrs = dict(list(attrs.items()) + list(extras.items()))
+ validated_data = dict(
+ list(validated_data.items()) +
+ list(extras.items())
+ )
if self.instance is not None:
- self.update(self.instance, attrs)
+ self.update(self.instance, validated_data)
else:
- self.instance = self.create(attrs)
+ self.instance = self.create(validated_data)
return self.instance
@@ -321,12 +324,6 @@ class ListSerializer(BaseSerializer):
def create(self, attrs_list):
return [self.child.create(attrs) for attrs in attrs_list]
- def save(self):
- if self.instance is not None:
- self.update(self.instance, self.validated_data)
- self.instance = self.create(self.validated_data)
- return self.instance
-
def __repr__(self):
return representation.list_repr(self, indent=1)