authentic/src/authentic2/attribute_kinds.py

221 lines
6.7 KiB
Python

import re
import string
import datetime
from itertools import chain
from django import forms
from django.core.exceptions import ValidationError
from django.core.validators import RegexValidator
from django.utils.translation import ugettext_lazy as _, pgettext_lazy
from django.utils.functional import allow_lazy
from django.template.defaultfilters import capfirst
from rest_framework import serializers
from .decorators import to_iter
from .plugins import collect_from_plugins
from . import app_settings
from .forms import widgets
capfirst = allow_lazy(capfirst, unicode)
DEFAULT_TITLE_CHOICES = (
(pgettext_lazy('title', 'Mrs'), pgettext_lazy('title', 'Mrs')),
(pgettext_lazy('title', 'Mr'), pgettext_lazy('title', 'Mr')),
)
class BirthdateWidget(widgets.DateWidget):
help_text = _('Format: yyyy-mm-dd')
def __init__(self, *args, **kwargs):
options = kwargs.setdefault('options', {})
options['endDate'] = '-1d'
super(BirthdateWidget, self).__init__(*args, **kwargs)
class BirthdateField(forms.DateField):
widget = BirthdateWidget
def clean(self, value):
value = super(BirthdateField, self).clean(value)
if value and value >= datetime.date.today():
raise ValidationError(_('birthdate must be in the past.'))
return value
@to_iter
def get_title_choices():
return app_settings.A2_ATTRIBUTE_KIND_TITLE_CHOICES or DEFAULT_TITLE_CHOICES
validate_phone_number = RegexValidator('^\+?\d{,20}$', message=_('Phone number can start with a + '
'an must contain only digits.'))
class PhoneNumberField(forms.CharField):
def __init__(self, *args, **kwargs):
kwargs['max_length'] = 30
kwargs.setdefault('help_text', _('ex.: 0699999999, +33 6 99 99 99 99'))
super(PhoneNumberField, self).__init__(*args, **kwargs)
def clean(self, value):
if value not in self.empty_values:
value = re.sub('[-.\s]', '', value)
validate_phone_number(value)
return value
class PhoneNumberDRFField(serializers.CharField):
default_validators = [validate_phone_number]
validate_fr_postcode = RegexValidator(
'^\d{5}$', message=_('The value must be a valid french postcode'))
class FrPostcodeField(forms.CharField):
def __init__(self, *args, **kwargs):
kwargs.setdefault('help_text', _('ex.: 13260'))
super(FrPostcodeField, self).__init__(*args, **kwargs)
def clean(self, value):
value = super(FrPostcodeField, self).clean(value)
if value not in self.empty_values:
value = value.strip()
validate_fr_postcode(value)
return value
class FrPostcodeDRFField(serializers.CharField):
default_validators = [validate_fr_postcode]
DEFAULT_ALLOW_BLANK = True
DEFAULT_MAX_LENGTH = 256
DEFAULT_ATTRIBUTE_KINDS = [
{
'label': _('string'),
'name': 'string',
'field_class': forms.CharField,
'kwargs': {
'max_length': DEFAULT_MAX_LENGTH,
},
},
{
'label': _('title'),
'name': 'title',
'field_class': forms.ChoiceField,
'kwargs': {
'choices': get_title_choices(),
'widget': forms.RadioSelect,
}
},
{
'label': _('boolean'),
'name': 'boolean',
'field_class': forms.BooleanField,
'serialize': lambda x: str(int(bool(x))),
'deserialize': lambda x: bool(int(x)),
'rest_framework_field_class': serializers.NullBooleanField,
},
{
'label': _('date'),
'name': 'date',
'field_class': forms.DateField,
'kwargs': {
'widget': widgets.DateWidget,
},
'serialize': lambda x: x.isoformat(),
'deserialize': lambda x: x and datetime.datetime.strptime(x, '%Y-%m-%d').date(),
'rest_framework_field_class': serializers.DateField,
},
{
'label': _('birthdate'),
'name': 'birthdate',
'field_class': BirthdateField,
'serialize': lambda x: x.isoformat(),
'deserialize': lambda x: x and datetime.datetime.strptime(x, '%Y-%m-%d').date(),
'rest_framework_field_class': serializers.DateField,
},
{
'label': _('french postcode'),
'name': 'fr_postcode',
'field_class': FrPostcodeField,
'rest_framework_field_class': FrPostcodeDRFField,
},
{
'label': _('phone number'),
'name': 'phone_number',
'field_class': PhoneNumberField,
'rest_framework_field_class': PhoneNumberDRFField,
},
]
def get_attribute_kinds():
attribute_kinds = {}
for attribute_kind in chain(DEFAULT_ATTRIBUTE_KINDS, app_settings.A2_ATTRIBUTE_KINDS):
attribute_kinds[attribute_kind['name']] = attribute_kind
for attribute_kind in chain(*collect_from_plugins('attribute_kinds')):
attribute_kinds[attribute_kind['name']] = attribute_kind
return attribute_kinds
@to_iter
def get_choices():
'''Produce a choice list to use in form fields'''
for d in get_attribute_kinds().itervalues():
yield (d['name'], capfirst(d['label']))
def only_digits(value):
return u''.join(x for x in value if x in string.digits)
def validate_lun(value):
l = [(int(x) * (1 + i % 2)) for i, x in enumerate(reversed(value))]
return sum(x - 9 if x > 10 else x for x in l) % 10 == 0
def validate_siret(value):
RegexValidator(r'^( *[0-9] *){14}$', _('SIRET number must contain 14 digits'), 'coin')(value)
value = only_digits(value)
if not validate_lun(value) or not validate_lun(value[:9]):
raise ValidationError(_('SIRET validation code does not match'))
class SIRETField(forms.CharField):
default_validators = [validate_siret]
def to_python(self, value):
value = super(SIRETField, self).to_python(value)
value = only_digits(value)
return value
def contribute_to_form(attribute_descriptions, form):
for attribute_description in attribute_descriptions:
attribute_description.contribute_to_form(form)
def get_form_field(kind, **kwargs):
defn = get_attribute_kinds()[kind]
if 'kwargs' in defn:
kwargs.update(defn['kwargs'])
return defn['field_class'](**kwargs)
def get_kind(kind):
d = get_attribute_kinds()[kind]
d.setdefault('default', None)
d.setdefault('serialize', lambda x: x)
d.setdefault('deserialize', lambda x: x)
rest_field_kwargs = d.setdefault('rest_framework_field_kwargs', {})
if 'rest_framework_field_class' not in d:
d['rest_framework_field_class'] = serializers.CharField
rest_field_kwargs.setdefault('allow_blank', DEFAULT_ALLOW_BLANK)
rest_field_kwargs.setdefault('max_length', DEFAULT_MAX_LENGTH)
return d