api: extend DRF date field to accept empty string (#36365)

This commit is contained in:
Nicolas Roche 2019-09-24 19:17:20 +02:00
parent 96f8538a08
commit 05340b110b
3 changed files with 128 additions and 9 deletions

View File

@ -33,6 +33,7 @@ from django.template.defaultfilters import capfirst
from django.core.files.storage import default_storage
from rest_framework import serializers
from rest_framework.fields import empty
from .decorators import to_iter
from .plugins import collect_from_plugins
@ -47,9 +48,36 @@ DEFAULT_TITLE_CHOICES = (
)
class BirthdateWidget(widgets.DateWidget):
class DateWidget(widgets.DateWidget):
help_text = _('Format: yyyy-mm-dd')
class DateField(forms.DateField):
widget = DateWidget
class DateRestField(serializers.DateField):
default_error_messages = {
'blank': _('This field may not be blank.'),
}
def __init__(self, **kwargs):
self.allow_blank = kwargs.pop('allow_blank', False)
self.trim_whitespace = kwargs.pop('trim_whitespace', True)
super(DateRestField, self).__init__(**kwargs)
def run_validation(self, data=empty):
# Test for the empty string here so that it does not get validated,
# and so that subclasses do not need to handle it explicitly
# inside the `to_internal_value()` method.
if data == '' or (self.trim_whitespace and six.text_type(data).strip() == ''):
if not self.allow_blank:
self.fail('blank')
return ''
return super(DateRestField, self).run_validation(data)
class BirthdateWidget(DateWidget):
def __init__(self, *args, **kwargs):
options = kwargs.setdefault('options', {})
options['endDate'] = '-1d'
@ -69,7 +97,7 @@ class BirthdateField(forms.DateField):
]
class BirthdateRestField(serializers.DateField):
class BirthdateRestField(DateRestField):
default_validators = [
validate_birthdate,
]
@ -203,19 +231,16 @@ DEFAULT_ATTRIBUTE_KINDS = [
{
'label': _('date'),
'name': 'date',
'field_class': forms.DateField,
'kwargs': {
'widget': widgets.DateWidget,
},
'serialize': lambda x: x.isoformat(),
'field_class': DateField,
'serialize': lambda x: x and x.isoformat(),
'deserialize': lambda x: x and datetime.datetime.strptime(x, '%Y-%m-%d').date(),
'rest_framework_field_class': serializers.DateField,
'rest_framework_field_class': DateRestField,
},
{
'label': _('birthdate'),
'name': 'birthdate',
'field_class': BirthdateField,
'serialize': lambda x: x.isoformat(),
'serialize': lambda x: x and x.isoformat(),
'deserialize': lambda x: x and datetime.datetime.strptime(x, '%Y-%m-%d').date(),
'rest_framework_field_class': BirthdateRestField,
},

View File

@ -178,6 +178,7 @@ class Attribute(models.Model):
def get_drf_field(self, **kwargs):
from rest_framework import serializers
from authentic2.attribute_kinds import DateRestField
kind = self.get_kind()
field_class = kind['rest_framework_field_class']
@ -198,8 +199,14 @@ class Attribute(models.Model):
if (issubclass(field_class, serializers.CharField) and 'allow_blank' not in
base_kwargs):
base_kwargs['allow_blank'] = True
elif (issubclass(field_class, DateRestField) and 'allow_blank' not in
base_kwargs):
base_kwargs['allow_blank'] = True
elif issubclass(field_class, serializers.CharField):
base_kwargs['allow_blank'] = False
elif issubclass(field_class, DateRestField):
base_kwargs['allow_blank'] = False
base_kwargs.update(kwargs)
return field_class(**base_kwargs)

View File

@ -1378,6 +1378,93 @@ def test_api_user_required_drf_attribute(settings, app, admin, simple_user):
resp = app.put_json('/api/users/{}/'.format(simple_user.uuid), params=payload, headers=headers, status=200)
def test_api_users_required_date_attributes(settings, app, admin, simple_user):
Attribute.objects.create(kind='string', name='prefered_color', label='prefered color', required=True)
Attribute.objects.create(kind='date', name='date', label='date', required=True)
Attribute.objects.create(kind='birthdate', name='birthdate', label='birthdate', required=True)
User = get_user_model()
payload = {
'username': simple_user.username,
'id': simple_user.id,
'email': 'john.doe@nowhere.null',
'first_name': 'Johnny',
'last_name': 'Doe',
}
headers = basic_authorization_header(admin)
resp = app.put_json('/api/users/{}/'.format(simple_user.uuid),
params=payload, headers=headers, status=400)
assert resp.json['result'] == 0
assert resp.json['errors']['prefered_color'] == ['This field is required.']
assert resp.json['errors']['date'] == ['This field is required.']
assert resp.json['errors']['birthdate'] == ['This field is required.']
payload['prefered_color'] = ''
payload['date'] = ''
payload['birthdate'] = ''
resp = app.put_json('/api/users/{}/'.format(simple_user.uuid),
params=payload, headers=headers, status=400)
assert resp.json['result'] == 0
assert resp.json['errors']['prefered_color'] == ['This field may not be blank.']
assert resp.json['errors']['date'] == ['This field may not be blank.']
assert resp.json['errors']['birthdate'] == ['This field may not be blank.']
payload['prefered_color'] = '?'*257
payload['date'] = '0000-00-00'
payload['birthdate'] = '1899-12-31'
resp = app.put_json('/api/users/{}/'.format(simple_user.uuid),
params=payload, headers=headers, status=400)
assert resp.json['result'] == 0
assert resp.json['errors']['prefered_color'] == ['Ensure this field has no more than 256 characters.']
assert resp.json['errors']['date'] == ['Date has wrong format. Use one of these formats instead: YYYY[-MM[-DD]].']
assert resp.json['errors']['birthdate'] == ['birthdate must be in the past and greater or equal than 1900-01-01.']
payload['prefered_color'] = 'blue'
payload['date'] = '1515-1-15'
payload['birthdate'] = '1900-2-22'
resp = app.put_json('/api/users/{}/'.format(simple_user.uuid),
params=payload, headers=headers, status=200)
resp = app.get('/api/users/{}/'.format(simple_user.uuid),
headers=headers, status=200)
assert resp.json['prefered_color'] == 'blue'
assert resp.json['date'] == '1515-01-15'
assert resp.json['birthdate'] == '1900-02-22'
def test_api_users_optional_date_attributes(settings, app, admin, simple_user):
Attribute.objects.create(kind='string', name='prefered_color', label='prefered color', required=False)
Attribute.objects.create(kind='date', name='date', label='date', required=False)
Attribute.objects.create(kind='birthdate', name='birthdate', label='birthdate', required=False)
User = get_user_model()
payload = {
'username': simple_user.username,
'id': simple_user.id,
'email': 'john.doe@nowhere.null',
'first_name': 'Johnny',
'last_name': 'Doe',
}
headers = basic_authorization_header(admin)
resp = app.put_json('/api/users/{}/'.format(simple_user.uuid),
params=payload, headers=headers, status=200)
resp = app.get('/api/users/{}/'.format(simple_user.uuid),
headers=headers, status=200)
payload['prefered_color'] = None
payload['date'] = None
payload['birthdate'] = None
payload['prefered_color'] = ''
payload['date'] = ''
payload['birthdate'] = ''
resp = app.put_json('/api/users/{}/'.format(simple_user.uuid),
params=payload, headers=headers, status=200)
resp = app.get('/api/users/{}/'.format(simple_user.uuid),
headers=headers, status=200)
payload['prefered_color'] = None
payload['date'] = None
payload['birthdate'] = None
def test_filter_users_by_service(app, admin, simple_user, role_random, service):
app.authorization = ('Basic', (admin.username, admin.username))