mirror of
https://github.com/encode/django-rest-framework.git
synced 2025-05-22 13:36:12 +03:00
Merge branch 'master' into version-3.1
This commit is contained in:
commit
c8d88c8c8a
|
@ -112,6 +112,8 @@ Two options are currently used in HTML form generation, `'input_type'` and `'bas
|
||||||
|
|
||||||
A boolean representation.
|
A boolean representation.
|
||||||
|
|
||||||
|
When using HTML encoded form input be aware that omitting a value will always be treated as setting a field to `False`, even if it has a `default=True` option specified. This is because HTML checkbox inputs represent the unchecked state by omitting the value, so REST framework treats omission as if it is an empty checkbox input.
|
||||||
|
|
||||||
Corresponds to `django.db.models.fields.BooleanField`.
|
Corresponds to `django.db.models.fields.BooleanField`.
|
||||||
|
|
||||||
**Signature:** `BooleanField()`
|
**Signature:** `BooleanField()`
|
||||||
|
|
|
@ -10,12 +10,24 @@ Together with [authentication] and [throttling], permissions determine whether a
|
||||||
|
|
||||||
Permission checks are always run at the very start of the view, before any other code is allowed to proceed. Permission checks will typically use the authentication information in the `request.user` and `request.auth` properties to determine if the incoming request should be permitted.
|
Permission checks are always run at the very start of the view, before any other code is allowed to proceed. Permission checks will typically use the authentication information in the `request.user` and `request.auth` properties to determine if the incoming request should be permitted.
|
||||||
|
|
||||||
|
Permissions are used to grant or deny access different classes of users to different parts of the API.
|
||||||
|
|
||||||
|
The simplest style of permission would be to allow access to any authenticated user, and deny access to any unauthenticated user. This corresponds the `IsAuthenticated` class in REST framework.
|
||||||
|
|
||||||
|
A slightly less strict style of permission would be to allow full access to authenticated users, but allow read-only access to unauthenticated users. This corresponds to the `IsAuthenticatedOrReadOnly` class in REST framework.
|
||||||
|
|
||||||
## How permissions are determined
|
## How permissions are determined
|
||||||
|
|
||||||
Permissions in REST framework are always defined as a list of permission classes.
|
Permissions in REST framework are always defined as a list of permission classes.
|
||||||
|
|
||||||
Before running the main body of the view each permission in the list is checked.
|
Before running the main body of the view each permission in the list is checked.
|
||||||
If any permission check fails an `exceptions.PermissionDenied` exception will be raised, and the main body of the view will not run.
|
If any permission check fails an `exceptions.PermissionDenied` or `exceptions.NotAuthenticated` exception will be raised, and the main body of the view will not run.
|
||||||
|
|
||||||
|
When the permissions checks fail either a "403 Forbidden" or a "401 Unauthorized" response will be returned, according to the following rules:
|
||||||
|
|
||||||
|
* The request was successfully authenticated, but permission was denied. *— An HTTP 403 Forbidden response will be returned.*
|
||||||
|
* The request was not successfully authenticated, and the highest priority authentication class *does not* use `WWW-Authenticate` headers. *— An HTTP 403 Forbidden response will be returned.*
|
||||||
|
* The request was not successfully authenticated, and the highest priority authentication class *does* use `WWW-Authenticate` headers. *— An HTTP 401 Unauthorized response, with an appropriate `WWW-Authenticate` header will be returned.*
|
||||||
|
|
||||||
## Object level permissions
|
## Object level permissions
|
||||||
|
|
||||||
|
|
|
@ -397,7 +397,7 @@ We could define a custom field that could be used to serialize tagged instances,
|
||||||
return 'Note: ' + value.text
|
return 'Note: ' + value.text
|
||||||
raise Exception('Unexpected type of tagged object')
|
raise Exception('Unexpected type of tagged object')
|
||||||
|
|
||||||
If you need the target of the relationship to have a nested representation, you can use the required serializers inside the `.to_native()` method:
|
If you need the target of the relationship to have a nested representation, you can use the required serializers inside the `.to_representation()` method:
|
||||||
|
|
||||||
def to_representation(self, value):
|
def to_representation(self, value):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -22,11 +22,13 @@ The serializers in REST framework work very similarly to Django's `Form` and `Mo
|
||||||
|
|
||||||
Let's start by creating a simple object we can use for example purposes:
|
Let's start by creating a simple object we can use for example purposes:
|
||||||
|
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
class Comment(object):
|
class Comment(object):
|
||||||
def __init__(self, email, content, created=None):
|
def __init__(self, email, content, created=None):
|
||||||
self.email = email
|
self.email = email
|
||||||
self.content = content
|
self.content = content
|
||||||
self.created = created or datetime.datetime.now()
|
self.created = created or datetime.now()
|
||||||
|
|
||||||
comment = Comment(email='leila@example.com', content='foo bar')
|
comment = Comment(email='leila@example.com', content='foo bar')
|
||||||
|
|
||||||
|
@ -61,10 +63,10 @@ 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...
|
Deserialization is similar. First we parse a stream into Python native datatypes...
|
||||||
|
|
||||||
from StringIO import StringIO
|
from django.utils.six import BytesIO
|
||||||
from rest_framework.parsers import JSONParser
|
from rest_framework.parsers import JSONParser
|
||||||
|
|
||||||
stream = StringIO(json)
|
stream = BytesIO(json)
|
||||||
data = JSONParser().parse(stream)
|
data = JSONParser().parse(stream)
|
||||||
|
|
||||||
...then we restore those native datatypes into a dictionary of validated data.
|
...then we restore those native datatypes into a dictionary of validated data.
|
||||||
|
@ -240,6 +242,12 @@ Serializer classes can also include reusable validators that are applied to the
|
||||||
|
|
||||||
For more information see the [validators documentation](validators.md).
|
For more information see the [validators documentation](validators.md).
|
||||||
|
|
||||||
|
## Accessing the initial data and instance
|
||||||
|
|
||||||
|
When passing an initial object or queryset to a serializer instance, the object will be made available as `.instance`. If no initial object is passed then the `.instance` attribute will be `None`.
|
||||||
|
|
||||||
|
When passing data to a serializer instance, the unmodified data will be made available as `.initial_data`. If the data keyword argument is not passed then the `.initial_data` attribute will not exist.
|
||||||
|
|
||||||
## Partial updates
|
## Partial updates
|
||||||
|
|
||||||
By default, serializers must be passed values for all required fields or they will raise validation errors. You can use the `partial` argument in order to allow partial updates.
|
By default, serializers must be passed values for all required fields or they will raise validation errors. You can use the `partial` argument in order to allow partial updates.
|
||||||
|
|
|
@ -40,9 +40,21 @@ You can determine your currently installed version using `pip freeze`:
|
||||||
|
|
||||||
## 3.0.x series
|
## 3.0.x series
|
||||||
|
|
||||||
|
### 3.0.2
|
||||||
|
|
||||||
|
**Date**: [17th December 2014][3.0.2-milestone].
|
||||||
|
|
||||||
|
* Ensure `request.user` is made available to response middleware. ([#2155][gh2155])
|
||||||
|
* `Client.logout()` also cancels any existing `force_authenticate`. ([#2218][gh2218], [#2259][gh2259])
|
||||||
|
* Extra assertions and better checks to preventing incorrect serializer API use. ([#2228][gh2228], [#2234][gh2234], [#2262][gh2262], [#2263][gh2263], [#2266][gh2266], [#2267][gh2267], [#2289][gh2289], [#2291][gh2291])
|
||||||
|
* Fixed `min_length` message for `CharField`. ([#2255][gh2255])
|
||||||
|
* Fix `UnicodeDecodeError`, which can occur on serializer `repr`. ([#2270][gh2270], [#2279][gh2279])
|
||||||
|
* Fix empty HTML values when a default is provided. ([#2280][gh2280], [#2294][gh2294])
|
||||||
|
* Fix `SlugRelatedField` raising `UnicodeEncodeError` when used as a multiple choice input. ([#2290][gh2290])
|
||||||
|
|
||||||
### 3.0.1
|
### 3.0.1
|
||||||
|
|
||||||
**Date**: [December 2014][3.0.1-milestone].
|
**Date**: [11th December 2014][3.0.1-milestone].
|
||||||
|
|
||||||
* More helpful error message when the default Serializer `create()` fails. ([#2013][gh2013])
|
* More helpful error message when the default Serializer `create()` fails. ([#2013][gh2013])
|
||||||
* Raise error when attempting to save serializer if data is not valid. ([#2098][gh2098])
|
* Raise error when attempting to save serializer if data is not valid. ([#2098][gh2098])
|
||||||
|
@ -665,9 +677,11 @@ For older release notes, [please see the GitHub repo](old-release-notes).
|
||||||
[ticket-582]: https://github.com/tomchristie/django-rest-framework/issues/582
|
[ticket-582]: https://github.com/tomchristie/django-rest-framework/issues/582
|
||||||
[rfc-6266]: http://tools.ietf.org/html/rfc6266#section-4.3
|
[rfc-6266]: http://tools.ietf.org/html/rfc6266#section-4.3
|
||||||
[old-release-notes]: https://github.com/tomchristie/django-rest-framework/blob/2.4.4/docs/topics/release-notes.md#04x-series
|
[old-release-notes]: https://github.com/tomchristie/django-rest-framework/blob/2.4.4/docs/topics/release-notes.md#04x-series
|
||||||
|
|
||||||
[3.0.1-milestone]: https://github.com/tomchristie/django-rest-framework/issues?q=milestone%3A%223.0.1+Release%22
|
[3.0.1-milestone]: https://github.com/tomchristie/django-rest-framework/issues?q=milestone%3A%223.0.1+Release%22
|
||||||
|
[3.0.2-milestone]: https://github.com/tomchristie/django-rest-framework/issues?q=milestone%3A%223.0.2+Release%22
|
||||||
|
|
||||||
|
<!-- 3.0.1 -->
|
||||||
[gh2013]: https://github.com/tomchristie/django-rest-framework/issues/2013
|
[gh2013]: https://github.com/tomchristie/django-rest-framework/issues/2013
|
||||||
[gh2098]: https://github.com/tomchristie/django-rest-framework/issues/2098
|
[gh2098]: https://github.com/tomchristie/django-rest-framework/issues/2098
|
||||||
[gh2109]: https://github.com/tomchristie/django-rest-framework/issues/2109
|
[gh2109]: https://github.com/tomchristie/django-rest-framework/issues/2109
|
||||||
|
@ -697,3 +711,21 @@ For older release notes, [please see the GitHub repo](old-release-notes).
|
||||||
[gh2242]: https://github.com/tomchristie/django-rest-framework/issues/2242
|
[gh2242]: https://github.com/tomchristie/django-rest-framework/issues/2242
|
||||||
[gh2243]: https://github.com/tomchristie/django-rest-framework/issues/2243
|
[gh2243]: https://github.com/tomchristie/django-rest-framework/issues/2243
|
||||||
[gh2244]: https://github.com/tomchristie/django-rest-framework/issues/2244
|
[gh2244]: https://github.com/tomchristie/django-rest-framework/issues/2244
|
||||||
|
<!-- 3.0.2 -->
|
||||||
|
[gh2155]: https://github.com/tomchristie/django-rest-framework/issues/2155
|
||||||
|
[gh2218]: https://github.com/tomchristie/django-rest-framework/issues/2218
|
||||||
|
[gh2228]: https://github.com/tomchristie/django-rest-framework/issues/2228
|
||||||
|
[gh2234]: https://github.com/tomchristie/django-rest-framework/issues/2234
|
||||||
|
[gh2255]: https://github.com/tomchristie/django-rest-framework/issues/2255
|
||||||
|
[gh2259]: https://github.com/tomchristie/django-rest-framework/issues/2259
|
||||||
|
[gh2262]: https://github.com/tomchristie/django-rest-framework/issues/2262
|
||||||
|
[gh2263]: https://github.com/tomchristie/django-rest-framework/issues/2263
|
||||||
|
[gh2266]: https://github.com/tomchristie/django-rest-framework/issues/2266
|
||||||
|
[gh2267]: https://github.com/tomchristie/django-rest-framework/issues/2267
|
||||||
|
[gh2270]: https://github.com/tomchristie/django-rest-framework/issues/2270
|
||||||
|
[gh2279]: https://github.com/tomchristie/django-rest-framework/issues/2279
|
||||||
|
[gh2280]: https://github.com/tomchristie/django-rest-framework/issues/2280
|
||||||
|
[gh2289]: https://github.com/tomchristie/django-rest-framework/issues/2289
|
||||||
|
[gh2290]: https://github.com/tomchristie/django-rest-framework/issues/2290
|
||||||
|
[gh2291]: https://github.com/tomchristie/django-rest-framework/issues/2291
|
||||||
|
[gh2294]: https://github.com/tomchristie/django-rest-framework/issues/2294
|
||||||
|
|
|
@ -161,9 +161,7 @@ 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...
|
Deserialization is similar. First we parse a stream into Python native datatypes...
|
||||||
|
|
||||||
# This import will use either `StringIO.StringIO` or `io.BytesIO`
|
from django.utils.six import BytesIO
|
||||||
# as appropriate, depending on if we're running Python 2 or Python 3.
|
|
||||||
from rest_framework.compat import BytesIO
|
|
||||||
|
|
||||||
stream = BytesIO(content)
|
stream = BytesIO(content)
|
||||||
data = JSONParser().parse(stream)
|
data = JSONParser().parse(stream)
|
||||||
|
@ -200,7 +198,7 @@ Open the file `snippets/serializers.py` again, and edit the `SnippetSerializer`
|
||||||
model = Snippet
|
model = Snippet
|
||||||
fields = ('id', 'title', 'code', 'linenos', 'language', 'style')
|
fields = ('id', 'title', 'code', 'linenos', 'language', 'style')
|
||||||
|
|
||||||
One nice property that serializers have is that you can inspect all the fields in a serializer instance, by printing it's representation. Open the Django shell with `python manange.py shell`, then try the following:
|
One nice property that serializers have is that you can inspect all the fields in a serializer instance, by printing it's representation. Open the Django shell with `python manage.py shell`, then try the following:
|
||||||
|
|
||||||
>>> from snippets.serializers import SnippetSerializer
|
>>> from snippets.serializers import SnippetSerializer
|
||||||
>>> serializer = SnippetSerializer()
|
>>> serializer = SnippetSerializer()
|
||||||
|
|
|
@ -206,7 +206,7 @@ If we try to create a snippet without authenticating, we'll get an error:
|
||||||
|
|
||||||
We can make a successful request by including the username and password of one of the users we created earlier.
|
We can make a successful request by including the username and password of one of the users we created earlier.
|
||||||
|
|
||||||
http POST -a tom:password http://127.0.0.1:8000/snippets/ code="print 789"
|
http -a tom:password POST http://127.0.0.1:8000/snippets/ code="print 789"
|
||||||
|
|
||||||
{
|
{
|
||||||
"id": 5,
|
"id": 5,
|
||||||
|
|
|
@ -44,8 +44,8 @@ Next we're going to replace the `SnippetList`, `SnippetDetail` and `SnippetHighl
|
||||||
snippet = self.get_object()
|
snippet = self.get_object()
|
||||||
return Response(snippet.highlighted)
|
return Response(snippet.highlighted)
|
||||||
|
|
||||||
def pre_save(self, obj):
|
def perform_create(self, serializer):
|
||||||
obj.owner = self.request.user
|
serializer.save(owner=self.request.user)
|
||||||
|
|
||||||
This time we've used the `ModelViewSet` class in order to get the complete set of default read and write operations.
|
This time we've used the `ModelViewSet` class in order to get the complete set of default read and write operations.
|
||||||
|
|
||||||
|
|
|
@ -19,7 +19,7 @@ Create a new Django project named `tutorial`, then start a new app called `quick
|
||||||
pip install djangorestframework
|
pip install djangorestframework
|
||||||
|
|
||||||
# Set up a new project with a single application
|
# Set up a new project with a single application
|
||||||
django-admin.py startproject tutorial .
|
django-admin.py startproject tutorial . # Note the trailing '.' character
|
||||||
cd tutorial
|
cd tutorial
|
||||||
django-admin.py startapp quickstart
|
django-admin.py startapp quickstart
|
||||||
cd ..
|
cd ..
|
||||||
|
|
|
@ -8,7 +8,7 @@ ______ _____ _____ _____ __
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__title__ = 'Django REST framework'
|
__title__ = 'Django REST framework'
|
||||||
__version__ = '3.0.1'
|
__version__ = '3.0.2'
|
||||||
__author__ = 'Tom Christie'
|
__author__ = 'Tom Christie'
|
||||||
__license__ = 'BSD 2-Clause'
|
__license__ = 'BSD 2-Clause'
|
||||||
__copyright__ = 'Copyright 2011-2014 Tom Christie'
|
__copyright__ = 'Copyright 2011-2014 Tom Christie'
|
||||||
|
|
|
@ -5,8 +5,8 @@ In addition Django's built in 403 and 404 exceptions are handled.
|
||||||
(`django.http.Http404` and `django.core.exceptions.PermissionDenied`)
|
(`django.http.Http404` and `django.core.exceptions.PermissionDenied`)
|
||||||
"""
|
"""
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
from django.utils import six
|
||||||
from django.utils.encoding import force_text
|
from django.utils.encoding import force_text
|
||||||
|
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
from django.utils.translation import ungettext_lazy
|
from django.utils.translation import ungettext_lazy
|
||||||
from rest_framework import status
|
from rest_framework import status
|
||||||
|
@ -66,7 +66,7 @@ class ValidationError(APIException):
|
||||||
self.detail = _force_text_recursive(detail)
|
self.detail = _force_text_recursive(detail)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return str(self.detail)
|
return six.text_type(self.detail)
|
||||||
|
|
||||||
|
|
||||||
class ParseError(APIException):
|
class ParseError(APIException):
|
||||||
|
|
|
@ -184,8 +184,11 @@ class Field(object):
|
||||||
self.style = {} if style is None else style
|
self.style = {} if style is None else style
|
||||||
self.allow_null = allow_null
|
self.allow_null = allow_null
|
||||||
|
|
||||||
if allow_null and self.default_empty_html is empty:
|
if self.default_empty_html is not empty:
|
||||||
self.default_empty_html = None
|
if not required:
|
||||||
|
self.default_empty_html = empty
|
||||||
|
elif default is not empty:
|
||||||
|
self.default_empty_html = default
|
||||||
|
|
||||||
if validators is not None:
|
if validators is not None:
|
||||||
self.validators = validators[:]
|
self.validators = validators[:]
|
||||||
|
@ -557,6 +560,11 @@ class CharField(Field):
|
||||||
message = self.error_messages['min_length'].format(min_length=min_length)
|
message = self.error_messages['min_length'].format(min_length=min_length)
|
||||||
self.validators.append(MinLengthValidator(min_length, message=message))
|
self.validators.append(MinLengthValidator(min_length, message=message))
|
||||||
|
|
||||||
|
if self.allow_null and (not self.allow_blank) and (self.default is empty):
|
||||||
|
# HTML input cannot represent `None` values, so we need to
|
||||||
|
# forcibly coerce empty HTML values to `None` if `allow_null=True`.
|
||||||
|
self.default_empty_html = None
|
||||||
|
|
||||||
def run_validation(self, data=empty):
|
def run_validation(self, data=empty):
|
||||||
# Test for the empty string here so that it does not get validated,
|
# Test for the empty string here so that it does not get validated,
|
||||||
# and so that subclasses do not need to handle it explicitly
|
# and so that subclasses do not need to handle it explicitly
|
||||||
|
|
|
@ -79,16 +79,14 @@ class GenericAPIView(views.APIView):
|
||||||
'view': self
|
'view': self
|
||||||
}
|
}
|
||||||
|
|
||||||
def get_serializer(self, instance=None, data=None, many=False, partial=False):
|
def get_serializer(self, *args, **kwargs):
|
||||||
"""
|
"""
|
||||||
Return the serializer instance that should be used for validating and
|
Return the serializer instance that should be used for validating and
|
||||||
deserializing input, and for serializing output.
|
deserializing input, and for serializing output.
|
||||||
"""
|
"""
|
||||||
serializer_class = self.get_serializer_class()
|
serializer_class = self.get_serializer_class()
|
||||||
context = self.get_serializer_context()
|
kwargs['context'] = self.get_serializer_context()
|
||||||
return serializer_class(
|
return serializer_class(*args, **kwargs)
|
||||||
instance, data=data, many=many, partial=partial, context=context
|
|
||||||
)
|
|
||||||
|
|
||||||
def get_pagination_serializer(self, page):
|
def get_pagination_serializer(self, page):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -1,13 +1,15 @@
|
||||||
from django.utils.encoding import smart_text
|
# coding: utf-8
|
||||||
from rest_framework.fields import get_attribute, empty, Field
|
from __future__ import unicode_literals
|
||||||
from rest_framework.reverse import reverse
|
|
||||||
from rest_framework.utils import html
|
|
||||||
from django.core.exceptions import ObjectDoesNotExist, ImproperlyConfigured
|
from django.core.exceptions import ObjectDoesNotExist, ImproperlyConfigured
|
||||||
from django.core.urlresolvers import resolve, get_script_prefix, NoReverseMatch, Resolver404
|
from django.core.urlresolvers import resolve, get_script_prefix, NoReverseMatch, Resolver404
|
||||||
from django.db.models.query import QuerySet
|
from django.db.models.query import QuerySet
|
||||||
from django.utils import six
|
from django.utils import six
|
||||||
|
from django.utils.encoding import smart_text
|
||||||
from django.utils.six.moves.urllib import parse as urlparse
|
from django.utils.six.moves.urllib import parse as urlparse
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
from rest_framework.fields import get_attribute, empty, Field
|
||||||
|
from rest_framework.reverse import reverse
|
||||||
|
from rest_framework.utils import html
|
||||||
|
|
||||||
|
|
||||||
class PKOnlyObject(object):
|
class PKOnlyObject(object):
|
||||||
|
@ -103,8 +105,8 @@ class RelatedField(Field):
|
||||||
def choices(self):
|
def choices(self):
|
||||||
return dict([
|
return dict([
|
||||||
(
|
(
|
||||||
str(self.to_representation(item)),
|
six.text_type(self.to_representation(item)),
|
||||||
str(item)
|
six.text_type(item)
|
||||||
)
|
)
|
||||||
for item in self.queryset.all()
|
for item in self.queryset.all()
|
||||||
])
|
])
|
||||||
|
@ -364,8 +366,8 @@ class ManyRelatedField(Field):
|
||||||
]
|
]
|
||||||
return dict([
|
return dict([
|
||||||
(
|
(
|
||||||
str(item_representation),
|
six.text_type(item_representation),
|
||||||
str(item) + ' - ' + str(item_representation)
|
six.text_type(item) + ' - ' + six.text_type(item_representation)
|
||||||
)
|
)
|
||||||
for item, item_representation in items_and_representations
|
for item, item_representation in items_and_representations
|
||||||
])
|
])
|
||||||
|
|
|
@ -435,12 +435,12 @@ class BrowsableAPIRenderer(BaseRenderer):
|
||||||
# serializer instance, rather than dynamically creating a new one.
|
# serializer instance, rather than dynamically creating a new one.
|
||||||
if request.method == method and serializer is not None:
|
if request.method == method and serializer is not None:
|
||||||
try:
|
try:
|
||||||
data = request.data
|
kwargs = {'data': request.data}
|
||||||
except ParseError:
|
except ParseError:
|
||||||
data = None
|
kwargs = {}
|
||||||
existing_serializer = serializer
|
existing_serializer = serializer
|
||||||
else:
|
else:
|
||||||
data = None
|
kwargs = {}
|
||||||
existing_serializer = None
|
existing_serializer = None
|
||||||
|
|
||||||
with override_method(view, request, method) as request:
|
with override_method(view, request, method) as request:
|
||||||
|
@ -460,11 +460,13 @@ class BrowsableAPIRenderer(BaseRenderer):
|
||||||
serializer = existing_serializer
|
serializer = existing_serializer
|
||||||
else:
|
else:
|
||||||
if method in ('PUT', 'PATCH'):
|
if method in ('PUT', 'PATCH'):
|
||||||
serializer = view.get_serializer(instance=instance, data=data)
|
serializer = view.get_serializer(instance=instance, **kwargs)
|
||||||
else:
|
else:
|
||||||
serializer = view.get_serializer(data=data)
|
serializer = view.get_serializer(**kwargs)
|
||||||
if data is not None:
|
|
||||||
|
if hasattr(serializer, 'initial_data'):
|
||||||
serializer.is_valid()
|
serializer.is_valid()
|
||||||
|
|
||||||
form_renderer = self.form_renderer_class()
|
form_renderer = self.form_renderer_class()
|
||||||
return form_renderer.render(
|
return form_renderer.render(
|
||||||
serializer.data,
|
serializer.data,
|
||||||
|
|
|
@ -277,8 +277,12 @@ class Request(object):
|
||||||
Sets the user on the current request. This is necessary to maintain
|
Sets the user on the current request. This is necessary to maintain
|
||||||
compatibility with django.contrib.auth where the user property is
|
compatibility with django.contrib.auth where the user property is
|
||||||
set in the login and logout functions.
|
set in the login and logout functions.
|
||||||
|
|
||||||
|
Note that we also set the user on Django's underlying `HttpRequest`
|
||||||
|
instance, ensuring that it is available to any middleware in the stack.
|
||||||
"""
|
"""
|
||||||
self._user = value
|
self._user = value
|
||||||
|
self._request.user = value
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def auth(self):
|
def auth(self):
|
||||||
|
@ -297,6 +301,7 @@ class Request(object):
|
||||||
request, such as an authentication token.
|
request, such as an authentication token.
|
||||||
"""
|
"""
|
||||||
self._auth = value
|
self._auth = value
|
||||||
|
self._request.auth = value
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def successful_authenticator(self):
|
def successful_authenticator(self):
|
||||||
|
@ -456,7 +461,7 @@ class Request(object):
|
||||||
|
|
||||||
if user_auth_tuple is not None:
|
if user_auth_tuple is not None:
|
||||||
self._authenticator = authenticator
|
self._authenticator = authenticator
|
||||||
self._user, self._auth = user_auth_tuple
|
self.user, self.auth = user_auth_tuple
|
||||||
return
|
return
|
||||||
|
|
||||||
self._not_authenticated()
|
self._not_authenticated()
|
||||||
|
@ -471,14 +476,14 @@ class Request(object):
|
||||||
self._authenticator = None
|
self._authenticator = None
|
||||||
|
|
||||||
if api_settings.UNAUTHENTICATED_USER:
|
if api_settings.UNAUTHENTICATED_USER:
|
||||||
self._user = api_settings.UNAUTHENTICATED_USER()
|
self.user = api_settings.UNAUTHENTICATED_USER()
|
||||||
else:
|
else:
|
||||||
self._user = None
|
self.user = None
|
||||||
|
|
||||||
if api_settings.UNAUTHENTICATED_TOKEN:
|
if api_settings.UNAUTHENTICATED_TOKEN:
|
||||||
self._auth = api_settings.UNAUTHENTICATED_TOKEN()
|
self.auth = api_settings.UNAUTHENTICATED_TOKEN()
|
||||||
else:
|
else:
|
||||||
self._auth = None
|
self.auth = None
|
||||||
|
|
||||||
def __getattr__(self, attr):
|
def __getattr__(self, attr):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -58,11 +58,31 @@ class BaseSerializer(Field):
|
||||||
"""
|
"""
|
||||||
The BaseSerializer class provides a minimal class which may be used
|
The BaseSerializer class provides a minimal class which may be used
|
||||||
for writing custom serializer implementations.
|
for writing custom serializer implementations.
|
||||||
|
|
||||||
|
Note that we strongly restrict the ordering of operations/properties
|
||||||
|
that may be used on the serializer in order to enforce correct usage.
|
||||||
|
|
||||||
|
In particular, if a `data=` argument is passed then:
|
||||||
|
|
||||||
|
.is_valid() - Available.
|
||||||
|
.initial_data - Available.
|
||||||
|
.validated_data - Only available after calling `is_valid()`
|
||||||
|
.errors - Only available after calling `is_valid()`
|
||||||
|
.data - Only available after calling `is_valid()`
|
||||||
|
|
||||||
|
If a `data=` argument is not passed then:
|
||||||
|
|
||||||
|
.is_valid() - Not available.
|
||||||
|
.initial_data - Not available.
|
||||||
|
.validated_data - Not available.
|
||||||
|
.errors - Not available.
|
||||||
|
.data - Available.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, instance=None, data=None, **kwargs):
|
def __init__(self, instance=None, data=empty, **kwargs):
|
||||||
self.instance = instance
|
self.instance = instance
|
||||||
self._initial_data = data
|
if data is not empty:
|
||||||
|
self.initial_data = data
|
||||||
self.partial = kwargs.pop('partial', False)
|
self.partial = kwargs.pop('partial', False)
|
||||||
self._context = kwargs.pop('context', {})
|
self._context = kwargs.pop('context', {})
|
||||||
kwargs.pop('many', None)
|
kwargs.pop('many', None)
|
||||||
|
@ -156,9 +176,14 @@ class BaseSerializer(Field):
|
||||||
(self.__class__.__module__, self.__class__.__name__)
|
(self.__class__.__module__, self.__class__.__name__)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
assert hasattr(self, 'initial_data'), (
|
||||||
|
'Cannot call `.is_valid()` as no `data=` keyword argument was'
|
||||||
|
'passed when instantiating the serializer instance.'
|
||||||
|
)
|
||||||
|
|
||||||
if not hasattr(self, '_validated_data'):
|
if not hasattr(self, '_validated_data'):
|
||||||
try:
|
try:
|
||||||
self._validated_data = self.run_validation(self._initial_data)
|
self._validated_data = self.run_validation(self.initial_data)
|
||||||
except ValidationError as exc:
|
except ValidationError as exc:
|
||||||
self._validated_data = {}
|
self._validated_data = {}
|
||||||
self._errors = exc.detail
|
self._errors = exc.detail
|
||||||
|
@ -172,6 +197,16 @@ class BaseSerializer(Field):
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def data(self):
|
def data(self):
|
||||||
|
if hasattr(self, 'initial_data') and not hasattr(self, '_validated_data'):
|
||||||
|
msg = (
|
||||||
|
'When a serializer is passed a `data` keyword argument you '
|
||||||
|
'must call `.is_valid()` before attempting to access the '
|
||||||
|
'serialized `.data` representation.\n'
|
||||||
|
'You should either call `.is_valid()` first, '
|
||||||
|
'or access `.initial_data` instead.'
|
||||||
|
)
|
||||||
|
raise AssertionError(msg)
|
||||||
|
|
||||||
if not hasattr(self, '_data'):
|
if not hasattr(self, '_data'):
|
||||||
if self.instance is not None and not getattr(self, '_errors', None):
|
if self.instance is not None and not getattr(self, '_errors', None):
|
||||||
self._data = self.to_representation(self.instance)
|
self._data = self.to_representation(self.instance)
|
||||||
|
@ -295,11 +330,11 @@ class Serializer(BaseSerializer):
|
||||||
return getattr(getattr(self, 'Meta', None), 'validators', [])
|
return getattr(getattr(self, 'Meta', None), 'validators', [])
|
||||||
|
|
||||||
def get_initial(self):
|
def get_initial(self):
|
||||||
if self._initial_data is not None:
|
if hasattr(self, 'initial_data'):
|
||||||
return OrderedDict([
|
return OrderedDict([
|
||||||
(field_name, field.get_value(self._initial_data))
|
(field_name, field.get_value(self.initial_data))
|
||||||
for field_name, field in self.fields.items()
|
for field_name, field in self.fields.items()
|
||||||
if field.get_value(self._initial_data) is not empty
|
if field.get_value(self.initial_data) is not empty
|
||||||
and not field.read_only
|
and not field.read_only
|
||||||
])
|
])
|
||||||
|
|
||||||
|
@ -447,8 +482,8 @@ class ListSerializer(BaseSerializer):
|
||||||
self.child.bind(field_name='', parent=self)
|
self.child.bind(field_name='', parent=self)
|
||||||
|
|
||||||
def get_initial(self):
|
def get_initial(self):
|
||||||
if self._initial_data is not None:
|
if hasattr(self, 'initial_data'):
|
||||||
return self.to_representation(self._initial_data)
|
return self.to_representation(self.initial_data)
|
||||||
return []
|
return []
|
||||||
|
|
||||||
def get_value(self, dictionary):
|
def get_value(self, dictionary):
|
||||||
|
|
3
setup.py
3
setup.py
|
@ -59,6 +59,9 @@ version = get_version('rest_framework')
|
||||||
|
|
||||||
|
|
||||||
if sys.argv[-1] == 'publish':
|
if sys.argv[-1] == 'publish':
|
||||||
|
if os.system("pip freeze | grep wheel"):
|
||||||
|
print("wheel not installed.\nUse `pip install wheel`.\nExiting.")
|
||||||
|
sys.exit()
|
||||||
os.system("python setup.py sdist upload")
|
os.system("python setup.py sdist upload")
|
||||||
os.system("python setup.py bdist_wheel upload")
|
os.system("python setup.py bdist_wheel upload")
|
||||||
print("You probably want to also tag the version now:")
|
print("You probably want to also tag the version now:")
|
||||||
|
|
|
@ -22,7 +22,7 @@ class TestSimpleBoundField:
|
||||||
amount = serializers.IntegerField()
|
amount = serializers.IntegerField()
|
||||||
|
|
||||||
serializer = ExampleSerializer(data={'text': 'abc', 'amount': 123})
|
serializer = ExampleSerializer(data={'text': 'abc', 'amount': 123})
|
||||||
|
assert serializer.is_valid()
|
||||||
assert serializer['text'].value == 'abc'
|
assert serializer['text'].value == 'abc'
|
||||||
assert serializer['text'].errors is None
|
assert serializer['text'].errors is None
|
||||||
assert serializer['text'].name == 'text'
|
assert serializer['text'].name == 'text'
|
||||||
|
|
|
@ -215,6 +215,48 @@ class TestBooleanHTMLInput:
|
||||||
assert serializer.validated_data == {'archived': False}
|
assert serializer.validated_data == {'archived': False}
|
||||||
|
|
||||||
|
|
||||||
|
class MockHTMLDict(dict):
|
||||||
|
"""
|
||||||
|
This class mocks up a dictionary like object, that behaves
|
||||||
|
as if it was returned for multipart or urlencoded data.
|
||||||
|
"""
|
||||||
|
getlist = None
|
||||||
|
|
||||||
|
|
||||||
|
class TestCharHTMLInput:
|
||||||
|
def test_empty_html_checkbox(self):
|
||||||
|
class TestSerializer(serializers.Serializer):
|
||||||
|
message = serializers.CharField(default='happy')
|
||||||
|
|
||||||
|
serializer = TestSerializer(data=MockHTMLDict())
|
||||||
|
assert serializer.is_valid()
|
||||||
|
assert serializer.validated_data == {'message': 'happy'}
|
||||||
|
|
||||||
|
def test_empty_html_checkbox_allow_null(self):
|
||||||
|
class TestSerializer(serializers.Serializer):
|
||||||
|
message = serializers.CharField(allow_null=True)
|
||||||
|
|
||||||
|
serializer = TestSerializer(data=MockHTMLDict())
|
||||||
|
assert serializer.is_valid()
|
||||||
|
assert serializer.validated_data == {'message': None}
|
||||||
|
|
||||||
|
def test_empty_html_checkbox_allow_null_allow_blank(self):
|
||||||
|
class TestSerializer(serializers.Serializer):
|
||||||
|
message = serializers.CharField(allow_null=True, allow_blank=True)
|
||||||
|
|
||||||
|
serializer = TestSerializer(data=MockHTMLDict({}))
|
||||||
|
assert serializer.is_valid()
|
||||||
|
assert serializer.validated_data == {'message': ''}
|
||||||
|
|
||||||
|
def test_empty_html_required_false(self):
|
||||||
|
class TestSerializer(serializers.Serializer):
|
||||||
|
message = serializers.CharField(required=False)
|
||||||
|
|
||||||
|
serializer = TestSerializer(data=MockHTMLDict())
|
||||||
|
assert serializer.is_valid()
|
||||||
|
assert serializer.validated_data == {}
|
||||||
|
|
||||||
|
|
||||||
class TestCreateOnlyDefault:
|
class TestCreateOnlyDefault:
|
||||||
def setup(self):
|
def setup(self):
|
||||||
default = serializers.CreateOnlyDefault('2001-01-01')
|
default = serializers.CreateOnlyDefault('2001-01-01')
|
||||||
|
|
37
tests/test_middleware.py
Normal file
37
tests/test_middleware.py
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
|
||||||
|
from django.conf.urls import patterns, url
|
||||||
|
from django.contrib.auth.models import User
|
||||||
|
from rest_framework.authentication import TokenAuthentication
|
||||||
|
from rest_framework.authtoken.models import Token
|
||||||
|
from rest_framework.test import APITestCase
|
||||||
|
from rest_framework.views import APIView
|
||||||
|
|
||||||
|
|
||||||
|
urlpatterns = patterns(
|
||||||
|
'',
|
||||||
|
url(r'^$', APIView.as_view(authentication_classes=(TokenAuthentication,))),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class MyMiddleware(object):
|
||||||
|
|
||||||
|
def process_response(self, request, response):
|
||||||
|
assert hasattr(request, 'user'), '`user` is not set on request'
|
||||||
|
assert request.user.is_authenticated(), '`user` is not authenticated'
|
||||||
|
return response
|
||||||
|
|
||||||
|
|
||||||
|
class TestMiddleware(APITestCase):
|
||||||
|
|
||||||
|
urls = 'tests.test_middleware'
|
||||||
|
|
||||||
|
def test_middleware_can_access_user_when_processing_response(self):
|
||||||
|
user = User.objects.create_user('john', 'john@example.com', 'password')
|
||||||
|
key = 'abcd1234'
|
||||||
|
Token.objects.create(key=key, user=user)
|
||||||
|
|
||||||
|
with self.settings(
|
||||||
|
MIDDLEWARE_CLASSES=('tests.test_middleware.MyMiddleware',)
|
||||||
|
):
|
||||||
|
auth = 'Token ' + key
|
||||||
|
self.client.get('/', HTTP_AUTHORIZATION=auth)
|
|
@ -224,7 +224,8 @@ class TestUserSetter(TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
# Pass request object through session middleware so session is
|
# Pass request object through session middleware so session is
|
||||||
# available to login and logout functions
|
# available to login and logout functions
|
||||||
self.request = Request(factory.get('/'))
|
self.wrapped_request = factory.get('/')
|
||||||
|
self.request = Request(self.wrapped_request)
|
||||||
SessionMiddleware().process_request(self.request)
|
SessionMiddleware().process_request(self.request)
|
||||||
|
|
||||||
User.objects.create_user('ringo', 'starr@thebeatles.com', 'yellow')
|
User.objects.create_user('ringo', 'starr@thebeatles.com', 'yellow')
|
||||||
|
@ -244,6 +245,10 @@ class TestUserSetter(TestCase):
|
||||||
logout(self.request)
|
logout(self.request)
|
||||||
self.assertTrue(self.request.user.is_anonymous())
|
self.assertTrue(self.request.user.is_anonymous())
|
||||||
|
|
||||||
|
def test_logged_in_user_is_set_on_wrapped_request(self):
|
||||||
|
login(self.request, self.user)
|
||||||
|
self.assertEqual(self.wrapped_request.user, self.user)
|
||||||
|
|
||||||
|
|
||||||
class TestAuthSetter(TestCase):
|
class TestAuthSetter(TestCase):
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user