diff options
| author | Tom Christie | 2014-09-26 10:46:52 +0100 | 
|---|---|---|
| committer | Tom Christie | 2014-09-26 10:46:52 +0100 | 
| commit | 2859eaf524bca23f27e666d24a0b63ba61698a76 (patch) | |
| tree | 8acd1be737f9052577e130608a764d6197551d93 /rest_framework | |
| parent | 417fe1b675bd1d42518fb89a6f81547caef5b735 (diff) | |
| download | django-rest-framework-2859eaf524bca23f27e666d24a0b63ba61698a76.tar.bz2 | |
request.data attribute
Diffstat (limited to 'rest_framework')
| -rw-r--r-- | rest_framework/authtoken/views.py | 2 | ||||
| -rw-r--r-- | rest_framework/fields.py | 51 | ||||
| -rw-r--r-- | rest_framework/filters.py | 6 | ||||
| -rw-r--r-- | rest_framework/generics.py | 4 | ||||
| -rw-r--r-- | rest_framework/mixins.py | 6 | ||||
| -rw-r--r-- | rest_framework/negotiation.py | 4 | ||||
| -rw-r--r-- | rest_framework/renderers.py | 4 | ||||
| -rw-r--r-- | rest_framework/request.py | 37 | ||||
| -rw-r--r-- | rest_framework/serializers.py | 21 | 
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) | 
