Merge branch 'master' into add-korean-translations

This commit is contained in:
tgoddessana 2025-04-02 03:07:59 +09:00
commit 42c09e49ec
44 changed files with 443 additions and 190 deletions

View File

@ -1,17 +0,0 @@
---
name: Issue
about: Please only raise an issue if you've been advised to do so after discussion. Thanks! 🙏
---
## Checklist
<!--
Note: REST framework is considered feature-complete. New functionality should be implemented outside the core REST framework. For details, please check the docs: https://www.django-rest-framework.org/community/third-party-packages/#about-third-party-packages
-->
- [ ] Raised initially as discussion #...
- [ ] This is not a feature request suitable for implementation outside this project. Please elaborate what it is:
- [ ] compatibility fix for new Django/Python version ...
- [ ] other type of bug fix
- [ ] other type of improvement that does not touch existing code or change existing behavior (e.g. wrapper for new Django field)
- [ ] I have reduced the issue to the simplest possible case.

View File

@ -1,6 +0,0 @@
blank_issues_enabled: false
contact_links:
- name: Discussions
url: https://github.com/encode/django-rest-framework/discussions
about: >
The "Discussions" forum is where you want to start. 💖

View File

@ -14,7 +14,6 @@ jobs:
strategy:
matrix:
python-version:
- '3.8'
- '3.9'
- '3.10'
- '3.11'
@ -34,7 +33,7 @@ jobs:
run: python -m pip install --upgrade pip setuptools virtualenv wheel
- name: Install dependencies
run: python -m pip install --upgrade codecov tox
run: python -m pip install --upgrade tox
- name: Run tox targets for ${{ matrix.python-version }}
run: tox run -f py$(echo ${{ matrix.python-version }} | tr -d . | cut -f 1 -d '-')
@ -45,8 +44,9 @@ jobs:
tox -e base,dist,docs
- name: Upload coverage
run: |
codecov -e TOXENV,DJANGO
uses: codecov/codecov-action@v5
with:
env_vars: TOXENV,DJANGO
test-docs:
name: Test documentation links

View File

@ -2,6 +2,4 @@
At this point in its lifespan we consider Django REST framework to be essentially feature-complete. We may accept pull requests that track the continued development of Django versions, but would prefer not to accept new features or code formatting changes.
Apart from minor documentation changes, the [GitHub discussions page](https://github.com/encode/django-rest-framework/discussions) should generally be your starting point. Please only raise an issue or pull request if you've been recommended to do so after discussion.
The [Contributing guide in the documentation](https://www.django-rest-framework.org/community/contributing/) gives some more information on our process and code of conduct.

View File

@ -54,8 +54,8 @@ Some reasons you might want to use REST framework:
# Requirements
* Python 3.8+
* Django 4.2, 5.0, 5.1
* Python 3.9+
* Django 4.2, 5.0, 5.1, 5.2
We **highly recommend** and only officially support the latest patch release of
each Python and Django series.

View File

@ -2,8 +2,6 @@
## Reporting a Vulnerability
Security issues are handled under the supervision of the [Django security team](https://www.djangoproject.com/foundation/teams/#security-team).
**Please report security issues by emailing security@encode.io**.
**Please report security issues by emailing security@djangoproject.com**.
The project maintainers will then work with you to resolve any issues where required, prior to any public disclosure.
The project maintainers will then work with you to resolve any issues where required, prior to any public disclosure.

View File

@ -454,6 +454,12 @@ There are currently two forks of this project.
More information can be found in the [Documentation](https://django-rest-durin.readthedocs.io/en/latest/index.html).
## django-pyoidc
[dango-pyoidc][django_pyoidc] adds support for OpenID Connect (OIDC) authentication. This allows you to delegate user management to an Identity Provider, which can be used to implement Single-Sign-On (SSO). It provides support for most uses-cases, such as customizing how token info are mapped to user models, using OIDC audiences for access control, etc.
More information can be found in the [Documentation](https://django-pyoidc.readthedocs.io/latest/index.html).
[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
@ -490,4 +496,5 @@ More information can be found in the [Documentation](https://django-rest-durin.r
[drfpasswordless]: https://github.com/aaronn/django-rest-framework-passwordless
[django-rest-authemail]: https://github.com/celiao/django-rest-authemail
[django-rest-durin]: https://github.com/eshaan7/django-rest-durin
[login-required-middleware]: https://docs.djangoproject.com/en/stable/ref/middleware/#django.contrib.auth.middleware.LoginRequiredMiddleware
[login-required-middleware]: https://docs.djangoproject.com/en/stable/ref/middleware/#django.contrib.auth.middleware.LoginRequiredMiddleware
[django-pyoidc] : https://github.com/makinacorpus/django_pyoidc

View File

@ -552,7 +552,7 @@ For further examples on `HiddenField` see the [validators](validators.md) docume
---
**Note:** `HiddenField()` does not appear in `partial=True` serializer (when making `PATCH` request). This behavior might change in future, follow updates on [github discussion](https://github.com/encode/django-rest-framework/discussions/8259).
**Note:** `HiddenField()` does not appear in `partial=True` serializer (when making `PATCH` request).
---

View File

@ -628,12 +628,16 @@ The [drf-nested-routers package][drf-nested-routers] provides routers and relati
The [rest-framework-generic-relations][drf-nested-relations] library provides read/write serialization for generic foreign keys.
The [rest-framework-gm2m-relations][drf-gm2m-relations] library provides read/write serialization for [django-gm2m][django-gm2m-field].
[cite]: http://users.ece.utexas.edu/~adnan/pike.html
[reverse-relationships]: https://docs.djangoproject.com/en/stable/topics/db/queries/#following-relationships-backward
[routers]: https://www.django-rest-framework.org/api-guide/routers#defaultrouter
[generic-relations]: https://docs.djangoproject.com/en/stable/ref/contrib/contenttypes/#id1
[drf-nested-routers]: https://github.com/alanjds/drf-nested-routers
[drf-nested-relations]: https://github.com/Ian-Foote/rest-framework-generic-relations
[drf-gm2m-relations]: https://github.com/mojtabaakbari221b/rest-framework-gm2m-relations
[django-gm2m-field]: https://github.com/tkhyn/django-gm2m
[django-intermediary-manytomany]: https://docs.djangoproject.com/en/stable/topics/db/models/#intermediary-manytomany
[dealing-with-nested-objects]: https://www.django-rest-framework.org/api-guide/serializers/#dealing-with-nested-objects
[to_internal_value]: https://www.django-rest-framework.org/api-guide/serializers/#to_internal_valueself-data

View File

@ -525,7 +525,7 @@ Comma-separated values are a plain-text tabular data format, that can be easily
## LaTeX
[Rest Framework Latex] provides a renderer that outputs PDFs using Laulatex. It is maintained by [Pebble (S/F Software)][mypebble].
[Rest Framework Latex] provides a renderer that outputs PDFs using Lualatex. It is maintained by [Pebble (S/F Software)][mypebble].
[cite]: https://docs.djangoproject.com/en/stable/ref/template-response/#the-rendering-process

View File

@ -233,7 +233,7 @@ Serializer classes can also include reusable validators that are applied to the
class EventSerializer(serializers.Serializer):
name = serializers.CharField()
room_number = serializers.IntegerField(choices=[101, 102, 103, 201])
room_number = serializers.ChoiceField(choices=[101, 102, 103, 201])
date = serializers.DateField()
class Meta:

View File

@ -460,4 +460,4 @@ Default: `None`
[cite]: https://www.python.org/dev/peps/pep-0020/
[rfc4627]: https://www.ietf.org/rfc/rfc4627.txt
[heroku-minified-json]: https://github.com/interagent/http-api-design#keep-json-minified-in-all-responses
[strftime]: https://docs.python.org/3/library/time.html#time.strftime
[strftime]: https://docs.python.org/3/library/datetime.html#strftime-and-strptime-format-codes

View File

@ -25,9 +25,12 @@ The `APIRequestFactory` class supports an almost identical API to Django's stand
factory = APIRequestFactory()
request = factory.post('/notes/', {'title': 'new idea'})
# Using the standard RequestFactory API to encode JSON data
request = factory.post('/notes/', {'title': 'new idea'}, content_type='application/json')
#### Using the `format` argument
Methods which create a request body, such as `post`, `put` and `patch`, include a `format` argument, which make it easy to generate requests using a content type other than multipart form data. For example:
Methods which create a request body, such as `post`, `put` and `patch`, include a `format` argument, which make it easy to generate requests using a wide set of request formats. When using this argument, the factory will select an appropriate renderer and its configured `content_type`. For example:
# Create a JSON POST request
factory = APIRequestFactory()
@ -41,7 +44,7 @@ To support a wider set of request formats, or change the default format, [see th
If you need to explicitly encode the request body, you can do so by setting the `content_type` flag. For example:
request = factory.post('/notes/', json.dumps({'title': 'new idea'}), content_type='application/json')
request = factory.post('/notes/', yaml.dump({'title': 'new idea'}), content_type='application/yaml')
#### PUT and PATCH with form data

View File

@ -45,7 +45,7 @@ The default throttling policy may be set globally, using the `DEFAULT_THROTTLE_C
}
}
The rate descriptions used in `DEFAULT_THROTTLE_RATES` may include `second`, `minute`, `hour` or `day` as the throttle period.
The rates used in `DEFAULT_THROTTLE_RATES` can be specified over a period of second, minute, hour or day. The period must be specified after the `/` separator using `s`, `m`, `h` or `d`, respectively. For increased clarity, extended units such as `second`, `minute`, `hour`, `day` or even abbreviations like `sec`, `min`, `hr` are allowed, as only the first character is relevant to identify the rate.
You can also set the throttling policy on a per-view or per-viewset basis,
using the `APIView` class-based views.
@ -110,7 +110,7 @@ You'll need to remember to also set your custom throttle class in the `'DEFAULT_
The built-in throttle implementations are open to [race conditions][race], so under high concurrency they may allow a few extra requests through.
If your project relies on guaranteeing the number of requests during concurrent requests, you will need to implement your own throttle class. See [issue #5181][gh5181] for more details.
If your project relies on guaranteeing the number of requests during concurrent requests, you will need to implement your own throttle class.
---
@ -220,5 +220,4 @@ The following is an example of a rate throttle, that will randomly throttle 1 in
[identifying-clients]: http://oxpedia.org/wiki/index.php?title=AppSuite:Grizzly#Multiple_Proxies_in_front_of_the_cluster
[cache-setting]: https://docs.djangoproject.com/en/stable/ref/settings/#caches
[cache-docs]: https://docs.djangoproject.com/en/stable/topics/cache/#setting-up-the-cache
[gh5181]: https://github.com/encode/django-rest-framework/issues/5181
[race]: https://en.wikipedia.org/wiki/Race_condition#Data_race

View File

@ -48,7 +48,7 @@ If we open up the Django shell using `manage.py shell` we can now
CustomerReportSerializer():
id = IntegerField(label='ID', read_only=True)
time_raised = DateTimeField(read_only=True)
reference = CharField(max_length=20, validators=[<UniqueValidator(queryset=CustomerReportRecord.objects.all())>])
reference = CharField(max_length=20, validators=[UniqueValidator(queryset=CustomerReportRecord.objects.all())])
description = CharField(style={'type': 'textarea'})
The interesting bit here is the `reference` field. We can see that the uniqueness constraint is being explicitly enforced by a validator on the serializer field.
@ -166,7 +166,7 @@ If you want the date field to be entirely hidden from the user, then use `Hidden
---
**Note:** `HiddenField()` does not appear in `partial=True` serializer (when making `PATCH` request). This behavior might change in future, follow updates on [github discussion](https://github.com/encode/django-rest-framework/discussions/8259).
**Note:** `HiddenField()` does not appear in `partial=True` serializer (when making `PATCH` request).
---

View File

@ -128,6 +128,8 @@ You may inspect these attributes to adjust behavior based on the current action.
permission_classes = [IsAdminUser]
return [permission() for permission in permission_classes]
**Note**: the `action` attribute is not available in the `get_parsers`, `get_authenticators` and `get_content_negotiator` methods, as it is set _after_ they are called in the framework lifecycle. If you override one of these methods and try to access the `action` attribute in them, you will get an `AttributeError` error.
## Marking extra actions for routing
If you have ad-hoc methods that should be routable, you can mark them as such with the `@action` decorator. Like regular actions, extra actions may be intended for either a single object, or an entire collection. To indicate this, set the `detail` argument to `True` or `False`. The router will configure its URL patterns accordingly. e.g., the `DefaultRouter` will configure detail actions to contain `pk` in their URL patterns.

View File

@ -0,0 +1,42 @@
<style>
.promo li a {
float: left;
width: 130px;
height: 20px;
text-align: center;
margin: 10px 30px;
padding: 150px 0 0 0;
background-position: 0 50%;
background-size: 130px auto;
background-repeat: no-repeat;
font-size: 120%;
color: black;
}
.promo li {
list-style: none;
}
</style>
# Django REST framework 3.16
At the Internet, on March 28th, 2025, we are happy to announce the release of Django REST framework 3.16.
## Updated Django and Python support
The latest release now fully supports Django 5.1 and the upcoming 5.2 LTS as well as Python 3.13.
The current minimum versions of Django is now 4.2 and Python 3.9.
## Django LoginRequiredMiddleware
The new `LoginRequiredMiddleware` introduced by Django 5.1 can now be used alongside Django REST Framework, however it is not honored for API views as an equivalent behaviour can be configured via `DEFAULT_AUTHENTICATION_CLASSES`. See [our dedicated section](../api-guide/authentication.md#django-51-loginrequiredmiddleware) in the docs for more information.
## Improved support for UniqueConstraint
The generation of validators for [UniqueConstraint](https://docs.djangoproject.com/en/stable/ref/models/constraints/#uniqueconstraint) has been improved to support better nullable fields and constraints with conditions.
## Other fixes and improvements
There are a number of fixes and minor improvements in this release, ranging from documentation, internal infrastructure (typing, testing, requirements, deprecation, etc.), security and overall behaviour.
See the [release notes](release-notes.md) page for a complete listing.

View File

@ -4,8 +4,6 @@
>
> &mdash; [Tim Berners-Lee][cite]
There are many ways you can contribute to Django REST framework. We'd like it to be a community-led project, so please get involved and help shape the future of the project.
!!! note
At this point in its lifespan we consider Django REST framework to be feature-complete. We focus on pull requests that track the continued development of Django versions, and generally do not accept new features or code formatting changes.
@ -30,24 +28,9 @@ The [Django code of conduct][code-of-conduct] gives a fuller set of guidelines f
# Issues
Our contribution process is that the [GitHub discussions page](https://github.com/encode/django-rest-framework/discussions) should generally be your starting point. Please only raise an issue or pull request if you've been recommended to do so after discussion.
Some tips on good potential issue reporting:
* Django REST framework is considered feature-complete. Please do not file requests to change behavior, unless it is required for security reasons or to maintain compatibility with upcoming Django or Python versions.
* Search the GitHub project page for related items, and make sure you're running the latest version of REST framework before reporting an issue.
* Feature requests will typically be closed with a recommendation that they be implemented outside the core REST framework library (e.g. as third-party libraries). This approach allows us to keep down the maintenance overhead of REST framework, so that the focus can be on continued stability and great documentation.
## Triaging issues
Getting involved in triaging incoming issues is a good way to start contributing. Every single ticket that comes into the ticket tracker needs to be reviewed in order to determine what the next steps should be. Anyone can help out with this, you just need to be willing to
* Read through the ticket - does it make sense, is it missing any context that would help explain it better?
* Is the ticket reported in the correct place, would it be better suited as a discussion on the discussion group?
* If the ticket is a bug report, can you reproduce it? Are you able to write a failing test case that demonstrates the issue and that can be submitted as a pull request?
* If the ticket is a feature request, could the feature request instead be implemented as a third party package?
* If a ticket hasn't had much activity and addresses something you need, then comment on the ticket and try to find out what's needed to get it moving again.
# Development
To start developing on Django REST framework, first create a Fork from the
@ -206,7 +189,6 @@ If you want to draw attention to a note or warning, use a pair of enclosing line
[code-of-conduct]: https://www.djangoproject.com/conduct/
[google-group]: https://groups.google.com/forum/?fromgroups#!forum/django-rest-framework
[so-filter]: https://stackexchange.com/filters/66475/rest-framework
[issues]: https://github.com/encode/django-rest-framework/issues?state=open
[pep-8]: https://www.python.org/dev/peps/pep-0008/
[build-status]: ../img/build-status.png
[pull-requests]: https://help.github.com/articles/using-pull-requests

View File

@ -34,7 +34,6 @@ Further notes for maintainers:
* Code changes should come in the form of a pull request - do not push directly to master.
* Maintainers should typically not merge their own pull requests.
* Each issue/pull request should have exactly one label once triaged.
* Search for un-triaged issues with [is:open no:label][un-triaged].
---
@ -157,7 +156,6 @@ The following issues still need to be addressed:
* Document ownership and management of the security mailing list.
[bus-factor]: https://en.wikipedia.org/wiki/Bus_factor
[un-triaged]: https://github.com/encode/django-rest-framework/issues?q=is%3Aopen+no%3Alabel
[transifex-project]: https://www.transifex.com/projects/p/django-rest-framework/
[transifex-client]: https://pypi.org/project/transifex-client/
[translation-memory]: http://docs.transifex.com/guides/tm#let-tm-automatically-populate-translations

View File

@ -36,6 +36,90 @@ You can determine your currently installed version using `pip show`:
---
## 3.16.x series
### 3.16.0
**Date**: 28th March 2025
This release is considered a significant release to improve upstream support with Django and Python. Some of these may change the behaviour of existing features and pre-existing behaviour. Specifically, some fixes were added to around the support of `UniqueConstraint` with nullable fields which will improve built-in serializer validation.
## Features
* Add official support for Django 5.1 and its new `LoginRequiredMiddleware` in [#9514](https://github.com/encode/django-rest-framework/pull/9514) and [#9657](https://github.com/encode/django-rest-framework/pull/9657)
* Add official Django 5.2a1 support in [#9634](https://github.com/encode/django-rest-framework/pull/9634)
* Add support for Python 3.13 in [#9527](https://github.com/encode/django-rest-framework/pull/9527) and [#9556](https://github.com/encode/django-rest-framework/pull/9556)
* Support Django 2.1+ test client JSON data automatically serialized in [#6511](https://github.com/encode/django-rest-framework/pull/6511) and fix a regression in [#9615](https://github.com/encode/django-rest-framework/pull/9615)
## Bug fixes
* Fix unique together validator to respect condition's fields from `UniqueConstraint` in [#9360](https://github.com/encode/django-rest-framework/pull/9360)
* Fix raising on nullable fields part of `UniqueConstraint` in [#9531](https://github.com/encode/django-rest-framework/pull/9531)
* Fix `unique_together` validation with source in [#9482](https://github.com/encode/django-rest-framework/pull/9482)
* Added protections to `AttributeError` raised within properties in [#9455](https://github.com/encode/django-rest-framework/pull/9455)
* Fix `get_template_context` to handle also lists in [#9467](https://github.com/encode/django-rest-framework/pull/9467)
* Fix "Converter is already registered" deprecation warning. in [#9512](https://github.com/encode/django-rest-framework/pull/9512)
* Fix noisy warning and accept integers as min/max values of `DecimalField` in [#9515](https://github.com/encode/django-rest-framework/pull/9515)
* Fix usages of `open()` in `setup.py` in [#9661](https://github.com/encode/django-rest-framework/pull/9661)
## Translations
* Add some missing Chinese translations in [#9505](https://github.com/encode/django-rest-framework/pull/9505)
* Fix spelling mistakes in Farsi language were corrected in [#9521](https://github.com/encode/django-rest-framework/pull/9521)
* Fixing and adding missing Brazilian Portuguese translations in [#9535](https://github.com/encode/django-rest-framework/pull/9535)
## Removals
* Remove support for Python 3.8 in [#9670](https://github.com/encode/django-rest-framework/pull/9670)
* Remove long deprecated code from request wrapper in [#9441](https://github.com/encode/django-rest-framework/pull/9441)
* Remove deprecated `AutoSchema._get_reference` method in [#9525](https://github.com/encode/django-rest-framework/pull/9525)
## Documentation and internal changes
* Provide tests for hashing of `OperandHolder` in [#9437](https://github.com/encode/django-rest-framework/pull/9437)
* Update documentation: Add `adrf` third party package in [#9198](https://github.com/encode/django-rest-framework/pull/9198)
* Update tutorials links in Community contributions docs in [#9476](https://github.com/encode/django-rest-framework/pull/9476)
* Fix usage of deprecated Django function in example from docs in [#9509](https://github.com/encode/django-rest-framework/pull/9509)
* Move path converter docs into a separate section in [#9524](https://github.com/encode/django-rest-framework/pull/9524)
* Add test covering update view without `queryset` attribute in [#9528](https://github.com/encode/django-rest-framework/pull/9528)
* Fix Transifex link in [#9541](https://github.com/encode/django-rest-framework/pull/9541)
* Fix example `httpie` call in docs in [#9543](https://github.com/encode/django-rest-framework/pull/9543)
* Fix example for serializer field with choices in docs in [#9563](https://github.com/encode/django-rest-framework/pull/9563)
* Remove extra `<>` in validators example in [#9590](https://github.com/encode/django-rest-framework/pull/9590)
* Update `strftime` link in the docs in [#9624](https://github.com/encode/django-rest-framework/pull/9624)
* Switch to codecov GHA in [#9618](https://github.com/encode/django-rest-framework/pull/9618)
* Add note regarding availability of the `action` attribute in 'Introspecting ViewSet actions' docs section in [#9633](https://github.com/encode/django-rest-framework/pull/9633)
* Improved description of allowed throttling rates in documentation in [#9640](https://github.com/encode/django-rest-framework/pull/9640)
* Add `rest-framework-gm2m-relations` package to the list of 3rd party libraries in [#9063](https://github.com/encode/django-rest-framework/pull/9063)
* Fix a number of typos in the test suite in the docs in [#9662](https://github.com/encode/django-rest-framework/pull/9662)
* Add `django-pyoidc` as a third party authentication library in [#9667](https://github.com/encode/django-rest-framework/pull/9667)
## New Contributors
* [`@maerteijn`](https://github.com/maerteijn) made their first contribution in [#9198](https://github.com/encode/django-rest-framework/pull/9198)
* [`@FraCata00`](https://github.com/FraCata00) made their first contribution in [#9444](https://github.com/encode/django-rest-framework/pull/9444)
* [`@AlvaroVega`](https://github.com/AlvaroVega) made their first contribution in [#9451](https://github.com/encode/django-rest-framework/pull/9451)
* [`@james`](https://github.com/james)-mchugh made their first contribution in [#9455](https://github.com/encode/django-rest-framework/pull/9455)
* [`@ifeanyidavid`](https://github.com/ifeanyidavid) made their first contribution in [#9479](https://github.com/encode/django-rest-framework/pull/9479)
* [`@p`](https://github.com/p)-schlickmann made their first contribution in [#9480](https://github.com/encode/django-rest-framework/pull/9480)
* [`@akkuman`](https://github.com/akkuman) made their first contribution in [#9505](https://github.com/encode/django-rest-framework/pull/9505)
* [`@rafaelgramoschi`](https://github.com/rafaelgramoschi) made their first contribution in [#9509](https://github.com/encode/django-rest-framework/pull/9509)
* [`@Sinaatkd`](https://github.com/Sinaatkd) made their first contribution in [#9521](https://github.com/encode/django-rest-framework/pull/9521)
* [`@gtkacz`](https://github.com/gtkacz) made their first contribution in [#9535](https://github.com/encode/django-rest-framework/pull/9535)
* [`@sliverc`](https://github.com/sliverc) made their first contribution in [#9556](https://github.com/encode/django-rest-framework/pull/9556)
* [`@gabrielromagnoli1987`](https://github.com/gabrielromagnoli1987) made their first contribution in [#9543](https://github.com/encode/django-rest-framework/pull/9543)
* [`@cheehong1030`](https://github.com/cheehong1030) made their first contribution in [#9563](https://github.com/encode/django-rest-framework/pull/9563)
* [`@amansharma612`](https://github.com/amansharma612) made their first contribution in [#9590](https://github.com/encode/django-rest-framework/pull/9590)
* [`@Gluroda`](https://github.com/Gluroda) made their first contribution in [#9616](https://github.com/encode/django-rest-framework/pull/9616)
* [`@deepakangadi`](https://github.com/deepakangadi) made their first contribution in [#9624](https://github.com/encode/django-rest-framework/pull/9624)
* [`@EXG1O`](https://github.com/EXG1O) made their first contribution in [#9633](https://github.com/encode/django-rest-framework/pull/9633)
* [`@decadenza`](https://github.com/decadenza) made their first contribution in [#9640](https://github.com/encode/django-rest-framework/pull/9640)
* [`@mojtabaakbari221b`](https://github.com/mojtabaakbari221b) made their first contribution in [#9063](https://github.com/encode/django-rest-framework/pull/9063)
* [`@mikemanger`](https://github.com/mikemanger) made their first contribution in [#9661](https://github.com/encode/django-rest-framework/pull/9661)
* [`@gbip`](https://github.com/gbip) made their first contribution in [#9667](https://github.com/encode/django-rest-framework/pull/9667)
**Full Changelog**: https://github.com/encode/django-rest-framework/compare/3.15.2...3.16.0
## 3.15.x series
### 3.15.2
@ -121,7 +205,7 @@ Date: 15th March 2024
* Fix 404 when page query parameter is empty string [[#8578](https://github.com/encode/django-rest-framework/pull/8578)]
* Fixes instance check in ListSerializer.to_representation [[#8726](https://github.com/encode/django-rest-framework/pull/8726)] [[#8727](https://github.com/encode/django-rest-framework/pull/8727)]
* FloatField will crash if the input is a number that is too big [[#8725](https://github.com/encode/django-rest-framework/pull/8725)]
* Add missing DurationField to SimpleMetada label_lookup [[#8702](https://github.com/encode/django-rest-framework/pull/8702)]
* Add missing DurationField to SimpleMetadata label_lookup [[#8702](https://github.com/encode/django-rest-framework/pull/8702)]
* Add support for Python 3.11 [[#8752](https://github.com/encode/django-rest-framework/pull/8752)]
* Make request consistently available in pagination classes [[#8764](https://github.com/encode/django-rest-framework/pull/9764)]
* Possibility to remove trailing zeros on DecimalFields representation [[#6514](https://github.com/encode/django-rest-framework/pull/6514)]
@ -428,7 +512,7 @@ Be sure to upgrade to Python 3 before upgrading to Django REST Framework 3.10.
* Allow hashing of ErrorDetail. [#5932][gh5932]
* Correct schema parsing for JSONField [#5878][gh5878]
* Render descriptions (from help_text) using safe [#5869][gh5869]
* Removed input value from deault_error_message [#5881][gh5881]
* Removed input value from default_error_message [#5881][gh5881]
* Added min_value/max_value support in DurationField [#5643][gh5643]
* Fixed instance being overwritten in pk-only optimization try/except block [#5747][gh5747]
* Fixed AttributeError from items filter when value is None [#5981][gh5981]

View File

@ -32,7 +32,7 @@ We suggest adding your package to the [REST Framework][rest-framework-grid] grid
#### Adding to the Django REST framework docs
Create a [Pull Request][drf-create-pr] or [Issue][drf-create-issue] on GitHub, and we'll add a link to it from the main REST framework documentation. You can add your package under **Third party packages** of the API Guide section that best applies, like [Authentication][authentication] or [Permissions][permissions]. You can also link your package under the [Third Party Packages][third-party-packages] section.
Create a [Pull Request][drf-create-pr] on GitHub, and we'll add a link to it from the main REST framework documentation. You can add your package under **Third party packages** of the API Guide section that best applies, like [Authentication][authentication] or [Permissions][permissions]. You can also link your package under the [Third Party Packages][third-party-packages] section.
#### Announce on the discussion group.
@ -44,7 +44,7 @@ Django REST Framework has a growing community of developers, packages, and resou
Check out a grid detailing all the packages and ecosystem around Django REST Framework at [Django Packages][rest-framework-grid].
To submit new content, [open an issue][drf-create-issue] or [create a pull request][drf-create-pr].
To submit new content, [create a pull request][drf-create-pr].
## Async Support
@ -62,6 +62,7 @@ To submit new content, [open an issue][drf-create-issue] or [create a pull reque
* [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.
* [dango-pyoidc][django-pyoidc] adds support for OpenID Connect (OIDC) authentication.
### Permissions
@ -173,7 +174,6 @@ To submit new content, [open an issue][drf-create-issue] or [create a pull reque
[drf-compat]: https://github.com/encode/django-rest-framework/blob/master/rest_framework/compat.py
[rest-framework-grid]: https://www.djangopackages.com/grids/g/django-rest-framework/
[drf-create-pr]: https://github.com/encode/django-rest-framework/compare
[drf-create-issue]: https://github.com/encode/django-rest-framework/issues/new
[authentication]: ../api-guide/authentication.md
[permissions]: ../api-guide/permissions.md
[third-party-packages]: ../topics/third-party-packages/#existing-third-party-packages
@ -256,3 +256,4 @@ To submit new content, [open an issue][drf-create-issue] or [create a pull reque
[drf-api-action]: https://github.com/Ori-Roza/drf-api-action
[drf-redesign]: https://github.com/youzarsiph/drf-redesign
[drf-material]: https://github.com/youzarsiph/drf-material
[django-pyoidc] : https://github.com/makinacorpus/django_pyoidc

View File

@ -12,7 +12,7 @@ There are a wide range of resources available for learning and using Django REST
<img src="../../img/books/tsd-cover.png"/>
</a>
<a class="book-cover" href="https://djangoforapis.com">
<img src="../../img/books/dfa-cover.jpg"/>
<img src="../../img/books/dfa-40-cover.jpg"/>
</a>
<a class="book-cover" href="https://books.agiliq.com/projects/django-api-polls-tutorial/en/latest/">
<img src="../../img/books/bda-cover.png"/>
@ -28,7 +28,6 @@ There are a wide range of resources available for learning and using Django REST
* [Beginner's Guide to the Django REST Framework][beginners-guide-to-the-django-rest-framework]
* [Django REST Framework - An Introduction][drf-an-intro]
* [Django REST Framework Tutorial][drf-tutorial]
* [Django REST Framework Course][django-rest-framework-course]
* [Building a RESTful API with Django REST Framework][building-a-restful-api-with-drf]
* [Getting Started with Django REST Framework and AngularJS][getting-started-with-django-rest-framework-and-angularjs]
* [End to End Web App with Django REST Framework & AngularJS][end-to-end-web-app-with-django-rest-framework-angularjs]
@ -41,8 +40,8 @@ There are a wide range of resources available for learning and using Django REST
* [Creating a Production Ready API with Python and Django REST Framework Part 2][creating-a-production-ready-api-with-python-and-drf-part2]
* [Creating a Production Ready API with Python and Django REST Framework Part 3][creating-a-production-ready-api-with-python-and-drf-part3]
* [Creating a Production Ready API with Python and Django REST Framework Part 4][creating-a-production-ready-api-with-python-and-drf-part4]
* [Django REST Framework Tutorial - Build a Blog API][django-rest-framework-tutorial-build-a-blog]
* [Django REST Framework & React Tutorial - Build a Todo List API][django-rest-framework-react-tutorial-build-a-todo-list]
* [Django Polls Tutorial API][django-polls-api]
* [Django REST Framework Tutorial: Todo API][django-rest-framework-todo-api]
* [Tutorial: Django REST with React (Django 2.0)][django-rest-react-valentinog]
@ -51,11 +50,11 @@ There are a wide range of resources available for learning and using Django REST
### Talks
* [Level Up! Rethinking the Web API Framework][pycon-us-2017]
* [How to Make a Full Fledged REST API with Django OAuth Toolkit][full-fledged-rest-api-with-django-oauth-tookit]
* [How to Make a Full Fledged REST API with Django OAuth Toolkit][full-fledged-rest-api-with-django-oauth-toolkit]
* [Django REST API - So Easy You Can Learn It in 25 Minutes][django-rest-api-so-easy]
* [Tom Christie about Django Rest Framework at Django: Under The Hood][django-under-hood-2014]
* [Django REST Framework: Schemas, Hypermedia & Client Libraries][pycon-uk-2016]
* [Finally Understand Authentication in Django REST Framework][django-con-2018]
### Tutorials
@ -105,7 +104,6 @@ Want your Django REST Framework talk/tutorial/article to be added to our website
[api-development-with-django-and-django-rest-framework]: https://bnotions.com/news-and-insights/api-development-with-django-and-django-rest-framework/
[cdrf.co]:http://www.cdrf.co
[medium-django-rest-framework]: https://medium.com/django-rest-framework
[django-rest-framework-course]: https://teamtreehouse.com/library/django-rest-framework
[pycon-uk-2016]: https://www.youtube.com/watch?v=FjmiGh7OqVg
[django-under-hood-2014]: https://www.youtube.com/watch?v=3cSsbe-tA0E
[integrating-pandas-drf-and-bokeh]: https://web.archive.org/web/20180104205117/http://machinalis.com/blog/pandas-django-rest-framework-bokeh/
@ -121,10 +119,10 @@ Want your Django REST Framework talk/tutorial/article to be added to our website
[creating-a-production-ready-api-with-python-and-drf-part2]: https://www.andreagrandi.it/posts/creating-a-production-ready-api-with-python-and-django-rest-framework-part-2/
[creating-a-production-ready-api-with-python-and-drf-part3]: https://www.andreagrandi.it/posts/creating-a-production-ready-api-with-python-and-django-rest-framework-part-3/
[creating-a-production-ready-api-with-python-and-drf-part4]: https://www.andreagrandi.it/posts/creating-a-production-ready-api-with-python-and-django-rest-framework-part-4/
[django-rest-framework-tutorial-build-a-blog]: https://wsvincent.com/django-rest-framework-tutorial/
[django-rest-framework-react-tutorial-build-a-todo-list]: https://wsvincent.com/django-rest-framework-react-tutorial/
[django-polls-api]: https://learndjango.com/tutorials/django-polls-tutorial-api
[django-rest-framework-todo-api]: https://learndjango.com/tutorials/django-rest-framework-tutorial-todo-api
[django-rest-api-so-easy]: https://www.youtube.com/watch?v=cqP758k1BaQ
[full-fledged-rest-api-with-django-oauth-tookit]: https://www.youtube.com/watch?v=M6Ud3qC2tTk
[full-fledged-rest-api-with-django-oauth-toolkit]: https://www.youtube.com/watch?v=M6Ud3qC2tTk
[drf-in-your-pjs]: https://www.youtube.com/watch?v=xMtHsWa72Ww
[building-a-rest-api-using-django-and-drf]: https://www.youtube.com/watch?v=PwssEec3IRw
[drf-tutorials]: https://www.youtube.com/watch?v=axRCBgbOJp8&list=PLJtp8Jm8EDzjgVg9vVyIUMoGyqtegj7FH
@ -139,3 +137,4 @@ Want your Django REST Framework talk/tutorial/article to be added to our website
[django-rest-react-valentinog]: https://www.valentinog.com/blog/tutorial-api-django-rest-react/
[doordash-implementing-rest-apis]: https://doordash.engineering/2013/10/07/implementing-rest-apis-with-embedded-privacy/
[developing-restful-apis-with-django-rest-framework]: https://testdriven.io/courses/django-rest-framework/
[django-con-2018]: https://youtu.be/pY-oje5b5Qk?si=AOU6tLi0IL1_pVzq

Binary file not shown.

After

Width:  |  Height:  |  Size: 755 KiB

View File

@ -87,8 +87,8 @@ continued development by **[signing up for a paid plan][funding]**.
REST framework requires the following:
* Django (4.2, 5.0, 5.1)
* Python (3.8, 3.9, 3.10, 3.11, 3.12, 3.13)
* Django (4.2, 5.0, 5.1, 5.2)
* Python (3.9, 3.10, 3.11, 3.12, 3.13)
We **highly recommend** and only officially support the latest patch release of
each Python and Django series.
@ -196,9 +196,7 @@ For priority support please sign up for a [professional or premium sponsorship p
## Security
Security issues are handled under the supervision of the [Django security team](https://www.djangoproject.com/foundation/teams/#security-team).
**Please report security issues by emailing security@djangoproject.com**.
**Please report security issues by emailing security@encode.io**.
The project maintainers will then work with you to resolve any issues where required, prior to any public disclosure.

View File

@ -66,6 +66,7 @@ nav:
- 'Contributing to REST framework': 'community/contributing.md'
- 'Project management': 'community/project-management.md'
- 'Release Notes': 'community/release-notes.md'
- '3.16 Announcement': 'community/3.16-announcement.md'
- '3.15 Announcement': 'community/3.15-announcement.md'
- '3.14 Announcement': 'community/3.14-announcement.md'
- '3.13 Announcement': 'community/3.13-announcement.md'

View File

@ -8,7 +8,7 @@ ______ _____ _____ _____ __
"""
__title__ = 'Django REST framework'
__version__ = '3.15.2'
__version__ = '3.16.0'
__author__ = 'Tom Christie'
__license__ = 'BSD 3-Clause'
__copyright__ = 'Copyright 2011-2023 Encode OSS Ltd'
@ -23,9 +23,5 @@ HTTP_HEADER_ENCODING = 'iso-8859-1'
ISO_8601 = 'iso-8601'
class RemovedInDRF316Warning(DeprecationWarning):
pass
class RemovedInDRF317Warning(PendingDeprecationWarning):
pass

View File

@ -3,6 +3,9 @@ The `compat` module provides support for backwards compatibility with older
versions of Django/Python, and compatibility wrappers around optional packages.
"""
import django
from django.db import models
from django.db.models.constants import LOOKUP_SEP
from django.db.models.sql.query import Node
from django.views.generic import View
@ -157,6 +160,10 @@ if django.VERSION >= (5, 1):
# 1) the list of validators and 2) the error message. Starting from
# Django 5.1 ip_address_validators only returns the list of validators
from django.core.validators import ip_address_validators
def get_referenced_base_fields_from_q(q):
return q.referenced_base_fields
else:
# Django <= 5.1: create a compatibility shim for ip_address_validators
from django.core.validators import \
@ -165,6 +172,35 @@ else:
def ip_address_validators(protocol, unpack_ipv4):
return _ip_address_validators(protocol, unpack_ipv4)[0]
# Django < 5.1: create a compatibility shim for Q.referenced_base_fields
# https://github.com/django/django/blob/5.1a1/django/db/models/query_utils.py#L179
def _get_paths_from_expression(expr):
if isinstance(expr, models.F):
yield expr.name
elif hasattr(expr, 'flatten'):
for child in expr.flatten():
if isinstance(child, models.F):
yield child.name
elif isinstance(child, models.Q):
yield from _get_children_from_q(child)
def _get_children_from_q(q):
for child in q.children:
if isinstance(child, Node):
yield from _get_children_from_q(child)
elif isinstance(child, tuple):
lhs, rhs = child
yield lhs
if hasattr(rhs, 'resolve_expression'):
yield from _get_paths_from_expression(rhs)
elif hasattr(child, 'resolve_expression'):
yield from _get_paths_from_expression(child)
def get_referenced_base_fields_from_q(q):
return {
child.split(LOOKUP_SEP, 1)[0] for child in _get_children_from_q(q)
}
# `separators` argument to `json.dumps()` differs between 2.x and 3.x
# See: https://bugs.python.org/issue22767

View File

@ -1,7 +1,7 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
#
#
# Translators:
# Cloves Oliveira <clovesolivier23@gmail.com>, 2020
# Craig Blaszczyk <masterjakul@gmail.com>, 2015
@ -10,6 +10,7 @@
# Hugo Leonardo Chalhoub Mendonça <hugoleonardocm@live.com>, 2015
# Jonatas Baldin <jonatas.baldin@gmail.com>, 2017
# Gabriel Mitelman Tkacz <gmtkacz@proton.me>, 2024
# Matheus Oliveira <moliveiracdev@gmail.com>, 2025
msgid ""
msgstr ""
"Project-Id-Version: Django REST framework\n"
@ -141,7 +142,7 @@ msgstr "Não foi possível satisfazer a requisição do cabeçalho Accept."
#: exceptions.py:212
#, python-brace-format
msgid "Unsupported media type \"{media_type}\" in request."
msgstr "Tipo de mídia \"{media_type}\" no pedido não é suportado."
msgstr "Tipo de mídia \"{media_type}\" no pedido não é suportado."
#: exceptions.py:223
msgid "Request was throttled."
@ -200,17 +201,17 @@ msgstr "Este valor não corresponde ao padrão exigido."
msgid ""
"Enter a valid \"slug\" consisting of letters, numbers, underscores or "
"hyphens."
msgstr "Entrar um \"slug\" válido que consista de letras, números, sublinhados ou hífens."
msgstr "Insira um \"slug\" válido que consista de letras, números, sublinhados ou hífens."
#: fields.py:839
msgid ""
"Enter a valid \"slug\" consisting of Unicode letters, numbers, underscores, "
"or hyphens."
msgstr "Digite um \"slug\" válido que consista de letras Unicode, números, sublinhados ou hífens."
msgstr "Insira um \"slug\" válido que consista de letras Unicode, números, sublinhados ou hífens."
#: fields.py:854
msgid "Enter a valid URL."
msgstr "Entrar um URL válido."
msgstr "Insira um URL válido."
#: fields.py:867
msgid "Must be a valid UUID."
@ -290,12 +291,12 @@ msgstr "Necessário uma data mas recebeu uma data e hora."
#: fields.py:1303
#, python-brace-format
msgid "Time has wrong format. Use one of these formats instead: {format}."
msgstr "Formato inválido para Tempo. Use um dos formatos a seguir: {format}."
msgstr "Formato inválido para tempo. Use um dos formatos a seguir: {format}."
#: fields.py:1365
#, python-brace-format
msgid "Duration has wrong format. Use one of these formats instead: {format}."
msgstr "Formato inválido para Duração. Use um dos formatos a seguir: {format}."
msgstr "Formato inválido para duração. Use um dos formatos a seguir: {format}."
#: fields.py:1399 fields.py:1456
#, python-brace-format
@ -348,7 +349,7 @@ msgstr "Certifique-se de que o nome do arquivo tem menos de {max_length} caracte
msgid ""
"Upload a valid image. The file you uploaded was either not an image or a "
"corrupted image."
msgstr "Fazer upload de uma imagem válida. O arquivo enviado não é um arquivo de imagem ou está corrompido."
msgstr "Faça upload de uma imagem válida. O arquivo enviado não é um arquivo de imagem ou está corrompido."
#: fields.py:1604 relations.py:486 serializers.py:571
msgid "This list may not be empty."
@ -375,7 +376,7 @@ msgstr "Este dicionário não pode estar vazio."
#: fields.py:1755
msgid "Value must be valid JSON."
msgstr "Valor devo ser JSON válido."
msgstr "Valor deve ser JSON válido."
#: filters.py:49 templates/rest_framework/filters/search.html:2
msgid "Search"
@ -395,11 +396,11 @@ msgstr "Qual campo usar ao ordenar os resultados."
#: filters.py:287
msgid "ascending"
msgstr "ascendente"
msgstr "crescente"
#: filters.py:288
msgid "descending"
msgstr "descendente"
msgstr "decrescente"
#: pagination.py:174
msgid "A page number within the paginated result set."
@ -531,7 +532,7 @@ msgstr "Nenhum item para escholher."
#: validators.py:39
msgid "This field must be unique."
msgstr "Esse campo deve ser único."
msgstr "Esse campo deve ser único."
#: validators.py:89
#, python-brace-format

View File

@ -11,9 +11,7 @@ from django.core.validators import (
from django.db import models
from django.utils.encoding import force_str
from rest_framework import (
RemovedInDRF316Warning, exceptions, renderers, serializers
)
from rest_framework import exceptions, renderers, serializers
from rest_framework.compat import inflection, uritemplate
from rest_framework.fields import _UnvalidatedField, empty
from rest_framework.settings import api_settings
@ -721,11 +719,3 @@ class AutoSchema(ViewInspector):
path = path[1:]
return [path.split('/')[0].replace('_', '-')]
def _get_reference(self, serializer):
warnings.warn(
"Method `_get_reference()` has been renamed to `get_reference()`. "
"The old name will be removed in DRF v3.16.",
RemovedInDRF316Warning, stacklevel=2
)
return self.get_reference(serializer)

View File

@ -26,7 +26,9 @@ from django.utils import timezone
from django.utils.functional import cached_property
from django.utils.translation import gettext_lazy as _
from rest_framework.compat import postgres_fields
from rest_framework.compat import (
get_referenced_base_fields_from_q, postgres_fields
)
from rest_framework.exceptions import ErrorDetail, ValidationError
from rest_framework.fields import get_error_detail
from rest_framework.settings import api_settings
@ -1425,20 +1427,20 @@ class ModelSerializer(Serializer):
def get_unique_together_constraints(self, model):
"""
Returns iterator of (fields, queryset), each entry describes an unique together
constraint on `fields` in `queryset`.
Returns iterator of (fields, queryset, condition_fields, condition),
each entry describes an unique together constraint on `fields` in `queryset`
with respect of constraint's `condition`.
"""
for parent_class in [model] + list(model._meta.parents):
for unique_together in parent_class._meta.unique_together:
yield unique_together, model._default_manager
yield unique_together, model._default_manager, [], None
for constraint in parent_class._meta.constraints:
if isinstance(constraint, models.UniqueConstraint) and len(constraint.fields) > 1:
yield (
constraint.fields,
model._default_manager
if constraint.condition is None
else model._default_manager.filter(constraint.condition)
)
if constraint.condition is None:
condition_fields = []
else:
condition_fields = list(get_referenced_base_fields_from_q(constraint.condition))
yield (constraint.fields, model._default_manager, condition_fields, constraint.condition)
def get_uniqueness_extra_kwargs(self, field_names, declared_fields, extra_kwargs):
"""
@ -1470,9 +1472,10 @@ class ModelSerializer(Serializer):
# Include each of the `unique_together` and `UniqueConstraint` field names,
# so long as all the field names are included on the serializer.
for unique_together_list, queryset in self.get_unique_together_constraints(model):
if set(field_names).issuperset(unique_together_list):
unique_constraint_names |= set(unique_together_list)
for unique_together_list, queryset, condition_fields, condition in self.get_unique_together_constraints(model):
unique_together_list_and_condition_fields = set(unique_together_list) | set(condition_fields)
if set(field_names).issuperset(unique_together_list_and_condition_fields):
unique_constraint_names |= unique_together_list_and_condition_fields
# Now we have all the field names that have uniqueness constraints
# applied, we can add the extra 'required=...' or 'default=...'
@ -1490,6 +1493,8 @@ class ModelSerializer(Serializer):
default = timezone.now
elif unique_constraint_field.has_default():
default = unique_constraint_field.default
elif unique_constraint_field.null:
default = None
else:
default = empty
@ -1592,12 +1597,13 @@ class ModelSerializer(Serializer):
# Note that we make sure to check `unique_together` both on the
# base model class, but also on any parent classes.
validators = []
for unique_together, queryset in self.get_unique_together_constraints(self.Meta.model):
for unique_together, queryset, condition_fields, condition in self.get_unique_together_constraints(self.Meta.model):
# Skip if serializer does not map to all unique together sources
if not set(source_map).issuperset(unique_together):
unique_together_and_condition_fields = set(unique_together) | set(condition_fields)
if not set(source_map).issuperset(unique_together_and_condition_fields):
continue
for source in unique_together:
for source in unique_together_and_condition_fields:
assert len(source_map[source]) == 1, (
"Unable to create `UniqueTogetherValidator` for "
"`{model}.{field}` as `{serializer}` has multiple "
@ -1616,7 +1622,9 @@ class ModelSerializer(Serializer):
field_names = tuple(source_map[f][0] for f in unique_together)
validator = UniqueTogetherValidator(
queryset=queryset,
fields=field_names
fields=field_names,
condition_fields=tuple(source_map[f][0] for f in condition_fields),
condition=condition,
)
validators.append(validator)
return validators

View File

@ -150,15 +150,19 @@ class APIRequestFactory(DjangoRequestFactory):
"""
Encode the data returning a two tuple of (bytes, content_type)
"""
if data is None:
return ('', content_type)
return (b'', content_type)
assert format is None or content_type is None, (
'You may not set both `format` and `content_type`.'
)
if content_type:
try:
data = self._encode_json(data, content_type)
except AttributeError:
pass
# Content type specified explicitly, treat data as a raw bytestring
ret = force_bytes(data, settings.DEFAULT_CHARSET)

View File

@ -6,7 +6,9 @@ This gives us better separation of concerns, allows us to use single-step
object creation, and makes it possible to switch between using the implicit
`ModelSerializer` class and an equivalent explicit `Serializer` class.
"""
from django.core.exceptions import FieldError
from django.db import DataError
from django.db.models import Exists
from django.utils.translation import gettext_lazy as _
from rest_framework.exceptions import ValidationError
@ -23,6 +25,17 @@ def qs_exists(queryset):
return False
def qs_exists_with_condition(queryset, condition, against):
if condition is None:
return qs_exists(queryset)
try:
# use the same query as UniqueConstraint.validate
# https://github.com/django/django/blob/7ba2a0db20c37a5b1500434ca4ed48022311c171/django/db/models/constraints.py#L672
return (condition & Exists(queryset.filter(condition))).check(against)
except (TypeError, ValueError, DataError, FieldError):
return False
def qs_filter(queryset, **kwargs):
try:
return queryset.filter(**kwargs)
@ -99,10 +112,12 @@ class UniqueTogetherValidator:
missing_message = _('This field is required.')
requires_context = True
def __init__(self, queryset, fields, message=None):
def __init__(self, queryset, fields, message=None, condition_fields=None, condition=None):
self.queryset = queryset
self.fields = fields
self.message = message or self.message
self.condition_fields = [] if condition_fields is None else condition_fields
self.condition = condition
def enforce_required_fields(self, attrs, serializer):
"""
@ -114,7 +129,7 @@ class UniqueTogetherValidator:
missing_items = {
field_name: self.missing_message
for field_name in self.fields
for field_name in (*self.fields, *self.condition_fields)
if serializer.fields[field_name].source not in attrs
}
if missing_items:
@ -173,16 +188,19 @@ class UniqueTogetherValidator:
if attrs[field_name] != getattr(serializer.instance, field_name)
]
if checked_values and None not in checked_values and qs_exists(queryset):
condition_kwargs = {source: attrs[source] for source in self.condition_fields}
if checked_values and None not in checked_values and qs_exists_with_condition(queryset, self.condition, condition_kwargs):
field_names = ', '.join(self.fields)
message = self.message.format(field_names=field_names)
raise ValidationError(message, code='unique')
def __repr__(self):
return '<%s(queryset=%s, fields=%s)>' % (
return '<{}({})>'.format(
self.__class__.__name__,
smart_repr(self.queryset),
smart_repr(self.fields)
', '.join(
f'{attr}={smart_repr(getattr(self, attr))}'
for attr in ('queryset', 'fields', 'condition')
if getattr(self, attr) is not None)
)
def __eq__(self, other):

View File

@ -2,12 +2,11 @@ import os
import re
import shutil
import sys
from io import open
from setuptools import find_packages, setup
CURRENT_PYTHON = sys.version_info[:2]
REQUIRED_PYTHON = (3, 8)
REQUIRED_PYTHON = (3, 9)
# This check and everything above must remain compatible with Python 2.7.
if CURRENT_PYTHON < REQUIRED_PYTHON:
@ -36,7 +35,7 @@ an older version of Django REST Framework:
def read(f):
with open(f, 'r', encoding='utf-8') as file:
with open(f, encoding='utf-8') as file:
return file.read()
@ -83,7 +82,7 @@ setup(
packages=find_packages(exclude=['tests*']),
include_package_data=True,
install_requires=["django>=4.2", 'backports.zoneinfo;python_version<"3.9"'],
python_requires=">=3.8",
python_requires=">=3.9",
zip_safe=False,
classifiers=[
'Development Status :: 5 - Production/Stable',
@ -92,12 +91,12 @@ setup(
'Framework :: Django :: 4.2',
'Framework :: Django :: 5.0',
'Framework :: Django :: 5.1',
'Framework :: Django :: 5.2',
'Intended Audience :: Developers',
'License :: OSI Approved :: BSD License',
'Operating System :: OS Independent',
'Programming Language :: Python',
'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3.8',
'Programming Language :: Python :: 3.9',
'Programming Language :: Python :: 3.10',
'Programming Language :: Python :: 3.11',

View File

@ -1177,7 +1177,7 @@ class NamingCollisionViewSet(GenericViewSet):
"""
Example via: https://stackoverflow.com/questions/43778668/django-rest-framwork-occured-typeerror-link-object-does-not-support-item-ass/
"""
permision_class = ()
permission_classes = ()
@action(detail=False)
def detail(self, request):

View File

@ -19,7 +19,7 @@ factory = APIRequestFactory()
class SearchSplitTests(SimpleTestCase):
def test_keep_quoted_togheter_regardless_of_commas(self):
def test_keep_quoted_together_regardless_of_commas(self):
assert ['hello, world'] == list(filters.search_smart_split('"hello, world"'))
def test_strips_commas_around_quoted(self):
@ -516,7 +516,7 @@ class OrderingFilterModel(models.Model):
class OrderingFilterRelatedModel(models.Model):
related_object = models.ForeignKey(OrderingFilterModel, related_name="relateds", on_delete=models.CASCADE)
related_object = models.ForeignKey(OrderingFilterModel, related_name="related", on_delete=models.CASCADE)
index = models.SmallIntegerField(help_text="A non-related field to test with", default=0)
@ -725,9 +725,9 @@ class OrderingFilterTests(TestCase):
def test_ordering_by_aggregate_field(self):
# create some related models to aggregate order by
num_objs = [2, 5, 3]
for obj, num_relateds in zip(OrderingFilterModel.objects.all(),
num_objs):
for _ in range(num_relateds):
for obj, num_related in zip(OrderingFilterModel.objects.all(),
num_objs):
for _ in range(num_related):
new_related = OrderingFilterRelatedModel(
related_object=obj
)
@ -739,10 +739,10 @@ class OrderingFilterTests(TestCase):
ordering = 'title'
ordering_fields = '__all__'
queryset = OrderingFilterModel.objects.all().annotate(
models.Count("relateds"))
models.Count("related"))
view = OrderingListView.as_view()
request = factory.get('/', {'ordering': 'relateds__count'})
request = factory.get('/', {'ordering': 'related__count'})
response = view(request)
assert response.data == [
{'id': 1, 'title': 'zyx', 'text': 'abc'},

View File

@ -797,7 +797,7 @@ class TestIntegration(TestCase):
)
self.instance.many_to_many.set(self.many_to_many_targets)
def test_pk_retrival(self):
def test_pk_retrieval(self):
class TestSerializer(serializers.ModelSerializer):
class Meta:
model = RelationalModel

View File

@ -61,8 +61,8 @@ class TestAcceptedMediaType(TestCase):
def test_match_is_false_if_main_types_not_match(self):
mediatype = _MediaType('test_1')
anoter_mediatype = _MediaType('test_2')
assert mediatype.match(anoter_mediatype) is False
another_mediatype = _MediaType('test_2')
assert mediatype.match(another_mediatype) is False
def test_mediatype_match_is_false_if_keys_not_match(self):
mediatype = _MediaType(';test_param=foo')

View File

@ -513,7 +513,7 @@ class TestLimitOffset:
]
}
def test_erronous_offset(self):
def test_erroneous_offset(self):
request = Request(factory.get('/', {'limit': 5, 'offset': 1000}))
queryset = self.paginate_queryset(request)
self.get_paginated_content(queryset)

View File

@ -624,7 +624,7 @@ class PermissionsCompositionTests(TestCase):
)
assert composed_perm().has_permission(request, None) is True
def test_or_lazyness(self):
def test_or_laziness(self):
request = factory.get('/1', format='json')
request.user = AnonymousUser()
@ -644,7 +644,7 @@ class PermissionsCompositionTests(TestCase):
assert mock_deny.call_count == 1
assert mock_allow.call_count == 1
def test_object_or_lazyness(self):
def test_object_or_laziness(self):
request = factory.get('/1', format='json')
request.user = AnonymousUser()
@ -664,7 +664,7 @@ class PermissionsCompositionTests(TestCase):
assert mock_deny.call_count == 0
assert mock_allow.call_count == 1
def test_and_lazyness(self):
def test_and_laziness(self):
request = factory.get('/1', format='json')
request.user = AnonymousUser()
@ -684,7 +684,7 @@ class PermissionsCompositionTests(TestCase):
assert mock_deny.call_count == 1
mock_allow.assert_not_called()
def test_object_and_lazyness(self):
def test_object_and_laziness(self):
request = factory.get('/1', format='json')
request.user = AnonymousUser()

View File

@ -8,9 +8,11 @@ from django.shortcuts import redirect
from django.test import TestCase, override_settings
from django.urls import path
from rest_framework import fields, serializers
from rest_framework import fields, parsers, renderers, serializers, status
from rest_framework.authtoken.models import Token
from rest_framework.decorators import api_view
from rest_framework.decorators import (
api_view, parser_classes, renderer_classes
)
from rest_framework.response import Response
from rest_framework.test import (
APIClient, APIRequestFactory, URLPatternsTestCase, force_authenticate
@ -50,6 +52,18 @@ class BasicSerializer(serializers.Serializer):
flag = fields.BooleanField(default=lambda: True)
@api_view(['POST'])
@parser_classes((parsers.JSONParser,))
def post_json_view(request):
return Response(request.data)
@api_view(['DELETE'])
@renderer_classes((renderers.JSONRenderer, ))
def delete_json_view(request):
return Response(status=status.HTTP_204_NO_CONTENT)
@api_view(['POST'])
def post_view(request):
serializer = BasicSerializer(data=request.data)
@ -62,7 +76,9 @@ urlpatterns = [
path('session-view/', session_view),
path('redirect-view/', redirect_view),
path('redirect-view/<int:code>/', redirect_307_308_view),
path('post-view/', post_view)
path('post-json-view/', post_json_view),
path('delete-json-view/', delete_json_view),
path('post-view/', post_view),
]
@ -236,6 +252,22 @@ class TestAPITestClient(TestCase):
assert response.status_code == 200
assert response.data == {"flag": True}
def test_post_encodes_data_based_on_json_content_type(self):
data = {'data': True}
response = self.client.post(
'/post-json-view/',
data=data,
content_type='application/json'
)
assert response.status_code == 200
assert response.data == data
def test_delete_based_on_format(self):
response = self.client.delete('/delete-json-view/', format='json')
assert response.status_code == status.HTTP_204_NO_CONTENT
assert response.data is None
class TestAPIRequestFactory(TestCase):
def test_csrf_exempt_by_default(self):

View File

@ -406,7 +406,7 @@ class TestUniquenessTogetherValidation(TestCase):
"with a `UniqueTogetherValidator` using the desired field names.")
assert str(excinfo.value) == expected
def test_allow_explict_override(self):
def test_allow_explicit_override(self):
"""
Ensure validators can be explicitly removed..
"""
@ -441,6 +441,14 @@ class TestUniquenessTogetherValidation(TestCase):
serializer = NullUniquenessTogetherSerializer(data=data)
assert serializer.is_valid()
def test_ignore_validation_for_missing_nullable_fields(self):
data = {
'date': datetime.date(2000, 1, 1),
'race_name': 'Paris Marathon',
}
serializer = NullUniquenessTogetherSerializer(data=data)
assert serializer.is_valid(), serializer.errors
def test_do_not_ignore_validation_for_null_fields(self):
# None values that are not on fields part of the uniqueness constraint
# do not cause the instance to skip validation.
@ -513,7 +521,7 @@ class UniqueConstraintModel(models.Model):
race_name = models.CharField(max_length=100)
position = models.IntegerField()
global_id = models.IntegerField()
fancy_conditions = models.IntegerField(null=True)
fancy_conditions = models.IntegerField()
class Meta:
constraints = [
@ -535,7 +543,24 @@ class UniqueConstraintModel(models.Model):
name="unique_constraint_model_together_uniq",
fields=('race_name', 'position'),
condition=models.Q(race_name='example'),
)
),
models.UniqueConstraint(
name='unique_constraint_model_together_uniq2',
fields=('race_name', 'position'),
condition=models.Q(fancy_conditions__gte=10),
),
]
class UniqueConstraintNullableModel(models.Model):
title = models.CharField(max_length=100)
age = models.IntegerField(null=True)
tag = models.CharField(max_length=100, null=True)
class Meta:
constraints = [
# Unique constraint on 2 nullable fields
models.UniqueConstraint(name='unique_constraint', fields=('age', 'tag'))
]
@ -545,22 +570,31 @@ class UniqueConstraintSerializer(serializers.ModelSerializer):
fields = '__all__'
class UniqueConstraintNullableSerializer(serializers.ModelSerializer):
class Meta:
model = UniqueConstraintNullableModel
fields = ('title', 'age', 'tag')
class TestUniqueConstraintValidation(TestCase):
def setUp(self):
self.instance = UniqueConstraintModel.objects.create(
race_name='example',
position=1,
global_id=1
global_id=1,
fancy_conditions=1
)
UniqueConstraintModel.objects.create(
race_name='example',
position=2,
global_id=2
global_id=2,
fancy_conditions=1
)
UniqueConstraintModel.objects.create(
race_name='other',
position=1,
global_id=3
global_id=3,
fancy_conditions=1
)
def test_repr(self):
@ -575,33 +609,65 @@ class TestUniqueConstraintValidation(TestCase):
position = IntegerField\(.*required=True\)
global_id = IntegerField\(.*validators=\[<UniqueValidator\(queryset=UniqueConstraintModel.objects.all\(\)\)>\]\)
class Meta:
validators = \[<UniqueTogetherValidator\(queryset=<QuerySet \[<UniqueConstraintModel: UniqueConstraintModel object \(1\)>, <UniqueConstraintModel: UniqueConstraintModel object \(2\)>\]>, fields=\('race_name', 'position'\)\)>\]
validators = \[<UniqueTogetherValidator\(queryset=UniqueConstraintModel.objects.all\(\), fields=\('race_name', 'position'\), condition=<Q: \(AND: \('race_name', 'example'\)\)>\)>\]
""")
assert re.search(expected, repr(serializer)) is not None
def test_unique_together_field(self):
def test_unique_together_condition(self):
"""
UniqueConstraint fields and condition attributes must be passed
to UniqueTogetherValidator as fields and queryset
Fields used in UniqueConstraint's condition must be included
into queryset existence check
"""
serializer = UniqueConstraintSerializer()
assert len(serializer.validators) == 1
validator = serializer.validators[0]
assert validator.fields == ('race_name', 'position')
assert set(validator.queryset.values_list(flat=True)) == set(
UniqueConstraintModel.objects.filter(race_name='example').values_list(flat=True)
UniqueConstraintModel.objects.create(
race_name='condition',
position=1,
global_id=10,
fancy_conditions=10,
)
serializer = UniqueConstraintSerializer(data={
'race_name': 'condition',
'position': 1,
'global_id': 11,
'fancy_conditions': 9,
})
assert serializer.is_valid()
serializer = UniqueConstraintSerializer(data={
'race_name': 'condition',
'position': 1,
'global_id': 11,
'fancy_conditions': 11,
})
assert not serializer.is_valid()
def test_unique_together_condition_fields_required(self):
"""
Fields used in UniqueConstraint's condition must be present in serializer
"""
serializer = UniqueConstraintSerializer(data={
'race_name': 'condition',
'position': 1,
'global_id': 11,
})
assert not serializer.is_valid()
assert serializer.errors == {'fancy_conditions': ['This field is required.']}
class NoFieldsSerializer(serializers.ModelSerializer):
class Meta:
model = UniqueConstraintModel
fields = ('race_name', 'position', 'global_id')
serializer = NoFieldsSerializer()
assert len(serializer.validators) == 1
def test_single_field_uniq_validators(self):
"""
UniqueConstraint with single field must be transformed into
field's UniqueValidator
"""
# Django 5 includes Max and Min values validators for IntergerField
# Django 5 includes Max and Min values validators for IntegerField
extra_validators_qty = 2 if django_version[0] >= 5 else 0
#
serializer = UniqueConstraintSerializer()
assert len(serializer.validators) == 1
assert len(serializer.validators) == 2
validators = serializer.fields['global_id'].validators
assert len(validators) == 1 + extra_validators_qty
assert validators[0].queryset == UniqueConstraintModel.objects
@ -611,6 +677,12 @@ class TestUniqueConstraintValidation(TestCase):
ids_in_qs = {frozenset(v.queryset.values_list(flat=True)) for v in validators if hasattr(v, "queryset")}
assert ids_in_qs == {frozenset([1]), frozenset([3])}
def test_nullable_unique_constraint_fields_are_not_required(self):
serializer = UniqueConstraintNullableSerializer(data={'title': 'Bob'})
self.assertTrue(serializer.is_valid(), serializer.errors)
result = serializer.save()
self.assertIsInstance(result, UniqueConstraintNullableModel)
# Tests for `UniqueForDateValidator`
# ----------------------------------

View File

@ -47,7 +47,7 @@ def custom_handler(exc, context):
return Response({'error': 'UnknownError'}, status=500)
class OverridenSettingsView(APIView):
class OverriddenSettingsView(APIView):
settings = APISettings({'EXCEPTION_HANDLER': custom_handler})
def get(self, request, *args, **kwargs):
@ -131,7 +131,7 @@ class TestCustomExceptionHandler(TestCase):
class TestCustomSettings(TestCase):
def setUp(self):
self.view = OverridenSettingsView.as_view()
self.view = OverriddenSettingsView.as_view()
def test_get_exception_handler(self):
request = factory.get('/', content_type='application/json')

14
tox.ini
View File

@ -1,10 +1,10 @@
[tox]
envlist =
{py38,py39}-{django42}
{py310}-{django42,django50,django51,djangomain}
{py311}-{django42,django50,django51,djangomain}
{py312}-{django42,django50,django51,djangomain}
{py313}-{django51,djangomain}
{py39}-{django42}
{py310}-{django42,django51,django52,djangomain}
{py311}-{django42,django51,django52,djangomain}
{py312}-{django42,django51,django52,djangomain}
{py313}-{django51,django52,djangomain}
base
dist
docs
@ -19,6 +19,7 @@ deps =
django42: Django>=4.2,<5.0
django50: Django>=5.0,<5.1
django51: Django>=5.1,<5.2
django52: Django>=5.2b1,<6.0
djangomain: https://github.com/django/django/archive/main.tar.gz
-rrequirements/requirements-testing.txt
-rrequirements/requirements-optionals.txt
@ -52,3 +53,6 @@ ignore_outcome = true
[testenv:py312-djangomain]
ignore_outcome = true
[testenv:py313-djangomain]
ignore_outcome = true