Merge master

This commit is contained in:
Tom Christie 2016-09-29 09:58:06 +01:00
commit c2cec78bd4
25 changed files with 192 additions and 31 deletions

View File

@ -24,9 +24,10 @@ The initial aim is to provide a single full-time position on REST framework.
<a href="http://jobs.rover.com/"><img src="https://raw.githubusercontent.com/tomchristie/django-rest-framework/master/docs/img/premium/rover-readme.png"/></a> <a href="http://jobs.rover.com/"><img src="https://raw.githubusercontent.com/tomchristie/django-rest-framework/master/docs/img/premium/rover-readme.png"/></a>
<a href="https://getsentry.com/welcome/"><img src="https://raw.githubusercontent.com/tomchristie/django-rest-framework/master/docs/img/premium/sentry-readme.png"/></a> <a href="https://getsentry.com/welcome/"><img src="https://raw.githubusercontent.com/tomchristie/django-rest-framework/master/docs/img/premium/sentry-readme.png"/></a>
<a href="https://getstream.io/?utm_source=drf&utm_medium=banner&utm_campaign=drf"><img src="https://raw.githubusercontent.com/tomchristie/django-rest-framework/master/docs/img/premium/stream-readme.png"/></a> <a href="https://getstream.io/?utm_source=drf&utm_medium=banner&utm_campaign=drf"><img src="https://raw.githubusercontent.com/tomchristie/django-rest-framework/master/docs/img/premium/stream-readme.png"/></a>
<a href="http://www.machinalis.com/#services"><img src="https://raw.githubusercontent.com/tomchristie/django-rest-framework/master/docs/img/premium/machinalis-readme.png"/></a>
</p> </p>
*Many thanks to all our [wonderful sponsors][sponsors], and in particular to our premium backers, [Rover](http://jobs.rover.com/), [Sentry](https://getsentry.com/welcome/), and [Stream](https://getstream.io/?utm_source=drf&utm_medium=banner&utm_campaign=drf).* *Many thanks to all our [wonderful sponsors][sponsors], and in particular to our premium backers, [Rover](http://jobs.rover.com/), [Sentry](https://getsentry.com/welcome/), [Stream](https://getstream.io/?utm_source=drf&utm_medium=banner&utm_campaign=drf), and [Machinalis](http://www.machinalis.com/#services).*
--- ---

View File

@ -148,7 +148,7 @@ For clients to authenticate, the token key should be included in the `Authorizat
If successfully authenticated, `TokenAuthentication` provides the following credentials. If successfully authenticated, `TokenAuthentication` provides the following credentials.
* `request.user` will be a Django `User` instance. * `request.user` will be a Django `User` instance.
* `request.auth` will be a `rest_framework.authtoken.models.BasicToken` instance. * `request.auth` will be a `rest_framework.authtoken.models.Token` instance.
Unauthenticated responses that are denied permission will result in an `HTTP 401 Unauthorized` response with an appropriate WWW-Authenticate header. For example: Unauthenticated responses that are denied permission will result in an `HTTP 401 Unauthorized` response with an appropriate WWW-Authenticate header. For example:

View File

@ -330,7 +330,8 @@ For example, if you need to lookup objects based on multiple fields in the URL c
queryset = self.filter_queryset(queryset) # Apply any filter backends queryset = self.filter_queryset(queryset) # Apply any filter backends
filter = {} filter = {}
for field in self.lookup_fields: for field in self.lookup_fields:
filter[field] = self.kwargs[field] if self.kwargs[field]: # Ignore empty fields.
filter[field] = self.kwargs[field]
return get_object_or_404(queryset, **filter) # Lookup the object return get_object_or_404(queryset, **filter) # Lookup the object
You can then simply apply this mixin to a view or viewset anytime you need to apply the custom behavior. You can then simply apply this mixin to a view or viewset anytime you need to apply the custom behavior.

View File

@ -442,7 +442,7 @@ Declaring a `ModelSerializer` looks like this:
By default, all the model fields on the class will be mapped to a corresponding serializer fields. By default, all the model fields on the class will be mapped to a corresponding serializer fields.
Any relationships such as foreign keys on the model will be mapped to `PrimaryKeyRelatedField`. Reverse relationships are not included by default unless explicitly included as described below. Any relationships such as foreign keys on the model will be mapped to `PrimaryKeyRelatedField`. Reverse relationships are not included by default unless explicitly included as specified in the [serializer relations][relations] documentation.
#### Inspecting a `ModelSerializer` #### Inspecting a `ModelSerializer`

View File

@ -188,7 +188,7 @@ The following is an example of a rate throttle, that will randomly throttle 1 in
class RandomRateThrottle(throttling.BaseThrottle): class RandomRateThrottle(throttling.BaseThrottle):
def allow_request(self, request, view): def allow_request(self, request, view):
return random.randint(1, 10) == 1 return random.randint(1, 10) != 1
[cite]: https://dev.twitter.com/docs/error-codes-responses [cite]: https://dev.twitter.com/docs/error-codes-responses
[permissions]: permissions.md [permissions]: permissions.md

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 52 KiB

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 20 KiB

View File

@ -74,10 +74,11 @@ The initial aim is to provide a single full-time position on REST framework.
<li><a href="http://jobs.rover.com/" style="background-image: url(https://fund-rest-framework.s3.amazonaws.com/rover_130x130.png)">Rover.com</a></li> <li><a href="http://jobs.rover.com/" style="background-image: url(https://fund-rest-framework.s3.amazonaws.com/rover_130x130.png)">Rover.com</a></li>
<li><a href="https://getsentry.com/welcome/" style="background-image: url(https://fund-rest-framework.s3.amazonaws.com/sentry130.png)">Sentry</a></li> <li><a href="https://getsentry.com/welcome/" style="background-image: url(https://fund-rest-framework.s3.amazonaws.com/sentry130.png)">Sentry</a></li>
<li><a href="https://getstream.io/?utm_source=drf&utm_medium=banner&utm_campaign=drf" style="background-image: url(https://fund-rest-framework.s3.amazonaws.com/stream-130.png)">Stream</a></li> <li><a href="https://getstream.io/?utm_source=drf&utm_medium=banner&utm_campaign=drf" style="background-image: url(https://fund-rest-framework.s3.amazonaws.com/stream-130.png)">Stream</a></li>
<li><a href="http://www.machinalis.com/#services" style="background-image: url(https://fund-rest-framework.s3.amazonaws.com/Machinalis130.png)">Machinalis</a></li>
</ul> </ul>
<div style="clear: both; padding-bottom: 20px;"></div> <div style="clear: both; padding-bottom: 20px;"></div>
*Many thanks to all our [wonderful sponsors][sponsors], and in particular to our premium backers, [Rover](http://jobs.rover.com/), [Sentry](https://getsentry.com/welcome/), and [Stream](https://getstream.io/?utm_source=drf&utm_medium=banner&utm_campaign=drf).* *Many thanks to all our [wonderful sponsors][sponsors], and in particular to our premium backers, [Rover](http://jobs.rover.com/), [Sentry](https://getsentry.com/welcome/), [Stream](https://getstream.io/?utm_source=drf&utm_medium=banner&utm_campaign=drf), and [Machinalis](http://www.machinalis.com/#services).*
--- ---

View File

@ -40,6 +40,27 @@ You can determine your currently installed version using `pip freeze`:
## 3.4.x series ## 3.4.x series
### 3.4.7
**Date**: [21st September 2016][3.4.7-milestone]
* Fallback behavior for request parsing when request.POST already accessed. ([#3951][gh3951], [#4500][gh4500])
* Fix regression of `RegexField`. ([#4489][gh4489], [#4490][gh4490], [#2617][gh2617])
* Missing comma in `admin.html` causing CSRF error. ([#4472][gh4472], [#4473][gh4473])
* Fix response rendering with empty context. ([#4495][gh4495])
* Fix indentation regression in API listing. ([#4493][gh4493])
* Fixed an issue where the incorrect value is set to `ResolverMatch.func_name` of api_view decorated view. ([#4465][gh4465], [#4462][gh4462])
* Fix `APIClient.get()` when path contains unicode arguments ([#4458][gh4458])
### 3.4.6
**Date**: [23rd August 2016][3.4.6-milestone]
* Fix malformed Javascript in browsable API. ([#4435][gh4435])
* Skip HiddenField from Schema fields. ([#4425][gh4425], [#4429][gh4429])
* Improve Create to show the original exception traceback. ([#3508][gh3508])
* Fix `AdminRenderer` display of PK only related fields. ([#4419][gh4419], [#4423][gh4423])
### 3.4.5 ### 3.4.5
**Date**: [19th August 2016][3.4.5-milestone] **Date**: [19th August 2016][3.4.5-milestone]
@ -573,6 +594,11 @@ For older release notes, [please see the version 2.x documentation][old-release-
[3.4.3-milestone]: https://github.com/tomchristie/django-rest-framework/issues?q=milestone%3A%223.4.3+Release%22 [3.4.3-milestone]: https://github.com/tomchristie/django-rest-framework/issues?q=milestone%3A%223.4.3+Release%22
[3.4.4-milestone]: https://github.com/tomchristie/django-rest-framework/issues?q=milestone%3A%223.4.4+Release%22 [3.4.4-milestone]: https://github.com/tomchristie/django-rest-framework/issues?q=milestone%3A%223.4.4+Release%22
[3.4.5-milestone]: https://github.com/tomchristie/django-rest-framework/issues?q=milestone%3A%223.4.5+Release%22 [3.4.5-milestone]: https://github.com/tomchristie/django-rest-framework/issues?q=milestone%3A%223.4.5+Release%22
<<<<<<< HEAD
=======
[3.4.6-milestone]: https://github.com/tomchristie/django-rest-framework/issues?q=milestone%3A%223.4.6+Release%22
[3.4.7-milestone]: https://github.com/tomchristie/django-rest-framework/issues?q=milestone%3A%223.4.7+Release%22
>>>>>>> master
<!-- 3.0.1 --> <!-- 3.0.1 -->
[gh2013]: https://github.com/tomchristie/django-rest-framework/issues/2013 [gh2013]: https://github.com/tomchristie/django-rest-framework/issues/2013
@ -1090,3 +1116,27 @@ For older release notes, [please see the version 2.x documentation][old-release-
[gh4403]: https://github.com/tomchristie/django-rest-framework/issues/4403 [gh4403]: https://github.com/tomchristie/django-rest-framework/issues/4403
[gh4404]: https://github.com/tomchristie/django-rest-framework/issues/4404 [gh4404]: https://github.com/tomchristie/django-rest-framework/issues/4404
[gh4412]: https://github.com/tomchristie/django-rest-framework/issues/4412 [gh4412]: https://github.com/tomchristie/django-rest-framework/issues/4412
<!-- 3.4.6 -->
[gh4435]: https://github.com/tomchristie/django-rest-framework/issues/4435
[gh4425]: https://github.com/tomchristie/django-rest-framework/issues/4425
[gh4429]: https://github.com/tomchristie/django-rest-framework/issues/4429
[gh3508]: https://github.com/tomchristie/django-rest-framework/issues/3508
[gh4419]: https://github.com/tomchristie/django-rest-framework/issues/4419
[gh4423]: https://github.com/tomchristie/django-rest-framework/issues/4423
<!-- 3.4.7 -->
[gh3951]: https://github.com/tomchristie/django-rest-framework/issues/3951
[gh4500]: https://github.com/tomchristie/django-rest-framework/issues/4500
[gh4489]: https://github.com/tomchristie/django-rest-framework/issues/4489
[gh4490]: https://github.com/tomchristie/django-rest-framework/issues/4490
[gh2617]: https://github.com/tomchristie/django-rest-framework/issues/2617
[gh4472]: https://github.com/tomchristie/django-rest-framework/issues/4472
[gh4473]: https://github.com/tomchristie/django-rest-framework/issues/4473
[gh4495]: https://github.com/tomchristie/django-rest-framework/issues/4495
[gh4493]: https://github.com/tomchristie/django-rest-framework/issues/4493
[gh4465]: https://github.com/tomchristie/django-rest-framework/issues/4465
[gh4462]: https://github.com/tomchristie/django-rest-framework/issues/4462
[gh4458]: https://github.com/tomchristie/django-rest-framework/issues/4458

View File

@ -8,7 +8,7 @@ ______ _____ _____ _____ __
""" """
__title__ = 'Django REST framework' __title__ = 'Django REST framework'
__version__ = '3.4.5' __version__ = '3.5.0'
__author__ = 'Tom Christie' __author__ = 'Tom Christie'
__license__ = 'BSD 2-Clause' __license__ = 'BSD 2-Clause'
__copyright__ = 'Copyright 2011-2016 Tom Christie' __copyright__ = 'Copyright 2011-2016 Tom Christie'

View File

@ -55,6 +55,7 @@ def api_view(http_method_names=None):
setattr(WrappedAPIView, method.lower(), handler) setattr(WrappedAPIView, method.lower(), handler)
WrappedAPIView.__name__ = func.__name__ WrappedAPIView.__name__ = func.__name__
WrappedAPIView.__module__ = func.__module__
WrappedAPIView.renderer_classes = getattr(func, 'renderer_classes', WrappedAPIView.renderer_classes = getattr(func, 'renderer_classes',
APIView.renderer_classes) APIView.renderer_classes)

View File

@ -252,6 +252,8 @@ class SkipField(Exception):
pass pass
REGEX_TYPE = type(re.compile(''))
NOT_READ_ONLY_WRITE_ONLY = 'May not set both `read_only` and `write_only`' NOT_READ_ONLY_WRITE_ONLY = 'May not set both `read_only` and `write_only`'
NOT_READ_ONLY_REQUIRED = 'May not set both `read_only` and `required`' NOT_READ_ONLY_REQUIRED = 'May not set both `read_only` and `required`'
NOT_REQUIRED_DEFAULT = 'May not set both `required` and `default`' NOT_REQUIRED_DEFAULT = 'May not set both `required` and `default`'
@ -581,16 +583,17 @@ class Field(object):
When cloning fields we instantiate using the arguments it was When cloning fields we instantiate using the arguments it was
originally created with, rather than copying the complete state. originally created with, rather than copying the complete state.
""" """
args = copy.deepcopy(self._args) # Treat regexes and validators as immutable.
kwargs = dict(self._kwargs)
# Bit ugly, but we need to special case 'validators' as Django's
# RegexValidator does not support deepcopy.
# We treat validator callables as immutable objects.
# See https://github.com/tomchristie/django-rest-framework/issues/1954 # See https://github.com/tomchristie/django-rest-framework/issues/1954
validators = kwargs.pop('validators', None) # and https://github.com/tomchristie/django-rest-framework/pull/4489
kwargs = copy.deepcopy(kwargs) args = [
if validators is not None: copy.deepcopy(item) if not isinstance(item, REGEX_TYPE) else item
kwargs['validators'] = validators for item in self._args
]
kwargs = {
key: (copy.deepcopy(value) if (key not in ('validators', 'regex')) else value)
for key, value in self._kwargs.items()
}
return self.__class__(*args, **kwargs) return self.__class__(*args, **kwargs)
def __repr__(self): def __repr__(self):

View File

@ -15,6 +15,7 @@ import sys
from django.conf import settings from django.conf import settings
from django.http import QueryDict from django.http import QueryDict
from django.http.multipartparser import parse_header from django.http.multipartparser import parse_header
from django.http.request import RawPostDataException
from django.utils import six from django.utils import six
from django.utils.datastructures import MultiValueDict from django.utils.datastructures import MultiValueDict
@ -263,10 +264,20 @@ class Request(object):
if content_length == 0: if content_length == 0:
self._stream = None self._stream = None
elif hasattr(self._request, 'read'): elif not self._request._read_started:
self._stream = self._request self._stream = self._request
else: else:
self._stream = six.BytesIO(self.raw_post_data) self._stream = six.BytesIO(self.body)
def _supports_form_parsing(self):
"""
Return True if this requests supports parsing form data.
"""
form_media = (
'application/x-www-form-urlencoded',
'multipart/form-data'
)
return any([parser.media_type in form_media for parser in self.parsers])
def _parse(self): def _parse(self):
""" """
@ -274,8 +285,18 @@ class Request(object):
May raise an `UnsupportedMediaType`, or `ParseError` exception. May raise an `UnsupportedMediaType`, or `ParseError` exception.
""" """
stream = self.stream
media_type = self.content_type media_type = self.content_type
try:
stream = self.stream
except RawPostDataException:
if not hasattr(self._request, '_post'):
raise
# If request.POST has been accessed in middleware, and a method='POST'
# request was made with 'multipart/form-data', then the request stream
# will already have been exhausted.
if self._supports_form_parsing():
return (self._request.POST, self._request.FILES)
stream = None
if stream is None or media_type is None: if stream is None or media_type is None:
empty_data = QueryDict('', encoding=self._request._encoding) empty_data = QueryDict('', encoding=self._request._encoding)

View File

@ -56,7 +56,7 @@ class Response(SimpleTemplateResponse):
assert renderer, ".accepted_renderer not set on Response" assert renderer, ".accepted_renderer not set on Response"
assert accepted_media_type, ".accepted_media_type not set on Response" assert accepted_media_type, ".accepted_media_type not set on Response"
assert context, ".renderer_context not set on Response" assert context is not None, ".renderer_context not set on Response"
context['response'] = self context['response'] = self
media_type = renderer.media_type media_type = renderer.media_type

View File

@ -296,8 +296,9 @@ class SchemaGenerator(object):
fields = [] fields = []
for field in serializer.fields.values(): for field in serializer.fields.values():
if field.read_only: if field.read_only or isinstance(field, serializers.HiddenField):
continue continue
required = field.required and method != 'PATCH' required = field.required and method != 'PATCH'
description = force_text(field.help_text) if field.help_text else '' description = force_text(field.help_text) if field.help_text else ''
field = coreapi.Field( field = coreapi.Field(

View File

@ -232,7 +232,7 @@
{% block script %} {% block script %}
<script> <script>
window.drf = { window.drf = {
csrfHeaderName: "{{ csrf_header_name|default:'X-CSRFToken' }}" csrfHeaderName: "{{ csrf_header_name|default:'X-CSRFToken' }}",
csrfCookieName: "{{ csrf_cookie_name|default:'csrftoken' }}" csrfCookieName: "{{ csrf_cookie_name|default:'csrftoken' }}"
}; };
</script> </script>

View File

@ -150,10 +150,10 @@
</div> </div>
<div class="response-info"> <div class="response-info">
<pre class="prettyprint"><span class="meta nocode"><b>HTTP {{ response.status_code }} {{ response.status_text }}</b>{% autoescape off %} <pre class="prettyprint"><span class="meta nocode"><b>HTTP {{ response.status_code }} {{ response.status_text }}</b>{% autoescape off %}{% for key, val in response_headers.items %}
{% for key, val in response_headers.items %}<b>{{ key }}:</b> <span class="lit">{{ val|break_long_headers|urlize_quoted_links }}</span> <b>{{ key }}:</b> <span class="lit">{{ val|break_long_headers|urlize_quoted_links }}</span>{% endfor %}
{% endfor %}
</span>{{ content|urlize_quoted_links }}</pre>{% endautoescape %} </span>{{ content|urlize_quoted_links }}</pre>{% endautoescape %}
</div> </div>
</div> </div>
@ -263,7 +263,7 @@
{% block script %} {% block script %}
<script> <script>
window.drf = { window.drf = {
csrfHeaderName: "{{ csrf_header_name|default:'X-CSRFToken' }}" csrfHeaderName: "{{ csrf_header_name|default:'X-CSRFToken' }}",
csrfCookieName: "{{ csrf_cookie_name|default:'csrftoken' }}" csrfCookieName: "{{ csrf_cookie_name|default:'csrftoken' }}"
}; };
</script> </script>

View File

@ -195,10 +195,13 @@ class APIRequestFactory(DjangoRequestFactory):
r = { r = {
'QUERY_STRING': urlencode(data or {}, doseq=True), 'QUERY_STRING': urlencode(data or {}, doseq=True),
} }
# Fix to support old behavior where you have the arguments in the url
# See #1461
if not data and '?' in path: if not data and '?' in path:
r['QUERY_STRING'] = path.split('?')[1] # Fix to support old behavior where you have the arguments in the
# url. See #1461.
query_string = force_bytes(path.split('?')[1])
if six.PY3:
query_string = query_string.decode('iso-8859-1')
r['QUERY_STRING'] = query_string
r.update(extra) r.update(extra)
return self.generic('GET', path, **r) return self.generic('GET', path, **r)

View File

@ -1,5 +1,6 @@
import datetime import datetime
import os import os
import re
import uuid import uuid
from decimal import Decimal from decimal import Decimal
@ -590,6 +591,20 @@ class TestRegexField(FieldValues):
field = serializers.RegexField(regex='[a-z][0-9]') field = serializers.RegexField(regex='[a-z][0-9]')
class TestiCompiledRegexField(FieldValues):
"""
Valid and invalid values for `RegexField`.
"""
valid_inputs = {
'a9': 'a9',
}
invalid_inputs = {
'A9': ["This value does not match the required pattern."]
}
outputs = {}
field = serializers.RegexField(regex=re.compile('[a-z][0-9]'))
class TestSlugField(FieldValues): class TestSlugField(FieldValues):
""" """
Valid and invalid values for `SlugField`. Valid and invalid values for `SlugField`.

View File

@ -7,11 +7,16 @@ from django import forms
from django.core.files.uploadhandler import ( from django.core.files.uploadhandler import (
MemoryFileUploadHandler, TemporaryFileUploadHandler MemoryFileUploadHandler, TemporaryFileUploadHandler
) )
from django.http.request import RawPostDataException
from django.test import TestCase from django.test import TestCase
from django.utils.six.moves import StringIO from django.utils.six.moves import StringIO
from rest_framework.exceptions import ParseError from rest_framework.exceptions import ParseError
from rest_framework.parsers import FileUploadParser, FormParser from rest_framework.parsers import (
FileUploadParser, FormParser, JSONParser, MultiPartParser
)
from rest_framework.request import Request
from rest_framework.test import APIRequestFactory
class Form(forms.Form): class Form(forms.Form):
@ -122,3 +127,39 @@ class TestFileUploadParser(TestCase):
def __replace_content_disposition(self, disposition): def __replace_content_disposition(self, disposition):
self.parser_context['request'].META['HTTP_CONTENT_DISPOSITION'] = disposition self.parser_context['request'].META['HTTP_CONTENT_DISPOSITION'] = disposition
class TestPOSTAccessed(TestCase):
def setUp(self):
self.factory = APIRequestFactory()
def test_post_accessed_in_post_method(self):
django_request = self.factory.post('/', {'foo': 'bar'})
request = Request(django_request, parsers=[FormParser(), MultiPartParser()])
django_request.POST
assert request.POST == {'foo': ['bar']}
assert request.data == {'foo': ['bar']}
def test_post_accessed_in_post_method_with_json_parser(self):
django_request = self.factory.post('/', {'foo': 'bar'})
request = Request(django_request, parsers=[JSONParser()])
django_request.POST
assert request.POST == {}
assert request.data == {}
def test_post_accessed_in_put_method(self):
django_request = self.factory.put('/', {'foo': 'bar'})
request = Request(django_request, parsers=[FormParser(), MultiPartParser()])
django_request.POST
assert request.POST == {'foo': ['bar']}
assert request.data == {'foo': ['bar']}
def test_request_read_before_parsing(self):
django_request = self.factory.put('/', {'foo': 'bar'})
request = Request(django_request, parsers=[FormParser(), MultiPartParser()])
django_request.read()
with pytest.raises(RawPostDataException):
request.POST
with pytest.raises(RawPostDataException):
request.POST
request.data

View File

@ -26,6 +26,8 @@ class ExamplePagination(pagination.PageNumberPagination):
class ExampleSerializer(serializers.Serializer): class ExampleSerializer(serializers.Serializer):
a = serializers.CharField(required=True, help_text='A field description') a = serializers.CharField(required=True, help_text='A field description')
b = serializers.CharField(required=False) b = serializers.CharField(required=False)
read_only = serializers.CharField(read_only=True)
hidden = serializers.HiddenField(default='hello')
class AnotherSerializer(serializers.Serializer): class AnotherSerializer(serializers.Serializer):

View File

@ -2,6 +2,7 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import pickle import pickle
import re
import pytest import pytest
@ -337,3 +338,16 @@ class TestDefaultInclusions:
assert serializer.is_valid() assert serializer.is_valid()
assert serializer.validated_data == {'integer': 456} assert serializer.validated_data == {'integer': 456}
assert serializer.errors == {} assert serializer.errors == {}
class TestSerializerValidationWithCompiledRegexField:
def setup(self):
class ExampleSerializer(serializers.Serializer):
name = serializers.RegexField(re.compile(r'\d'), required=True)
self.Serializer = ExampleSerializer
def test_validation_success(self):
serializer = self.Serializer(data={'name': '2'})
assert serializer.is_valid()
assert serializer.validated_data == {'name': '2'}
assert serializer.errors == {}

View File

@ -245,3 +245,10 @@ class TestAPIRequestFactory(TestCase):
self.assertEqual(dict(request.GET), {'demo': ['test']}) self.assertEqual(dict(request.GET), {'demo': ['test']})
request = factory.get('/view/', {'demo': 'test'}) request = factory.get('/view/', {'demo': 'test'})
self.assertEqual(dict(request.GET), {'demo': ['test']}) self.assertEqual(dict(request.GET), {'demo': ['test']})
def test_request_factory_url_arguments_with_unicode(self):
factory = APIRequestFactory()
request = factory.get('/view/?demo=testé')
self.assertEqual(dict(request.GET), {'demo': ['testé']})
request = factory.get('/view/', {'demo': 'testé'})
self.assertEqual(dict(request.GET), {'demo': ['testé']})