Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding MXSocialSecurityNumberField to local flavor #42

Closed
wants to merge 8 commits into from
1 change: 1 addition & 0 deletions AUTHORS
Expand Up @@ -43,6 +43,7 @@ answer newbie questions, and generally made Django that much better:
ajs <adi@sieker.info>
alang@bright-green.com
A S Alam <aalam@users.sf.net>
Francisco Albarran Cristobal <pahko.xd@gmail.com>
Andi Albrecht <albrecht.andi@gmail.com>
Marty Alchin <gulopine@gamemusic.org>
Ahmad Alhashemi <trans@ahmadh.com>
Expand Down
58 changes: 54 additions & 4 deletions django/contrib/localflavor/mx/forms.py
Expand Up @@ -17,7 +17,6 @@
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',
Expand Down Expand Up @@ -46,6 +45,7 @@
u'WUEY',
]


class MXStateSelect(Select):
"""
A Select widget that uses a list of Mexican states as its choices.
Expand Down Expand Up @@ -105,8 +105,8 @@ class MXRFCField(RegexField):
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.'),
'invalid': _(u'Enter a valid RFC.'),
'invalid_checksum': _(u'Invalid checksum for RFC.'),
}

def __init__(self, min_length=9, max_length=13, *args, **kwargs):
Expand Down Expand Up @@ -186,7 +186,7 @@ class MXCURPField(RegexField):
http://www.condusef.gob.mx/index.php/clave-unica-de-registro-de-poblacion-curp
"""
default_error_messages = {
'invalid': _('Enter a valid CURP.'),
'invalid': _(u'Enter a valid CURP.'),
'invalid_checksum': _(u'Invalid checksum for CURP.'),
}

Expand Down Expand Up @@ -223,3 +223,53 @@ def _checksum(self, value):
def _has_inconvenient_word(self, curp):
first_four = curp[:4]
return first_four in CURP_INCONVENIENT_WORDS


class MXSocialSecurityNumberField(RegexField):
"""
A field that validates a Mexican Social Security Number.

The Social Security Number is integrated by a juxtaposition of digits
following the next pattern:

===== =======================================
Index Required numbers
===== =======================================
1-2 The number of the branch office where the Social Security Number
was designated.
3-4 The year of inscription to the Social Security.
5-6 The year of birth of the Social Security Number owner.
7-10 The progressive number of procedure for the IMSS.
(This digit is provided exclusively by the Institute as it regards
the Folio number of such procedure).
11 The verification digit.
"""
default_error_messages = {
'invalid': _(u'Enter a valid Social Security Number.'),
'invalid_checksum': _(u'Invalid checksum for Social Security Number.'),
}

def __init__(self, min_length=11, max_length=11, *args, **kwargs):
ssn_re = ur'^\d{11}$'
ssn_re = re.compile(ssn_re)
super(MXSocialSecurityNumberField, self).__init__(ssn_re,
min_length=min_length, max_length=max_length, *args, **kwargs)

def clean(self, value):
value = super(MXSocialSecurityNumberField, self).clean(value)
if value in EMPTY_VALUES:
return u''
if value[-1] != self.__checksum(value[:-1]):
raise ValidationError(self.default_error_messages['invalid_checksum'])
return value

def __checksum(self, value):
multipliers = [1 if i % 2 == 0 else 2 for i in xrange(10)]

s = [int(v) * m for v, m in zip(value, multipliers)]
s = sum(map(int, ''.join(map(str, s))))
checksum = 10 - s % 10

if checksum == 10:
return u'0'
return unicode(checksum)
22 changes: 20 additions & 2 deletions django/contrib/localflavor/mx/models.py
Expand Up @@ -3,7 +3,8 @@

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)
MXZipCodeField as MXZipCodeFormField, MXCURPField as MXCURPFormField,
MXSocialSecurityNumberField as MXSocialSecurityNumberFormField)


class MXStateField(CharField):
Expand Down Expand Up @@ -67,4 +68,21 @@ def __init__(self, *args, **kwargs):
def formfield(self, **kwargs):
defaults = {'form_class': MXCURPFormField}
defaults.update(kwargs)
return super(MXCURPField, self).formfield(**defaults)
return super(MXCURPField, self).formfield(**defaults)


class MXSocialSecurityNumberField(CharField):
"""
A model field that forms represent as a forms.MXSocialSecurityNumberField
field and stores the value of a valid Mexican Social Security Number.
"""
description = _("Mexican Social Security Number")

def __init__(self, *args, **kwargs):
kwargs['max_length'] = 11
super(MXSocialSecurityNumberField, self).__init__(*args, **kwargs)

def formfield(self, **kwargs):
defaults = {'form_class': MXSocialSecurityNumberFormField}
defaults.update(kwargs)
return super(MXSocialSecurityNumberField, self).formfield(**defaults)
10 changes: 10 additions & 0 deletions docs/ref/contrib/localflavor.txt
Expand Up @@ -842,6 +842,11 @@ Mexico (``mx``)

.. _curp: http://www.condusef.gob.mx/index.php/clave-unica-de-registro-de-poblacion-curp

.. class:: mx.forms.MXSocialSecurityNumberField

A field that validates a Mexican Social Security Number(NSS) for *Instituto Mexicano del Seguro Social*.


.. class:: mx.forms.MXStateSelect

A ``Select`` widget that uses a list of Mexican states as its choices.
Expand Down Expand Up @@ -874,6 +879,11 @@ Mexico (``mx``)
A model field that forms represent as a ``forms.MXCURPField`` field and
stores the value of a valid Mexican CURP.

.. class:: mx.models.MXSocialSecurityNumberField

A model field that forms represent as a ``forms.MXSocialSecurityNumberField`` field and
stores the eleven-digit Mexican Social Security Number for *Instituto Mexicano del Seguro Social*.

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:
Expand Down
4 changes: 3 additions & 1 deletion tests/regressiontests/localflavor/mx/models.py
@@ -1,5 +1,6 @@
from django.contrib.localflavor.mx.models import (
MXStateField, MXRFCField, MXCURPField, MXZipCodeField)
MXStateField, MXRFCField, MXCURPField, MXZipCodeField,
MXSocialSecurityNumberField)
from django.db import models


Expand All @@ -8,6 +9,7 @@ class MXPersonProfile(models.Model):
rfc = MXRFCField()
curp = MXCURPField()
zip_code = MXZipCodeField()
ssn = MXSocialSecurityNumberField()

class Meta:
app_label = 'localflavor'
48 changes: 47 additions & 1 deletion tests/regressiontests/localflavor/mx/tests.py
Expand Up @@ -2,7 +2,7 @@
from __future__ import absolute_import

from django.contrib.localflavor.mx.forms import (MXZipCodeField, MXRFCField,
MXStateSelect, MXCURPField)
MXStateSelect, MXCURPField, MXSocialSecurityNumberField)
from django.test import SimpleTestCase

from .forms import MXPersonProfileForm
Expand All @@ -16,6 +16,7 @@ def setUp(self):
'rfc': 'toma880125kv3',
'curp': 'toma880125hmnrrn02',
'zip_code': '58120',
'ssn': '53987417457',
})

def test_get_display_methods(self):
Expand All @@ -30,12 +31,14 @@ def test_errors(self):
'rfc': 'invalid rfc',
'curp': 'invalid curp',
'zip_code': 'xxx',
'ssn': 'invalid ssn',
})
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.'])
self.assertEqual(form.errors['ssn'], [u'Enter a valid Social Security Number.'])

def test_field_blank_option(self):
"""Test that the empty option is there."""
Expand Down Expand Up @@ -196,3 +199,46 @@ def test_MXCURPField(self):
'OOMG890727HMNRSR07': error_checksum,
}
self.assertFieldOutput(MXCURPField, valid, invalid)

def test_MXSocialSecurityNumberField(self):
error_format = [u'Enter a valid Social Security Number.']
error_checksum = [u'Invalid checksum for Social Security Number.']
valid = {
'53987417457': u'53987417457',
'53916912966': u'53916912966',
'53986504172': u'53986504172',
'17300426925': u'17300426925',
'53067407212': u'53067407212',
'53018000538': u'53018000538',
'10836311612': u'10836311612',
'37007910666': u'37007910666',
'53055700974': u'53055700974',
'17303364941': u'17303364941',
'53078528469': u'53078528469',
}
invalid = {
# Invalid format
'5398741A457': error_format,
'53487G12031': error_format,
'530P8028702': error_format,
'173004K6925': error_format,
'5306T407212': error_format,
'53018N00538': error_format,
'E0836311612': error_format,
'3700U910666': error_format,
'530557 0974': error_format,
'173033?4941': error_format,
'53#88417917': error_format,
# Incorrect checksum
'53987417451': error_checksum,
'53018522942': error_checksum,
'53897239693': error_checksum,
'01704423244': error_checksum,
'53855919735': error_checksum,
'53926201296': error_checksum,
'53017919037': error_checksum,
'53884201248': error_checksum,
'42805762629': error_checksum,
'53563800130': error_checksum,
}
self.assertFieldOutput(MXSocialSecurityNumberField, valid, invalid)