Merge branch 'master' into localizedfloatfield

This commit is contained in:
kgeorgy 2017-03-31 16:14:17 +02:00 committed by GitHub
commit 479f3233ff
13 changed files with 116 additions and 84 deletions

View File

@ -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

View File

@ -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

View File

@ -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.
> >

View File

@ -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

View File

@ -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),
... ...
] ]

View File

@ -1,4 +1,4 @@
{% extends "base.html" %} {% extends "main.html" %}
{% block content %} {% block content %}

View File

@ -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-->

View File

@ -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 %}

View File

@ -1,2 +1,2 @@
# MkDocs to build our documentation. # MkDocs to build our documentation.
mkdocs==0.15.3 mkdocs==0.16.2

View File

@ -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,

View File

@ -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'
) )
} }

View File

@ -123,18 +123,70 @@ 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:
# Ensure that max_value is passed explicitly as a keyword arg,
# rather than as a validator.
max_value = next((
validator.limit_value for validator in validator_kwarg
if isinstance(validator, validators.MaxValueValidator)
), None)
if max_value is not None and isinstance(model_field, NUMERIC_FIELD_TYPES):
kwargs['max_value'] = max_value
validator_kwarg = [
validator for validator in validator_kwarg
if not isinstance(validator, validators.MaxValueValidator)
]
# Our decimal validation is handled in the field code, not validator code. # Ensure that max_value is passed explicitly as a keyword arg,
# (In Django 1.9+ this differs from previous style) # rather than as a validator.
if isinstance(model_field, models.DecimalField) and DecimalValidator: min_value = next((
validator_kwarg = [ validator.limit_value for validator in validator_kwarg
validator for validator in validator_kwarg if isinstance(validator, validators.MinValueValidator)
if not isinstance(validator, DecimalValidator) ), None)
] if min_value is not None and isinstance(model_field, NUMERIC_FIELD_TYPES):
kwargs['min_value'] = min_value
validator_kwarg = [
validator for validator in validator_kwarg
if not isinstance(validator, validators.MinValueValidator)
]
# URLField does not need to include the URLValidator argument,
# as it is explicitly added in.
if isinstance(model_field, models.URLField):
validator_kwarg = [
validator for validator in validator_kwarg
if not isinstance(validator, validators.URLValidator)
]
# EmailField does not need to include the validate_email argument,
# as it is explicitly added in.
if isinstance(model_field, models.EmailField):
validator_kwarg = [
validator for validator in validator_kwarg
if validator is not validators.validate_email
]
# SlugField do not need to include the 'validate_slug' argument,
if isinstance(model_field, models.SlugField):
validator_kwarg = [
validator for validator in validator_kwarg
if validator is not validators.validate_slug
]
# IPAddressField do not need to include the 'validate_ipv46_address' argument,
if isinstance(model_field, models.GenericIPAddressField):
validator_kwarg = [
validator for validator in validator_kwarg
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, # Ensure that max_length is passed explicitly as a keyword arg,
# rather than as a validator. # rather than as a validator.
@ -160,62 +212,6 @@ def get_field_kwargs(field_name, model_field):
if not isinstance(validator, validators.MinLengthValidator) if not isinstance(validator, validators.MinLengthValidator)
] ]
# Ensure that max_value is passed explicitly as a keyword arg,
# rather than as a validator.
max_value = next((
validator.limit_value for validator in validator_kwarg
if isinstance(validator, validators.MaxValueValidator)
), None)
if max_value is not None and isinstance(model_field, NUMERIC_FIELD_TYPES):
kwargs['max_value'] = max_value
validator_kwarg = [
validator for validator in validator_kwarg
if not isinstance(validator, validators.MaxValueValidator)
]
# Ensure that max_value is passed explicitly as a keyword arg,
# rather than as a validator.
min_value = next((
validator.limit_value for validator in validator_kwarg
if isinstance(validator, validators.MinValueValidator)
), None)
if min_value is not None and isinstance(model_field, NUMERIC_FIELD_TYPES):
kwargs['min_value'] = min_value
validator_kwarg = [
validator for validator in validator_kwarg
if not isinstance(validator, validators.MinValueValidator)
]
# URLField does not need to include the URLValidator argument,
# as it is explicitly added in.
if isinstance(model_field, models.URLField):
validator_kwarg = [
validator for validator in validator_kwarg
if not isinstance(validator, validators.URLValidator)
]
# EmailField does not need to include the validate_email argument,
# as it is explicitly added in.
if isinstance(model_field, models.EmailField):
validator_kwarg = [
validator for validator in validator_kwarg
if validator is not validators.validate_email
]
# SlugField do not need to include the 'validate_slug' argument,
if isinstance(model_field, models.SlugField):
validator_kwarg = [
validator for validator in validator_kwarg
if validator is not validators.validate_slug
]
# IPAddressField do not need to include the 'validate_ipv46_address' argument,
if isinstance(model_field, models.GenericIPAddressField):
validator_kwarg = [
validator for validator in validator_kwarg
if validator is not validators.validate_ipv46_address
]
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)
if unique_error_message: if unique_error_message:

View File

@ -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.']}