Skip to content

Commit 233d8d9

Browse files
authored
Merge branch 'master' into dj5
2 parents 744a191 + 37ec04d commit 233d8d9

File tree

10 files changed

+57
-23
lines changed

10 files changed

+57
-23
lines changed

docs/api-guide/validators.md

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -173,11 +173,9 @@ If you want the date field to be entirely hidden from the user, then use `Hidden
173173
# Advanced field defaults
174174

175175
Validators that are applied across multiple fields in the serializer can sometimes require a field input that should not be provided by the API client, but that *is* available as input to the validator.
176+
For this purposes use `HiddenField`. This field will be present in `validated_data` but *will not* be used in the serializer output representation.
176177

177-
Two patterns that you may want to use for this sort of validation include:
178-
179-
* Using `HiddenField`. This field will be present in `validated_data` but *will not* be used in the serializer output representation.
180-
* Using a standard field with `read_only=True`, but that also includes a `default=…` argument. This field *will* be used in the serializer output representation, but cannot be set directly by the user.
178+
**Note:** Using a `read_only=True` field is excluded from writable fields so it won't use a `default=…` argument. Look [3.8 announcement](https://www.django-rest-framework.org/community/3.8-announcement/#altered-the-behaviour-of-read_only-plus-default-on-field).
181179

182180
REST framework includes a couple of defaults that may be useful in this context.
183181

@@ -189,7 +187,7 @@ A default class that can be used to represent the current user. In order to use
189187
default=serializers.CurrentUserDefault()
190188
)
191189

192-
#### CreateOnlyDefault
190+
#### CreateOnlyDefault
193191

194192
A default class that can be used to *only set a default argument during create operations*. During updates the field is omitted.
195193

docs/api-guide/viewsets.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -311,7 +311,7 @@ You may need to provide custom `ViewSet` classes that do not have the full set o
311311

312312
To create a base viewset class that provides `create`, `list` and `retrieve` operations, inherit from `GenericViewSet`, and mixin the required actions:
313313

314-
from rest_framework import mixins
314+
from rest_framework import mixins, viewsets
315315

316316
class CreateListRetrieveViewSet(mixins.CreateModelMixin,
317317
mixins.ListModelMixin,

docs/community/3.14-announcement.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,10 @@ Our requirements are now:
2828
* Python 3.6+
2929
* Django 4.1, 4.0, 3.2, 3.1, 3.0
3030

31-
## `raise_exceptions` argument for `is_valid` is now keyword-only.
31+
## `raise_exception` argument for `is_valid` is now keyword-only.
3232

3333
Calling `serializer_instance.is_valid(True)` is no longer acceptable syntax.
34-
If you'd like to use the `raise_exceptions` argument, you must use it as a
34+
If you'd like to use the `raise_exception` argument, you must use it as a
3535
keyword argument.
3636

3737
See Pull Request [#7952](https://github.com/encode/django-rest-framework/pull/7952) for more details.

docs/community/third-party-packages.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -154,8 +154,8 @@ To submit new content, [open an issue][drf-create-issue] or [create a pull reque
154154

155155
### Customization
156156

157-
* [rest-framework-redesign][rest-framework-redesign] - A package for customizing the API using Bootstrap 5.
158-
* [rest-framework-material][rest-framework-material] - Material design for Django REST Framework API.
157+
* [drf-redesign][drf-redesign] - A project that gives a fresh look to the browse-able API using Bootstrap 5.
158+
* [drf-material][drf-material] - A project that gives a sleek and elegant look to the browsable API using Material Design.
159159

160160
[cite]: http://www.software-ecosystems.com/Software_Ecosystems/Ecosystems.html
161161
[cookiecutter]: https://github.com/jpadilla/cookiecutter-django-rest-framework
@@ -248,5 +248,5 @@ To submit new content, [open an issue][drf-create-issue] or [create a pull reque
248248
[django-requestlogs]: https://github.com/Raekkeri/django-requestlogs
249249
[drf-standardized-errors]: https://github.com/ghazi-git/drf-standardized-errors
250250
[drf-api-action]: https://github.com/Ori-Roza/drf-api-action
251-
[rest-framework-redesign]: https://github.com/youzarsiph/rest-framework-redesign
252-
[rest-framework-material]: https://github.com/youzarsiph/rest-framework-material
251+
[drf-redesign]: https://github.com/youzarsiph/drf-redesign
252+
[drf-material]: https://github.com/youzarsiph/drf-material

rest_framework/templates/rest_framework/admin.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@
4242
<ul class="nav navbar-nav pull-right">
4343
{% block userlinks %}
4444
{% if user.is_authenticated %}
45-
{% optional_logout request user %}
45+
{% optional_logout request user csrf_token %}
4646
{% else %}
4747
{% optional_login request %}
4848
{% endif %}

rest_framework/templates/rest_framework/base.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@
4646
<ul class="nav navbar-nav pull-right">
4747
{% block userlinks %}
4848
{% if user.is_authenticated %}
49-
{% optional_logout request user %}
49+
{% optional_logout request user csrf_token %}
5050
{% else %}
5151
{% optional_login request %}
5252
{% endif %}

rest_framework/templatetags/rest_framework.py

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ def optional_docs_login(request):
119119

120120

121121
@register.simple_tag
122-
def optional_logout(request, user):
122+
def optional_logout(request, user, csrf_token):
123123
"""
124124
Include a logout snippet if REST framework's logout view is in the URLconf.
125125
"""
@@ -135,11 +135,16 @@ def optional_logout(request, user):
135135
<b class="caret"></b>
136136
</a>
137137
<ul class="dropdown-menu">
138-
<li><a href='{href}?next={next}'>Log out</a></li>
138+
<form id="logoutForm" method="post" action="{href}?next={next}">
139+
<input type="hidden" name="csrfmiddlewaretoken" value="{csrf_token}">
140+
</form>
141+
<li>
142+
<a href="#" onclick='document.getElementById("logoutForm").submit()'>Log out</a>
143+
</li>
139144
</ul>
140145
</li>"""
141-
snippet = format_html(snippet, user=escape(user), href=logout_url, next=escape(request.path))
142-
146+
snippet = format_html(snippet, user=escape(user), href=logout_url,
147+
next=escape(request.path), csrf_token=csrf_token)
143148
return mark_safe(snippet)
144149

145150

rest_framework/validators.py

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -160,10 +160,19 @@ def __call__(self, attrs, serializer):
160160
queryset = self.exclude_current_instance(attrs, queryset, serializer.instance)
161161

162162
# Ignore validation if any field is None
163-
checked_values = [
164-
value for field, value in attrs.items() if field in self.fields
165-
]
166-
if None not in checked_values and qs_exists(queryset):
163+
if serializer.instance is None:
164+
checked_values = [
165+
value for field, value in attrs.items() if field in self.fields
166+
]
167+
else:
168+
# Ignore validation if all field values are unchanged
169+
checked_values = [
170+
value
171+
for field, value in attrs.items()
172+
if field in self.fields and value != getattr(serializer.instance, field)
173+
]
174+
175+
if checked_values and None not in checked_values and qs_exists(queryset):
167176
field_names = ', '.join(self.fields)
168177
message = self.message.format(field_names=field_names)
169178
raise ValidationError(message, code='unique')

tests/browsable_api/test_browsable_api.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,12 @@ def test_login_shown_when_logged_out(self):
6565
content = response.content.decode()
6666
assert '>Log in<' in content
6767

68+
def test_dropdown_contains_logout_form(self):
69+
self.client.login(username=self.username, password=self.password)
70+
response = self.client.get('/')
71+
content = response.content.decode()
72+
assert '<form id="logoutForm" method="post" action="/auth/logout/?next=/">' in content
73+
6874

6975
@override_settings(ROOT_URLCONF='tests.browsable_api.no_auth_urls')
7076
class NoDropdownWithoutAuthTests(TestCase):

tests/test_validators.py

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import datetime
22
import re
3-
from unittest.mock import MagicMock
3+
from unittest.mock import MagicMock, patch
44

55
import pytest
66
from django import VERSION as django_version
@@ -453,6 +453,22 @@ def test_do_not_ignore_validation_for_null_fields(self):
453453
serializer = NullUniquenessTogetherSerializer(data=data)
454454
assert not serializer.is_valid()
455455

456+
def test_ignore_validation_for_unchanged_fields(self):
457+
"""
458+
If all fields in the unique together constraint are unchanged,
459+
then the instance should skip uniqueness validation.
460+
"""
461+
instance = UniquenessTogetherModel.objects.create(
462+
race_name="Paris Marathon", position=1
463+
)
464+
data = {"race_name": "Paris Marathon", "position": 1}
465+
serializer = UniquenessTogetherSerializer(data=data, instance=instance)
466+
with patch(
467+
"rest_framework.validators.qs_exists"
468+
) as mock:
469+
assert serializer.is_valid()
470+
assert not mock.called
471+
456472
def test_filter_queryset_do_not_skip_existing_attribute(self):
457473
"""
458474
filter_queryset should add value from existing instance attribute

0 commit comments

Comments
 (0)