diff options
| author | Jannis Leidel <jannis@leidel.info> | 2011-07-29 10:20:16 +0000 |
|---|---|---|
| committer | Jannis Leidel <jannis@leidel.info> | 2011-07-29 10:20:16 +0000 |
| commit | 0fbadfd1c8ce30fd4c9f2857d467047d5bfac63d (patch) | |
| tree | f4375be27f4ddc0416e21e61e74048ff41289258 /django | |
| parent | 4a6e1b56135b1a413576ea1a6d469387a1a5eb6d (diff) | |
Fixed #16497 -- Added new form and model fields to the Mexican local flavor. Many thanks to Andrés Torres Marroquín and Gerardo Orozco.
git-svn-id: http://code.djangoproject.com/svn/django/trunk@16572 bcc190cf-cafb-0310-a4f2-bffc1f526a37
Diffstat (limited to 'django')
| -rw-r--r-- | django/contrib/localflavor/mx/forms.py | 215 | ||||
| -rw-r--r-- | django/contrib/localflavor/mx/models.py | 70 | ||||
| -rw-r--r-- | django/contrib/localflavor/mx/mx_states.py | 2 |
3 files changed, 284 insertions, 3 deletions
diff --git a/django/contrib/localflavor/mx/forms.py b/django/contrib/localflavor/mx/forms.py index 95819375ef..95ca73d901 100644 --- a/django/contrib/localflavor/mx/forms.py +++ b/django/contrib/localflavor/mx/forms.py @@ -1,14 +1,225 @@ +# -*- coding: utf-8 -*- """ Mexican-specific form helpers. """ +import re -from django.forms.fields import Select +from django.forms import ValidationError +from django.forms.fields import Select, RegexField +from django.utils.translation import ugettext_lazy as _ +from django.core.validators import EMPTY_VALUES +from django.contrib.localflavor.mx.mx_states import STATE_CHOICES + +DATE_RE = r'\d{2}((01|03|05|07|08|10|12)(0[1-9]|[12]\d|3[01])|02(0[1-9]|[12]\d)|(04|06|09|11)(0[1-9]|[12]\d|30))' + +""" +This is the list of inconvenient words according to the `Anexo IV` of the +document described in the next link: + http://www.sisi.org.mx/jspsi/documentos/2005/seguimiento/06101/0610100162005_065.doc +""" + +RFC_INCONVENIENT_WORDS = [ + u'BUEI', u'BUEY', u'CACA', u'CACO', u'CAGA', u'CAGO', u'CAKA', u'CAKO', + u'COGE', u'COJA', u'COJE', u'COJI', u'COJO', u'CULO', u'FETO', u'GUEY', + u'JOTO', u'KACA', u'KACO', u'KAGA', u'KAGO', u'KOGE', u'KOJO', u'KAKA', + u'KULO', u'MAME', u'MAMO', u'MEAR', u'MEAS', u'MEON', u'MION', u'MOCO', + u'MULA', u'PEDA', u'PEDO', u'PENE', u'PUTA', u'PUTO', u'QULO', u'RATA', + u'RUIN', +] + +""" +This is the list of inconvenient words according to the `Anexo 2` of the +document described in the next link: + http://portal.veracruz.gob.mx/pls/portal/url/ITEM/444112558A57C6E0E040A8C02E00695C +""" +CURP_INCONVENIENT_WORDS = [ + u'BACA', u'BAKA', u'BUEI', u'BUEY', u'CACA', u'CACO', u'CAGA', u'CAGO', + u'CAKA', u'CAKO', u'COGE', u'COGI', u'COJA', u'COJE', u'COJI', u'COJO', + u'COLA', u'CULO', u'FALO', u'FETO', u'GETA', u'GUEI', u'GUEY', u'JETA', + u'JOTO', u'KACA', u'KACO', u'KAGA', u'KAGO', u'KAKA', u'KAKO', u'KOGE', + u'KOGI', u'KOJA', u'KOJE', u'KOJI', u'KOJO', u'KOLA', u'KULO', u'LILO', + u'LOCA', u'LOCO', u'LOKA', u'LOKO', u'MAME', u'MAMO', u'MEAR', u'MEAS', + u'MEON', u'MIAR', u'MION', u'MOCO', u'MOKO', u'MULA', u'MULO', u'NACA', + u'NACO', u'PEDA', u'PEDO', u'PENE', u'PIPI', u'PITO', u'POPO', u'PUTA', + u'PUTO', u'QULO', u'RATA', u'ROBA', u'ROBE', u'ROBO', u'RUIN', u'SENO', + u'TETA', u'VACA', u'VAGA', u'VAGO', u'VAKA', u'VUEI', u'VUEY', u'WUEI', + u'WUEY', +] class MXStateSelect(Select): """ A Select widget that uses a list of Mexican states as its choices. """ def __init__(self, attrs=None): - from mx_states import STATE_CHOICES super(MXStateSelect, self).__init__(attrs, choices=STATE_CHOICES) + +class MXZipCodeField(RegexField): + """ + A form field that accepts a Mexican Zip Code. + + More info about this: + http://en.wikipedia.org/wiki/List_of_postal_codes_in_Mexico + """ + default_error_messages = { + 'invalid': _(u'Enter a valid zip code in the format XXXXX.'), + } + + def __init__(self, *args, **kwargs): + zip_code_re = ur'^(0[1-9]|[1][0-6]|[2-9]\d)(\d{3})$' + super(MXZipCodeField, self).__init__(zip_code_re, *args, **kwargs) + + +class MXRFCField(RegexField): + """ + A form field that validates a Mexican *Registro Federal de Contribuyentes* + for either `Persona física` or `Persona moral`. + + The Persona física RFC string is integrated by a juxtaposition of + characters following the next pattern: + + ===== ====== =========================================== + Index Format Accepted Characters + ===== ====== =========================================== + 1 X Any letter + 2 X Any vowel + 3-4 XX Any letter + 5-10 YYMMDD Any valid date + 11-12 XX Any letter or number between 0 and 9 + 13 X Any digit between 0 and 9 or the letter *A* + ===== ====== =========================================== + + The Persona moral RFC string is integrated by a juxtaposition of + characters following the next pattern: + + ===== ====== ============================================ + Index Format Accepted Characters + ===== ====== ============================================ + 1-3 XXX Any letter including *&* and *Ñ* chars + 4-9 YYMMDD Any valid date + 10-11 XX Any letter or number between 0 and 9 + 12 X Any number between 0 and 9 or the letter *A* + ===== ====== ============================================ + + More info about this: + http://es.wikipedia.org/wiki/Registro_Federal_de_Contribuyentes_(M%C3%A9xico) + """ + default_error_messages = { + 'invalid': _('Enter a valid RFC.'), + 'invalid_checksum': _('Invalid checksum for RFC.'), + } + + def __init__(self, min_length=9, max_length=13, *args, **kwargs): + rfc_re = re.compile(ur'^([A-Z&Ññ]{3}|[A-Z][AEIOU][A-Z]{2})%s([A-Z0-9]{2}[0-9A])?$' % DATE_RE, + re.IGNORECASE) + super(MXRFCField, self).__init__(rfc_re, min_length=min_length, + max_length=max_length, *args, **kwargs) + + def clean(self, value): + value = super(MXRFCField, self).clean(value) + if value in EMPTY_VALUES: + return u'' + value = value.upper() + if self._has_homoclave(value): + if not value[-1] == self._checksum(value[:-1]): + raise ValidationError(self.default_error_messages['invalid_checksum']) + if self._has_inconvenient_word(value): + raise ValidationError(self.default_error_messages['invalid']) + return value + + def _has_homoclave(self, rfc): + """ + This check is done due to the existance of RFCs without a *homoclave* + since the current algorithm to calculate it had not been created for + the first RFCs ever in Mexico. + """ + rfc_without_homoclave_re = re.compile(ur'^[A-Z&Ññ]{3,4}%s$' % DATE_RE, + re.IGNORECASE) + return not rfc_without_homoclave_re.match(rfc) + + def _checksum(self, rfc): + """ + More info about this procedure: + www.sisi.org.mx/jspsi/documentos/2005/seguimiento/06101/0610100162005_065.doc + """ + chars = u'0123456789ABCDEFGHIJKLMN&OPQRSTUVWXYZ-Ñ' + if len(rfc) is 11: + rfc = '-' + rfc + + sum_ = sum(i * chars.index(c) for i, c in zip(reversed(xrange(14)), rfc)) + checksum = 11 - sum_ % 11 + + if checksum == 10: + return u'A' + elif checksum == 11: + return u'0' + + return unicode(checksum) + + def _has_inconvenient_word(self, rfc): + first_four = rfc[:4] + return first_four in RFC_INCONVENIENT_WORDS + + +class MXCURPField(RegexField): + """ + A field that validates a Mexican Clave Única de Registro de Población. + + The CURP is integrated by a juxtaposition of characters following the next + pattern: + + ===== ====== =================================================== + Index Format Accepted Characters + ===== ====== =================================================== + 1 X Any letter + 2 X Any vowel + 3-4 XX Any letter + 5-10 YYMMDD Any valid date + 11 X Either `H` or `M`, depending on the person's gender + 12-13 XX Any valid acronym for a state in Mexico + 14-16 XXX Any consonant + 17 X Any number between 0 and 9 or any letter + 18 X Any number between 0 and 9 + ===== ====== =================================================== + + More info about this: + http://www.condusef.gob.mx/index.php/clave-unica-de-registro-de-poblacion-curp + """ + default_error_messages = { + 'invalid': _('Enter a valid CURP.'), + 'invalid_checksum': _(u'Invalid checksum for CURP.'), + } + + def __init__(self, min_length=18, max_length=18, *args, **kwargs): + states_re = r'(AS|BC|BS|CC|CL|CM|CS|CH|DF|DG|GT|GR|HG|JC|MC|MN|MS|NT|NL|OC|PL|QT|QR|SP|SL|SR|TC|TS|TL|VZ|YN|ZS|NE)' + consonants_re = r'[B-DF-HJ-NP-TV-Z]' + curp_re = (ur'^[A-Z][AEIOU][A-Z]{2}%s[HM]%s%s{3}[0-9A-Z]\d$' % + (DATE_RE, states_re, consonants_re)) + curp_re = re.compile(curp_re, re.IGNORECASE) + super(MXCURPField, self).__init__(curp_re, min_length=min_length, + max_length=max_length, *args, **kwargs) + + def clean(self, value): + value = super(MXCURPField, self).clean(value) + if value in EMPTY_VALUES: + return u'' + value = value.upper() + if value[-1] != self._checksum(value[:-1]): + raise ValidationError(self.default_error_messages['invalid_checksum']) + if self._has_inconvenient_word(value): + raise ValidationError(self.default_error_messages['invalid']) + return value + + def _checksum(self, value): + chars = u'0123456789ABCDEFGHIJKLMN&OPQRSTUVWXYZ' + + s = sum(i * chars.index(c) for i, c in zip(reversed(xrange(19)), value)) + checksum = 10 - s % 10 + + if checksum == 10: + return u'0' + return unicode(checksum) + + def _has_inconvenient_word(self, curp): + first_four = curp[:4] + return first_four in CURP_INCONVENIENT_WORDS diff --git a/django/contrib/localflavor/mx/models.py b/django/contrib/localflavor/mx/models.py new file mode 100644 index 0000000000..3ef8d5fb03 --- /dev/null +++ b/django/contrib/localflavor/mx/models.py @@ -0,0 +1,70 @@ +from django.utils.translation import ugettext_lazy as _ +from django.db.models.fields import CharField + +from django.contrib.localflavor.mx.mx_states import STATE_CHOICES +from django.contrib.localflavor.mx.forms import (MXRFCField as MXRFCFormField, + MXZipCodeField as MXZipCodeFormField, MXCURPField as MXCURPFormField) + + +class MXStateField(CharField): + """ + A model field that stores the three-letter Mexican state abbreviation in the + database. + """ + description = _("Mexico state (three uppercase letters)") + + def __init__(self, *args, **kwargs): + kwargs['choices'] = STATE_CHOICES + kwargs['max_length'] = 3 + super(MXStateField, self).__init__(*args, **kwargs) + + +class MXZipCodeField(CharField): + """ + A model field that forms represent as a forms.MXZipCodeField field and + stores the five-digit Mexican zip code. + """ + description = _("Mexico zip code") + + def __init__(self, *args, **kwargs): + kwargs['max_length'] = 5 + super(MXZipCodeField, self).__init__(*args, **kwargs) + + def formfield(self, **kwargs): + defaults = {'form_class': MXZipCodeFormField} + defaults.update(kwargs) + return super(MXZipCodeField, self).formfield(**defaults) + + +class MXRFCField(CharField): + """ + A model field that forms represent as a forms.MXRFCField field and + stores the value of a valid Mexican RFC. + """ + description = _("Mexican RFC") + + def __init__(self, *args, **kwargs): + kwargs['max_length'] = 13 + super(MXRFCField, self).__init__(*args, **kwargs) + + def formfield(self, **kwargs): + defaults = {'form_class': MXRFCFormField} + defaults.update(kwargs) + return super(MXRFCField, self).formfield(**defaults) + + +class MXCURPField(CharField): + """ + A model field that forms represent as a forms.MXCURPField field and + stores the value of a valid Mexican CURP. + """ + description = _("Mexican CURP") + + def __init__(self, *args, **kwargs): + kwargs['max_length'] = 18 + super(MXCURPField, self).__init__(*args, **kwargs) + + def formfield(self, **kwargs): + defaults = {'form_class': MXCURPFormField} + defaults.update(kwargs) + return super(MXCURPField, self).formfield(**defaults)
\ No newline at end of file diff --git a/django/contrib/localflavor/mx/mx_states.py b/django/contrib/localflavor/mx/mx_states.py index eed1700efb..2aba63ef26 100644 --- a/django/contrib/localflavor/mx/mx_states.py +++ b/django/contrib/localflavor/mx/mx_states.py @@ -8,6 +8,7 @@ when explicitly needed. from django.utils.translation import ugettext_lazy as _ +# All 31 states, plus the `Distrito Federal`. STATE_CHOICES = ( ('AGU', _(u'Aguascalientes')), ('BCN', _(u'Baja California')), @@ -42,4 +43,3 @@ STATE_CHOICES = ( ('YUC', _(u'Yucatán')), ('ZAC', _(u'Zacatecas')), ) - |
