mirror of
https://github.com/encode/django-rest-framework.git
synced 2025-07-16 11:12:21 +03:00
Merge branch 'master' into fix-get_template_context
This commit is contained in:
commit
f2fff36db9
|
@ -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,17 +9,17 @@ 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.13.0
|
||||
rev: 1.16.0
|
||||
hooks:
|
||||
- id: blacken-docs
|
||||
exclude: ^(?!docs).*$
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -311,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,
|
||||
|
|
|
@ -64,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.
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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]
|
||||
|
@ -954,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])
|
||||
|
|
|
@ -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...",
|
||||
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -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):
|
||||
"""
|
||||
|
|
|
@ -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