From 001d6ec2c907e638496fb4ef0cffa915bf0718fd Mon Sep 17 00:00:00 2001 From: Mathieu Dupuy Date: Sun, 14 May 2023 02:00:13 +0200 Subject: [PATCH 1/5] remove django 2.2 from docs index (#8982) --- docs/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/index.md b/docs/index.md index cab5511ac..ad241c0a3 100644 --- a/docs/index.md +++ b/docs/index.md @@ -86,7 +86,7 @@ continued development by **[signing up for a paid plan][funding]**. REST framework requires the following: * Python (3.6, 3.7, 3.8, 3.9, 3.10, 3.11) -* Django (2.2, 3.0, 3.1, 3.2, 4.0, 4.1) +* Django (3.0, 3.1, 3.2, 4.0, 4.1) We **highly recommend** and only officially support the latest patch release of each Python and Django series. From 7bebe9772488fcf67949e1221e70af642ed0cb74 Mon Sep 17 00:00:00 2001 From: Mehraz Hossain Rumman <59512321+MehrazRumman@users.noreply.github.com> Date: Mon, 15 May 2023 21:02:17 +0600 Subject: [PATCH 2/5] Declared Django 4.2 support in README.md (#8985) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e8375291d..c2975a418 100644 --- a/README.md +++ b/README.md @@ -55,7 +55,7 @@ There is a live example API for testing purposes, [available here][sandbox]. # Requirements * Python 3.6+ -* Django 4.1, 4.0, 3.2, 3.1, 3.0 +* Django 4.2, 4.1, 4.0, 3.2, 3.1, 3.0 We **highly recommend** and only officially support the latest patch release of each Python and Django series. From 332e9560ab0b3a1b8c0ab8f68e95c09bc2d8999f Mon Sep 17 00:00:00 2001 From: Dominik Bruhn Date: Wed, 17 May 2023 07:46:48 +0200 Subject: [PATCH 3/5] Fix Links in Documentation to Django `reverse` and `reverse_lazy` (#8986) * Fix Django Docs url in reverse.md Django URLs of the documentation of `reverse` and `reverse_lazy` were wrong. * Update reverse.md --- docs/api-guide/reverse.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/api-guide/reverse.md b/docs/api-guide/reverse.md index 82dd16881..c3fa52f21 100644 --- a/docs/api-guide/reverse.md +++ b/docs/api-guide/reverse.md @@ -54,5 +54,5 @@ As with the `reverse` function, you should **include the request as a keyword ar api_root = reverse_lazy('api-root', request=request) [cite]: https://www.ics.uci.edu/~fielding/pubs/dissertation/rest_arch_style.htm#sec_5_1_5 -[reverse]: https://docs.djangoproject.com/en/stable/topics/http/urls/#reverse -[reverse-lazy]: https://docs.djangoproject.com/en/stable/topics/http/urls/#reverse-lazy +[reverse]: https://docs.djangoproject.com/en/stable/ref/urlresolvers/#reverse +[reverse-lazy]: https://docs.djangoproject.com/en/stable/ref/urlresolvers/#reverse-lazy From a25aac7d6739c43a4997829a0ff79cce12e8c0de Mon Sep 17 00:00:00 2001 From: jornvanwier Date: Thu, 18 May 2023 05:46:40 +0200 Subject: [PATCH 4/5] fix URLPathVersioning reverse fallback (#7247) * fix URLPathVersioning reverse fallback * add test for URLPathVersioning reverse fallback * Update tests/test_versioning.py --------- Co-authored-by: Jorn van Wier Co-authored-by: Asif Saif Uddin --- rest_framework/versioning.py | 6 ++++-- tests/test_versioning.py | 10 ++++++++++ 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/rest_framework/versioning.py b/rest_framework/versioning.py index 78cfc9dc8..c2764c7a4 100644 --- a/rest_framework/versioning.py +++ b/rest_framework/versioning.py @@ -81,8 +81,10 @@ class URLPathVersioning(BaseVersioning): def reverse(self, viewname, args=None, kwargs=None, request=None, format=None, **extra): if request.version is not None: - kwargs = {} if (kwargs is None) else kwargs - kwargs[self.version_param] = request.version + kwargs = { + self.version_param: request.version, + **(kwargs or {}) + } return super().reverse( viewname, args, kwargs, request, format, **extra diff --git a/tests/test_versioning.py b/tests/test_versioning.py index 93f61d2be..b21646184 100644 --- a/tests/test_versioning.py +++ b/tests/test_versioning.py @@ -152,6 +152,8 @@ class TestURLReversing(URLPatternsTestCase, APITestCase): path('v1/', include((included, 'v1'), namespace='v1')), path('another/', dummy_view, name='another'), re_path(r'^(?P[v1|v2]+)/another/$', dummy_view, name='another'), + re_path(r'^(?P.+)/unversioned/$', dummy_view, name='unversioned'), + ] def test_reverse_unversioned(self): @@ -198,6 +200,14 @@ class TestURLReversing(URLPatternsTestCase, APITestCase): response = view(request) assert response.data == {'url': 'http://testserver/another/'} + # Test fallback when kwargs is not None + request = factory.get('/v1/endpoint/') + request.versioning_scheme = scheme() + request.version = 'v1' + + reversed_url = reverse('unversioned', request=request, kwargs={'foo': 'bar'}) + assert reversed_url == 'http://testserver/bar/unversioned/' + def test_reverse_namespace_versioning(self): class FakeResolverMatch(ResolverMatch): namespace = 'v1' From d252d22343b9c9688db77b59aa72dabd540bd252 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20Beaul=C3=A9?= Date: Wed, 24 May 2023 10:59:42 -0300 Subject: [PATCH 5/5] Make set_value a method within `Serializer` (#8001) * Make set_value a static method for Serializers As an alternative to #7671, let the method be overridden if needed. As the function is only used for serializers, it has a better place in the Serializer class. * Set `set_value` as an object (non-static) method * Add tests for set_value() These tests follow the examples given in the method. --- rest_framework/fields.py | 21 --------------------- rest_framework/serializers.py | 24 ++++++++++++++++++++++-- tests/test_serializer.py | 21 +++++++++++++++++++++ 3 files changed, 43 insertions(+), 23 deletions(-) diff --git a/rest_framework/fields.py b/rest_framework/fields.py index 4f82e4a10..623e72e0a 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -113,27 +113,6 @@ def get_attribute(instance, attrs): return instance -def set_value(dictionary, keys, value): - """ - Similar to Python's built in `dictionary[key] = value`, - but takes a list of nested keys instead of a single key. - - set_value({'a': 1}, [], {'b': 2}) -> {'a': 1, 'b': 2} - set_value({'a': 1}, ['x'], 2) -> {'a': 1, 'x': 2} - set_value({'a': 1}, ['x', 'y'], 2) -> {'a': 1, 'x': {'y': 2}} - """ - if not keys: - dictionary.update(value) - return - - for key in keys[:-1]: - if key not in dictionary: - dictionary[key] = {} - dictionary = dictionary[key] - - dictionary[keys[-1]] = value - - def to_choices_dict(choices): """ Convert choices into key/value dicts. diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index 01bebf5fc..a3d68b03d 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -28,7 +28,7 @@ from django.utils.translation import gettext_lazy as _ from rest_framework.compat import postgres_fields from rest_framework.exceptions import ErrorDetail, ValidationError -from rest_framework.fields import get_error_detail, set_value +from rest_framework.fields import get_error_detail from rest_framework.settings import api_settings from rest_framework.utils import html, model_meta, representation from rest_framework.utils.field_mapping import ( @@ -346,6 +346,26 @@ class Serializer(BaseSerializer, metaclass=SerializerMetaclass): 'invalid': _('Invalid data. Expected a dictionary, but got {datatype}.') } + def set_value(self, dictionary, keys, value): + """ + Similar to Python's built in `dictionary[key] = value`, + but takes a list of nested keys instead of a single key. + + set_value({'a': 1}, [], {'b': 2}) -> {'a': 1, 'b': 2} + set_value({'a': 1}, ['x'], 2) -> {'a': 1, 'x': 2} + set_value({'a': 1}, ['x', 'y'], 2) -> {'a': 1, 'x': {'y': 2}} + """ + if not keys: + dictionary.update(value) + return + + for key in keys[:-1]: + if key not in dictionary: + dictionary[key] = {} + dictionary = dictionary[key] + + dictionary[keys[-1]] = value + @cached_property def fields(self): """ @@ -492,7 +512,7 @@ class Serializer(BaseSerializer, metaclass=SerializerMetaclass): except SkipField: pass else: - set_value(ret, field.source_attrs, validated_value) + self.set_value(ret, field.source_attrs, validated_value) if errors: raise ValidationError(errors) diff --git a/tests/test_serializer.py b/tests/test_serializer.py index 1d9efaa43..10fa8afb9 100644 --- a/tests/test_serializer.py +++ b/tests/test_serializer.py @@ -762,3 +762,24 @@ class Test8301Regression: assert (s.data | {}).__class__ == s.data.__class__ assert ({} | s.data).__class__ == s.data.__class__ + + +class TestSetValueMethod: + # Serializer.set_value() modifies the first parameter in-place. + + s = serializers.Serializer() + + def test_no_keys(self): + ret = {'a': 1} + self.s.set_value(ret, [], {'b': 2}) + assert ret == {'a': 1, 'b': 2} + + def test_one_key(self): + ret = {'a': 1} + self.s.set_value(ret, ['x'], 2) + assert ret == {'a': 1, 'x': 2} + + def test_nested_key(self): + ret = {'a': 1} + self.s.set_value(ret, ['x', 'y'], 2) + assert ret == {'a': 1, 'x': {'y': 2}}