aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rwxr-xr-xdocs/api-guide/authentication.md14
-rw-r--r--docs/api-guide/content-negotiation.md6
-rw-r--r--docs/api-guide/fields.md13
-rw-r--r--docs/api-guide/filtering.md14
-rwxr-xr-xdocs/api-guide/generic-views.md12
-rw-r--r--docs/api-guide/pagination.md7
-rw-r--r--docs/api-guide/parsers.md4
-rw-r--r--docs/api-guide/permissions.md16
-rw-r--r--docs/api-guide/relations.md11
-rw-r--r--docs/api-guide/renderers.md9
-rw-r--r--docs/api-guide/reverse.md4
-rw-r--r--docs/api-guide/routers.md9
-rw-r--r--docs/api-guide/serializers.md7
-rw-r--r--docs/api-guide/status-codes.md1
-rw-r--r--docs/api-guide/testing.md21
-rw-r--r--docs/api-guide/throttling.md4
-rw-r--r--docs/api-guide/viewsets.md9
-rw-r--r--docs/topics/credits.md2
-rw-r--r--docs/topics/release-notes.md1
-rw-r--r--rest_framework/fields.py14
-rw-r--r--rest_framework/generics.py2
-rw-r--r--rest_framework/tests/test_fields.py9
22 files changed, 163 insertions, 26 deletions
diff --git a/docs/api-guide/authentication.md b/docs/api-guide/authentication.md
index b1ab4622..f30b16ed 100755
--- a/docs/api-guide/authentication.md
+++ b/docs/api-guide/authentication.md
@@ -46,6 +46,11 @@ The default authentication schemes may be set globally, using the `DEFAULT_AUTHE
You can also set the authentication scheme on a per-view or per-viewset basis,
using the `APIView` class based views.
+ from rest_framework.authentication import SessionAuthentication, BasicAuthentication
+ from rest_framework.permissions import IsAuthenticated
+ from rest_framework.response import Response
+ from rest_framework.views import APIView
+
class ExampleView(APIView):
authentication_classes = (SessionAuthentication, BasicAuthentication)
permission_classes = (IsAuthenticated,)
@@ -157,11 +162,16 @@ The `curl` command line tool may be useful for testing token authenticated APIs.
If you want every user to have an automatically generated Token, you can simply catch the User's `post_save` signal.
+ from django.dispatch import receiver
+ from rest_framework.authtoken.models import Token
+
@receiver(post_save, sender=User)
def create_auth_token(sender, instance=None, created=False, **kwargs):
if created:
Token.objects.create(user=instance)
+Note that you'll want to ensure you place this code snippet in an installed `models.py` module, or some other location that will be imported by Django on startup.
+
If you've already created some users, you can generate tokens for all existing users like this:
from django.contrib.auth.models import User
@@ -336,6 +346,10 @@ If the `.authenticate_header()` method is not overridden, the authentication sch
The following example will authenticate any incoming request as the user given by the username in a custom request header named 'X_USERNAME'.
+ from django.contrib.auth.models import User
+ from rest_framework import authentication
+ from rest_framework import exceptions
+
class ExampleAuthentication(authentication.BaseAuthentication):
def authenticate(self, request):
username = request.META.get('X_USERNAME')
diff --git a/docs/api-guide/content-negotiation.md b/docs/api-guide/content-negotiation.md
index 2a774278..94dd59ca 100644
--- a/docs/api-guide/content-negotiation.md
+++ b/docs/api-guide/content-negotiation.md
@@ -54,6 +54,8 @@ The `select_renderer()` method should return a two-tuple of (renderer instance,
The following is a custom content negotiation class which ignores the client
request when selecting the appropriate parser or renderer.
+ from rest_framework.negotiation import BaseContentNegotiation
+
class IgnoreClientContentNegotiation(BaseContentNegotiation):
def select_parser(self, request, parsers):
"""
@@ -77,6 +79,10 @@ The default content negotiation class may be set globally, using the `DEFAULT_CO
You can also set the content negotiation used for an individual view, or viewset, using the `APIView` class based views.
+ from myapp.negotiation import IgnoreClientContentNegotiation
+ from rest_framework.response import Response
+ from rest_framework.views import APIView
+
class NoNegotiationView(APIView):
"""
An example view that does not perform content negotiation.
diff --git a/docs/api-guide/fields.md b/docs/api-guide/fields.md
index d69730c9..962c49e2 100644
--- a/docs/api-guide/fields.md
+++ b/docs/api-guide/fields.md
@@ -78,6 +78,9 @@ A generic, **read-only** field. You can use this field for any attribute that d
For example, using the following model.
+ from django.db import models
+ from django.utils.timezone import now
+
class Account(models.Model):
owner = models.ForeignKey('auth.user')
name = models.CharField(max_length=100)
@@ -85,13 +88,14 @@ For example, using the following model.
payment_expiry = models.DateTimeField()
def has_expired(self):
- now = datetime.datetime.now()
- return now > self.payment_expiry
+ return now() > self.payment_expiry
A serializer definition that looked like this:
+ from rest_framework import serializers
+
class AccountSerializer(serializers.HyperlinkedModelSerializer):
- expired = Field(source='has_expired')
+ expired = serializers.Field(source='has_expired')
class Meta:
fields = ('url', 'owner', 'name', 'expired')
@@ -125,12 +129,11 @@ The `ModelField` class is generally intended for internal use, but can be used b
This is a read-only field. It gets its value by calling a method on the serializer class it is attached to. It can be used to add any sort of data to the serialized representation of your object. The field's constructor accepts a single argument, which is the name of the method on the serializer to be called. The method should accept a single argument (in addition to `self`), which is the object being serialized. It should return whatever you want to be included in the serialized representation of the object. For example:
- from rest_framework import serializers
from django.contrib.auth.models import User
from django.utils.timezone import now
+ from rest_framework import serializers
class UserSerializer(serializers.ModelSerializer):
-
days_since_joined = serializers.SerializerMethodField('get_days_since_joined')
class Meta:
diff --git a/docs/api-guide/filtering.md b/docs/api-guide/filtering.md
index 05c997a3..649462da 100644
--- a/docs/api-guide/filtering.md
+++ b/docs/api-guide/filtering.md
@@ -20,6 +20,10 @@ You can do so by filtering based on the value of `request.user`.
For example:
+ from myapp.models import Purchase
+ from myapp.serializers import PurchaseSerializer
+ from rest_framework import generics
+
class PurchaseList(generics.ListAPIView)
serializer_class = PurchaseSerializer
@@ -90,6 +94,11 @@ The default filter backends may be set globally, using the `DEFAULT_FILTER_BACKE
You can also set the filter backends on a per-view, or per-viewset basis,
using the `GenericAPIView` class based views.
+ from django.contrib.auth.models import User
+ from myapp.serializers import UserSerializer
+ from rest_framework import filters
+ from rest_framework import generics
+
class UserListView(generics.ListAPIView):
queryset = User.objects.all()
serializer = UserSerializer
@@ -150,6 +159,11 @@ This will automatically create a `FilterSet` class for the given fields, and wil
For more advanced filtering requirements you can specify a `FilterSet` class that should be used by the view. For example:
+ import django_filters
+ from myapp.models import Product
+ from myapp.serializers import ProductSerializer
+ from rest_framework import generics
+
class ProductFilter(django_filters.FilterSet):
min_price = django_filters.NumberFilter(lookup_type='gte')
max_price = django_filters.NumberFilter(lookup_type='lte')
diff --git a/docs/api-guide/generic-views.md b/docs/api-guide/generic-views.md
index 32a4feef..931cae54 100755
--- a/docs/api-guide/generic-views.md
+++ b/docs/api-guide/generic-views.md
@@ -17,6 +17,11 @@ If the generic views don't suit the needs of your API, you can drop down to usin
Typically when using the generic views, you'll override the view, and set several class attributes.
+ from django.contrib.auth.models import User
+ from myapp.serializers import UserSerializer
+ from rest_framework import generics
+ from rest_framework.permissions import IsAdminUser
+
class UserList(generics.ListCreateAPIView):
queryset = User.objects.all()
serializer_class = UserSerializer
@@ -108,7 +113,12 @@ For example:
filter = {}
for field in self.multiple_lookup_fields:
filter[field] = self.kwargs[field]
- return get_object_or_404(queryset, **filter)
+
+ obj = get_object_or_404(queryset, **filter)
+ self.check_object_permissions(self.request, obj)
+ return obj
+
+Note that if your API doesn't include any object level permissions, you may optionally exclude the ``self.check_object_permissions, and simply return the object from the `get_object_or_404` lookup.
#### `get_serializer_class(self)`
diff --git a/docs/api-guide/pagination.md b/docs/api-guide/pagination.md
index 912ce41b..ca0174b7 100644
--- a/docs/api-guide/pagination.md
+++ b/docs/api-guide/pagination.md
@@ -13,6 +13,7 @@ REST framework includes a `PaginationSerializer` class that makes it easy to ret
Let's start by taking a look at an example from the Django documentation.
from django.core.paginator import Paginator
+
objects = ['john', 'paul', 'george', 'ringo']
paginator = Paginator(objects, 2)
page = paginator.page(1)
@@ -22,6 +23,7 @@ Let's start by taking a look at an example from the Django documentation.
At this point we've got a page object. If we wanted to return this page object as a JSON response, we'd need to provide the client with context such as next and previous links, so that it would be able to page through the remaining results.
from rest_framework.pagination import PaginationSerializer
+
serializer = PaginationSerializer(instance=page)
serializer.data
# {'count': 4, 'next': '?page=2', 'previous': None, 'results': [u'john', u'paul']}
@@ -114,6 +116,9 @@ You can also override the name used for the object list field, by setting the `r
For example, to nest a pair of links labelled 'prev' and 'next', and set the name for the results field to 'objects', you might use something like this.
+ from rest_framework import pagination
+ from rest_framework import serializers
+
class LinksSerializer(serializers.Serializer):
next = pagination.NextPageField(source='*')
prev = pagination.PreviousPageField(source='*')
@@ -135,7 +140,7 @@ To have your custom pagination serializer be used by default, use the `DEFAULT_P
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):
+ class PaginatedListView(generics.ListAPIView):
model = ExampleModel
pagination_serializer_class = CustomPaginationSerializer
paginate_by = 10
diff --git a/docs/api-guide/parsers.md b/docs/api-guide/parsers.md
index 5bd79a31..d3c42b1c 100644
--- a/docs/api-guide/parsers.md
+++ b/docs/api-guide/parsers.md
@@ -37,6 +37,10 @@ The default set of parsers may be set globally, using the `DEFAULT_PARSER_CLASSE
You can also set the renderers used for an individual view, or viewset,
using the `APIView` class based views.
+ from rest_framework.parsers import YAMLParser
+ from rest_framework.response import Response
+ from rest_framework.views import APIView
+
class ExampleView(APIView):
"""
A view that can accept POST requests with YAML content.
diff --git a/docs/api-guide/permissions.md b/docs/api-guide/permissions.md
index c6372f98..12aa4c18 100644
--- a/docs/api-guide/permissions.md
+++ b/docs/api-guide/permissions.md
@@ -25,9 +25,17 @@ Object level permissions are run by REST framework's generic views when `.get_ob
As with view level permissions, an `exceptions.PermissionDenied` exception will be raised if the user is not allowed to act on the given object.
If you're writing your own views and want to enforce object level permissions,
-you'll need to explicitly call the `.check_object_permissions(request, obj)` method on the view at the point at which you've retrieved the object.
+or if you override the `get_object` method on a generic view, then you'll need to explicitly call the `.check_object_permissions(request, obj)` method on the view at the point at which you've retrieved the object.
+
This will either raise a `PermissionDenied` or `NotAuthenticated` exception, or simply return if the view has the appropriate permissions.
+For example:
+
+ def get_object(self):
+ obj = get_object_or_404(self.get_queryset())
+ self.check_object_permissions(self.request, obj)
+ return obj
+
## Setting the permission policy
The default permission policy may be set globally, using the `DEFAULT_PERMISSION_CLASSES` setting. For example.
@@ -47,6 +55,10 @@ If not specified, this setting defaults to allowing unrestricted access:
You can also set the authentication policy on a per-view, or per-viewset basis,
using the `APIView` class based views.
+ from rest_framework.permissions import IsAuthenticated
+ from rest_framework.responses import Response
+ from rest_framework.views import APIView
+
class ExampleView(APIView):
permission_classes = (IsAuthenticated,)
@@ -157,6 +169,8 @@ For more details see the [2.2 release announcement][2.2-announcement].
The following is an example of a permission class that checks the incoming request's IP address against a blacklist, and denies the request if the IP has been blacklisted.
+ from rest_framework import permissions
+
class BlacklistPermission(permissions.BasePermission):
"""
Global permission check for blacklisted IPs.
diff --git a/docs/api-guide/relations.md b/docs/api-guide/relations.md
index 829a3c54..aa14bc72 100644
--- a/docs/api-guide/relations.md
+++ b/docs/api-guide/relations.md
@@ -76,7 +76,7 @@ This field is read only.
For example, the following serializer:
class AlbumSerializer(serializers.ModelSerializer):
- tracks = PrimaryKeyRelatedField(many=True, read_only=True)
+ tracks = serializers.PrimaryKeyRelatedField(many=True, read_only=True)
class Meta:
model = Album
@@ -110,8 +110,8 @@ By default this field is read-write, although you can change this behavior using
For example, the following serializer:
class AlbumSerializer(serializers.ModelSerializer):
- tracks = HyperlinkedRelatedField(many=True, read_only=True,
- view_name='track-detail')
+ tracks = serializers.HyperlinkedRelatedField(many=True, read_only=True,
+ view_name='track-detail')
class Meta:
model = Album
@@ -148,7 +148,8 @@ By default this field is read-write, although you can change this behavior using
For example, the following serializer:
class AlbumSerializer(serializers.ModelSerializer):
- tracks = SlugRelatedField(many=True, read_only=True, slug_field='title')
+ tracks = serializers.SlugRelatedField(many=True, read_only=True,
+ slug_field='title')
class Meta:
model = Album
@@ -183,7 +184,7 @@ When using `SlugRelatedField` as a read-write field, you will normally want to e
This field can be applied as an identity relationship, such as the `'url'` field on a HyperlinkedModelSerializer. It can also be used for an attribute on the object. For example, the following serializer:
class AlbumSerializer(serializers.HyperlinkedModelSerializer):
- track_listing = HyperlinkedIdentityField(view_name='track-list')
+ track_listing = serializers.HyperlinkedIdentityField(view_name='track-list')
class Meta:
model = Album
diff --git a/docs/api-guide/renderers.md b/docs/api-guide/renderers.md
index bb3d2015..7fc1fc1f 100644
--- a/docs/api-guide/renderers.md
+++ b/docs/api-guide/renderers.md
@@ -30,11 +30,16 @@ The default set of renderers may be set globally, using the `DEFAULT_RENDERER_CL
You can also set the renderers used for an individual view, or viewset,
using the `APIView` class based views.
+ from django.contrib.auth.models import User
+ from rest_framework.renderers import JSONRenderer, YAMLRenderer
+ from rest_framework.response import Response
+ from rest_framework.views import APIView
+
class UserCountView(APIView):
"""
- A view that returns the count of active users, in JSON or JSONp.
+ A view that returns the count of active users, in JSON or YAML.
"""
- renderer_classes = (JSONRenderer, JSONPRenderer)
+ renderer_classes = (JSONRenderer, YAMLRenderer)
def get(self, request, format=None):
user_count = User.objects.filter(active=True).count()
diff --git a/docs/api-guide/reverse.md b/docs/api-guide/reverse.md
index 94262366..383eca4c 100644
--- a/docs/api-guide/reverse.md
+++ b/docs/api-guide/reverse.md
@@ -27,13 +27,13 @@ Has the same behavior as [`django.core.urlresolvers.reverse`][reverse], except t
You should **include the request as a keyword argument** to the function, for example:
- import datetime
from rest_framework.reverse import reverse
from rest_framework.views import APIView
+ from django.utils.timezone import now
class APIRootView(APIView):
def get(self, request):
- year = datetime.datetime.now().year
+ year = now().year
data = {
...
'year-summary-url': reverse('year-summary', args=[year], request=request)
diff --git a/docs/api-guide/routers.md b/docs/api-guide/routers.md
index c8465418..f083b3d4 100644
--- a/docs/api-guide/routers.md
+++ b/docs/api-guide/routers.md
@@ -14,6 +14,8 @@ REST framework adds support for automatic URL routing to Django, and provides yo
Here's an example of a simple URL conf, that uses `DefaultRouter`.
+ from rest_framework import routers
+
router = routers.SimpleRouter()
router.register(r'users', UserViewSet)
router.register(r'accounts', AccountViewSet)
@@ -40,6 +42,9 @@ The example above would generate the following URL patterns:
Any methods on the viewset decorated with `@detail_route` or `@list_route` will also be routed.
For example, given a method like this on the `UserViewSet` class:
+ from myapp.permissions import IsAdminOrIsSelf
+ from rest_framework.decorators import detail_route
+
@detail_route(methods=['post'], permission_classes=[IsAdminOrIsSelf])
def set_password(self, request, pk=None):
...
@@ -122,6 +127,8 @@ The arguments to the `Route` named tuple are:
The following example will only route to the `list` and `retrieve` actions, and does not use the trailing slash convention.
+ from rest_framework.routers import Route, SimpleRouter
+
class ReadOnlyRouter(SimpleRouter):
"""
A router for read-only APIs, which doesn't use trailing slashes.
@@ -146,4 +153,4 @@ If you want to provide totally custom behavior, you can override `BaseRouter` an
You may also want to override the `get_default_base_name(self, viewset)` method, or else always explicitly set the `base_name` argument when registering your viewsets with the router.
[cite]: http://guides.rubyonrails.org/routing.html
-[route-decorators]: viewsets.html#marking-extra-actions-for-routing \ No newline at end of file
+[route-decorators]: viewsets.html#marking-extra-actions-for-routing
diff --git a/docs/api-guide/serializers.md b/docs/api-guide/serializers.md
index bbc8d019..d9fd4643 100644
--- a/docs/api-guide/serializers.md
+++ b/docs/api-guide/serializers.md
@@ -28,6 +28,8 @@ We'll declare a serializer that we can use to serialize and deserialize `Comment
Declaring a serializer looks very similar to declaring a form:
+ from rest_framework import serializers
+
class CommentSerializer(serializers.Serializer):
email = serializers.EmailField()
content = serializers.CharField(max_length=200)
@@ -59,6 +61,8 @@ We can now use `CommentSerializer` to serialize a comment, or list of comments.
At this point we've translated the model instance into Python native datatypes. To finalise the serialization process we render the data into `json`.
+ from rest_framework.renderers import JSONRenderer
+
json = JSONRenderer().render(serializer.data)
json
# '{"email": "leila@example.com", "content": "foo bar", "created": "2012-08-22T16:20:09.822"}'
@@ -67,6 +71,9 @@ At this point we've translated the model instance into Python native datatypes.
Deserialization is similar. First we parse a stream into Python native datatypes...
+ from StringIO import StringIO
+ from rest_framework.parsers import JSONParser
+
stream = StringIO(json)
data = JSONParser().parse(stream)
diff --git a/docs/api-guide/status-codes.md b/docs/api-guide/status-codes.md
index db2e059c..409f659b 100644
--- a/docs/api-guide/status-codes.md
+++ b/docs/api-guide/status-codes.md
@@ -9,6 +9,7 @@
Using bare status codes in your responses isn't recommended. REST framework includes a set of named constants that you can use to make more code more obvious and readable.
from rest_framework import status
+ from rest_framework.response import Response
def empty_view(self):
content = {'please move along': 'nothing to see here'}
diff --git a/docs/api-guide/testing.md b/docs/api-guide/testing.md
index 92f8d54a..b3880f8f 100644
--- a/docs/api-guide/testing.md
+++ b/docs/api-guide/testing.md
@@ -16,6 +16,8 @@ Extends [Django's existing `RequestFactory` class][requestfactory].
The `APIRequestFactory` class supports an almost identical API to Django's standard `RequestFactory` class. This means the that standard `.get()`, `.post()`, `.put()`, `.patch()`, `.delete()`, `.head()` and `.options()` methods are all available.
+ from rest_framework.test import APIRequestFactory
+
# Using the standard RequestFactory API to create a form POST request
factory = APIRequestFactory()
request = factory.post('/notes/', {'title': 'new idea'})
@@ -49,6 +51,8 @@ For example, using `APIRequestFactory`, you can make a form PUT request like so:
Using Django's `RequestFactory`, you'd need to explicitly encode the data yourself:
+ from django.test.client import encode_multipart, RequestFactory
+
factory = RequestFactory()
data = {'title': 'remember to email dave'}
content = encode_multipart('BoUnDaRyStRiNg', data)
@@ -72,6 +76,12 @@ To forcibly authenticate a request, use the `force_authenticate()` method.
The signature for the method is `force_authenticate(request, user=None, token=None)`. When making the call, either or both of the user and token may be set.
+For example, when forcibly authenticating using a token, you might do something like the following:
+
+ user = User.objects.get(username='olivia')
+ request = factory.get('/accounts/django-superstars/')
+ force_authenticate(request, user=user, token=user.token)
+
---
**Note**: When using `APIRequestFactory`, the object that is returned is Django's standard `HttpRequest`, and not REST framework's `Request` object, which is only generated once the view is called.
@@ -105,6 +115,8 @@ Extends [Django's existing `Client` class][client].
The `APIClient` class supports the same request interface as `APIRequestFactory`. This means the that standard `.get()`, `.post()`, `.put()`, `.patch()`, `.delete()`, `.head()` and `.options()` methods are all available. For example:
+ from rest_framework.test import APIClient
+
client = APIClient()
client.post('/notes/', {'title': 'new idea'}, format='json')
@@ -131,8 +143,11 @@ The `login` method is appropriate for testing APIs that use session authenticati
The `credentials` method can be used to set headers that will then be included on all subsequent requests by the test client.
+ from rest_framework.authtoken.models import Token
+ from rest_framework.test import APIClient
+
# Include an appropriate `Authorization:` header on all requests.
- token = Token.objects.get(username='lauren')
+ token = Token.objects.get(user__username='lauren')
client = APIClient()
client.credentials(HTTP_AUTHORIZATION='Token ' + token.key)
@@ -190,10 +205,10 @@ You can use any of REST framework's test case classes as you would for the regul
Ensure we can create a new account object.
"""
url = reverse('account-list')
- data = {'name': 'DabApps'}
+ expected = {'name': 'DabApps'}
response = self.client.post(url, data, format='json')
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
- self.assertEqual(response.data, data)
+ self.assertEqual(response.data, expected)
---
diff --git a/docs/api-guide/throttling.md b/docs/api-guide/throttling.md
index 56f32f58..42f9c228 100644
--- a/docs/api-guide/throttling.md
+++ b/docs/api-guide/throttling.md
@@ -43,6 +43,10 @@ The rate descriptions used in `DEFAULT_THROTTLE_RATES` may include `second`, `mi
You can also set the throttling policy on a per-view or per-viewset basis,
using the `APIView` class based views.
+ from rest_framework.response import Response
+ from rest_framework.throttling import UserRateThrottle
+ from rest_framework.views import APIView
+
class ExampleView(APIView):
throttle_classes = (UserRateThrottle,)
diff --git a/docs/api-guide/viewsets.md b/docs/api-guide/viewsets.md
index 9005e7cb..c4a2d61e 100644
--- a/docs/api-guide/viewsets.md
+++ b/docs/api-guide/viewsets.md
@@ -19,6 +19,12 @@ Typically, rather than explicitly registering the views in a viewset in the urlc
Let's define a simple viewset that can be used to list or retrieve all the users in the system.
+ from django.contrib.auth.models import User
+ from django.shortcuts import get_object_or_404
+ from myapps.serializers import UserSerializer
+ from rest_framework import viewsets
+ from rest_framewor.responses import Response
+
class UserViewSet(viewsets.ViewSet):
"""
A simple ViewSet that for listing or retrieving users.
@@ -41,6 +47,9 @@ If we need to, we can bind this viewset into two separate views, like so:
Typically we wouldn't do this, but would instead register the viewset with a router, and allow the urlconf to be automatically generated.
+ from myapp.views import UserViewSet
+ from rest_framework.routers import DefaultRouter
+
router = DefaultRouter()
router.register(r'users', UserViewSet)
urlpatterns = router.urls
diff --git a/docs/topics/credits.md b/docs/topics/credits.md
index e9b60074..a894ee7c 100644
--- a/docs/topics/credits.md
+++ b/docs/topics/credits.md
@@ -158,6 +158,7 @@ The following people have helped make REST framework great.
* Martin Clement - [martync]
* Jeremy Satterfield - [jsatt]
* Christopher Paolini - [chrispaolini]
+* Filipe A Ximenes - [filipeximenes]
Many thanks to everyone who's contributed to the project.
@@ -352,3 +353,4 @@ You can also contact [@_tomchristie][twitter] directly on twitter.
[martync]: https://github.com/martync
[jsatt]: https://github.com/jsatt
[chrispaolini]: https://github.com/chrispaolini
+[filipeximenes]: https://github.com/filipeximenes
diff --git a/docs/topics/release-notes.md b/docs/topics/release-notes.md
index f3bb19c6..58f85c6f 100644
--- a/docs/topics/release-notes.md
+++ b/docs/topics/release-notes.md
@@ -46,6 +46,7 @@ You can determine your currently installed version using `pip freeze`:
* `six` no longer bundled. For Django <= 1.4.1, install `six` package.
* Support customizable view name and description functions, using the `VIEW_NAME_FUNCTION` and `VIEW_DESCRIPTION_FUNCTION` settings.
* Bugfix: `?page_size=0` query parameter now falls back to default page size for view, instead of always turning pagination off.
+* Bugfix: `required=True` argument fixed for boolean serializer fields.
### 2.3.7
diff --git a/rest_framework/fields.py b/rest_framework/fields.py
index 8ae8dd4a..518ba41a 100644
--- a/rest_framework/fields.py
+++ b/rest_framework/fields.py
@@ -16,6 +16,7 @@ from django.core import validators
from django.core.exceptions import ValidationError
from django.conf import settings
from django.db.models.fields import BLANK_CHOICE_DASH
+from django.http import QueryDict
from django.forms import widgets
from django.utils.encoding import is_protected_type
from django.utils.translation import ugettext_lazy as _
@@ -392,10 +393,15 @@ class BooleanField(WritableField):
}
empty = False
- # Note: we set default to `False` in order to fill in missing value not
- # supplied by html form. TODO: Fix so that only html form input gets
- # this behavior.
- default = False
+ def field_from_native(self, data, files, field_name, into):
+ # HTML checkboxes do not explicitly represent unchecked as `False`
+ # we deal with that here...
+ if isinstance(data, QueryDict):
+ self.default = False
+
+ return super(BooleanField, self).field_from_native(
+ data, files, field_name, into
+ )
def from_native(self, value):
if value in ('true', 't', 'True', '1'):
diff --git a/rest_framework/generics.py b/rest_framework/generics.py
index bcd62bf9..8e6b8e26 100644
--- a/rest_framework/generics.py
+++ b/rest_framework/generics.py
@@ -144,7 +144,7 @@ class GenericAPIView(views.APIView):
page_query_param = self.request.QUERY_PARAMS.get(self.page_kwarg)
page = page_kwarg or page_query_param or 1
try:
- page_number = int(page)
+ page_number = strict_positive_int(page)
except ValueError:
if page == 'last':
page_number = paginator.num_pages
diff --git a/rest_framework/tests/test_fields.py b/rest_framework/tests/test_fields.py
index 6836ec86..ebccba7d 100644
--- a/rest_framework/tests/test_fields.py
+++ b/rest_framework/tests/test_fields.py
@@ -896,3 +896,12 @@ class CustomIntegerField(TestCase):
self.assertFalse(serializer.is_valid())
+class BooleanField(TestCase):
+ """
+ Tests for BooleanField
+ """
+ def test_boolean_required(self):
+ class BooleanRequiredSerializer(serializers.Serializer):
+ bool_field = serializers.BooleanField(required=True)
+
+ self.assertFalse(BooleanRequiredSerializer(data={}).is_valid())