Skip to content

Commit 4a92b5f

Browse files
authored
Merge branch 'main' into python-support-315
2 parents 77c73be + 045af72 commit 4a92b5f

33 files changed

Lines changed: 916 additions & 171 deletions

‎.github/workflows/prepare-release.yml‎

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,12 +119,18 @@ jobs:
119119
f.write(content)
120120
EOF
121121
122+
- name: Install gettext
123+
run: sudo apt install -y gettext
124+
125+
- name: Ensure translations are up to date
126+
run: uv tool run --from django django-admin compilemessages
127+
122128
- name: Create branch and commit
123129
run: |
124130
git config user.name "github-actions[bot]"
125131
git config user.email "github-actions[bot]@users.noreply.github.com"
126132
git checkout -b feat/release-${{ github.event.inputs.version }}
127-
git add rest_framework/__init__.py docs/community/release-notes.md
133+
git add rest_framework/__init__.py docs/community/release-notes.md rest_framework/locale
128134
git commit -m "Prepare release ${{ github.event.inputs.version }}"
129135
git push origin feat/release-${{ github.event.inputs.version }}
130136

‎.pre-commit-config.yaml‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ repos:
99
- id: check-symlinks
1010
- id: check-toml
1111
- repo: https://github.com/PyCQA/isort
12-
rev: 7.0.0
12+
rev: 9.0.1
1313
hooks:
1414
- id: isort
1515
- repo: https://github.com/PyCQA/flake8
@@ -28,7 +28,7 @@ repos:
2828
- black==26.1.0
2929
- repo: https://github.com/codespell-project/codespell
3030
# Configuration for codespell is in pyproject.toml
31-
rev: v2.4.1
31+
rev: v2.4.3
3232
hooks:
3333
- id: codespell
3434
additional_dependencies:
@@ -40,10 +40,10 @@ repos:
4040
- id: pyupgrade
4141
args: ["--py310-plus", "--keep-percent-format"]
4242
- repo: https://github.com/tox-dev/pyproject-fmt
43-
rev: v2.11.1
43+
rev: v2.29.4
4444
hooks:
4545
- id: pyproject-fmt
4646
- repo: https://github.com/adamchainz/django-upgrade
47-
rev: 1.24.0
47+
rev: 1.32.0
4848
hooks:
4949
- id: django-upgrade

‎docs/api-guide/fields.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ For example:
6363

6464
When serializing the instance, default will be used if the object attribute or dictionary key is not present in the instance.
6565

66-
Note that setting a `default` value implies that the field is not required. Including both the `default` and `required` keyword arguments is invalid and will raise an error.
66+
Note that setting a `default` value implies that the field is not required. Combining `default` with `required=True` is invalid and will raise an error. An explicit `required=False` alongside `default` is allowed, but redundant.
6767

6868
### `allow_null`
6969

‎docs/api-guide/pagination.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -369,7 +369,7 @@ The [`DRF-extensions` package][drf-extensions] includes a [`PaginateByMaxMixin`
369369

370370
### drf-proxy-pagination
371371

372-
The [`drf-proxy-pagination` package][drf-proxy-pagination] includes a `ProxyPagination` class which allows to choose pagination class with a query parameter.
372+
The [`drf-proxy-pagination` package][drf-proxy-pagination] includes a `ProxyPagination` class which allows choosing the pagination class with a query parameter.
373373

374374
### link-header-pagination
375375

‎docs/api-guide/serializers.md‎

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,9 @@ When deserializing data, you always need to call `is_valid()` before attempting
159159

160160
Each key in the dictionary will be the field name, and the values will be lists of strings of any error messages corresponding to that field. The `non_field_errors` key may also be present, and will list any general validation errors. The name of the `non_field_errors` key may be customized using the `NON_FIELD_ERRORS_KEY` REST framework setting.
161161

162-
When deserializing a list of items, errors will be returned as a list of dictionaries representing each of the deserialized items.
162+
When deserializing a list of items, errors are returned as a dictionary keyed by the indexes of invalid items. Valid items are omitted from the dictionary.
163+
164+
To temporarily use the list-based format from versions before REST framework 3.18, set `LIST_SERIALIZER_ERRORS_AS_DICT` to `False`. This format includes an empty dictionary for each valid item and is deprecated. It will be removed in REST framework 3.20.
163165

164166
#### Raising an exception on invalid data
165167

@@ -559,6 +561,8 @@ This option should be a list or tuple of field names, and is declared as follows
559561

560562
Model fields which have `editable=False` set, and `AutoField` fields will be set to read-only by default, and do not need to be added to the `read_only_fields` option.
561563

564+
Please keep in mind that, if a field has already been explicitly declared on the serializer class (or a parent class), then the `read_only_fields` option will not apply to that field. Set `read_only=True` on the field itself instead.
565+
562566
!!! note
563567
There is a special-case where a read-only field is part of a `unique_together` constraint at the model level. In this case the field is required by the serializer class in order to validate the constraint, but should also not be editable by the user.
564568

@@ -591,7 +595,7 @@ This option is a dictionary, mapping field names to a dictionary of keyword argu
591595
user.save()
592596
return user
593597

594-
Please keep in mind that, if the field has already been explicitly declared on the serializer class, then the `extra_kwargs` option will be ignored.
598+
Please keep in mind that, if the field has already been explicitly declared on the serializer class, then the `extra_kwargs` option will be ignored. The same is true of `read_only_fields`, which is implemented using `extra_kwargs`.
595599

596600
### Relational fields
597601

@@ -852,6 +856,30 @@ Here's an example of how you might choose to implement multiple updates:
852856
class Meta:
853857
list_serializer_class = BookListSerializer
854858

859+
If the child serializer includes uniqueness validators (`UniqueValidator`, `UniqueTogetherValidator`, or the
860+
`UniqueForDateValidator` family), they need to know which object each item in the list is updating, so
861+
that the object itself is not reported as a uniqueness conflict. By default the child serializer's
862+
`.instance` is the whole queryset or list that was passed to the list serializer, so these validators will
863+
raise a `RuntimeError` during a multiple update. To support this, override `run_child_validation()` on
864+
your `ListSerializer` subclass to set the child's `.instance` and `.initial_data` for each item before
865+
validation. For example, if `self.instance` is a queryset:
866+
867+
class BookListSerializer(serializers.ListSerializer):
868+
def run_child_validation(self, data):
869+
# `.instance` stays `None` for items that do not exist yet, and
870+
# for malformed items, which child validation will then reject
871+
# as usual.
872+
self.child.instance = None
873+
try:
874+
self.child.instance = self.instance.filter(pk=data['id']).first()
875+
except (TypeError, KeyError, ValueError):
876+
pass
877+
self.child.initial_data = data
878+
return super().run_child_validation(data)
879+
880+
def update(self, instance, validated_data):
881+
...
882+
855883
### Customizing ListSerializer initialization
856884

857885
When a serializer with `many=True` is instantiated, we need to determine which arguments and keyword arguments should be passed to the `.__init__()` method for both the child `Serializer` class, and for the parent `ListSerializer` class.
@@ -1190,7 +1218,7 @@ The [html-json-forms][html-json-forms] package provides an algorithm and seriali
11901218

11911219
### DRF Writable Nested
11921220

1193-
The [drf-writable-nested][drf-writable-nested] package provides writable nested model serializer which allows to create/update models with nested related data.
1221+
The [drf-writable-nested][drf-writable-nested] package provides writable nested model serializer which allows creating/updating models with nested related data.
11941222

11951223
### DRF Encrypt Content
11961224

‎docs/api-guide/settings.md‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -462,6 +462,16 @@ A string representing the key that should be used for serializer errors that do
462462

463463
Default: `'non_field_errors'`
464464

465+
#### LIST_SERIALIZER_ERRORS_AS_DICT
466+
467+
Controls the format of per-item validation errors produced by `ListSerializer`, including serializers instantiated with `many=True`.
468+
469+
When set to `True`, errors are returned as a dictionary keyed by the indexes of invalid items. Valid items are omitted from the dictionary. This format was introduced in REST framework 3.18.0.
470+
471+
When set to `False`, errors are returned in the list-based format used before REST framework 3.18, with one entry for each input item and an empty dictionary for each valid item. This format is deprecated and will be removed in REST framework 3.20.
472+
473+
Default: `True`
474+
465475
#### URL_FIELD_NAME
466476

467477
A string representing the key that should be used for the URL fields generated by `HyperlinkedModelSerializer`.

‎docs/api-guide/validators.md‎

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ The validator should be applied to *serializer classes*, like so:
102102
]
103103

104104
!!! note
105-
The `UniqueTogetherValidator` class always imposes an implicit constraint that all the fields it applies to are always treated as required. Fields with `default` values are an exception to this as they always supply a value even when omitted from user input.
105+
The `UniqueTogetherValidator` class always imposes an implicit constraint that all the fields it applies to are always treated as required. Fields with `default` values are an exception to this as they always supply a value even when omitted from user input. When using `ModelSerializer`, model fields with `null=True` default to `None`, and `CharField` or `TextField` model fields with `blank=True` default to an empty string, so these are not treated as required either.
106106

107107
## UniqueForDateValidator
108108

@@ -222,6 +222,28 @@ For example:
222222
extra_kwargs = {'client': {'required': False}}
223223
validators = [] # Remove a default "unique together" constraint.
224224

225+
### UniqueConstraint with conditions
226+
227+
When using Django's `UniqueConstraint` with conditions that reference other model fields, DRF will automatically use
228+
`UniqueTogetherValidator` instead of field-level `UniqueValidator`. This ensures proper validation behavior when the constraint
229+
effectively involves multiple fields.
230+
231+
For example, a single-field constraint with a condition becomes a multi-field validation when the condition references other fields.
232+
233+
class MyModel(models.Model):
234+
name = models.CharField(max_length=100)
235+
status = models.CharField(max_length=20)
236+
237+
class Meta:
238+
constraints = [
239+
models.UniqueConstraint(
240+
fields=['name'],
241+
condition=models.Q(status='active'),
242+
name='unique_active_name'
243+
)
244+
]
245+
246+
225247
### Updating nested serializers
226248

227249
When applying an update to an existing instance, uniqueness validators will

‎docs/community/contributing.md‎

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,52 @@ Once you've made a pull request take a look at the build status in the GitHub in
160160

161161
Sometimes, in order to ensure your code works on various different versions of Django, Python or third party libraries, you'll need to run slightly different code depending on the environment. Any code that branches in this way should be isolated into the `compat.py` module, and should provide a single common interface that the rest of the codebase can use.
162162

163+
### Deprecating a feature
164+
165+
REST framework follows a formal [deprecation policy][deprecation-policy]: a feature deprecated during the development of `3.X` keeps working until `3.X+2` removes it. In practice this means that a pull request that deprecates something should not change any behavior, only warn about the change to come.
166+
167+
The warning classes live in `rest_framework/deprecation.py`, and are named after the release that removes the feature. For example, during the development of 3.0 you'll find:
168+
169+
* `RemovedInDRF31Warning`, a `DeprecationWarning`, aliased as `RemovedInNextDRFVersionWarning`. Features flagged with it were deprecated one cycle ago, and are removed in the next release.
170+
* `RemovedInDRF32Warning`, a `PendingDeprecationWarning`, aliased as `RemovedAfterNextDRFVersionWarning`. This is the class **new deprecations** should use.
171+
172+
To deprecate a feature:
173+
174+
1. Keep the existing behavior working, and raise the pending deprecation warning from the deprecated code path. Refer to the warning class by its concrete name rather than the alias, so that it's easy to grep for everything that has to go when the removal comes around.
175+
176+
```python
177+
import warnings
178+
179+
from rest_framework.deprecation import RemovedInDRF32Warning
180+
181+
warnings.warn(
182+
"The `foo` argument is deprecated and will be removed in DRF 3.2. "
183+
"Use `bar` instead.",
184+
RemovedInDRF32Warning,
185+
stacklevel=2,
186+
)
187+
```
188+
189+
The message should say what is deprecated, which release removes it, and what to use instead. Pick a `stacklevel` that points the warning at the user's code rather than at REST framework internals.
190+
191+
2. If the deprecation can be worked around ahead of time, consider adding a setting that opts into the new behavior early, and mention it in the warning message.
192+
193+
3. Add a test asserting that the warning is raised, and update the existing tests of the old behavior to expect it:
194+
195+
```python
196+
with pytest.warns(RemovedInDRF32Warning, match="Use `bar` instead"):
197+
...
198+
```
199+
200+
4. Update the documentation for the feature, flagging the deprecation with an admonition:
201+
202+
```
203+
!!! warning
204+
The `foo` argument is deprecated and will be removed in DRF 3.2. Use `bar` instead.
205+
```
206+
207+
Removing a deprecated feature is the mirror image, and happens as part of the release process. When 3.1 is released, everything raising a `RemovedInDRF31Warning` is deleted, along with the class itself and the documentation of the deprecated behavior, `RemovedInDRF32Warning` is escalated to subclass `DeprecationWarning`, a fresh `RemovedInDRF33Warning` is added for the next cycle's deprecations, and the two aliases are moved on to point at them.
208+
163209
## Documentation
164210

165211
The documentation for REST framework is built from the [Markdown][markdown] source files in [the docs directory][docs].
@@ -244,3 +290,4 @@ The documentation theme styles `info`, `warning`, `tip` and `danger` admonition
244290
[repo]: https://github.com/encode/django-rest-framework
245291
[how-to-fork]: https://help.github.com/articles/fork-a-repo/
246292
[admonition]: https://python-markdown.github.io/extensions/admonition/
293+
[deprecation-policy]: release-notes.md#deprecation-policy

‎docs/community/project-management.md‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,8 @@ Our PyPI releases is automated in GitHub actions on tag pushes. The following te
5858
- `pyproject.toml` Python & Django version trove classifiers
5959
- `README` Python & Django versions
6060
- `docs` Python & Django versions
61-
- Ensure the pull request increments the version to `*.*.*` in [`restframework/__init__.py`](https://github.com/encode/django-rest-framework/blob/main/rest_framework/__init__.py).
61+
- Ensure the pull request increments the version to `*.*.*` in [`rest_framework/__init__.py`](https://github.com/encode/django-rest-framework/blob/main/rest_framework/__init__.py).
62+
- Ensure the deprecation warnings in [`rest_framework/deprecation.py`](https://github.com/encode/django-rest-framework/blob/main/rest_framework/deprecation.py) have been rotated for the release, following the [deprecation policy](release-notes.md#deprecation-policy).
6263
- Ensure documentation validates
6364
- Build and serve docs `mkdocs serve`
6465
- Validate links `pylinkvalidate.py -P http://127.0.0.1:8000`

‎docs/community/release-notes.md‎

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,29 @@ The timeline for deprecation of a feature present in version 1.0 would work as f
2222

2323
* Version 1.3 would remove the deprecated bits of API entirely.
2424

25+
Two aliases always point at the warning classes for the current release cycle, so you can refer to them without updating your configuration on every release:
26+
27+
* `RemovedInNextDRFVersionWarning` — features that the next feature release removes. Always a `DeprecationWarning`.
28+
* `RemovedAfterNextDRFVersionWarning` — features that the release after next removes. Always a `PendingDeprecationWarning`.
29+
2530
Note that in line with Django's policy, any parts of the framework not mentioned in the documentation should generally be considered private API, and may be subject to change.
2631

32+
### Surfacing deprecations in your test suite
33+
34+
Deprecations are much easier to deal with one release at a time, so it's worth failing your test suite on anything that is due to be removed in the next release, while keeping the earlier warnings visible.
35+
36+
If you run your tests with pytest, add the following to your `pyproject.toml`:
37+
38+
```toml
39+
[tool.pytest]
40+
filterwarnings = [
41+
"error::rest_framework.deprecation.RemovedInNextDRFVersionWarning",
42+
"always::rest_framework.deprecation.RemovedAfterNextDRFVersionWarning",
43+
]
44+
```
45+
46+
When a warning does fire, the message names the release that removes the feature and what to use instead. Once you have dealt with them all, drop the `always` filter down to `error` too, and you'll be ready for the release after next as well.
47+
2748
## Upgrading
2849

2950
To upgrade Django REST framework to the latest version, use pip:
@@ -38,6 +59,31 @@ You can determine your currently installed version using `pip show`:
3859

3960
## 3.18.x series
4061

62+
### 3.18.1
63+
64+
**Date**: 7th September 2026
65+
66+
#### Bug fixes
67+
68+
* Fix duplicate validation errors for GenericIPAddressField with protocol by [@browniebroke](https://github.com/browniebroke) in [#10011](https://github.com/encode/django-rest-framework/pull/10011)
69+
* Fix int64 format detection for negative `IntegerField` minimums in OpenAPI schema definition by [@zainnadeem786](https://github.com/zainnadeem786) in [#9989](https://github.com/encode/django-rest-framework/pull/9989)
70+
* Fix list serializer on unique constraint validator by [@MehrazRumman](https://github.com/MehrazRumman) in [#10033](https://github.com/encode/django-rest-framework/pull/10033)
71+
* Reject non-finite values (nan, inf) in FloatField by [@winklemad](https://github.com/winklemad) in [#9998](https://github.com/encode/django-rest-framework/pull/9998)
72+
73+
#### Other changes
74+
75+
* Add a compatibility setting for `ListSerializer` error formats by [@Kub-AT](https://github.com/Kub-AT) in [#10027](https://github.com/encode/django-rest-framework/pull/10027)
76+
* Expand deprecation classes and review deprecation policy by [@browniebroke](https://github.com/browniebroke) in [#10034](https://github.com/encode/django-rest-framework/pull/10034)
77+
78+
#### New Contributors
79+
80+
* [@abidaliamanat9](https://github.com/abidaliamanat9) made their first contribution in [#10028](https://github.com/encode/django-rest-framework/pull/10028)
81+
* [@epuronta](https://github.com/epuronta) made their first contribution in [#10030](https://github.com/encode/django-rest-framework/pull/10030)
82+
* [@Kub-AT](https://github.com/Kub-AT) made their first contribution in [#10027](https://github.com/encode/django-rest-framework/pull/10027)
83+
* [@winklemad](https://github.com/winklemad) made their first contribution in [#9998](https://github.com/encode/django-rest-framework/pull/9998)
84+
85+
**Full Changelog**: [3.18.0...3.18.1](https://github.com/encode/django-rest-framework/compare/3.18.0...3.18.1)
86+
4187
### 3.18.0
4288

4389
**Date**: 7th August 2026

0 commit comments

Comments
 (0)