Merge branch 'master' into update_irc_server

This commit is contained in:
Tom Christie 2021-08-06 16:46:20 +01:00 committed by GitHub
commit c1c8b9f3de
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 83 additions and 35 deletions

View File

@ -105,7 +105,7 @@ The TemplateHTMLRenderer will create a `RequestContext`, using the `response.dat
---
**Note:** When used with a view that makes use of a serializer the `Response` sent for rendering may not be a dictionay and will need to be wrapped in a dict before returning to allow the TemplateHTMLRenderer to render it. For example:
**Note:** When used with a view that makes use of a serializer the `Response` sent for rendering may not be a dictionary and will need to be wrapped in a dict before returning to allow the TemplateHTMLRenderer to render it. For example:
```
response.data = {'results': response.data}
@ -528,7 +528,7 @@ Comma-separated values are a plain-text tabular data format, that can be easily
[Rest Framework Latex] provides a renderer that outputs PDFs using Laulatex. It is maintained by [Pebble (S/F Software)][mypebble].
[cite]: https://docs.djangoproject.com/en/stable/stable/template-response/#the-rendering-process
[cite]: https://docs.djangoproject.com/en/stable/ref/template-response/#the-rendering-process
[conneg]: content-negotiation.md
[html-and-forms]: ../topics/html-and-forms.md
[browser-accept-headers]: http://www.gethifi.com/blog/browser-rest-http-accept-headers

View File

@ -605,13 +605,13 @@ For `ModelSerializer` this defaults to `PrimaryKeyRelatedField`.
For `HyperlinkedModelSerializer` this defaults to `serializers.HyperlinkedRelatedField`.
### `serializer_url_field`
### `.serializer_url_field`
The serializer field class that should be used for any `url` field on the serializer.
Defaults to `serializers.HyperlinkedIdentityField`
### `serializer_choice_field`
### `.serializer_choice_field`
The serializer field class that should be used for any choice fields on the serializer.

View File

@ -186,7 +186,7 @@ Framework.
## Support
For support please see the [REST framework discussion group][group], try the `#restframework` channel on `irc.libera.chat`, search [the IRC archives][botbot], or raise a question on [Stack Overflow][stack-overflow], making sure to include the ['django-rest-framework'][django-rest-framework-tag] tag.
For support please see the [REST framework discussion group][group], try the `#restframework` channel on `irc.libera.chat`, or raise a question on [Stack Overflow][stack-overflow], making sure to include the ['django-rest-framework'][django-rest-framework-tag] tag.
For priority support please sign up for a [professional or premium sponsorship plan](https://fund.django-rest-framework.org/topics/funding/).
@ -257,7 +257,6 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
[funding]: community/funding.md
[group]: https://groups.google.com/forum/?fromgroups#!forum/django-rest-framework
[botbot]: https://botbot.me/freenode/restframework/
[stack-overflow]: https://stackoverflow.com/
[django-rest-framework-tag]: https://stackoverflow.com/questions/tagged/django-rest-framework
[security-mail]: mailto:rest-framework-security@googlegroups.com

View File

@ -320,7 +320,7 @@ class Field:
default_empty_html = empty
initial = None
def __init__(self, read_only=False, write_only=False,
def __init__(self, *, read_only=False, write_only=False,
required=None, default=empty, initial=empty, source=None,
label=None, help_text=None, style=None,
error_messages=None, validators=None, allow_null=False):
@ -1046,6 +1046,11 @@ class DecimalField(Field):
'Invalid rounding option %s. Valid values for rounding are: %s' % (rounding, valid_roundings))
self.rounding = rounding
def validate_empty_values(self, data):
if smart_str(data).strip() == '' and self.allow_null:
return (True, None)
return super().validate_empty_values(data)
def to_internal_value(self, data):
"""
Validate that the input is a decimal number and return a Decimal
@ -1063,9 +1068,6 @@ class DecimalField(Field):
try:
value = decimal.Decimal(data)
except decimal.DecimalException:
if data == '' and self.allow_null:
return None
self.fail('invalid')
if value.is_nan():
@ -1161,14 +1163,14 @@ class DateTimeField(Field):
}
datetime_parser = datetime.datetime.strptime
def __init__(self, format=empty, input_formats=None, default_timezone=None, *args, **kwargs):
def __init__(self, format=empty, input_formats=None, default_timezone=None, **kwargs):
if format is not empty:
self.format = format
if input_formats is not None:
self.input_formats = input_formats
if default_timezone is not None:
self.timezone = default_timezone
super().__init__(*args, **kwargs)
super().__init__(**kwargs)
def enforce_timezone(self, value):
"""
@ -1247,12 +1249,12 @@ class DateField(Field):
}
datetime_parser = datetime.datetime.strptime
def __init__(self, format=empty, input_formats=None, *args, **kwargs):
def __init__(self, format=empty, input_formats=None, **kwargs):
if format is not empty:
self.format = format
if input_formats is not None:
self.input_formats = input_formats
super().__init__(*args, **kwargs)
super().__init__(**kwargs)
def to_internal_value(self, value):
input_formats = getattr(self, 'input_formats', api_settings.DATE_INPUT_FORMATS)
@ -1313,12 +1315,12 @@ class TimeField(Field):
}
datetime_parser = datetime.datetime.strptime
def __init__(self, format=empty, input_formats=None, *args, **kwargs):
def __init__(self, format=empty, input_formats=None, **kwargs):
if format is not empty:
self.format = format
if input_formats is not None:
self.input_formats = input_formats
super().__init__(*args, **kwargs)
super().__init__(**kwargs)
def to_internal_value(self, value):
input_formats = getattr(self, 'input_formats', api_settings.TIME_INPUT_FORMATS)
@ -1468,9 +1470,9 @@ class MultipleChoiceField(ChoiceField):
}
default_empty_html = []
def __init__(self, *args, **kwargs):
def __init__(self, **kwargs):
self.allow_empty = kwargs.pop('allow_empty', True)
super().__init__(*args, **kwargs)
super().__init__(**kwargs)
def get_value(self, dictionary):
if self.field_name not in dictionary:
@ -1527,12 +1529,12 @@ class FileField(Field):
'max_length': _('Ensure this filename has at most {max_length} characters (it has {length}).'),
}
def __init__(self, *args, **kwargs):
def __init__(self, **kwargs):
self.max_length = kwargs.pop('max_length', None)
self.allow_empty_file = kwargs.pop('allow_empty_file', False)
if 'use_url' in kwargs:
self.use_url = kwargs.pop('use_url')
super().__init__(*args, **kwargs)
super().__init__(**kwargs)
def to_internal_value(self, data):
try:
@ -1576,9 +1578,9 @@ class ImageField(FileField):
),
}
def __init__(self, *args, **kwargs):
def __init__(self, **kwargs):
self._DjangoImageField = kwargs.pop('_DjangoImageField', DjangoImageField)
super().__init__(*args, **kwargs)
super().__init__(**kwargs)
def to_internal_value(self, data):
# Image validation is a bit grungy, so we'll just outright
@ -1593,8 +1595,8 @@ class ImageField(FileField):
# Composite field types...
class _UnvalidatedField(Field):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.allow_blank = True
self.allow_null = True
@ -1615,7 +1617,7 @@ class ListField(Field):
'max_length': _('Ensure this field has no more than {max_length} elements.')
}
def __init__(self, *args, **kwargs):
def __init__(self, **kwargs):
self.child = kwargs.pop('child', copy.deepcopy(self.child))
self.allow_empty = kwargs.pop('allow_empty', True)
self.max_length = kwargs.pop('max_length', None)
@ -1627,7 +1629,7 @@ class ListField(Field):
"Remove `source=` from the field declaration."
)
super().__init__(*args, **kwargs)
super().__init__(**kwargs)
self.child.bind(field_name='', parent=self)
if self.max_length is not None:
message = lazy_format(self.error_messages['max_length'], max_length=self.max_length)
@ -1692,7 +1694,7 @@ class DictField(Field):
'empty': _('This dictionary may not be empty.'),
}
def __init__(self, *args, **kwargs):
def __init__(self, **kwargs):
self.child = kwargs.pop('child', copy.deepcopy(self.child))
self.allow_empty = kwargs.pop('allow_empty', True)
@ -1702,7 +1704,7 @@ class DictField(Field):
"Remove `source=` from the field declaration."
)
super().__init__(*args, **kwargs)
super().__init__(**kwargs)
self.child.bind(field_name='', parent=self)
def get_value(self, dictionary):
@ -1751,8 +1753,8 @@ class DictField(Field):
class HStoreField(DictField):
child = CharField(allow_blank=True, allow_null=True)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
def __init__(self, **kwargs):
super().__init__(**kwargs)
assert isinstance(self.child, CharField), (
"The `child` argument must be an instance of `CharField`, "
"as the hstore extension stores values as strings."
@ -1767,11 +1769,11 @@ class JSONField(Field):
# Workaround for isinstance calls when importing the field isn't possible
_is_jsonfield = True
def __init__(self, *args, **kwargs):
def __init__(self, **kwargs):
self.binary = kwargs.pop('binary', False)
self.encoder = kwargs.pop('encoder', None)
self.decoder = kwargs.pop('decoder', None)
super().__init__(*args, **kwargs)
super().__init__(**kwargs)
def get_value(self, dictionary):
if html.is_html_input(dictionary) and self.field_name in dictionary:

View File

@ -1326,9 +1326,8 @@ class ModelSerializer(Serializer):
"""
if extra_kwargs.get('read_only', False):
for attr in [
'required', 'default', 'allow_blank', 'allow_null',
'min_length', 'max_length', 'min_value', 'max_value',
'validators', 'queryset'
'required', 'default', 'allow_blank', 'min_length',
'max_length', 'min_value', 'max_value', 'validators', 'queryset'
]:
kwargs.pop(attr, None)

View File

@ -2,6 +2,7 @@ import uuid
import warnings
import pytest
from django.db import models
from django.test import RequestFactory, TestCase, override_settings
from django.urls import path
from django.utils.translation import gettext_lazy as _
@ -110,6 +111,24 @@ class TestFieldMapping(TestCase):
assert data['properties']['default_false']['default'] is False, "default must be false"
assert 'default' not in data['properties']['without_default'], "default must not be defined"
def test_nullable_fields(self):
class Model(models.Model):
rw_field = models.CharField(null=True)
ro_field = models.CharField(null=True)
class Serializer(serializers.ModelSerializer):
class Meta:
model = Model
fields = ["rw_field", "ro_field"]
read_only_fields = ["ro_field"]
inspector = AutoSchema()
data = inspector.map_serializer(Serializer())
assert data['properties']['rw_field']['nullable'], "rw_field nullable must be true"
assert data['properties']['ro_field']['nullable'], "ro_field nullable must be true"
assert data['properties']['ro_field']['readOnly'], "ro_field read_only must be true"
@pytest.mark.skipif(uritemplate is None, reason='uritemplate not installed.')
class TestOperationIntrospection(TestCase):

View File

@ -1163,6 +1163,30 @@ class TestMinMaxDecimalField(FieldValues):
)
class TestAllowEmptyStrDecimalFieldWithValidators(FieldValues):
"""
Check that empty string ('', ' ') is acceptable value for the DecimalField
if allow_null=True and there are max/min validators
"""
valid_inputs = {
None: None,
'': None,
' ': None,
' ': None,
5: Decimal('5'),
'0': Decimal('0'),
'10': Decimal('10'),
}
invalid_inputs = {
-1: ['Ensure this value is greater than or equal to 0.'],
11: ['Ensure this value is less than or equal to 10.'],
}
outputs = {
None: '',
}
field = serializers.DecimalField(max_digits=3, decimal_places=1, allow_null=True, min_value=0, max_value=10)
class TestNoMaxDigitsDecimalField(FieldValues):
field = serializers.DecimalField(
max_value=100, min_value=0,
@ -1986,6 +2010,11 @@ class TestListField(FieldValues):
field.to_internal_value(input_value)
assert exc_info.value.detail == ['Expected a list of items but got type "dict".']
def test_constructor_misuse_raises(self):
# Test that `ListField` can only be instantiated with keyword arguments
with pytest.raises(TypeError):
serializers.ListField(serializers.CharField())
class TestNestedListField(FieldValues):
"""