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
This commit is contained in:
Jannis Leidel 2011-07-29 10:20:16 +00:00
parent 4a6e1b5613
commit 0fbadfd1c8
16 changed files with 578 additions and 5 deletions

View File

@ -340,6 +340,7 @@ answer newbie questions, and generally made Django that much better:
Nuno Mariz <nmariz@gmail.com>
mark@junklight.com
Orestis Markou <orestis@orestis.gr>
Andrés Torres Marroquín <andres.torres.marroquin@gmail.com>
Takashi Matsuo <matsuo.takashi@gmail.com>
Zlatko Mašek <zlatko.masek@gmail.com>
Yasushi Masuda <whosaysni@gmail.com>
@ -380,6 +381,7 @@ answer newbie questions, and generally made Django that much better:
Neal Norwitz <nnorwitz@google.com>
Todd O'Bryan <toddobryan@mac.com>
Selwin Ong <selwin@ui.co.id>
Gerardo Orozco <gerardo.orozco.mosqueda@gmail.com>
Christian Oudard <christian.oudard@gmail.com>
oggie rob <oz.robharvey@gmail.com>
oggy <ognjen.maric@gmail.com>

View File

@ -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

View File

@ -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)

View File

@ -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')),
)

View File

@ -798,10 +798,79 @@ Macedonia (``mk``)
Mexico (``mx``)
===============
.. class:: mx.forms.MXZipCodeField
.. versionadded:: 1.4
A form field that accepts a Mexican Zip Code.
More info about this: List of postal codes in Mexico (zipcodes_)
.. _zipcodes: http://en.wikipedia.org/wiki/List_of_postal_codes_in_Mexico
.. class:: mx.forms.MXRFCField
.. versionadded:: 1.4
A form field that validates a Mexican *Registro Federal de Contribuyentes* for
either **Persona física** or **Persona moral**. This field accepts RFC strings
whether or not it contains a *homoclave*.
More info about this: Registro Federal de Contribuyentes (rfc_)
.. _rfc: http://es.wikipedia.org/wiki/Registro_Federal_de_Contribuyentes_(M%C3%A9xico)
.. class:: mx.forms.MXCURPField
.. versionadded:: 1.4
A field that validates a Mexican *Clave Única de Registro de Población*.
More info about this: Clave Unica de Registro de Poblacion (curp_)
.. _curp: http://www.condusef.gob.mx/index.php/clave-unica-de-registro-de-poblacion-curp
.. class:: mx.forms.MXStateSelect
A ``Select`` widget that uses a list of Mexican states as its choices.
.. class:: mx.models.MXStateField
.. versionadded:: 1.4
A model field that stores the three-letter Mexican state abbreviation in the
database.
.. class:: mx.models.MXZipCodeField
.. versionadded:: 1.4
A model field that forms represent as a ``forms.MXZipCodeField`` field and
stores the five-digit Mexican zip code.
.. class:: mx.models.MXRFCField
.. versionadded:: 1.4
A model field that forms represent as a ``forms.MXRFCField`` field and
stores the value of a valid Mexican RFC.
.. class:: mx.models.MXCURPField
.. versionadded:: 1.4
A model field that forms represent as a ``forms.MXCURPField`` field and
stores the value of a valid Mexican CURP.
Additionally, a choice tuple is provided in ``django.contrib.localflavor.mx.mx_states``,
allowing customized model and form fields, and form presentations, for subsets of
Mexican states abbreviations:
.. data:: mx.mx_states.STATE_CHOICES
A tuple of choices of the states abbreviations for all 31 Mexican states,
plus the `Distrito Federal`.
Norway (``no``)
===============

View File

@ -0,0 +1,127 @@
# -*- coding: utf-8 -*-
from django.contrib.localflavor.mx.forms import (MXZipCodeField, MXRFCField,
MXStateSelect, MXCURPField)
from utils import LocalFlavorTestCase
class MXLocalFlavorTests(LocalFlavorTestCase):
def test_MXStateSelect(self):
f = MXStateSelect()
out = u'''<select name="state">
<option value="AGU">Aguascalientes</option>
<option value="BCN">Baja California</option>
<option value="BCS">Baja California Sur</option>
<option value="CAM">Campeche</option>
<option value="CHH">Chihuahua</option>
<option value="CHP">Chiapas</option>
<option value="COA">Coahuila</option>
<option value="COL">Colima</option>
<option value="DIF">Distrito Federal</option>
<option value="DUR">Durango</option>
<option value="GRO">Guerrero</option>
<option value="GUA">Guanajuato</option>
<option value="HID">Hidalgo</option>
<option value="JAL">Jalisco</option>
<option value="MEX">Estado de México</option>
<option value="MIC" selected="selected">Michoacán</option>
<option value="MOR">Morelos</option>
<option value="NAY">Nayarit</option>
<option value="NLE">Nuevo León</option>
<option value="OAX">Oaxaca</option>
<option value="PUE">Puebla</option>
<option value="QUE">Querétaro</option>
<option value="ROO">Quintana Roo</option>
<option value="SIN">Sinaloa</option>
<option value="SLP">San Luis Potosí</option>
<option value="SON">Sonora</option>
<option value="TAB">Tabasco</option>
<option value="TAM">Tamaulipas</option>
<option value="TLA">Tlaxcala</option>
<option value="VER">Veracruz</option>
<option value="YUC">Yucatán</option>
<option value="ZAC">Zacatecas</option>
</select>'''
self.assertEqual(f.render('state', 'MIC'), out)
def test_MXZipCodeField(self):
error_format = [u'Enter a valid zip code in the format XXXXX.']
valid = {
'58120': u'58120',
'58502': u'58502',
'59310': u'59310',
'99999': u'99999',
}
invalid = {
'17000': error_format,
'18000': error_format,
'19000': error_format,
'00000': error_format,
}
self.assertFieldOutput(MXZipCodeField, valid, invalid)
def test_MXRFCField(self):
error_format = [u'Enter a valid RFC.']
error_checksum = [u'Invalid checksum for RFC.']
valid = {
'MoFN641205eX5': u'MOFN641205EX5',
'ICa060120873': u'ICA060120873',
'eUcG751104rT0': u'EUCG751104RT0',
'GME08100195A': u'GME08100195A',
'AA&060524KX5': u'AA&060524KX5',
'CAÑ0708045P7': u'CAÑ0708045P7',
'aaa000101aa9': u'AAA000101AA9',
}
invalid = {
'MED0000000XA': error_format,
'0000000000XA': error_format,
'AAA000000AA6': error_format,
# Dates
'XXX880002XXX': error_format,
'XXX880200XXX': error_format,
'XXX880132XXX': error_format,
'XXX880230XXX': error_format,
'XXX880431XXX': error_format,
# Incorrect checksum
'MOGR650524E73': error_checksum,
'HVA7810058F1': error_checksum,
'MoFN641205eX2': error_checksum,
'ICa060120871': error_checksum,
'eUcG751104rT7': error_checksum,
'GME081001955': error_checksum,
'AA&060524KX9': error_checksum,
'CAÑ0708045P2': error_checksum,
}
self.assertFieldOutput(MXRFCField, valid, invalid)
def test_MXCURPField(self):
error_format = [u'Enter a valid CURP.']
error_checksum = [u'Invalid checksum for CURP.']
valid = {
'AaMG890608HDFLJL00': u'AAMG890608HDFLJL00',
'BAAd890419HMNRRV07': u'BAAD890419HMNRRV07',
'VIAA900930MMNClL08': u'VIAA900930MMNCLL08',
'HEGR891009HMNRRD09': u'HEGR891009HMNRRD09',
'MARR890512HMNRMN09': u'MARR890512HMNRMN09',
'MESJ890928HMNZNS00': u'MESJ890928HMNZNS00',
'BAAA890317HDFRLL03': u'BAAA890317HDFRLL03',
'TOMA880125HMNRRNO2': u'TOMA880125HMNRRNO2',
'OOMG890727HMNRSR06': u'OOMG890727HMNRSR06',
'AAAA000101HDFCCC09': u'AAAA000101HDFCCC09',
}
invalid = {
'AAAA000000HDFCCC09': error_format,
'AAAA000000HDFAAA03': error_format,
'AAAA000000HXXCCC08': error_format,
'AAAA000000XMNCCC02': error_format,
'HEGR891009HMNRRD0A': error_format,
'MARR890512HMNRMN0A': error_format,
'AaMG890608HDFLJL01': error_checksum,
'BAAd890419HMNRRV08': error_checksum,
'VIAA900930MMNClL09': error_checksum,
'MESJ890928HMNZNS01': error_checksum,
'BAAA890317HDFRLL04': error_checksum,
'TOMA880125HMNRRNO3': error_checksum,
'OOMG890727HMNRSR07': error_checksum,
}
self.assertFieldOutput(MXCURPField, valid, invalid)

View File

@ -27,6 +27,7 @@ from localflavor.it import ITLocalFlavorTests
from localflavor.jp import JPLocalFlavorTests
from localflavor.kw import KWLocalFlavorTests
from localflavor.mk import MKLocalFlavorTests
from localflavor.mx import MXLocalFlavorTests
from localflavor.nl import NLLocalFlavorTests
from localflavor.pl import PLLocalFlavorTests
from localflavor.pt import PTLocalFlavorTests

View File

@ -40,6 +40,7 @@ from regressiontests.forms.localflavortests import (
JPLocalFlavorTests,
KWLocalFlavorTests,
MKLocalFlavorTests,
MXLocalFlavorTests,
NLLocalFlavorTests,
PLLocalFlavorTests,
PTLocalFlavorTests,

View File

@ -8,7 +8,7 @@ class MKPerson(models.Model):
umcn = UMCNField()
id_number = MKIdentityCardNumberField()
municipality = MKMunicipalityField(blank = True)
municipality_req = MKMunicipalityField(blank = False)
municipality_req = MKMunicipalityField(blank = False)
class Meta:
app_label = 'localflavor'

View File

@ -0,0 +1,7 @@
from django.forms import ModelForm
from models import MXPersonProfile
class MXPersonProfileForm(ModelForm):
class Meta:
model = MXPersonProfile

View File

@ -0,0 +1,12 @@
from django.db import models
from django.contrib.localflavor.mx.models import (
MXStateField, MXRFCField, MXCURPField, MXZipCodeField)
class MXPersonProfile(models.Model):
state = MXStateField()
rfc = MXRFCField()
curp = MXCURPField()
zip_code = MXZipCodeField()
class Meta:
app_label = 'localflavor'

View File

@ -0,0 +1,71 @@
# -*- coding: utf-8 -*-
from django.test import TestCase
from forms import MXPersonProfileForm
class MXLocalFlavorTests(TestCase):
def setUp(self):
self.form = MXPersonProfileForm({
'state': 'MIC',
'rfc': 'toma880125kv3',
'curp': 'toma880125hmnrrn02',
'zip_code': '58120',
})
def test_get_display_methods(self):
"""Test that the get_*_display() methods are added to the model instances."""
place = self.form.save()
self.assertEqual(place.get_state_display(), u'Michoacán')
def test_errors(self):
"""Test that required MXFields throw appropriate errors."""
form = MXPersonProfileForm({
'state': 'Invalid state',
'rfc': 'invalid rfc',
'curp': 'invalid curp',
'zip_code': 'xxx',
})
self.assertFalse(form.is_valid())
self.assertEqual(form.errors['state'], [u'Select a valid choice. Invalid state is not one of the available choices.'])
self.assertEqual(form.errors['rfc'], [u'Enter a valid RFC.'])
self.assertEqual(form.errors['curp'], [u'Ensure this value has at least 18 characters (it has 12).', u'Enter a valid CURP.'])
self.assertEqual(form.errors['zip_code'], [u'Enter a valid zip code in the format XXXXX.'])
def test_field_blank_option(self):
"""Test that the empty option is there."""
state_select_html = """\
<select name="state" id="id_state">
<option value="">---------</option>
<option value="AGU">Aguascalientes</option>
<option value="BCN">Baja California</option>
<option value="BCS">Baja California Sur</option>
<option value="CAM">Campeche</option>
<option value="CHH">Chihuahua</option>
<option value="CHP">Chiapas</option>
<option value="COA">Coahuila</option>
<option value="COL">Colima</option>
<option value="DIF">Distrito Federal</option>
<option value="DUR">Durango</option>
<option value="GRO">Guerrero</option>
<option value="GUA">Guanajuato</option>
<option value="HID">Hidalgo</option>
<option value="JAL">Jalisco</option>
<option value="MEX">Estado de México</option>
<option value="MIC" selected="selected">Michoacán</option>
<option value="MOR">Morelos</option>
<option value="NAY">Nayarit</option>
<option value="NLE">Nuevo León</option>
<option value="OAX">Oaxaca</option>
<option value="PUE">Puebla</option>
<option value="QUE">Querétaro</option>
<option value="ROO">Quintana Roo</option>
<option value="SIN">Sinaloa</option>
<option value="SLP">San Luis Potosí</option>
<option value="SON">Sonora</option>
<option value="TAB">Tabasco</option>
<option value="TAM">Tamaulipas</option>
<option value="TLA">Tlaxcala</option>
<option value="VER">Veracruz</option>
<option value="YUC">Yucatán</option>
<option value="ZAC">Zacatecas</option>
</select>"""
self.assertEqual(str(self.form['state']), state_select_html)

View File

@ -1,4 +1,5 @@
from au.tests import *
from mk.tests import *
from mx.tests import *
from us.tests import *

View File

@ -2,6 +2,6 @@ from django.forms import ModelForm
from models import USPlace
class USPlaceForm(ModelForm):
"""docstring for PlaceForm"""
class Meta:
model = USPlace

View File

@ -11,5 +11,6 @@ class USPlace(models.Model):
state_default = USStateField(default="CA", blank=True)
postal_code = USPostalCodeField(blank=True)
name = models.CharField(max_length=20)
class Meta:
app_label = 'localflavor'