/rest_framework/generics.py
Python | 543 lines | 303 code | 83 blank | 157 comment | 45 complexity | 590bee1554f1607c864f9ad6f14a39c7 MD5 | raw file
- """
- 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.http import Http404
- from django.shortcuts import get_object_or_404 as _get_object_or_404
- from django.utils.translation import ugettext as _
- from rest_framework import views, mixins, exceptions
- from rest_framework.request import clone_request
- from rest_framework.settings import api_settings
- import warnings
- def strict_positive_int(integer_string, cutoff=None):
- """
- Cast a string to a strictly positive integer.
- """
- ret = int(integer_string)
- if ret <= 0:
- raise ValueError()
- if cutoff:
- ret = min(ret, cutoff)
- return ret
- def get_object_or_404(queryset, *filter_args, **filter_kwargs):
- """
- Same as Django's standard shortcut, but make sure to raise 404
- if the filter_kwargs don't match the required types.
- """
- try:
- return _get_object_or_404(queryset, *filter_args, **filter_kwargs)
- except (TypeError, ValueError):
- raise Http404
- class GenericAPIView(views.APIView):
- """
- Base class for all other generic views.
- """
- # You'll need to either set these attributes,
- # or override `get_queryset()`/`get_serializer_class()`.
- 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'
- lookup_url_kwarg = None
- # Pagination settings
- paginate_by = api_settings.PAGINATE_BY
- paginate_by_param = api_settings.PAGINATE_BY_PARAM
- max_paginate_by = api_settings.MAX_PAGINATE_BY
- pagination_serializer_class = api_settings.DEFAULT_PAGINATION_SERIALIZER_CLASS
- page_kwarg = 'page'
- # The filter backend classes to use for queryset filtering
- filter_backends = api_settings.DEFAULT_FILTER_BACKENDS
- # The following attributes 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.
- """
- return {
- 'request': self.request,
- 'format': self.format_kwarg,
- 'view': self
- }
- def get_serializer(self, instance=None, data=None, files=None, many=False,
- partial=False, allow_add_remove=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)
- def get_pagination_serializer(self, page):
- """
- Return a serializer instance to use with paginated data.
- """
- class SerializerClass(self.pagination_serializer_class):
- class Meta:
- object_serializer_class = self.get_serializer_class()
- pagination_serializer_class = SerializerClass
- context = self.get_serializer_context()
- return pagination_serializer_class(instance=page, context=context)
- def paginate_queryset(self, queryset, page_size=None):
- """
- 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 due to be 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.',
- PendingDeprecationWarning, 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 due to be deprecated. '
- 'To use `allow_empty=False` style behavior, You should override '
- '`get_queryset()` and explicitly raise a 404 on empty querysets.',
- PendingDeprecationWarning, stacklevel=2
- )
- paginator = self.paginator_class(queryset, page_size,
- allow_empty_first_page=self.allow_empty)
- page_kwarg = self.kwargs.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)
- except InvalidPage:
- if page == 'last':
- page_number = paginator.num_pages
- else:
- raise Http404(_("Page is not 'last', nor can it be converted to an int."))
- try:
- page = paginator.page(page_number)
- except InvalidPage as e:
- raise Http404(_('Invalid page (%(page_number)s): %(message)s') % {
- 'page_number': page_number,
- 'message': str(e)
- })
- if deprecated_style:
- return (paginator, page, page.object_list, page.has_other_pages())
- return page
- def filter_queryset(self, queryset):
- """
- Given a queryset, filter it with whichever filter backend is in use.
- You are unlikely to want to override this method, although you may need
- to call it either from a list view, or from a custom `get_object`
- method if you want to apply the configured filtering backend to the
- default queryset.
- """
- for backend in self.get_filter_backends():
- queryset = backend().filter_queryset(self.request, queryset, self)
- return queryset
- def get_filter_backends(self):
- """
- Returns the list of filter backends that this view requires.
- """
- filter_backends = self.filter_backends or []
- if not filter_backends and self.filter_backend:
- warnings.warn(
- 'The `filter_backend` attribute and `FILTER_BACKEND` setting '
- 'are due to be deprecated in favor of a `filter_backends` '
- 'attribute and `DEFAULT_FILTER_BACKENDS` setting, that take '
- 'a *list* of filter backend classes.',
- PendingDeprecationWarning, stacklevel=2
- )
- filter_backends = [self.filter_backend]
- return 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):
- """
- Return the size of pages to use with pagination.
- If `PAGINATE_BY_PARAM` is set it will attempt to get the page size
- from a named query parameter in the url, eg. ?page_size=100
- Otherwise defaults to using `self.paginate_by`.
- """
- if queryset is not None:
- warnings.warn('The `queryset` parameter to `get_paginate_by()` '
- 'is due to be deprecated.',
- PendingDeprecationWarning, stacklevel=2)
- if self.paginate_by_param:
- try:
- return strict_positive_int(
- self.request.QUERY_PARAMS[self.paginate_by_param],
- cutoff=self.max_paginate_by
- )
- except (KeyError, ValueError):
- pass
- return self.paginate_by
- def get_serializer_class(self):
- """
- Return the class to use for the serializer.
- Defaults to using `self.serializer_class`.
- You may want to override this if you need to provide different
- serializations depending on the incoming request.
- (Eg. admins get full serialization, others get basic serialization)
- """
- serializer_class = self.serializer_class
- if serializer_class is not None:
- return serializer_class
- 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." \
- % self.__class__.__name__
- class DefaultSerializer(self.model_serializer_class):
- class Meta:
- model = self.model
- return DefaultSerializer
- def get_queryset(self):
- """
- Get the list of items for this view.
- This must be an iterable, and may be a queryset.
- Defaults to using `self.queryset`.
- You may want to override this if you need to provide different
- querysets depending on the incoming request.
- (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:
- return self.model._default_manager.all()
- raise ImproperlyConfigured("'%s' must define 'queryset' or 'model'"
- % self.__class__.__name__)
- def get_object(self, queryset=None):
- """
- Returns the object the view is displaying.
- You may want to override this if you need to provide non-standard
- 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
- # 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 due to be deprecated. '
- 'Use the `lookup_field` attribute instead',
- PendingDeprecationWarning
- )
- filter_kwargs = {'pk': pk}
- elif slug is not None and self.lookup_field == 'pk':
- warnings.warn(
- 'The `slug_url_kwarg` attribute is due to be deprecated. '
- 'Use the `lookup_field` attribute instead',
- PendingDeprecationWarning
- )
- 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)
- )
- obj = get_object_or_404(queryset, **filter_kwargs)
- # May raise a permission denied
- self.check_object_permissions(self.request, obj)
- 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
- cloned_request = clone_request(request, method)
- try:
- # Test global permissions
- self.check_permissions(cloned_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()
- if actions:
- ret['actions'] = actions
- return ret
- ##########################################################
- ### Concrete view classes that provide method handlers ###
- ### by composing the mixin classes with the base view. ###
- ##########################################################
- class CreateAPIView(mixins.CreateModelMixin,
- GenericAPIView):
- """
- Concrete view for creating a model instance.
- """
- def post(self, request, *args, **kwargs):
- return self.create(request, *args, **kwargs)
- class ListAPIView(mixins.ListModelMixin,
- GenericAPIView):
- """
- Concrete view for listing a queryset.
- """
- def get(self, request, *args, **kwargs):
- return self.list(request, *args, **kwargs)
- class RetrieveAPIView(mixins.RetrieveModelMixin,
- GenericAPIView):
- """
- Concrete view for retrieving a model instance.
- """
- def get(self, request, *args, **kwargs):
- return self.retrieve(request, *args, **kwargs)
- class DestroyAPIView(mixins.DestroyModelMixin,
- GenericAPIView):
- """
- Concrete view for deleting a model instance.
- """
- def delete(self, request, *args, **kwargs):
- return self.destroy(request, *args, **kwargs)
- class UpdateAPIView(mixins.UpdateModelMixin,
- GenericAPIView):
- """
- Concrete view for updating a model instance.
- """
- def put(self, request, *args, **kwargs):
- return self.update(request, *args, **kwargs)
- def patch(self, request, *args, **kwargs):
- return self.partial_update(request, *args, **kwargs)
- class ListCreateAPIView(mixins.ListModelMixin,
- mixins.CreateModelMixin,
- GenericAPIView):
- """
- Concrete view for listing a queryset or creating a model instance.
- """
- def get(self, request, *args, **kwargs):
- return self.list(request, *args, **kwargs)
- def post(self, request, *args, **kwargs):
- return self.create(request, *args, **kwargs)
- class RetrieveUpdateAPIView(mixins.RetrieveModelMixin,
- mixins.UpdateModelMixin,
- GenericAPIView):
- """
- Concrete view for retrieving, updating a model instance.
- """
- def get(self, request, *args, **kwargs):
- return self.retrieve(request, *args, **kwargs)
- def put(self, request, *args, **kwargs):
- return self.update(request, *args, **kwargs)
- def patch(self, request, *args, **kwargs):
- return self.partial_update(request, *args, **kwargs)
- class RetrieveDestroyAPIView(mixins.RetrieveModelMixin,
- mixins.DestroyModelMixin,
- GenericAPIView):
- """
- Concrete view for retrieving or deleting a model instance.
- """
- def get(self, request, *args, **kwargs):
- return self.retrieve(request, *args, **kwargs)
- def delete(self, request, *args, **kwargs):
- return self.destroy(request, *args, **kwargs)
- class RetrieveUpdateDestroyAPIView(mixins.RetrieveModelMixin,
- mixins.UpdateModelMixin,
- mixins.DestroyModelMixin,
- GenericAPIView):
- """
- Concrete view for retrieving, updating or deleting a model instance.
- """
- def get(self, request, *args, **kwargs):
- return self.retrieve(request, *args, **kwargs)
- def put(self, request, *args, **kwargs):
- return self.update(request, *args, **kwargs)
- def patch(self, request, *args, **kwargs):
- return self.partial_update(request, *args, **kwargs)
- 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 due to be deprecated. '
- 'You should simply subclass `GenericAPIView` instead.',
- PendingDeprecationWarning, stacklevel=2
- )
- super(MultipleObjectAPIView, self).__init__(*args, **kwargs)
- class SingleObjectAPIView(GenericAPIView):
- def __init__(self, *args, **kwargs):
- warnings.warn(
- 'Subclassing `SingleObjectAPIView` is due to be deprecated. '
- 'You should simply subclass `GenericAPIView` instead.',
- PendingDeprecationWarning, stacklevel=2
- )
- super(SingleObjectAPIView, self).__init__(*args, **kwargs)