diff --git a/AUTHORS b/AUTHORS
index 31a3300..d3a1e38 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -540,6 +540,8 @@ answer newbie questions, and generally made Django that much better:
     Gasper Zejn <zejn@kiberpipa.org>
     Jarek Zgoda <jarek.zgoda@gmail.com>
     Cheng Zhang
+    Gerardo Orozco <gerardo.orozco.mosqueda@gmail.com>
+    Andrés Torres Marroquín <andres.torres.marroquin@gmail.com>
 
 A big THANK YOU goes to:
 
diff --git a/django/contrib/localflavor/mx/forms.py b/django/contrib/localflavor/mx/forms.py
index 9581937..ee1530b 100644
--- a/django/contrib/localflavor/mx/forms.py
+++ b/django/contrib/localflavor/mx/forms.py
@@ -1,14 +1,222 @@
+# -*- coding: utf-8 -*-
 """
 Mexican-specific form helpers.
 """
 
-from django.forms.fields import Select
+from django.forms import ValidationError
+from django.forms.fields import Select, RegexField, CharField
+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
+import re
+
+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': _(u'Enter a valid RFC.'),
+        'invalid_checksum': _(u'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'
+        else:
+            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 0000000..dd9a64b
--- /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
+
+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):
+        from django.contrib.localflavor.mx.forms import MXZipCodeField as Field
+        defaults = {'form_class': Field}
+        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):
+        from django.contrib.localflavor.mx.forms import MXRFCField as Field
+        defaults = {'form_class': Field}
+        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):
+        from django.contrib.localflavor.mx.forms import MXCURPField as Field
+        defaults = {'form_class': Field}
+        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 eed1700..8632888 100644
--- a/django/contrib/localflavor/mx/mx_states.py
+++ b/django/contrib/localflavor/mx/mx_states.py
@@ -8,7 +8,8 @@ when explicitly needed.
 
 from django.utils.translation import ugettext_lazy as _
 
-STATE_CHOICES = (
+# All 31 states, plus the `Distrito Federal`.
+MX_STATES = (
     ('AGU', _(u'Aguascalientes')),
     ('BCN', _(u'Baja California')),
     ('BCS', _(u'Baja California Sur')),
diff --git a/docs/ref/contrib/localflavor.txt b/docs/ref/contrib/localflavor.txt
index 60539e9..f4bc892 100644
--- a/docs/ref/contrib/localflavor.txt
+++ b/docs/ref/contrib/localflavor.txt
@@ -774,10 +774,65 @@ Macedonia (``mk``)
 Mexico (``mx``)
 ===============
 
+.. class:: mx.forms.MXZipCodeField
+
+    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
+
+    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
+
+   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
+
+    A model field that stores the three-letter Mexican state abbreviation in the
+    database.
+
+.. class:: mx.models.MXZipCodeField
+
+    A model field that forms represent as a ``forms.MXZipCodeField`` field and
+    stores the five-digit Mexican zip code.
+
+.. class:: mx.models.MXRFCField
+
+    A model field that forms represent as a ``forms.MXRFCField`` field and
+    stores the value of a valid Mexican RFC.
+
+.. class:: mx.models.MXCURPField
+
+    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.MX_STATES
+
+    A tuple of choices of the states abbreviations for all 31 Mexican states,
+    plus the `Distrito Federal`.
+
 Norway (``no``)
 ===============
 
diff --git a/tests/regressiontests/forms/localflavor/mx.py b/tests/regressiontests/forms/localflavor/mx.py
new file mode 100644
index 0000000..1ffc11c
--- /dev/null
+++ b/tests/regressiontests/forms/localflavor/mx.py
@@ -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)
diff --git a/tests/regressiontests/forms/localflavortests.py b/tests/regressiontests/forms/localflavortests.py
index 7f29fea..09afad8 100644
--- a/tests/regressiontests/forms/localflavortests.py
+++ b/tests/regressiontests/forms/localflavortests.py
@@ -25,6 +25,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
diff --git a/tests/regressiontests/forms/tests/__init__.py b/tests/regressiontests/forms/tests/__init__.py
index 6715451..fa947f9 100644
--- a/tests/regressiontests/forms/tests/__init__.py
+++ b/tests/regressiontests/forms/tests/__init__.py
@@ -38,6 +38,7 @@ from regressiontests.forms.localflavortests import (
     JPLocalFlavorTests,
     KWLocalFlavorTests,
     MKLocalFlavorTests,
+    MXLocalFlavorTests,
     NLLocalFlavorTests,
     PLLocalFlavorTests,
     PTLocalFlavorTests,
diff --git a/tests/regressiontests/localflavor/mx/__init__.py b/tests/regressiontests/localflavor/mx/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/tests/regressiontests/localflavor/mx/forms.py b/tests/regressiontests/localflavor/mx/forms.py
new file mode 100644
index 0000000..8a45781
--- /dev/null
+++ b/tests/regressiontests/localflavor/mx/forms.py
@@ -0,0 +1,7 @@
+from django.forms import ModelForm
+from models import MXPersonProfile
+
+class MXPersonProfileForm(ModelForm):
+    """docstring for MXPersonProfileForm"""
+    class Meta:
+        model = MXPersonProfile
diff --git a/tests/regressiontests/localflavor/mx/models.py b/tests/regressiontests/localflavor/mx/models.py
new file mode 100644
index 0000000..81a5018
--- /dev/null
+++ b/tests/regressiontests/localflavor/mx/models.py
@@ -0,0 +1,14 @@
+from django.db import models
+from django.contrib.localflavor.mx.models import (MXStateField,
+                        MXRFCField, MXCURPField, MXZipCodeField)
+
+# When creating models you need to remember to add a app_label as
+# 'localflavor', so your model can be found
+
+class MXPersonProfile(models.Model):
+    state = MXStateField()
+    rfc = MXRFCField()
+    curp = MXCURPField()
+    zip_code = MXZipCodeField()
+    class Meta:
+        app_label = 'localflavor'
diff --git a/tests/regressiontests/localflavor/mx/tests.py b/tests/regressiontests/localflavor/mx/tests.py
new file mode 100644
index 0000000..40da38a
--- /dev/null
+++ b/tests/regressiontests/localflavor/mx/tests.py
@@ -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)
diff --git a/tests/regressiontests/localflavor/tests.py b/tests/regressiontests/localflavor/tests.py
index 6a15d46..95a5f99 100644
--- a/tests/regressiontests/localflavor/tests.py
+++ b/tests/regressiontests/localflavor/tests.py
@@ -1,4 +1,5 @@
 from au.tests import *
 from mk.tests import *
+from mx.tests import *
 from us.tests import *
 
