diff options
Diffstat (limited to 'rest_framework/generics.py')
| -rw-r--r-- | rest_framework/generics.py | 292 |
1 files changed, 45 insertions, 247 deletions
diff --git a/rest_framework/generics.py b/rest_framework/generics.py index a62da00b..3d6cf168 100644 --- a/rest_framework/generics.py +++ b/rest_framework/generics.py @@ -3,15 +3,14 @@ Generic views that provide commonly needed behaviour. """ from __future__ import unicode_literals -from django.core.exceptions import ImproperlyConfigured, PermissionDenied from django.core.paginator import Paginator, InvalidPage +from django.db.models.query import QuerySet from django.http import Http404 from django.shortcuts import get_object_or_404 as _get_object_or_404 +from django.utils import six from django.utils.translation import ugettext as _ -from rest_framework import views, mixins, exceptions -from rest_framework.request import clone_request +from rest_framework import views, mixins from rest_framework.settings import api_settings -import warnings def strict_positive_int(integer_string, cutoff=None): @@ -28,7 +27,7 @@ def strict_positive_int(integer_string, cutoff=None): def get_object_or_404(queryset, *filter_args, **filter_kwargs): """ - Same as Django's standard shortcut, but make sure to raise 404 + Same as Django's standard shortcut, but make sure to also raise 404 if the filter_kwargs don't match the required types. """ try: @@ -51,11 +50,6 @@ class GenericAPIView(views.APIView): queryset = None serializer_class = None - # This shortcut may be used instead of setting either or both - # of the `queryset`/`serializer_class` attributes, although using - # the explicit style is generally preferred. - model = None - # If you want to use object lookups other than pk, set this attribute. # For more complex lookup requirements override `get_object()`. lookup_field = 'pk' @@ -71,20 +65,10 @@ class GenericAPIView(views.APIView): # The filter backend classes to use for queryset filtering filter_backends = api_settings.DEFAULT_FILTER_BACKENDS - # The following attributes may be subject to change, + # The following attribute may be subject to change, # and should be considered private API. - model_serializer_class = api_settings.DEFAULT_MODEL_SERIALIZER_CLASS paginator_class = Paginator - ###################################### - # These are pending deprecation... - - pk_url_kwarg = 'pk' - slug_url_kwarg = 'slug' - slug_field = 'slug' - allow_empty = True - filter_backend = api_settings.FILTER_BACKEND - def get_serializer_context(self): """ Extra context provided to the serializer class. @@ -95,18 +79,16 @@ class GenericAPIView(views.APIView): 'view': self } - def get_serializer(self, instance=None, data=None, files=None, many=False, - partial=False, allow_add_remove=False): + def get_serializer(self, instance=None, data=None, many=False, partial=False): """ 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, files=files, - many=many, partial=partial, - allow_add_remove=allow_add_remove, - context=context) + return serializer_class( + instance, data=data, many=many, partial=partial, context=context + ) def get_pagination_serializer(self, page): """ @@ -120,39 +102,18 @@ class GenericAPIView(views.APIView): context = self.get_serializer_context() return pagination_serializer_class(instance=page, context=context) - def paginate_queryset(self, queryset, page_size=None): + def paginate_queryset(self, queryset): """ Paginate a queryset if required, either returning a page object, or `None` if pagination is not configured for this view. """ - deprecated_style = False - if page_size is not None: - warnings.warn('The `page_size` parameter to `paginate_queryset()` ' - 'is deprecated. ' - 'Note that the return style of this method is also ' - 'changed, and will simply return a page object ' - 'when called without a `page_size` argument.', - DeprecationWarning, stacklevel=2) - deprecated_style = True - else: - # Determine the required page size. - # If pagination is not configured, simply return None. - page_size = self.get_paginate_by() - if not page_size: - return None - - if not self.allow_empty: - warnings.warn( - 'The `allow_empty` parameter is deprecated. ' - 'To use `allow_empty=False` style behavior, You should override ' - '`get_queryset()` and explicitly raise a 404 on empty querysets.', - DeprecationWarning, stacklevel=2 - ) - - paginator = self.paginator_class(queryset, page_size, - allow_empty_first_page=self.allow_empty) + page_size = self.get_paginate_by() + if not page_size: + return None + + 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) @@ -167,11 +128,9 @@ class GenericAPIView(views.APIView): error_format = _('Invalid page (%(page_number)s): %(message)s') raise Http404(error_format % { 'page_number': page_number, - 'message': str(exc) + 'message': six.text_type(exc) }) - if deprecated_style: - return (paginator, page, page.object_list, page.has_other_pages()) return page def filter_queryset(self, queryset): @@ -191,29 +150,12 @@ class GenericAPIView(views.APIView): """ Returns the list of filter backends that this view requires. """ - if self.filter_backends is None: - filter_backends = [] - else: - # Note that we are returning a *copy* of the class attribute, - # so that it is safe for the view to mutate it if needed. - filter_backends = list(self.filter_backends) - - if not filter_backends and self.filter_backend: - warnings.warn( - 'The `filter_backend` attribute and `FILTER_BACKEND` setting ' - 'are deprecated in favor of a `filter_backends` ' - 'attribute and `DEFAULT_FILTER_BACKENDS` setting, that take ' - 'a *list* of filter backend classes.', - DeprecationWarning, stacklevel=2 - ) - filter_backends = [self.filter_backend] - - return filter_backends + return list(self.filter_backends) # The following methods provide default implementations # that you may want to override for more complex cases. - def get_paginate_by(self, queryset=None): + def get_paginate_by(self): """ Return the size of pages to use with pagination. @@ -222,15 +164,10 @@ class GenericAPIView(views.APIView): Otherwise defaults to using `self.paginate_by`. """ - if queryset is not None: - warnings.warn('The `queryset` parameter to `get_paginate_by()` ' - 'is deprecated.', - DeprecationWarning, stacklevel=2) - 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): @@ -248,26 +185,13 @@ class GenericAPIView(views.APIView): (Eg. admins get full serialization, others get basic serialization) """ - serializer_class = self.serializer_class - if serializer_class is not None: - return serializer_class - - warnings.warn( - 'The `.model` attribute on view classes is now deprecated in favor ' - 'of the more explicit `serializer_class` and `queryset` attributes.', - DeprecationWarning, stacklevel=2 - ) - - assert self.model is not None, \ - "'%s' should either include a 'serializer_class' attribute, " \ - "or use the 'model' attribute as a shortcut for " \ - "automatically generating a serializer class." \ + assert self.serializer_class is not None, ( + "'%s' should either include a `serializer_class` attribute, " + "or override the `get_serializer_class()` method." % self.__class__.__name__ + ) - class DefaultSerializer(self.model_serializer_class): - class Meta: - model = self.model - return DefaultSerializer + return self.serializer_class def get_queryset(self): """ @@ -284,21 +208,19 @@ class GenericAPIView(views.APIView): (Eg. return a list of items that is specific to the user) """ - if self.queryset is not None: - return self.queryset._clone() - - if self.model is not None: - warnings.warn( - 'The `.model` attribute on view classes is now deprecated in favor ' - 'of the more explicit `serializer_class` and `queryset` attributes.', - DeprecationWarning, stacklevel=2 - ) - return self.model._default_manager.all() + assert self.queryset is not None, ( + "'%s' should either include a `queryset` attribute, " + "or override the `get_queryset()` method." + % self.__class__.__name__ + ) - error_format = "'%s' must define 'queryset' or 'model'" - raise ImproperlyConfigured(error_format % self.__class__.__name__) + queryset = self.queryset + if isinstance(queryset, QuerySet): + # Ensure queryset is re-evaluated on each request. + queryset = queryset.all() + return queryset - def get_object(self, queryset=None): + def get_object(self): """ Returns the object the view is displaying. @@ -306,43 +228,19 @@ class GenericAPIView(views.APIView): queryset lookups. Eg if objects are referenced using multiple keyword arguments in the url conf. """ - # Determine the base queryset to use. - if queryset is None: - queryset = self.filter_queryset(self.get_queryset()) - else: - pass # Deprecation warning + queryset = self.filter_queryset(self.get_queryset()) # Perform the lookup filtering. - # Note that `pk` and `slug` are deprecated styles of lookup filtering. lookup_url_kwarg = self.lookup_url_kwarg or self.lookup_field - lookup = self.kwargs.get(lookup_url_kwarg, None) - pk = self.kwargs.get(self.pk_url_kwarg, None) - slug = self.kwargs.get(self.slug_url_kwarg, None) - - if lookup is not None: - filter_kwargs = {self.lookup_field: lookup} - elif pk is not None and self.lookup_field == 'pk': - warnings.warn( - 'The `pk_url_kwarg` attribute is deprecated. ' - 'Use the `lookup_field` attribute instead', - DeprecationWarning - ) - filter_kwargs = {'pk': pk} - elif slug is not None and self.lookup_field == 'pk': - warnings.warn( - 'The `slug_url_kwarg` attribute is deprecated. ' - 'Use the `lookup_field` attribute instead', - DeprecationWarning - ) - filter_kwargs = {self.slug_field: slug} - else: - raise ImproperlyConfigured( - 'Expected view %s to be called with a URL keyword argument ' - 'named "%s". Fix your URL conf, or set the `.lookup_field` ' - 'attribute on the view correctly.' % - (self.__class__.__name__, self.lookup_field) - ) + assert lookup_url_kwarg in self.kwargs, ( + 'Expected view %s to be called with a URL keyword argument ' + 'named "%s". Fix your URL conf, or set the `.lookup_field` ' + 'attribute on the view correctly.' % + (self.__class__.__name__, lookup_url_kwarg) + ) + + filter_kwargs = {self.lookup_field: self.kwargs[lookup_url_kwarg]} obj = get_object_or_404(queryset, **filter_kwargs) # May raise a permission denied @@ -350,84 +248,6 @@ class GenericAPIView(views.APIView): return obj - # The following are placeholder methods, - # and are intended to be overridden. - # - # The are not called by GenericAPIView directly, - # but are used by the mixin methods. - - def pre_save(self, obj): - """ - Placeholder method for calling before saving an object. - - May be used to set attributes on the object that are implicit - in either the request, or the url. - """ - pass - - def post_save(self, obj, created=False): - """ - Placeholder method for calling after saving an object. - """ - pass - - def pre_delete(self, obj): - """ - Placeholder method for calling before deleting an object. - """ - pass - - def post_delete(self, obj): - """ - Placeholder method for calling after deleting an object. - """ - pass - - def metadata(self, request): - """ - Return a dictionary of metadata about the view. - Used to return responses for OPTIONS requests. - - We override the default behavior, and add some extra information - about the required request body for POST and PUT operations. - """ - ret = super(GenericAPIView, self).metadata(request) - - actions = {} - for method in ('PUT', 'POST'): - if method not in self.allowed_methods: - continue - - original_request = self.request - self.request = clone_request(request, method) - try: - # Test global permissions - self.check_permissions(self.request) - # Test object permissions - if method == 'PUT': - try: - self.get_object() - except Http404: - # Http404 should be acceptable and the serializer - # metadata should be populated. Except this so the - # outer "else" clause of the try-except-else block - # will be executed. - pass - except (exceptions.APIException, PermissionDenied): - pass - else: - # If user has appropriate permissions for the view, include - # appropriate metadata about the fields that should be supplied. - serializer = self.get_serializer() - actions[method] = serializer.metadata() - finally: - self.request = original_request - - if actions: - ret['actions'] = actions - - return ret - # Concrete view classes that provide method handlers # by composing the mixin classes with the base view. @@ -543,25 +363,3 @@ class RetrieveUpdateDestroyAPIView(mixins.RetrieveModelMixin, def delete(self, request, *args, **kwargs): return self.destroy(request, *args, **kwargs) - - -# Deprecated classes - -class MultipleObjectAPIView(GenericAPIView): - def __init__(self, *args, **kwargs): - warnings.warn( - 'Subclassing `MultipleObjectAPIView` is deprecated. ' - 'You should simply subclass `GenericAPIView` instead.', - DeprecationWarning, stacklevel=2 - ) - super(MultipleObjectAPIView, self).__init__(*args, **kwargs) - - -class SingleObjectAPIView(GenericAPIView): - def __init__(self, *args, **kwargs): - warnings.warn( - 'Subclassing `SingleObjectAPIView` is deprecated. ' - 'You should simply subclass `GenericAPIView` instead.', - DeprecationWarning, stacklevel=2 - ) - super(SingleObjectAPIView, self).__init__(*args, **kwargs) |
