mirror of
https://github.com/encode/django-rest-framework.git
synced 2025-07-16 11:12:21 +03:00
Merge branch 'encode:master' into master
This commit is contained in:
commit
516b5db8ef
2
.github/workflows/main.yml
vendored
2
.github/workflows/main.yml
vendored
|
@ -22,7 +22,7 @@ jobs:
|
|||
- '3.11'
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: actions/setup-python@v4
|
||||
with:
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
repos:
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||
rev: v3.4.0
|
||||
rev: v4.5.0
|
||||
hooks:
|
||||
- id: check-added-large-files
|
||||
- id: check-case-conflict
|
||||
|
@ -9,12 +9,19 @@ repos:
|
|||
- id: check-symlinks
|
||||
- id: check-toml
|
||||
- repo: https://github.com/pycqa/isort
|
||||
rev: 5.12.0
|
||||
rev: 5.13.2
|
||||
hooks:
|
||||
- id: isort
|
||||
- repo: https://github.com/PyCQA/flake8
|
||||
rev: 3.9.0
|
||||
rev: 7.0.0
|
||||
hooks:
|
||||
- id: flake8
|
||||
additional_dependencies:
|
||||
- flake8-tidy-imports
|
||||
- repo: https://github.com/adamchainz/blacken-docs
|
||||
rev: 1.16.0
|
||||
hooks:
|
||||
- id: blacken-docs
|
||||
exclude: ^(?!docs).*$
|
||||
additional_dependencies:
|
||||
- black==23.1.0
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# Contributing to REST framework
|
||||
|
||||
At this point in it's 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.
|
||||
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.
|
||||
|
||||
|
|
|
@ -27,8 +27,9 @@ The initial aim is to provide a single full-time position on REST framework.
|
|||
[![][posthog-img]][posthog-url]
|
||||
[![][cryptapi-img]][cryptapi-url]
|
||||
[![][fezto-img]][fezto-url]
|
||||
[![][svix-img]][svix-url]
|
||||
|
||||
Many thanks to all our [wonderful sponsors][sponsors], and in particular to our premium backers, [Sentry][sentry-url], [Stream][stream-url], [Spacinov][spacinov-url], [Retool][retool-url], [bit.io][bitio-url], [PostHog][posthog-url], [CryptAPI][cryptapi-url], and [FEZTO][fezto-url].
|
||||
Many thanks to all our [wonderful sponsors][sponsors], and in particular to our premium backers, [Sentry][sentry-url], [Stream][stream-url], [Spacinov][spacinov-url], [Retool][retool-url], [bit.io][bitio-url], [PostHog][posthog-url], [CryptAPI][cryptapi-url], [FEZTO][fezto-url], and [Svix][svix-url].
|
||||
|
||||
---
|
||||
|
||||
|
@ -38,7 +39,7 @@ Django REST framework is a powerful and flexible toolkit for building Web APIs.
|
|||
|
||||
Some reasons you might want to use REST framework:
|
||||
|
||||
* The [Web browsable API][sandbox] is a huge usability win for your developers.
|
||||
* The Web browsable API is a huge usability win for your developers.
|
||||
* [Authentication policies][authentication] including optional packages for [OAuth1a][oauth1-section] and [OAuth2][oauth2-section].
|
||||
* [Serialization][serializers] that supports both [ORM][modelserializer-section] and [non-ORM][serializer-section] data sources.
|
||||
* Customizable all the way down - just use [regular function-based views][functionview-section] if you don't need the [more][generic-views] [powerful][viewsets] [features][routers].
|
||||
|
@ -200,6 +201,7 @@ Please see the [security policy][security-policy].
|
|||
[posthog-img]: https://raw.githubusercontent.com/encode/django-rest-framework/master/docs/img/premium/posthog-readme.png
|
||||
[cryptapi-img]: https://raw.githubusercontent.com/encode/django-rest-framework/master/docs/img/premium/cryptapi-readme.png
|
||||
[fezto-img]: https://raw.githubusercontent.com/encode/django-rest-framework/master/docs/img/premium/fezto-readme.png
|
||||
[svix-img]: https://raw.githubusercontent.com/encode/django-rest-framework/master/docs/img/premium/svix-premium.png
|
||||
|
||||
[sentry-url]: https://getsentry.com/welcome/
|
||||
[stream-url]: https://getstream.io/?utm_source=DjangoRESTFramework&utm_medium=Webpage_Logo_Ad&utm_content=Developer&utm_campaign=DjangoRESTFramework_Jan2022_HomePage
|
||||
|
@ -209,6 +211,7 @@ Please see the [security policy][security-policy].
|
|||
[posthog-url]: https://posthog.com?utm_source=drf&utm_medium=sponsorship&utm_campaign=open-source-sponsorship
|
||||
[cryptapi-url]: https://cryptapi.io
|
||||
[fezto-url]: https://www.fezto.xyz/?utm_source=DjangoRESTFramework
|
||||
[svix-url]: https://www.svix.com/?utm_source=django-REST&utm_medium=sponsorship
|
||||
|
||||
[oauth1-section]: https://www.django-rest-framework.org/api-guide/authentication/#django-rest-framework-oauth
|
||||
[oauth2-section]: https://www.django-rest-framework.org/api-guide/authentication/#django-oauth-toolkit
|
||||
|
|
|
@ -454,7 +454,7 @@ More information can be found in the [Documentation](https://django-rest-durin.r
|
|||
[basicauth]: https://tools.ietf.org/html/rfc2617
|
||||
[permission]: permissions.md
|
||||
[throttling]: throttling.md
|
||||
[csrf-ajax]: https://docs.djangoproject.com/en/stable/ref/csrf/#ajax
|
||||
[csrf-ajax]: https://docs.djangoproject.com/en/stable/howto/csrf/#using-csrf-protection-with-ajax
|
||||
[mod_wsgi_official]: https://modwsgi.readthedocs.io/en/develop/configuration-directives/WSGIPassAuthorization.html
|
||||
[django-oauth-toolkit-getting-started]: https://django-oauth-toolkit.readthedocs.io/en/latest/rest-framework/getting_started.html
|
||||
[django-rest-framework-oauth]: https://jpadilla.github.io/django-rest-framework-oauth/
|
||||
|
|
|
@ -28,33 +28,33 @@ from rest_framework import viewsets
|
|||
|
||||
class UserViewSet(viewsets.ViewSet):
|
||||
# With cookie: cache requested url for each user for 2 hours
|
||||
@method_decorator(cache_page(60*60*2))
|
||||
@method_decorator(cache_page(60 * 60 * 2))
|
||||
@method_decorator(vary_on_cookie)
|
||||
def list(self, request, format=None):
|
||||
content = {
|
||||
'user_feed': request.user.get_user_feed()
|
||||
"user_feed": request.user.get_user_feed(),
|
||||
}
|
||||
return Response(content)
|
||||
|
||||
|
||||
class ProfileView(APIView):
|
||||
# With auth: cache requested url for each user for 2 hours
|
||||
@method_decorator(cache_page(60*60*2))
|
||||
@method_decorator(vary_on_headers("Authorization",))
|
||||
@method_decorator(cache_page(60 * 60 * 2))
|
||||
@method_decorator(vary_on_headers("Authorization"))
|
||||
def get(self, request, format=None):
|
||||
content = {
|
||||
'user_feed': request.user.get_user_feed()
|
||||
"user_feed": request.user.get_user_feed(),
|
||||
}
|
||||
return Response(content)
|
||||
|
||||
|
||||
class PostView(APIView):
|
||||
# Cache page for the requested url
|
||||
@method_decorator(cache_page(60*60*2))
|
||||
@method_decorator(cache_page(60 * 60 * 2))
|
||||
def get(self, request, format=None):
|
||||
content = {
|
||||
'title': 'Post title',
|
||||
'body': 'Post content'
|
||||
"title": "Post title",
|
||||
"body": "Post content",
|
||||
}
|
||||
return Response(content)
|
||||
```
|
||||
|
|
|
@ -303,7 +303,7 @@ Corresponds to `django.db.models.fields.DecimalField`.
|
|||
* `min_value` Validate that the number provided is no less than this value.
|
||||
* `localize` Set to `True` to enable localization of input and output based on the current locale. This will also force `coerce_to_string` to `True`. Defaults to `False`. Note that data formatting is enabled if you have set `USE_L10N=True` in your settings file.
|
||||
* `rounding` Sets the rounding mode used when quantizing to the configured precision. Valid values are [`decimal` module rounding modes][python-decimal-rounding-modes]. Defaults to `None`.
|
||||
* `normalize_output` Will normalize the decimal value when serialized. This will strip all trailing zeroes and change the value's precision to the minimum required precision to be able to represent the value without loosing data. Defaults to `False`.
|
||||
* `normalize_output` Will normalize the decimal value when serialized. This will strip all trailing zeroes and change the value's precision to the minimum required precision to be able to represent the value without losing data. Defaults to `False`.
|
||||
|
||||
#### Example usage
|
||||
|
||||
|
|
|
@ -94,11 +94,13 @@ urlpatterns = [
|
|||
# Use the `get_schema_view()` helper to add a `SchemaView` to project URLs.
|
||||
# * `title` and `description` parameters are passed to `SchemaGenerator`.
|
||||
# * Provide view name for use with `reverse()`.
|
||||
path('openapi', get_schema_view(
|
||||
title="Your Project",
|
||||
description="API for all things …",
|
||||
version="1.0.0"
|
||||
), name='openapi-schema'),
|
||||
path(
|
||||
"openapi",
|
||||
get_schema_view(
|
||||
title="Your Project", description="API for all things …", version="1.0.0"
|
||||
),
|
||||
name="openapi-schema",
|
||||
),
|
||||
# ...
|
||||
]
|
||||
```
|
||||
|
@ -259,11 +261,13 @@ class CustomSchema(AutoSchema):
|
|||
"""
|
||||
AutoSchema subclass using schema_extra_info on the view.
|
||||
"""
|
||||
|
||||
...
|
||||
|
||||
|
||||
class CustomView(APIView):
|
||||
schema = CustomSchema()
|
||||
schema_extra_info = ... some extra info ...
|
||||
schema_extra_info = ... # some extra info
|
||||
```
|
||||
|
||||
Here, the `AutoSchema` subclass goes looking for `schema_extra_info` on the
|
||||
|
@ -278,10 +282,13 @@ class BaseSchema(AutoSchema):
|
|||
"""
|
||||
AutoSchema subclass that knows how to use extra_info.
|
||||
"""
|
||||
|
||||
...
|
||||
|
||||
|
||||
class CustomSchema(BaseSchema):
|
||||
extra_info = ... some extra info ...
|
||||
extra_info = ... # some extra info
|
||||
|
||||
|
||||
class CustomView(APIView):
|
||||
schema = CustomSchema()
|
||||
|
@ -302,10 +309,9 @@ class CustomSchema(BaseSchema):
|
|||
self.extra_info = kwargs.pop("extra_info")
|
||||
super().__init__(**kwargs)
|
||||
|
||||
|
||||
class CustomView(APIView):
|
||||
schema = CustomSchema(
|
||||
extra_info=... some extra info ...
|
||||
)
|
||||
schema = CustomSchema(extra_info=...) # some extra info
|
||||
```
|
||||
|
||||
This saves you having to create a custom subclass per-view for a commonly used option.
|
||||
|
|
|
@ -163,6 +163,12 @@ The string that should used for any versioning parameters, such as in the media
|
|||
|
||||
Default: `'version'`
|
||||
|
||||
#### DEFAULT_VERSIONING_CLASS
|
||||
|
||||
The default versioning scheme to use.
|
||||
|
||||
Default: `None`
|
||||
|
||||
---
|
||||
|
||||
## Authentication settings
|
||||
|
|
|
@ -173,11 +173,9 @@ If you want the date field to be entirely hidden from the user, then use `Hidden
|
|||
# Advanced field defaults
|
||||
|
||||
Validators that are applied across multiple fields in the serializer can sometimes require a field input that should not be provided by the API client, but that *is* available as input to the validator.
|
||||
For this purposes use `HiddenField`. This field will be present in `validated_data` but *will not* be used in the serializer output representation.
|
||||
|
||||
Two patterns that you may want to use for this sort of validation include:
|
||||
|
||||
* Using `HiddenField`. This field will be present in `validated_data` but *will not* be used in the serializer output representation.
|
||||
* Using a standard field with `read_only=True`, but that also includes a `default=…` argument. This field *will* be used in the serializer output representation, but cannot be set directly by the user.
|
||||
**Note:** Using a `read_only=True` field is excluded from writable fields so it won't use a `default=…` argument. Look [3.8 announcement](https://www.django-rest-framework.org/community/3.8-announcement/#altered-the-behaviour-of-read_only-plus-default-on-field).
|
||||
|
||||
REST framework includes a couple of defaults that may be useful in this context.
|
||||
|
||||
|
@ -189,7 +187,7 @@ A default class that can be used to represent the current user. In order to use
|
|||
default=serializers.CurrentUserDefault()
|
||||
)
|
||||
|
||||
#### CreateOnlyDefault
|
||||
#### CreateOnlyDefault
|
||||
|
||||
A default class that can be used to *only set a default argument during create operations*. During updates the field is omitted.
|
||||
|
||||
|
|
|
@ -201,15 +201,16 @@ To view all extra actions, call the `.get_extra_actions()` method.
|
|||
Extra actions can map additional HTTP methods to separate `ViewSet` methods. For example, the above password set/unset methods could be consolidated into a single route. Note that additional mappings do not accept arguments.
|
||||
|
||||
```python
|
||||
@action(detail=True, methods=['put'], name='Change Password')
|
||||
def password(self, request, pk=None):
|
||||
"""Update the user's password."""
|
||||
...
|
||||
@action(detail=True, methods=["put"], name="Change Password")
|
||||
def password(self, request, pk=None):
|
||||
"""Update the user's password."""
|
||||
...
|
||||
|
||||
@password.mapping.delete
|
||||
def delete_password(self, request, pk=None):
|
||||
"""Delete the user's password."""
|
||||
...
|
||||
|
||||
@password.mapping.delete
|
||||
def delete_password(self, request, pk=None):
|
||||
"""Delete the user's password."""
|
||||
...
|
||||
```
|
||||
|
||||
## Reversing action URLs
|
||||
|
@ -220,14 +221,14 @@ Note that the `basename` is provided by the router during `ViewSet` registration
|
|||
|
||||
Using the example from the previous section:
|
||||
|
||||
```python
|
||||
>>> view.reverse_action('set-password', args=['1'])
|
||||
```pycon
|
||||
>>> view.reverse_action("set-password", args=["1"])
|
||||
'http://localhost:8000/api/users/1/set_password'
|
||||
```
|
||||
|
||||
Alternatively, you can use the `url_name` attribute set by the `@action` decorator.
|
||||
|
||||
```python
|
||||
```pycon
|
||||
>>> view.reverse_action(view.set_password.url_name, args=['1'])
|
||||
'http://localhost:8000/api/users/1/set_password'
|
||||
```
|
||||
|
@ -310,7 +311,7 @@ You may need to provide custom `ViewSet` classes that do not have the full set o
|
|||
|
||||
To create a base viewset class that provides `create`, `list` and `retrieve` operations, inherit from `GenericViewSet`, and mixin the required actions:
|
||||
|
||||
from rest_framework import mixins
|
||||
from rest_framework import mixins, viewsets
|
||||
|
||||
class CreateListRetrieveViewSet(mixins.CreateModelMixin,
|
||||
mixins.ListModelMixin,
|
||||
|
|
|
@ -41,8 +41,8 @@ update your REST framework settings to include `DEFAULT_SCHEMA_CLASS` explicitly
|
|||
|
||||
```python
|
||||
REST_FRAMEWORK = {
|
||||
...
|
||||
'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.coreapi.AutoSchema'
|
||||
...: ...,
|
||||
"DEFAULT_SCHEMA_CLASS": "rest_framework.schemas.coreapi.AutoSchema",
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -74,10 +74,11 @@ urlpatterns = [
|
|||
# Use the `get_schema_view()` helper to add a `SchemaView` to project URLs.
|
||||
# * `title` and `description` parameters are passed to `SchemaGenerator`.
|
||||
# * Provide view name for use with `reverse()`.
|
||||
path('openapi', get_schema_view(
|
||||
title="Your Project",
|
||||
description="API for all things …"
|
||||
), name='openapi-schema'),
|
||||
path(
|
||||
"openapi",
|
||||
get_schema_view(title="Your Project", description="API for all things …"),
|
||||
name="openapi-schema",
|
||||
),
|
||||
# ...
|
||||
]
|
||||
```
|
||||
|
|
|
@ -43,10 +43,11 @@ be extracted from the class docstring:
|
|||
|
||||
```python
|
||||
class DocStringExampleListView(APIView):
|
||||
"""
|
||||
get: A description of my GET operation.
|
||||
post: A description of my POST operation.
|
||||
"""
|
||||
"""
|
||||
get: A description of my GET operation.
|
||||
post: A description of my POST operation.
|
||||
"""
|
||||
|
||||
permission_classes = [permissions.IsAuthenticatedOrReadOnly]
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
|
@ -63,7 +64,7 @@ In some circumstances a Validator class or a Default class may need to access th
|
|||
* Uniqueness validators need to be able to determine the name of the field to which they are applied, in order to run an appropriate database query.
|
||||
* The `CurrentUserDefault` needs to be able to determine the context with which the serializer was instantiated, in order to return the current user instance.
|
||||
|
||||
Previous our approach to this was that implementations could include a `set_context` method, which would be called prior to validation. However this approach had issues with potential race conditions. We have now move this approach into a pending deprecation state. It will continue to function, but will be escalated to a deprecated state in 3.12, and removed entirely in 3.13.
|
||||
Our previous approach to this was that implementations could include a `set_context` method, which would be called prior to validation. However this approach had issues with potential race conditions. We have now move this approach into a pending deprecation state. It will continue to function, but will be escalated to a deprecated state in 3.12, and removed entirely in 3.13.
|
||||
|
||||
Instead, validators or defaults which require the serializer context, should include a `requires_context = True` attribute on the class.
|
||||
|
||||
|
|
|
@ -41,7 +41,7 @@ The tags used for a particular view may also be overridden...
|
|||
|
||||
```python
|
||||
class MyOrders(APIView):
|
||||
schema = AutoSchema(tags=['users', 'orders'])
|
||||
schema = AutoSchema(tags=["users", "orders"])
|
||||
...
|
||||
```
|
||||
|
||||
|
@ -68,7 +68,7 @@ may be overridden if needed](https://www.django-rest-framework.org/api-guide/sch
|
|||
|
||||
```python
|
||||
class MyOrders(APIView):
|
||||
schema = AutoSchema(component_name="OrderDetails")
|
||||
schema = AutoSchema(component_name="OrderDetails")
|
||||
```
|
||||
|
||||
## More Public API
|
||||
|
@ -118,10 +118,11 @@ class SitesSearchView(generics.ListAPIView):
|
|||
by a search against the site name or location. (Location searches are
|
||||
matched against the region and country names.)
|
||||
"""
|
||||
|
||||
queryset = Sites.objects.all()
|
||||
serializer_class = SitesSerializer
|
||||
filter_backends = [filters.SearchFilter]
|
||||
search_fields = ['site_name', 'location__region', 'location__country']
|
||||
search_fields = ["site_name", "location__region", "location__country"]
|
||||
```
|
||||
|
||||
### Searches against annotate fields
|
||||
|
@ -135,10 +136,11 @@ class PublisherSearchView(generics.ListAPIView):
|
|||
Search for publishers, optionally filtering the search against the average
|
||||
rating of all their books.
|
||||
"""
|
||||
queryset = Publisher.objects.annotate(avg_rating=Avg('book__rating'))
|
||||
|
||||
queryset = Publisher.objects.annotate(avg_rating=Avg("book__rating"))
|
||||
serializer_class = PublisherSerializer
|
||||
filter_backends = [filters.SearchFilter]
|
||||
search_fields = ['avg_rating']
|
||||
search_fields = ["avg_rating"]
|
||||
```
|
||||
|
||||
---
|
||||
|
|
|
@ -64,14 +64,10 @@ from rest_framework.schemas import get_schema_view
|
|||
from rest_framework_swagger.renderers import OpenAPIRenderer, SwaggerUIRenderer
|
||||
|
||||
schema_view = get_schema_view(
|
||||
title='Example API',
|
||||
renderer_classes=[OpenAPIRenderer, SwaggerUIRenderer]
|
||||
title="Example API", renderer_classes=[OpenAPIRenderer, SwaggerUIRenderer]
|
||||
)
|
||||
|
||||
urlpatterns = [
|
||||
path('swagger/', schema_view),
|
||||
...
|
||||
]
|
||||
urlpatterns = [path("swagger/", schema_view), ...]
|
||||
```
|
||||
|
||||
There have been a large number of fixes to the schema generation. These should
|
||||
|
|
|
@ -65,15 +65,12 @@ from rest_framework.renderers import JSONOpenAPIRenderer
|
|||
from django.urls import path
|
||||
|
||||
schema_view = get_schema_view(
|
||||
title='Server Monitoring API',
|
||||
url='https://www.example.org/api/',
|
||||
renderer_classes=[JSONOpenAPIRenderer]
|
||||
title="Server Monitoring API",
|
||||
url="https://www.example.org/api/",
|
||||
renderer_classes=[JSONOpenAPIRenderer],
|
||||
)
|
||||
|
||||
urlpatterns = [
|
||||
path('schema.json', schema_view),
|
||||
...
|
||||
]
|
||||
urlpatterns = [path("schema.json", schema_view), ...]
|
||||
```
|
||||
|
||||
And here's how you can use the `generateschema` management command:
|
||||
|
|
|
@ -47,7 +47,7 @@ Date: 22nd September 2022
|
|||
* Stop calling `set_context` on Validators. [[#8589](https://github.com/encode/django-rest-framework/pull/8589)]
|
||||
* Return `NotImplemented` from `ErrorDetails.__ne__`. [[#8538](https://github.com/encode/django-rest-framework/pull/8538)]
|
||||
* Don't evaluate `DateTimeField.default_timezone` when a custom timezone is set. [[#8531](https://github.com/encode/django-rest-framework/pull/8531)]
|
||||
* Make relative URLs clickable in Browseable API. [[#8464](https://github.com/encode/django-rest-framework/pull/8464)]
|
||||
* Make relative URLs clickable in Browsable API. [[#8464](https://github.com/encode/django-rest-framework/pull/8464)]
|
||||
* Support `ManyRelatedField` falling back to the default value when the attribute specified by dot notation doesn't exist. Matches `ManyRelatedField.get_attribute` to `Field.get_attribute`. [[#7574](https://github.com/encode/django-rest-framework/pull/7574)]
|
||||
* Make `schemas.openapi.get_reference` public. [[#7515](https://github.com/encode/django-rest-framework/pull/7515)]
|
||||
* Make `ReturnDict` support `dict` union operators on Python 3.9 and later. [[#8302](https://github.com/encode/django-rest-framework/pull/8302)]
|
||||
|
@ -65,7 +65,7 @@ Date: 15th December 2021
|
|||
|
||||
Date: 13th December 2021
|
||||
|
||||
* Django 4.0 compatability. [#8178]
|
||||
* Django 4.0 compatibility. [#8178]
|
||||
* Add `max_length` and `min_length` options to `ListSerializer`. [#8165]
|
||||
* Add `get_request_serializer` and `get_response_serializer` hooks to `AutoSchema`. [#7424]
|
||||
* Fix OpenAPI representation of null-able read only fields. [#8116]
|
||||
|
@ -306,7 +306,11 @@ Be sure to upgrade to Python 3 before upgrading to Django REST Framework 3.10.
|
|||
class NullableCharField(serializers.CharField):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.validators = [v for v in self.validators if not isinstance(v, ProhibitNullCharactersValidator)]
|
||||
self.validators = [
|
||||
v
|
||||
for v in self.validators
|
||||
if not isinstance(v, ProhibitNullCharactersValidator)
|
||||
]
|
||||
```
|
||||
* Add `OpenAPIRenderer` and `generate_schema` management command. [#6229][gh6229]
|
||||
* Add OpenAPIRenderer by default, and add schema docs. [#6233][gh6233]
|
||||
|
@ -950,7 +954,7 @@ See the [release announcement][3.6-release].
|
|||
* Prevent raising exception when limit is 0. ([#4098][gh4098])
|
||||
* TokenAuthentication: Allow custom keyword in the header. ([#4097][gh4097])
|
||||
* Handle incorrectly padded HTTP basic auth header. ([#4090][gh4090])
|
||||
* LimitOffset pagination crashes Browseable API when limit=0. ([#4079][gh4079])
|
||||
* LimitOffset pagination crashes Browsable API when limit=0. ([#4079][gh4079])
|
||||
* Fixed DecimalField arbitrary precision support. ([#4075][gh4075])
|
||||
* Added support for custom CSRF cookie names. ([#4049][gh4049])
|
||||
* Fix regression introduced by #4035. ([#4041][gh4041])
|
||||
|
|
|
@ -19,6 +19,10 @@ There are a wide range of resources available for learning and using Django REST
|
|||
</a>
|
||||
</div>
|
||||
|
||||
## Courses
|
||||
|
||||
* [Developing RESTful APIs with Django REST Framework][developing-restful-apis-with-django-rest-framework]
|
||||
|
||||
## Tutorials
|
||||
|
||||
* [Beginner's Guide to the Django REST Framework][beginners-guide-to-the-django-rest-framework]
|
||||
|
@ -130,3 +134,4 @@ Want your Django REST Framework talk/tutorial/article to be added to our website
|
|||
[pycon-us-2017]: https://www.youtube.com/watch?v=Rk6MHZdust4
|
||||
[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/
|
||||
|
|
BIN
docs/img/premium/svix-premium.png
Normal file
BIN
docs/img/premium/svix-premium.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 31 KiB |
|
@ -48,7 +48,7 @@ Django REST framework is a powerful and flexible toolkit for building Web APIs.
|
|||
|
||||
Some reasons you might want to use REST framework:
|
||||
|
||||
* The [Web browsable API][sandbox] is a huge usability win for your developers.
|
||||
* The Web browsable API is a huge usability win for your developers.
|
||||
* [Authentication policies][authentication] including packages for [OAuth1a][oauth1-section] and [OAuth2][oauth2-section].
|
||||
* [Serialization][serializers] that supports both [ORM][modelserializer-section] and [non-ORM][serializer-section] data sources.
|
||||
* Customizable all the way down - just use [regular function-based views][functionview-section] if you don't need the [more][generic-views] [powerful][viewsets] [features][routers].
|
||||
|
@ -74,10 +74,11 @@ continued development by **[signing up for a paid plan][funding]**.
|
|||
<li><a href="https://posthog.com?utm_source=DRF&utm_medium=sponsor&utm_campaign=DRF_sponsorship" style="background-image: url(https://fund-rest-framework.s3.amazonaws.com/135996800-d49fe024-32d9-441a-98d9-4c7596287a67.png)">PostHog</a></li>
|
||||
<li><a href="https://cryptapi.io" style="background-image: url(https://fund-rest-framework.s3.amazonaws.com/cryptapi.png)">CryptAPI</a></li>
|
||||
<li><a href="https://www.fezto.xyz/?utm_source=DjangoRESTFramework" style="background-image: url(https://fund-rest-framework.s3.amazonaws.com/fezto.png)">FEZTO</a></li>
|
||||
<li><a href="https://www.svix.com/?utm_source=django-REST&utm_medium=sponsorship" style="background-image: url(https://fund-rest-framework.s3.amazonaws.com/svix.png)">Svix</a></li>
|
||||
</ul>
|
||||
<div style="clear: both; padding-bottom: 20px;"></div>
|
||||
|
||||
*Many thanks to all our [wonderful sponsors][sponsors], and in particular to our premium backers, [Sentry](https://getsentry.com/welcome/), [Stream](https://getstream.io/?utm_source=DjangoRESTFramework&utm_medium=Webpage_Logo_Ad&utm_content=Developer&utm_campaign=DjangoRESTFramework_Jan2022_HomePage), [Spacinov](https://www.spacinov.com/), [Retool](https://retool.com/?utm_source=djangorest&utm_medium=sponsorship), [bit.io](https://bit.io/jobs?utm_source=DRF&utm_medium=sponsor&utm_campaign=DRF_sponsorship), [PostHog](https://posthog.com?utm_source=DRF&utm_medium=sponsor&utm_campaign=DRF_sponsorship), [CryptAPI](https://cryptapi.io), and [FEZTO](https://www.fezto.xyz/?utm_source=DjangoRESTFramework).*
|
||||
*Many thanks to all our [wonderful sponsors][sponsors], and in particular to our premium backers, [Sentry](https://getsentry.com/welcome/), [Stream](https://getstream.io/?utm_source=DjangoRESTFramework&utm_medium=Webpage_Logo_Ad&utm_content=Developer&utm_campaign=DjangoRESTFramework_Jan2022_HomePage), [Spacinov](https://www.spacinov.com/), [Retool](https://retool.com/?utm_source=djangorest&utm_medium=sponsorship), [bit.io](https://bit.io/jobs?utm_source=DRF&utm_medium=sponsor&utm_campaign=DRF_sponsorship), [PostHog](https://posthog.com?utm_source=DRF&utm_medium=sponsor&utm_campaign=DRF_sponsorship), [CryptAPI](https://cryptapi.io), [FEZTO](https://www.fezto.xyz/?utm_source=DjangoRESTFramework), and [Svix](https://www.svix.com/?utm_source=django-REST&utm_medium=sponsorship).*
|
||||
|
||||
---
|
||||
|
||||
|
@ -86,7 +87,7 @@ continued development by **[signing up for a paid plan][funding]**.
|
|||
REST framework requires the following:
|
||||
|
||||
* Python (3.6, 3.7, 3.8, 3.9, 3.10, 3.11)
|
||||
* Django (3.0, 3.1, 3.2, 4.0, 4.1)
|
||||
* Django (3.0, 3.1, 3.2, 4.0, 4.1, 4.2)
|
||||
|
||||
We **highly recommend** and only officially support the latest patch release of
|
||||
each Python and Django series.
|
||||
|
|
|
@ -35,7 +35,7 @@ The best way to deal with CORS in REST framework is to add the required response
|
|||
|
||||
[cite]: https://blog.codinghorror.com/preventing-csrf-and-xsrf-attacks/
|
||||
[csrf]: https://www.owasp.org/index.php/Cross-Site_Request_Forgery_(CSRF)
|
||||
[csrf-ajax]: https://docs.djangoproject.com/en/stable/ref/csrf/#ajax
|
||||
[csrf-ajax]: https://docs.djangoproject.com/en/stable/howto/csrf/#using-csrf-protection-with-ajax
|
||||
[cors]: https://www.w3.org/TR/cors/
|
||||
[adamchainz]: https://github.com/adamchainz
|
||||
[django-cors-headers]: https://github.com/adamchainz/django-cors-headers
|
||||
|
|
|
@ -15,6 +15,18 @@ If you include fully-qualified URLs in your resource output, they will be 'urliz
|
|||
|
||||
By default, the API will return the format specified by the headers, which in the case of the browser is HTML. The format can be specified using `?format=` in the request, so you can look at the raw JSON response in a browser by adding `?format=json` to the URL. There are helpful extensions for viewing JSON in [Firefox][ffjsonview] and [Chrome][chromejsonview].
|
||||
|
||||
## Authentication
|
||||
|
||||
To quickly add authentication to the browesable api, add a routes named `"login"` and `"logout"` under the namespace `"rest_framework"`. DRF provides default routes for this which you can add to your urlconf:
|
||||
|
||||
```python
|
||||
urlpatterns = [
|
||||
# ...
|
||||
url(r"^api-auth/", include("rest_framework.urls", namespace="rest_framework"))
|
||||
]
|
||||
```
|
||||
|
||||
|
||||
## Customizing
|
||||
|
||||
The browsable API is built with [Twitter's Bootstrap][bootstrap] (v 3.4.1), making it easy to customize the look-and-feel.
|
||||
|
|
|
@ -96,10 +96,14 @@ urlpatterns = [
|
|||
# ...
|
||||
# Route TemplateView to serve Swagger UI template.
|
||||
# * Provide `extra_context` with view name of `SchemaView`.
|
||||
path('swagger-ui/', TemplateView.as_view(
|
||||
template_name='swagger-ui.html',
|
||||
extra_context={'schema_url':'openapi-schema'}
|
||||
), name='swagger-ui'),
|
||||
path(
|
||||
"swagger-ui/",
|
||||
TemplateView.as_view(
|
||||
template_name="swagger-ui.html",
|
||||
extra_context={"schema_url": "openapi-schema"},
|
||||
),
|
||||
name="swagger-ui",
|
||||
),
|
||||
]
|
||||
```
|
||||
|
||||
|
@ -145,10 +149,13 @@ urlpatterns = [
|
|||
# ...
|
||||
# Route TemplateView to serve the ReDoc template.
|
||||
# * Provide `extra_context` with view name of `SchemaView`.
|
||||
path('redoc/', TemplateView.as_view(
|
||||
template_name='redoc.html',
|
||||
extra_context={'schema_url':'openapi-schema'}
|
||||
), name='redoc'),
|
||||
path(
|
||||
"redoc/",
|
||||
TemplateView.as_view(
|
||||
template_name="redoc.html", extra_context={"schema_url": "openapi-schema"}
|
||||
),
|
||||
name="redoc",
|
||||
),
|
||||
]
|
||||
```
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@ A `ViewSet` class is only bound to a set of method handlers at the last moment,
|
|||
|
||||
Let's take our current set of views, and refactor them into view sets.
|
||||
|
||||
First of all let's refactor our `UserList` and `UserDetail` classes into a single `UserViewSet` class. We can remove the two view classes, and replace them with a single ViewSet class:
|
||||
First of all let's refactor our `UserList` and `UserDetail` classes into a single `UserViewSet` class. In the `snippets/views.py` file, we can remove the two view classes and replace them with a single ViewSet class:
|
||||
|
||||
from rest_framework import viewsets
|
||||
|
||||
|
|
|
@ -105,7 +105,7 @@ Right, we'd better write some views then. Open `tutorial/quickstart/views.py` a
|
|||
"""
|
||||
API endpoint that allows groups to be viewed or edited.
|
||||
"""
|
||||
queryset = Group.objects.all()
|
||||
queryset = Group.objects.all().order_by('name')
|
||||
serializer_class = GroupSerializer
|
||||
permission_classes = [permissions.IsAuthenticated]
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
# MkDocs to build our documentation.
|
||||
mkdocs>=1.1.2,<1.2
|
||||
mkdocs==1.2.4
|
||||
jinja2>=2.10,<3.1.0 # contextfilter has been renamed
|
||||
|
||||
# pylinkvalidator to check for broken links in documentation.
|
||||
|
|
|
@ -4,7 +4,7 @@ coreschema==0.0.4
|
|||
django-filter
|
||||
django-guardian>=2.4.0,<2.5
|
||||
inflection==0.5.1
|
||||
markdown==3.3
|
||||
markdown>=3.3.7
|
||||
psycopg2-binary>=2.9.5,<2.10
|
||||
pygments==2.12
|
||||
pygments>=2.12.0,<2.14.0
|
||||
pyyaml>=5.3.1,<5.4
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
# Pytest for running the tests.
|
||||
pytest>=6.2.0,<8.0
|
||||
pytest>=7.0.1,<8.0
|
||||
pytest-cov>=4.0.0,<5.0
|
||||
pytest-django>=4.5.2,<5.0
|
||||
importlib-metadata<5.0
|
||||
|
|
|
@ -169,6 +169,21 @@ else:
|
|||
}
|
||||
|
||||
|
||||
if django.VERSION >= (5, 1):
|
||||
# Django 5.1+: use the stock ip_address_validators function
|
||||
# Note: Before Django 5.1, ip_address_validators returns a tuple containing
|
||||
# 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
|
||||
else:
|
||||
# Django <= 5.1: create a compatibility shim for ip_address_validators
|
||||
from django.core.validators import \
|
||||
ip_address_validators as _ip_address_validators
|
||||
|
||||
def ip_address_validators(protocol, unpack_ipv4):
|
||||
return _ip_address_validators(protocol, unpack_ipv4)[0]
|
||||
|
||||
|
||||
# `separators` argument to `json.dumps()` differs between 2.x and 3.x
|
||||
# See: https://bugs.python.org/issue22767
|
||||
SHORT_SEPARATORS = (',', ':')
|
||||
|
|
|
@ -36,7 +36,7 @@ def api_view(http_method_names=None):
|
|||
# WrappedAPIView.__doc__ = func.doc <--- Not possible to do this
|
||||
|
||||
# api_view applied without (method_names)
|
||||
assert not(isinstance(http_method_names, types.FunctionType)), \
|
||||
assert not isinstance(http_method_names, types.FunctionType), \
|
||||
'@api_view missing list of allowed HTTP methods'
|
||||
|
||||
# api_view applied with eg. string instead of list of strings
|
||||
|
|
|
@ -16,7 +16,7 @@ from django.core.exceptions import ValidationError as DjangoValidationError
|
|||
from django.core.validators import (
|
||||
EmailValidator, MaxLengthValidator, MaxValueValidator, MinLengthValidator,
|
||||
MinValueValidator, ProhibitNullCharactersValidator, RegexValidator,
|
||||
URLValidator, ip_address_validators
|
||||
URLValidator
|
||||
)
|
||||
from django.forms import FilePathField as DjangoFilePathField
|
||||
from django.forms import ImageField as DjangoImageField
|
||||
|
@ -36,6 +36,7 @@ except ImportError:
|
|||
pytz = None
|
||||
|
||||
from rest_framework import ISO_8601
|
||||
from rest_framework.compat import ip_address_validators
|
||||
from rest_framework.exceptions import ErrorDetail, ValidationError
|
||||
from rest_framework.settings import api_settings
|
||||
from rest_framework.utils import html, humanize_datetime, json, representation
|
||||
|
@ -866,7 +867,7 @@ class IPAddressField(CharField):
|
|||
self.protocol = protocol.lower()
|
||||
self.unpack_ipv4 = (self.protocol == 'both')
|
||||
super().__init__(**kwargs)
|
||||
validators, error_message = ip_address_validators(protocol, self.unpack_ipv4)
|
||||
validators = ip_address_validators(protocol, self.unpack_ipv4)
|
||||
self.validators.extend(validators)
|
||||
|
||||
def to_internal_value(self, data):
|
||||
|
|
|
@ -21,14 +21,14 @@ from rest_framework.settings import api_settings
|
|||
|
||||
|
||||
def search_smart_split(search_terms):
|
||||
"""generator that first splits string by spaces, leaving quoted phrases togheter,
|
||||
"""generator that first splits string by spaces, leaving quoted phrases together,
|
||||
then it splits non-quoted phrases by commas.
|
||||
"""
|
||||
for term in smart_split(search_terms):
|
||||
# trim commas to avoid bad matching for quoted phrases
|
||||
term = term.strip(',')
|
||||
if term.startswith(('"', "'")) and term[0] == term[-1]:
|
||||
# quoted phrases are kept togheter without any other split
|
||||
# quoted phrases are kept together without any other split
|
||||
yield unescape_string_literal(term)
|
||||
else:
|
||||
# non-quoted tokens are split by comma, keeping only non-empty ones
|
||||
|
|
|
@ -102,12 +102,12 @@ class EndpointEnumerator:
|
|||
Given a URL conf regex, return a URI template string.
|
||||
"""
|
||||
# ???: Would it be feasible to adjust this such that we generate the
|
||||
# path, plus the kwargs, plus the type from the convertor, such that we
|
||||
# path, plus the kwargs, plus the type from the converter, such that we
|
||||
# could feed that straight into the parameter schema object?
|
||||
|
||||
path = simplify_regex(path_regex)
|
||||
|
||||
# Strip Django 2.0 convertors as they are incompatible with uritemplate format
|
||||
# Strip Django 2.0 converters as they are incompatible with uritemplate format
|
||||
return re.sub(_PATH_PARAMETER_COMPONENT_RE, r'{\g<parameter>}', path)
|
||||
|
||||
def should_include_endpoint(self, path, callback):
|
||||
|
|
|
@ -84,7 +84,7 @@ class SchemaGenerator(BaseSchemaGenerator):
|
|||
continue
|
||||
if components_schemas[k] == components[k]:
|
||||
continue
|
||||
warnings.warn('Schema component "{}" has been overriden with a different value.'.format(k))
|
||||
warnings.warn('Schema component "{}" has been overridden with a different value.'.format(k))
|
||||
|
||||
components_schemas.update(components)
|
||||
|
||||
|
|
|
@ -116,7 +116,7 @@ DEFAULTS = {
|
|||
'COERCE_DECIMAL_TO_STRING': True,
|
||||
'UPLOADED_FILES_USE_URL': True,
|
||||
|
||||
# Browseable API
|
||||
# Browsable API
|
||||
'HTML_SELECT_CUTOFF': 1000,
|
||||
'HTML_SELECT_CUTOFF_TEXT': "More than {count} items...",
|
||||
|
||||
|
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
2
rest_framework/static/rest_framework/js/jquery-3.7.1.min.js
vendored
Normal file
2
rest_framework/static/rest_framework/js/jquery-3.7.1.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
|
@ -250,7 +250,7 @@
|
|||
"csrfToken": "{{ csrf_token }}"
|
||||
}
|
||||
</script>
|
||||
<script src="{% static "rest_framework/js/jquery-3.6.4.min.js" %}"></script>
|
||||
<script src="{% static "rest_framework/js/jquery-3.7.1.min.js" %}"></script>
|
||||
<script src="{% static "rest_framework/js/ajax-form.js" %}"></script>
|
||||
<script src="{% static "rest_framework/js/csrf.js" %}"></script>
|
||||
<script src="{% static "rest_framework/js/bootstrap.min.js" %}"></script>
|
||||
|
|
|
@ -293,7 +293,7 @@
|
|||
"csrfToken": "{% if request %}{{ csrf_token }}{% endif %}"
|
||||
}
|
||||
</script>
|
||||
<script src="{% static "rest_framework/js/jquery-3.6.4.min.js" %}"></script>
|
||||
<script src="{% static "rest_framework/js/jquery-3.7.1.min.js" %}"></script>
|
||||
<script src="{% static "rest_framework/js/ajax-form.js" %}"></script>
|
||||
<script src="{% static "rest_framework/js/csrf.js" %}"></script>
|
||||
<script src="{% static "rest_framework/js/bootstrap.min.js" %}"></script>
|
||||
|
|
|
@ -66,6 +66,6 @@ at <code>rest_framework/docs/error.html</code>.</p>
|
|||
|
||||
|
||||
|
||||
<script src="{% static 'rest_framework/js/jquery-3.6.4.min.js' %}"></script>
|
||||
<script src="{% static 'rest_framework/js/jquery-3.7.1.min.js' %}"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -38,7 +38,7 @@
|
|||
{% include "rest_framework/docs/auth/basic.html" %}
|
||||
{% include "rest_framework/docs/auth/session.html" %}
|
||||
|
||||
<script src="{% static 'rest_framework/js/jquery-3.6.4.min.js' %}"></script>
|
||||
<script src="{% static 'rest_framework/js/jquery-3.7.1.min.js' %}"></script>
|
||||
<script src="{% static 'rest_framework/js/bootstrap.min.js' %}"></script>
|
||||
<script src="{% static 'rest_framework/docs/js/jquery.json-view.min.js' %}"></script>
|
||||
<script src="{% static 'rest_framework/docs/js/api.js' %}"></script>
|
||||
|
|
|
@ -7,7 +7,7 @@ Usage: `get_field_info(model)` returns a `FieldInfo` instance.
|
|||
"""
|
||||
from collections import namedtuple
|
||||
|
||||
FieldInfo = namedtuple('FieldResult', [
|
||||
FieldInfo = namedtuple('FieldInfo', [
|
||||
'pk', # Model field instance
|
||||
'fields', # Dict of field name -> model field instance
|
||||
'forward_relations', # Dict of field name -> RelationInfo
|
||||
|
|
|
@ -160,10 +160,19 @@ class UniqueTogetherValidator:
|
|||
queryset = self.exclude_current_instance(attrs, queryset, serializer.instance)
|
||||
|
||||
# Ignore validation if any field is None
|
||||
checked_values = [
|
||||
value for field, value in attrs.items() if field in self.fields
|
||||
]
|
||||
if None not in checked_values and qs_exists(queryset):
|
||||
if serializer.instance is None:
|
||||
checked_values = [
|
||||
value for field, value in attrs.items() if field in self.fields
|
||||
]
|
||||
else:
|
||||
# Ignore validation if all field values are unchanged
|
||||
checked_values = [
|
||||
value
|
||||
for field, value in attrs.items()
|
||||
if field in self.fields and value != getattr(serializer.instance, field)
|
||||
]
|
||||
|
||||
if checked_values and None not in checked_values and qs_exists(queryset):
|
||||
field_names = ', '.join(self.fields)
|
||||
message = self.message.format(field_names=field_names)
|
||||
raise ValidationError(message, code='unique')
|
||||
|
|
13
runtests.py
13
runtests.py
|
@ -1,4 +1,5 @@
|
|||
#! /usr/bin/env python3
|
||||
import os
|
||||
import sys
|
||||
|
||||
import pytest
|
||||
|
@ -34,6 +35,18 @@ if __name__ == "__main__":
|
|||
'--cov-report', 'xml',
|
||||
] + pytest_args
|
||||
|
||||
try:
|
||||
pytest_args.remove('--no-pkgroot')
|
||||
except ValueError:
|
||||
pass
|
||||
else:
|
||||
sys.path.pop(0)
|
||||
|
||||
# import rest_framework before pytest re-adds the package root directory.
|
||||
import rest_framework
|
||||
package_dir = os.path.join(os.getcwd(), 'rest_framework')
|
||||
assert not rest_framework.__file__.startswith(package_dir)
|
||||
|
||||
if first_arg.startswith('-'):
|
||||
# `runtests.py [flags]`
|
||||
pytest_args = ['tests'] + pytest_args
|
||||
|
|
|
@ -1,15 +1,10 @@
|
|||
import os
|
||||
import sys
|
||||
|
||||
import django
|
||||
from django.core import management
|
||||
|
||||
|
||||
def pytest_addoption(parser):
|
||||
parser.addoption('--no-pkgroot', action='store_true', default=False,
|
||||
help='Remove package root directory from sys.path, ensuring that '
|
||||
'rest_framework is imported from the installed site-packages. '
|
||||
'Used for testing the distribution.')
|
||||
parser.addoption('--staticfiles', action='store_true', default=False,
|
||||
help='Run tests with static files collection, using manifest '
|
||||
'staticfiles storage. Used for testing the distribution.')
|
||||
|
@ -87,19 +82,15 @@ def pytest_configure(config):
|
|||
'guardian',
|
||||
)
|
||||
|
||||
if config.getoption('--no-pkgroot'):
|
||||
sys.path.pop(0)
|
||||
|
||||
# import rest_framework before pytest re-adds the package root directory.
|
||||
import rest_framework
|
||||
package_dir = os.path.join(os.getcwd(), 'rest_framework')
|
||||
assert not rest_framework.__file__.startswith(package_dir)
|
||||
|
||||
# Manifest storage will raise an exception if static files are not present (ie, a packaging failure).
|
||||
if config.getoption('--staticfiles'):
|
||||
import rest_framework
|
||||
settings.STATIC_ROOT = os.path.join(os.path.dirname(rest_framework.__file__), 'static-root')
|
||||
settings.STATICFILES_STORAGE = 'django.contrib.staticfiles.storage.ManifestStaticFilesStorage'
|
||||
backend = 'django.contrib.staticfiles.storage.ManifestStaticFilesStorage'
|
||||
if django.VERSION < (4, 2):
|
||||
settings.STATICFILES_STORAGE = backend
|
||||
else:
|
||||
settings.STORAGES['staticfiles']['BACKEND'] = backend
|
||||
|
||||
django.setup()
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@ from django.test import TestCase
|
|||
from django.test.utils import override_settings
|
||||
from django.urls import path
|
||||
|
||||
from rest_framework.compat import uritemplate, yaml
|
||||
from rest_framework.compat import coreapi, uritemplate, yaml
|
||||
from rest_framework.management.commands import generateschema
|
||||
from rest_framework.utils import formatting, json
|
||||
from rest_framework.views import APIView
|
||||
|
@ -91,6 +91,7 @@ class GenerateSchemaTests(TestCase):
|
|||
os.remove(path)
|
||||
|
||||
@pytest.mark.skipif(yaml is None, reason='PyYAML is required.')
|
||||
@pytest.mark.skipif(coreapi is None, reason='coreapi is required.')
|
||||
@override_settings(REST_FRAMEWORK={'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.AutoSchema'})
|
||||
def test_coreapi_renders_default_schema_with_custom_title_url_and_description(self):
|
||||
expected_out = """info:
|
||||
|
@ -113,6 +114,7 @@ class GenerateSchemaTests(TestCase):
|
|||
|
||||
self.assertIn(formatting.dedent(expected_out), self.out.getvalue())
|
||||
|
||||
@pytest.mark.skipif(coreapi is None, reason='coreapi is required.')
|
||||
@override_settings(REST_FRAMEWORK={'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.AutoSchema'})
|
||||
def test_coreapi_renders_openapi_json_schema(self):
|
||||
expected_out = {
|
||||
|
@ -142,6 +144,7 @@ class GenerateSchemaTests(TestCase):
|
|||
|
||||
self.assertDictEqual(out_json, expected_out)
|
||||
|
||||
@pytest.mark.skipif(coreapi is None, reason='coreapi is required.')
|
||||
@override_settings(REST_FRAMEWORK={'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.AutoSchema'})
|
||||
def test_renders_corejson_schema(self):
|
||||
expected_out = """{"_type":"document","":{"list":{"_type":"link","url":"/","action":"get"}}}"""
|
||||
|
|
|
@ -1347,7 +1347,7 @@ class TestGenerator(TestCase):
|
|||
|
||||
assert len(w) == 1
|
||||
assert issubclass(w[-1].category, UserWarning)
|
||||
assert 'has been overriden with a different value.' in str(w[-1].message)
|
||||
assert 'has been overridden with a different value.' in str(w[-1].message)
|
||||
|
||||
assert 'components' in schema
|
||||
assert 'schemas' in schema['components']
|
||||
|
|
|
@ -132,7 +132,7 @@ urlpatterns = [
|
|||
]
|
||||
|
||||
|
||||
# TODO: Clean tests bellow - remove duplicates with above, better unit testing, ...
|
||||
# TODO: Clean tests below - remove duplicates with above, better unit testing, ...
|
||||
@override_settings(ROOT_URLCONF='tests.test_response')
|
||||
class RendererIntegrationTests(TestCase):
|
||||
"""
|
||||
|
|
|
@ -223,7 +223,7 @@ class TestNotRequiredNestedSerializerWithMany:
|
|||
input_data = {}
|
||||
serializer = self.Serializer(data=input_data)
|
||||
|
||||
# request is empty, therefor 'nested' should not be in serializer.data
|
||||
# request is empty, therefore 'nested' should not be in serializer.data
|
||||
assert serializer.is_valid()
|
||||
assert 'nested' not in serializer.validated_data
|
||||
|
||||
|
@ -237,7 +237,7 @@ class TestNotRequiredNestedSerializerWithMany:
|
|||
input_data = QueryDict('')
|
||||
serializer = self.Serializer(data=input_data)
|
||||
|
||||
# the querydict is empty, therefor 'nested' should not be in serializer.data
|
||||
# the querydict is empty, therefore 'nested' should not be in serializer.data
|
||||
assert serializer.is_valid()
|
||||
assert 'nested' not in serializer.validated_data
|
||||
|
||||
|
|
|
@ -192,7 +192,7 @@ class ThrottlingTests(TestCase):
|
|||
if expect is not None:
|
||||
assert response['Retry-After'] == expect
|
||||
else:
|
||||
assert not'Retry-After' in response
|
||||
assert 'Retry-After' not in response
|
||||
|
||||
def test_seconds_fields(self):
|
||||
"""
|
||||
|
|
|
@ -9,6 +9,7 @@ from rest_framework.serializers import ModelSerializer
|
|||
from rest_framework.utils import json
|
||||
from rest_framework.utils.breadcrumbs import get_breadcrumbs
|
||||
from rest_framework.utils.formatting import lazy_format
|
||||
from rest_framework.utils.model_meta import FieldInfo, RelationInfo
|
||||
from rest_framework.utils.urls import remove_query_param, replace_query_param
|
||||
from rest_framework.views import APIView
|
||||
from rest_framework.viewsets import ModelViewSet
|
||||
|
@ -267,3 +268,9 @@ class LazyFormatTests(TestCase):
|
|||
assert message.format.call_count == 1
|
||||
str(formatted)
|
||||
assert message.format.call_count == 1
|
||||
|
||||
|
||||
class ModelMetaNamedTupleNames(TestCase):
|
||||
def test_named_tuple_names(self):
|
||||
assert FieldInfo.__name__ == 'FieldInfo'
|
||||
assert RelationInfo.__name__ == 'RelationInfo'
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import datetime
|
||||
from unittest.mock import MagicMock
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
import pytest
|
||||
from django.db import DataError, models
|
||||
|
@ -447,6 +447,22 @@ class TestUniquenessTogetherValidation(TestCase):
|
|||
serializer = NullUniquenessTogetherSerializer(data=data)
|
||||
assert not serializer.is_valid()
|
||||
|
||||
def test_ignore_validation_for_unchanged_fields(self):
|
||||
"""
|
||||
If all fields in the unique together constraint are unchanged,
|
||||
then the instance should skip uniqueness validation.
|
||||
"""
|
||||
instance = UniquenessTogetherModel.objects.create(
|
||||
race_name="Paris Marathon", position=1
|
||||
)
|
||||
data = {"race_name": "Paris Marathon", "position": 1}
|
||||
serializer = UniquenessTogetherSerializer(data=data, instance=instance)
|
||||
with patch(
|
||||
"rest_framework.validators.qs_exists"
|
||||
) as mock:
|
||||
assert serializer.is_valid()
|
||||
assert not mock.called
|
||||
|
||||
def test_filter_queryset_do_not_skip_existing_attribute(self):
|
||||
"""
|
||||
filter_queryset should add value from existing instance attribute
|
||||
|
|
Loading…
Reference in New Issue
Block a user