Source code for localflavor.pt.forms

"""
django_localflavot_pt.forms
~~~~~~~~~~~~~~~~~~~~~~~~~~~
Contains PT-specific Django form helpers.
"""

from re import compile as regex_compile

from django.forms import ValidationError
from django.forms.fields import CharField, RegexField, Select
from django.utils.translation import gettext_lazy as _

from .pt_regions import REGION_CHOICES

CITIZEN_CARD_NUMBER_REGEX = regex_compile(r'^(\d{8})-?(\d[A-Z0-9]{2}\d)$')
SOCIAL_SECURITY_NUMBER_MULTIPLIERS = [29, 23, 19, 17, 13, 11, 7, 5, 3, 2]
SOCIAL_SECURITY_NUMBER_REGEX = regex_compile(r'^[12]\d{10}$')
ZIP_CODE_REGEX = regex_compile(r'^[1-9]\d{3}-\d{3}$')


[docs]class PTCitizenCardNumberField(CharField): """ A field which validates Portuguese Citizen Card numbers (locally CC - 'Cartão do Cidadão'). - Citizen Card numbers have the format XXXXXXXXXYYX or XXXXXXXX-XYYX (where X is a digit and Y is an alphanumeric character). - Citizen Card numbers validate as per http://bit.ly/RP0BzW. - The input string may or may not have an hyphen separating the identity number from the document's check-digits. - This field does NOT validate old ID card numbers (locally BI - 'Bilhete de Identidade'). """ default_error_messages = { 'badchecksum': _('The specified value is not a valid Citizen Card number.'), 'invalid': _('Citizen Card numbers have the format XXXXXXXXXYYX or XXXXXXXX-XYYX ' '(where X is a digit and Y is an alphanumeric character).'), }
[docs] def clean(self, value): value = super().clean(value) if value in self.empty_values: return value match = CITIZEN_CARD_NUMBER_REGEX.match(value) if not match: raise ValidationError(self.error_messages['invalid'], code='invalid') number, checkdigits = match.groups() encoded = number + checkdigits decoded = [int(digit, 36) for digit in encoded] checksum = sum([PTCitizenCardNumberField.compute(index, decoded_value) for index, decoded_value in enumerate(decoded)]) if not checksum % 10 == 0: raise ValidationError(self.error_messages['badchecksum'], code='badchecksum') return '{0}-{1}'.format(number, checkdigits)
@staticmethod def compute(index, value): if index % 2: return value else: value *= 2 return value if value < 10 else value - 9
[docs]class PTRegionSelect(Select): """ A select widget which uses a list of Portuguese regions as its choices. - Regions correspond to the Portuguese 'distritos' and 'regiões autónomas' as per ISO3166:2-PT. """ def __init__(self, attrs=None): super().__init__(attrs, choices=REGION_CHOICES)
[docs]class PTSocialSecurityNumberField(CharField): """ A field which validates Portuguese Social Security numbers. (locally NISS - 'Número de Identificação na Segurança Social'). - Social Security numbers must be in the format XYYYYYYYYYY (where X is either 1 or 2 and Y is any other digit). """ default_error_messages = { 'badchecksum': _('The specified number is not a valid Social Security number.'), 'invalid': _('Social Security numbers must be in the format XYYYYYYYYYY ' '(where X is either 1 or 2 and Y is any other digit).'), }
[docs] def clean(self, value): value = super().clean(value) if value in self.empty_values: return value match = SOCIAL_SECURITY_NUMBER_REGEX.search(value) if not match: raise ValidationError(self.error_messages['invalid'], code='invalid') digits = [int(digit) for digit in value] factors = list(zip(digits, SOCIAL_SECURITY_NUMBER_MULTIPLIERS)) dotproduct = sum(p * q for p, q in factors) checksum = 9 - dotproduct % 10 checkdigit = int(value[-1]) if not checksum == checkdigit: raise ValidationError(self.error_messages['badchecksum'], code='badchecksum') return int(value)
[docs]class PTZipCodeField(RegexField): """ A field which validates Portuguese zip codes. NOTE - Zip codes have the format XYYY-YYY (where X is a digit between 1 and 9 and Y is any other digit). """ default_error_messages = { 'invalid': _('Zip codes must be in the format XYYY-YYY' ' (where X is a digit between 1 and 9 and Y is any other digit).'), } def __init__(self, **kwargs): super().__init__(ZIP_CODE_REGEX, **kwargs)