Source code for localflavor.pt.forms

# -*- coding: utf-8 -*-


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


from __future__ import unicode_literals

from re import compile as regex_compile
from re import sub as regex_replace

from django.core.validators import EMPTY_VALUES
from django.forms import ValidationError
from django.forms.fields import Field, RegexField, Select
from django.utils.encoding import force_text
from django.utils.translation import ugettext_lazy as _

from .pt_regions import REGION_CHOICES

CITIZEN_CARD_NUMBER_REGEX = regex_compile(r'^(\d{8})-?(\d[A-Z0-9]{2}\d)$')
PHONE_NUMBER_REGEX = regex_compile(r'^((00|\+)351)?\d{3,9}$')
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(Field): """ 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).'), } def clean(self, value): super(PTCitizenCardNumberField, self).clean(value) if value in EMPTY_VALUES: return '' match = CITIZEN_CARD_NUMBER_REGEX.match(value) if not match: raise ValidationError(self.error_messages['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']) 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 PTPhoneNumberField(Field): """ A field which validates Portuguese phone numbers. - Phone numbers have at least 3 and at most 9 digits and may optionally be prefixed with '00351' or '+351'. - The input string is allowed to contain spaces (though they will be stripped). """ default_error_messages = { 'invalid': _('Phone numbers have at least 3 and at most 9 digits and may optionally be prefixed with \'00351\' or \'+351\'.'), } def clean(self, value): super(PTPhoneNumberField, self).clean(value) if value in EMPTY_VALUES: return '' value = regex_replace('(\.|\s)', '', force_text(value)) match = PHONE_NUMBER_REGEX.search(value) if not match: raise ValidationError(self.error_messages['invalid']) return '{0}'.format(value)
[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(PTRegionSelect, self).__init__(attrs, choices=REGION_CHOICES)
[docs]class PTSocialSecurityNumberField(Field): """ 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).'), } def clean(self, value): super(PTSocialSecurityNumberField, self).clean(value) if value in EMPTY_VALUES: return '' match = SOCIAL_SECURITY_NUMBER_REGEX.search(value) if not match: raise ValidationError(self.error_messages['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']) 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, max_length=None, min_length=None, *args, **kwargs): super(PTZipCodeField, self).__init__(ZIP_CODE_REGEX, max_length, min_length, *args, **kwargs)