aboutsummaryrefslogtreecommitdiffstats
path: root/rest_framework/generics.py
diff options
context:
space:
mode:
Diffstat (limited to 'rest_framework/generics.py')
-rw-r--r--rest_framework/generics.py292
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)