summaryrefslogtreecommitdiff
path: root/django
diff options
context:
space:
mode:
authorJannis Leidel <jannis@leidel.info>2011-07-29 10:20:16 +0000
committerJannis Leidel <jannis@leidel.info>2011-07-29 10:20:16 +0000
commit0fbadfd1c8ce30fd4c9f2857d467047d5bfac63d (patch)
treef4375be27f4ddc0416e21e61e74048ff41289258 /django
parent4a6e1b56135b1a413576ea1a6d469387a1a5eb6d (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.py215
-rw-r--r--django/contrib/localflavor/mx/models.py70
-rw-r--r--django/contrib/localflavor/mx/mx_states.py2
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')),
)
-