mirror of
https://github.com/encode/django-rest-framework.git
synced 2025-08-05 04:50:12 +03:00
Merge branch 'master' into localizedfloatfield
This commit is contained in:
commit
479f3233ff
|
@ -356,6 +356,10 @@ HTTP Signature (currently a [IETF draft][http-signature-ietf-draft]) provides a
|
||||||
|
|
||||||
[Django-rest-knox][django-rest-knox] library provides models and views to handle token based authentication in a more secure and extensible way than the built-in TokenAuthentication scheme - with Single Page Applications and Mobile clients in mind. It provides per-client tokens, and views to generate them when provided some other authentication (usually basic authentication), to delete the token (providing a server enforced logout) and to delete all tokens (logs out all clients that a user is logged into).
|
[Django-rest-knox][django-rest-knox] library provides models and views to handle token based authentication in a more secure and extensible way than the built-in TokenAuthentication scheme - with Single Page Applications and Mobile clients in mind. It provides per-client tokens, and views to generate them when provided some other authentication (usually basic authentication), to delete the token (providing a server enforced logout) and to delete all tokens (logs out all clients that a user is logged into).
|
||||||
|
|
||||||
|
## drfpasswordless
|
||||||
|
|
||||||
|
[drfpasswordless][drfpasswordless] adds (Medium, Square Cash inspired) passwordless support to Django REST Framework's own TokenAuthentication scheme. Users log in and sign up with a token sent to a contact point like an email address or a mobile number.
|
||||||
|
|
||||||
[cite]: http://jacobian.org/writing/rest-worst-practices/
|
[cite]: http://jacobian.org/writing/rest-worst-practices/
|
||||||
[http401]: http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.2
|
[http401]: http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.2
|
||||||
[http403]: http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.4
|
[http403]: http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.4
|
||||||
|
@ -396,3 +400,4 @@ HTTP Signature (currently a [IETF draft][http-signature-ietf-draft]) provides a
|
||||||
[django-rest-auth]: https://github.com/Tivix/django-rest-auth
|
[django-rest-auth]: https://github.com/Tivix/django-rest-auth
|
||||||
[django-rest-framework-social-oauth2]: https://github.com/PhilipGarnero/django-rest-framework-social-oauth2
|
[django-rest-framework-social-oauth2]: https://github.com/PhilipGarnero/django-rest-framework-social-oauth2
|
||||||
[django-rest-knox]: https://github.com/James1345/django-rest-knox
|
[django-rest-knox]: https://github.com/James1345/django-rest-knox
|
||||||
|
[drfpasswordless]: https://github.com/aaronn/django-rest-framework-passwordless
|
||||||
|
|
|
@ -142,7 +142,7 @@ Note that you can use both an overridden `.get_queryset()` and generic filtering
|
||||||
The `django-filter` library includes a `DjangoFilterBackend` class which
|
The `django-filter` library includes a `DjangoFilterBackend` class which
|
||||||
supports highly customizable field filtering for REST framework.
|
supports highly customizable field filtering for REST framework.
|
||||||
|
|
||||||
To use `DjangoFilterBackend`, first install `django-filter`.
|
To use `DjangoFilterBackend`, first install `django-filter`. Then add `django_filters` to Django's `INSTALLED_APPS`
|
||||||
|
|
||||||
pip install django-filter
|
pip install django-filter
|
||||||
|
|
||||||
|
|
|
@ -140,10 +140,14 @@ Sign up for a paid plan today, and help ensure that REST framework becomes a sus
|
||||||
>
|
>
|
||||||
> — José Padilla, Django REST framework contributor
|
> — José Padilla, Django REST framework contributor
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
> The number one feature of the Python programming language is its community. Such a community is only possible because of the Open Source nature of the language and all the culture that comes from it. Building great Open Source projects require great minds. Given that, we at Vinta are not only proud to sponsor the team behind DRF but we also recognize the ROI that comes from it.
|
> The number one feature of the Python programming language is its community. Such a community is only possible because of the Open Source nature of the language and all the culture that comes from it. Building great Open Source projects require great minds. Given that, we at Vinta are not only proud to sponsor the team behind DRF but we also recognize the ROI that comes from it.
|
||||||
>
|
>
|
||||||
> — Filipe Ximenes, Vinta Software
|
> — Filipe Ximenes, Vinta Software
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
> It's really awesome that this project continues to endure. The code base is top notch and the maintainers are committed to the highest level of quality.
|
> It's really awesome that this project continues to endure. The code base is top notch and the maintainers are committed to the highest level of quality.
|
||||||
DRF is one of the core reasons why Django is top choice among web frameworks today. In my opinion, it sets the standard for rest frameworks for the development community at large.
|
DRF is one of the core reasons why Django is top choice among web frameworks today. In my opinion, it sets the standard for rest frameworks for the development community at large.
|
||||||
>
|
>
|
||||||
|
|
|
@ -190,6 +190,7 @@ To submit new content, [open an issue][drf-create-issue] or [create a pull reque
|
||||||
* [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.
|
||||||
* [django-rest-auth][django-rest-auth] - Provides a set of REST API endpoints for registration, authentication (including social media authentication), password reset, retrieve and update user details, etc.
|
* [django-rest-auth][django-rest-auth] - Provides a set of REST API endpoints for registration, authentication (including social media authentication), password reset, retrieve and update user details, etc.
|
||||||
* [drf-oidc-auth][drf-oidc-auth] - Implements OpenID Connect token authentication for DRF.
|
* [drf-oidc-auth][drf-oidc-auth] - Implements OpenID Connect token authentication for DRF.
|
||||||
|
* [drfpasswordless][drfpasswordless] - Adds (Medium, Square Cash inspired) passwordless logins and signups via email and mobile numbers.
|
||||||
|
|
||||||
### Permissions
|
### Permissions
|
||||||
|
|
||||||
|
@ -330,3 +331,4 @@ To submit new content, [open an issue][drf-create-issue] or [create a pull reque
|
||||||
[drf-oidc-auth]: https://github.com/ByteInternet/drf-oidc-auth
|
[drf-oidc-auth]: https://github.com/ByteInternet/drf-oidc-auth
|
||||||
[drf-serializer-extensions]: https://github.com/evenicoulddoit/django-rest-framework-serializer-extensions
|
[drf-serializer-extensions]: https://github.com/evenicoulddoit/django-rest-framework-serializer-extensions
|
||||||
[djangorestframework-queryfields]: https://github.com/wimglenn/djangorestframework-queryfields
|
[djangorestframework-queryfields]: https://github.com/wimglenn/djangorestframework-queryfields
|
||||||
|
[drfpasswordless]: https://github.com/aaronn/django-rest-framework-passwordless
|
||||||
|
|
|
@ -41,7 +41,7 @@ view in our URL configuration.
|
||||||
schema_view = get_schema_view(title='Pastebin API')
|
schema_view = get_schema_view(title='Pastebin API')
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
url(r'^schema/$', schema_view),
|
url(r'^schema/$', schema_view),
|
||||||
...
|
...
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
{% extends "base.html" %}
|
{% extends "main.html" %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
|
||||||
|
|
|
@ -4,11 +4,11 @@
|
||||||
<head>
|
<head>
|
||||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<title>{% if page_title %}{{ page_title }} - {% endif %}{{ site_name }}</title>
|
<title>{% if page.title %}{{ page.title }} - {% endif %}{{ config.site_name }}</title>
|
||||||
<link href="{{ base_url }}/img/favicon.ico" rel="icon" type="image/x-icon">
|
<link href="{{ base_url }}/img/favicon.ico" rel="icon" type="image/x-icon">
|
||||||
<link rel="canonical" href="{{ canonical_url }}" />
|
<link rel="canonical" href="{{ page.canonical_url }}" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<meta name="description" content="Django, API, REST{% if current_page %}, {{ current_page.title }}{% endif %}">
|
<meta name="description" content="Django, API, REST{% if page %}, {{ page.title }}{% endif %}">
|
||||||
<meta name="author" content="Tom Christie">
|
<meta name="author" content="Tom Christie">
|
||||||
|
|
||||||
<!-- Le styles -->
|
<!-- Le styles -->
|
||||||
|
@ -51,7 +51,7 @@
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body onload="prettyPrint()" class="{% if current_page and current_page.is_homepage %}index{% endif %}-page">
|
<body onload="prettyPrint()" class="{% if page and page.is_homepage %}index{% endif %}-page">
|
||||||
|
|
||||||
<div class="wrapper">
|
<div class="wrapper">
|
||||||
{% include "nav.html" %}
|
{% include "nav.html" %}
|
||||||
|
@ -83,14 +83,14 @@
|
||||||
<div class="span3">
|
<div class="span3">
|
||||||
<div id="table-of-contents">
|
<div id="table-of-contents">
|
||||||
<ul class="nav nav-list side-nav well sidebar-nav-fixed">
|
<ul class="nav nav-list side-nav well sidebar-nav-fixed">
|
||||||
{% if current_page and current_page.is_homepage %}
|
{% if page and page.is_homepage %}
|
||||||
<li class="main">
|
<li class="main">
|
||||||
<a href="#">Django REST framework</a>
|
<a href="#">Django REST framework</a>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% for toc_item in toc %}
|
{% for toc_item in page.toc %}
|
||||||
<li class="{% if current_page and not current_page.is_homepage %}main{% endif %}">
|
<li class="{% if page and not page.is_homepage %}main{% endif %}">
|
||||||
<a href="{{ toc_item.url }}">{{ toc_item.title }}</a>
|
<a href="{{ toc_item.url }}">{{ toc_item.title }}</a>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
|
@ -112,15 +112,15 @@
|
||||||
|
|
||||||
<div id="main-content" class="span9">
|
<div id="main-content" class="span9">
|
||||||
{% block content %}
|
{% block content %}
|
||||||
{% if meta.source %}
|
{% if page.meta.source %}
|
||||||
{% for filename in meta.source %}
|
{% for filename in page.meta.source %}
|
||||||
<a class="github" href="https://github.com/tomchristie/django-rest-framework/tree/master/rest_framework/{{ filename }}">
|
<a class="github" href="https://github.com/tomchristie/django-rest-framework/tree/master/rest_framework/{{ filename }}">
|
||||||
<span class="label label-info">{{ filename }}</span>
|
<span class="label label-info">{{ filename }}</span>
|
||||||
</a>
|
</a>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{{ content }}
|
{{ page.content }}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
</div> <!--/span-->
|
</div> <!--/span-->
|
|
@ -2,10 +2,10 @@
|
||||||
<div class="navbar-inner">
|
<div class="navbar-inner">
|
||||||
<div class="container-fluid">
|
<div class="container-fluid">
|
||||||
<a class="repo-link btn btn-primary btn-small" href="https://github.com/tomchristie/django-rest-framework/tree/master">GitHub</a>
|
<a class="repo-link btn btn-primary btn-small" href="https://github.com/tomchristie/django-rest-framework/tree/master">GitHub</a>
|
||||||
<a class="repo-link btn btn-inverse btn-small {% if not next_page %}disabled{% endif %}" rel="prev" {% if next_page %}href="{{ next_page.url }}"{% endif %}>
|
<a class="repo-link btn btn-inverse btn-small {% if not page.next_page %}disabled{% endif %}" rel="prev" {% if page.next_page %}href="{{ page.next_page.url }}"{% endif %}>
|
||||||
Next <i class="icon-arrow-right icon-white"></i>
|
Next <i class="icon-arrow-right icon-white"></i>
|
||||||
</a>
|
</a>
|
||||||
<a class="repo-link btn btn-inverse btn-small {% if not previous_page %}disabled{% endif %}" rel="next" {% if previous_page %}href="{{ previous_page.url }}"{% endif %}>
|
<a class="repo-link btn btn-inverse btn-small {% if not page.previous_page %}disabled{% endif %}" rel="next" {% if page.previous_page %}href="{{ page.previous_page.url }}"{% endif %}>
|
||||||
<i class="icon-arrow-left icon-white"></i> Previous
|
<i class="icon-arrow-left icon-white"></i> Previous
|
||||||
</a>
|
</a>
|
||||||
<a id="search_modal_show" class="repo-link btn btn-inverse btn-small" href="#mkdocs_search_modal" data-toggle="modal" data-target="#mkdocs_search_modal"><i class="icon-search icon-white"></i> Search</a>
|
<a id="search_modal_show" class="repo-link btn btn-inverse btn-small" href="#mkdocs_search_modal" data-toggle="modal" data-target="#mkdocs_search_modal"><i class="icon-search icon-white"></i> Search</a>
|
||||||
|
@ -16,7 +16,7 @@
|
||||||
</a>
|
</a>
|
||||||
<a class="brand" href="http://www.django-rest-framework.org">Django REST framework</a>
|
<a class="brand" href="http://www.django-rest-framework.org">Django REST framework</a>
|
||||||
<div class="nav-collapse collapse">
|
<div class="nav-collapse collapse">
|
||||||
{% if include_nav %}
|
{% if nav|length>1 %}
|
||||||
<!-- Main navigation -->
|
<!-- Main navigation -->
|
||||||
<ul class="nav navbar-nav">
|
<ul class="nav navbar-nav">
|
||||||
{% for nav_item in nav %} {% if nav_item.children %}
|
{% for nav_item in nav %} {% if nav_item.children %}
|
||||||
|
|
|
@ -1,2 +1,2 @@
|
||||||
# MkDocs to build our documentation.
|
# MkDocs to build our documentation.
|
||||||
mkdocs==0.15.3
|
mkdocs==0.16.2
|
||||||
|
|
|
@ -651,6 +651,7 @@ class BooleanField(Field):
|
||||||
initial = False
|
initial = False
|
||||||
TRUE_VALUES = {
|
TRUE_VALUES = {
|
||||||
't', 'T',
|
't', 'T',
|
||||||
|
'y', 'Y', 'yes', 'YES',
|
||||||
'true', 'True', 'TRUE',
|
'true', 'True', 'TRUE',
|
||||||
'on', 'On', 'ON',
|
'on', 'On', 'ON',
|
||||||
'1', 1,
|
'1', 1,
|
||||||
|
@ -658,6 +659,7 @@ class BooleanField(Field):
|
||||||
}
|
}
|
||||||
FALSE_VALUES = {
|
FALSE_VALUES = {
|
||||||
'f', 'F',
|
'f', 'F',
|
||||||
|
'n', 'N', 'no', 'NO',
|
||||||
'false', 'False', 'FALSE',
|
'false', 'False', 'FALSE',
|
||||||
'off', 'Off', 'OFF',
|
'off', 'Off', 'OFF',
|
||||||
'0', 0, 0.0,
|
'0', 0, 0.0,
|
||||||
|
|
|
@ -9,7 +9,8 @@ REST_FRAMEWORK = {
|
||||||
)
|
)
|
||||||
'DEFAULT_PARSER_CLASSES': (
|
'DEFAULT_PARSER_CLASSES': (
|
||||||
'rest_framework.parsers.JSONParser',
|
'rest_framework.parsers.JSONParser',
|
||||||
'rest_framework.parsers.TemplateHTMLRenderer',
|
'rest_framework.parsers.FormParser',
|
||||||
|
'rest_framework.parsers.MultiPartParser'
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -123,43 +123,8 @@ def get_field_kwargs(field_name, model_field):
|
||||||
kwargs['allow_folders'] = model_field.allow_folders
|
kwargs['allow_folders'] = model_field.allow_folders
|
||||||
|
|
||||||
if model_field.choices:
|
if model_field.choices:
|
||||||
# If this model field contains choices, then return early.
|
|
||||||
# Further keyword arguments are not valid.
|
|
||||||
kwargs['choices'] = model_field.choices
|
kwargs['choices'] = model_field.choices
|
||||||
return kwargs
|
else:
|
||||||
|
|
||||||
# Our decimal validation is handled in the field code, not validator code.
|
|
||||||
# (In Django 1.9+ this differs from previous style)
|
|
||||||
if isinstance(model_field, models.DecimalField) and DecimalValidator:
|
|
||||||
validator_kwarg = [
|
|
||||||
validator for validator in validator_kwarg
|
|
||||||
if not isinstance(validator, DecimalValidator)
|
|
||||||
]
|
|
||||||
|
|
||||||
# Ensure that max_length is passed explicitly as a keyword arg,
|
|
||||||
# rather than as a validator.
|
|
||||||
max_length = getattr(model_field, 'max_length', None)
|
|
||||||
if max_length is not None and (isinstance(model_field, models.CharField) or
|
|
||||||
isinstance(model_field, models.TextField)):
|
|
||||||
kwargs['max_length'] = max_length
|
|
||||||
validator_kwarg = [
|
|
||||||
validator for validator in validator_kwarg
|
|
||||||
if not isinstance(validator, validators.MaxLengthValidator)
|
|
||||||
]
|
|
||||||
|
|
||||||
# Ensure that min_length is passed explicitly as a keyword arg,
|
|
||||||
# rather than as a validator.
|
|
||||||
min_length = next((
|
|
||||||
validator.limit_value for validator in validator_kwarg
|
|
||||||
if isinstance(validator, validators.MinLengthValidator)
|
|
||||||
), None)
|
|
||||||
if min_length is not None and isinstance(model_field, models.CharField):
|
|
||||||
kwargs['min_length'] = min_length
|
|
||||||
validator_kwarg = [
|
|
||||||
validator for validator in validator_kwarg
|
|
||||||
if not isinstance(validator, validators.MinLengthValidator)
|
|
||||||
]
|
|
||||||
|
|
||||||
# Ensure that max_value is passed explicitly as a keyword arg,
|
# Ensure that max_value is passed explicitly as a keyword arg,
|
||||||
# rather than as a validator.
|
# rather than as a validator.
|
||||||
max_value = next((
|
max_value = next((
|
||||||
|
@ -215,6 +180,37 @@ def get_field_kwargs(field_name, model_field):
|
||||||
validator for validator in validator_kwarg
|
validator for validator in validator_kwarg
|
||||||
if validator is not validators.validate_ipv46_address
|
if validator is not validators.validate_ipv46_address
|
||||||
]
|
]
|
||||||
|
# Our decimal validation is handled in the field code, not validator code.
|
||||||
|
# (In Django 1.9+ this differs from previous style)
|
||||||
|
if isinstance(model_field, models.DecimalField) and DecimalValidator:
|
||||||
|
validator_kwarg = [
|
||||||
|
validator for validator in validator_kwarg
|
||||||
|
if not isinstance(validator, DecimalValidator)
|
||||||
|
]
|
||||||
|
|
||||||
|
# Ensure that max_length is passed explicitly as a keyword arg,
|
||||||
|
# rather than as a validator.
|
||||||
|
max_length = getattr(model_field, 'max_length', None)
|
||||||
|
if max_length is not None and (isinstance(model_field, models.CharField) or
|
||||||
|
isinstance(model_field, models.TextField)):
|
||||||
|
kwargs['max_length'] = max_length
|
||||||
|
validator_kwarg = [
|
||||||
|
validator for validator in validator_kwarg
|
||||||
|
if not isinstance(validator, validators.MaxLengthValidator)
|
||||||
|
]
|
||||||
|
|
||||||
|
# Ensure that min_length is passed explicitly as a keyword arg,
|
||||||
|
# rather than as a validator.
|
||||||
|
min_length = next((
|
||||||
|
validator.limit_value for validator in validator_kwarg
|
||||||
|
if isinstance(validator, validators.MinLengthValidator)
|
||||||
|
), None)
|
||||||
|
if min_length is not None and isinstance(model_field, models.CharField):
|
||||||
|
kwargs['min_length'] = min_length
|
||||||
|
validator_kwarg = [
|
||||||
|
validator for validator in validator_kwarg
|
||||||
|
if not isinstance(validator, validators.MinLengthValidator)
|
||||||
|
]
|
||||||
|
|
||||||
if getattr(model_field, 'unique', False):
|
if getattr(model_field, 'unique', False):
|
||||||
unique_error_message = model_field.error_messages.get('unique', None)
|
unique_error_message = model_field.error_messages.get('unique', None)
|
||||||
|
|
|
@ -99,6 +99,15 @@ class Issue3674ChildModel(models.Model):
|
||||||
value = models.CharField(primary_key=True, max_length=64)
|
value = models.CharField(primary_key=True, max_length=64)
|
||||||
|
|
||||||
|
|
||||||
|
class UniqueChoiceModel(models.Model):
|
||||||
|
CHOICES = (
|
||||||
|
('choice1', 'choice 1'),
|
||||||
|
('choice2', 'choice 1'),
|
||||||
|
)
|
||||||
|
|
||||||
|
name = models.CharField(max_length=254, unique=True, choices=CHOICES)
|
||||||
|
|
||||||
|
|
||||||
class TestModelSerializer(TestCase):
|
class TestModelSerializer(TestCase):
|
||||||
def test_create_method(self):
|
def test_create_method(self):
|
||||||
class TestSerializer(serializers.ModelSerializer):
|
class TestSerializer(serializers.ModelSerializer):
|
||||||
|
@ -1080,3 +1089,16 @@ class Issue4897TestCase(TestCase):
|
||||||
with pytest.raises(AssertionError) as cm:
|
with pytest.raises(AssertionError) as cm:
|
||||||
TestSerializer(obj).fields
|
TestSerializer(obj).fields
|
||||||
cm.match(r'readonly_fields')
|
cm.match(r'readonly_fields')
|
||||||
|
|
||||||
|
|
||||||
|
class Test5004UniqueChoiceField(TestCase):
|
||||||
|
def test_unique_choice_field(self):
|
||||||
|
class TestUniqueChoiceSerializer(serializers.ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = UniqueChoiceModel
|
||||||
|
fields = '__all__'
|
||||||
|
|
||||||
|
UniqueChoiceModel.objects.create(name='choice1')
|
||||||
|
serializer = TestUniqueChoiceSerializer(data={'name': 'choice1'})
|
||||||
|
assert not serializer.is_valid()
|
||||||
|
assert serializer.errors == {'name': ['unique choice model with this name already exists.']}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user