From 3e956df6eb7e3b645d334fec372ad7f8a487d765 Mon Sep 17 00:00:00 2001 From: Tonye Jack Date: Tue, 2 Feb 2021 20:54:21 -0500 Subject: [PATCH 01/25] Fixed test --- requirements/requirements-testing.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements/requirements-testing.txt b/requirements/requirements-testing.txt index ad246e857..99463560e 100644 --- a/requirements/requirements-testing.txt +++ b/requirements/requirements-testing.txt @@ -2,3 +2,4 @@ pytest>=5.4.1,<5.5 pytest-django>=3.9.0,<3.10 pytest-cov>=2.7.1 +six>=1.14.0 From 1ec0f86b585cd87e4b413aeaad1ecc947bacfef2 Mon Sep 17 00:00:00 2001 From: Asif Saif Uddin Date: Tue, 16 Feb 2021 18:17:29 +0600 Subject: [PATCH 02/25] Dj32 (#7713) adds django 3.2 line to the build matrix --- .gitignore | 2 ++ .travis.yml | 7 +++++-- tox.ini | 5 ++++- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index 41768084c..82e885ede 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,8 @@ *.db *~ .* +*.py.bak + /site/ /htmlcov/ diff --git a/.travis.yml b/.travis.yml index 7a820766e..f9f22336f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,18 +10,20 @@ matrix: - { python: "3.6", env: DJANGO=2.2 } - { python: "3.6", env: DJANGO=3.0 } - { python: "3.6", env: DJANGO=3.1 } - - { python: "3.6", env: DJANGO=master } + - { python: "3.6", env: DJANGO=3.2 } - { python: "3.7", env: DJANGO=2.2 } - { python: "3.7", env: DJANGO=3.0 } - { python: "3.7", env: DJANGO=3.1 } - - { python: "3.7", env: DJANGO=master } + - { python: "3.7", env: DJANGO=3.2 } - { python: "3.8", env: DJANGO=3.0 } - { python: "3.8", env: DJANGO=3.1 } + - { python: "3.8", env: DJANGO=3.2 } - { python: "3.8", env: DJANGO=master } - { python: "3.9", env: DJANGO=3.1 } + - { python: "3.9", env: DJANGO=3.2 } - { python: "3.9", env: DJANGO=master } - { python: "3.8", env: TOXENV=base } @@ -38,6 +40,7 @@ matrix: allow_failures: - env: DJANGO=master + - env: DJANGO=3.2 install: - pip install tox tox-travis diff --git a/tox.ini b/tox.ini index df6387d5e..544bab163 100644 --- a/tox.ini +++ b/tox.ini @@ -3,7 +3,8 @@ envlist = {py35,py36,py37}-django22, {py36,py37,py38}-django30, {py36,py37,py38,py39}-django31, - {py36,py37,py38,py39}-djangomaster, + {py36,py37,py38,py39}-django32, + {py38,py39}-djangomaster, base,dist,lint,docs, [travis:env] @@ -11,6 +12,7 @@ DJANGO = 2.2: django22 3.0: django30 3.1: django31 + 3.2: django32 master: djangomaster [testenv] @@ -23,6 +25,7 @@ deps = django22: Django>=2.2,<3.0 django30: Django>=3.0,<3.1 django31: Django>=3.1,<3.2 + django32: Django>=3.2a1,<4.0 djangomaster: https://github.com/django/django/archive/master.tar.gz -rrequirements/requirements-testing.txt -rrequirements/requirements-optionals.txt From 8f6d2d2f9c7d9bb91a2b894533c0233620fa360f Mon Sep 17 00:00:00 2001 From: Usoof Mansoor Date: Tue, 2 Mar 2021 11:26:31 +0400 Subject: [PATCH 03/25] Update docs link to Django OAuth Toolkit. (#7737) --- docs/api-guide/authentication.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/api-guide/authentication.md b/docs/api-guide/authentication.md index da932a06c..61687e642 100644 --- a/docs/api-guide/authentication.md +++ b/docs/api-guide/authentication.md @@ -357,7 +357,7 @@ The following third party packages are also available. ## Django OAuth Toolkit -The [Django OAuth Toolkit][django-oauth-toolkit] package provides OAuth 2.0 support and works with Python 3.4+. The package is maintained by [Evonove][evonove] and uses the excellent [OAuthLib][oauthlib]. The package is well documented, and well supported and is currently our **recommended package for OAuth 2.0 support**. +The [Django OAuth Toolkit][django-oauth-toolkit] package provides OAuth 2.0 support and works with Python 3.4+. The package is maintained by [jazzband][jazzband] and uses the excellent [OAuthLib][oauthlib]. The package is well documented, and well supported and is currently our **recommended package for OAuth 2.0 support**. #### Installation & configuration @@ -448,7 +448,7 @@ There are currently two forks of this project. [djangorestframework-digestauth]: https://github.com/juanriaza/django-rest-framework-digestauth [oauth-1.0a]: https://oauth.net/core/1.0a/ [django-oauth-toolkit]: https://github.com/evonove/django-oauth-toolkit -[evonove]: https://github.com/evonove/ +[jazzband]: https://github.com/jazzband/ [oauthlib]: https://github.com/idan/oauthlib [djangorestframework-simplejwt]: https://github.com/davesque/django-rest-framework-simplejwt [etoccalino]: https://github.com/etoccalino/ From de7468d0b4c48007aed734fee22db0b79b22e70b Mon Sep 17 00:00:00 2001 From: Jonathan Mortensen <56177725+jmo-qap@users.noreply.github.com> Date: Wed, 3 Mar 2021 03:15:39 -0800 Subject: [PATCH 04/25] support multi db atomic_requests (#7739) --- rest_framework/views.py | 8 ++++---- tests/conftest.py | 4 ++++ tests/test_atomic_requests.py | 35 +++++++++++++++++++++++++++++++++++ 3 files changed, 43 insertions(+), 4 deletions(-) diff --git a/rest_framework/views.py b/rest_framework/views.py index d1b5e4ed9..5b0622069 100644 --- a/rest_framework/views.py +++ b/rest_framework/views.py @@ -3,7 +3,7 @@ Provides an APIView class that is the base of all views in REST framework. """ from django.conf import settings from django.core.exceptions import PermissionDenied -from django.db import connection, models, transaction +from django.db import connections, models from django.http import Http404 from django.http.response import HttpResponseBase from django.utils.cache import cc_delim_re, patch_vary_headers @@ -63,9 +63,9 @@ def get_view_description(view, html=False): def set_rollback(): - atomic_requests = connection.settings_dict.get('ATOMIC_REQUESTS', False) - if atomic_requests and connection.in_atomic_block: - transaction.set_rollback(True) + for db in connections.all(): + if db.settings_dict['ATOMIC_REQUESTS'] and db.in_atomic_block: + db.set_rollback(True) def exception_handler(exc, context): diff --git a/tests/conftest.py b/tests/conftest.py index ac29e4a42..cc32cc637 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -24,6 +24,10 @@ def pytest_configure(config): 'default': { 'ENGINE': 'django.db.backends.sqlite3', 'NAME': ':memory:' + }, + 'secondary': { + 'ENGINE': 'django.db.backends.sqlite3', + 'NAME': ':memory:' } }, SITE_ID=1, diff --git a/tests/test_atomic_requests.py b/tests/test_atomic_requests.py index 15b41e02f..beda5cba1 100644 --- a/tests/test_atomic_requests.py +++ b/tests/test_atomic_requests.py @@ -130,6 +130,41 @@ class DBTransactionAPIExceptionTests(TestCase): assert BasicModel.objects.count() == 0 +@unittest.skipUnless( + connection.features.uses_savepoints, + "'atomic' requires transactions and savepoints." +) +class MultiDBTransactionAPIExceptionTests(TestCase): + databases = '__all__' + + def setUp(self): + self.view = APIExceptionView.as_view() + connections.databases['default']['ATOMIC_REQUESTS'] = True + connections.databases['secondary']['ATOMIC_REQUESTS'] = True + + def tearDown(self): + connections.databases['default']['ATOMIC_REQUESTS'] = False + connections.databases['secondary']['ATOMIC_REQUESTS'] = False + + def test_api_exception_rollback_transaction(self): + """ + Transaction is rollbacked by our transaction atomic block. + """ + request = factory.post('/') + num_queries = 4 if connection.features.can_release_savepoints else 3 + with self.assertNumQueries(num_queries): + # 1 - begin savepoint + # 2 - insert + # 3 - rollback savepoint + # 4 - release savepoint + with transaction.atomic(), transaction.atomic(using='secondary'): + response = self.view(request) + assert transaction.get_rollback() + assert transaction.get_rollback(using='secondary') + assert response.status_code == status.HTTP_500_INTERNAL_SERVER_ERROR + assert BasicModel.objects.count() == 0 + + @unittest.skipUnless( connection.features.uses_savepoints, "'atomic' requires transactions and savepoints." From 1deb8ae370df6c65b6ec3fadf71d9391236be06f Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Wed, 3 Mar 2021 15:06:42 +0000 Subject: [PATCH 05/25] Update FUNDING.yml --- .github/FUNDING.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml index d7c23d635..5a830ca53 100644 --- a/.github/FUNDING.yml +++ b/.github/FUNDING.yml @@ -1 +1,2 @@ +github: encode custom: https://fund.django-rest-framework.org/topics/funding/ From 344235ab371dcd80e2ff2546bf673d586cde4310 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Wed, 3 Mar 2021 15:32:19 +0000 Subject: [PATCH 06/25] Create config.yml --- .github/ISSUE_TEMPLATE/config.yml | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/config.yml diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 000000000..8bb8d8c21 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,5 @@ +contact_links: +- name: Discussions + url: https://github.com/encode/django-rest-framework/discussions + about: > + The "Discussions" forum is where you want to be headed too. Please only raise an issue if you've been advised to do so after discussion. Thank you! 🙏 From c9a00bdb2c0838f17e9a64f6ccab69d14d8ea6e7 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Wed, 3 Mar 2021 15:33:14 +0000 Subject: [PATCH 07/25] Update config.yml --- .github/ISSUE_TEMPLATE/config.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index 8bb8d8c21..fd0db4d66 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -2,4 +2,6 @@ contact_links: - name: Discussions url: https://github.com/encode/django-rest-framework/discussions about: > - The "Discussions" forum is where you want to be headed too. Please only raise an issue if you've been advised to do so after discussion. Thank you! 🙏 + The "Discussions" forum is where you want to be headed too. + Please only raise an issue if you've been advised to do so after discussion. + Thank you! 🙏 From db0bb5ef42879a69e7262aebf0f42ce173248a61 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Wed, 3 Mar 2021 15:39:22 +0000 Subject: [PATCH 08/25] Update config.yml --- .github/ISSUE_TEMPLATE/config.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index fd0db4d66..bf0c054a6 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -2,6 +2,4 @@ contact_links: - name: Discussions url: https://github.com/encode/django-rest-framework/discussions about: > - The "Discussions" forum is where you want to be headed too. - Please only raise an issue if you've been advised to do so after discussion. - Thank you! 🙏 + The "Discussions" forum is where you want to start. 💖 From 37b8d2018d4dbe2efc012f23baff1cca4df15675 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Wed, 3 Mar 2021 15:42:38 +0000 Subject: [PATCH 09/25] Create 1-issue.md --- .github/ISSUE_TEMPLATE/1-issue.md | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/1-issue.md diff --git a/.github/ISSUE_TEMPLATE/1-issue.md b/.github/ISSUE_TEMPLATE/1-issue.md new file mode 100644 index 000000000..0da154953 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/1-issue.md @@ -0,0 +1,10 @@ +--- +name: Issue +about: Please only raise an issue if you've been advised to do so after discussion. Thanks! 🙏 +--- + +## Checklist + +- [ ] Raised initially as discussion #... +- [ ] This cannot be dealt with as a third party library. (We prefer new functionality to be [in the form of third party libraries](https://www.django-rest-framework.org/community/third-party-packages/#about-third-party-packages) where possible.) +- [ ] I have reduced the issue to the simplest possible case. From ee51145574c9d868baab87a65ac18878dee4ee12 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Wed, 3 Mar 2021 15:43:57 +0000 Subject: [PATCH 10/25] Update config.yml --- .github/ISSUE_TEMPLATE/config.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index bf0c054a6..382fc521a 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -1,3 +1,4 @@ +blank_issues_enabled: false contact_links: - name: Discussions url: https://github.com/encode/django-rest-framework/discussions From ec29ff8a8013dd3383344bb78eea479d025e2a87 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Wed, 3 Mar 2021 15:45:40 +0000 Subject: [PATCH 11/25] Delete ISSUE_TEMPLATE.md --- ISSUE_TEMPLATE.md | 14 -------------- 1 file changed, 14 deletions(-) delete mode 100644 ISSUE_TEMPLATE.md diff --git a/ISSUE_TEMPLATE.md b/ISSUE_TEMPLATE.md deleted file mode 100644 index 566bf9543..000000000 --- a/ISSUE_TEMPLATE.md +++ /dev/null @@ -1,14 +0,0 @@ -## Checklist - -- [ ] I have verified that that issue exists against the `master` branch of Django REST framework. -- [ ] I have searched for similar issues in both open and closed tickets and cannot find a duplicate. -- [ ] This is not a usage question. (Those should be directed to the [discussion group](https://groups.google.com/forum/#!forum/django-rest-framework) instead.) -- [ ] This cannot be dealt with as a third party library. (We prefer new functionality to be [in the form of third party libraries](https://www.django-rest-framework.org/community/third-party-packages/#about-third-party-packages) where possible.) -- [ ] I have reduced the issue to the simplest possible case. -- [ ] I have included a failing test as a pull request. (If you are unable to do so we can still accept the issue.) - -## Steps to reproduce - -## Expected behavior - -## Actual behavior From ef112f5017bb6d3d6a331ff485dbf6a9209fb8b4 Mon Sep 17 00:00:00 2001 From: arcanemachine Date: Mon, 8 Mar 2021 04:46:35 -0700 Subject: [PATCH 12/25] Provide example for dict in ValidationError detail (#7788) Added a sentence describing the use of a dictionary as the `detail` argument of a ValidationError, and how it can be used to add field-level errors during object-level validation. --- docs/api-guide/exceptions.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api-guide/exceptions.md b/docs/api-guide/exceptions.md index fbf3097e0..e62a7e4f9 100644 --- a/docs/api-guide/exceptions.md +++ b/docs/api-guide/exceptions.md @@ -222,7 +222,7 @@ By default this exception results in a response with the HTTP status code "429 T The `ValidationError` exception is slightly different from the other `APIException` classes: * The `detail` argument is mandatory, not optional. -* The `detail` argument may be a list or dictionary of error details, and may also be a nested data structure. +* The `detail` argument may be a list or dictionary of error details, and may also be a nested data structure. By using a dictionary, you can specify field-level errors while performing object-level validation in the `validate()` method of a serializer. For example. `raise serializers.ValidationError({'name': 'Please enter a valid name.'})` * By convention you should import the serializers module and use a fully qualified `ValidationError` style, in order to differentiate it from Django's built-in validation error. For example. `raise serializers.ValidationError('This field must be an integer value.')` The `ValidationError` class should be used for serializer and field validation, and by validator classes. It is also raised when calling `serializer.is_valid` with the `raise_exception` keyword argument: From 234527959d5ad6eef2bc0f8af1aa2e149fc8bc60 Mon Sep 17 00:00:00 2001 From: Jean-Pierre Merx Date: Mon, 8 Mar 2021 13:08:26 +0100 Subject: [PATCH 13/25] Have options example in documenting-your-api.md to return a Response (#7639) It was returning data which is not correct. Closes #7638. Co-authored-by: Jean-Pierre Merx --- docs/topics/documenting-your-api.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/topics/documenting-your-api.md b/docs/topics/documenting-your-api.md index cd7e5098f..5eabeee7b 100644 --- a/docs/topics/documenting-your-api.md +++ b/docs/topics/documenting-your-api.md @@ -202,7 +202,7 @@ You can modify the response behavior to `OPTIONS` requests by overriding the `op meta = self.metadata_class() data = meta.determine_metadata(request, self) data.pop('description') - return data + return Response(data=data, status=status.HTTP_200_OK) See [the Metadata docs][metadata-docs] for more details. From e32ebc41998ffd7f22f6e691badb86a709c89ba7 Mon Sep 17 00:00:00 2001 From: Alex Cotsarelis <57880995+alex-cots@users.noreply.github.com> Date: Mon, 8 Mar 2021 07:09:17 -0500 Subject: [PATCH 14/25] Docs: DjangoModelPermissions works on views with get_queryset() method. (#7693) Sentinel querysets not needed after v3.1.2 --- docs/api-guide/permissions.md | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/docs/api-guide/permissions.md b/docs/api-guide/permissions.md index ade146257..f694d6be5 100644 --- a/docs/api-guide/permissions.md +++ b/docs/api-guide/permissions.md @@ -169,7 +169,7 @@ This permission is suitable if you want to your API to allow read permissions to ## DjangoModelPermissions -This permission class ties into Django's standard `django.contrib.auth` [model permissions][contribauth]. This permission must only be applied to views that have a `.queryset` property set. Authorization will only be granted if the user *is authenticated* and has the *relevant model permissions* assigned. +This permission class ties into Django's standard `django.contrib.auth` [model permissions][contribauth]. This permission must only be applied to views that have a `.queryset` property or `get_queryset()` method. Authorization will only be granted if the user *is authenticated* and has the *relevant model permissions* assigned. * `POST` requests require the user to have the `add` permission on the model. * `PUT` and `PATCH` requests require the user to have the `change` permission on the model. @@ -179,12 +179,6 @@ The default behaviour can also be overridden to support custom model permissions To use custom model permissions, override `DjangoModelPermissions` and set the `.perms_map` property. Refer to the source code for details. -#### Using with views that do not include a `queryset` attribute. - -If you're using this permission with a view that uses an overridden `get_queryset()` method there may not be a `queryset` attribute on the view. In this case we suggest also marking the view with a sentinel queryset, so that this class can determine the required permissions. For example: - - queryset = User.objects.none() # Required for DjangoModelPermissions - ## DjangoModelPermissionsOrAnonReadOnly Similar to `DjangoModelPermissions`, but also allows unauthenticated users to have read-only access to the API. From b463878132004d33182b2f61be8209bfad79af7f Mon Sep 17 00:00:00 2001 From: Igor Polyakov Date: Tue, 9 Mar 2021 17:16:19 +0700 Subject: [PATCH 15/25] Commas added in README (#7730) To make it more comfortable for users to copy and paste snippets --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 8af1466f8..305f92389 100644 --- a/README.md +++ b/README.md @@ -113,7 +113,7 @@ router.register(r'users', UserViewSet) # Additionally, we include login URLs for the browsable API. urlpatterns = [ path('', include(router.urls)), - path('api-auth/', include('rest_framework.urls', namespace='rest_framework')) + path('api-auth/', include('rest_framework.urls', namespace='rest_framework')), ] ``` @@ -131,7 +131,7 @@ REST_FRAMEWORK = { # Use Django's standard `django.contrib.auth` permissions, # or allow read-only access for unauthenticated users. 'DEFAULT_PERMISSION_CLASSES': [ - 'rest_framework.permissions.DjangoModelPermissionsOrAnonReadOnly' + 'rest_framework.permissions.DjangoModelPermissionsOrAnonReadOnly', ] } ``` From e9a54e38e1c864919c79a8b88d83b8d61d477053 Mon Sep 17 00:00:00 2001 From: kuter Date: Tue, 9 Mar 2021 11:17:30 +0100 Subject: [PATCH 16/25] add support for Yes/No literals with BooleanField (#7701) --- rest_framework/fields.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rest_framework/fields.py b/rest_framework/fields.py index fdfba13f2..b6c9ee5c5 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -704,7 +704,7 @@ class BooleanField(Field): initial = False TRUE_VALUES = { 't', 'T', - 'y', 'Y', 'yes', 'YES', + 'y', 'Y', 'yes', 'Yes', 'YES', 'true', 'True', 'TRUE', 'on', 'On', 'ON', '1', 1, @@ -712,7 +712,7 @@ class BooleanField(Field): } FALSE_VALUES = { 'f', 'F', - 'n', 'N', 'no', 'NO', + 'n', 'N', 'no', 'No', 'NO', 'false', 'False', 'FALSE', 'off', 'Off', 'OFF', '0', 0, 0.0, From 393f8679952b3e81b56db3e3c498aeb1f8849f52 Mon Sep 17 00:00:00 2001 From: Cas Ebbers <617080+CasEbbers@users.noreply.github.com> Date: Tue, 9 Mar 2021 11:21:11 +0100 Subject: [PATCH 17/25] Overlooked translation in search.html (#7551) --- rest_framework/templates/rest_framework/filters/search.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rest_framework/templates/rest_framework/filters/search.html b/rest_framework/templates/rest_framework/filters/search.html index edb28d45d..065c3889a 100644 --- a/rest_framework/templates/rest_framework/filters/search.html +++ b/rest_framework/templates/rest_framework/filters/search.html @@ -5,7 +5,7 @@
- +
From a89a6427d3af7045c8c35693cc830c8b76b8a00d Mon Sep 17 00:00:00 2001 From: Nathan Glover <15344788+nathanglover@users.noreply.github.com> Date: Tue, 9 Mar 2021 05:22:37 -0500 Subject: [PATCH 18/25] #7703 adding deprecations to release notes (#7716) * #7703 adding deprecations to release notes * #7703 - update link for gh6687 --- docs/community/release-notes.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/community/release-notes.md b/docs/community/release-notes.md index c981b9ac9..49fb655b0 100644 --- a/docs/community/release-notes.md +++ b/docs/community/release-notes.md @@ -177,6 +177,8 @@ Date: 28th September 2020 * Don't strict disallow redundant `SerializerMethodField` field name arguments. * Don't render extra actions in browable API if not authenticated. * Strip null characters from search parameters. +* Deprecate the `detail_route` decorator in favor of `action`, which accepts a `detail` bool. Use `@action(detail=True)` instead. [gh6687] +* Deprecate the `list_route` decorator in favor of `action`, which accepts a `detail` bool. Use `@action(detail=False)` instead. [gh6687] ## 3.9.x series @@ -2270,6 +2272,7 @@ For older release notes, [please see the version 2.x documentation][old-release- [gh6680]: https://github.com/encode/django-rest-framework/issues/6680 [gh6317]: https://github.com/encode/django-rest-framework/issues/6317 +[gh6687]: https://github.com/encode/django-rest-framework/issues/6687 [gh6892]: https://github.com/encode/django-rest-framework/issues/6892 From 05512160abb4c2110afff9e82f8f523be68476cf Mon Sep 17 00:00:00 2001 From: David Kerkeslager Date: Tue, 9 Mar 2021 05:49:03 -0500 Subject: [PATCH 19/25] Respect allow_null=True on DecimalFields (#7718) * Handle None in to_representation() * Return None as '' in to_representation() when coerce_to_string=True * Handle '' as None in to_internal_value(), for symmetry with to_representation(), and because the empty concept doesn't make sense for Decimal. --- rest_framework/fields.py | 9 +++++++++ tests/test_fields.py | 29 +++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+) diff --git a/rest_framework/fields.py b/rest_framework/fields.py index b6c9ee5c5..d91299484 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -1063,6 +1063,9 @@ class DecimalField(Field): try: value = decimal.Decimal(data) except decimal.DecimalException: + if data == '' and self.allow_null: + return None + self.fail('invalid') if value.is_nan(): @@ -1112,6 +1115,12 @@ class DecimalField(Field): def to_representation(self, value): coerce_to_string = getattr(self, 'coerce_to_string', api_settings.COERCE_DECIMAL_TO_STRING) + if value is None: + if coerce_to_string: + return '' + else: + return None + if not isinstance(value, decimal.Decimal): value = decimal.Decimal(str(value).strip()) diff --git a/tests/test_fields.py b/tests/test_fields.py index fdd570d8a..5842553f0 100644 --- a/tests/test_fields.py +++ b/tests/test_fields.py @@ -1090,6 +1090,9 @@ class TestDecimalField(FieldValues): '2E+1': Decimal('20'), } invalid_inputs = ( + (None, ["This field may not be null."]), + ('', ["A valid number is required."]), + (' ', ["A valid number is required."]), ('abc', ["A valid number is required."]), (Decimal('Nan'), ["A valid number is required."]), (Decimal('Snan'), ["A valid number is required."]), @@ -1115,6 +1118,32 @@ class TestDecimalField(FieldValues): field = serializers.DecimalField(max_digits=3, decimal_places=1) +class TestAllowNullDecimalField(FieldValues): + valid_inputs = { + None: None, + '': None, + ' ': None, + } + invalid_inputs = {} + outputs = { + None: '', + } + field = serializers.DecimalField(max_digits=3, decimal_places=1, allow_null=True) + + +class TestAllowNullNoStringCoercionDecimalField(FieldValues): + valid_inputs = { + None: None, + '': None, + ' ': None, + } + invalid_inputs = {} + outputs = { + None: None, + } + field = serializers.DecimalField(max_digits=3, decimal_places=1, allow_null=True, coerce_to_string=False) + + class TestMinMaxDecimalField(FieldValues): """ Valid and invalid values for `DecimalField` with min and max limits. From 95ae92ef23859b45d03bcc2facf04fab0acee09d Mon Sep 17 00:00:00 2001 From: Berkant Date: Tue, 9 Mar 2021 14:34:18 +0300 Subject: [PATCH 20/25] Fix #7706 (#7724) Handle non-dict values for NestedSerializer during BrowsableAPI rendering. --- rest_framework/utils/serializer_helpers.py | 4 ++-- tests/test_bound_fields.py | 27 ++++++++++++++++++++++ 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/rest_framework/utils/serializer_helpers.py b/rest_framework/utils/serializer_helpers.py index b18fbe0df..cd0373adc 100644 --- a/rest_framework/utils/serializer_helpers.py +++ b/rest_framework/utils/serializer_helpers.py @@ -1,5 +1,5 @@ from collections import OrderedDict -from collections.abc import MutableMapping +from collections.abc import Mapping, MutableMapping from django.utils.encoding import force_str @@ -101,7 +101,7 @@ class NestedBoundField(BoundField): """ def __init__(self, field, value, errors, prefix=''): - if value is None or value == '': + if value is None or value == '' or not isinstance(value, Mapping): value = {} super().__init__(field, value, errors, prefix) diff --git a/tests/test_bound_fields.py b/tests/test_bound_fields.py index dc5ab542f..dec8793c3 100644 --- a/tests/test_bound_fields.py +++ b/tests/test_bound_fields.py @@ -163,6 +163,33 @@ class TestNestedBoundField: rendered_packed = ''.join(rendered.split()) assert rendered_packed == expected_packed + def test_rendering_nested_fields_with_not_mappable_value(self): + from rest_framework.renderers import HTMLFormRenderer + + class Nested(serializers.Serializer): + text_field = serializers.CharField() + + class ExampleSerializer(serializers.Serializer): + nested = Nested() + + serializer = ExampleSerializer(data={'nested': 1}) + assert not serializer.is_valid() + renderer = HTMLFormRenderer() + for field in serializer: + rendered = renderer.render_field(field, {}) + expected_packed = ( + '
' + 'Nested' + '' + '' + '' + '' + '
' + ) + + rendered_packed = ''.join(rendered.split()) + assert rendered_packed == expected_packed + class TestJSONBoundField: def test_as_form_fields(self): From 82b8a64a02ccc4ff678ac9f9565f25463ecad871 Mon Sep 17 00:00:00 2001 From: Chris Guo <41265033+chrisguox@users.noreply.github.com> Date: Tue, 9 Mar 2021 19:49:19 +0800 Subject: [PATCH 21/25] docs: add example for caching (#7118) --- docs/api-guide/caching.md | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/docs/api-guide/caching.md b/docs/api-guide/caching.md index 96517b15e..ab4f82cd2 100644 --- a/docs/api-guide/caching.md +++ b/docs/api-guide/caching.md @@ -13,13 +13,13 @@ provided in Django. Django provides a [`method_decorator`][decorator] to use decorators with class based views. This can be used with -other cache decorators such as [`cache_page`][page] and -[`vary_on_cookie`][cookie]. +other cache decorators such as [`cache_page`][page], +[`vary_on_cookie`][cookie] and [`vary_on_headers`][headers]. ```python from django.utils.decorators import method_decorator from django.views.decorators.cache import cache_page -from django.views.decorators.vary import vary_on_cookie +from django.views.decorators.vary import vary_on_cookie, vary_on_headers from rest_framework.response import Response from rest_framework.views import APIView @@ -27,8 +27,7 @@ from rest_framework import viewsets class UserViewSet(viewsets.ViewSet): - - # Cache requested url for each user for 2 hours + # With cookie: cache requested url for each user for 2 hours @method_decorator(cache_page(60*60*2)) @method_decorator(vary_on_cookie) def list(self, request, format=None): @@ -38,8 +37,18 @@ class UserViewSet(viewsets.ViewSet): return Response(content) -class PostView(APIView): +class ProfileView(APIView): + # With auth: cache requested url for each user for 2 hours + @method_decorator(cache_page(60*60*2)) + @method_decorator(vary_on_headers("Authorization",)) + def get(self, request, format=None): + content = { + 'user_feed': request.user.get_user_feed() + } + return Response(content) + +class PostView(APIView): # Cache page for the requested url @method_decorator(cache_page(60*60*2)) def get(self, request, format=None): @@ -55,4 +64,5 @@ class PostView(APIView): [page]: https://docs.djangoproject.com/en/dev/topics/cache/#the-per-view-cache [cookie]: https://docs.djangoproject.com/en/dev/topics/http/decorators/#django.views.decorators.vary.vary_on_cookie +[headers]: https://docs.djangoproject.com/en/dev/topics/http/decorators/#django.views.decorators.vary.vary_on_headers [decorator]: https://docs.djangoproject.com/en/dev/topics/class-based-views/intro/#decorating-the-class From 747fef6134539c8ce9042b38639459c59e0440a5 Mon Sep 17 00:00:00 2001 From: Celia Oakley Date: Tue, 9 Mar 2021 22:51:52 +1100 Subject: [PATCH 22/25] Add django-rest-authemail to Third Party Packages (#7679) * Add django-rest-authemail to Authentication * Add django-rest-authemail to Third Party Packages --- docs/api-guide/authentication.md | 5 +++++ docs/community/third-party-packages.md | 2 ++ 2 files changed, 7 insertions(+) diff --git a/docs/api-guide/authentication.md b/docs/api-guide/authentication.md index 61687e642..d13c5a2f0 100644 --- a/docs/api-guide/authentication.md +++ b/docs/api-guide/authentication.md @@ -432,6 +432,10 @@ There are currently two forks of this project. [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. +## django-rest-authemail + +[django-rest-authemail][django-rest-authemail] provides a RESTful API interface for user signup and authentication. Email addresses are used for authentication, rather than usernames. API endpoints are available for signup, signup email verification, login, logout, password reset, password reset verification, email change, email change verification, password change, and user detail. A fully-functional example project and detailed instructions are included. + [cite]: https://jacobian.org/writing/rest-worst-practices/ [http401]: https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.2 [http403]: https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.4 @@ -466,3 +470,4 @@ There are currently two forks of this project. [django-rest-framework-social-oauth2]: https://github.com/PhilipGarnero/django-rest-framework-social-oauth2 [django-rest-knox]: https://github.com/James1345/django-rest-knox [drfpasswordless]: https://github.com/aaronn/django-rest-framework-passwordless +[django-rest-authemail]: https://github.com/celiao/django-rest-authemail diff --git a/docs/community/third-party-packages.md b/docs/community/third-party-packages.md index d4359890d..88836cfc6 100644 --- a/docs/community/third-party-packages.md +++ b/docs/community/third-party-packages.md @@ -190,6 +190,7 @@ To submit new content, [open an issue][drf-create-issue] or [create a pull reque * [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. * [drfpasswordless][drfpasswordless] - Adds (Medium, Square Cash inspired) passwordless logins and signups via email and mobile numbers. +* [django-rest-authemail][django-rest-authemail] - Provides a RESTful API for user signup and authentication using email addresses. ### Permissions @@ -362,3 +363,4 @@ To submit new content, [open an issue][drf-create-issue] or [create a pull reque [django-elasticsearch-dsl-drf]: https://github.com/barseghyanartur/django-elasticsearch-dsl-drf [django-api-client]: https://github.com/rhenter/django-api-client [drf-psq]: https://github.com/drf-psq/drf-psq +[django-rest-authemail]: https://github.com/celiao/django-rest-authemail From 4e0d6c411805743688bd25a2ad8021441a1ae1ac Mon Sep 17 00:00:00 2001 From: Vitor Hugo Date: Tue, 9 Mar 2021 08:54:58 -0300 Subject: [PATCH 23/25] Update default.css (#7643) When I apply a theme to the bootstrap used in the project, boolean inputs are out of line with the rest of the form. With this small payment, this no longer occurs. --- rest_framework/static/rest_framework/css/default.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rest_framework/static/rest_framework/css/default.css b/rest_framework/static/rest_framework/css/default.css index 86fef1773..51ca3ba19 100644 --- a/rest_framework/static/rest_framework/css/default.css +++ b/rest_framework/static/rest_framework/css/default.css @@ -40,7 +40,7 @@ td.nested > table { margin: 0; } -form select, form input, form textarea { +form select, form input:not([type=checkbox]), form textarea { width: 90%; } From 750bad0a58bdec85fa5a630a519277bac3b36535 Mon Sep 17 00:00:00 2001 From: Romain Rigaux Date: Tue, 9 Mar 2021 04:00:51 -0800 Subject: [PATCH 24/25] Actually use the loginUser arguments in the example (#7714) --- docs/topics/api-clients.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/topics/api-clients.md b/docs/topics/api-clients.md index 9b61eaf42..b9f5e3ecd 100644 --- a/docs/topics/api-clients.md +++ b/docs/topics/api-clients.md @@ -453,7 +453,7 @@ For example, using the "Django REST framework JWT" package function loginUser(username, password) { let action = ["api-token-auth", "obtain-token"]; - let params = {username: "example", email: "example@example.com"}; + let params = {username: username, password: password}; client.action(schema, action, params).then(function(result) { // On success, instantiate an authenticated client. let auth = window.coreapi.auth.TokenAuthentication({ From a3ae8ea77efa2fa9af69da5dfda9128ef94c0fde Mon Sep 17 00:00:00 2001 From: Dmitry Mugtasimov Date: Tue, 9 Mar 2021 15:06:12 +0300 Subject: [PATCH 25/25] Do not do `SELECT count(*) FROM ...` if pagination is not requested (#6098) * Do not do `SELECT count(*) FROM ...` if pagination is not requested * Update pagination.py Co-authored-by: Tom Christie --- rest_framework/pagination.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rest_framework/pagination.py b/rest_framework/pagination.py index 60a57c8e4..4db646116 100644 --- a/rest_framework/pagination.py +++ b/rest_framework/pagination.py @@ -376,11 +376,11 @@ class LimitOffsetPagination(BasePagination): template = 'rest_framework/pagination/numbers.html' def paginate_queryset(self, queryset, request, view=None): - self.count = self.get_count(queryset) self.limit = self.get_limit(request) if self.limit is None: return None + self.count = self.get_count(queryset) self.offset = self.get_offset(request) self.request = request if self.count > self.limit and self.template is not None: