mirror of
https://github.com/encode/django-rest-framework.git
synced 2025-04-15 06:32:11 +03:00
Merge branch 'master' into add-korean-translations
This commit is contained in:
commit
42c09e49ec
17
.github/ISSUE_TEMPLATE/1-issue.md
vendored
17
.github/ISSUE_TEMPLATE/1-issue.md
vendored
|
@ -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.
|
6
.github/ISSUE_TEMPLATE/config.yml
vendored
6
.github/ISSUE_TEMPLATE/config.yml
vendored
|
@ -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. 💖
|
8
.github/workflows/main.yml
vendored
8
.github/workflows/main.yml
vendored
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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).
|
||||
|
||||
---
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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).
|
||||
|
||||
---
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
42
docs/community/3.16-announcement.md
Normal file
42
docs/community/3.16-announcement.md
Normal 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.
|
|
@ -4,8 +4,6 @@
|
|||
>
|
||||
> — [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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
BIN
docs/img/books/dfa-40-cover.jpg
Normal file
BIN
docs/img/books/dfa-40-cover.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 755 KiB |
|
@ -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.
|
||||
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
Binary file not shown.
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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):
|
||||
|
|
9
setup.py
9
setup.py
|
@ -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',
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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'},
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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`
|
||||
# ----------------------------------
|
||||
|
|
|
@ -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
14
tox.ini
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue
Block a user