mirror of
https://github.com/encode/django-rest-framework.git
synced 2025-08-04 20:40:14 +03:00
Merge branch 'master' into localizedfloatfield
This commit is contained in:
commit
5094299826
|
@ -330,7 +330,7 @@ The [Django OAuth2 Consumer][doac] library from [Rediker Software][rediker] is a
|
||||||
|
|
||||||
## JSON Web Token Authentication
|
## JSON Web Token Authentication
|
||||||
|
|
||||||
JSON Web Token is a fairly new standard which can be used for token-based authentication. Unlike the built-in TokenAuthentication scheme, JWT Authentication doesn't need to use a database to validate a token. [Blimp][blimp] maintains the [djangorestframework-jwt][djangorestframework-jwt] package which provides a JWT Authentication class as well as a mechanism for clients to obtain a JWT given the username and password.
|
JSON Web Token is a fairly new standard which can be used for token-based authentication. Unlike the built-in TokenAuthentication scheme, JWT Authentication doesn't need to use a database to validate a token. [Blimp][blimp] maintains the [djangorestframework-jwt][djangorestframework-jwt] package which provides a JWT Authentication class as well as a mechanism for clients to obtain a JWT given the username and password. An alternative package for JWT authentication is [djangorestframework-simplejwt][djangorestframework-simplejwt] which provides different features as well as a pluggable token blacklist app.
|
||||||
|
|
||||||
## Hawk HTTP Authentication
|
## Hawk HTTP Authentication
|
||||||
|
|
||||||
|
@ -388,6 +388,7 @@ HTTP Signature (currently a [IETF draft][http-signature-ietf-draft]) provides a
|
||||||
[doac-rest-framework]: https://github.com/Rediker-Software/doac/blob/master/docs/integrations.md#
|
[doac-rest-framework]: https://github.com/Rediker-Software/doac/blob/master/docs/integrations.md#
|
||||||
[blimp]: https://github.com/GetBlimp
|
[blimp]: https://github.com/GetBlimp
|
||||||
[djangorestframework-jwt]: https://github.com/GetBlimp/django-rest-framework-jwt
|
[djangorestframework-jwt]: https://github.com/GetBlimp/django-rest-framework-jwt
|
||||||
|
[djangorestframework-simplejwt]: https://github.com/davesque/django-rest-framework-simplejwt
|
||||||
[etoccalino]: https://github.com/etoccalino/
|
[etoccalino]: https://github.com/etoccalino/
|
||||||
[djangorestframework-httpsignature]: https://github.com/etoccalino/django-rest-framework-httpsignature
|
[djangorestframework-httpsignature]: https://github.com/etoccalino/django-rest-framework-httpsignature
|
||||||
[amazon-http-signature]: http://docs.aws.amazon.com/general/latest/gr/signature-version-4.html
|
[amazon-http-signature]: http://docs.aws.amazon.com/general/latest/gr/signature-version-4.html
|
||||||
|
|
|
@ -329,7 +329,7 @@ For further enquires please contact <a href=mailto:funding@django-rest-framework
|
||||||
|
|
||||||
## Accountability
|
## Accountability
|
||||||
|
|
||||||
In an effort to keep the project as transparent as possible, we are releasing [monthly progress reports](http://www.encode.io/reports/may-2017) and regularly include financial reports and cost breakdowns.
|
In an effort to keep the project as transparent as possible, we are releasing [monthly progress reports](http://www.encode.io/reports/june-2017) and regularly include financial reports and cost breakdowns.
|
||||||
|
|
||||||
<!-- Begin MailChimp Signup Form -->
|
<!-- Begin MailChimp Signup Form -->
|
||||||
<link href="//cdn-images.mailchimp.com/embedcode/classic-10_7.css" rel="stylesheet" type="text/css">
|
<link href="//cdn-images.mailchimp.com/embedcode/classic-10_7.css" rel="stylesheet" type="text/css">
|
||||||
|
|
|
@ -185,6 +185,7 @@ To submit new content, [open an issue][drf-create-issue] or [create a pull reque
|
||||||
* [django-oauth-toolkit][django-oauth-toolkit] - Provides OAuth 2.0 support.
|
* [django-oauth-toolkit][django-oauth-toolkit] - Provides OAuth 2.0 support.
|
||||||
* [doac][doac] - Provides OAuth 2.0 support.
|
* [doac][doac] - Provides OAuth 2.0 support.
|
||||||
* [djangorestframework-jwt][djangorestframework-jwt] - Provides JSON Web Token Authentication support.
|
* [djangorestframework-jwt][djangorestframework-jwt] - Provides JSON Web Token Authentication support.
|
||||||
|
* [djangorestframework-simplejwt][djangorestframework-simplejwt] - An alternative package that provides JSON Web Token Authentication support.
|
||||||
* [hawkrest][hawkrest] - Provides Hawk HTTP Authorization.
|
* [hawkrest][hawkrest] - Provides Hawk HTTP Authorization.
|
||||||
* [djangorestframework-httpsignature][djangorestframework-httpsignature] - Provides an easy to use HTTP Signature Authentication mechanism.
|
* [djangorestframework-httpsignature][djangorestframework-httpsignature] - Provides an easy to use HTTP Signature Authentication mechanism.
|
||||||
* [djoser][djoser] - Provides a set of views to handle basic actions such as registration, login, logout, password reset and account activation.
|
* [djoser][djoser] - Provides a set of views to handle basic actions such as registration, login, logout, password reset and account activation.
|
||||||
|
@ -284,6 +285,7 @@ To submit new content, [open an issue][drf-create-issue] or [create a pull reque
|
||||||
[django-oauth-toolkit]: https://github.com/evonove/django-oauth-toolkit
|
[django-oauth-toolkit]: https://github.com/evonove/django-oauth-toolkit
|
||||||
[doac]: https://github.com/Rediker-Software/doac
|
[doac]: https://github.com/Rediker-Software/doac
|
||||||
[djangorestframework-jwt]: https://github.com/GetBlimp/django-rest-framework-jwt
|
[djangorestframework-jwt]: https://github.com/GetBlimp/django-rest-framework-jwt
|
||||||
|
[djangorestframework-simplejwt]: https://github.com/davesque/django-rest-framework-simplejwt
|
||||||
[hawkrest]: https://github.com/kumar303/hawkrest
|
[hawkrest]: https://github.com/kumar303/hawkrest
|
||||||
[djangorestframework-httpsignature]: https://github.com/etoccalino/django-rest-framework-httpsignature
|
[djangorestframework-httpsignature]: https://github.com/etoccalino/django-rest-framework-httpsignature
|
||||||
[djoser]: https://github.com/sunscrapers/djoser
|
[djoser]: https://github.com/sunscrapers/djoser
|
||||||
|
|
0
rest_framework/authtoken/management/__init__.py
Normal file
0
rest_framework/authtoken/management/__init__.py
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
from django.contrib.auth import get_user_model
|
||||||
|
from django.core.management.base import BaseCommand, CommandError
|
||||||
|
from rest_framework.authtoken.models import Token
|
||||||
|
|
||||||
|
|
||||||
|
UserModel = get_user_model()
|
||||||
|
|
||||||
|
|
||||||
|
class Command(BaseCommand):
|
||||||
|
help = 'Create DRF Token for a given user'
|
||||||
|
|
||||||
|
def create_user_token(self, username, reset_token):
|
||||||
|
user = UserModel._default_manager.get_by_natural_key(username)
|
||||||
|
|
||||||
|
if reset_token:
|
||||||
|
Token.objects.filter(user=user).delete()
|
||||||
|
|
||||||
|
token = Token.objects.get_or_create(user=user)
|
||||||
|
return token[0]
|
||||||
|
|
||||||
|
def add_arguments(self, parser):
|
||||||
|
parser.add_argument('username', type=str, nargs='+')
|
||||||
|
|
||||||
|
parser.add_argument(
|
||||||
|
'-r',
|
||||||
|
'--reset',
|
||||||
|
action='store_true',
|
||||||
|
dest='reset_token',
|
||||||
|
default=False,
|
||||||
|
help='Reset existing User token and create a new one',
|
||||||
|
)
|
||||||
|
|
||||||
|
def handle(self, *args, **options):
|
||||||
|
username = options['username']
|
||||||
|
reset_token = options['reset_token']
|
||||||
|
|
||||||
|
try:
|
||||||
|
token = self.create_user_token(username, reset_token)
|
||||||
|
except UserModel.DoesNotExist:
|
||||||
|
raise CommandError(
|
||||||
|
'Cannot create the Token: user {0} does not exist'.format(
|
||||||
|
username)
|
||||||
|
)
|
||||||
|
self.stdout.write(
|
||||||
|
'Generated token {0} for user {1}'.format(token.key, username))
|
|
@ -793,13 +793,17 @@ class RegexField(CharField):
|
||||||
|
|
||||||
class SlugField(CharField):
|
class SlugField(CharField):
|
||||||
default_error_messages = {
|
default_error_messages = {
|
||||||
'invalid': _('Enter a valid "slug" consisting of letters, numbers, underscores or hyphens.')
|
'invalid': _('Enter a valid "slug" consisting of letters, numbers, underscores or hyphens.'),
|
||||||
|
'invalid_unicode': _('Enter a valid "slug" consisting of Unicode letters, numbers, underscores, or hyphens.')
|
||||||
}
|
}
|
||||||
|
|
||||||
def __init__(self, **kwargs):
|
def __init__(self, allow_unicode=False, **kwargs):
|
||||||
super(SlugField, self).__init__(**kwargs)
|
super(SlugField, self).__init__(**kwargs)
|
||||||
slug_regex = re.compile(r'^[-a-zA-Z0-9_]+$')
|
self.allow_unicode = allow_unicode
|
||||||
validator = RegexValidator(slug_regex, message=self.error_messages['invalid'])
|
if self.allow_unicode:
|
||||||
|
validator = RegexValidator(re.compile(r'^[-\w]+\Z', re.UNICODE), message=self.error_messages['invalid_unicode'])
|
||||||
|
else:
|
||||||
|
validator = RegexValidator(re.compile(r'^[-a-zA-Z0-9_]+$'), message=self.error_messages['invalid'])
|
||||||
self.validators.append(validator)
|
self.validators.append(validator)
|
||||||
|
|
||||||
|
|
||||||
|
@ -1148,18 +1152,16 @@ class DateTimeField(Field):
|
||||||
if input_format.lower() == ISO_8601:
|
if input_format.lower() == ISO_8601:
|
||||||
try:
|
try:
|
||||||
parsed = parse_datetime(value)
|
parsed = parse_datetime(value)
|
||||||
except (ValueError, TypeError):
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
if parsed is not None:
|
if parsed is not None:
|
||||||
return self.enforce_timezone(parsed)
|
return self.enforce_timezone(parsed)
|
||||||
|
except (ValueError, TypeError):
|
||||||
|
pass
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
parsed = self.datetime_parser(value, input_format)
|
parsed = self.datetime_parser(value, input_format)
|
||||||
|
return self.enforce_timezone(parsed)
|
||||||
except (ValueError, TypeError):
|
except (ValueError, TypeError):
|
||||||
pass
|
pass
|
||||||
else:
|
|
||||||
return self.enforce_timezone(parsed)
|
|
||||||
|
|
||||||
humanized_format = humanize_datetime.datetime_formats(input_formats)
|
humanized_format = humanize_datetime.datetime_formats(input_formats)
|
||||||
self.fail('invalid', format=humanized_format)
|
self.fail('invalid', format=humanized_format)
|
||||||
|
|
|
@ -556,7 +556,10 @@ class BrowsableAPIRenderer(BaseRenderer):
|
||||||
accepted = self.accepted_media_type
|
accepted = self.accepted_media_type
|
||||||
context = self.renderer_context.copy()
|
context = self.renderer_context.copy()
|
||||||
context['indent'] = 4
|
context['indent'] = 4
|
||||||
content = renderer.render(serializer.data, accepted, context)
|
data = {k: v for (k, v) in serializer.data.items()
|
||||||
|
if not isinstance(serializer.fields[k],
|
||||||
|
serializers.HiddenField)}
|
||||||
|
content = renderer.render(data, accepted, context)
|
||||||
else:
|
else:
|
||||||
content = None
|
content = None
|
||||||
|
|
||||||
|
|
|
@ -250,6 +250,10 @@ class Request(object):
|
||||||
else:
|
else:
|
||||||
self._full_data = self._data
|
self._full_data = self._data
|
||||||
|
|
||||||
|
# copy files refs to the underlying request so that closable
|
||||||
|
# objects are handled appropriately.
|
||||||
|
self._request._files = self._files
|
||||||
|
|
||||||
def _load_stream(self):
|
def _load_stream(self):
|
||||||
"""
|
"""
|
||||||
Return the content body of the request, as a stream.
|
Return the content body of the request, as a stream.
|
||||||
|
|
|
@ -13,7 +13,7 @@
|
||||||
{% if 'javascript' in langs %}{% include "rest_framework/docs/langs/javascript-intro.html" %}{% endif %}
|
{% if 'javascript' in langs %}{% include "rest_framework/docs/langs/javascript-intro.html" %}{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{% if document.data %}
|
||||||
{% for section_key, section in document.data|items %}
|
{% for section_key, section in document.data|items %}
|
||||||
{% if section_key %}
|
{% if section_key %}
|
||||||
<h2 id="{{ section_key }}" class="coredocs-section-title">{{ section_key }} <a href="#{{ section_key }}"><i class="fa fa-link" aria-hidden="true"></i>
|
<h2 id="{{ section_key }}" class="coredocs-section-title">{{ section_key }} <a href="#{{ section_key }}"><i class="fa fa-link" aria-hidden="true"></i>
|
||||||
|
@ -28,3 +28,4 @@
|
||||||
{% for link_key, link in document.links|items %}
|
{% for link_key, link in document.links|items %}
|
||||||
{% include "rest_framework/docs/link.html" %}
|
{% include "rest_framework/docs/link.html" %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
|
|
@ -5,16 +5,18 @@
|
||||||
<i class="fa fa-bars fa-2x toggle-btn" data-toggle="collapse" data-target="#menu-content"></i>
|
<i class="fa fa-bars fa-2x toggle-btn" data-toggle="collapse" data-target="#menu-content"></i>
|
||||||
<div class="menu-list">
|
<div class="menu-list">
|
||||||
<ul id="menu-content" class="menu-content collapse out">
|
<ul id="menu-content" class="menu-content collapse out">
|
||||||
|
{% if document.data %}
|
||||||
{% for section_key, section in document.data|items %}
|
{% for section_key, section in document.data|items %}
|
||||||
<li data-toggle="collapse" data-target="#{{ section_key }}-dropdown" class="collapsed">
|
<li data-toggle="collapse" data-target="#{{ section_key }}-dropdown" class="collapsed">
|
||||||
<a><i class="fa fa-dot-circle-o fa-lg"></i> {% if section_key %}{{ section_key }}{% else %}API Endpoints{% endif %} <span class="arrow"></span></a>
|
<a><i class="fa fa-dot-circle-o fa-lg"></i> {% if section_key %}{{ section_key }}{% else %}API Endpoints{% endif %} <span class="arrow"></span></a>
|
||||||
</li>
|
<ul class="sub-menu {% if section_key %}collapse{% endif %}" id="{{ section_key }}-dropdown">
|
||||||
<ul class="sub-menu {% if section_key %}collapse{% endif %}" id="{{ section_key }}-dropdown">
|
|
||||||
{% for link_key, link in section.links|items %}
|
{% for link_key, link in section.links|items %}
|
||||||
<li><a href="#{{ section_key }}-{{ link_key }}">{{ link.title|default:link_key }}</a></li>
|
<li><a href="#{{ section_key }}-{{ link_key }}">{{ link.title|default:link_key }}</a></li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
|
</li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<ul class="menu-list menu-list-bottom">
|
<ul class="menu-list menu-list-bottom">
|
||||||
|
|
|
@ -82,6 +82,10 @@ class ViewSetMixin(object):
|
||||||
if hasattr(self, 'get') and not hasattr(self, 'head'):
|
if hasattr(self, 'get') and not hasattr(self, 'head'):
|
||||||
self.head = self.get
|
self.head = self.get
|
||||||
|
|
||||||
|
self.request = request
|
||||||
|
self.args = args
|
||||||
|
self.kwargs = kwargs
|
||||||
|
|
||||||
# And continue as usual
|
# And continue as usual
|
||||||
return self.dispatch(request, *args, **kwargs)
|
return self.dispatch(request, *args, **kwargs)
|
||||||
|
|
||||||
|
|
|
@ -275,13 +275,13 @@ class APIClientTests(APITestCase):
|
||||||
client = CoreAPIClient()
|
client = CoreAPIClient()
|
||||||
schema = client.get('http://api.example.com/')
|
schema = client.get('http://api.example.com/')
|
||||||
|
|
||||||
temp = tempfile.NamedTemporaryFile()
|
with tempfile.NamedTemporaryFile() as temp:
|
||||||
temp.write(b'example file content')
|
temp.write(b'example file content')
|
||||||
temp.flush()
|
temp.flush()
|
||||||
|
temp.seek(0)
|
||||||
|
|
||||||
with open(temp.name, 'rb') as upload:
|
name = os.path.basename(temp.name)
|
||||||
name = os.path.basename(upload.name)
|
data = client.action(schema, ['encoding', 'multipart'], params={'example': temp})
|
||||||
data = client.action(schema, ['encoding', 'multipart'], params={'example': upload})
|
|
||||||
|
|
||||||
expected = {
|
expected = {
|
||||||
'method': 'POST',
|
'method': 'POST',
|
||||||
|
@ -407,13 +407,13 @@ class APIClientTests(APITestCase):
|
||||||
client = CoreAPIClient()
|
client = CoreAPIClient()
|
||||||
schema = client.get('http://api.example.com/')
|
schema = client.get('http://api.example.com/')
|
||||||
|
|
||||||
temp = tempfile.NamedTemporaryFile()
|
with tempfile.NamedTemporaryFile(delete=False) as temp:
|
||||||
temp.write(b'example file content')
|
temp.write(b'example file content')
|
||||||
temp.flush()
|
temp.flush()
|
||||||
|
temp.seek(0)
|
||||||
|
|
||||||
with open(temp.name, 'rb') as upload:
|
name = os.path.basename(temp.name)
|
||||||
name = os.path.basename(upload.name)
|
data = client.action(schema, ['encoding', 'raw_upload'], params={'example': temp})
|
||||||
data = client.action(schema, ['encoding', 'raw_upload'], params={'example': upload})
|
|
||||||
|
|
||||||
expected = {
|
expected = {
|
||||||
'method': 'POST',
|
'method': 'POST',
|
||||||
|
|
|
@ -45,6 +45,7 @@ class NonAtomicAPIExceptionView(APIView):
|
||||||
BasicModel.objects.all()
|
BasicModel.objects.all()
|
||||||
raise Http404
|
raise Http404
|
||||||
|
|
||||||
|
|
||||||
urlpatterns = (
|
urlpatterns = (
|
||||||
url(r'^$', NonAtomicAPIExceptionView.as_view()),
|
url(r'^$', NonAtomicAPIExceptionView.as_view()),
|
||||||
)
|
)
|
||||||
|
|
|
@ -4,6 +4,8 @@ from django.contrib.auth.models import User
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
|
|
||||||
from rest_framework.authtoken.admin import TokenAdmin
|
from rest_framework.authtoken.admin import TokenAdmin
|
||||||
|
from rest_framework.authtoken.management.commands.drf_create_token import \
|
||||||
|
Command as AuthTokenCommand
|
||||||
from rest_framework.authtoken.models import Token
|
from rest_framework.authtoken.models import Token
|
||||||
from rest_framework.authtoken.serializers import AuthTokenSerializer
|
from rest_framework.authtoken.serializers import AuthTokenSerializer
|
||||||
from rest_framework.exceptions import ValidationError
|
from rest_framework.exceptions import ValidationError
|
||||||
|
@ -33,3 +35,36 @@ class AuthTokenTests(TestCase):
|
||||||
self.user.set_password(data['password'])
|
self.user.set_password(data['password'])
|
||||||
self.user.save()
|
self.user.save()
|
||||||
assert AuthTokenSerializer(data=data).is_valid()
|
assert AuthTokenSerializer(data=data).is_valid()
|
||||||
|
|
||||||
|
|
||||||
|
class AuthTokenCommandTests(TestCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
self.site = site
|
||||||
|
self.user = User.objects.create_user(username='test_user')
|
||||||
|
|
||||||
|
def test_command_create_user_token(self):
|
||||||
|
token = AuthTokenCommand().create_user_token(self.user.username, False)
|
||||||
|
assert token is not None
|
||||||
|
token_saved = Token.objects.first()
|
||||||
|
assert token.key == token_saved.key
|
||||||
|
|
||||||
|
def test_command_create_user_token_invalid_user(self):
|
||||||
|
with pytest.raises(User.DoesNotExist):
|
||||||
|
AuthTokenCommand().create_user_token('not_existing_user', False)
|
||||||
|
|
||||||
|
def test_command_reset_user_token(self):
|
||||||
|
AuthTokenCommand().create_user_token(self.user.username, False)
|
||||||
|
first_token_key = Token.objects.first().key
|
||||||
|
AuthTokenCommand().create_user_token(self.user.username, True)
|
||||||
|
second_token_key = Token.objects.first().key
|
||||||
|
|
||||||
|
assert first_token_key != second_token_key
|
||||||
|
|
||||||
|
def test_command_do_not_reset_user_token(self):
|
||||||
|
AuthTokenCommand().create_user_token(self.user.username, False)
|
||||||
|
first_token_key = Token.objects.first().key
|
||||||
|
AuthTokenCommand().create_user_token(self.user.username, False)
|
||||||
|
second_token_key = Token.objects.first().key
|
||||||
|
|
||||||
|
assert first_token_key == second_token_key
|
||||||
|
|
|
@ -5,6 +5,7 @@ import unittest
|
||||||
import uuid
|
import uuid
|
||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
|
|
||||||
|
import django
|
||||||
import pytest
|
import pytest
|
||||||
from django.http import QueryDict
|
from django.http import QueryDict
|
||||||
from django.test import TestCase, override_settings
|
from django.test import TestCase, override_settings
|
||||||
|
@ -704,6 +705,17 @@ class TestSlugField(FieldValues):
|
||||||
outputs = {}
|
outputs = {}
|
||||||
field = serializers.SlugField()
|
field = serializers.SlugField()
|
||||||
|
|
||||||
|
def test_allow_unicode_true(self):
|
||||||
|
field = serializers.SlugField(allow_unicode=True)
|
||||||
|
|
||||||
|
validation_error = False
|
||||||
|
try:
|
||||||
|
field.run_validation(u'slug-99-\u0420')
|
||||||
|
except serializers.ValidationError:
|
||||||
|
validation_error = True
|
||||||
|
|
||||||
|
assert not validation_error
|
||||||
|
|
||||||
|
|
||||||
class TestURLField(FieldValues):
|
class TestURLField(FieldValues):
|
||||||
"""
|
"""
|
||||||
|
@ -1165,12 +1177,11 @@ class TestDateTimeField(FieldValues):
|
||||||
'2001-01-01T13:00Z': datetime.datetime(2001, 1, 1, 13, 00, tzinfo=utc),
|
'2001-01-01T13:00Z': datetime.datetime(2001, 1, 1, 13, 00, tzinfo=utc),
|
||||||
datetime.datetime(2001, 1, 1, 13, 00): datetime.datetime(2001, 1, 1, 13, 00, tzinfo=utc),
|
datetime.datetime(2001, 1, 1, 13, 00): datetime.datetime(2001, 1, 1, 13, 00, tzinfo=utc),
|
||||||
datetime.datetime(2001, 1, 1, 13, 00, tzinfo=utc): datetime.datetime(2001, 1, 1, 13, 00, tzinfo=utc),
|
datetime.datetime(2001, 1, 1, 13, 00, tzinfo=utc): datetime.datetime(2001, 1, 1, 13, 00, tzinfo=utc),
|
||||||
# Django 1.4 does not support timezone string parsing.
|
|
||||||
'2001-01-01T13:00Z': datetime.datetime(2001, 1, 1, 13, 00, tzinfo=utc)
|
|
||||||
}
|
}
|
||||||
invalid_inputs = {
|
invalid_inputs = {
|
||||||
'abc': ['Datetime has wrong format. Use one of these formats instead: YYYY-MM-DDThh:mm[:ss[.uuuuuu]][+HH:MM|-HH:MM|Z].'],
|
'abc': ['Datetime has wrong format. Use one of these formats instead: YYYY-MM-DDThh:mm[:ss[.uuuuuu]][+HH:MM|-HH:MM|Z].'],
|
||||||
'2001-99-99T99:00': ['Datetime has wrong format. Use one of these formats instead: YYYY-MM-DDThh:mm[:ss[.uuuuuu]][+HH:MM|-HH:MM|Z].'],
|
'2001-99-99T99:00': ['Datetime has wrong format. Use one of these formats instead: YYYY-MM-DDThh:mm[:ss[.uuuuuu]][+HH:MM|-HH:MM|Z].'],
|
||||||
|
'2018-08-16 22:00-24:00': ['Datetime has wrong format. Use one of these formats instead: YYYY-MM-DDThh:mm[:ss[.uuuuuu]][+HH:MM|-HH:MM|Z].'],
|
||||||
datetime.date(2001, 1, 1): ['Expected a datetime but got a date.'],
|
datetime.date(2001, 1, 1): ['Expected a datetime but got a date.'],
|
||||||
}
|
}
|
||||||
outputs = {
|
outputs = {
|
||||||
|
@ -1184,6 +1195,11 @@ class TestDateTimeField(FieldValues):
|
||||||
field = serializers.DateTimeField(default_timezone=utc)
|
field = serializers.DateTimeField(default_timezone=utc)
|
||||||
|
|
||||||
|
|
||||||
|
if django.VERSION[:2] <= (1, 8):
|
||||||
|
# Doesn't raise an error on earlier versions of Django
|
||||||
|
TestDateTimeField.invalid_inputs.pop('2018-08-16 22:00-24:00')
|
||||||
|
|
||||||
|
|
||||||
class TestCustomInputFormatDateTimeField(FieldValues):
|
class TestCustomInputFormatDateTimeField(FieldValues):
|
||||||
"""
|
"""
|
||||||
Valid and invalid values for `DateTimeField` with a custom input format.
|
Valid and invalid values for `DateTimeField` with a custom input format.
|
||||||
|
|
|
@ -246,6 +246,7 @@ class ObjectPermissionInstanceView(generics.RetrieveUpdateDestroyAPIView):
|
||||||
authentication_classes = [authentication.BasicAuthentication]
|
authentication_classes = [authentication.BasicAuthentication]
|
||||||
permission_classes = [ViewObjectPermissions]
|
permission_classes = [ViewObjectPermissions]
|
||||||
|
|
||||||
|
|
||||||
object_permissions_view = ObjectPermissionInstanceView.as_view()
|
object_permissions_view = ObjectPermissionInstanceView.as_view()
|
||||||
|
|
||||||
|
|
||||||
|
@ -255,6 +256,7 @@ class ObjectPermissionListView(generics.ListAPIView):
|
||||||
authentication_classes = [authentication.BasicAuthentication]
|
authentication_classes = [authentication.BasicAuthentication]
|
||||||
permission_classes = [ViewObjectPermissions]
|
permission_classes = [ViewObjectPermissions]
|
||||||
|
|
||||||
|
|
||||||
object_permissions_list_view = ObjectPermissionListView.as_view()
|
object_permissions_list_view = ObjectPermissionListView.as_view()
|
||||||
|
|
||||||
|
|
||||||
|
@ -443,6 +445,7 @@ class DeniedObjectView(PermissionInstanceView):
|
||||||
class DeniedObjectViewWithDetail(PermissionInstanceView):
|
class DeniedObjectViewWithDetail(PermissionInstanceView):
|
||||||
permission_classes = (BasicObjectPermWithDetail,)
|
permission_classes = (BasicObjectPermWithDetail,)
|
||||||
|
|
||||||
|
|
||||||
denied_view = DeniedView.as_view()
|
denied_view = DeniedView.as_view()
|
||||||
|
|
||||||
denied_view_with_detail = DeniedViewWithDetail.as_view()
|
denied_view_with_detail = DeniedViewWithDetail.as_view()
|
||||||
|
|
|
@ -108,6 +108,7 @@ class HTMLView1(APIView):
|
||||||
def get(self, request, **kwargs):
|
def get(self, request, **kwargs):
|
||||||
return Response('text')
|
return Response('text')
|
||||||
|
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
url(r'^.*\.(?P<format>.+)$', MockView.as_view(renderer_classes=[RendererA, RendererB])),
|
url(r'^.*\.(?P<format>.+)$', MockView.as_view(renderer_classes=[RendererA, RendererB])),
|
||||||
url(r'^$', MockView.as_view(renderer_classes=[RendererA, RendererB])),
|
url(r'^$', MockView.as_view(renderer_classes=[RendererA, RendererB])),
|
||||||
|
|
|
@ -3,6 +3,9 @@ Tests for content parsing, and form-overloaded content parsing.
|
||||||
"""
|
"""
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import os.path
|
||||||
|
import tempfile
|
||||||
|
|
||||||
from django.conf.urls import url
|
from django.conf.urls import url
|
||||||
from django.contrib.auth import authenticate, login, logout
|
from django.contrib.auth import authenticate, login, logout
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
|
@ -120,11 +123,39 @@ class MockView(APIView):
|
||||||
|
|
||||||
return Response(status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
return Response(status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
||||||
|
|
||||||
|
|
||||||
|
class FileUploadView(APIView):
|
||||||
|
def post(self, request):
|
||||||
|
filenames = [file.temporary_file_path() for file in request.FILES.values()]
|
||||||
|
|
||||||
|
for filename in filenames:
|
||||||
|
assert os.path.exists(filename)
|
||||||
|
|
||||||
|
return Response(status=status.HTTP_200_OK, data=filenames)
|
||||||
|
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
url(r'^$', MockView.as_view()),
|
url(r'^$', MockView.as_view()),
|
||||||
|
url(r'^upload/$', FileUploadView.as_view())
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@override_settings(
|
||||||
|
ROOT_URLCONF='tests.test_request',
|
||||||
|
FILE_UPLOAD_HANDLERS=['django.core.files.uploadhandler.TemporaryFileUploadHandler'])
|
||||||
|
class FileUploadTests(TestCase):
|
||||||
|
|
||||||
|
def test_fileuploads_closed_at_request_end(self):
|
||||||
|
with tempfile.NamedTemporaryFile() as f:
|
||||||
|
response = self.client.post('/upload/', {'file': f})
|
||||||
|
|
||||||
|
# sanity check that file was processed
|
||||||
|
assert len(response.data) == 1
|
||||||
|
|
||||||
|
for file in response.data:
|
||||||
|
assert not os.path.exists(file)
|
||||||
|
|
||||||
|
|
||||||
@override_settings(ROOT_URLCONF='tests.test_request')
|
@override_settings(ROOT_URLCONF='tests.test_request')
|
||||||
class TestContentParsingWithAuthentication(TestCase):
|
class TestContentParsingWithAuthentication(TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
|
|
|
@ -32,6 +32,7 @@ class MockJsonRenderer(BaseRenderer):
|
||||||
class MockTextMediaRenderer(BaseRenderer):
|
class MockTextMediaRenderer(BaseRenderer):
|
||||||
media_type = 'text/html'
|
media_type = 'text/html'
|
||||||
|
|
||||||
|
|
||||||
DUMMYSTATUS = status.HTTP_200_OK
|
DUMMYSTATUS = status.HTTP_200_OK
|
||||||
DUMMYCONTENT = 'dummycontent'
|
DUMMYCONTENT = 'dummycontent'
|
||||||
|
|
||||||
|
|
|
@ -13,6 +13,7 @@ factory = APIRequestFactory()
|
||||||
def null_view(request):
|
def null_view(request):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
url(r'^view$', null_view, name='view'),
|
url(r'^view$', null_view, name='view'),
|
||||||
]
|
]
|
||||||
|
|
|
@ -70,6 +70,7 @@ class ExampleViewSet(ModelViewSet):
|
||||||
assert self.action
|
assert self.action
|
||||||
return super(ExampleViewSet, self).get_serializer(*args, **kwargs)
|
return super(ExampleViewSet, self).get_serializer(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
if coreapi:
|
if coreapi:
|
||||||
schema_view = get_schema_view(title='Example API')
|
schema_view = get_schema_view(title='Example API')
|
||||||
else:
|
else:
|
||||||
|
|
|
@ -243,6 +243,7 @@ class RegexSerializer(serializers.Serializer):
|
||||||
validators=[RegexValidator(regex=re.compile('^[0-9]{4,6}$'),
|
validators=[RegexValidator(regex=re.compile('^[0-9]{4,6}$'),
|
||||||
message='A PIN is 4-6 digits')])
|
message='A PIN is 4-6 digits')])
|
||||||
|
|
||||||
|
|
||||||
expected_repr = """
|
expected_repr = """
|
||||||
RegexSerializer():
|
RegexSerializer():
|
||||||
pin = CharField(validators=[<django.core.validators.RegexValidator object>])
|
pin = CharField(validators=[<django.core.validators.RegexValidator object>])
|
||||||
|
|
|
@ -13,6 +13,15 @@ class BasicViewSet(GenericViewSet):
|
||||||
return Response({'ACTION': 'LIST'})
|
return Response({'ACTION': 'LIST'})
|
||||||
|
|
||||||
|
|
||||||
|
class InstanceViewSet(GenericViewSet):
|
||||||
|
|
||||||
|
def dispatch(self, request, *args, **kwargs):
|
||||||
|
return self.dummy(request, *args, **kwargs)
|
||||||
|
|
||||||
|
def dummy(self, request, *args, **kwargs):
|
||||||
|
return Response({'view': self})
|
||||||
|
|
||||||
|
|
||||||
class InitializeViewSetsTestCase(TestCase):
|
class InitializeViewSetsTestCase(TestCase):
|
||||||
def test_initialize_view_set_with_actions(self):
|
def test_initialize_view_set_with_actions(self):
|
||||||
request = factory.get('/', '', content_type='application/json')
|
request = factory.get('/', '', content_type='application/json')
|
||||||
|
@ -42,3 +51,17 @@ class InitializeViewSetsTestCase(TestCase):
|
||||||
"For example `.as_view({'get': 'list'})`")
|
"For example `.as_view({'get': 'list'})`")
|
||||||
else:
|
else:
|
||||||
self.fail("actions must not be empty.")
|
self.fail("actions must not be empty.")
|
||||||
|
|
||||||
|
def test_args_kwargs_request_action_map_on_self(self):
|
||||||
|
"""
|
||||||
|
Test a view only has args, kwargs, request, action_map
|
||||||
|
once `as_view` has been called.
|
||||||
|
"""
|
||||||
|
bare_view = InstanceViewSet()
|
||||||
|
view = InstanceViewSet.as_view(actions={
|
||||||
|
'get': 'dummy',
|
||||||
|
})(factory.get('/')).data['view']
|
||||||
|
|
||||||
|
for attribute in ('args', 'kwargs', 'request', 'action_map'):
|
||||||
|
self.assertNotIn(attribute, dir(bare_view))
|
||||||
|
self.assertIn(attribute, dir(view))
|
||||||
|
|
Loading…
Reference in New Issue
Block a user