From 5443dd5f3c5f75cd1524eb26c6d5b53df3594f9b Mon Sep 17 00:00:00 2001 From: Marko Tibold Date: Tue, 13 Nov 2012 23:26:17 +0100 Subject: Added a FileField and an ImageField (copied from django.forms.fields). Adjusted generics, mixins and serializers to take a `files` arg where applicable. --- rest_framework/fields.py | 91 +++++++++++++++++++++++++++++++++++++++++++ rest_framework/generics.py | 3 +- rest_framework/mixins.py | 4 +- rest_framework/serializers.py | 21 ++++++---- 4 files changed, 108 insertions(+), 11 deletions(-) diff --git a/rest_framework/fields.py b/rest_framework/fields.py index 4c206426..9cd84c0d 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -904,3 +904,94 @@ class FloatField(WritableField): except (TypeError, ValueError): msg = self.error_messages['invalid'] % value raise ValidationError(msg) + + +class FileField(WritableField): + type_name = 'FileField' + + default_error_messages = { + 'invalid': _("No file was submitted. Check the encoding type on the form."), + 'missing': _("No file was submitted."), + 'empty': _("The submitted file is empty."), + 'max_length': _('Ensure this filename has at most %(max)d characters (it has %(length)d).'), + 'contradiction': _('Please either submit a file or check the clear checkbox, not both.') + } + + def __init__(self, *args, **kwargs): + self.max_length = kwargs.pop('max_length', None) + self.allow_empty_file = kwargs.pop('allow_empty_file', False) + super(FileField, self).__init__(*args, **kwargs) + + def from_native(self, data): + if data in validators.EMPTY_VALUES: + return None + + # UploadedFile objects should have name and size attributes. + try: + file_name = data.name + file_size = data.size + except AttributeError: + raise ValidationError(self.error_messages['invalid']) + + if self.max_length is not None and len(file_name) > self.max_length: + error_values = {'max': self.max_length, 'length': len(file_name)} + raise ValidationError(self.error_messages['max_length'] % error_values) + if not file_name: + raise ValidationError(self.error_messages['invalid']) + if not self.allow_empty_file and not file_size: + raise ValidationError(self.error_messages['empty']) + + return data + + def to_native(self, value): + """ + No need to return anything, the file can be accessed form its url. + """ + return + + +class ImageField(FileField): + default_error_messages = { + 'invalid_image': _("Upload a valid image. The file you uploaded was either not an image or a corrupted image."), + } + + def from_native(self, data): + """ + Checks that the file-upload field data contains a valid image (GIF, JPG, + PNG, possibly others -- whatever the Python Imaging Library supports). + """ + f = super(ImageField, self).from_native(data) + if f is None: + return None + + # Try to import PIL in either of the two ways it can end up installed. + try: + from PIL import Image + except ImportError: + import Image + + # We need to get a file object for PIL. We might have a path or we might + # have to read the data into memory. + if hasattr(data, 'temporary_file_path'): + file = data.temporary_file_path() + else: + if hasattr(data, 'read'): + file = BytesIO(data.read()) + else: + file = BytesIO(data['content']) + + try: + # load() could spot a truncated JPEG, but it loads the entire + # image in memory, which is a DoS vector. See #3848 and #18520. + # verify() must be called immediately after the constructor. + Image.open(file).verify() + except ImportError: + # Under PyPy, it is possible to import PIL. However, the underlying + # _imaging C module isn't available, so an ImportError will be + # raised. Catch and re-raise. + raise + except Exception: # Python Imaging Library doesn't recognize it as an image + raise ValidationError(self.error_messages['invalid_image']) + if hasattr(f, 'seek') and callable(f.seek): + f.seek(0) + return f diff --git a/rest_framework/generics.py b/rest_framework/generics.py index ebd06e45..d47c39cd 100644 --- a/rest_framework/generics.py +++ b/rest_framework/generics.py @@ -44,11 +44,10 @@ class GenericAPIView(views.APIView): return serializer_class def get_serializer(self, instance=None, data=None, files=None): - # TODO: add support for files # TODO: add support for seperate serializer/deserializer serializer_class = self.get_serializer_class() context = self.get_serializer_context() - return serializer_class(instance, data=data, context=context) + return serializer_class(instance, data=data, files=files, context=context) class MultipleObjectAPIView(MultipleObjectMixin, GenericAPIView): diff --git a/rest_framework/mixins.py b/rest_framework/mixins.py index c3625a88..991f4c50 100644 --- a/rest_framework/mixins.py +++ b/rest_framework/mixins.py @@ -15,7 +15,7 @@ class CreateModelMixin(object): Should be mixed in with any `BaseView`. """ def create(self, request, *args, **kwargs): - serializer = self.get_serializer(data=request.DATA) + serializer = self.get_serializer(data=request.DATA, files=request.FILES) if serializer.is_valid(): self.pre_save(serializer.object) self.object = serializer.save() @@ -80,7 +80,7 @@ class UpdateModelMixin(object): self.object = None success_status = status.HTTP_201_CREATED - serializer = self.get_serializer(self.object, data=request.DATA) + serializer = self.get_serializer(self.object, data=request.DATA, files=request.FILES) if serializer.is_valid(): self.pre_save(serializer.object) diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index 46d4765e..a46432a9 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -91,7 +91,7 @@ class BaseSerializer(Field): _options_class = SerializerOptions _dict_class = SortedDictWithMetadata # Set to unsorted dict for backwards compatability with unsorted implementations. - def __init__(self, instance=None, data=None, context=None, **kwargs): + def __init__(self, instance=None, data=None, files=None, context=None, **kwargs): super(BaseSerializer, self).__init__(**kwargs) self.opts = self._options_class(self.Meta) self.fields = copy.deepcopy(self.base_fields) @@ -101,9 +101,11 @@ class BaseSerializer(Field): self.context = context or {} self.init_data = data + self.init_files = files self.object = instance self._data = None + self._files = None self._errors = None ##### @@ -187,7 +189,7 @@ class BaseSerializer(Field): ret.fields[key] = field return ret - def restore_fields(self, data): + def restore_fields(self, data, files): """ Core of deserialization, together with `restore_object`. Converts a dictionary of data into a dictionary of deserialized fields. @@ -196,7 +198,10 @@ class BaseSerializer(Field): reverted_data = {} for field_name, field in fields.items(): try: - field.field_from_native(data, field_name, reverted_data) + if isinstance(field, (FileField, ImageField)): + field.field_from_native(files, field_name, reverted_data) + else: + field.field_from_native(data, field_name, reverted_data) except ValidationError as err: self._errors[field_name] = list(err.messages) @@ -250,7 +255,7 @@ class BaseSerializer(Field): return [self.convert_object(item) for item in obj] return self.convert_object(obj) - def from_native(self, data): + def from_native(self, data, files): """ Deserialize primatives -> objects. """ @@ -259,8 +264,8 @@ class BaseSerializer(Field): return (self.from_native(item) for item in data) self._errors = {} - if data is not None: - attrs = self.restore_fields(data) + if data is not None or files is not None: + attrs = self.restore_fields(data, files) attrs = self.perform_validation(attrs) else: self._errors['non_field_errors'] = ['No input provided'] @@ -288,7 +293,7 @@ class BaseSerializer(Field): setting self.object if no errors occurred. """ if self._errors is None: - obj = self.from_native(self.init_data) + obj = self.from_native(self.init_data, self.init_files) if not self._errors: self.object = obj return self._errors @@ -440,6 +445,8 @@ class ModelSerializer(Serializer): models.TextField: CharField, models.CommaSeparatedIntegerField: CharField, models.BooleanField: BooleanField, + models.FileField: FileField, + models.ImageField: ImageField, } try: return field_mapping[model_field.__class__](**kwargs) -- cgit v1.2.3 From 8cdbc0a33a69f0a170e92be47189f6006c147137 Mon Sep 17 00:00:00 2001 From: Marko Tibold Date: Wed, 14 Nov 2012 00:09:39 +0100 Subject: Properly render file inputs in the Browsable api. --- rest_framework/fields.py | 2 +- rest_framework/renderers.py | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/rest_framework/fields.py b/rest_framework/fields.py index 9cd84c0d..162d2271 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -908,7 +908,7 @@ class FloatField(WritableField): class FileField(WritableField): type_name = 'FileField' - + widget = widgets.FileInput default_error_messages = { 'invalid': _("No file was submitted. Check the encoding type on the form."), 'missing': _("No file was submitted."), diff --git a/rest_framework/renderers.py b/rest_framework/renderers.py index 22fd6e74..dab97346 100644 --- a/rest_framework/renderers.py +++ b/rest_framework/renderers.py @@ -320,7 +320,9 @@ class BrowsableAPIRenderer(BaseRenderer): serializers.SlugRelatedField: forms.ChoiceField, serializers.ManySlugRelatedField: forms.MultipleChoiceField, serializers.HyperlinkedRelatedField: forms.ChoiceField, - serializers.ManyHyperlinkedRelatedField: forms.MultipleChoiceField + serializers.ManyHyperlinkedRelatedField: forms.MultipleChoiceField, + serializers.FileField: forms.FileField, + serializers.ImageField: forms.ImageField, } fields = {} -- cgit v1.2.3 From 023b065ddc08735c487adff76cc62a864efe1697 Mon Sep 17 00:00:00 2001 From: Stephan Groß Date: Wed, 14 Nov 2012 16:02:50 +0100 Subject: added support for passing page_size per request --- rest_framework/mixins.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/rest_framework/mixins.py b/rest_framework/mixins.py index c3625a88..f725fc9e 100644 --- a/rest_framework/mixins.py +++ b/rest_framework/mixins.py @@ -55,6 +55,16 @@ class ListModelMixin(object): return Response(serializer.data) + def get_paginate_by(self, queryset): + page_size_param = self.request.QUERY_PARAMS.get('page_size') + if page_size_param: + try: + page_size = int(page_size_param) + return page_size + except ValueError: + pass + return super(ListModelMixin, self).get_paginate_by(queryset) + class RetrieveModelMixin(object): """ -- cgit v1.2.3 From 33fe0d2bea5ce391a69047236c121ec5b33d9b2f Mon Sep 17 00:00:00 2001 From: Stephan Groß Date: Wed, 14 Nov 2012 16:08:14 +0100 Subject: added release note --- docs/topics/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/topics/release-notes.md b/docs/topics/release-notes.md index 35e8a8b3..40ce4352 100644 --- a/docs/topics/release-notes.md +++ b/docs/topics/release-notes.md @@ -7,6 +7,7 @@ ## Master * Support for `read_only_fields` on `ModelSerializer` classes. +* Support for `page_size` GET parameter in view which inherit ListModelMixin ## 2.1.2 -- cgit v1.2.3 From 5967f15f7f5c87987ab60e4b7dc682b06f3ab511 Mon Sep 17 00:00:00 2001 From: Stephan Groß Date: Wed, 14 Nov 2012 16:11:35 +0100 Subject: updated docs --- docs/api-guide/generic-views.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/api-guide/generic-views.md b/docs/api-guide/generic-views.md index 360ef1a2..9e5119cb 100644 --- a/docs/api-guide/generic-views.md +++ b/docs/api-guide/generic-views.md @@ -147,6 +147,10 @@ Provides a `.list(request, *args, **kwargs)` method, that implements listing a q Should be mixed in with [MultipleObjectAPIView]. +**Arguments**: + +* `page_size` - Hook to adjust page_size per request. + ## CreateModelMixin Provides a `.create(request, *args, **kwargs)` method, that implements creating and saving a new model instance. -- cgit v1.2.3 From c35b9eb065b2a9cacaee1dc0849f01f3483e6130 Mon Sep 17 00:00:00 2001 From: Marko Tibold Date: Wed, 14 Nov 2012 21:13:23 +0100 Subject: Processed review comments. No type checking in .restore_fields() Added missing BytesIO import. --- rest_framework/fields.py | 22 ++++++++++++++++------ rest_framework/serializers.py | 5 +---- 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/rest_framework/fields.py b/rest_framework/fields.py index 162d2271..dce31c5a 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -3,6 +3,8 @@ import datetime import inspect import warnings +from io import BytesIO + from django.core import validators from django.core.exceptions import ObjectDoesNotExist, ValidationError from django.core.urlresolvers import resolve, get_script_prefix @@ -31,6 +33,7 @@ class Field(object): creation_counter = 0 empty = '' type_name = None + _use_files = None def __init__(self, source=None): self.parent = None @@ -51,7 +54,7 @@ class Field(object): self.root = parent.root or parent self.context = self.root.context - def field_from_native(self, data, field_name, into): + def field_from_native(self, data, files, field_name, into): """ Given a dictionary and a field name, updates the dictionary `into`, with the field and it's deserialized value. @@ -166,7 +169,7 @@ class WritableField(Field): if errors: raise ValidationError(errors) - def field_from_native(self, data, field_name, into): + def field_from_native(self, data, files, field_name, into): """ Given a dictionary and a field name, updates the dictionary `into`, with the field and it's deserialized value. @@ -175,7 +178,10 @@ class WritableField(Field): return try: - native = data[field_name] + if self._use_files: + native = files[field_name] + else: + native = data[field_name] except KeyError: if self.default is not None: native = self.default @@ -323,7 +329,7 @@ class RelatedField(WritableField): value = getattr(obj, self.source or field_name) return self.to_native(value) - def field_from_native(self, data, field_name, into): + def field_from_native(self, data, files, field_name, into): if self.read_only: return @@ -341,7 +347,7 @@ class ManyRelatedMixin(object): value = getattr(obj, self.source or field_name) return [self.to_native(item) for item in value.all()] - def field_from_native(self, data, field_name, into): + def field_from_native(self, data, files, field_name, into): if self.read_only: return @@ -907,8 +913,10 @@ class FloatField(WritableField): class FileField(WritableField): + _use_files = True type_name = 'FileField' widget = widgets.FileInput + default_error_messages = { 'invalid': _("No file was submitted. Check the encoding type on the form."), 'missing': _("No file was submitted."), @@ -951,6 +959,8 @@ class FileField(WritableField): class ImageField(FileField): + _use_files = True + default_error_messages = { 'invalid_image': _("Upload a valid image. The file you uploaded was either not an image or a corrupted image."), } @@ -990,7 +1000,7 @@ class ImageField(FileField): # _imaging C module isn't available, so an ImportError will be # raised. Catch and re-raise. raise - except Exception: # Python Imaging Library doesn't recognize it as an image + except Exception: # Python Imaging Library doesn't recognize it as an image raise ValidationError(self.error_messages['invalid_image']) if hasattr(f, 'seek') and callable(f.seek): f.seek(0) diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index a46432a9..4a13a091 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -198,10 +198,7 @@ class BaseSerializer(Field): reverted_data = {} for field_name, field in fields.items(): try: - if isinstance(field, (FileField, ImageField)): - field.field_from_native(files, field_name, reverted_data) - else: - field.field_from_native(data, field_name, reverted_data) + field.field_from_native(data, files, field_name, reverted_data) except ValidationError as err: self._errors[field_name] = list(err.messages) -- cgit v1.2.3 From e112a806d863c0d6662dc3a65909ac191c02f03e Mon Sep 17 00:00:00 2001 From: Marko Tibold Date: Wed, 14 Nov 2012 21:40:52 +0100 Subject: .to_native() now returns the file-name. --- rest_framework/fields.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/rest_framework/fields.py b/rest_framework/fields.py index dce31c5a..fd57aa2c 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -952,10 +952,7 @@ class FileField(WritableField): return data def to_native(self, value): - """ - No need to return anything, the file can be accessed form its url. - """ - return + return value.name class ImageField(FileField): -- cgit v1.2.3 From 69a01d71256b9923aac1b8d1b91063068ecfebf7 Mon Sep 17 00:00:00 2001 From: Marko Tibold Date: Wed, 14 Nov 2012 23:04:46 +0100 Subject: Added a test for the FileField. --- rest_framework/tests/files.py | 55 +++++++++++++++++++++++-------------------- 1 file changed, 30 insertions(+), 25 deletions(-) diff --git a/rest_framework/tests/files.py b/rest_framework/tests/files.py index 61d7f7b1..5dd57b7c 100644 --- a/rest_framework/tests/files.py +++ b/rest_framework/tests/files.py @@ -1,34 +1,39 @@ -# from django.test import TestCase -# from django import forms +import StringIO +import datetime -# from django.test.client import RequestFactory -# from rest_framework.views import View -# from rest_framework.response import Response +from django.test import TestCase -# import StringIO +from rest_framework import serializers -# class UploadFilesTests(TestCase): -# """Check uploading of files""" -# def setUp(self): -# self.factory = RequestFactory() +class UploadedFile(object): + def __init__(self, file, created=None): + self.file = file + self.created = created or datetime.datetime.now() -# def test_upload_file(self): -# class FileForm(forms.Form): -# file = forms.FileField() +class UploadedFileSerializer(serializers.Serializer): + file = serializers.FileField() + created = serializers.DateTimeField() -# class MockView(View): -# permissions = () -# form = FileForm + def restore_object(self, attrs, instance=None): + if instance: + instance.file = attrs['file'] + instance.created = attrs['created'] + return instance + return UploadedFile(**attrs) -# def post(self, request, *args, **kwargs): -# return Response({'FILE_NAME': self.CONTENT['file'].name, -# 'FILE_CONTENT': self.CONTENT['file'].read()}) -# file = StringIO.StringIO('stuff') -# file.name = 'stuff.txt' -# request = self.factory.post('/', {'file': file}) -# view = MockView.as_view() -# response = view(request) -# self.assertEquals(response.raw_content, {"FILE_CONTENT": "stuff", "FILE_NAME": "stuff.txt"}) +class FileSerializerTests(TestCase): + + def test_create(self): + now = datetime.datetime.now() + file = StringIO.StringIO('stuff') + file.name = 'stuff.txt' + file.size = file.len + serializer = UploadedFileSerializer(data={'created': now}, files={'file': file}) + uploaded_file = UploadedFile(file=file, created=now) + self.assertTrue(serializer.is_valid()) + self.assertEquals(serializer.object.created, uploaded_file.created) + self.assertEquals(serializer.object.file, uploaded_file.file) + self.assertFalse(serializer.object is uploaded_file) -- cgit v1.2.3 From 3b258d69c92e9d9293f7c5d1690f0ca434e677e3 Mon Sep 17 00:00:00 2001 From: Marko Tibold Date: Wed, 14 Nov 2012 23:24:18 +0100 Subject: Fix 404 Fixes #417--- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 644df873..c3ffc9a7 100644 --- a/README.md +++ b/README.md @@ -139,7 +139,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. [twitter]: https://twitter.com/_tomchristie [0.4]: https://github.com/tomchristie/django-rest-framework/tree/0.4.X [sandbox]: http://restframework.herokuapp.com/ -[rest-framework-2-announcement]: topics/rest-framework-2-announcement.md +[rest-framework-2-announcement]: http://django-rest-framework.org/topics/rest-framework-2-announcement.html [2.1.0-notes]: https://groups.google.com/d/topic/django-rest-framework/Vv2M0CMY9bg/discussion [docs]: http://django-rest-framework.org/ -- cgit v1.2.3 From 38e94bb8b4e04249b18b9b57ef2ddcb7cfc4efa4 Mon Sep 17 00:00:00 2001 From: Stephan Groß Date: Thu, 15 Nov 2012 11:15:05 +0100 Subject: added global and per resource on/off switch + updated docs --- docs/api-guide/generic-views.md | 3 ++- docs/api-guide/settings.md | 6 ++++++ rest_framework/mixins.py | 18 +++++++++++------- rest_framework/settings.py | 4 +++- 4 files changed, 22 insertions(+), 9 deletions(-) diff --git a/docs/api-guide/generic-views.md b/docs/api-guide/generic-views.md index 9e5119cb..734a91e9 100644 --- a/docs/api-guide/generic-views.md +++ b/docs/api-guide/generic-views.md @@ -149,7 +149,8 @@ Should be mixed in with [MultipleObjectAPIView]. **Arguments**: -* `page_size` - Hook to adjust page_size per request. +* `allow_page_size_param` - Allows you to overwrite the global settings `ALLOW_PAGE_SIZE_PARAM` for a specific view. +* `page_size_param` - Allows you to customize the page_size parameter. Default is `page_size`. ## CreateModelMixin diff --git a/docs/api-guide/settings.md b/docs/api-guide/settings.md index 4f87b30d..2f90369b 100644 --- a/docs/api-guide/settings.md +++ b/docs/api-guide/settings.md @@ -150,4 +150,10 @@ Default: `'accept'` Default: `'format'` +## ALLOW_PAGE_SIZE_PARAM + +Allows you to globally pass a page size parameter for an individual request. + +Default: `'True'` + [cite]: http://www.python.org/dev/peps/pep-0020/ diff --git a/rest_framework/mixins.py b/rest_framework/mixins.py index f725fc9e..d64e7e56 100644 --- a/rest_framework/mixins.py +++ b/rest_framework/mixins.py @@ -7,6 +7,7 @@ which allows mixin classes to be composed in interesting ways. from django.http import Http404 from rest_framework import status from rest_framework.response import Response +from rest_framework.settings import api_settings class CreateModelMixin(object): @@ -32,6 +33,8 @@ class ListModelMixin(object): Should be mixed in with `MultipleObjectAPIView`. """ empty_error = u"Empty list and '%(class_name)s.allow_empty' is False." + allow_page_size_param = api_settings.ALLOW_PAGE_SIZE_PARAM + page_size_param = 'page_size' def list(self, request, *args, **kwargs): self.object_list = self.get_filtered_queryset() @@ -56,13 +59,14 @@ class ListModelMixin(object): return Response(serializer.data) def get_paginate_by(self, queryset): - page_size_param = self.request.QUERY_PARAMS.get('page_size') - if page_size_param: - try: - page_size = int(page_size_param) - return page_size - except ValueError: - pass + if self.allow_page_size_param: + page_size_param = self.request.QUERY_PARAMS.get(self.page_size_param) + if page_size_param: + try: + page_size = int(page_size_param) + return page_size + except ValueError: + pass return super(ListModelMixin, self).get_paginate_by(queryset) diff --git a/rest_framework/settings.py b/rest_framework/settings.py index 906a7cf6..1daa9dfd 100644 --- a/rest_framework/settings.py +++ b/rest_framework/settings.py @@ -66,7 +66,9 @@ DEFAULTS = { 'URL_ACCEPT_OVERRIDE': 'accept', 'URL_FORMAT_OVERRIDE': 'format', - 'FORMAT_SUFFIX_KWARG': 'format' + 'FORMAT_SUFFIX_KWARG': 'format', + + 'ALLOW_PAGE_SIZE_PARAM': True } -- cgit v1.2.3 From b17a9818008cf3828adb896ae9be134fb63c5693 Mon Sep 17 00:00:00 2001 From: Stephan Groß Date: Thu, 15 Nov 2012 11:24:17 +0100 Subject: updated release noted for page_size stuff --- docs/topics/release-notes.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/topics/release-notes.md b/docs/topics/release-notes.md index 40ce4352..f4a76c89 100644 --- a/docs/topics/release-notes.md +++ b/docs/topics/release-notes.md @@ -7,7 +7,9 @@ ## Master * Support for `read_only_fields` on `ModelSerializer` classes. -* Support for `page_size` GET parameter in view which inherit ListModelMixin +* Support for `page_size` GET parameter in views which inherit ListModelMixin. +* Support for customizing `page_size` param via `page_size_param` attribute. +* Support for allowing `page_size` param globally (via `ALLOW_PAGE_SIZE_PARAM`) and for individual views (via `allow_page_size_param`) ## 2.1.2 -- cgit v1.2.3 From 3ae203a0184d27318a8a828ce322b151ade0340f Mon Sep 17 00:00:00 2001 From: Stephan Groß Date: Thu, 15 Nov 2012 12:06:43 +0100 Subject: updated script to just use page_size_kwarg --- docs/api-guide/generic-views.md | 3 +-- docs/api-guide/settings.md | 8 ++++++-- docs/topics/release-notes.md | 4 +--- rest_framework/mixins.py | 11 +++++------ rest_framework/settings.py | 2 +- 5 files changed, 14 insertions(+), 14 deletions(-) diff --git a/docs/api-guide/generic-views.md b/docs/api-guide/generic-views.md index 734a91e9..3346c70a 100644 --- a/docs/api-guide/generic-views.md +++ b/docs/api-guide/generic-views.md @@ -149,8 +149,7 @@ Should be mixed in with [MultipleObjectAPIView]. **Arguments**: -* `allow_page_size_param` - Allows you to overwrite the global settings `ALLOW_PAGE_SIZE_PARAM` for a specific view. -* `page_size_param` - Allows you to customize the page_size parameter. Default is `page_size`. +* `page_size_kwarg` - Allows you to overwrite the global settings `PAGE_SIZE_KWARG` for a specific view. You can also turn it off for a specific view by setting it to `None`. Default is `page_size`. ## CreateModelMixin diff --git a/docs/api-guide/settings.md b/docs/api-guide/settings.md index 2f90369b..8fce9e4e 100644 --- a/docs/api-guide/settings.md +++ b/docs/api-guide/settings.md @@ -150,10 +150,14 @@ Default: `'accept'` Default: `'format'` -## ALLOW_PAGE_SIZE_PARAM +## PAGE_SIZE_KWARG Allows you to globally pass a page size parameter for an individual request. -Default: `'True'` +The name of the GET parameter of views which inherit ListModelMixin for requesting data with an individual page size. + +If the value if this setting is `None` the passing a page size is turned off by default. + +Default: `'page_size'` [cite]: http://www.python.org/dev/peps/pep-0020/ diff --git a/docs/topics/release-notes.md b/docs/topics/release-notes.md index f4a76c89..85c19f5b 100644 --- a/docs/topics/release-notes.md +++ b/docs/topics/release-notes.md @@ -7,9 +7,7 @@ ## Master * Support for `read_only_fields` on `ModelSerializer` classes. -* Support for `page_size` GET parameter in views which inherit ListModelMixin. -* Support for customizing `page_size` param via `page_size_param` attribute. -* Support for allowing `page_size` param globally (via `ALLOW_PAGE_SIZE_PARAM`) and for individual views (via `allow_page_size_param`) +* Support for individual page sizes per request via `page_size` GET parameter in views which inherit ListModelMixin. ## 2.1.2 diff --git a/rest_framework/mixins.py b/rest_framework/mixins.py index d64e7e56..d85e0bfb 100644 --- a/rest_framework/mixins.py +++ b/rest_framework/mixins.py @@ -33,8 +33,7 @@ class ListModelMixin(object): Should be mixed in with `MultipleObjectAPIView`. """ empty_error = u"Empty list and '%(class_name)s.allow_empty' is False." - allow_page_size_param = api_settings.ALLOW_PAGE_SIZE_PARAM - page_size_param = 'page_size' + page_size_kwarg = api_settings.PAGE_SIZE_KWARG def list(self, request, *args, **kwargs): self.object_list = self.get_filtered_queryset() @@ -59,11 +58,11 @@ class ListModelMixin(object): return Response(serializer.data) def get_paginate_by(self, queryset): - if self.allow_page_size_param: - page_size_param = self.request.QUERY_PARAMS.get(self.page_size_param) - if page_size_param: + if self.page_size_kwarg is not None: + page_size_kwarg = self.request.QUERY_PARAMS.get(self.page_size_kwarg) + if page_size_kwarg: try: - page_size = int(page_size_param) + page_size = int(page_size_kwarg) return page_size except ValueError: pass diff --git a/rest_framework/settings.py b/rest_framework/settings.py index 1daa9dfd..c7b0643f 100644 --- a/rest_framework/settings.py +++ b/rest_framework/settings.py @@ -68,7 +68,7 @@ DEFAULTS = { 'FORMAT_SUFFIX_KWARG': 'format', - 'ALLOW_PAGE_SIZE_PARAM': True + 'PAGE_SIZE_KWARG': 'page_size' } -- cgit v1.2.3 From a701a21587a69ed959533cbcfdaa9c63337c3ccc Mon Sep 17 00:00:00 2001 From: Stephan Groß Date: Thu, 15 Nov 2012 14:35:34 +0100 Subject: added page_size_kwarg tests --- rest_framework/tests/pagination.py | 143 ++++++++++++++++++++++++++++++++++++- 1 file changed, 142 insertions(+), 1 deletion(-) diff --git a/rest_framework/tests/pagination.py b/rest_framework/tests/pagination.py index 713a7255..8aae2147 100644 --- a/rest_framework/tests/pagination.py +++ b/rest_framework/tests/pagination.py @@ -34,6 +34,29 @@ if django_filters: filter_backend = filters.DjangoFilterBackend +class DefaultPageSizeKwargView(generics.ListAPIView): + """ + View for testing default page_size usage + """ + model = BasicModel + + +class CustomPageSizeKwargView(generics.ListAPIView): + """ + View for testing custom page_size usage + """ + model = BasicModel + page_size_kwarg = 'ps' + + +class NonePageSizeKwargView(generics.ListAPIView): + """ + View for testing None page_size usage + """ + model = BasicModel + page_size_kwarg = None + + class IntegrationTestPagination(TestCase): """ Integration tests for paginated list views. @@ -135,7 +158,7 @@ class IntegrationTestPaginationAndFiltering(TestCase): class UnitTestPagination(TestCase): """ - Unit tests for pagination of primative objects. + Unit tests for pagination of primitive objects. """ def setUp(self): @@ -156,3 +179,121 @@ class UnitTestPagination(TestCase): self.assertEquals(serializer.data['next'], None) self.assertEquals(serializer.data['previous'], '?page=2') self.assertEquals(serializer.data['results'], self.objects[20:]) + + +class TestDefaultPageSizeKwarg(TestCase): + """ + Tests for list views with default page size kwarg + """ + + def setUp(self): + """ + Create 13 BasicModel instances. + """ + for i in range(13): + BasicModel(text=i).save() + self.objects = BasicModel.objects + self.data = [ + {'id': obj.id, 'text': obj.text} + for obj in self.objects.all() + ] + self.view = DefaultPageSizeKwargView.as_view() + + def test_default_page_size(self): + """ + Tests the default page size for this view. + no page size --> no limit --> no meta data + """ + request = factory.get('/') + response = self.view(request).render() + self.assertEquals(response.data, self.data) + + def test_default_page_size_kwarg(self): + """ + If page_size_kwarg is set not set, the default page_size kwarg should limit per view requests. + """ + request = factory.get('/?page_size=5') + response = self.view(request).render() + self.assertEquals(response.data['count'], 13) + self.assertEquals(response.data['results'], self.data[:5]) + + +class TestCustomPageSizeKwarg(TestCase): + """ + Tests for list views with default page size kwarg + """ + + def setUp(self): + """ + Create 13 BasicModel instances. + """ + for i in range(13): + BasicModel(text=i).save() + self.objects = BasicModel.objects + self.data = [ + {'id': obj.id, 'text': obj.text} + for obj in self.objects.all() + ] + self.view = CustomPageSizeKwargView.as_view() + + def test_default_page_size(self): + """ + Tests the default page size for this view. + no page size --> no limit --> no meta data + """ + request = factory.get('/') + response = self.view(request).render() + self.assertEquals(response.data, self.data) + + def test_disabled_default_page_size_kwarg(self): + """ + If page_size_kwarg is set set, the default page_size kwarg should not work. + """ + request = factory.get('/?page_size=5') + response = self.view(request).render() + self.assertEquals(response.data, self.data) + + def test_custom_page_size_kwarg(self): + """ + If page_size_kwarg is set set, the new kwarg should limit per view requests. + """ + request = factory.get('/?ps=5') + response = self.view(request).render() + self.assertEquals(response.data['count'], 13) + self.assertEquals(response.data['results'], self.data[:5]) + + +class TestNonePageSizeKwarg(TestCase): + """ + Tests for list views with default page size kwarg + """ + + def setUp(self): + """ + Create 13 BasicModel instances. + """ + for i in range(13): + BasicModel(text=i).save() + self.objects = BasicModel.objects + self.data = [ + {'id': obj.id, 'text': obj.text} + for obj in self.objects.all() + ] + self.view = NonePageSizeKwargView.as_view() + + def test_default_page_size(self): + """ + Tests the default page size for this view. + no page size --> no limit --> no meta data + """ + request = factory.get('/') + response = self.view(request).render() + self.assertEquals(response.data, self.data) + + def test_none_page_size_kwarg(self): + """ + If page_size_kwarg is set to None, custom page_size per request should be disabled. + """ + request = factory.get('/?page_size=5') + response = self.view(request).render() + self.assertEquals(response.data, self.data) -- cgit v1.2.3 From cba181f4bce6684a45aa869cae0b2cca0c35eee0 Mon Sep 17 00:00:00 2001 From: Marko Tibold Date: Thu, 15 Nov 2012 23:31:53 +0100 Subject: ./mkdocs.py -p opens a preview in your default browser. Tested on Mac, but should work on windows and Linux as well. --- mkdocs.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/mkdocs.py b/mkdocs.py index 8106e8e2..2918f7d3 100755 --- a/mkdocs.py +++ b/mkdocs.py @@ -11,6 +11,7 @@ docs_dir = os.path.join(root_dir, 'docs') html_dir = os.path.join(root_dir, 'html') local = not '--deploy' in sys.argv +preview = '-p' in sys.argv if local: base_url = 'file://%s/' % os.path.normpath(os.path.join(os.getcwd(), html_dir)) @@ -80,3 +81,15 @@ for (dirpath, dirnames, filenames) in os.walk(docs_dir): output = re.sub(r'
', r'', output)
output = re.sub(r'', code_label, output)
open(output_path, 'w').write(output.encode('utf-8'))
+
+if preview:
+ import subprocess
+
+ url = 'html/index.html'
+
+ try:
+ subprocess.Popen(["open", url]) # Mac
+ except OSError:
+ subprocess.Popen(["xdg-open", url]) # Linux
+ except:
+ os.startfile(url) # Windows
--
cgit v1.2.3
From b4cfb46a56c8f7d9bc4340d5443f3a2d57ba9b58 Mon Sep 17 00:00:00 2001
From: Marko Tibold
Date: Fri, 16 Nov 2012 00:22:08 +0100
Subject: WIP on docs for File- and ImageFileds.
---
docs/api-guide/fields.md | 32 ++++++++++++++++++++++++++++++++
1 file changed, 32 insertions(+)
diff --git a/docs/api-guide/fields.md b/docs/api-guide/fields.md
index 0485b158..0b25a6ef 100644
--- a/docs/api-guide/fields.md
+++ b/docs/api-guide/fields.md
@@ -165,6 +165,38 @@ A floating point representation.
Corresponds to `django.db.models.fields.FloatField`.
+## FileField
+
+A file representation. Performs Django's standard FileField validation.
+
+Corresponds to `django.forms.fields.FileField`.
+
+### Optional arguments
+
+#### `max_length`
+
+Maximum length for the file name. This value is obtained from the model when used with a ModelSerializer.
+
+Defaults to `None`, meaning validation is skipped.
+
+#### `allow_empty_file`
+
+Determines if empty file uploads are allowed.
+
+Defaults to `False`
+
+## ImageField
+
+An image representation.
+
+Corresponds to `django.forms.fields.ImageField`.
+
+### Optional arguments
+
+Same as FileField.
+
+Requires the `PIL` package.
+
---
# Relational Fields
--
cgit v1.2.3
From c5765641a44ad2fb3b80f63f9a47e0dd7f432c94 Mon Sep 17 00:00:00 2001
From: Tom Christie
Date: Fri, 16 Nov 2012 17:28:08 +0000
Subject: Fix typo
---
docs/api-guide/filtering.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/api-guide/filtering.md b/docs/api-guide/filtering.md
index 14ab9a26..95d9d526 100644
--- a/docs/api-guide/filtering.md
+++ b/docs/api-guide/filtering.md
@@ -71,7 +71,7 @@ We can override `.get_queryset()` to deal with URLs such as `http://example.com/
by filtering against a `username` query parameter in the URL.
"""
queryset = Purchase.objects.all()
- username = self.request.QUERY_PARAMS.get('username', None):
+ username = self.request.QUERY_PARAMS.get('username', None)
if username is not None:
queryset = queryset.filter(purchaser__username=username)
return queryset
--
cgit v1.2.3
From 4edc801d5912b2c31855647b432e461e35322511 Mon Sep 17 00:00:00 2001
From: Marko Tibold
Date: Fri, 16 Nov 2012 21:42:04 +0100
Subject: Reproduces #421
---
rest_framework/tests/serializer.py | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/rest_framework/tests/serializer.py b/rest_framework/tests/serializer.py
index 059593a9..a51df146 100644
--- a/rest_framework/tests/serializer.py
+++ b/rest_framework/tests/serializer.py
@@ -239,6 +239,14 @@ class ValidationTests(TestCase):
self.assertEquals(serializer.is_valid(), True)
self.assertEquals(serializer.errors, {})
+ def test_modelserializer_max_length_exceeded(self):
+ data = {
+ 'title': 'x' * 201,
+ }
+ serializer = ActionItemSerializer(data=data)
+ self.assertEquals(serializer.is_valid(), False)
+ self.assertEquals(serializer.errors, {'content': [u'Ensure this value has at most 200 characters (it has 201).']})
+
class MetadataTests(TestCase):
def test_empty(self):
--
cgit v1.2.3
From 2f2bde69e42825ad55318e5a5745ee9655b3f81b Mon Sep 17 00:00:00 2001
From: Tom Christie
Date: Fri, 16 Nov 2012 20:58:49 +0000
Subject: Docs, tox and travis use django-filter 0.5.4
---
.travis.yml | 3 +--
docs/api-guide/filtering.md | 5 ++---
docs/index.md | 4 ++--
tox.ini | 12 ++++++------
4 files changed, 11 insertions(+), 13 deletions(-)
diff --git a/.travis.yml b/.travis.yml
index 800ba241..ccfdeacb 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -11,8 +11,7 @@ env:
install:
- pip install $DJANGO
- - pip install -r requirements.txt --use-mirrors
- - pip install -e git+https://github.com/alex/django-filter.git@0e4b3d703b31574922ab86fc78a86164aad0c1d0#egg=django-filter
+ - pip install django-filter==0.5.4 --use-mirrors
- export PYTHONPATH=.
script:
diff --git a/docs/api-guide/filtering.md b/docs/api-guide/filtering.md
index 95d9d526..53ea7cbc 100644
--- a/docs/api-guide/filtering.md
+++ b/docs/api-guide/filtering.md
@@ -84,9 +84,9 @@ As well as being able to override the default queryset, REST framework also incl
REST framework supports pluggable backends to implement filtering, and provides an implementation which uses the [django-filter] package.
-To use REST framework's default filtering backend, first install `django-filter`.
+To use REST framework's filtering backend, first install `django-filter`.
- pip install -e git+https://github.com/alex/django-filter.git#egg=django-filter
+ pip install django-filter
You must also set the filter backend to `DjangoFilterBackend` in your settings:
@@ -94,7 +94,6 @@ You must also set the filter backend to `DjangoFilterBackend` in your settings:
'FILTER_BACKEND': 'rest_framework.filters.DjangoFilterBackend'
}
-**Note**: The currently supported version of `django-filter` is the `master` branch. A PyPI release is expected to be coming soon.
## Specifying filter fields
diff --git a/docs/index.md b/docs/index.md
index fd834540..cc0f2a13 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -34,7 +34,7 @@ The following packages are optional:
* [Markdown][markdown] (2.1.0+) - Markdown support for the browseable API.
* [PyYAML][yaml] (3.10+) - YAML content-type support.
-* [django-filter][django-filter] (master) - Filtering support.
+* [django-filter][django-filter] (0.5.4+) - Filtering support.
## Installation
@@ -43,7 +43,7 @@ Install using `pip`, including any optional packages you want...
pip install djangorestframework
pip install markdown # Markdown support for the browseable API.
pip install pyyaml # YAML content-type support.
- pip install -e git+https://github.com/alex/django-filter.git#egg=django-filter # Filtering support
+ pip install django-filter # Filtering support
...or clone the project from github.
diff --git a/tox.ini b/tox.ini
index 3596bbdc..69eb3823 100644
--- a/tox.ini
+++ b/tox.ini
@@ -8,29 +8,29 @@ commands = {envpython} rest_framework/runtests/runtests.py
[testenv:py2.7-django1.5]
basepython = python2.7
deps = https://github.com/django/django/zipball/master
- git+https://github.com/alex/django-filter.git@0e4b3d703b31574922ab86fc78a86164aad0c1d0#egg=django-filter
+ django-filter==0.5.4
[testenv:py2.7-django1.4]
basepython = python2.7
deps = django==1.4.1
- git+https://github.com/alex/django-filter.git@0e4b3d703b31574922ab86fc78a86164aad0c1d0#egg=django-filter
+ django-filter==0.5.4
[testenv:py2.7-django1.3]
basepython = python2.7
deps = django==1.3.3
- git+https://github.com/alex/django-filter.git@0e4b3d703b31574922ab86fc78a86164aad0c1d0#egg=django-filter
+ django-filter==0.5.4
[testenv:py2.6-django1.5]
basepython = python2.6
deps = https://github.com/django/django/zipball/master
- git+https://github.com/alex/django-filter.git@0e4b3d703b31574922ab86fc78a86164aad0c1d0#egg=django-filter
+ django-filter==0.5.4
[testenv:py2.6-django1.4]
basepython = python2.6
deps = django==1.4.1
- git+https://github.com/alex/django-filter.git@0e4b3d703b31574922ab86fc78a86164aad0c1d0#egg=django-filter
+ django-filter==0.5.4
[testenv:py2.6-django1.3]
basepython = python2.6
deps = django==1.3.3
- git+https://github.com/alex/django-filter.git@0e4b3d703b31574922ab86fc78a86164aad0c1d0#egg=django-filter
+ django-filter==0.5.4
--
cgit v1.2.3
From aa013a428948802dff9c8ca00df3b7af6faf139b Mon Sep 17 00:00:00 2001
From: Marko Tibold
Date: Fri, 16 Nov 2012 22:18:57 +0100
Subject: Fixes #421
---
rest_framework/serializers.py | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py
index 0f943ac1..e5c057fb 100644
--- a/rest_framework/serializers.py
+++ b/rest_framework/serializers.py
@@ -427,6 +427,12 @@ class ModelSerializer(Serializer):
kwargs['choices'] = model_field.flatchoices
return ChoiceField(**kwargs)
+ max_length = getattr(model_field, 'max_length', None)
+ if max_length:
+ if not isinstance(model_field, models.CharField):
+ import pdb; pdb.set_trace()
+ kwargs['max_length'] = max_length
+
field_mapping = {
models.FloatField: FloatField,
models.IntegerField: IntegerField,
--
cgit v1.2.3
From f385b72d80b7e9767a6f345496fd108ccc66a4bc Mon Sep 17 00:00:00 2001
From: Marko Tibold
Date: Fri, 16 Nov 2012 22:20:26 +0100
Subject: Whoops … Drop pdb
---
rest_framework/serializers.py | 2 --
1 file changed, 2 deletions(-)
diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py
index e5c057fb..8f4b7ae2 100644
--- a/rest_framework/serializers.py
+++ b/rest_framework/serializers.py
@@ -429,8 +429,6 @@ class ModelSerializer(Serializer):
max_length = getattr(model_field, 'max_length', None)
if max_length:
- if not isinstance(model_field, models.CharField):
- import pdb; pdb.set_trace()
kwargs['max_length'] = max_length
field_mapping = {
--
cgit v1.2.3
From 8d3581f4bd9b0abbf88a7713a1cb8b67f820602a Mon Sep 17 00:00:00 2001
From: Tom Christie
Date: Fri, 16 Nov 2012 21:27:34 +0000
Subject: Minor tweaks to internals of generics and mixins
---
rest_framework/generics.py | 3 ---
rest_framework/mixins.py | 27 +++++++++++++++------------
2 files changed, 15 insertions(+), 15 deletions(-)
diff --git a/rest_framework/generics.py b/rest_framework/generics.py
index ddb604e0..9ad03f71 100644
--- a/rest_framework/generics.py
+++ b/rest_framework/generics.py
@@ -66,9 +66,6 @@ class MultipleObjectAPIView(MultipleObjectMixin, GenericAPIView):
backend = self.filter_backend()
return backend.filter_queryset(self.request, queryset, self)
- def get_filtered_queryset(self):
- return self.filter_queryset(self.get_queryset())
-
def get_pagination_serializer_class(self):
"""
Return the class to use for the pagination serializer.
diff --git a/rest_framework/mixins.py b/rest_framework/mixins.py
index cd104a7c..53c4d984 100644
--- a/rest_framework/mixins.py
+++ b/rest_framework/mixins.py
@@ -22,13 +22,13 @@ class CreateModelMixin(object):
headers = self.get_success_headers(serializer.data)
return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
-
+
def get_success_headers(self, data):
- if 'url' in data:
- return {'Location': data.get('url')}
- else:
+ try:
+ return {'Location': data['url']}
+ except (TypeError, KeyError):
return {}
-
+
def pre_save(self, obj):
pass
@@ -41,14 +41,16 @@ class ListModelMixin(object):
empty_error = u"Empty list and '%(class_name)s.allow_empty' is False."
def list(self, request, *args, **kwargs):
- self.object_list = self.get_filtered_queryset()
+ queryset = self.get_queryset()
+ self.object_list = self.filter_queryset(queryset)
# Default is to allow empty querysets. This can be altered by setting
# `.allow_empty = False`, to raise 404 errors on empty querysets.
allow_empty = self.get_allow_empty()
- if not allow_empty and len(self.object_list) == 0:
- error_args = {'class_name': self.__class__.__name__}
- raise Http404(self.empty_error % error_args)
+ if not allow_empty and not self.object_list:
+ class_name = self.__class__.__name__
+ error_msg = self.empty_error % {'class_name': class_name}
+ raise Http404(error_msg)
# Pagination size is set by the `.paginate_by` attribute,
# which may be `None` to disable pagination.
@@ -82,17 +84,18 @@ class UpdateModelMixin(object):
def update(self, request, *args, **kwargs):
try:
self.object = self.get_object()
- success_status = status.HTTP_200_OK
+ created = False
except Http404:
self.object = None
- success_status = status.HTTP_201_CREATED
+ created = True
serializer = self.get_serializer(self.object, data=request.DATA)
if serializer.is_valid():
self.pre_save(serializer.object)
self.object = serializer.save()
- return Response(serializer.data, status=success_status)
+ status_code = created and status.HTTP_201_CREATED or status.HTTP_200_OK
+ return Response(serializer.data, status=status_code)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
--
cgit v1.2.3
From 1a436dd6d9f56b62de61c55c89084d60c09966ba Mon Sep 17 00:00:00 2001
From: Marko Tibold
Date: Fri, 16 Nov 2012 22:43:16 +0100
Subject: Added URLField and SlugField. Fixed
test_modelserializer_max_length_exceeded
---
rest_framework/fields.py | 17 +++++++++++++++++
rest_framework/serializers.py | 2 ++
rest_framework/tests/serializer.py | 2 +-
3 files changed, 20 insertions(+), 1 deletion(-)
diff --git a/rest_framework/fields.py b/rest_framework/fields.py
index 6ef53975..641a1417 100644
--- a/rest_framework/fields.py
+++ b/rest_framework/fields.py
@@ -700,6 +700,23 @@ class CharField(WritableField):
return smart_unicode(value)
+class URLField(CharField):
+ type_name = 'URLField'
+
+ def __init__(self, **kwargs):
+ kwargs['max_length'] = kwargs.get('max_length', 200)
+ kwargs['validators'] = [validators.URLValidator()]
+ super(URLField, self).__init__(**kwargs)
+
+
+class SlugField(CharField):
+ type_name = 'SlugField'
+
+ def __init__(self, *args, **kwargs):
+ kwargs['max_length'] = kwargs.get('max_length', 50)
+ super(SlugField, self).__init__(*args, **kwargs)
+
+
class ChoiceField(WritableField):
type_name = 'ChoiceField'
widget = widgets.Select
diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py
index 8f4b7ae2..dbd9fe27 100644
--- a/rest_framework/serializers.py
+++ b/rest_framework/serializers.py
@@ -441,6 +441,8 @@ class ModelSerializer(Serializer):
models.DateField: DateField,
models.EmailField: EmailField,
models.CharField: CharField,
+ models.URLField: URLField,
+ models.SlugField: SlugField,
models.TextField: CharField,
models.CommaSeparatedIntegerField: CharField,
models.BooleanField: BooleanField,
diff --git a/rest_framework/tests/serializer.py b/rest_framework/tests/serializer.py
index a51df146..fb1be7eb 100644
--- a/rest_framework/tests/serializer.py
+++ b/rest_framework/tests/serializer.py
@@ -245,7 +245,7 @@ class ValidationTests(TestCase):
}
serializer = ActionItemSerializer(data=data)
self.assertEquals(serializer.is_valid(), False)
- self.assertEquals(serializer.errors, {'content': [u'Ensure this value has at most 200 characters (it has 201).']})
+ self.assertEquals(serializer.errors, {'title': [u'Ensure this value has at most 200 characters (it has 201).']})
class MetadataTests(TestCase):
--
cgit v1.2.3
From 0076e2f462402dbb7bd7b3a446d2c397e6bf8d81 Mon Sep 17 00:00:00 2001
From: Marko Tibold
Date: Fri, 16 Nov 2012 23:23:34 +0100
Subject: Added brief docs for URLField and SlugField.
---
docs/api-guide/fields.md | 12 ++++++++++++
1 file changed, 12 insertions(+)
diff --git a/docs/api-guide/fields.md b/docs/api-guide/fields.md
index 0485b158..5977cae2 100644
--- a/docs/api-guide/fields.md
+++ b/docs/api-guide/fields.md
@@ -131,6 +131,18 @@ or `django.db.models.fields.TextField`.
**Signature:** `CharField(max_length=None, min_length=None)`
+## URLField
+
+Corresponds to `django.db.models.fields.URLField`. Uses Django's `django.core.validators.URLValidator` for validation.
+
+**Signature:** `CharField(max_length=200, min_length=None)`
+
+## SlugField
+
+Corresponds to `django.db.models.fields.SlugField`.
+
+**Signature:** `CharField(max_length=50, min_length=None)`
+
## ChoiceField
A field that can accept a value out of a limited set of choices.
--
cgit v1.2.3
From f801e5d3050591403de04fde7d18522fabc8fe49 Mon Sep 17 00:00:00 2001
From: Marko Tibold
Date: Fri, 16 Nov 2012 23:44:55 +0100
Subject: Simplified docs a bit for FileField and ImageField. Added note about
MultipartParser only supporting file uploads and Django's default upload
handlers.
---
docs/api-guide/fields.md | 26 +++++++++++---------------
1 file changed, 11 insertions(+), 15 deletions(-)
diff --git a/docs/api-guide/fields.md b/docs/api-guide/fields.md
index 0b25a6ef..7f42dc5e 100644
--- a/docs/api-guide/fields.md
+++ b/docs/api-guide/fields.md
@@ -171,19 +171,11 @@ A file representation. Performs Django's standard FileField validation.
Corresponds to `django.forms.fields.FileField`.
-### Optional arguments
+**Signature:** `FileField(max_length=None, allow_empty_file=False)`
-#### `max_length`
-
-Maximum length for the file name. This value is obtained from the model when used with a ModelSerializer.
-
-Defaults to `None`, meaning validation is skipped.
-
-#### `allow_empty_file`
-
-Determines if empty file uploads are allowed.
-
-Defaults to `False`
+ - `max_length` designates the maximum length for the file name.
+
+ - `allow_empty_file` designates if empty files are allowed.
## ImageField
@@ -191,11 +183,14 @@ An image representation.
Corresponds to `django.forms.fields.ImageField`.
-### Optional arguments
+Requires the `PIL` package.
-Same as FileField.
+Signature and validation is the same as with `FileField`.
-Requires the `PIL` package.
+---
+
+**Note:** `FileFields` and `ImageFields` are only suitable for use with MultiPartParser, since eg json doesn't support file uploads.
+Django's regular [FILE_UPLOAD_HANDLERS] are used for handling uploaded files.
---
@@ -318,3 +313,4 @@ This field is always read-only.
* `slug_url_kwarg` - The named url parameter for the slug field lookup. Default is to use the same value as given for `slug_field`.
[cite]: http://www.python.org/dev/peps/pep-0020/
+[FILE_UPLOAD_HANDLERS]: https://docs.djangoproject.com/en/dev/ref/settings/#std:setting-FILE_UPLOAD_HANDLERS
--
cgit v1.2.3
From 31f01bd6315f46bf28bb4c9c25a5298785fc4fc6 Mon Sep 17 00:00:00 2001
From: Tom Christie
Date: Fri, 16 Nov 2012 22:45:57 +0000
Subject: Polishing to page size query parameters & more docs
---
docs/api-guide/generic-views.md | 22 ++++++++--
docs/api-guide/settings.md | 26 +++++++-----
docs/topics/release-notes.md | 3 +-
rest_framework/generics.py | 32 ++++++++++----
rest_framework/mixins.py | 13 ------
rest_framework/settings.py | 9 +++-
rest_framework/tests/pagination.py | 85 ++++++--------------------------------
7 files changed, 79 insertions(+), 111 deletions(-)
diff --git a/docs/api-guide/generic-views.md b/docs/api-guide/generic-views.md
index 3346c70a..33ec89d2 100644
--- a/docs/api-guide/generic-views.md
+++ b/docs/api-guide/generic-views.md
@@ -123,18 +123,36 @@ Each of the generic views provided is built by combining one of the base views b
Extends REST framework's `APIView` class, adding support for serialization of model instances and model querysets.
+**Attributes**:
+
+* `model` - The model that should be used for this view. Used as a fallback for determining the serializer if `serializer_class` is not set, and as a fallback for determining the queryset if `queryset` is not set. Otherwise not required.
+* `serializer_class` - The serializer class that should be used for validating and deserializing input, and for serializing output. If unset, this defaults to creating a serializer class using `self.model`, with the `DEFAULT_MODEL_SERIALIZER_CLASS` setting as the base serializer class.
+
## MultipleObjectAPIView
Provides a base view for acting on a single object, by combining REST framework's `APIView`, and Django's [MultipleObjectMixin].
**See also:** ccbv.co.uk documentation for [MultipleObjectMixin][multiple-object-mixin-classy].
+**Attributes**:
+
+* `queryset` - The queryset that should be used for returning objects from this view. If unset, defaults to the default queryset manager for `self.model`.
+* `paginate_by` - The size of pages to use with paginated data. If set to `None` then pagination is turned off. If unset this uses the same value as the `PAGINATE_BY` setting, which defaults to `None`.
+* `paginate_by_param` - The name of a query parameter, which can be used by the client to overide the default page size to use for pagination. If unset this uses the same value as the `PAGINATE_BY_PARAM` setting, which defaults to `None`.
+
## SingleObjectAPIView
Provides a base view for acting on a single object, by combining REST framework's `APIView`, and Django's [SingleObjectMixin].
**See also:** ccbv.co.uk documentation for [SingleObjectMixin][single-object-mixin-classy].
+**Attributes**:
+
+* `queryset` - The queryset that should be used when retrieving an object from this view. If unset, defaults to the default queryset manager for `self.model`.
+* `pk_kwarg` - The URL kwarg that should be used to look up objects by primary key. Defaults to `'pk'`. [Can only be set to non-default on Django 1.4+]
+* `slug_kwarg` - The URL kwarg that should be used to look up objects by a slug. Defaults to `'slug'`. [Can only be set to non-default on Django 1.4+]
+* `slug_field` - The field on the model that should be used to look up objects by a slug. If used, this should typically be set to a field with `unique=True`. Defaults to `'slug'`.
+
---
# Mixins
@@ -147,10 +165,6 @@ Provides a `.list(request, *args, **kwargs)` method, that implements listing a q
Should be mixed in with [MultipleObjectAPIView].
-**Arguments**:
-
-* `page_size_kwarg` - Allows you to overwrite the global settings `PAGE_SIZE_KWARG` for a specific view. You can also turn it off for a specific view by setting it to `None`. Default is `page_size`.
-
## CreateModelMixin
Provides a `.create(request, *args, **kwargs)` method, that implements creating and saving a new model instance.
diff --git a/docs/api-guide/settings.md b/docs/api-guide/settings.md
index 8fce9e4e..7884d096 100644
--- a/docs/api-guide/settings.md
+++ b/docs/api-guide/settings.md
@@ -96,11 +96,21 @@ Default: `rest_framework.serializers.ModelSerializer`
Default: `rest_framework.pagination.PaginationSerializer`
-## FORMAT_SUFFIX_KWARG
+## FILTER_BACKEND
-**TODO**
+The filter backend class that should be used for generic filtering. If set to `None` then generic filtering is disabled.
-Default: `'format'`
+## PAGINATE_BY
+
+The default page size to use for pagination. If set to `None`, pagination is disabled by default.
+
+Default: `None`
+
+## PAGINATE_BY_KWARG
+
+The name of a query parameter, which can be used by the client to overide the default page size to use for pagination. If set to `None`, clients may not override the default page size.
+
+Default: `None`
## UNAUTHENTICATED_USER
@@ -150,14 +160,10 @@ Default: `'accept'`
Default: `'format'`
-## PAGE_SIZE_KWARG
-
-Allows you to globally pass a page size parameter for an individual request.
-
-The name of the GET parameter of views which inherit ListModelMixin for requesting data with an individual page size.
+## FORMAT_SUFFIX_KWARG
-If the value if this setting is `None` the passing a page size is turned off by default.
+**TODO**
-Default: `'page_size'`
+Default: `'format'`
[cite]: http://www.python.org/dev/peps/pep-0020/
diff --git a/docs/topics/release-notes.md b/docs/topics/release-notes.md
index 85c19f5b..869cabc8 100644
--- a/docs/topics/release-notes.md
+++ b/docs/topics/release-notes.md
@@ -7,7 +7,8 @@
## Master
* Support for `read_only_fields` on `ModelSerializer` classes.
-* Support for individual page sizes per request via `page_size` GET parameter in views which inherit ListModelMixin.
+* Support for clients overriding the pagination page sizes. Use the `PAGINATE_BY_PARAM` setting or set the `paginate_by_param` attribute on a generic view.
+* 201 Responses now return a 'Location' header.
## 2.1.2
diff --git a/rest_framework/generics.py b/rest_framework/generics.py
index 9ad03f71..dcf4dfd9 100644
--- a/rest_framework/generics.py
+++ b/rest_framework/generics.py
@@ -14,6 +14,7 @@ class GenericAPIView(views.APIView):
"""
Base class for all other generic views.
"""
+ model = None
serializer_class = None
model_serializer_class = api_settings.DEFAULT_MODEL_SERIALIZER_CLASS
@@ -30,8 +31,10 @@ class GenericAPIView(views.APIView):
def get_serializer_class(self):
"""
Return the class to use for the serializer.
- Use `self.serializer_class`, falling back to constructing a
- model serializer class from `self.model_serializer_class`
+
+ Defaults to using `self.serializer_class`, falls back to constructing a
+ model serializer class using `self.model_serializer_class`, with
+ `self.model` as the model.
"""
serializer_class = self.serializer_class
@@ -58,29 +61,42 @@ class MultipleObjectAPIView(MultipleObjectMixin, GenericAPIView):
pagination_serializer_class = api_settings.DEFAULT_PAGINATION_SERIALIZER_CLASS
paginate_by = api_settings.PAGINATE_BY
+ paginate_by_param = api_settings.PAGINATE_BY_PARAM
filter_backend = api_settings.FILTER_BACKEND
def filter_queryset(self, queryset):
+ """
+ Given a queryset, filter it with whichever filter backend is in use.
+ """
if not self.filter_backend:
return queryset
backend = self.filter_backend()
return backend.filter_queryset(self.request, queryset, self)
- def get_pagination_serializer_class(self):
+ def get_pagination_serializer(self, page=None):
"""
- Return the class to use for the pagination serializer.
+ Return a serializer instance to use with paginated data.
"""
class SerializerClass(self.pagination_serializer_class):
class Meta:
object_serializer_class = self.get_serializer_class()
- return SerializerClass
-
- def get_pagination_serializer(self, page=None):
- pagination_serializer_class = self.get_pagination_serializer_class()
+ pagination_serializer_class = SerializerClass
context = self.get_serializer_context()
return pagination_serializer_class(instance=page, context=context)
+ def get_paginate_by(self, queryset):
+ """
+ Return the size of pages to use with pagination.
+ """
+ if self.paginate_by_param:
+ params = self.request.QUERY_PARAMS
+ try:
+ return int(params[self.paginate_by_param])
+ except (KeyError, ValueError):
+ pass
+ return self.paginate_by
+
class SingleObjectAPIView(SingleObjectMixin, GenericAPIView):
"""
diff --git a/rest_framework/mixins.py b/rest_framework/mixins.py
index 0da4c2cc..53c4d984 100644
--- a/rest_framework/mixins.py
+++ b/rest_framework/mixins.py
@@ -7,7 +7,6 @@ which allows mixin classes to be composed in interesting ways.
from django.http import Http404
from rest_framework import status
from rest_framework.response import Response
-from rest_framework.settings import api_settings
class CreateModelMixin(object):
@@ -40,7 +39,6 @@ class ListModelMixin(object):
Should be mixed in with `MultipleObjectAPIView`.
"""
empty_error = u"Empty list and '%(class_name)s.allow_empty' is False."
- page_size_kwarg = api_settings.PAGE_SIZE_KWARG
def list(self, request, *args, **kwargs):
queryset = self.get_queryset()
@@ -66,17 +64,6 @@ class ListModelMixin(object):
return Response(serializer.data)
- def get_paginate_by(self, queryset):
- if self.page_size_kwarg is not None:
- page_size_kwarg = self.request.QUERY_PARAMS.get(self.page_size_kwarg)
- if page_size_kwarg:
- try:
- page_size = int(page_size_kwarg)
- return page_size
- except ValueError:
- pass
- return super(ListModelMixin, self).get_paginate_by(queryset)
-
class RetrieveModelMixin(object):
"""
diff --git a/rest_framework/settings.py b/rest_framework/settings.py
index 8883b963..ee24a4ad 100644
--- a/rest_framework/settings.py
+++ b/rest_framework/settings.py
@@ -54,12 +54,19 @@ DEFAULTS = {
'user': None,
'anon': None,
},
+
+ # Pagination
'PAGINATE_BY': None,
+ 'PAGINATE_BY_PARAM': None,
+
+ # Filtering
'FILTER_BACKEND': None,
+ # Authentication
'UNAUTHENTICATED_USER': 'django.contrib.auth.models.AnonymousUser',
'UNAUTHENTICATED_TOKEN': None,
+ # Browser enhancements
'FORM_METHOD_OVERRIDE': '_method',
'FORM_CONTENT_OVERRIDE': '_content',
'FORM_CONTENTTYPE_OVERRIDE': '_content_type',
@@ -67,8 +74,6 @@ DEFAULTS = {
'URL_FORMAT_OVERRIDE': 'format',
'FORMAT_SUFFIX_KWARG': 'format',
-
- 'PAGE_SIZE_KWARG': 'page_size'
}
diff --git a/rest_framework/tests/pagination.py b/rest_framework/tests/pagination.py
index 8aae2147..3062007d 100644
--- a/rest_framework/tests/pagination.py
+++ b/rest_framework/tests/pagination.py
@@ -36,25 +36,17 @@ if django_filters:
class DefaultPageSizeKwargView(generics.ListAPIView):
"""
- View for testing default page_size usage
+ View for testing default paginate_by_param usage
"""
model = BasicModel
-class CustomPageSizeKwargView(generics.ListAPIView):
+class PaginateByParamView(generics.ListAPIView):
"""
- View for testing custom page_size usage
+ View for testing custom paginate_by_param usage
"""
model = BasicModel
- page_size_kwarg = 'ps'
-
-
-class NonePageSizeKwargView(generics.ListAPIView):
- """
- View for testing None page_size usage
- """
- model = BasicModel
- page_size_kwarg = None
+ paginate_by_param = 'page_size'
class IntegrationTestPagination(TestCase):
@@ -181,9 +173,9 @@ class UnitTestPagination(TestCase):
self.assertEquals(serializer.data['results'], self.objects[20:])
-class TestDefaultPageSizeKwarg(TestCase):
+class TestUnpaginated(TestCase):
"""
- Tests for list views with default page size kwarg
+ Tests for list views without pagination.
"""
def setUp(self):
@@ -199,26 +191,17 @@ class TestDefaultPageSizeKwarg(TestCase):
]
self.view = DefaultPageSizeKwargView.as_view()
- def test_default_page_size(self):
+ def test_unpaginated(self):
"""
Tests the default page size for this view.
no page size --> no limit --> no meta data
"""
request = factory.get('/')
- response = self.view(request).render()
+ response = self.view(request)
self.assertEquals(response.data, self.data)
- def test_default_page_size_kwarg(self):
- """
- If page_size_kwarg is set not set, the default page_size kwarg should limit per view requests.
- """
- request = factory.get('/?page_size=5')
- response = self.view(request).render()
- self.assertEquals(response.data['count'], 13)
- self.assertEquals(response.data['results'], self.data[:5])
-
-class TestCustomPageSizeKwarg(TestCase):
+class TestCustomPaginateByParam(TestCase):
"""
Tests for list views with default page size kwarg
"""
@@ -234,7 +217,7 @@ class TestCustomPageSizeKwarg(TestCase):
{'id': obj.id, 'text': obj.text}
for obj in self.objects.all()
]
- self.view = CustomPageSizeKwargView.as_view()
+ self.view = PaginateByParamView.as_view()
def test_default_page_size(self):
"""
@@ -245,55 +228,11 @@ class TestCustomPageSizeKwarg(TestCase):
response = self.view(request).render()
self.assertEquals(response.data, self.data)
- def test_disabled_default_page_size_kwarg(self):
+ def test_paginate_by_param(self):
"""
- If page_size_kwarg is set set, the default page_size kwarg should not work.
+ If paginate_by_param is set, the new kwarg should limit per view requests.
"""
request = factory.get('/?page_size=5')
response = self.view(request).render()
- self.assertEquals(response.data, self.data)
-
- def test_custom_page_size_kwarg(self):
- """
- If page_size_kwarg is set set, the new kwarg should limit per view requests.
- """
- request = factory.get('/?ps=5')
- response = self.view(request).render()
self.assertEquals(response.data['count'], 13)
self.assertEquals(response.data['results'], self.data[:5])
-
-
-class TestNonePageSizeKwarg(TestCase):
- """
- Tests for list views with default page size kwarg
- """
-
- def setUp(self):
- """
- Create 13 BasicModel instances.
- """
- for i in range(13):
- BasicModel(text=i).save()
- self.objects = BasicModel.objects
- self.data = [
- {'id': obj.id, 'text': obj.text}
- for obj in self.objects.all()
- ]
- self.view = NonePageSizeKwargView.as_view()
-
- def test_default_page_size(self):
- """
- Tests the default page size for this view.
- no page size --> no limit --> no meta data
- """
- request = factory.get('/')
- response = self.view(request).render()
- self.assertEquals(response.data, self.data)
-
- def test_none_page_size_kwarg(self):
- """
- If page_size_kwarg is set to None, custom page_size per request should be disabled.
- """
- request = factory.get('/?page_size=5')
- response = self.view(request).render()
- self.assertEquals(response.data, self.data)
--
cgit v1.2.3
From 19b0516bfefe3398683b4f878774e3dd80bf653a Mon Sep 17 00:00:00 2001
From: Tom Christie
Date: Fri, 16 Nov 2012 22:49:15 +0000
Subject: Getting 2.1.3 release notes ready
---
README.md | 8 ++++++++
docs/topics/release-notes.md | 4 +++-
2 files changed, 11 insertions(+), 1 deletion(-)
diff --git a/README.md b/README.md
index c3ffc9a7..d03fc80e 100644
--- a/README.md
+++ b/README.md
@@ -58,6 +58,14 @@ To run the tests.
# Changelog
+## 2.1.3
+
+**Date**: 16th Nov 2012
+
+* Support for `read_only_fields` on `ModelSerializer` classes.
+* Support for clients overriding the pagination page sizes. Use the `PAGINATE_BY_PARAM` setting or set the `paginate_by_param` attribute on a generic view.
+* 201 Responses now return a 'Location' header.
+
## 2.1.2
**Date**: 9th Nov 2012
diff --git a/docs/topics/release-notes.md b/docs/topics/release-notes.md
index 869cabc8..5931a75a 100644
--- a/docs/topics/release-notes.md
+++ b/docs/topics/release-notes.md
@@ -4,7 +4,9 @@
>
> — Eric S. Raymond, [The Cathedral and the Bazaar][cite].
-## Master
+## 2.1.3
+
+**Date**: 16th Nov 2012
* Support for `read_only_fields` on `ModelSerializer` classes.
* Support for clients overriding the pagination page sizes. Use the `PAGINATE_BY_PARAM` setting or set the `paginate_by_param` attribute on a generic view.
--
cgit v1.2.3
From 016ef5019ff43808540f948d674e8dd33247cb99 Mon Sep 17 00:00:00 2001
From: Tom Christie
Date: Fri, 16 Nov 2012 22:58:17 +0000
Subject: Version 2.1.3
---
README.md | 3 +++
docs/topics/release-notes.md | 3 +++
rest_framework/__init__.py | 2 +-
3 files changed, 7 insertions(+), 1 deletion(-)
diff --git a/README.md b/README.md
index d03fc80e..9a12d535 100644
--- a/README.md
+++ b/README.md
@@ -62,9 +62,12 @@ To run the tests.
**Date**: 16th Nov 2012
+* Added `FileField` and `ImageField`. For use with `MultiPartParser`.
+* Added `URLField` and `SlugField`.
* Support for `read_only_fields` on `ModelSerializer` classes.
* Support for clients overriding the pagination page sizes. Use the `PAGINATE_BY_PARAM` setting or set the `paginate_by_param` attribute on a generic view.
* 201 Responses now return a 'Location' header.
+* Bugfix: Serializer fields now respect `max_length`.
## 2.1.2
diff --git a/docs/topics/release-notes.md b/docs/topics/release-notes.md
index 5931a75a..2a6fbe83 100644
--- a/docs/topics/release-notes.md
+++ b/docs/topics/release-notes.md
@@ -8,9 +8,12 @@
**Date**: 16th Nov 2012
+* Added `FileField` and `ImageField`. For use with `MultiPartParser`.
+* Added `URLField` and `SlugField`.
* Support for `read_only_fields` on `ModelSerializer` classes.
* Support for clients overriding the pagination page sizes. Use the `PAGINATE_BY_PARAM` setting or set the `paginate_by_param` attribute on a generic view.
* 201 Responses now return a 'Location' header.
+* Bugfix: Serializer fields now respect `max_length`.
## 2.1.2
diff --git a/rest_framework/__init__.py b/rest_framework/__init__.py
index fd176603..88108a8d 100644
--- a/rest_framework/__init__.py
+++ b/rest_framework/__init__.py
@@ -1,3 +1,3 @@
-__version__ = '2.1.2'
+__version__ = '2.1.3'
VERSION = __version__ # synonym
--
cgit v1.2.3
From acbe991209ed9112af80db99d832704641276844 Mon Sep 17 00:00:00 2001
From: Tom Christie
Date: Fri, 16 Nov 2012 23:22:15 +0000
Subject: Tidying
---
rest_framework/generics.py | 14 ++++++++++----
1 file changed, 10 insertions(+), 4 deletions(-)
diff --git a/rest_framework/generics.py b/rest_framework/generics.py
index be225d0a..dd8dfcf8 100644
--- a/rest_framework/generics.py
+++ b/rest_framework/generics.py
@@ -14,6 +14,7 @@ class GenericAPIView(views.APIView):
"""
Base class for all other generic views.
"""
+
model = None
serializer_class = None
model_serializer_class = api_settings.DEFAULT_MODEL_SERIALIZER_CLASS
@@ -47,7 +48,10 @@ class GenericAPIView(views.APIView):
return serializer_class
def get_serializer(self, instance=None, data=None, files=None):
- # TODO: add support for seperate serializer/deserializer
+ """
+ Return the serializer instance that should be used for validating and
+ deserializing input, and for serializing output.
+ """
serializer_class = self.get_serializer_class()
context = self.get_serializer_context()
return serializer_class(instance, data=data, files=files, context=context)
@@ -58,9 +62,9 @@ class MultipleObjectAPIView(MultipleObjectMixin, GenericAPIView):
Base class for generic views onto a queryset.
"""
- pagination_serializer_class = api_settings.DEFAULT_PAGINATION_SERIALIZER_CLASS
paginate_by = api_settings.PAGINATE_BY
paginate_by_param = api_settings.PAGINATE_BY_PARAM
+ pagination_serializer_class = api_settings.DEFAULT_PAGINATION_SERIALIZER_CLASS
filter_backend = api_settings.FILTER_BACKEND
def filter_queryset(self, queryset):
@@ -89,9 +93,9 @@ class MultipleObjectAPIView(MultipleObjectMixin, GenericAPIView):
Return the size of pages to use with pagination.
"""
if self.paginate_by_param:
- params = self.request.QUERY_PARAMS
+ query_params = self.request.QUERY_PARAMS
try:
- return int(params[self.paginate_by_param])
+ return int(query_params[self.paginate_by_param])
except (KeyError, ValueError):
pass
return self.paginate_by
@@ -101,8 +105,10 @@ class SingleObjectAPIView(SingleObjectMixin, GenericAPIView):
"""
Base class for generic views onto a model instance.
"""
+
pk_url_kwarg = 'pk' # Not provided in Django 1.3
slug_url_kwarg = 'slug' # Not provided in Django 1.3
+ slug_field = 'slug'
def get_object(self, queryset=None):
"""
--
cgit v1.2.3
From 0eba278e1391604086dab1dfa1bd6ea86fea282e Mon Sep 17 00:00:00 2001
From: Tom Christie
Date: Fri, 16 Nov 2012 23:22:23 +0000
Subject: Improve pagination docs
---
docs/api-guide/pagination.md | 28 +++++++++++++++++++++-------
1 file changed, 21 insertions(+), 7 deletions(-)
diff --git a/docs/api-guide/pagination.md b/docs/api-guide/pagination.md
index 597baba4..39e6a32d 100644
--- a/docs/api-guide/pagination.md
+++ b/docs/api-guide/pagination.md
@@ -80,23 +80,21 @@ We could now use our pagination serializer in a view like this.
## Pagination in the generic views
-The generic class based views `ListAPIView` and `ListCreateAPIView` provide pagination of the returned querysets by default. You can customise this behaviour by altering the pagination style, by modifying the default number of results, or by turning pagination off completely.
+The generic class based views `ListAPIView` and `ListCreateAPIView` provide pagination of the returned querysets by default. You can customise this behaviour by altering the pagination style, by modifying the default number of results, by allowing clients to override the page size using a query parameter, or by turning pagination off completely.
-The default pagination style may be set globally, using the `PAGINATION_SERIALIZER` and `PAGINATE_BY` settings. For example.
+The default pagination style may be set globally, using the `DEFAULT_PAGINATION_SERIALIZER_CLASS`, `PAGINATE_BY` and `PAGINATE_BY_PARAM` settings. For example.
REST_FRAMEWORK = {
- 'PAGINATION_SERIALIZER': (
- 'example_app.pagination.CustomPaginationSerializer',
- ),
- 'PAGINATE_BY': 10
+ 'PAGINATE_BY': 10,
+ 'PAGINATE_BY_PARAM': 'page_size'
}
You can also set the pagination style on a per-view basis, using the `ListAPIView` generic class-based view.
class PaginatedListView(ListAPIView):
model = ExampleModel
- pagination_serializer_class = CustomPaginationSerializer
paginate_by = 10
+ paginate_by_param = 'page_size'
For more complex requirements such as serialization that differs depending on the requested media type you can override the `.get_paginate_by()` and `.get_pagination_serializer_class()` methods.
@@ -122,4 +120,20 @@ For example, to nest a pair of links labelled 'prev' and 'next', and set the nam
results_field = 'objects'
+## Using your custom pagination serializer
+
+To have your custom pagination serializer be used by default use the `DEFAULT_PAGINATION_SERIALIZER_CLASS` setting:
+
+ REST_FRAMEWORK = {
+ 'DEFAULT_PAGINATION_SERIALIZER_CLASS':
+ 'example_app.pagination.CustomPaginationSerializer',
+ }
+
+Alternatively, to set your custom pagination serializer on a per-view basis, use the `pagination_serializer_class` attribute on a generic class based view:
+
+ class PaginatedListView(ListAPIView):
+ model = ExampleModel
+ pagination_serializer_class = CustomPaginationSerializer
+ paginate_by = 10
+
[cite]: https://docs.djangoproject.com/en/dev/topics/pagination/
--
cgit v1.2.3
From 2263ed8b9409c709f6dbad2157f8debffb16c1d8 Mon Sep 17 00:00:00 2001
From: Tom Christie
Date: Fri, 16 Nov 2012 23:24:36 +0000
Subject: Tweak
---
docs/api-guide/pagination.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/api-guide/pagination.md b/docs/api-guide/pagination.md
index 39e6a32d..5a35ed75 100644
--- a/docs/api-guide/pagination.md
+++ b/docs/api-guide/pagination.md
@@ -122,7 +122,7 @@ For example, to nest a pair of links labelled 'prev' and 'next', and set the nam
## Using your custom pagination serializer
-To have your custom pagination serializer be used by default use the `DEFAULT_PAGINATION_SERIALIZER_CLASS` setting:
+To have your custom pagination serializer be used by default, use the `DEFAULT_PAGINATION_SERIALIZER_CLASS` setting:
REST_FRAMEWORK = {
'DEFAULT_PAGINATION_SERIALIZER_CLASS':
--
cgit v1.2.3
From 4068323df4a8a8ad8825d5e0ed1d31ee2a36484f Mon Sep 17 00:00:00 2001
From: Eugene MechanisM
Date: Sat, 17 Nov 2012 04:03:43 +0400
Subject: Missing import of "Permission" model in docs
Missing import of "Permission" model in docs---
docs/tutorial/quickstart.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/tutorial/quickstart.md b/docs/tutorial/quickstart.md
index 93da1a59..9a36a2b0 100644
--- a/docs/tutorial/quickstart.md
+++ b/docs/tutorial/quickstart.md
@@ -8,7 +8,7 @@ Create a new Django project, and start a new app called `quickstart`. Once you'
First up we're going to define some serializers in `quickstart/serializers.py` that we'll use for our data representations.
- from django.contrib.auth.models import User, Group
+ from django.contrib.auth.models import User, Group, Permission
from rest_framework import serializers
--
cgit v1.2.3
From 346a79b170b0a25fd28354de765c5aa5aca9a119 Mon Sep 17 00:00:00 2001
From: Tom Christie
Date: Sat, 17 Nov 2012 00:29:15 +0000
Subject: Added @MechanisM - Thanks!
(That's a mighty kick ass Gravatar)---
docs/topics/credits.md | 2 ++
1 file changed, 2 insertions(+)
diff --git a/docs/topics/credits.md b/docs/topics/credits.md
index 8e71c937..f037e816 100644
--- a/docs/topics/credits.md
+++ b/docs/topics/credits.md
@@ -60,6 +60,7 @@ The following people have helped make REST framework great.
* Ben Konrath - [benkonrath]
* Marc Aymerich - [glic3rinu]
* Ludwig Kraatz - [ludwigkraatz]
+* Eugene Mechanism - [mechanism]
Many thanks to everyone who's contributed to the project.
@@ -155,3 +156,4 @@ To contact the author directly:
[benkonrath]: https://github.com/benkonrath
[glic3rinu]: https://github.com/glic3rinu
[ludwigkraatz]: https://github.com/ludwigkraatz
+[mechanism]: https://github.com/mechanism
--
cgit v1.2.3
From f131e533edf58dc8ba7b712b4c3486a3ab053ffc Mon Sep 17 00:00:00 2001
From: Tom Christie
Date: Sun, 18 Nov 2012 17:57:02 +0000
Subject: Docs, docs, docs, docs, docs, docs
---
docs/api-guide/generic-views.md | 18 ++++++++++++++++++
docs/api-guide/pagination.md | 5 +++--
2 files changed, 21 insertions(+), 2 deletions(-)
diff --git a/docs/api-guide/generic-views.md b/docs/api-guide/generic-views.md
index 33ec89d2..428323b8 100644
--- a/docs/api-guide/generic-views.md
+++ b/docs/api-guide/generic-views.md
@@ -163,30 +163,48 @@ The mixin classes provide the actions that are used to provide the basic view be
Provides a `.list(request, *args, **kwargs)` method, that implements listing a queryset.
+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` reponse, unless the `.allow_empty` attribute on the view is set to `False`, in which case it will return a `404 Not Found`.
+
Should be mixed in with [MultipleObjectAPIView].
## CreateModelMixin
Provides a `.create(request, *args, **kwargs)` method, that implements creating and saving a new model instance.
+If an object is created this returns a `201 Created` response, with a serialized representation of the object as the body of the response. If the representation contains a key named `url`, then the `Location` header of the response will be populated with that value.
+
+If the request data provided for creating the object was invalid, a `400 Bad Request` response will be returned, with the error details as the body of the response.
+
Should be mixed in with any [GenericAPIView].
## RetrieveModelMixin
Provides a `.retrieve(request, *args, **kwargs)` method, that implements returning an existing model instance in a response.
+If an object can be retrieve this returns a `200 OK` response, with a serialized representation of the object as the body of the response. Otherwise it will return a `404 Not Found`.
+
Should be mixed in with [SingleObjectAPIView].
## UpdateModelMixin
Provides a `.update(request, *args, **kwargs)` method, that implements updating and saving an existing model instance.
+If an object is updated this returns a `200 OK` response, with a serialized representation of the object as the body of the response.
+
+If an object is created, for example when making a `DELETE` request followed by a `PUT` request to the same URL, this returns a `201 Created` response, with a serialized representation of the object as the body of the response.
+
+If the request data provided for updating the object was invalid, a `400 Bad Request` response will be returned, with the error details as the body of the response.
+
Should be mixed in with [SingleObjectAPIView].
## DestroyModelMixin
Provides a `.destroy(request, *args, **kwargs)` method, that implements deletion of an existing model instance.
+If an object is deleted this returns a `204 No Content` response, otherwise it will return a `404 Not Found`.
+
Should be mixed in with [SingleObjectAPIView].
[cite]: https://docs.djangoproject.com/en/dev/ref/class-based-views/#base-vs-generic-views
diff --git a/docs/api-guide/pagination.md b/docs/api-guide/pagination.md
index 5a35ed75..ab335e6e 100644
--- a/docs/api-guide/pagination.md
+++ b/docs/api-guide/pagination.md
@@ -70,11 +70,12 @@ We could now use our pagination serializer in a view like this.
# If page is not an integer, deliver first page.
users = paginator.page(1)
except EmptyPage:
- # If page is out of range (e.g. 9999), deliver last page of results.
+ # If page is out of range (e.g. 9999),
+ # deliver last page of results.
users = paginator.page(paginator.num_pages)
serializer_context = {'request': request}
- serializer = PaginatedUserSerializer(instance=users,
+ serializer = PaginatedUserSerializer(users,
context=serializer_context)
return Response(serializer.data)
--
cgit v1.2.3
From b03804fe05225d22c14471a18b96197fdf31dce9 Mon Sep 17 00:00:00 2001
From: glic3rinu
Date: Mon, 19 Nov 2012 00:14:03 +0100
Subject: Fixed identation on filter_fields
---
rest_framework/filters.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/rest_framework/filters.py b/rest_framework/filters.py
index ccae4825..bcc87660 100644
--- a/rest_framework/filters.py
+++ b/rest_framework/filters.py
@@ -45,7 +45,7 @@ class DjangoFilterBackend(BaseFilterBackend):
class AutoFilterSet(self.default_filter_set):
class Meta:
model = view_model
- fields = filter_fields
+ fields = filter_fields
return AutoFilterSet
return None
--
cgit v1.2.3