aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorTom Christie2014-12-18 11:21:25 +0000
committerTom Christie2014-12-18 11:21:25 +0000
commitc8d88c8c8a594e3b66547a34462db4766292ea9e (patch)
tree09d1e53c9c019501b85ff8892dca4177c95a6e0b
parent47fe6977077ae33dfe2f8b6d04d81083b9b9f4d7 (diff)
parentd8803a35bd2dc8cbf4c892f68b48c72f24e83916 (diff)
downloaddjango-rest-framework-c8d88c8c8a594e3b66547a34462db4766292ea9e.tar.bz2
Merge branch 'master' into version-3.1
-rw-r--r--docs/api-guide/fields.md2
-rw-r--r--docs/api-guide/permissions.md14
-rw-r--r--docs/api-guide/relations.md2
-rw-r--r--docs/api-guide/serializers.md14
-rw-r--r--docs/topics/release-notes.md36
-rw-r--r--docs/tutorial/1-serialization.md6
-rw-r--r--docs/tutorial/4-authentication-and-permissions.md2
-rw-r--r--docs/tutorial/6-viewsets-and-routers.md4
-rw-r--r--docs/tutorial/quickstart.md2
-rw-r--r--rest_framework/__init__.py2
-rw-r--r--rest_framework/exceptions.py4
-rw-r--r--rest_framework/fields.py12
-rw-r--r--rest_framework/generics.py8
-rw-r--r--rest_framework/relations.py18
-rw-r--r--rest_framework/renderers.py16
-rw-r--r--rest_framework/request.py15
-rw-r--r--rest_framework/serializers.py51
-rwxr-xr-xsetup.py3
-rw-r--r--tests/test_bound_fields.py2
-rw-r--r--tests/test_fields.py42
-rw-r--r--tests/test_middleware.py37
-rw-r--r--tests/test_request.py7
22 files changed, 244 insertions, 55 deletions
diff --git a/docs/api-guide/fields.md b/docs/api-guide/fields.md
index e4ef1d4a..f06db56c 100644
--- a/docs/api-guide/fields.md
+++ b/docs/api-guide/fields.md
@@ -112,6 +112,8 @@ Two options are currently used in HTML form generation, `'input_type'` and `'bas
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`.
**Signature:** `BooleanField()`
diff --git a/docs/api-guide/permissions.md b/docs/api-guide/permissions.md
index 6d86b72c..8731cab0 100644
--- a/docs/api-guide/permissions.md
+++ b/docs/api-guide/permissions.md
@@ -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.
+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
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.
-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
diff --git a/docs/api-guide/relations.md b/docs/api-guide/relations.md
index a79b6ea5..e56db229 100644
--- a/docs/api-guide/relations.md
+++ b/docs/api-guide/relations.md
@@ -397,7 +397,7 @@ We could define a custom field that could be used to serialize tagged instances,
return 'Note: ' + value.text
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):
"""
diff --git a/docs/api-guide/serializers.md b/docs/api-guide/serializers.md
index 5fe6b4c2..b9f0e7bc 100644
--- a/docs/api-guide/serializers.md
+++ b/docs/api-guide/serializers.md
@@ -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:
+ from datetime import datetime
+
class Comment(object):
def __init__(self, email, content, created=None):
self.email = email
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')
@@ -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...
- from StringIO import StringIO
+ from django.utils.six import BytesIO
from rest_framework.parsers import JSONParser
- stream = StringIO(json)
+ stream = BytesIO(json)
data = JSONParser().parse(stream)
...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).
+## 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
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.
diff --git a/docs/topics/release-notes.md b/docs/topics/release-notes.md
index f00d3c54..b9216e36 100644
--- a/docs/topics/release-notes.md
+++ b/docs/topics/release-notes.md
@@ -40,9 +40,21 @@ You can determine your currently installed version using `pip freeze`:
## 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
-**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])
* 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
[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
-[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
[gh2098]: https://github.com/tomchristie/django-rest-framework/issues/2098
[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
[gh2243]: https://github.com/tomchristie/django-rest-framework/issues/2243
[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
diff --git a/docs/tutorial/1-serialization.md b/docs/tutorial/1-serialization.md
index dea43cc0..ff507a2b 100644
--- a/docs/tutorial/1-serialization.md
+++ b/docs/tutorial/1-serialization.md
@@ -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...
- # This import will use either `StringIO.StringIO` or `io.BytesIO`
- # as appropriate, depending on if we're running Python 2 or Python 3.
- from rest_framework.compat import BytesIO
+ from django.utils.six import BytesIO
stream = BytesIO(content)
data = JSONParser().parse(stream)
@@ -200,7 +198,7 @@ Open the file `snippets/serializers.py` again, and edit the `SnippetSerializer`
model = Snippet
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
>>> serializer = SnippetSerializer()
diff --git a/docs/tutorial/4-authentication-and-permissions.md b/docs/tutorial/4-authentication-and-permissions.md
index a6d27bf7..592c77e8 100644
--- a/docs/tutorial/4-authentication-and-permissions.md
+++ b/docs/tutorial/4-authentication-and-permissions.md
@@ -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.
- 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,
diff --git a/docs/tutorial/6-viewsets-and-routers.md b/docs/tutorial/6-viewsets-and-routers.md
index 816e9da6..d55a60de 100644
--- a/docs/tutorial/6-viewsets-and-routers.md
+++ b/docs/tutorial/6-viewsets-and-routers.md
@@ -44,8 +44,8 @@ Next we're going to replace the `SnippetList`, `SnippetDetail` and `SnippetHighl
snippet = self.get_object()
return Response(snippet.highlighted)
- def pre_save(self, obj):
- obj.owner = self.request.user
+ def perform_create(self, serializer):
+ 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.
diff --git a/docs/tutorial/quickstart.md b/docs/tutorial/quickstart.md
index c3f95994..a4474c34 100644
--- a/docs/tutorial/quickstart.md
+++ b/docs/tutorial/quickstart.md
@@ -19,7 +19,7 @@ Create a new Django project named `tutorial`, then start a new app called `quick
pip install djangorestframework
# 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
django-admin.py startapp quickstart
cd ..
diff --git a/rest_framework/__init__.py b/rest_framework/__init__.py
index f30f781a..6808b74b 100644
--- a/rest_framework/__init__.py
+++ b/rest_framework/__init__.py
@@ -8,7 +8,7 @@ ______ _____ _____ _____ __
"""
__title__ = 'Django REST framework'
-__version__ = '3.0.1'
+__version__ = '3.0.2'
__author__ = 'Tom Christie'
__license__ = 'BSD 2-Clause'
__copyright__ = 'Copyright 2011-2014 Tom Christie'
diff --git a/rest_framework/exceptions.py b/rest_framework/exceptions.py
index 238934db..bcfd8961 100644
--- a/rest_framework/exceptions.py
+++ b/rest_framework/exceptions.py
@@ -5,8 +5,8 @@ In addition Django's built in 403 and 404 exceptions are handled.
(`django.http.Http404` and `django.core.exceptions.PermissionDenied`)
"""
from __future__ import unicode_literals
+from django.utils import six
from django.utils.encoding import force_text
-
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import ungettext_lazy
from rest_framework import status
@@ -66,7 +66,7 @@ class ValidationError(APIException):
self.detail = _force_text_recursive(detail)
def __str__(self):
- return str(self.detail)
+ return six.text_type(self.detail)
class ParseError(APIException):
diff --git a/rest_framework/fields.py b/rest_framework/fields.py
index f3e17b18..c40dc3fb 100644
--- a/rest_framework/fields.py
+++ b/rest_framework/fields.py
@@ -184,8 +184,11 @@ class Field(object):
self.style = {} if style is None else style
self.allow_null = allow_null
- if allow_null and self.default_empty_html is empty:
- self.default_empty_html = None
+ if self.default_empty_html is not empty:
+ if not required:
+ self.default_empty_html = empty
+ elif default is not empty:
+ self.default_empty_html = default
if validators is not None:
self.validators = validators[:]
@@ -557,6 +560,11 @@ class CharField(Field):
message = self.error_messages['min_length'].format(min_length=min_length)
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):
# Test for the empty string here so that it does not get validated,
# and so that subclasses do not need to handle it explicitly
diff --git a/rest_framework/generics.py b/rest_framework/generics.py
index 3d6cf168..e6db155e 100644
--- a/rest_framework/generics.py
+++ b/rest_framework/generics.py
@@ -79,16 +79,14 @@ class GenericAPIView(views.APIView):
'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
deserializing input, and for serializing output.
"""
serializer_class = self.get_serializer_class()
- context = self.get_serializer_context()
- return serializer_class(
- instance, data=data, many=many, partial=partial, context=context
- )
+ kwargs['context'] = self.get_serializer_context()
+ return serializer_class(*args, **kwargs)
def get_pagination_serializer(self, page):
"""
diff --git a/rest_framework/relations.py b/rest_framework/relations.py
index 892ce6c1..7b119291 100644
--- a/rest_framework/relations.py
+++ b/rest_framework/relations.py
@@ -1,13 +1,15 @@
-from django.utils.encoding import smart_text
-from rest_framework.fields import get_attribute, empty, Field
-from rest_framework.reverse import reverse
-from rest_framework.utils import html
+# coding: utf-8
+from __future__ import unicode_literals
from django.core.exceptions import ObjectDoesNotExist, ImproperlyConfigured
from django.core.urlresolvers import resolve, get_script_prefix, NoReverseMatch, Resolver404
from django.db.models.query import QuerySet
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.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):
@@ -103,8 +105,8 @@ class RelatedField(Field):
def choices(self):
return dict([
(
- str(self.to_representation(item)),
- str(item)
+ six.text_type(self.to_representation(item)),
+ six.text_type(item)
)
for item in self.queryset.all()
])
@@ -364,8 +366,8 @@ class ManyRelatedField(Field):
]
return dict([
(
- str(item_representation),
- str(item) + ' - ' + str(item_representation)
+ six.text_type(item_representation),
+ six.text_type(item) + ' - ' + six.text_type(item_representation)
)
for item, item_representation in items_and_representations
])
diff --git a/rest_framework/renderers.py b/rest_framework/renderers.py
index f8d18fc6..c4de30db 100644
--- a/rest_framework/renderers.py
+++ b/rest_framework/renderers.py
@@ -435,12 +435,12 @@ class BrowsableAPIRenderer(BaseRenderer):
# serializer instance, rather than dynamically creating a new one.
if request.method == method and serializer is not None:
try:
- data = request.data
+ kwargs = {'data': request.data}
except ParseError:
- data = None
+ kwargs = {}
existing_serializer = serializer
else:
- data = None
+ kwargs = {}
existing_serializer = None
with override_method(view, request, method) as request:
@@ -460,11 +460,13 @@ class BrowsableAPIRenderer(BaseRenderer):
serializer = existing_serializer
else:
if method in ('PUT', 'PATCH'):
- serializer = view.get_serializer(instance=instance, data=data)
+ serializer = view.get_serializer(instance=instance, **kwargs)
else:
- serializer = view.get_serializer(data=data)
- if data is not None:
- serializer.is_valid()
+ serializer = view.get_serializer(**kwargs)
+
+ if hasattr(serializer, 'initial_data'):
+ serializer.is_valid()
+
form_renderer = self.form_renderer_class()
return form_renderer.render(
serializer.data,
diff --git a/rest_framework/request.py b/rest_framework/request.py
index 20e049ed..cfbbdecc 100644
--- a/rest_framework/request.py
+++ b/rest_framework/request.py
@@ -277,8 +277,12 @@ class Request(object):
Sets the user on the current request. This is necessary to maintain
compatibility with django.contrib.auth where the user property is
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._request.user = value
@property
def auth(self):
@@ -297,6 +301,7 @@ class Request(object):
request, such as an authentication token.
"""
self._auth = value
+ self._request.auth = value
@property
def successful_authenticator(self):
@@ -456,7 +461,7 @@ class Request(object):
if user_auth_tuple is not None:
self._authenticator = authenticator
- self._user, self._auth = user_auth_tuple
+ self.user, self.auth = user_auth_tuple
return
self._not_authenticated()
@@ -471,14 +476,14 @@ class Request(object):
self._authenticator = None
if api_settings.UNAUTHENTICATED_USER:
- self._user = api_settings.UNAUTHENTICATED_USER()
+ self.user = api_settings.UNAUTHENTICATED_USER()
else:
- self._user = None
+ self.user = None
if api_settings.UNAUTHENTICATED_TOKEN:
- self._auth = api_settings.UNAUTHENTICATED_TOKEN()
+ self.auth = api_settings.UNAUTHENTICATED_TOKEN()
else:
- self._auth = None
+ self.auth = None
def __getattr__(self, attr):
"""
diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py
index e9860a2f..8de22f4b 100644
--- a/rest_framework/serializers.py
+++ b/rest_framework/serializers.py
@@ -58,11 +58,31 @@ class BaseSerializer(Field):
"""
The BaseSerializer class provides a minimal class which may be used
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._initial_data = data
+ if data is not empty:
+ self.initial_data = data
self.partial = kwargs.pop('partial', False)
self._context = kwargs.pop('context', {})
kwargs.pop('many', None)
@@ -156,9 +176,14 @@ class BaseSerializer(Field):
(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'):
try:
- self._validated_data = self.run_validation(self._initial_data)
+ self._validated_data = self.run_validation(self.initial_data)
except ValidationError as exc:
self._validated_data = {}
self._errors = exc.detail
@@ -172,6 +197,16 @@ class BaseSerializer(Field):
@property
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 self.instance is not None and not getattr(self, '_errors', None):
self._data = self.to_representation(self.instance)
@@ -295,11 +330,11 @@ class Serializer(BaseSerializer):
return getattr(getattr(self, 'Meta', None), 'validators', [])
def get_initial(self):
- if self._initial_data is not None:
+ if hasattr(self, 'initial_data'):
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()
- 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
])
@@ -447,8 +482,8 @@ class ListSerializer(BaseSerializer):
self.child.bind(field_name='', parent=self)
def get_initial(self):
- if self._initial_data is not None:
- return self.to_representation(self._initial_data)
+ if hasattr(self, 'initial_data'):
+ return self.to_representation(self.initial_data)
return []
def get_value(self, dictionary):
diff --git a/setup.py b/setup.py
index 2c56cd75..50bac046 100755
--- a/setup.py
+++ b/setup.py
@@ -59,6 +59,9 @@ version = get_version('rest_framework')
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 bdist_wheel upload")
print("You probably want to also tag the version now:")
diff --git a/tests/test_bound_fields.py b/tests/test_bound_fields.py
index 469437e4..bfc54b23 100644
--- a/tests/test_bound_fields.py
+++ b/tests/test_bound_fields.py
@@ -22,7 +22,7 @@ class TestSimpleBoundField:
amount = serializers.IntegerField()
serializer = ExampleSerializer(data={'text': 'abc', 'amount': 123})
-
+ assert serializer.is_valid()
assert serializer['text'].value == 'abc'
assert serializer['text'].errors is None
assert serializer['text'].name == 'text'
diff --git a/tests/test_fields.py b/tests/test_fields.py
index c20bdd8c..04c721d3 100644
--- a/tests/test_fields.py
+++ b/tests/test_fields.py
@@ -215,6 +215,48 @@ class TestBooleanHTMLInput:
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:
def setup(self):
default = serializers.CreateOnlyDefault('2001-01-01')
diff --git a/tests/test_middleware.py b/tests/test_middleware.py
new file mode 100644
index 00000000..4c099fca
--- /dev/null
+++ b/tests/test_middleware.py
@@ -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)
diff --git a/tests/test_request.py b/tests/test_request.py
index 7cf8c327..02a9b1e2 100644
--- a/tests/test_request.py
+++ b/tests/test_request.py
@@ -224,7 +224,8 @@ class TestUserSetter(TestCase):
def setUp(self):
# Pass request object through session middleware so session is
# 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)
User.objects.create_user('ringo', 'starr@thebeatles.com', 'yellow')
@@ -244,6 +245,10 @@ class TestUserSetter(TestCase):
logout(self.request)
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):