mirror of
https://github.com/encode/django-rest-framework.git
synced 2025-08-04 12:30:11 +03:00
Merge 4ac4676a40
into efaa37376c
This commit is contained in:
commit
84fe1feceb
|
@ -19,8 +19,8 @@ Typically when using the generic views, you'll override the view, and set severa
|
||||||
|
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
from myapp.serializers import UserSerializer
|
from myapp.serializers import UserSerializer
|
||||||
from rest_framework import generics
|
from rest_framework import generics
|
||||||
from rest_framework.permissions import IsAdminUser
|
from rest_framework.permissions import IsAdminUser
|
||||||
|
|
||||||
class UserList(generics.ListCreateAPIView):
|
class UserList(generics.ListCreateAPIView):
|
||||||
queryset = User.objects.all()
|
queryset = User.objects.all()
|
||||||
|
@ -212,8 +212,6 @@ Provides a `.list(request, *args, **kwargs)` method, that implements listing a q
|
||||||
|
|
||||||
If the queryset is populated, this returns a `200 OK` response, with a serialized representation of the queryset as the body of the response. The response data may optionally be paginated.
|
If the queryset is populated, this returns a `200 OK` response, with a serialized representation of the queryset as the body of the response. The response data may optionally be paginated.
|
||||||
|
|
||||||
If the queryset is empty this returns a `200 OK` response, unless the `.allow_empty` attribute on the view is set to `False`, in which case it will return a `404 Not Found`.
|
|
||||||
|
|
||||||
## CreateModelMixin
|
## CreateModelMixin
|
||||||
|
|
||||||
Provides a `.create(request, *args, **kwargs)` method, that implements creating and saving a new model instance.
|
Provides a `.create(request, *args, **kwargs)` method, that implements creating and saving a new model instance.
|
||||||
|
|
|
@ -10,7 +10,6 @@ from __future__ import unicode_literals
|
||||||
from django.utils import six
|
from django.utils import six
|
||||||
from rest_framework.views import APIView
|
from rest_framework.views import APIView
|
||||||
import types
|
import types
|
||||||
import warnings
|
|
||||||
|
|
||||||
|
|
||||||
def api_view(http_method_names):
|
def api_view(http_method_names):
|
||||||
|
@ -130,37 +129,3 @@ def list_route(methods=['get'], **kwargs):
|
||||||
func.kwargs = kwargs
|
func.kwargs = kwargs
|
||||||
return func
|
return func
|
||||||
return decorator
|
return decorator
|
||||||
|
|
||||||
|
|
||||||
# These are now pending deprecation, in favor of `detail_route` and `list_route`.
|
|
||||||
|
|
||||||
def link(**kwargs):
|
|
||||||
"""
|
|
||||||
Used to mark a method on a ViewSet that should be routed for detail GET requests.
|
|
||||||
"""
|
|
||||||
msg = 'link is pending deprecation. Use detail_route instead.'
|
|
||||||
warnings.warn(msg, PendingDeprecationWarning, stacklevel=2)
|
|
||||||
|
|
||||||
def decorator(func):
|
|
||||||
func.bind_to_methods = ['get']
|
|
||||||
func.detail = True
|
|
||||||
func.kwargs = kwargs
|
|
||||||
return func
|
|
||||||
|
|
||||||
return decorator
|
|
||||||
|
|
||||||
|
|
||||||
def action(methods=['post'], **kwargs):
|
|
||||||
"""
|
|
||||||
Used to mark a method on a ViewSet that should be routed for detail POST requests.
|
|
||||||
"""
|
|
||||||
msg = 'action is pending deprecation. Use detail_route instead.'
|
|
||||||
warnings.warn(msg, PendingDeprecationWarning, stacklevel=2)
|
|
||||||
|
|
||||||
def decorator(func):
|
|
||||||
func.bind_to_methods = methods
|
|
||||||
func.detail = True
|
|
||||||
func.kwargs = kwargs
|
|
||||||
return func
|
|
||||||
|
|
||||||
return decorator
|
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -3,7 +3,7 @@ Generic views that provide commonly needed behaviour.
|
||||||
"""
|
"""
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from django.core.exceptions import ImproperlyConfigured, PermissionDenied
|
from django.core.exceptions import PermissionDenied
|
||||||
from django.core.paginator import Paginator, InvalidPage
|
from django.core.paginator import Paginator, InvalidPage
|
||||||
from django.http import Http404
|
from django.http import Http404
|
||||||
from django.shortcuts import get_object_or_404 as _get_object_or_404
|
from django.shortcuts import get_object_or_404 as _get_object_or_404
|
||||||
|
@ -11,7 +11,6 @@ from django.utils.translation import ugettext as _
|
||||||
from rest_framework import views, mixins, exceptions
|
from rest_framework import views, mixins, exceptions
|
||||||
from rest_framework.request import clone_request
|
from rest_framework.request import clone_request
|
||||||
from rest_framework.settings import api_settings
|
from rest_framework.settings import api_settings
|
||||||
import warnings
|
|
||||||
|
|
||||||
|
|
||||||
def strict_positive_int(integer_string, cutoff=None):
|
def strict_positive_int(integer_string, cutoff=None):
|
||||||
|
@ -51,11 +50,6 @@ class GenericAPIView(views.APIView):
|
||||||
queryset = None
|
queryset = None
|
||||||
serializer_class = 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.
|
# If you want to use object lookups other than pk, set this attribute.
|
||||||
# For more complex lookup requirements override `get_object()`.
|
# For more complex lookup requirements override `get_object()`.
|
||||||
lookup_field = 'pk'
|
lookup_field = 'pk'
|
||||||
|
@ -71,20 +65,10 @@ class GenericAPIView(views.APIView):
|
||||||
# The filter backend classes to use for queryset filtering
|
# The filter backend classes to use for queryset filtering
|
||||||
filter_backends = api_settings.DEFAULT_FILTER_BACKENDS
|
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.
|
# and should be considered private API.
|
||||||
model_serializer_class = api_settings.DEFAULT_MODEL_SERIALIZER_CLASS
|
|
||||||
paginator_class = Paginator
|
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):
|
def get_serializer_context(self):
|
||||||
"""
|
"""
|
||||||
Extra context provided to the serializer class.
|
Extra context provided to the serializer class.
|
||||||
|
@ -95,18 +79,16 @@ class GenericAPIView(views.APIView):
|
||||||
'view': self
|
'view': self
|
||||||
}
|
}
|
||||||
|
|
||||||
def get_serializer(self, instance=None, data=None, files=None, many=False,
|
def get_serializer(self, instance=None, data=None, many=False, partial=False):
|
||||||
partial=False, allow_add_remove=False):
|
|
||||||
"""
|
"""
|
||||||
Return the serializer instance that should be used for validating and
|
Return the serializer instance that should be used for validating and
|
||||||
deserializing input, and for serializing output.
|
deserializing input, and for serializing output.
|
||||||
"""
|
"""
|
||||||
serializer_class = self.get_serializer_class()
|
serializer_class = self.get_serializer_class()
|
||||||
context = self.get_serializer_context()
|
context = self.get_serializer_context()
|
||||||
return serializer_class(instance, data=data, files=files,
|
return serializer_class(
|
||||||
many=many, partial=partial,
|
instance, data=data, many=many, partial=partial, context=context
|
||||||
allow_add_remove=allow_add_remove,
|
)
|
||||||
context=context)
|
|
||||||
|
|
||||||
def get_pagination_serializer(self, page):
|
def get_pagination_serializer(self, page):
|
||||||
"""
|
"""
|
||||||
|
@ -120,37 +102,16 @@ class GenericAPIView(views.APIView):
|
||||||
context = self.get_serializer_context()
|
context = self.get_serializer_context()
|
||||||
return pagination_serializer_class(instance=page, context=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,
|
Paginate a queryset if required, either returning a page object,
|
||||||
or `None` if pagination is not configured for this view.
|
or `None` if pagination is not configured for this view.
|
||||||
"""
|
"""
|
||||||
deprecated_style = False
|
page_size = self.get_paginate_by()
|
||||||
if page_size is not None:
|
if not page_size:
|
||||||
warnings.warn('The `page_size` parameter to `paginate_queryset()` '
|
return None
|
||||||
'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:
|
paginator = self.paginator_class(queryset, page_size)
|
||||||
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_kwarg = self.kwargs.get(self.page_kwarg)
|
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
|
page = page_kwarg or page_query_param or 1
|
||||||
|
@ -170,8 +131,6 @@ class GenericAPIView(views.APIView):
|
||||||
'message': str(exc)
|
'message': str(exc)
|
||||||
})
|
})
|
||||||
|
|
||||||
if deprecated_style:
|
|
||||||
return (paginator, page, page.object_list, page.has_other_pages())
|
|
||||||
return page
|
return page
|
||||||
|
|
||||||
def filter_queryset(self, queryset):
|
def filter_queryset(self, queryset):
|
||||||
|
@ -191,29 +150,12 @@ class GenericAPIView(views.APIView):
|
||||||
"""
|
"""
|
||||||
Returns the list of filter backends that this view requires.
|
Returns the list of filter backends that this view requires.
|
||||||
"""
|
"""
|
||||||
if self.filter_backends is None:
|
return list(self.filter_backends)
|
||||||
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
|
|
||||||
|
|
||||||
# The following methods provide default implementations
|
# The following methods provide default implementations
|
||||||
# that you may want to override for more complex cases.
|
# 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.
|
Return the size of pages to use with pagination.
|
||||||
|
|
||||||
|
@ -222,11 +164,6 @@ class GenericAPIView(views.APIView):
|
||||||
|
|
||||||
Otherwise defaults to using `self.paginate_by`.
|
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:
|
if self.paginate_by_param:
|
||||||
try:
|
try:
|
||||||
return strict_positive_int(
|
return strict_positive_int(
|
||||||
|
@ -248,26 +185,13 @@ class GenericAPIView(views.APIView):
|
||||||
|
|
||||||
(Eg. admins get full serialization, others get basic serialization)
|
(Eg. admins get full serialization, others get basic serialization)
|
||||||
"""
|
"""
|
||||||
serializer_class = self.serializer_class
|
assert self.serializer_class is not None, (
|
||||||
if serializer_class is not None:
|
"'%s' should either include a `serializer_class` attribute, "
|
||||||
return serializer_class
|
"or override the `get_serializer_class()` method."
|
||||||
|
% self.__class__.__name__
|
||||||
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, \
|
return self.serializer_class
|
||||||
"'%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):
|
def get_queryset(self):
|
||||||
"""
|
"""
|
||||||
|
@ -284,21 +208,15 @@ class GenericAPIView(views.APIView):
|
||||||
|
|
||||||
(Eg. return a list of items that is specific to the user)
|
(Eg. return a list of items that is specific to the user)
|
||||||
"""
|
"""
|
||||||
if self.queryset is not None:
|
assert self.queryset is not None, (
|
||||||
return self.queryset._clone()
|
"'%s' should either include a `queryset` attribute, "
|
||||||
|
"or override the `get_queryset()` method."
|
||||||
|
% self.__class__.__name__
|
||||||
|
)
|
||||||
|
|
||||||
if self.model is not None:
|
return self.queryset._clone()
|
||||||
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()
|
|
||||||
|
|
||||||
error_format = "'%s' must define 'queryset' or 'model'"
|
def get_object(self):
|
||||||
raise ImproperlyConfigured(error_format % self.__class__.__name__)
|
|
||||||
|
|
||||||
def get_object(self, queryset=None):
|
|
||||||
"""
|
"""
|
||||||
Returns the object the view is displaying.
|
Returns the object the view is displaying.
|
||||||
|
|
||||||
|
@ -306,43 +224,19 @@ class GenericAPIView(views.APIView):
|
||||||
queryset lookups. Eg if objects are referenced using multiple
|
queryset lookups. Eg if objects are referenced using multiple
|
||||||
keyword arguments in the url conf.
|
keyword arguments in the url conf.
|
||||||
"""
|
"""
|
||||||
# Determine the base queryset to use.
|
queryset = self.filter_queryset(self.get_queryset())
|
||||||
if queryset is None:
|
|
||||||
queryset = self.filter_queryset(self.get_queryset())
|
|
||||||
else:
|
|
||||||
pass # Deprecation warning
|
|
||||||
|
|
||||||
# Perform the lookup filtering.
|
# 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_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:
|
assert lookup_url_kwarg in self.kwargs, (
|
||||||
filter_kwargs = {self.lookup_field: lookup}
|
'Expected view %s to be called with a URL keyword argument '
|
||||||
elif pk is not None and self.lookup_field == 'pk':
|
'named "%s". Fix your URL conf, or set the `.lookup_field` '
|
||||||
warnings.warn(
|
'attribute on the view correctly.' %
|
||||||
'The `pk_url_kwarg` attribute is deprecated. '
|
(self.__class__.__name__, lookup_url_kwarg)
|
||||||
'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)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
filter_kwargs = {self.lookup_field: self.kwargs[lookup_url_kwarg]}
|
||||||
obj = get_object_or_404(queryset, **filter_kwargs)
|
obj = get_object_or_404(queryset, **filter_kwargs)
|
||||||
|
|
||||||
# May raise a permission denied
|
# May raise a permission denied
|
||||||
|
@ -540,25 +434,3 @@ class RetrieveUpdateDestroyAPIView(mixins.RetrieveModelMixin,
|
||||||
|
|
||||||
def delete(self, request, *args, **kwargs):
|
def delete(self, request, *args, **kwargs):
|
||||||
return self.destroy(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)
|
|
||||||
|
|
|
@ -12,10 +12,9 @@ from rest_framework import status
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
from rest_framework.request import clone_request
|
from rest_framework.request import clone_request
|
||||||
from rest_framework.settings import api_settings
|
from rest_framework.settings import api_settings
|
||||||
import warnings
|
|
||||||
|
|
||||||
|
|
||||||
def _get_validation_exclusions(obj, pk=None, slug_field=None, lookup_field=None):
|
def _get_validation_exclusions(obj, lookup_field=None):
|
||||||
"""
|
"""
|
||||||
Given a model instance, and an optional pk and slug field,
|
Given a model instance, and an optional pk and slug field,
|
||||||
return the full list of all other field names on that model.
|
return the full list of all other field names on that model.
|
||||||
|
@ -23,23 +22,13 @@ def _get_validation_exclusions(obj, pk=None, slug_field=None, lookup_field=None)
|
||||||
For use when performing full_clean on a model instance,
|
For use when performing full_clean on a model instance,
|
||||||
so we only clean the required fields.
|
so we only clean the required fields.
|
||||||
"""
|
"""
|
||||||
include = []
|
if lookup_field == 'pk':
|
||||||
|
|
||||||
if pk:
|
|
||||||
# Deprecated
|
|
||||||
pk_field = obj._meta.pk
|
pk_field = obj._meta.pk
|
||||||
while pk_field.rel:
|
while pk_field.rel:
|
||||||
pk_field = pk_field.rel.to._meta.pk
|
pk_field = pk_field.rel.to._meta.pk
|
||||||
include.append(pk_field.name)
|
lookup_field = pk_field.name
|
||||||
|
|
||||||
if slug_field:
|
return [field.name for field in obj._meta.fields if field.name != lookup_field]
|
||||||
# Deprecated
|
|
||||||
include.append(slug_field)
|
|
||||||
|
|
||||||
if lookup_field and lookup_field != 'pk':
|
|
||||||
include.append(lookup_field)
|
|
||||||
|
|
||||||
return [field.name for field in obj._meta.fields if field.name not in include]
|
|
||||||
|
|
||||||
|
|
||||||
class CreateModelMixin(object):
|
class CreateModelMixin(object):
|
||||||
|
@ -47,12 +36,10 @@ class CreateModelMixin(object):
|
||||||
Create a model instance.
|
Create a model instance.
|
||||||
"""
|
"""
|
||||||
def create(self, request, *args, **kwargs):
|
def create(self, request, *args, **kwargs):
|
||||||
serializer = self.get_serializer(data=request.DATA, files=request.FILES)
|
serializer = self.get_serializer(data=request.DATA)
|
||||||
|
|
||||||
if serializer.is_valid():
|
if serializer.is_valid():
|
||||||
self.pre_save(serializer.object)
|
self.object = serializer.save()
|
||||||
self.object = serializer.save(force_insert=True)
|
|
||||||
self.post_save(self.object, created=True)
|
|
||||||
headers = self.get_success_headers(serializer.data)
|
headers = self.get_success_headers(serializer.data)
|
||||||
return Response(serializer.data, status=status.HTTP_201_CREATED,
|
return Response(serializer.data, status=status.HTTP_201_CREATED,
|
||||||
headers=headers)
|
headers=headers)
|
||||||
|
@ -70,24 +57,9 @@ class ListModelMixin(object):
|
||||||
"""
|
"""
|
||||||
List a queryset.
|
List a queryset.
|
||||||
"""
|
"""
|
||||||
empty_error = "Empty list and '%(class_name)s.allow_empty' is False."
|
|
||||||
|
|
||||||
def list(self, request, *args, **kwargs):
|
def list(self, request, *args, **kwargs):
|
||||||
self.object_list = self.filter_queryset(self.get_queryset())
|
self.object_list = self.filter_queryset(self.get_queryset())
|
||||||
|
|
||||||
# Default is to allow empty querysets. This can be altered by setting
|
|
||||||
# `.allow_empty = False`, to raise 404 errors on empty querysets.
|
|
||||||
if not self.allow_empty and not self.object_list:
|
|
||||||
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
|
|
||||||
)
|
|
||||||
class_name = self.__class__.__name__
|
|
||||||
error_msg = self.empty_error % {'class_name': class_name}
|
|
||||||
raise Http404(error_msg)
|
|
||||||
|
|
||||||
# Switch between paginated or standard style responses
|
# Switch between paginated or standard style responses
|
||||||
page = self.paginate_queryset(self.object_list)
|
page = self.paginate_queryset(self.object_list)
|
||||||
if page is not None:
|
if page is not None:
|
||||||
|
@ -116,26 +88,20 @@ class UpdateModelMixin(object):
|
||||||
partial = kwargs.pop('partial', False)
|
partial = kwargs.pop('partial', False)
|
||||||
self.object = self.get_object_or_none()
|
self.object = self.get_object_or_none()
|
||||||
|
|
||||||
serializer = self.get_serializer(self.object, data=request.DATA,
|
serializer = self.get_serializer(self.object, data=request.DATA, partial=partial)
|
||||||
files=request.FILES, partial=partial)
|
|
||||||
|
|
||||||
if not serializer.is_valid():
|
if not serializer.is_valid():
|
||||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
try:
|
lookup_url_kwarg = self.lookup_url_kwarg or self.lookup_field
|
||||||
self.pre_save(serializer.object)
|
lookup_value = self.kwargs[lookup_url_kwarg]
|
||||||
except ValidationError as err:
|
extras = {self.lookup_field: lookup_value}
|
||||||
# full_clean on model instance may be called in pre_save,
|
|
||||||
# so we have to handle eventual errors.
|
|
||||||
return Response(err.message_dict, status=status.HTTP_400_BAD_REQUEST)
|
|
||||||
|
|
||||||
if self.object is None:
|
if self.object is None:
|
||||||
self.object = serializer.save(force_insert=True)
|
self.object = serializer.save(extras=extras)
|
||||||
self.post_save(self.object, created=True)
|
|
||||||
return Response(serializer.data, status=status.HTTP_201_CREATED)
|
return Response(serializer.data, status=status.HTTP_201_CREATED)
|
||||||
|
|
||||||
self.object = serializer.save(force_update=True)
|
self.object = serializer.save(extras=extras)
|
||||||
self.post_save(self.object, created=False)
|
|
||||||
return Response(serializer.data, status=status.HTTP_200_OK)
|
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||||
|
|
||||||
def partial_update(self, request, *args, **kwargs):
|
def partial_update(self, request, *args, **kwargs):
|
||||||
|
@ -161,26 +127,15 @@ class UpdateModelMixin(object):
|
||||||
"""
|
"""
|
||||||
Set any attributes on the object that are implicit in the request.
|
Set any attributes on the object that are implicit in the request.
|
||||||
"""
|
"""
|
||||||
# pk and/or slug attributes are implicit in the URL.
|
|
||||||
lookup_url_kwarg = self.lookup_url_kwarg or self.lookup_field
|
lookup_url_kwarg = self.lookup_url_kwarg or self.lookup_field
|
||||||
lookup = self.kwargs.get(lookup_url_kwarg, None)
|
lookup_value = self.kwargs[lookup_url_kwarg]
|
||||||
pk = self.kwargs.get(self.pk_url_kwarg, None)
|
|
||||||
slug = self.kwargs.get(self.slug_url_kwarg, None)
|
|
||||||
slug_field = slug and self.slug_field or None
|
|
||||||
|
|
||||||
if lookup:
|
setattr(obj, self.lookup_field, lookup_value)
|
||||||
setattr(obj, self.lookup_field, lookup)
|
|
||||||
|
|
||||||
if pk:
|
|
||||||
setattr(obj, 'pk', pk)
|
|
||||||
|
|
||||||
if slug:
|
|
||||||
setattr(obj, slug_field, slug)
|
|
||||||
|
|
||||||
# Ensure we clean the attributes so that we don't eg return integer
|
# Ensure we clean the attributes so that we don't eg return integer
|
||||||
# pk using a string representation, as provided by the url conf kwarg.
|
# pk using a string representation, as provided by the url conf kwarg.
|
||||||
if hasattr(obj, 'full_clean'):
|
if hasattr(obj, 'full_clean'):
|
||||||
exclude = _get_validation_exclusions(obj, pk, slug_field, self.lookup_field)
|
exclude = _get_validation_exclusions(obj, self.lookup_field)
|
||||||
obj.full_clean(exclude)
|
obj.full_clean(exclude)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -48,17 +48,17 @@ class DefaultObjectSerializer(serializers.Field):
|
||||||
super(DefaultObjectSerializer, self).__init__(source=source)
|
super(DefaultObjectSerializer, self).__init__(source=source)
|
||||||
|
|
||||||
|
|
||||||
class PaginationSerializerOptions(serializers.SerializerOptions):
|
# class PaginationSerializerOptions(serializers.SerializerOptions):
|
||||||
"""
|
# """
|
||||||
An object that stores the options that may be provided to a
|
# An object that stores the options that may be provided to a
|
||||||
pagination serializer by using the inner `Meta` class.
|
# pagination serializer by using the inner `Meta` class.
|
||||||
|
|
||||||
Accessible on the instance as `serializer.opts`.
|
# Accessible on the instance as `serializer.opts`.
|
||||||
"""
|
# """
|
||||||
def __init__(self, meta):
|
# def __init__(self, meta):
|
||||||
super(PaginationSerializerOptions, self).__init__(meta)
|
# super(PaginationSerializerOptions, self).__init__(meta)
|
||||||
self.object_serializer_class = getattr(meta, 'object_serializer_class',
|
# self.object_serializer_class = getattr(meta, 'object_serializer_class',
|
||||||
DefaultObjectSerializer)
|
# DefaultObjectSerializer)
|
||||||
|
|
||||||
|
|
||||||
class BasePaginationSerializer(serializers.Serializer):
|
class BasePaginationSerializer(serializers.Serializer):
|
||||||
|
@ -66,7 +66,7 @@ class BasePaginationSerializer(serializers.Serializer):
|
||||||
A base class for pagination serializers to inherit from,
|
A base class for pagination serializers to inherit from,
|
||||||
to make implementing custom serializers more easy.
|
to make implementing custom serializers more easy.
|
||||||
"""
|
"""
|
||||||
_options_class = PaginationSerializerOptions
|
# _options_class = PaginationSerializerOptions
|
||||||
results_field = 'results'
|
results_field = 'results'
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
|
|
|
@ -1,595 +0,0 @@
|
||||||
"""
|
|
||||||
Serializer fields that deal with relationships.
|
|
||||||
|
|
||||||
These fields allow you to specify the style that should be used to represent
|
|
||||||
model relationships, including hyperlinks, primary keys, or slugs.
|
|
||||||
"""
|
|
||||||
from __future__ import unicode_literals
|
|
||||||
from django.core.exceptions import ObjectDoesNotExist, ValidationError
|
|
||||||
from django.core.urlresolvers import resolve, get_script_prefix, NoReverseMatch
|
|
||||||
from django import forms
|
|
||||||
from django.db.models.fields import BLANK_CHOICE_DASH
|
|
||||||
from django.forms import widgets
|
|
||||||
from django.forms.models import ModelChoiceIterator
|
|
||||||
from django.utils.translation import ugettext_lazy as _
|
|
||||||
from rest_framework.fields import Field, WritableField, get_component, is_simple_callable
|
|
||||||
from rest_framework.reverse import reverse
|
|
||||||
from rest_framework.compat import urlparse
|
|
||||||
from rest_framework.compat import smart_text
|
|
||||||
import warnings
|
|
||||||
|
|
||||||
|
|
||||||
# Relational fields
|
|
||||||
|
|
||||||
# Not actually Writable, but subclasses may need to be.
|
|
||||||
class RelatedField(WritableField):
|
|
||||||
"""
|
|
||||||
Base class for related model fields.
|
|
||||||
|
|
||||||
This represents a relationship using the unicode representation of the target.
|
|
||||||
"""
|
|
||||||
widget = widgets.Select
|
|
||||||
many_widget = widgets.SelectMultiple
|
|
||||||
form_field_class = forms.ChoiceField
|
|
||||||
many_form_field_class = forms.MultipleChoiceField
|
|
||||||
null_values = (None, '', 'None')
|
|
||||||
|
|
||||||
cache_choices = False
|
|
||||||
empty_label = None
|
|
||||||
read_only = True
|
|
||||||
many = False
|
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
queryset = kwargs.pop('queryset', None)
|
|
||||||
self.many = kwargs.pop('many', self.many)
|
|
||||||
if self.many:
|
|
||||||
self.widget = self.many_widget
|
|
||||||
self.form_field_class = self.many_form_field_class
|
|
||||||
|
|
||||||
kwargs['read_only'] = kwargs.pop('read_only', self.read_only)
|
|
||||||
super(RelatedField, self).__init__(*args, **kwargs)
|
|
||||||
|
|
||||||
if not self.required:
|
|
||||||
# Accessed in ModelChoiceIterator django/forms/models.py:1034
|
|
||||||
# If set adds empty choice.
|
|
||||||
self.empty_label = BLANK_CHOICE_DASH[0][1]
|
|
||||||
|
|
||||||
self.queryset = queryset
|
|
||||||
|
|
||||||
def initialize(self, parent, field_name):
|
|
||||||
super(RelatedField, self).initialize(parent, field_name)
|
|
||||||
if self.queryset is None and not self.read_only:
|
|
||||||
manager = getattr(self.parent.opts.model, self.source or field_name)
|
|
||||||
if hasattr(manager, 'related'): # Forward
|
|
||||||
self.queryset = manager.related.model._default_manager.all()
|
|
||||||
else: # Reverse
|
|
||||||
self.queryset = manager.field.rel.to._default_manager.all()
|
|
||||||
|
|
||||||
# We need this stuff to make form choices work...
|
|
||||||
|
|
||||||
def prepare_value(self, obj):
|
|
||||||
return self.to_native(obj)
|
|
||||||
|
|
||||||
def label_from_instance(self, obj):
|
|
||||||
"""
|
|
||||||
Return a readable representation for use with eg. select widgets.
|
|
||||||
"""
|
|
||||||
desc = smart_text(obj)
|
|
||||||
ident = smart_text(self.to_native(obj))
|
|
||||||
if desc == ident:
|
|
||||||
return desc
|
|
||||||
return "%s - %s" % (desc, ident)
|
|
||||||
|
|
||||||
def _get_queryset(self):
|
|
||||||
return self._queryset
|
|
||||||
|
|
||||||
def _set_queryset(self, queryset):
|
|
||||||
self._queryset = queryset
|
|
||||||
self.widget.choices = self.choices
|
|
||||||
|
|
||||||
queryset = property(_get_queryset, _set_queryset)
|
|
||||||
|
|
||||||
def _get_choices(self):
|
|
||||||
# If self._choices is set, then somebody must have manually set
|
|
||||||
# the property self.choices. In this case, just return self._choices.
|
|
||||||
if hasattr(self, '_choices'):
|
|
||||||
return self._choices
|
|
||||||
|
|
||||||
# Otherwise, execute the QuerySet in self.queryset to determine the
|
|
||||||
# choices dynamically. Return a fresh ModelChoiceIterator that has not been
|
|
||||||
# consumed. Note that we're instantiating a new ModelChoiceIterator *each*
|
|
||||||
# time _get_choices() is called (and, thus, each time self.choices is
|
|
||||||
# accessed) so that we can ensure the QuerySet has not been consumed. This
|
|
||||||
# construct might look complicated but it allows for lazy evaluation of
|
|
||||||
# the queryset.
|
|
||||||
return ModelChoiceIterator(self)
|
|
||||||
|
|
||||||
def _set_choices(self, value):
|
|
||||||
# Setting choices also sets the choices on the widget.
|
|
||||||
# choices can be any iterable, but we call list() on it because
|
|
||||||
# it will be consumed more than once.
|
|
||||||
self._choices = self.widget.choices = list(value)
|
|
||||||
|
|
||||||
choices = property(_get_choices, _set_choices)
|
|
||||||
|
|
||||||
# Default value handling
|
|
||||||
|
|
||||||
def get_default_value(self):
|
|
||||||
default = super(RelatedField, self).get_default_value()
|
|
||||||
if self.many and default is None:
|
|
||||||
return []
|
|
||||||
return default
|
|
||||||
|
|
||||||
# Regular serializer stuff...
|
|
||||||
|
|
||||||
def field_to_native(self, obj, field_name):
|
|
||||||
try:
|
|
||||||
if self.source == '*':
|
|
||||||
return self.to_native(obj)
|
|
||||||
|
|
||||||
source = self.source or field_name
|
|
||||||
value = obj
|
|
||||||
|
|
||||||
for component in source.split('.'):
|
|
||||||
if value is None:
|
|
||||||
break
|
|
||||||
value = get_component(value, component)
|
|
||||||
except ObjectDoesNotExist:
|
|
||||||
return None
|
|
||||||
|
|
||||||
if value is None:
|
|
||||||
return None
|
|
||||||
|
|
||||||
if self.many:
|
|
||||||
if is_simple_callable(getattr(value, 'all', None)):
|
|
||||||
return [self.to_native(item) for item in value.all()]
|
|
||||||
else:
|
|
||||||
# Also support non-queryset iterables.
|
|
||||||
# This allows us to also support plain lists of related items.
|
|
||||||
return [self.to_native(item) for item in value]
|
|
||||||
return self.to_native(value)
|
|
||||||
|
|
||||||
def field_from_native(self, data, files, field_name, into):
|
|
||||||
if self.read_only:
|
|
||||||
return
|
|
||||||
|
|
||||||
try:
|
|
||||||
if self.many:
|
|
||||||
try:
|
|
||||||
# Form data
|
|
||||||
value = data.getlist(field_name)
|
|
||||||
if value == [''] or value == []:
|
|
||||||
raise KeyError
|
|
||||||
except AttributeError:
|
|
||||||
# Non-form data
|
|
||||||
value = data[field_name]
|
|
||||||
else:
|
|
||||||
value = data[field_name]
|
|
||||||
except KeyError:
|
|
||||||
if self.partial:
|
|
||||||
return
|
|
||||||
value = self.get_default_value()
|
|
||||||
|
|
||||||
if value in self.null_values:
|
|
||||||
if self.required:
|
|
||||||
raise ValidationError(self.error_messages['required'])
|
|
||||||
into[(self.source or field_name)] = None
|
|
||||||
elif self.many:
|
|
||||||
into[(self.source or field_name)] = [self.from_native(item) for item in value]
|
|
||||||
else:
|
|
||||||
into[(self.source or field_name)] = self.from_native(value)
|
|
||||||
|
|
||||||
|
|
||||||
# PrimaryKey relationships
|
|
||||||
|
|
||||||
class PrimaryKeyRelatedField(RelatedField):
|
|
||||||
"""
|
|
||||||
Represents a relationship as a pk value.
|
|
||||||
"""
|
|
||||||
read_only = False
|
|
||||||
|
|
||||||
default_error_messages = {
|
|
||||||
'does_not_exist': _("Invalid pk '%s' - object does not exist."),
|
|
||||||
'incorrect_type': _('Incorrect type. Expected pk value, received %s.'),
|
|
||||||
}
|
|
||||||
|
|
||||||
# TODO: Remove these field hacks...
|
|
||||||
def prepare_value(self, obj):
|
|
||||||
return self.to_native(obj.pk)
|
|
||||||
|
|
||||||
def label_from_instance(self, obj):
|
|
||||||
"""
|
|
||||||
Return a readable representation for use with eg. select widgets.
|
|
||||||
"""
|
|
||||||
desc = smart_text(obj)
|
|
||||||
ident = smart_text(self.to_native(obj.pk))
|
|
||||||
if desc == ident:
|
|
||||||
return desc
|
|
||||||
return "%s - %s" % (desc, ident)
|
|
||||||
|
|
||||||
# TODO: Possibly change this to just take `obj`, through prob less performant
|
|
||||||
def to_native(self, pk):
|
|
||||||
return pk
|
|
||||||
|
|
||||||
def from_native(self, data):
|
|
||||||
if self.queryset is None:
|
|
||||||
raise Exception('Writable related fields must include a `queryset` argument')
|
|
||||||
|
|
||||||
try:
|
|
||||||
return self.queryset.get(pk=data)
|
|
||||||
except ObjectDoesNotExist:
|
|
||||||
msg = self.error_messages['does_not_exist'] % smart_text(data)
|
|
||||||
raise ValidationError(msg)
|
|
||||||
except (TypeError, ValueError):
|
|
||||||
received = type(data).__name__
|
|
||||||
msg = self.error_messages['incorrect_type'] % received
|
|
||||||
raise ValidationError(msg)
|
|
||||||
|
|
||||||
def field_to_native(self, obj, field_name):
|
|
||||||
if self.many:
|
|
||||||
# To-many relationship
|
|
||||||
|
|
||||||
queryset = None
|
|
||||||
if not self.source:
|
|
||||||
# Prefer obj.serializable_value for performance reasons
|
|
||||||
try:
|
|
||||||
queryset = obj.serializable_value(field_name)
|
|
||||||
except AttributeError:
|
|
||||||
pass
|
|
||||||
if queryset is None:
|
|
||||||
# RelatedManager (reverse relationship)
|
|
||||||
source = self.source or field_name
|
|
||||||
queryset = obj
|
|
||||||
for component in source.split('.'):
|
|
||||||
if queryset is None:
|
|
||||||
return []
|
|
||||||
queryset = get_component(queryset, component)
|
|
||||||
|
|
||||||
# Forward relationship
|
|
||||||
if is_simple_callable(getattr(queryset, 'all', None)):
|
|
||||||
return [self.to_native(item.pk) for item in queryset.all()]
|
|
||||||
else:
|
|
||||||
# Also support non-queryset iterables.
|
|
||||||
# This allows us to also support plain lists of related items.
|
|
||||||
return [self.to_native(item.pk) for item in queryset]
|
|
||||||
|
|
||||||
# To-one relationship
|
|
||||||
try:
|
|
||||||
# Prefer obj.serializable_value for performance reasons
|
|
||||||
pk = obj.serializable_value(self.source or field_name)
|
|
||||||
except AttributeError:
|
|
||||||
# RelatedObject (reverse relationship)
|
|
||||||
try:
|
|
||||||
pk = getattr(obj, self.source or field_name).pk
|
|
||||||
except (ObjectDoesNotExist, AttributeError):
|
|
||||||
return None
|
|
||||||
|
|
||||||
# Forward relationship
|
|
||||||
return self.to_native(pk)
|
|
||||||
|
|
||||||
|
|
||||||
# Slug relationships
|
|
||||||
|
|
||||||
class SlugRelatedField(RelatedField):
|
|
||||||
"""
|
|
||||||
Represents a relationship using a unique field on the target.
|
|
||||||
"""
|
|
||||||
read_only = False
|
|
||||||
|
|
||||||
default_error_messages = {
|
|
||||||
'does_not_exist': _("Object with %s=%s does not exist."),
|
|
||||||
'invalid': _('Invalid value.'),
|
|
||||||
}
|
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
self.slug_field = kwargs.pop('slug_field', None)
|
|
||||||
assert self.slug_field, 'slug_field is required'
|
|
||||||
super(SlugRelatedField, self).__init__(*args, **kwargs)
|
|
||||||
|
|
||||||
def to_native(self, obj):
|
|
||||||
return getattr(obj, self.slug_field)
|
|
||||||
|
|
||||||
def from_native(self, data):
|
|
||||||
if self.queryset is None:
|
|
||||||
raise Exception('Writable related fields must include a `queryset` argument')
|
|
||||||
|
|
||||||
try:
|
|
||||||
return self.queryset.get(**{self.slug_field: data})
|
|
||||||
except ObjectDoesNotExist:
|
|
||||||
raise ValidationError(self.error_messages['does_not_exist'] %
|
|
||||||
(self.slug_field, smart_text(data)))
|
|
||||||
except (TypeError, ValueError):
|
|
||||||
msg = self.error_messages['invalid']
|
|
||||||
raise ValidationError(msg)
|
|
||||||
|
|
||||||
|
|
||||||
# Hyperlinked relationships
|
|
||||||
|
|
||||||
class HyperlinkedRelatedField(RelatedField):
|
|
||||||
"""
|
|
||||||
Represents a relationship using hyperlinking.
|
|
||||||
"""
|
|
||||||
read_only = False
|
|
||||||
lookup_field = 'pk'
|
|
||||||
|
|
||||||
default_error_messages = {
|
|
||||||
'no_match': _('Invalid hyperlink - No URL match'),
|
|
||||||
'incorrect_match': _('Invalid hyperlink - Incorrect URL match'),
|
|
||||||
'configuration_error': _('Invalid hyperlink due to configuration error'),
|
|
||||||
'does_not_exist': _("Invalid hyperlink - object does not exist."),
|
|
||||||
'incorrect_type': _('Incorrect type. Expected url string, received %s.'),
|
|
||||||
}
|
|
||||||
|
|
||||||
# These are all deprecated
|
|
||||||
pk_url_kwarg = 'pk'
|
|
||||||
slug_field = 'slug'
|
|
||||||
slug_url_kwarg = None # Defaults to same as `slug_field` unless overridden
|
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
try:
|
|
||||||
self.view_name = kwargs.pop('view_name')
|
|
||||||
except KeyError:
|
|
||||||
raise ValueError("Hyperlinked field requires 'view_name' kwarg")
|
|
||||||
|
|
||||||
self.lookup_field = kwargs.pop('lookup_field', self.lookup_field)
|
|
||||||
self.format = kwargs.pop('format', None)
|
|
||||||
|
|
||||||
# These are deprecated
|
|
||||||
if 'pk_url_kwarg' in kwargs:
|
|
||||||
msg = 'pk_url_kwarg is deprecated. Use lookup_field instead.'
|
|
||||||
warnings.warn(msg, DeprecationWarning, stacklevel=2)
|
|
||||||
if 'slug_url_kwarg' in kwargs:
|
|
||||||
msg = 'slug_url_kwarg is deprecated. Use lookup_field instead.'
|
|
||||||
warnings.warn(msg, DeprecationWarning, stacklevel=2)
|
|
||||||
if 'slug_field' in kwargs:
|
|
||||||
msg = 'slug_field is deprecated. Use lookup_field instead.'
|
|
||||||
warnings.warn(msg, DeprecationWarning, stacklevel=2)
|
|
||||||
|
|
||||||
self.pk_url_kwarg = kwargs.pop('pk_url_kwarg', self.pk_url_kwarg)
|
|
||||||
self.slug_field = kwargs.pop('slug_field', self.slug_field)
|
|
||||||
default_slug_kwarg = self.slug_url_kwarg or self.slug_field
|
|
||||||
self.slug_url_kwarg = kwargs.pop('slug_url_kwarg', default_slug_kwarg)
|
|
||||||
|
|
||||||
super(HyperlinkedRelatedField, self).__init__(*args, **kwargs)
|
|
||||||
|
|
||||||
def get_url(self, obj, view_name, request, format):
|
|
||||||
"""
|
|
||||||
Given an object, return the URL that hyperlinks to the object.
|
|
||||||
|
|
||||||
May raise a `NoReverseMatch` if the `view_name` and `lookup_field`
|
|
||||||
attributes are not configured to correctly match the URL conf.
|
|
||||||
"""
|
|
||||||
lookup_field = getattr(obj, self.lookup_field)
|
|
||||||
kwargs = {self.lookup_field: lookup_field}
|
|
||||||
try:
|
|
||||||
return reverse(view_name, kwargs=kwargs, request=request, format=format)
|
|
||||||
except NoReverseMatch:
|
|
||||||
pass
|
|
||||||
|
|
||||||
if self.pk_url_kwarg != 'pk':
|
|
||||||
# Only try pk if it has been explicitly set.
|
|
||||||
# Otherwise, the default `lookup_field = 'pk'` has us covered.
|
|
||||||
pk = obj.pk
|
|
||||||
kwargs = {self.pk_url_kwarg: pk}
|
|
||||||
try:
|
|
||||||
return reverse(view_name, kwargs=kwargs, request=request, format=format)
|
|
||||||
except NoReverseMatch:
|
|
||||||
pass
|
|
||||||
|
|
||||||
slug = getattr(obj, self.slug_field, None)
|
|
||||||
if slug is not None:
|
|
||||||
# Only try slug if it corresponds to an attribute on the object.
|
|
||||||
kwargs = {self.slug_url_kwarg: slug}
|
|
||||||
try:
|
|
||||||
ret = reverse(view_name, kwargs=kwargs, request=request, format=format)
|
|
||||||
if self.slug_field == 'slug' and self.slug_url_kwarg == 'slug':
|
|
||||||
# If the lookup succeeds using the default slug params,
|
|
||||||
# then `slug_field` is being used implicitly, and we
|
|
||||||
# we need to warn about the pending deprecation.
|
|
||||||
msg = 'Implicit slug field hyperlinked fields are deprecated.' \
|
|
||||||
'You should set `lookup_field=slug` on the HyperlinkedRelatedField.'
|
|
||||||
warnings.warn(msg, DeprecationWarning, stacklevel=2)
|
|
||||||
return ret
|
|
||||||
except NoReverseMatch:
|
|
||||||
pass
|
|
||||||
|
|
||||||
raise NoReverseMatch()
|
|
||||||
|
|
||||||
def get_object(self, queryset, view_name, view_args, view_kwargs):
|
|
||||||
"""
|
|
||||||
Return the object corresponding to a matched URL.
|
|
||||||
|
|
||||||
Takes the matched URL conf arguments, and the queryset, and should
|
|
||||||
return an object instance, or raise an `ObjectDoesNotExist` exception.
|
|
||||||
"""
|
|
||||||
lookup = view_kwargs.get(self.lookup_field, None)
|
|
||||||
pk = view_kwargs.get(self.pk_url_kwarg, None)
|
|
||||||
slug = view_kwargs.get(self.slug_url_kwarg, None)
|
|
||||||
|
|
||||||
if lookup is not None:
|
|
||||||
filter_kwargs = {self.lookup_field: lookup}
|
|
||||||
elif pk is not None:
|
|
||||||
filter_kwargs = {'pk': pk}
|
|
||||||
elif slug is not None:
|
|
||||||
filter_kwargs = {self.slug_field: slug}
|
|
||||||
else:
|
|
||||||
raise ObjectDoesNotExist()
|
|
||||||
|
|
||||||
return queryset.get(**filter_kwargs)
|
|
||||||
|
|
||||||
def to_native(self, obj):
|
|
||||||
view_name = self.view_name
|
|
||||||
request = self.context.get('request', None)
|
|
||||||
format = self.format or self.context.get('format', None)
|
|
||||||
|
|
||||||
assert request is not None, (
|
|
||||||
"`HyperlinkedRelatedField` requires the request in the serializer "
|
|
||||||
"context. Add `context={'request': request}` when instantiating "
|
|
||||||
"the serializer."
|
|
||||||
)
|
|
||||||
|
|
||||||
# If the object has not yet been saved then we cannot hyperlink to it.
|
|
||||||
if getattr(obj, 'pk', None) is None:
|
|
||||||
return
|
|
||||||
|
|
||||||
# Return the hyperlink, or error if incorrectly configured.
|
|
||||||
try:
|
|
||||||
return self.get_url(obj, view_name, request, format)
|
|
||||||
except NoReverseMatch:
|
|
||||||
msg = (
|
|
||||||
'Could not resolve URL for hyperlinked relationship using '
|
|
||||||
'view name "%s". You may have failed to include the related '
|
|
||||||
'model in your API, or incorrectly configured the '
|
|
||||||
'`lookup_field` attribute on this field.'
|
|
||||||
)
|
|
||||||
raise Exception(msg % view_name)
|
|
||||||
|
|
||||||
def from_native(self, value):
|
|
||||||
# Convert URL -> model instance pk
|
|
||||||
# TODO: Use values_list
|
|
||||||
queryset = self.queryset
|
|
||||||
if queryset is None:
|
|
||||||
raise Exception('Writable related fields must include a `queryset` argument')
|
|
||||||
|
|
||||||
try:
|
|
||||||
http_prefix = value.startswith(('http:', 'https:'))
|
|
||||||
except AttributeError:
|
|
||||||
msg = self.error_messages['incorrect_type']
|
|
||||||
raise ValidationError(msg % type(value).__name__)
|
|
||||||
|
|
||||||
if http_prefix:
|
|
||||||
# If needed convert absolute URLs to relative path
|
|
||||||
value = urlparse.urlparse(value).path
|
|
||||||
prefix = get_script_prefix()
|
|
||||||
if value.startswith(prefix):
|
|
||||||
value = '/' + value[len(prefix):]
|
|
||||||
|
|
||||||
try:
|
|
||||||
match = resolve(value)
|
|
||||||
except Exception:
|
|
||||||
raise ValidationError(self.error_messages['no_match'])
|
|
||||||
|
|
||||||
if match.view_name != self.view_name:
|
|
||||||
raise ValidationError(self.error_messages['incorrect_match'])
|
|
||||||
|
|
||||||
try:
|
|
||||||
return self.get_object(queryset, match.view_name,
|
|
||||||
match.args, match.kwargs)
|
|
||||||
except (ObjectDoesNotExist, TypeError, ValueError):
|
|
||||||
raise ValidationError(self.error_messages['does_not_exist'])
|
|
||||||
|
|
||||||
|
|
||||||
class HyperlinkedIdentityField(Field):
|
|
||||||
"""
|
|
||||||
Represents the instance, or a property on the instance, using hyperlinking.
|
|
||||||
"""
|
|
||||||
lookup_field = 'pk'
|
|
||||||
read_only = True
|
|
||||||
|
|
||||||
# These are all deprecated
|
|
||||||
pk_url_kwarg = 'pk'
|
|
||||||
slug_field = 'slug'
|
|
||||||
slug_url_kwarg = None # Defaults to same as `slug_field` unless overridden
|
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
try:
|
|
||||||
self.view_name = kwargs.pop('view_name')
|
|
||||||
except KeyError:
|
|
||||||
msg = "HyperlinkedIdentityField requires 'view_name' argument"
|
|
||||||
raise ValueError(msg)
|
|
||||||
|
|
||||||
self.format = kwargs.pop('format', None)
|
|
||||||
lookup_field = kwargs.pop('lookup_field', None)
|
|
||||||
self.lookup_field = lookup_field or self.lookup_field
|
|
||||||
|
|
||||||
# These are deprecated
|
|
||||||
if 'pk_url_kwarg' in kwargs:
|
|
||||||
msg = 'pk_url_kwarg is deprecated. Use lookup_field instead.'
|
|
||||||
warnings.warn(msg, DeprecationWarning, stacklevel=2)
|
|
||||||
if 'slug_url_kwarg' in kwargs:
|
|
||||||
msg = 'slug_url_kwarg is deprecated. Use lookup_field instead.'
|
|
||||||
warnings.warn(msg, DeprecationWarning, stacklevel=2)
|
|
||||||
if 'slug_field' in kwargs:
|
|
||||||
msg = 'slug_field is deprecated. Use lookup_field instead.'
|
|
||||||
warnings.warn(msg, DeprecationWarning, stacklevel=2)
|
|
||||||
|
|
||||||
self.slug_field = kwargs.pop('slug_field', self.slug_field)
|
|
||||||
default_slug_kwarg = self.slug_url_kwarg or self.slug_field
|
|
||||||
self.pk_url_kwarg = kwargs.pop('pk_url_kwarg', self.pk_url_kwarg)
|
|
||||||
self.slug_url_kwarg = kwargs.pop('slug_url_kwarg', default_slug_kwarg)
|
|
||||||
|
|
||||||
super(HyperlinkedIdentityField, self).__init__(*args, **kwargs)
|
|
||||||
|
|
||||||
def field_to_native(self, obj, field_name):
|
|
||||||
request = self.context.get('request', None)
|
|
||||||
format = self.context.get('format', None)
|
|
||||||
view_name = self.view_name
|
|
||||||
|
|
||||||
assert request is not None, (
|
|
||||||
"`HyperlinkedIdentityField` requires the request in the serializer"
|
|
||||||
" context. Add `context={'request': request}` when instantiating "
|
|
||||||
"the serializer."
|
|
||||||
)
|
|
||||||
|
|
||||||
# By default use whatever format is given for the current context
|
|
||||||
# unless the target is a different type to the source.
|
|
||||||
#
|
|
||||||
# Eg. Consider a HyperlinkedIdentityField pointing from a json
|
|
||||||
# representation to an html property of that representation...
|
|
||||||
#
|
|
||||||
# '/snippets/1/' should link to '/snippets/1/highlight/'
|
|
||||||
# ...but...
|
|
||||||
# '/snippets/1/.json' should link to '/snippets/1/highlight/.html'
|
|
||||||
if format and self.format and self.format != format:
|
|
||||||
format = self.format
|
|
||||||
|
|
||||||
# Return the hyperlink, or error if incorrectly configured.
|
|
||||||
try:
|
|
||||||
return self.get_url(obj, view_name, request, format)
|
|
||||||
except NoReverseMatch:
|
|
||||||
msg = (
|
|
||||||
'Could not resolve URL for hyperlinked relationship using '
|
|
||||||
'view name "%s". You may have failed to include the related '
|
|
||||||
'model in your API, or incorrectly configured the '
|
|
||||||
'`lookup_field` attribute on this field.'
|
|
||||||
)
|
|
||||||
raise Exception(msg % view_name)
|
|
||||||
|
|
||||||
def get_url(self, obj, view_name, request, format):
|
|
||||||
"""
|
|
||||||
Given an object, return the URL that hyperlinks to the object.
|
|
||||||
|
|
||||||
May raise a `NoReverseMatch` if the `view_name` and `lookup_field`
|
|
||||||
attributes are not configured to correctly match the URL conf.
|
|
||||||
"""
|
|
||||||
lookup_field = getattr(obj, self.lookup_field, None)
|
|
||||||
kwargs = {self.lookup_field: lookup_field}
|
|
||||||
|
|
||||||
# Handle unsaved object case
|
|
||||||
if lookup_field is None:
|
|
||||||
return None
|
|
||||||
|
|
||||||
try:
|
|
||||||
return reverse(view_name, kwargs=kwargs, request=request, format=format)
|
|
||||||
except NoReverseMatch:
|
|
||||||
pass
|
|
||||||
|
|
||||||
if self.pk_url_kwarg != 'pk':
|
|
||||||
# Only try pk lookup if it has been explicitly set.
|
|
||||||
# Otherwise, the default `lookup_field = 'pk'` has us covered.
|
|
||||||
kwargs = {self.pk_url_kwarg: obj.pk}
|
|
||||||
try:
|
|
||||||
return reverse(view_name, kwargs=kwargs, request=request, format=format)
|
|
||||||
except NoReverseMatch:
|
|
||||||
pass
|
|
||||||
|
|
||||||
slug = getattr(obj, self.slug_field, None)
|
|
||||||
if slug:
|
|
||||||
# Only use slug lookup if a slug field exists on the model
|
|
||||||
kwargs = {self.slug_url_kwarg: slug}
|
|
||||||
try:
|
|
||||||
return reverse(view_name, kwargs=kwargs, request=request, format=format)
|
|
||||||
except NoReverseMatch:
|
|
||||||
pass
|
|
||||||
|
|
||||||
raise NoReverseMatch()
|
|
|
@ -458,7 +458,7 @@ class BrowsableAPIRenderer(BaseRenderer):
|
||||||
):
|
):
|
||||||
return
|
return
|
||||||
|
|
||||||
serializer = view.get_serializer(instance=obj, data=data, files=files)
|
serializer = view.get_serializer(instance=obj, data=data)
|
||||||
serializer.is_valid()
|
serializer.is_valid()
|
||||||
data = serializer.data
|
data = serializer.data
|
||||||
|
|
||||||
|
@ -579,10 +579,10 @@ class BrowsableAPIRenderer(BaseRenderer):
|
||||||
'available_formats': [renderer_cls.format for renderer_cls in view.renderer_classes],
|
'available_formats': [renderer_cls.format for renderer_cls in view.renderer_classes],
|
||||||
'response_headers': response_headers,
|
'response_headers': response_headers,
|
||||||
|
|
||||||
'put_form': self.get_rendered_html_form(view, 'PUT', request),
|
#'put_form': self.get_rendered_html_form(view, 'PUT', request),
|
||||||
'post_form': self.get_rendered_html_form(view, 'POST', request),
|
#'post_form': self.get_rendered_html_form(view, 'POST', request),
|
||||||
'delete_form': self.get_rendered_html_form(view, 'DELETE', request),
|
#'delete_form': self.get_rendered_html_form(view, 'DELETE', request),
|
||||||
'options_form': self.get_rendered_html_form(view, 'OPTIONS', request),
|
#'options_form': self.get_rendered_html_form(view, 'OPTIONS', request),
|
||||||
|
|
||||||
'raw_data_put_form': raw_data_put_form,
|
'raw_data_put_form': raw_data_put_form,
|
||||||
'raw_data_post_form': raw_data_post_form,
|
'raw_data_post_form': raw_data_post_form,
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -111,9 +111,6 @@ DEFAULTS = {
|
||||||
),
|
),
|
||||||
'TIME_FORMAT': None,
|
'TIME_FORMAT': None,
|
||||||
|
|
||||||
# Pending deprecation
|
|
||||||
'FILTER_BACKEND': None,
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -129,7 +126,6 @@ IMPORT_STRINGS = (
|
||||||
'DEFAULT_PAGINATION_SERIALIZER_CLASS',
|
'DEFAULT_PAGINATION_SERIALIZER_CLASS',
|
||||||
'DEFAULT_FILTER_BACKENDS',
|
'DEFAULT_FILTER_BACKENDS',
|
||||||
'EXCEPTION_HANDLER',
|
'EXCEPTION_HANDLER',
|
||||||
'FILTER_BACKEND',
|
|
||||||
'TEST_REQUEST_RENDERER_CLASSES',
|
'TEST_REQUEST_RENDERER_CLASSES',
|
||||||
'UNAUTHENTICATED_USER',
|
'UNAUTHENTICATED_USER',
|
||||||
'UNAUTHENTICATED_TOKEN',
|
'UNAUTHENTICATED_TOKEN',
|
||||||
|
@ -196,15 +192,9 @@ class APISettings(object):
|
||||||
if val and attr in self.import_strings:
|
if val and attr in self.import_strings:
|
||||||
val = perform_import(val, attr)
|
val = perform_import(val, attr)
|
||||||
|
|
||||||
self.validate_setting(attr, val)
|
|
||||||
|
|
||||||
# Cache the result
|
# Cache the result
|
||||||
setattr(self, attr, val)
|
setattr(self, attr, val)
|
||||||
return val
|
return val
|
||||||
|
|
||||||
def validate_setting(self, attr, val):
|
|
||||||
if attr == 'FILTER_BACKEND' and val is not None:
|
|
||||||
# Make sure we can initialize the class
|
|
||||||
val()
|
|
||||||
|
|
||||||
api_settings = APISettings(USER_SETTINGS, DEFAULTS, IMPORT_STRINGS)
|
api_settings = APISettings(USER_SETTINGS, DEFAULTS, IMPORT_STRINGS)
|
||||||
|
|
|
@ -7,7 +7,7 @@ from django.db.models.query import QuerySet
|
||||||
from django.utils.datastructures import SortedDict
|
from django.utils.datastructures import SortedDict
|
||||||
from django.utils.functional import Promise
|
from django.utils.functional import Promise
|
||||||
from rest_framework.compat import force_text
|
from rest_framework.compat import force_text
|
||||||
from rest_framework.serializers import DictWithMetadata, SortedDictWithMetadata
|
# from rest_framework.serializers import DictWithMetadata, SortedDictWithMetadata
|
||||||
import datetime
|
import datetime
|
||||||
import decimal
|
import decimal
|
||||||
import types
|
import types
|
||||||
|
@ -106,14 +106,14 @@ else:
|
||||||
SortedDict,
|
SortedDict,
|
||||||
yaml.representer.SafeRepresenter.represent_dict
|
yaml.representer.SafeRepresenter.represent_dict
|
||||||
)
|
)
|
||||||
SafeDumper.add_representer(
|
# SafeDumper.add_representer(
|
||||||
DictWithMetadata,
|
# DictWithMetadata,
|
||||||
yaml.representer.SafeRepresenter.represent_dict
|
# yaml.representer.SafeRepresenter.represent_dict
|
||||||
)
|
# )
|
||||||
SafeDumper.add_representer(
|
# SafeDumper.add_representer(
|
||||||
SortedDictWithMetadata,
|
# SortedDictWithMetadata,
|
||||||
yaml.representer.SafeRepresenter.represent_dict
|
# yaml.representer.SafeRepresenter.represent_dict
|
||||||
)
|
# )
|
||||||
SafeDumper.add_representer(
|
SafeDumper.add_representer(
|
||||||
types.GeneratorType,
|
types.GeneratorType,
|
||||||
yaml.representer.SafeRepresenter.represent_list
|
yaml.representer.SafeRepresenter.represent_list
|
||||||
|
|
86
rest_framework/utils/html.py
Normal file
86
rest_framework/utils/html.py
Normal file
|
@ -0,0 +1,86 @@
|
||||||
|
"""
|
||||||
|
Helpers for dealing with HTML input.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def is_html_input(dictionary):
|
||||||
|
# MultiDict type datastructures are used to represent HTML form input,
|
||||||
|
# which may have more than one value for each key.
|
||||||
|
return hasattr(dictionary, 'getlist')
|
||||||
|
|
||||||
|
|
||||||
|
def parse_html_list(dictionary, prefix=''):
|
||||||
|
"""
|
||||||
|
Used to suport list values in HTML forms.
|
||||||
|
Supports lists of primitives and/or dictionaries.
|
||||||
|
|
||||||
|
* List of primitives.
|
||||||
|
|
||||||
|
{
|
||||||
|
'[0]': 'abc',
|
||||||
|
'[1]': 'def',
|
||||||
|
'[2]': 'hij'
|
||||||
|
}
|
||||||
|
-->
|
||||||
|
[
|
||||||
|
'abc',
|
||||||
|
'def',
|
||||||
|
'hij'
|
||||||
|
]
|
||||||
|
|
||||||
|
* List of dictionaries.
|
||||||
|
|
||||||
|
{
|
||||||
|
'[0]foo': 'abc',
|
||||||
|
'[0]bar': 'def',
|
||||||
|
'[1]foo': 'hij',
|
||||||
|
'[2]bar': 'klm',
|
||||||
|
}
|
||||||
|
-->
|
||||||
|
[
|
||||||
|
{'foo': 'abc', 'bar': 'def'},
|
||||||
|
{'foo': 'hij', 'bar': 'klm'}
|
||||||
|
]
|
||||||
|
"""
|
||||||
|
Dict = type(dictionary)
|
||||||
|
ret = {}
|
||||||
|
regex = re.compile(r'^%s\[([0-9]+)\](.*)$' % re.escape(prefix))
|
||||||
|
for field, value in dictionary.items():
|
||||||
|
match = regex.match(field)
|
||||||
|
if not match:
|
||||||
|
continue
|
||||||
|
index, key = match.groups()
|
||||||
|
index = int(index)
|
||||||
|
if not key:
|
||||||
|
ret[index] = value
|
||||||
|
elif isinstance(ret.get(index), dict):
|
||||||
|
ret[index][key] = value
|
||||||
|
else:
|
||||||
|
ret[index] = Dict({key: value})
|
||||||
|
return [ret[item] for item in sorted(ret.keys())]
|
||||||
|
|
||||||
|
|
||||||
|
def parse_html_dict(dictionary, prefix):
|
||||||
|
"""
|
||||||
|
Used to support dictionary values in HTML forms.
|
||||||
|
|
||||||
|
{
|
||||||
|
'profile.username': 'example',
|
||||||
|
'profile.email': 'example@example.com',
|
||||||
|
}
|
||||||
|
-->
|
||||||
|
{
|
||||||
|
'profile': {
|
||||||
|
'username': 'example,
|
||||||
|
'email': 'example@example.com'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
ret = {}
|
||||||
|
regex = re.compile(r'^%s\.(.+)$' % re.escape(prefix))
|
||||||
|
for field, value in dictionary.items():
|
||||||
|
match = regex.match(field)
|
||||||
|
if not match:
|
||||||
|
continue
|
||||||
|
key = match.groups()[0]
|
||||||
|
ret[key] = value
|
||||||
|
return ret
|
|
@ -1,7 +0,0 @@
|
||||||
from rest_framework import serializers
|
|
||||||
from tests.models import NullableForeignKeySource
|
|
||||||
|
|
||||||
|
|
||||||
class NullableFKSourceSerializer(serializers.ModelSerializer):
|
|
||||||
class Meta:
|
|
||||||
model = NullableForeignKeySource
|
|
|
@ -16,9 +16,14 @@ factory = APIRequestFactory()
|
||||||
|
|
||||||
|
|
||||||
if django_filters:
|
if django_filters:
|
||||||
|
class FilterableItemSerializer(serializers.ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = FilterableItem
|
||||||
|
|
||||||
# Basic filter on a list view.
|
# Basic filter on a list view.
|
||||||
class FilterFieldsRootView(generics.ListCreateAPIView):
|
class FilterFieldsRootView(generics.ListCreateAPIView):
|
||||||
model = FilterableItem
|
queryset = FilterableItem.objects.all()
|
||||||
|
serializer_class = FilterableItemSerializer
|
||||||
filter_fields = ['decimal', 'date']
|
filter_fields = ['decimal', 'date']
|
||||||
filter_backends = (filters.DjangoFilterBackend,)
|
filter_backends = (filters.DjangoFilterBackend,)
|
||||||
|
|
||||||
|
@ -33,7 +38,8 @@ if django_filters:
|
||||||
fields = ['text', 'decimal', 'date']
|
fields = ['text', 'decimal', 'date']
|
||||||
|
|
||||||
class FilterClassRootView(generics.ListCreateAPIView):
|
class FilterClassRootView(generics.ListCreateAPIView):
|
||||||
model = FilterableItem
|
queryset = FilterableItem.objects.all()
|
||||||
|
serializer_class = FilterableItemSerializer
|
||||||
filter_class = SeveralFieldsFilter
|
filter_class = SeveralFieldsFilter
|
||||||
filter_backends = (filters.DjangoFilterBackend,)
|
filter_backends = (filters.DjangoFilterBackend,)
|
||||||
|
|
||||||
|
@ -46,12 +52,14 @@ if django_filters:
|
||||||
fields = ['text']
|
fields = ['text']
|
||||||
|
|
||||||
class IncorrectlyConfiguredRootView(generics.ListCreateAPIView):
|
class IncorrectlyConfiguredRootView(generics.ListCreateAPIView):
|
||||||
model = FilterableItem
|
queryset = FilterableItem.objects.all()
|
||||||
|
serializer_class = FilterableItemSerializer
|
||||||
filter_class = MisconfiguredFilter
|
filter_class = MisconfiguredFilter
|
||||||
filter_backends = (filters.DjangoFilterBackend,)
|
filter_backends = (filters.DjangoFilterBackend,)
|
||||||
|
|
||||||
class FilterClassDetailView(generics.RetrieveAPIView):
|
class FilterClassDetailView(generics.RetrieveAPIView):
|
||||||
model = FilterableItem
|
queryset = FilterableItem.objects.all()
|
||||||
|
serializer_class = FilterableItemSerializer
|
||||||
filter_class = SeveralFieldsFilter
|
filter_class = SeveralFieldsFilter
|
||||||
filter_backends = (filters.DjangoFilterBackend,)
|
filter_backends = (filters.DjangoFilterBackend,)
|
||||||
|
|
||||||
|
@ -63,15 +71,12 @@ if django_filters:
|
||||||
model = BaseFilterableItem
|
model = BaseFilterableItem
|
||||||
|
|
||||||
class BaseFilterableItemFilterRootView(generics.ListCreateAPIView):
|
class BaseFilterableItemFilterRootView(generics.ListCreateAPIView):
|
||||||
model = FilterableItem
|
queryset = FilterableItem.objects.all()
|
||||||
|
serializer_class = FilterableItemSerializer
|
||||||
filter_class = BaseFilterableItemFilter
|
filter_class = BaseFilterableItemFilter
|
||||||
filter_backends = (filters.DjangoFilterBackend,)
|
filter_backends = (filters.DjangoFilterBackend,)
|
||||||
|
|
||||||
# Regression test for #814
|
# Regression test for #814
|
||||||
class FilterableItemSerializer(serializers.ModelSerializer):
|
|
||||||
class Meta:
|
|
||||||
model = FilterableItem
|
|
||||||
|
|
||||||
class FilterFieldsQuerysetView(generics.ListCreateAPIView):
|
class FilterFieldsQuerysetView(generics.ListCreateAPIView):
|
||||||
queryset = FilterableItem.objects.all()
|
queryset = FilterableItem.objects.all()
|
||||||
serializer_class = FilterableItemSerializer
|
serializer_class = FilterableItemSerializer
|
||||||
|
@ -323,6 +328,11 @@ class SearchFilterModel(models.Model):
|
||||||
text = models.CharField(max_length=100)
|
text = models.CharField(max_length=100)
|
||||||
|
|
||||||
|
|
||||||
|
class SearchFilterSerializer(serializers.ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = SearchFilterModel
|
||||||
|
|
||||||
|
|
||||||
class SearchFilterTests(TestCase):
|
class SearchFilterTests(TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
# Sequence of title/text is:
|
# Sequence of title/text is:
|
||||||
|
@ -342,7 +352,8 @@ class SearchFilterTests(TestCase):
|
||||||
|
|
||||||
def test_search(self):
|
def test_search(self):
|
||||||
class SearchListView(generics.ListAPIView):
|
class SearchListView(generics.ListAPIView):
|
||||||
model = SearchFilterModel
|
queryset = SearchFilterModel.objects.all()
|
||||||
|
serializer_class = SearchFilterSerializer
|
||||||
filter_backends = (filters.SearchFilter,)
|
filter_backends = (filters.SearchFilter,)
|
||||||
search_fields = ('title', 'text')
|
search_fields = ('title', 'text')
|
||||||
|
|
||||||
|
@ -359,7 +370,8 @@ class SearchFilterTests(TestCase):
|
||||||
|
|
||||||
def test_exact_search(self):
|
def test_exact_search(self):
|
||||||
class SearchListView(generics.ListAPIView):
|
class SearchListView(generics.ListAPIView):
|
||||||
model = SearchFilterModel
|
queryset = SearchFilterModel.objects.all()
|
||||||
|
serializer_class = SearchFilterSerializer
|
||||||
filter_backends = (filters.SearchFilter,)
|
filter_backends = (filters.SearchFilter,)
|
||||||
search_fields = ('=title', 'text')
|
search_fields = ('=title', 'text')
|
||||||
|
|
||||||
|
@ -375,7 +387,8 @@ class SearchFilterTests(TestCase):
|
||||||
|
|
||||||
def test_startswith_search(self):
|
def test_startswith_search(self):
|
||||||
class SearchListView(generics.ListAPIView):
|
class SearchListView(generics.ListAPIView):
|
||||||
model = SearchFilterModel
|
queryset = SearchFilterModel.objects.all()
|
||||||
|
serializer_class = SearchFilterSerializer
|
||||||
filter_backends = (filters.SearchFilter,)
|
filter_backends = (filters.SearchFilter,)
|
||||||
search_fields = ('title', '^text')
|
search_fields = ('title', '^text')
|
||||||
|
|
||||||
|
@ -392,7 +405,8 @@ class SearchFilterTests(TestCase):
|
||||||
def test_search_with_nonstandard_search_param(self):
|
def test_search_with_nonstandard_search_param(self):
|
||||||
with temporary_setting('SEARCH_PARAM', 'query', module=filters):
|
with temporary_setting('SEARCH_PARAM', 'query', module=filters):
|
||||||
class SearchListView(generics.ListAPIView):
|
class SearchListView(generics.ListAPIView):
|
||||||
model = SearchFilterModel
|
queryset = SearchFilterModel.objects.all()
|
||||||
|
serializer_class = SearchFilterSerializer
|
||||||
filter_backends = (filters.SearchFilter,)
|
filter_backends = (filters.SearchFilter,)
|
||||||
search_fields = ('title', 'text')
|
search_fields = ('title', 'text')
|
||||||
|
|
||||||
|
@ -418,6 +432,11 @@ class OrderingFilterRelatedModel(models.Model):
|
||||||
related_name="relateds")
|
related_name="relateds")
|
||||||
|
|
||||||
|
|
||||||
|
class OrderingFilterSerializer(serializers.ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = OrdringFilterModel
|
||||||
|
|
||||||
|
|
||||||
class OrderingFilterTests(TestCase):
|
class OrderingFilterTests(TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
# Sequence of title/text is:
|
# Sequence of title/text is:
|
||||||
|
@ -440,7 +459,8 @@ class OrderingFilterTests(TestCase):
|
||||||
|
|
||||||
def test_ordering(self):
|
def test_ordering(self):
|
||||||
class OrderingListView(generics.ListAPIView):
|
class OrderingListView(generics.ListAPIView):
|
||||||
model = OrdringFilterModel
|
queryset = OrdringFilterModel.objects.all()
|
||||||
|
serializer_class = OrderingFilterSerializer
|
||||||
filter_backends = (filters.OrderingFilter,)
|
filter_backends = (filters.OrderingFilter,)
|
||||||
ordering = ('title',)
|
ordering = ('title',)
|
||||||
ordering_fields = ('text',)
|
ordering_fields = ('text',)
|
||||||
|
@ -459,7 +479,8 @@ class OrderingFilterTests(TestCase):
|
||||||
|
|
||||||
def test_reverse_ordering(self):
|
def test_reverse_ordering(self):
|
||||||
class OrderingListView(generics.ListAPIView):
|
class OrderingListView(generics.ListAPIView):
|
||||||
model = OrdringFilterModel
|
queryset = OrdringFilterModel.objects.all()
|
||||||
|
serializer_class = OrderingFilterSerializer
|
||||||
filter_backends = (filters.OrderingFilter,)
|
filter_backends = (filters.OrderingFilter,)
|
||||||
ordering = ('title',)
|
ordering = ('title',)
|
||||||
ordering_fields = ('text',)
|
ordering_fields = ('text',)
|
||||||
|
@ -478,7 +499,8 @@ class OrderingFilterTests(TestCase):
|
||||||
|
|
||||||
def test_incorrectfield_ordering(self):
|
def test_incorrectfield_ordering(self):
|
||||||
class OrderingListView(generics.ListAPIView):
|
class OrderingListView(generics.ListAPIView):
|
||||||
model = OrdringFilterModel
|
queryset = OrdringFilterModel.objects.all()
|
||||||
|
serializer_class = OrderingFilterSerializer
|
||||||
filter_backends = (filters.OrderingFilter,)
|
filter_backends = (filters.OrderingFilter,)
|
||||||
ordering = ('title',)
|
ordering = ('title',)
|
||||||
ordering_fields = ('text',)
|
ordering_fields = ('text',)
|
||||||
|
@ -497,7 +519,8 @@ class OrderingFilterTests(TestCase):
|
||||||
|
|
||||||
def test_default_ordering(self):
|
def test_default_ordering(self):
|
||||||
class OrderingListView(generics.ListAPIView):
|
class OrderingListView(generics.ListAPIView):
|
||||||
model = OrdringFilterModel
|
queryset = OrdringFilterModel.objects.all()
|
||||||
|
serializer_class = OrderingFilterSerializer
|
||||||
filter_backends = (filters.OrderingFilter,)
|
filter_backends = (filters.OrderingFilter,)
|
||||||
ordering = ('title',)
|
ordering = ('title',)
|
||||||
oredering_fields = ('text',)
|
oredering_fields = ('text',)
|
||||||
|
@ -516,7 +539,8 @@ class OrderingFilterTests(TestCase):
|
||||||
|
|
||||||
def test_default_ordering_using_string(self):
|
def test_default_ordering_using_string(self):
|
||||||
class OrderingListView(generics.ListAPIView):
|
class OrderingListView(generics.ListAPIView):
|
||||||
model = OrdringFilterModel
|
queryset = OrdringFilterModel.objects.all()
|
||||||
|
serializer_class = OrderingFilterSerializer
|
||||||
filter_backends = (filters.OrderingFilter,)
|
filter_backends = (filters.OrderingFilter,)
|
||||||
ordering = 'title'
|
ordering = 'title'
|
||||||
ordering_fields = ('text',)
|
ordering_fields = ('text',)
|
||||||
|
@ -545,7 +569,7 @@ class OrderingFilterTests(TestCase):
|
||||||
new_related.save()
|
new_related.save()
|
||||||
|
|
||||||
class OrderingListView(generics.ListAPIView):
|
class OrderingListView(generics.ListAPIView):
|
||||||
model = OrdringFilterModel
|
serializer_class = OrderingFilterSerializer
|
||||||
filter_backends = (filters.OrderingFilter,)
|
filter_backends = (filters.OrderingFilter,)
|
||||||
ordering = 'title'
|
ordering = 'title'
|
||||||
ordering_fields = '__all__'
|
ordering_fields = '__all__'
|
||||||
|
@ -567,7 +591,8 @@ class OrderingFilterTests(TestCase):
|
||||||
def test_ordering_with_nonstandard_ordering_param(self):
|
def test_ordering_with_nonstandard_ordering_param(self):
|
||||||
with temporary_setting('ORDERING_PARAM', 'order', filters):
|
with temporary_setting('ORDERING_PARAM', 'order', filters):
|
||||||
class OrderingListView(generics.ListAPIView):
|
class OrderingListView(generics.ListAPIView):
|
||||||
model = OrdringFilterModel
|
queryset = OrdringFilterModel.objects.all()
|
||||||
|
serializer_class = OrderingFilterSerializer
|
||||||
filter_backends = (filters.OrderingFilter,)
|
filter_backends = (filters.OrderingFilter,)
|
||||||
ordering = ('title',)
|
ordering = ('title',)
|
||||||
ordering_fields = ('text',)
|
ordering_fields = ('text',)
|
||||||
|
|
|
@ -11,18 +11,30 @@ from tests.models import ForeignKeySource, ForeignKeyTarget
|
||||||
factory = APIRequestFactory()
|
factory = APIRequestFactory()
|
||||||
|
|
||||||
|
|
||||||
|
class BasicSerializer(serializers.ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = BasicModel
|
||||||
|
|
||||||
|
|
||||||
|
class ForeignKeySerializer(serializers.ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = ForeignKeySource
|
||||||
|
|
||||||
|
|
||||||
class RootView(generics.ListCreateAPIView):
|
class RootView(generics.ListCreateAPIView):
|
||||||
"""
|
"""
|
||||||
Example description for OPTIONS.
|
Example description for OPTIONS.
|
||||||
"""
|
"""
|
||||||
model = BasicModel
|
queryset = BasicModel.objects.all()
|
||||||
|
serializer_class = BasicSerializer
|
||||||
|
|
||||||
|
|
||||||
class InstanceView(generics.RetrieveUpdateDestroyAPIView):
|
class InstanceView(generics.RetrieveUpdateDestroyAPIView):
|
||||||
"""
|
"""
|
||||||
Example description for OPTIONS.
|
Example description for OPTIONS.
|
||||||
"""
|
"""
|
||||||
model = BasicModel
|
queryset = BasicModel.objects.all()
|
||||||
|
serializer_class = BasicSerializer
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
queryset = super(InstanceView, self).get_queryset()
|
queryset = super(InstanceView, self).get_queryset()
|
||||||
|
@ -33,7 +45,8 @@ class FKInstanceView(generics.RetrieveUpdateDestroyAPIView):
|
||||||
"""
|
"""
|
||||||
FK: example description for OPTIONS.
|
FK: example description for OPTIONS.
|
||||||
"""
|
"""
|
||||||
model = ForeignKeySource
|
queryset = ForeignKeySource.objects.all()
|
||||||
|
serializer_class = ForeignKeySerializer
|
||||||
|
|
||||||
|
|
||||||
class SlugSerializer(serializers.ModelSerializer):
|
class SlugSerializer(serializers.ModelSerializer):
|
||||||
|
@ -48,7 +61,7 @@ class SlugBasedInstanceView(InstanceView):
|
||||||
"""
|
"""
|
||||||
A model with a slug-field.
|
A model with a slug-field.
|
||||||
"""
|
"""
|
||||||
model = SlugBasedModel
|
queryset = SlugBasedModel.objects.all()
|
||||||
serializer_class = SlugSerializer
|
serializer_class = SlugSerializer
|
||||||
lookup_field = 'slug'
|
lookup_field = 'slug'
|
||||||
|
|
||||||
|
@ -503,7 +516,7 @@ class TestOverriddenGetObject(TestCase):
|
||||||
"""
|
"""
|
||||||
Example detail view for override of get_object().
|
Example detail view for override of get_object().
|
||||||
"""
|
"""
|
||||||
model = BasicModel
|
serializer_class = BasicSerializer
|
||||||
|
|
||||||
def get_object(self):
|
def get_object(self):
|
||||||
pk = int(self.kwargs['pk'])
|
pk = int(self.kwargs['pk'])
|
||||||
|
@ -573,7 +586,7 @@ class ClassASerializer(serializers.ModelSerializer):
|
||||||
|
|
||||||
class ExampleView(generics.ListCreateAPIView):
|
class ExampleView(generics.ListCreateAPIView):
|
||||||
serializer_class = ClassASerializer
|
serializer_class = ClassASerializer
|
||||||
model = ClassA
|
queryset = ClassA.objects.all()
|
||||||
|
|
||||||
|
|
||||||
class TestM2MBrowseableAPI(TestCase):
|
class TestM2MBrowseableAPI(TestCase):
|
||||||
|
@ -603,7 +616,7 @@ class TwoFieldModel(models.Model):
|
||||||
|
|
||||||
|
|
||||||
class DynamicSerializerView(generics.ListCreateAPIView):
|
class DynamicSerializerView(generics.ListCreateAPIView):
|
||||||
model = TwoFieldModel
|
queryset = TwoFieldModel.objects.all()
|
||||||
renderer_classes = (renderers.BrowsableAPIRenderer, renderers.JSONRenderer)
|
renderer_classes = (renderers.BrowsableAPIRenderer, renderers.JSONRenderer)
|
||||||
|
|
||||||
def get_serializer_class(self):
|
def get_serializer_class(self):
|
||||||
|
@ -612,8 +625,11 @@ class DynamicSerializerView(generics.ListCreateAPIView):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = TwoFieldModel
|
model = TwoFieldModel
|
||||||
fields = ('field_b',)
|
fields = ('field_b',)
|
||||||
return DynamicSerializer
|
else:
|
||||||
return super(DynamicSerializerView, self).get_serializer_class()
|
class DynamicSerializer(serializers.ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = TwoFieldModel
|
||||||
|
return DynamicSerializer
|
||||||
|
|
||||||
|
|
||||||
class TestFilterBackendAppliedToViews(TestCase):
|
class TestFilterBackendAppliedToViews(TestCase):
|
||||||
|
|
|
@ -39,59 +39,85 @@ class AlbumSerializer(serializers.ModelSerializer):
|
||||||
fields = ('title', 'url')
|
fields = ('title', 'url')
|
||||||
|
|
||||||
|
|
||||||
|
class BasicSerializer(serializers.HyperlinkedModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = BasicModel
|
||||||
|
|
||||||
|
|
||||||
|
class AnchorSerializer(serializers.HyperlinkedModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = Anchor
|
||||||
|
|
||||||
|
|
||||||
|
class ManyToManySerializer(serializers.HyperlinkedModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = ManyToManyModel
|
||||||
|
|
||||||
|
|
||||||
|
class BlogPostSerializer(serializers.ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = BlogPost
|
||||||
|
|
||||||
|
|
||||||
|
class OptionalRelationSerializer(serializers.HyperlinkedModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = OptionalRelationModel
|
||||||
|
|
||||||
|
|
||||||
class BasicList(generics.ListCreateAPIView):
|
class BasicList(generics.ListCreateAPIView):
|
||||||
model = BasicModel
|
queryset = BasicModel.objects.all()
|
||||||
model_serializer_class = serializers.HyperlinkedModelSerializer
|
serializer_class = BasicSerializer
|
||||||
|
|
||||||
|
|
||||||
class BasicDetail(generics.RetrieveUpdateDestroyAPIView):
|
class BasicDetail(generics.RetrieveUpdateDestroyAPIView):
|
||||||
model = BasicModel
|
queryset = BasicModel.objects.all()
|
||||||
model_serializer_class = serializers.HyperlinkedModelSerializer
|
serializer_class = BasicSerializer
|
||||||
|
|
||||||
|
|
||||||
class AnchorDetail(generics.RetrieveAPIView):
|
class AnchorDetail(generics.RetrieveAPIView):
|
||||||
model = Anchor
|
queryset = Anchor.objects.all()
|
||||||
model_serializer_class = serializers.HyperlinkedModelSerializer
|
serializer_class = AnchorSerializer
|
||||||
|
|
||||||
|
|
||||||
class ManyToManyList(generics.ListAPIView):
|
class ManyToManyList(generics.ListAPIView):
|
||||||
model = ManyToManyModel
|
queryset = ManyToManyModel.objects.all()
|
||||||
model_serializer_class = serializers.HyperlinkedModelSerializer
|
serializer_class = ManyToManySerializer
|
||||||
|
|
||||||
|
|
||||||
class ManyToManyDetail(generics.RetrieveAPIView):
|
class ManyToManyDetail(generics.RetrieveAPIView):
|
||||||
model = ManyToManyModel
|
queryset = ManyToManyModel.objects.all()
|
||||||
model_serializer_class = serializers.HyperlinkedModelSerializer
|
serializer_class = ManyToManySerializer
|
||||||
|
|
||||||
|
|
||||||
class BlogPostCommentListCreate(generics.ListCreateAPIView):
|
class BlogPostCommentListCreate(generics.ListCreateAPIView):
|
||||||
model = BlogPostComment
|
queryset = BlogPostComment.objects.all()
|
||||||
serializer_class = BlogPostCommentSerializer
|
serializer_class = BlogPostCommentSerializer
|
||||||
|
|
||||||
|
|
||||||
class BlogPostCommentDetail(generics.RetrieveAPIView):
|
class BlogPostCommentDetail(generics.RetrieveAPIView):
|
||||||
model = BlogPostComment
|
queryset = BlogPostComment.objects.all()
|
||||||
serializer_class = BlogPostCommentSerializer
|
serializer_class = BlogPostCommentSerializer
|
||||||
|
|
||||||
|
|
||||||
class BlogPostDetail(generics.RetrieveAPIView):
|
class BlogPostDetail(generics.RetrieveAPIView):
|
||||||
model = BlogPost
|
queryset = BlogPost.objects.all()
|
||||||
|
serializer_class = BlogPostSerializer
|
||||||
|
|
||||||
|
|
||||||
class PhotoListCreate(generics.ListCreateAPIView):
|
class PhotoListCreate(generics.ListCreateAPIView):
|
||||||
model = Photo
|
queryset = Photo.objects.all()
|
||||||
model_serializer_class = PhotoSerializer
|
serializer_class = PhotoSerializer
|
||||||
|
|
||||||
|
|
||||||
class AlbumDetail(generics.RetrieveAPIView):
|
class AlbumDetail(generics.RetrieveAPIView):
|
||||||
model = Album
|
queryset = Album.objects.all()
|
||||||
serializer_class = AlbumSerializer
|
serializer_class = AlbumSerializer
|
||||||
lookup_field = 'title'
|
lookup_field = 'title'
|
||||||
|
|
||||||
|
|
||||||
class OptionalRelationDetail(generics.RetrieveUpdateDestroyAPIView):
|
class OptionalRelationDetail(generics.RetrieveUpdateDestroyAPIView):
|
||||||
model = OptionalRelationModel
|
queryset = OptionalRelationModel.objects.all()
|
||||||
model_serializer_class = serializers.HyperlinkedModelSerializer
|
serializer_class = OptionalRelationSerializer
|
||||||
|
|
||||||
|
|
||||||
urlpatterns = patterns(
|
urlpatterns = patterns(
|
||||||
|
|
|
@ -1,10 +1,19 @@
|
||||||
from django.core.urlresolvers import reverse
|
from django.core.urlresolvers import reverse
|
||||||
|
|
||||||
from django.conf.urls import patterns, url
|
from django.conf.urls import patterns, url
|
||||||
|
from rest_framework import serializers, generics
|
||||||
from rest_framework.test import APITestCase
|
from rest_framework.test import APITestCase
|
||||||
from tests.models import NullableForeignKeySource
|
from tests.models import NullableForeignKeySource
|
||||||
from tests.serializers import NullableFKSourceSerializer
|
|
||||||
from tests.views import NullableFKSourceDetail
|
|
||||||
|
class NullableFKSourceSerializer(serializers.ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = NullableForeignKeySource
|
||||||
|
|
||||||
|
|
||||||
|
class NullableFKSourceDetail(generics.RetrieveUpdateDestroyAPIView):
|
||||||
|
queryset = NullableForeignKeySource.objects.all()
|
||||||
|
serializer_class = NullableFKSourceSerializer
|
||||||
|
|
||||||
|
|
||||||
urlpatterns = patterns(
|
urlpatterns = patterns(
|
||||||
|
|
|
@ -4,7 +4,7 @@ from decimal import Decimal
|
||||||
from django.core.paginator import Paginator
|
from django.core.paginator import Paginator
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
from django.utils import unittest
|
from django.utils import unittest
|
||||||
from rest_framework import generics, status, pagination, filters, serializers
|
from rest_framework import generics, serializers, status, pagination, filters
|
||||||
from rest_framework.compat import django_filters
|
from rest_framework.compat import django_filters
|
||||||
from rest_framework.test import APIRequestFactory
|
from rest_framework.test import APIRequestFactory
|
||||||
from .models import BasicModel, FilterableItem
|
from .models import BasicModel, FilterableItem
|
||||||
|
@ -22,11 +22,22 @@ def split_arguments_from_url(url):
|
||||||
return path, args
|
return path, args
|
||||||
|
|
||||||
|
|
||||||
|
class BasicSerializer(serializers.ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = BasicModel
|
||||||
|
|
||||||
|
|
||||||
|
class FilterableItemSerializer(serializers.ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = FilterableItem
|
||||||
|
|
||||||
|
|
||||||
class RootView(generics.ListCreateAPIView):
|
class RootView(generics.ListCreateAPIView):
|
||||||
"""
|
"""
|
||||||
Example description for OPTIONS.
|
Example description for OPTIONS.
|
||||||
"""
|
"""
|
||||||
model = BasicModel
|
queryset = BasicModel.objects.all()
|
||||||
|
serializer_class = BasicSerializer
|
||||||
paginate_by = 10
|
paginate_by = 10
|
||||||
|
|
||||||
|
|
||||||
|
@ -34,14 +45,16 @@ class DefaultPageSizeKwargView(generics.ListAPIView):
|
||||||
"""
|
"""
|
||||||
View for testing default paginate_by_param usage
|
View for testing default paginate_by_param usage
|
||||||
"""
|
"""
|
||||||
model = BasicModel
|
queryset = BasicModel.objects.all()
|
||||||
|
serializer_class = BasicSerializer
|
||||||
|
|
||||||
|
|
||||||
class PaginateByParamView(generics.ListAPIView):
|
class PaginateByParamView(generics.ListAPIView):
|
||||||
"""
|
"""
|
||||||
View for testing custom paginate_by_param usage
|
View for testing custom paginate_by_param usage
|
||||||
"""
|
"""
|
||||||
model = BasicModel
|
queryset = BasicModel.objects.all()
|
||||||
|
serializer_class = BasicSerializer
|
||||||
paginate_by_param = 'page_size'
|
paginate_by_param = 'page_size'
|
||||||
|
|
||||||
|
|
||||||
|
@ -49,7 +62,8 @@ class MaxPaginateByView(generics.ListAPIView):
|
||||||
"""
|
"""
|
||||||
View for testing custom max_paginate_by usage
|
View for testing custom max_paginate_by usage
|
||||||
"""
|
"""
|
||||||
model = BasicModel
|
queryset = BasicModel.objects.all()
|
||||||
|
serializer_class = BasicSerializer
|
||||||
paginate_by = 3
|
paginate_by = 3
|
||||||
max_paginate_by = 5
|
max_paginate_by = 5
|
||||||
paginate_by_param = 'page_size'
|
paginate_by_param = 'page_size'
|
||||||
|
@ -140,7 +154,8 @@ class IntegrationTestPaginationAndFiltering(TestCase):
|
||||||
fields = ['text', 'decimal', 'date']
|
fields = ['text', 'decimal', 'date']
|
||||||
|
|
||||||
class FilterFieldsRootView(generics.ListCreateAPIView):
|
class FilterFieldsRootView(generics.ListCreateAPIView):
|
||||||
model = FilterableItem
|
queryset = FilterableItem.objects.all()
|
||||||
|
serializer_class = FilterableItemSerializer
|
||||||
paginate_by = 10
|
paginate_by = 10
|
||||||
filter_class = DecimalFilter
|
filter_class = DecimalFilter
|
||||||
filter_backends = (filters.DjangoFilterBackend,)
|
filter_backends = (filters.DjangoFilterBackend,)
|
||||||
|
@ -188,7 +203,8 @@ class IntegrationTestPaginationAndFiltering(TestCase):
|
||||||
return queryset.filter(decimal__lt=Decimal(request.GET['decimal']))
|
return queryset.filter(decimal__lt=Decimal(request.GET['decimal']))
|
||||||
|
|
||||||
class BasicFilterFieldsRootView(generics.ListCreateAPIView):
|
class BasicFilterFieldsRootView(generics.ListCreateAPIView):
|
||||||
model = FilterableItem
|
queryset = FilterableItem.objects.all()
|
||||||
|
serializer_class = FilterableItemSerializer
|
||||||
paginate_by = 10
|
paginate_by = 10
|
||||||
filter_backends = (DecimalFilterBackend,)
|
filter_backends = (DecimalFilterBackend,)
|
||||||
|
|
||||||
|
@ -387,7 +403,7 @@ class TestContextPassedToCustomField(TestCase):
|
||||||
|
|
||||||
def test_with_pagination(self):
|
def test_with_pagination(self):
|
||||||
class ListView(generics.ListCreateAPIView):
|
class ListView(generics.ListCreateAPIView):
|
||||||
model = BasicModel
|
queryset = BasicModel.objects.all()
|
||||||
serializer_class = BasicModelSerializer
|
serializer_class = BasicModelSerializer
|
||||||
paginate_by = 1
|
paginate_by = 1
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,7 @@ from django.contrib.auth.models import User, Permission, Group
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
from django.utils import unittest
|
from django.utils import unittest
|
||||||
from rest_framework import generics, status, permissions, authentication, HTTP_HEADER_ENCODING
|
from rest_framework import generics, serializers, status, permissions, authentication, HTTP_HEADER_ENCODING
|
||||||
from rest_framework.compat import guardian, get_model_name
|
from rest_framework.compat import guardian, get_model_name
|
||||||
from rest_framework.filters import DjangoObjectPermissionsFilter
|
from rest_framework.filters import DjangoObjectPermissionsFilter
|
||||||
from rest_framework.test import APIRequestFactory
|
from rest_framework.test import APIRequestFactory
|
||||||
|
@ -13,14 +13,21 @@ import base64
|
||||||
factory = APIRequestFactory()
|
factory = APIRequestFactory()
|
||||||
|
|
||||||
|
|
||||||
|
class BasicSerializer(serializers.ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = BasicModel
|
||||||
|
|
||||||
|
|
||||||
class RootView(generics.ListCreateAPIView):
|
class RootView(generics.ListCreateAPIView):
|
||||||
model = BasicModel
|
queryset = BasicModel.objects.all()
|
||||||
|
serializer_class = BasicSerializer
|
||||||
authentication_classes = [authentication.BasicAuthentication]
|
authentication_classes = [authentication.BasicAuthentication]
|
||||||
permission_classes = [permissions.DjangoModelPermissions]
|
permission_classes = [permissions.DjangoModelPermissions]
|
||||||
|
|
||||||
|
|
||||||
class InstanceView(generics.RetrieveUpdateDestroyAPIView):
|
class InstanceView(generics.RetrieveUpdateDestroyAPIView):
|
||||||
model = BasicModel
|
queryset = BasicModel.objects.all()
|
||||||
|
serializer_class = BasicSerializer
|
||||||
authentication_classes = [authentication.BasicAuthentication]
|
authentication_classes = [authentication.BasicAuthentication]
|
||||||
permission_classes = [permissions.DjangoModelPermissions]
|
permission_classes = [permissions.DjangoModelPermissions]
|
||||||
|
|
||||||
|
@ -167,6 +174,11 @@ class BasicPermModel(models.Model):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class BasicPermSerializer(serializers.ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = BasicPermModel
|
||||||
|
|
||||||
|
|
||||||
# Custom object-level permission, that includes 'view' permissions
|
# Custom object-level permission, that includes 'view' permissions
|
||||||
class ViewObjectPermissions(permissions.DjangoObjectPermissions):
|
class ViewObjectPermissions(permissions.DjangoObjectPermissions):
|
||||||
perms_map = {
|
perms_map = {
|
||||||
|
@ -181,7 +193,8 @@ class ViewObjectPermissions(permissions.DjangoObjectPermissions):
|
||||||
|
|
||||||
|
|
||||||
class ObjectPermissionInstanceView(generics.RetrieveUpdateDestroyAPIView):
|
class ObjectPermissionInstanceView(generics.RetrieveUpdateDestroyAPIView):
|
||||||
model = BasicPermModel
|
queryset = BasicPermModel.objects.all()
|
||||||
|
serializer_class = BasicPermSerializer
|
||||||
authentication_classes = [authentication.BasicAuthentication]
|
authentication_classes = [authentication.BasicAuthentication]
|
||||||
permission_classes = [ViewObjectPermissions]
|
permission_classes = [ViewObjectPermissions]
|
||||||
|
|
||||||
|
@ -189,7 +202,8 @@ object_permissions_view = ObjectPermissionInstanceView.as_view()
|
||||||
|
|
||||||
|
|
||||||
class ObjectPermissionListView(generics.ListAPIView):
|
class ObjectPermissionListView(generics.ListAPIView):
|
||||||
model = BasicPermModel
|
queryset = BasicPermModel.objects.all()
|
||||||
|
serializer_class = BasicPermSerializer
|
||||||
authentication_classes = [authentication.BasicAuthentication]
|
authentication_classes = [authentication.BasicAuthentication]
|
||||||
permission_classes = [ViewObjectPermissions]
|
permission_classes = [ViewObjectPermissions]
|
||||||
|
|
||||||
|
|
|
@ -86,14 +86,15 @@ class HTMLView1(APIView):
|
||||||
|
|
||||||
|
|
||||||
class HTMLNewModelViewSet(viewsets.ModelViewSet):
|
class HTMLNewModelViewSet(viewsets.ModelViewSet):
|
||||||
model = BasicModel
|
serializer_class = BasicModelSerializer
|
||||||
|
queryset = BasicModel.objects.all()
|
||||||
|
|
||||||
|
|
||||||
class HTMLNewModelView(generics.ListCreateAPIView):
|
class HTMLNewModelView(generics.ListCreateAPIView):
|
||||||
renderer_classes = (BrowsableAPIRenderer,)
|
renderer_classes = (BrowsableAPIRenderer,)
|
||||||
permission_classes = []
|
permission_classes = []
|
||||||
serializer_class = BasicModelSerializer
|
serializer_class = BasicModelSerializer
|
||||||
model = BasicModel
|
queryset = BasicModel.objects.all()
|
||||||
|
|
||||||
|
|
||||||
new_model_viewset_router = routers.DefaultRouter()
|
new_model_viewset_router = routers.DefaultRouter()
|
||||||
|
|
|
@ -22,7 +22,7 @@ class ValidationModelSerializer(serializers.ModelSerializer):
|
||||||
|
|
||||||
|
|
||||||
class UpdateValidationModel(generics.RetrieveUpdateDestroyAPIView):
|
class UpdateValidationModel(generics.RetrieveUpdateDestroyAPIView):
|
||||||
model = ValidationModel
|
queryset = ValidationModel.objects.all()
|
||||||
serializer_class = ValidationModelSerializer
|
serializer_class = ValidationModelSerializer
|
||||||
|
|
||||||
|
|
||||||
|
@ -117,7 +117,7 @@ class ValidationMaxValueValidatorModelSerializer(serializers.ModelSerializer):
|
||||||
|
|
||||||
|
|
||||||
class UpdateMaxValueValidationModel(generics.RetrieveUpdateDestroyAPIView):
|
class UpdateMaxValueValidationModel(generics.RetrieveUpdateDestroyAPIView):
|
||||||
model = ValidationMaxValueValidatorModel
|
queryset = ValidationMaxValueValidatorModel.objects.all()
|
||||||
serializer_class = ValidationMaxValueValidatorModelSerializer
|
serializer_class = ValidationMaxValueValidatorModelSerializer
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,8 +0,0 @@
|
||||||
from rest_framework import generics
|
|
||||||
from .models import NullableForeignKeySource
|
|
||||||
from .serializers import NullableFKSourceSerializer
|
|
||||||
|
|
||||||
|
|
||||||
class NullableFKSourceDetail(generics.RetrieveUpdateDestroyAPIView):
|
|
||||||
model = NullableForeignKeySource
|
|
||||||
model_serializer_class = NullableFKSourceSerializer
|
|
Loading…
Reference in New Issue
Block a user