diff --git a/rest_framework/fields.py b/rest_framework/fields.py index e4be54751d..da312cd5ae 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -375,12 +375,13 @@ def bind(self, field_name, parent): # In order to enforce a consistent style, we error if a redundant # 'source' argument has been used. For example: # my_field = serializer.CharField(source='my_field') - assert self.source != field_name, ( - "It is redundant to specify `source='%s'` on field '%s' in " - "serializer '%s', because it is the same as the field name. " - "Remove the `source` keyword argument." % - (field_name, self.__class__.__name__, parent.__class__.__name__) - ) + if field_name == self.source: + warnings.warn( + "It is redundant to specify `source='%s'` on field '%s' in " + "serializer '%s', because it is the same as the field name. " + "Remove the `source` keyword argument." % + (field_name, self.__class__.__name__, parent.__class__.__name__), + ) self.field_name = field_name self.parent = parent diff --git a/rest_framework/generics.py b/rest_framework/generics.py index 55cfafda44..d00a8e5dc2 100644 --- a/rest_framework/generics.py +++ b/rest_framework/generics.py @@ -21,7 +21,7 @@ def get_object_or_404(queryset, *filter_args, **filter_kwargs): raise Http404 -class GenericAPIView(views.APIView): +class GenericAPIView(mixins.PaginationMixin, views.APIView): """ Base class for all other generic views. """ @@ -42,9 +42,6 @@ class GenericAPIView(views.APIView): # The filter backend classes to use for queryset filtering filter_backends = api_settings.DEFAULT_FILTER_BACKENDS - # The style to use for queryset pagination. - pagination_class = api_settings.DEFAULT_PAGINATION_CLASS - def get_queryset(self): """ Get the list of items for this view. @@ -150,33 +147,6 @@ def filter_queryset(self, queryset): queryset = backend().filter_queryset(self.request, queryset, self) return queryset - @property - def paginator(self): - """ - The paginator instance associated with the view, or `None`. - """ - if not hasattr(self, '_paginator'): - if self.pagination_class is None: - self._paginator = None - else: - self._paginator = self.pagination_class() - return self._paginator - - def paginate_queryset(self, queryset): - """ - Return a single page of results, or `None` if pagination is disabled. - """ - if self.paginator is None: - return None - return self.paginator.paginate_queryset(queryset, self.request, view=self) - - def get_paginated_response(self, data): - """ - Return a paginated style `Response` object for the given output data. - """ - assert self.paginator is not None - return self.paginator.get_paginated_response(data) - # Concrete view classes that provide method handlers # by composing the mixin classes with the base view. diff --git a/rest_framework/mixins.py b/rest_framework/mixins.py index 7fa8947cb9..6c58606c40 100644 --- a/rest_framework/mixins.py +++ b/rest_framework/mixins.py @@ -30,6 +30,41 @@ def get_success_headers(self, data): return {} +class PaginationMixin(object): + """ + Adds pagination methods to a view. + """ + # The style to use for queryset pagination. + pagination_class = api_settings.DEFAULT_PAGINATION_CLASS + + @property + def paginator(self): + """ + The paginator instance associated with the view, or `None`. + """ + if not hasattr(self, '_paginator'): + if self.pagination_class is None: + self._paginator = None + else: + self._paginator = self.pagination_class() + return self._paginator + + def get_paginated_response(self, data): + """ + Return a paginated style `Response` object for the given output data. + """ + assert self.paginator is not None + return self.paginator.get_paginated_response(data) + + def paginate_queryset(self, queryset): + """ + Return a single page of results, or `None` if pagination is disabled. + """ + if self.paginator is None: + return None + return self.paginator.paginate_queryset(queryset, self.request, view=self) + + class ListModelMixin: """ List a queryset. diff --git a/rest_framework/relations.py b/rest_framework/relations.py index c987007842..d3efd2882a 100644 --- a/rest_framework/relations.py +++ b/rest_framework/relations.py @@ -1,4 +1,5 @@ import sys +import warnings from collections import OrderedDict from urllib import parse @@ -108,10 +109,11 @@ def __init__(self, **kwargs): 'Relational field must provide a `queryset` argument, ' 'override `get_queryset`, or set read_only=`True`.' ) - assert not (self.queryset is not None and kwargs.get('read_only')), ( - 'Relational fields should not provide a `queryset` argument, ' - 'when setting read_only=`True`.' - ) + if self.queryset is not None and kwargs.get('read_only'): + warnings.warn( + 'Relational fields should not provide a `queryset` argument, ' + 'when setting read_only=`True`.' + ) kwargs.pop('many', None) kwargs.pop('allow_empty', None) super().__init__(**kwargs) diff --git a/tests/test_fields.py b/tests/test_fields.py index 5842553f02..73a851868a 100644 --- a/tests/test_fields.py +++ b/tests/test_fields.py @@ -186,9 +186,10 @@ class ExampleSerializer(serializers.Serializer): def test_redundant_source(self): class ExampleSerializer(serializers.Serializer): example_field = serializers.CharField(source='example_field') - with pytest.raises(AssertionError) as exc_info: + with pytest.warns(UserWarning) as record: ExampleSerializer().fields - assert str(exc_info.value) == ( + assert len(record) == 1 + assert record[0].message.args[0] == ( "It is redundant to specify `source='example_field'` on field " "'CharField' in serializer 'ExampleSerializer', because it is the " "same as the field name. Remove the `source` keyword argument."