Skip to content

Commit 781cf7e

Browse files
Merge branch 'main' into improve-many-true-validation-guidance
2 parents 21417c8 + 22e231c commit 781cf7e

8 files changed

Lines changed: 130 additions & 16 deletions

File tree

.github/workflows/release.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ env:
2323
jobs:
2424
build:
2525
name: Build distribution 📦
26+
if: github.repository == 'encode/django-rest-framework'
2627
runs-on: ubuntu-24.04
2728
steps:
2829
- uses: actions/checkout@v6

docs/community/release-notes.md

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,105 @@ You can determine your currently installed version using `pip show`:
3636

3737
---
3838

39+
## 3.17.x series
40+
41+
### 3.17.1
42+
43+
**Date**: 24th March 2026
44+
45+
#### Bug fixes
46+
47+
* Fix `HTMLFormRenderer` with empty `datetime` values by [@p-r-a-v-i-n](https://github.com/p-r-a-v-i-n) in [#9928](https://github.com/encode/django-rest-framework/pull/9928)
48+
49+
**Full Changelog**: [3.17.0...3.17.1](https://github.com/encode/django-rest-framework/compare/3.17.0...3.17.1)
50+
51+
### 3.17.0
52+
53+
**Date**: 18th March 2026
54+
55+
#### Breaking changes
56+
57+
* Drop support for Python 3.9 by [@auvipy](https://github.com/auvipy) in [#9781](https://github.com/encode/django-rest-framework/pull/9781)
58+
* Drop deprecated coreapi support by [@browniebroke](https://github.com/browniebroke) in [#9895](https://github.com/encode/django-rest-framework/pull/9895)
59+
60+
#### Features
61+
62+
* Add Django 6.0 support by [@MehrazRumman](https://github.com/MehrazRumman) in [#9819](https://github.com/encode/django-rest-framework/pull/9819)
63+
* Add support for Python 3.14 by [@cclauss](https://github.com/cclauss) in [#9780](https://github.com/encode/django-rest-framework/pull/9780)
64+
* Add ability to specify output format for `DurationField` by [@sevdog](https://github.com/sevdog) in [#8532](https://github.com/encode/django-rest-framework/pull/8532)
65+
* Add missing decorators: `@versioning_class()`, `@content_negotiation_class()`, `@metadata_class()` for function-based views by [@qqii](https://github.com/qqii) in [#9719](https://github.com/encode/django-rest-framework/pull/9719)
66+
* Support `violation_error_code` and `violation_error_message` from `UniqueConstraint` in `UniqueTogetherValidator` by [@s-aleshin](https://github.com/s-aleshin) in [#9766](https://github.com/encode/django-rest-framework/pull/9766)
67+
* Add support for `ipaddress` objects in `JSONEncoder` by [@corenting](https://github.com/corenting) in [#9087](https://github.com/encode/django-rest-framework/pull/9087)
68+
* Add optional support to serialize `BigInteger` to string by [@HoodyH](https://github.com/HoodyH) in [#9775](https://github.com/encode/django-rest-framework/pull/9775)
69+
70+
#### Bug fixes
71+
72+
* Prevent small risk of `Token` overwrite by [@mahdirahimi1999](https://github.com/mahdirahimi1999) in [#9754](https://github.com/encode/django-rest-framework/pull/9754)
73+
* Fix `UniqueTogetherValidator` validation when condition references a read-only field by [@ticosax](https://github.com/ticosax) in [#9764](https://github.com/encode/django-rest-framework/pull/9764)
74+
* Fix validation on many to many field when `default=None` by [@Genarito](https://github.com/Genarito) in [#9790](https://github.com/encode/django-rest-framework/pull/9790)
75+
* Fix invalid SPDX license expression in `__init__.py` by [@TheFunctionalGuy](https://github.com/TheFunctionalGuy) in [#9799](https://github.com/encode/django-rest-framework/pull/9799)
76+
* Fix `HTMLFormRenderer` to ensure a valid `datetime-local` format by [@mgaligniana](https://github.com/mgaligniana) in [#9365](https://github.com/encode/django-rest-framework/pull/9365)
77+
* Fix mutable default arguments in OrderingFilter methods by [@killerdevildog](https://github.com/killerdevildog) in [#9742](https://github.com/encode/django-rest-framework/pull/9742)
78+
* Update TokenAdmin to respect USERNAME_FIELD of the user model by [@m000](https://github.com/m000) in [#9836](https://github.com/encode/django-rest-framework/pull/9836)
79+
* Preserve ordering in `MultipleChoiceField` by [@fbozhang](https://github.com/fbozhang) in [#9735](https://github.com/encode/django-rest-framework/pull/9735)
80+
81+
#### Translations
82+
83+
* Update French translation by [@SebCorbin](https://github.com/SebCorbin) in [#9770](https://github.com/encode/django-rest-framework/pull/9770)
84+
* Update Brazilian Portuguese translations by [@JVPinheiroReis](https://github.com/JVPinheiroReis) in [#9828](https://github.com/encode/django-rest-framework/pull/9828)
85+
* Fix and improve French translations by [@deronnax](https://github.com/deronnax) in [#9896](https://github.com/encode/django-rest-framework/pull/9896)
86+
* Add missing Russian translation by [@minorytanaka](https://github.com/minorytanaka) in [#9903](https://github.com/encode/django-rest-framework/pull/9903)
87+
88+
#### Packaging
89+
90+
* Migrate packaging to `pyproject.toml` by [@deronnax](https://github.com/deronnax) in [#9056](https://github.com/encode/django-rest-framework/pull/9056)
91+
* Move package data rules from `MANIFEST.in` to `pyproject.toml` by [@p-r-a-v-i-n](https://github.com/p-r-a-v-i-n) in [#9825](https://github.com/encode/django-rest-framework/pull/9825)
92+
* Set up release workflow with trusted publisher by [@browniebroke](https://github.com/browniebroke) in [#9852](https://github.com/encode/django-rest-framework/pull/9852)
93+
94+
#### Other changes
95+
96+
* Refactor token generation to use the `secrets` module by [@mahdirahimi1999](https://github.com/mahdirahimi1999) in [#9760](https://github.com/encode/django-rest-framework/pull/9760)
97+
* Add validation for decorator out-of-order with `@api_view` by [@kernelshard](https://github.com/kernelshard) in [#9821](https://github.com/encode/django-rest-framework/pull/9821)
98+
* Switch to mkdocs material theme for documentation by [@browniebroke](https://github.com/browniebroke) in [#9849](https://github.com/encode/django-rest-framework/pull/9849)
99+
100+
#### New Contributors
101+
102+
* [@khaledsukkar2](https://github.com/khaledsukkar2) made their first contribution in [#9717](https://github.com/encode/django-rest-framework/pull/9717)
103+
* [@qqii](https://github.com/qqii) made their first contribution in [#9719](https://github.com/encode/django-rest-framework/pull/9719)
104+
* [@zankoAn](https://github.com/zankoAn) made their first contribution in [#9788](https://github.com/encode/django-rest-framework/pull/9788)
105+
* [@uche-wealth](https://github.com/uche-wealth) made their first contribution in [#9795](https://github.com/encode/django-rest-framework/pull/9795)
106+
* [@s-aleshin](https://github.com/s-aleshin) made their first contribution in [#9766](https://github.com/encode/django-rest-framework/pull/9766)
107+
* [@Infamous003](https://github.com/Infamous003) made their first contribution in [#9794](https://github.com/encode/django-rest-framework/pull/9794)
108+
* [@Genarito](https://github.com/Genarito) made their first contribution in [#9790](https://github.com/encode/django-rest-framework/pull/9790)
109+
* [@TheFunctionalGuy](https://github.com/TheFunctionalGuy) made their first contribution in [#9799](https://github.com/encode/django-rest-framework/pull/9799)
110+
* [@mahdighadiriii](https://github.com/mahdighadiriii) made their first contribution in [#9800](https://github.com/encode/django-rest-framework/pull/9800)
111+
* [@p-r-a-v-i-n](https://github.com/p-r-a-v-i-n) made their first contribution in [#9801](https://github.com/encode/django-rest-framework/pull/9801)
112+
* [@itssimon](https://github.com/itssimon) made their first contribution in [#9718](https://github.com/encode/django-rest-framework/pull/9718)
113+
* [@huynguyengl99](https://github.com/huynguyengl99) made their first contribution in [#9785](https://github.com/encode/django-rest-framework/pull/9785)
114+
* [@corenting](https://github.com/corenting) made their first contribution in [#9087](https://github.com/encode/django-rest-framework/pull/9087)
115+
* [@killerdevildog](https://github.com/killerdevildog) made their first contribution in [#9742](https://github.com/encode/django-rest-framework/pull/9742)
116+
* [@dayandavid](https://github.com/dayandavid) made their first contribution in [#9820](https://github.com/encode/django-rest-framework/pull/9820)
117+
* [@abhishektiwari](https://github.com/abhishektiwari) made their first contribution in [#9826](https://github.com/encode/django-rest-framework/pull/9826)
118+
* [@HoodyH](https://github.com/HoodyH) made their first contribution in [#9775](https://github.com/encode/django-rest-framework/pull/9775)
119+
* [@Shrikantgiri25](https://github.com/Shrikantgiri25) made their first contribution in [#9808](https://github.com/encode/django-rest-framework/pull/9808)
120+
* [@JVPinheiroReis](https://github.com/JVPinheiroReis) made their first contribution in [#9828](https://github.com/encode/django-rest-framework/pull/9828)
121+
* [@m000](https://github.com/m000) made their first contribution in [#9836](https://github.com/encode/django-rest-framework/pull/9836)
122+
* [@Nabute](https://github.com/Nabute) made their first contribution in [#9767](https://github.com/encode/django-rest-framework/pull/9767)
123+
* [@therealjozber](https://github.com/therealjozber) made their first contribution in [#9845](https://github.com/encode/django-rest-framework/pull/9845)
124+
* [@nexapytech](https://github.com/nexapytech) made their first contribution in [#9867](https://github.com/encode/django-rest-framework/pull/9867)
125+
* [@RispaJoseph](https://github.com/RispaJoseph) made their first contribution in [#9874](https://github.com/encode/django-rest-framework/pull/9874)
126+
* [@LorenzoGuideri](https://github.com/LorenzoGuideri) made their first contribution in [#9875](https://github.com/encode/django-rest-framework/pull/9875)
127+
* [@maldoinc](https://github.com/maldoinc) made their first contribution in [#9893](https://github.com/encode/django-rest-framework/pull/9893)
128+
* [@0Nafi0](https://github.com/0Nafi0) made their first contribution in [#9861](https://github.com/encode/django-rest-framework/pull/9861)
129+
* [@MoeSalah1999](https://github.com/MoeSalah1999) made their first contribution in [#9870](https://github.com/encode/django-rest-framework/pull/9870)
130+
* [@kelsonbrito50](https://github.com/kelsonbrito50) made their first contribution in [#9901](https://github.com/encode/django-rest-framework/pull/9901)
131+
* [@fbozhang](https://github.com/fbozhang) made their first contribution in [#9735](https://github.com/encode/django-rest-framework/pull/9735)
132+
* [@minorytanaka](https://github.com/minorytanaka) made their first contribution in [#9903](https://github.com/encode/django-rest-framework/pull/9903)
133+
* [@kosbemrunal](https://github.com/kosbemrunal) made their first contribution in [#9904](https://github.com/encode/django-rest-framework/pull/9904)
134+
* [@htvictoire](https://github.com/htvictoire) made their first contribution in [#9916](https://github.com/encode/django-rest-framework/pull/9916)
135+
136+
**Full Changelog**: [3.16.1...3.17.0](https://github.com/encode/django-rest-framework/compare/3.16.1...3.17.0)
137+
39138
## 3.16.x series
40139

41140
### 3.16.1

docs/community/tutorials-and-resources.md

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,13 @@ There are a wide range of resources available for learning and using Django REST
66

77
<div class="book-covers">
88
<a class="book-cover" href="https://hellowebapp.com/order/">
9-
<img src="../../img/books/hwa-cover.png"/>
9+
<img src="../../img/books/hwa-cover.png" style="height: 300px"/>
1010
</a>
1111
<a class="book-cover" href="https://www.twoscoopspress.com/products/two-scoops-of-django-1-11">
12-
<img src="../../img/books/tsd-cover.png"/>
12+
<img src="../../img/books/tsd-cover.png" style="height: 300px"/>
1313
</a>
1414
<a class="book-cover" href="https://djangoforapis.com">
15-
<img src="../../img/books/dfa-40-cover.jpg"/>
16-
</a>
17-
<a class="book-cover" href="https://books.agiliq.com/projects/django-api-polls-tutorial/en/latest/">
18-
<img src="../../img/books/bda-cover.png"/>
15+
<img src="../../img/books/dfa-40-cover.jpg" style="height: 300px"/>
1916
</a>
2017
</div>
2118

@@ -43,7 +40,7 @@ There are a wide range of resources available for learning and using Django REST
4340
* [Django Polls Tutorial API][django-polls-api]
4441
* [Django REST Framework Tutorial: Todo API][django-rest-framework-todo-api]
4542
* [Tutorial: Django REST with React (Django 2.0)][django-rest-react-valentinog]
46-
43+
* [Building APIs with Django and Django REST framework](https://books.agiliq.com/projects/django-api-polls-tutorial/en/latest/)
4744

4845
## Videos
4946

@@ -137,4 +134,4 @@ Want your Django REST Framework talk/tutorial/article to be added to our website
137134
[django-rest-react-valentinog]: https://www.valentinog.com/blog/tutorial-api-django-rest-react/
138135
[doordash-implementing-rest-apis]: https://doordash.engineering/2013/10/07/implementing-rest-apis-with-embedded-privacy/
139136
[developing-restful-apis-with-django-rest-framework]: https://testdriven.io/courses/django-rest-framework/
140-
[django-con-2018]: https://youtu.be/pY-oje5b5Qk?si=AOU6tLi0IL1_pVzq
137+
[django-con-2018]: https://youtu.be/pY-oje5b5Qk?si=AOU6tLi0IL1_pVzq

docs/index.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ Some reasons you might want to use REST framework:
6565

6666
REST framework requires the following:
6767

68-
* Django (4.2, 5.0, 5.1, 5.2)
68+
* Django (4.2, 5.0, 5.1, 5.2, 6.0)
6969
* Python (3.10, 3.11, 3.12, 3.13, 3.14)
7070

7171
We **highly recommend** and only officially support the latest patch release of

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ test = [
5656
docs = [
5757
# MkDocs to build our documentation.
5858
"mkdocs==1.6.1",
59-
"mkdocs-material[imaging]==9.7.4",
59+
"mkdocs-material[imaging]==9.7.5",
6060
# pylinkvalidator to check for broken links in documentation.
6161
"pylinkvalidator==0.3",
6262
]

rest_framework/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
"""
99

1010
__title__ = 'Django REST framework'
11-
__version__ = '3.16.1'
11+
__version__ = '3.17.1'
1212
__author__ = 'Tom Christie'
1313
__license__ = 'BSD-3-Clause'
1414
__copyright__ = 'Copyright 2011-2023 Encode OSS Ltd'

rest_framework/renderers.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -345,7 +345,7 @@ def render_field(self, field, parent_style):
345345
except AttributeError:
346346
format_ = api_settings.DATETIME_FORMAT
347347

348-
if format_ is not None:
348+
if format_ is not None and field.value not in (None, ''):
349349
# field.value is expected to be a string
350350
# https://www.django-rest-framework.org/api-guide/fields/#datetimefield
351351
field_value = field.value
@@ -358,10 +358,11 @@ def render_field(self, field, parent_style):
358358
else datetime.datetime.strptime(field_value, format_)
359359
)
360360

361-
# The format of an input type="datetime-local" is "yyyy-MM-ddThh:mm"
362-
# followed by optional ":ss" or ":ss.SSS", so keep only the first three
363-
# digits of milliseconds to avoid browser console error.
364-
field.value = field.value.replace(tzinfo=None).isoformat(timespec="milliseconds")
361+
if isinstance(field.value, datetime.datetime):
362+
# The format of an input type="datetime-local" is "yyyy-MM-ddThh:mm"
363+
# followed by optional ":ss" or ":ss.SSS", so keep only the first three
364+
# digits of milliseconds to avoid browser console error.
365+
field.value = field.value.replace(tzinfo=None).isoformat(timespec="milliseconds")
365366

366367
if 'template' in style:
367368
template_name = style['template']

tests/test_renderers.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -566,6 +566,22 @@ def test_datetime_field_rendering_timezone_aware_datetime(self):
566566
"2024-12-23T09:55:30.345" # Rendered in -06:00
567567
)
568568

569+
def test_datetime_field_rendering_empty_string_raises_no_error(self):
570+
"""
571+
Regression test for #9927 (issue):
572+
Ensures that an empty string value doesn't cause a ValueError
573+
when the HTMLFormRenderer tries to parse it via fromisoformat.
574+
"""
575+
self._assert_datetime_rendering("", "")
576+
577+
def test_datetime_field_rendering_none_value_raises_no_error(self):
578+
"""
579+
Additional regression coverage for #9927:
580+
Ensures that a None value, which is converted to an empty string
581+
by as_form_field(), doesn't cause a ValueError when rendered.
582+
"""
583+
self._assert_datetime_rendering(None, "")
584+
569585

570586
class TestHTMLFormRenderer(TestCase):
571587
def setUp(self):

0 commit comments

Comments
 (0)