Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

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
  • Loading branch information...
commit 0fbadfd1c8ce30fd4c9f2857d467047d5bfac63d 1 parent 4a6e1b5
Jannis Leidel authored July 29, 2011
2  AUTHORS
@@ -340,6 +340,7 @@ answer newbie questions, and generally made Django that much better:
340 340
     Nuno Mariz <nmariz@gmail.com>
341 341
     mark@junklight.com
342 342
     Orestis Markou <orestis@orestis.gr>
  343
+    Andrés Torres Marroquín <andres.torres.marroquin@gmail.com>
343 344
     Takashi Matsuo <matsuo.takashi@gmail.com>
344 345
     Zlatko Mašek <zlatko.masek@gmail.com>
345 346
     Yasushi Masuda <whosaysni@gmail.com>
@@ -380,6 +381,7 @@ answer newbie questions, and generally made Django that much better:
380 381
     Neal Norwitz <nnorwitz@google.com>
381 382
     Todd O'Bryan <toddobryan@mac.com>
382 383
     Selwin Ong <selwin@ui.co.id>
  384
+    Gerardo Orozco <gerardo.orozco.mosqueda@gmail.com>
383 385
     Christian Oudard <christian.oudard@gmail.com>
384 386
     oggie rob <oz.robharvey@gmail.com>
385 387
     oggy <ognjen.maric@gmail.com>
215  django/contrib/localflavor/mx/forms.py
... ...
@@ -1,14 +1,225 @@
  1
+# -*- coding: utf-8 -*-
1 2
 """
2 3
 Mexican-specific form helpers.
3 4
 """
  5
+import re
4 6
 
5  
-from django.forms.fields import Select
  7
+from django.forms import ValidationError
  8
+from django.forms.fields import Select, RegexField
  9
+from django.utils.translation import ugettext_lazy as _
  10
+from django.core.validators import EMPTY_VALUES
  11
+from django.contrib.localflavor.mx.mx_states import STATE_CHOICES
  12
+
  13
+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))'
  14
+
  15
+"""
  16
+This is the list of inconvenient words according to the `Anexo IV` of the
  17
+document described in the next link:
  18
+    http://www.sisi.org.mx/jspsi/documentos/2005/seguimiento/06101/0610100162005_065.doc
  19
+"""
  20
+
  21
+RFC_INCONVENIENT_WORDS = [
  22
+    u'BUEI', u'BUEY', u'CACA', u'CACO', u'CAGA', u'CAGO', u'CAKA', u'CAKO',
  23
+    u'COGE', u'COJA', u'COJE', u'COJI', u'COJO', u'CULO', u'FETO', u'GUEY',
  24
+    u'JOTO', u'KACA', u'KACO', u'KAGA', u'KAGO', u'KOGE', u'KOJO', u'KAKA',
  25
+    u'KULO', u'MAME', u'MAMO', u'MEAR', u'MEAS', u'MEON', u'MION', u'MOCO',
  26
+    u'MULA', u'PEDA', u'PEDO', u'PENE', u'PUTA', u'PUTO', u'QULO', u'RATA',
  27
+    u'RUIN',
  28
+]
  29
+
  30
+"""
  31
+This is the list of inconvenient words according to the `Anexo 2` of the
  32
+document described in the next link:
  33
+    http://portal.veracruz.gob.mx/pls/portal/url/ITEM/444112558A57C6E0E040A8C02E00695C
  34
+"""
  35
+CURP_INCONVENIENT_WORDS = [
  36
+   u'BACA', u'BAKA', u'BUEI', u'BUEY', u'CACA', u'CACO', u'CAGA', u'CAGO',
  37
+   u'CAKA', u'CAKO', u'COGE', u'COGI', u'COJA', u'COJE', u'COJI', u'COJO',
  38
+   u'COLA', u'CULO', u'FALO', u'FETO', u'GETA', u'GUEI', u'GUEY', u'JETA',
  39
+   u'JOTO', u'KACA', u'KACO', u'KAGA', u'KAGO', u'KAKA', u'KAKO', u'KOGE',
  40
+   u'KOGI', u'KOJA', u'KOJE', u'KOJI', u'KOJO', u'KOLA', u'KULO', u'LILO',
  41
+   u'LOCA', u'LOCO', u'LOKA', u'LOKO', u'MAME', u'MAMO', u'MEAR', u'MEAS',
  42
+   u'MEON', u'MIAR', u'MION', u'MOCO', u'MOKO', u'MULA', u'MULO', u'NACA',
  43
+   u'NACO', u'PEDA', u'PEDO', u'PENE', u'PIPI', u'PITO', u'POPO', u'PUTA',
  44
+   u'PUTO', u'QULO', u'RATA', u'ROBA', u'ROBE', u'ROBO', u'RUIN', u'SENO',
  45
+   u'TETA', u'VACA', u'VAGA', u'VAGO', u'VAKA', u'VUEI', u'VUEY', u'WUEI',
  46
+   u'WUEY',
  47
+]
6 48
 
7 49
 class MXStateSelect(Select):
8 50
     """
9 51
     A Select widget that uses a list of Mexican states as its choices.
10 52
     """
11 53
     def __init__(self, attrs=None):
12  
-        from mx_states import STATE_CHOICES
13 54
         super(MXStateSelect, self).__init__(attrs, choices=STATE_CHOICES)
14 55
 
  56
+
  57
+class MXZipCodeField(RegexField):
  58
+    """
  59
+    A form field that accepts a Mexican Zip Code.
  60
+
  61
+    More info about this:
  62
+        http://en.wikipedia.org/wiki/List_of_postal_codes_in_Mexico
  63
+    """
  64
+    default_error_messages = {
  65
+        'invalid': _(u'Enter a valid zip code in the format XXXXX.'),
  66
+    }
  67
+
  68
+    def __init__(self, *args, **kwargs):
  69
+        zip_code_re = ur'^(0[1-9]|[1][0-6]|[2-9]\d)(\d{3})$'
  70
+        super(MXZipCodeField, self).__init__(zip_code_re, *args, **kwargs)
  71
+
  72
+
  73
+class MXRFCField(RegexField):
  74
+    """
  75
+    A form field that validates a Mexican *Registro Federal de Contribuyentes*
  76
+    for either `Persona física` or `Persona moral`.
  77
+
  78
+    The Persona física RFC string is integrated by a juxtaposition of
  79
+    characters following the next pattern:
  80
+
  81
+        =====  ======  ===========================================
  82
+        Index  Format  Accepted Characters
  83
+        =====  ======  ===========================================
  84
+        1      X       Any letter
  85
+        2      X       Any vowel
  86
+        3-4    XX      Any letter
  87
+        5-10   YYMMDD  Any valid date
  88
+        11-12  XX      Any letter or number between 0 and 9
  89
+        13     X       Any digit between 0 and 9 or the letter *A*
  90
+        =====  ======  ===========================================
  91
+
  92
+    The Persona moral RFC string is integrated by a juxtaposition of
  93
+    characters following the next pattern:
  94
+
  95
+        =====  ======  ============================================
  96
+        Index  Format  Accepted Characters
  97
+        =====  ======  ============================================
  98
+        1-3    XXX     Any letter including *&* and *Ñ* chars
  99
+        4-9    YYMMDD  Any valid date
  100
+        10-11  XX      Any letter or number between 0 and 9
  101
+        12     X       Any number between 0 and 9 or the letter *A*
  102
+        =====  ======  ============================================
  103
+
  104
+    More info about this:
  105
+        http://es.wikipedia.org/wiki/Registro_Federal_de_Contribuyentes_(M%C3%A9xico)
  106
+    """
  107
+    default_error_messages = {
  108
+        'invalid': _('Enter a valid RFC.'),
  109
+        'invalid_checksum': _('Invalid checksum for RFC.'),
  110
+    }
  111
+
  112
+    def __init__(self, min_length=9, max_length=13, *args, **kwargs):
  113
+        rfc_re = re.compile(ur'^([A-Z&Ññ]{3}|[A-Z][AEIOU][A-Z]{2})%s([A-Z0-9]{2}[0-9A])?$' % DATE_RE,
  114
+                            re.IGNORECASE)
  115
+        super(MXRFCField, self).__init__(rfc_re, min_length=min_length,
  116
+                                         max_length=max_length, *args, **kwargs)
  117
+
  118
+    def clean(self, value):
  119
+        value = super(MXRFCField, self).clean(value)
  120
+        if value in EMPTY_VALUES:
  121
+            return u''
  122
+        value = value.upper()
  123
+        if self._has_homoclave(value):
  124
+            if not value[-1] == self._checksum(value[:-1]):
  125
+                raise ValidationError(self.default_error_messages['invalid_checksum'])
  126
+        if self._has_inconvenient_word(value):
  127
+            raise ValidationError(self.default_error_messages['invalid'])
  128
+        return value
  129
+
  130
+    def _has_homoclave(self, rfc):
  131
+        """
  132
+        This check is done due to the existance of RFCs without a *homoclave*
  133
+        since the current algorithm to calculate it had not been created for
  134
+        the first RFCs ever in Mexico.
  135
+        """
  136
+        rfc_without_homoclave_re = re.compile(ur'^[A-Z&Ññ]{3,4}%s$' % DATE_RE,
  137
+                                              re.IGNORECASE)
  138
+        return not rfc_without_homoclave_re.match(rfc)
  139
+
  140
+    def _checksum(self, rfc):
  141
+        """
  142
+        More info about this procedure:
  143
+            www.sisi.org.mx/jspsi/documentos/2005/seguimiento/06101/0610100162005_065.doc
  144
+        """
  145
+        chars = u'0123456789ABCDEFGHIJKLMN&OPQRSTUVWXYZ-Ñ'
  146
+        if len(rfc) is 11:
  147
+            rfc = '-' + rfc
  148
+
  149
+        sum_ = sum(i * chars.index(c) for i, c in zip(reversed(xrange(14)), rfc))
  150
+        checksum = 11 - sum_ % 11
  151
+
  152
+        if checksum == 10:
  153
+            return u'A'
  154
+        elif checksum == 11:
  155
+            return u'0'
  156
+
  157
+        return unicode(checksum)
  158
+
  159
+    def _has_inconvenient_word(self, rfc):
  160
+        first_four = rfc[:4]
  161
+        return first_four in RFC_INCONVENIENT_WORDS
  162
+
  163
+
  164
+class MXCURPField(RegexField):
  165
+    """
  166
+    A field that validates a Mexican Clave Única de Registro de Población.
  167
+
  168
+    The CURP is integrated by a juxtaposition of characters following the next
  169
+    pattern:
  170
+
  171
+        =====  ======  ===================================================
  172
+        Index  Format  Accepted Characters
  173
+        =====  ======  ===================================================
  174
+        1      X       Any letter
  175
+        2      X       Any vowel
  176
+        3-4    XX      Any letter
  177
+        5-10   YYMMDD  Any valid date
  178
+        11     X       Either `H` or `M`, depending on the person's gender
  179
+        12-13  XX      Any valid acronym for a state in Mexico
  180
+        14-16  XXX     Any consonant
  181
+        17     X       Any number between 0 and 9 or any letter
  182
+        18     X       Any number between 0 and 9
  183
+        =====  ======  ===================================================
  184
+
  185
+    More info about this:
  186
+        http://www.condusef.gob.mx/index.php/clave-unica-de-registro-de-poblacion-curp
  187
+    """
  188
+    default_error_messages = {
  189
+        'invalid': _('Enter a valid CURP.'),
  190
+        'invalid_checksum': _(u'Invalid checksum for CURP.'),
  191
+    }
  192
+
  193
+    def __init__(self, min_length=18, max_length=18, *args, **kwargs):
  194
+        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)'
  195
+        consonants_re = r'[B-DF-HJ-NP-TV-Z]'
  196
+        curp_re = (ur'^[A-Z][AEIOU][A-Z]{2}%s[HM]%s%s{3}[0-9A-Z]\d$' %
  197
+                   (DATE_RE, states_re, consonants_re))
  198
+        curp_re = re.compile(curp_re, re.IGNORECASE)
  199
+        super(MXCURPField, self).__init__(curp_re, min_length=min_length,
  200
+                                          max_length=max_length, *args, **kwargs)
  201
+
  202
+    def clean(self, value):
  203
+        value = super(MXCURPField, self).clean(value)
  204
+        if value in EMPTY_VALUES:
  205
+            return u''
  206
+        value = value.upper()
  207
+        if value[-1] != self._checksum(value[:-1]):
  208
+            raise ValidationError(self.default_error_messages['invalid_checksum'])
  209
+        if self._has_inconvenient_word(value):
  210
+            raise ValidationError(self.default_error_messages['invalid'])
  211
+        return value
  212
+
  213
+    def _checksum(self, value):
  214
+        chars = u'0123456789ABCDEFGHIJKLMN&OPQRSTUVWXYZ'
  215
+
  216
+        s = sum(i * chars.index(c) for i, c in zip(reversed(xrange(19)), value))
  217
+        checksum = 10 - s % 10
  218
+
  219
+        if checksum == 10:
  220
+            return u'0'
  221
+        return unicode(checksum)
  222
+
  223
+    def _has_inconvenient_word(self, curp):
  224
+        first_four = curp[:4]
  225
+        return first_four in CURP_INCONVENIENT_WORDS
70  django/contrib/localflavor/mx/models.py
... ...
@@ -0,0 +1,70 @@
  1
+from django.utils.translation import ugettext_lazy as _
  2
+from django.db.models.fields import CharField
  3
+
  4
+from django.contrib.localflavor.mx.mx_states import STATE_CHOICES
  5
+from django.contrib.localflavor.mx.forms import (MXRFCField as MXRFCFormField,
  6
+    MXZipCodeField as MXZipCodeFormField, MXCURPField as MXCURPFormField)
  7
+
  8
+
  9
+class MXStateField(CharField):
  10
+    """
  11
+    A model field that stores the three-letter Mexican state abbreviation in the
  12
+    database.
  13
+    """
  14
+    description = _("Mexico state (three uppercase letters)")
  15
+
  16
+    def __init__(self, *args, **kwargs):
  17
+        kwargs['choices'] = STATE_CHOICES
  18
+        kwargs['max_length'] = 3
  19
+        super(MXStateField, self).__init__(*args, **kwargs)
  20
+
  21
+
  22
+class MXZipCodeField(CharField):
  23
+    """
  24
+    A model field that forms represent as a forms.MXZipCodeField field and
  25
+    stores the five-digit Mexican zip code.
  26
+    """
  27
+    description = _("Mexico zip code")
  28
+
  29
+    def __init__(self, *args, **kwargs):
  30
+        kwargs['max_length'] = 5
  31
+        super(MXZipCodeField, self).__init__(*args, **kwargs)
  32
+
  33
+    def formfield(self, **kwargs):
  34
+        defaults = {'form_class': MXZipCodeFormField}
  35
+        defaults.update(kwargs)
  36
+        return super(MXZipCodeField, self).formfield(**defaults)
  37
+
  38
+
  39
+class MXRFCField(CharField):
  40
+    """
  41
+    A model field that forms represent as a forms.MXRFCField field and
  42
+    stores the value of a valid Mexican RFC.
  43
+    """
  44
+    description = _("Mexican RFC")
  45
+
  46
+    def __init__(self, *args, **kwargs):
  47
+        kwargs['max_length'] = 13
  48
+        super(MXRFCField, self).__init__(*args, **kwargs)
  49
+
  50
+    def formfield(self, **kwargs):
  51
+        defaults = {'form_class': MXRFCFormField}
  52
+        defaults.update(kwargs)
  53
+        return super(MXRFCField, self).formfield(**defaults)
  54
+
  55
+
  56
+class MXCURPField(CharField):
  57
+    """
  58
+    A model field that forms represent as a forms.MXCURPField field and
  59
+    stores the value of a valid Mexican CURP.
  60
+    """
  61
+    description = _("Mexican CURP")
  62
+
  63
+    def __init__(self, *args, **kwargs):
  64
+        kwargs['max_length'] = 18
  65
+        super(MXCURPField, self).__init__(*args, **kwargs)
  66
+
  67
+    def formfield(self, **kwargs):
  68
+        defaults = {'form_class': MXCURPFormField}
  69
+        defaults.update(kwargs)
  70
+        return super(MXCURPField, self).formfield(**defaults)
2  django/contrib/localflavor/mx/mx_states.py
@@ -8,6 +8,7 @@
8 8
 
9 9
 from django.utils.translation import ugettext_lazy as _
10 10
 
  11
+# All 31 states, plus the `Distrito Federal`.
11 12
 STATE_CHOICES = (
12 13
     ('AGU', _(u'Aguascalientes')),
13 14
     ('BCN', _(u'Baja California')),
@@ -42,4 +43,3 @@
42 43
     ('YUC', _(u'Yucatán')),
43 44
     ('ZAC', _(u'Zacatecas')),
44 45
 )
45  
-
69  docs/ref/contrib/localflavor.txt
@@ -798,10 +798,79 @@ Macedonia (``mk``)
798 798
 Mexico (``mx``)
799 799
 ===============
800 800
 
  801
+.. class:: mx.forms.MXZipCodeField
  802
+
  803
+    .. versionadded:: 1.4
  804
+
  805
+    A form field that accepts a Mexican Zip Code.
  806
+
  807
+    More info about this: List of postal codes in Mexico (zipcodes_)
  808
+
  809
+.. _zipcodes: http://en.wikipedia.org/wiki/List_of_postal_codes_in_Mexico
  810
+
  811
+.. class:: mx.forms.MXRFCField
  812
+
  813
+    .. versionadded:: 1.4
  814
+
  815
+    A form field that validates a Mexican *Registro Federal de Contribuyentes* for
  816
+    either **Persona física** or **Persona moral**. This field accepts RFC strings
  817
+    whether or not it contains a *homoclave*.
  818
+
  819
+    More info about this: Registro Federal de Contribuyentes (rfc_)
  820
+
  821
+.. _rfc: http://es.wikipedia.org/wiki/Registro_Federal_de_Contribuyentes_(M%C3%A9xico)
  822
+
  823
+.. class:: mx.forms.MXCURPField
  824
+
  825
+    .. versionadded:: 1.4
  826
+
  827
+   A field that validates a Mexican *Clave Única de Registro de Población*.
  828
+
  829
+   More info about this: Clave Unica de Registro de Poblacion (curp_)
  830
+
  831
+.. _curp: http://www.condusef.gob.mx/index.php/clave-unica-de-registro-de-poblacion-curp
  832
+
801 833
 .. class:: mx.forms.MXStateSelect
802 834
 
803 835
     A ``Select`` widget that uses a list of Mexican states as its choices.
804 836
 
  837
+.. class:: mx.models.MXStateField
  838
+
  839
+    .. versionadded:: 1.4
  840
+
  841
+    A model field that stores the three-letter Mexican state abbreviation in the
  842
+    database.
  843
+
  844
+.. class:: mx.models.MXZipCodeField
  845
+
  846
+    .. versionadded:: 1.4
  847
+
  848
+    A model field that forms represent as a ``forms.MXZipCodeField`` field and
  849
+    stores the five-digit Mexican zip code.
  850
+
  851
+.. class:: mx.models.MXRFCField
  852
+
  853
+    .. versionadded:: 1.4
  854
+
  855
+    A model field that forms represent as a ``forms.MXRFCField`` field and
  856
+    stores the value of a valid Mexican RFC.
  857
+
  858
+.. class:: mx.models.MXCURPField
  859
+
  860
+    .. versionadded:: 1.4
  861
+
  862
+    A model field that forms represent as a ``forms.MXCURPField`` field and
  863
+    stores the value of a valid Mexican CURP.
  864
+
  865
+Additionally, a choice tuple is provided in ``django.contrib.localflavor.mx.mx_states``,
  866
+allowing customized model and form fields, and form presentations, for subsets of
  867
+Mexican states abbreviations:
  868
+
  869
+.. data:: mx.mx_states.STATE_CHOICES
  870
+
  871
+    A tuple of choices of the states abbreviations for all 31 Mexican states,
  872
+    plus the `Distrito Federal`.
  873
+
805 874
 Norway (``no``)
806 875
 ===============
807 876
 
127  tests/regressiontests/forms/localflavor/mx.py
... ...
@@ -0,0 +1,127 @@
  1
+# -*- coding: utf-8 -*-
  2
+from django.contrib.localflavor.mx.forms import (MXZipCodeField, MXRFCField,
  3
+    MXStateSelect, MXCURPField)
  4
+
  5
+from utils import LocalFlavorTestCase
  6
+
  7
+
  8
+class MXLocalFlavorTests(LocalFlavorTestCase):
  9
+    def test_MXStateSelect(self):
  10
+        f = MXStateSelect()
  11
+        out = u'''<select name="state">
  12
+<option value="AGU">Aguascalientes</option>
  13
+<option value="BCN">Baja California</option>
  14
+<option value="BCS">Baja California Sur</option>
  15
+<option value="CAM">Campeche</option>
  16
+<option value="CHH">Chihuahua</option>
  17
+<option value="CHP">Chiapas</option>
  18
+<option value="COA">Coahuila</option>
  19
+<option value="COL">Colima</option>
  20
+<option value="DIF">Distrito Federal</option>
  21
+<option value="DUR">Durango</option>
  22
+<option value="GRO">Guerrero</option>
  23
+<option value="GUA">Guanajuato</option>
  24
+<option value="HID">Hidalgo</option>
  25
+<option value="JAL">Jalisco</option>
  26
+<option value="MEX">Estado de México</option>
  27
+<option value="MIC" selected="selected">Michoacán</option>
  28
+<option value="MOR">Morelos</option>
  29
+<option value="NAY">Nayarit</option>
  30
+<option value="NLE">Nuevo León</option>
  31
+<option value="OAX">Oaxaca</option>
  32
+<option value="PUE">Puebla</option>
  33
+<option value="QUE">Querétaro</option>
  34
+<option value="ROO">Quintana Roo</option>
  35
+<option value="SIN">Sinaloa</option>
  36
+<option value="SLP">San Luis Potosí</option>
  37
+<option value="SON">Sonora</option>
  38
+<option value="TAB">Tabasco</option>
  39
+<option value="TAM">Tamaulipas</option>
  40
+<option value="TLA">Tlaxcala</option>
  41
+<option value="VER">Veracruz</option>
  42
+<option value="YUC">Yucatán</option>
  43
+<option value="ZAC">Zacatecas</option>
  44
+</select>'''
  45
+        self.assertEqual(f.render('state', 'MIC'), out)
  46
+
  47
+    def test_MXZipCodeField(self):
  48
+        error_format = [u'Enter a valid zip code in the format XXXXX.']
  49
+        valid = {
  50
+            '58120': u'58120',
  51
+            '58502': u'58502',
  52
+            '59310': u'59310',
  53
+            '99999': u'99999',
  54
+        }
  55
+        invalid = {
  56
+            '17000': error_format,
  57
+            '18000': error_format,
  58
+            '19000': error_format,
  59
+            '00000': error_format,
  60
+        }
  61
+        self.assertFieldOutput(MXZipCodeField, valid, invalid)
  62
+
  63
+    def test_MXRFCField(self):
  64
+        error_format = [u'Enter a valid RFC.']
  65
+        error_checksum = [u'Invalid checksum for RFC.']
  66
+        valid = {
  67
+            'MoFN641205eX5': u'MOFN641205EX5',
  68
+            'ICa060120873': u'ICA060120873',
  69
+            'eUcG751104rT0': u'EUCG751104RT0',
  70
+            'GME08100195A': u'GME08100195A',
  71
+            'AA&060524KX5': u'AA&060524KX5',
  72
+            'CAÑ0708045P7': u'CAÑ0708045P7',
  73
+            'aaa000101aa9': u'AAA000101AA9',
  74
+        }
  75
+        invalid = {
  76
+            'MED0000000XA': error_format,
  77
+            '0000000000XA': error_format,
  78
+            'AAA000000AA6': error_format,
  79
+            # Dates
  80
+            'XXX880002XXX': error_format,
  81
+            'XXX880200XXX': error_format,
  82
+            'XXX880132XXX': error_format,
  83
+            'XXX880230XXX': error_format,
  84
+            'XXX880431XXX': error_format,
  85
+            # Incorrect checksum
  86
+            'MOGR650524E73': error_checksum,
  87
+            'HVA7810058F1': error_checksum,
  88
+            'MoFN641205eX2': error_checksum,
  89
+            'ICa060120871': error_checksum,
  90
+            'eUcG751104rT7': error_checksum,
  91
+            'GME081001955': error_checksum,
  92
+            'AA&060524KX9': error_checksum,
  93
+            'CAÑ0708045P2': error_checksum,
  94
+        }
  95
+        self.assertFieldOutput(MXRFCField, valid, invalid)
  96
+
  97
+    def test_MXCURPField(self):
  98
+        error_format = [u'Enter a valid CURP.']
  99
+        error_checksum = [u'Invalid checksum for CURP.']
  100
+        valid = {
  101
+            'AaMG890608HDFLJL00': u'AAMG890608HDFLJL00',
  102
+            'BAAd890419HMNRRV07': u'BAAD890419HMNRRV07',
  103
+            'VIAA900930MMNClL08': u'VIAA900930MMNCLL08',
  104
+            'HEGR891009HMNRRD09': u'HEGR891009HMNRRD09',
  105
+            'MARR890512HMNRMN09': u'MARR890512HMNRMN09',
  106
+            'MESJ890928HMNZNS00': u'MESJ890928HMNZNS00',
  107
+            'BAAA890317HDFRLL03': u'BAAA890317HDFRLL03',
  108
+            'TOMA880125HMNRRNO2': u'TOMA880125HMNRRNO2',
  109
+            'OOMG890727HMNRSR06': u'OOMG890727HMNRSR06',
  110
+            'AAAA000101HDFCCC09': u'AAAA000101HDFCCC09',
  111
+        }
  112
+        invalid = {
  113
+            'AAAA000000HDFCCC09': error_format,
  114
+            'AAAA000000HDFAAA03': error_format,
  115
+            'AAAA000000HXXCCC08': error_format,
  116
+            'AAAA000000XMNCCC02': error_format,
  117
+            'HEGR891009HMNRRD0A': error_format,
  118
+            'MARR890512HMNRMN0A': error_format,
  119
+            'AaMG890608HDFLJL01': error_checksum,
  120
+            'BAAd890419HMNRRV08': error_checksum,
  121
+            'VIAA900930MMNClL09': error_checksum,
  122
+            'MESJ890928HMNZNS01': error_checksum,
  123
+            'BAAA890317HDFRLL04': error_checksum,
  124
+            'TOMA880125HMNRRNO3': error_checksum,
  125
+            'OOMG890727HMNRSR07': error_checksum,
  126
+        }
  127
+        self.assertFieldOutput(MXCURPField, valid, invalid)
1  tests/regressiontests/forms/localflavortests.py
@@ -27,6 +27,7 @@
27 27
 from localflavor.jp import JPLocalFlavorTests
28 28
 from localflavor.kw import KWLocalFlavorTests
29 29
 from localflavor.mk import MKLocalFlavorTests
  30
+from localflavor.mx import MXLocalFlavorTests
30 31
 from localflavor.nl import NLLocalFlavorTests
31 32
 from localflavor.pl import PLLocalFlavorTests
32 33
 from localflavor.pt import PTLocalFlavorTests
1  tests/regressiontests/forms/tests/__init__.py
@@ -40,6 +40,7 @@
40 40
     JPLocalFlavorTests,
41 41
     KWLocalFlavorTests,
42 42
     MKLocalFlavorTests,
  43
+    MXLocalFlavorTests,
43 44
     NLLocalFlavorTests,
44 45
     PLLocalFlavorTests,
45 46
     PTLocalFlavorTests,
2  tests/regressiontests/localflavor/mk/models.py
@@ -8,7 +8,7 @@ class MKPerson(models.Model):
8 8
     umcn = UMCNField()
9 9
     id_number = MKIdentityCardNumberField()
10 10
     municipality  = MKMunicipalityField(blank = True)
11  
-    municipality_req = MKMunicipalityField(blank = False) 
  11
+    municipality_req = MKMunicipalityField(blank = False)
12 12
 
13 13
     class Meta:
14 14
         app_label = 'localflavor'
0  tests/regressiontests/localflavor/mx/__init__.py
No changes.
7  tests/regressiontests/localflavor/mx/forms.py
... ...
@@ -0,0 +1,7 @@
  1
+from django.forms import ModelForm
  2
+from models import MXPersonProfile
  3
+
  4
+class MXPersonProfileForm(ModelForm):
  5
+
  6
+    class Meta:
  7
+        model = MXPersonProfile
12  tests/regressiontests/localflavor/mx/models.py
... ...
@@ -0,0 +1,12 @@
  1
+from django.db import models
  2
+from django.contrib.localflavor.mx.models import (
  3
+    MXStateField, MXRFCField, MXCURPField, MXZipCodeField)
  4
+
  5
+class MXPersonProfile(models.Model):
  6
+    state = MXStateField()
  7
+    rfc = MXRFCField()
  8
+    curp = MXCURPField()
  9
+    zip_code = MXZipCodeField()
  10
+
  11
+    class Meta:
  12
+        app_label = 'localflavor'
71  tests/regressiontests/localflavor/mx/tests.py
... ...
@@ -0,0 +1,71 @@
  1
+# -*- coding: utf-8 -*-
  2
+from django.test import TestCase
  3
+from forms import MXPersonProfileForm
  4
+
  5
+class MXLocalFlavorTests(TestCase):
  6
+    def setUp(self):
  7
+        self.form = MXPersonProfileForm({
  8
+            'state': 'MIC',
  9
+            'rfc': 'toma880125kv3',
  10
+            'curp': 'toma880125hmnrrn02',
  11
+            'zip_code': '58120',
  12
+        })
  13
+
  14
+    def test_get_display_methods(self):
  15
+        """Test that the get_*_display() methods are added to the model instances."""
  16
+        place = self.form.save()
  17
+        self.assertEqual(place.get_state_display(), u'Michoacán')
  18
+
  19
+    def test_errors(self):
  20
+        """Test that required MXFields throw appropriate errors."""
  21
+        form = MXPersonProfileForm({
  22
+            'state': 'Invalid state',
  23
+            'rfc': 'invalid rfc',
  24
+            'curp': 'invalid curp',
  25
+            'zip_code': 'xxx',
  26
+        })
  27
+        self.assertFalse(form.is_valid())
  28
+        self.assertEqual(form.errors['state'], [u'Select a valid choice. Invalid state is not one of the available choices.'])
  29
+        self.assertEqual(form.errors['rfc'], [u'Enter a valid RFC.'])
  30
+        self.assertEqual(form.errors['curp'], [u'Ensure this value has at least 18 characters (it has 12).', u'Enter a valid CURP.'])
  31
+        self.assertEqual(form.errors['zip_code'], [u'Enter a valid zip code in the format XXXXX.'])
  32
+
  33
+    def test_field_blank_option(self):
  34
+        """Test that the empty option is there."""
  35
+        state_select_html = """\
  36
+<select name="state" id="id_state">
  37
+<option value="">---------</option>
  38
+<option value="AGU">Aguascalientes</option>
  39
+<option value="BCN">Baja California</option>
  40
+<option value="BCS">Baja California Sur</option>
  41
+<option value="CAM">Campeche</option>
  42
+<option value="CHH">Chihuahua</option>
  43
+<option value="CHP">Chiapas</option>
  44
+<option value="COA">Coahuila</option>
  45
+<option value="COL">Colima</option>
  46
+<option value="DIF">Distrito Federal</option>
  47
+<option value="DUR">Durango</option>
  48
+<option value="GRO">Guerrero</option>
  49
+<option value="GUA">Guanajuato</option>
  50
+<option value="HID">Hidalgo</option>
  51
+<option value="JAL">Jalisco</option>
  52
+<option value="MEX">Estado de México</option>
  53
+<option value="MIC" selected="selected">Michoacán</option>
  54
+<option value="MOR">Morelos</option>
  55
+<option value="NAY">Nayarit</option>
  56
+<option value="NLE">Nuevo León</option>
  57
+<option value="OAX">Oaxaca</option>
  58
+<option value="PUE">Puebla</option>
  59
+<option value="QUE">Querétaro</option>
  60
+<option value="ROO">Quintana Roo</option>
  61
+<option value="SIN">Sinaloa</option>
  62
+<option value="SLP">San Luis Potosí</option>
  63
+<option value="SON">Sonora</option>
  64
+<option value="TAB">Tabasco</option>
  65
+<option value="TAM">Tamaulipas</option>
  66
+<option value="TLA">Tlaxcala</option>
  67
+<option value="VER">Veracruz</option>
  68
+<option value="YUC">Yucatán</option>
  69
+<option value="ZAC">Zacatecas</option>
  70
+</select>"""
  71
+        self.assertEqual(str(self.form['state']), state_select_html)
1  tests/regressiontests/localflavor/tests.py
... ...
@@ -1,4 +1,5 @@
1 1
 from au.tests import *
2 2
 from mk.tests import *
  3
+from mx.tests import *
3 4
 from us.tests import *
4 5
 
2  tests/regressiontests/localflavor/us/forms.py
@@ -2,6 +2,6 @@
2 2
 from models import USPlace
3 3
 
4 4
 class USPlaceForm(ModelForm):
5  
-    """docstring for PlaceForm"""
  5
+
6 6
     class Meta:
7 7
         model = USPlace
1  tests/regressiontests/localflavor/us/models.py
@@ -11,5 +11,6 @@ class USPlace(models.Model):
11 11
     state_default = USStateField(default="CA", blank=True)
12 12
     postal_code = USPostalCodeField(blank=True)
13 13
     name = models.CharField(max_length=20)
  14
+
14 15
     class Meta:
15 16
         app_label = 'localflavor'

0 notes on commit 0fbadfd

Please sign in to comment.
Something went wrong with that request. Please try again.