Skip to content

Commit

Permalink
Add MXCLABEField for Mexico
Browse files Browse the repository at this point in the history
  • Loading branch information
scaramagus authored and Agustin Scaramuzza committed Apr 19, 2016
1 parent 29160ca commit edc5323
Show file tree
Hide file tree
Showing 6 changed files with 103 additions and 5 deletions.
2 changes: 2 additions & 0 deletions docs/changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ New fields for existing flavors:
(`gh-151 <https://github.com/django/django-localflavor/pull/151>`_).
- Added NLZipCodeField, NLProvinceField, NLSoFiNumberField, NLPhoneNumberField model fields.
(`gh-152 <https://github.com/django/django-localflavor/pull/152>`_).
- Added MXCLABEField form field.
(`gh-227 <https://github.com/django/django-localflavor/pull/227>`_).

Modifications to existing flavors:

Expand Down
44 changes: 44 additions & 0 deletions localflavor/mx/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,50 @@ def _has_inconvenient_word(self, rfc):
return first_four in RFC_INCONVENIENT_WORDS


class MXCLABEField(RegexField):
"""This field validates a CLABE (Clave Bancaria Estandarizada).
A CLABE is a 18-digits long number. The first 6 digits denote bank and branch number.
The remaining 12 digits denote an account number, plus a verifying digit.
More info:
https://es.wikipedia.org/wiki/CLABE
.. versionadded:: 1.3
"""

default_error_messages = {
'invalid': _('Enter a valid CLABE.'),
'invalid_checksum': _('Invalid checksum for CLABE.'),
}

def __init__(self, min_length=18, max_length=18, *args, **kwargs):
clabe_re = r'^\d{18}$'
clabe_re = re.compile(clabe_re)
super(MXCLABEField, self).__init__(clabe_re, min_length, max_length, *args, **kwargs)

def _checksum(self, value):
verification_digit = int(value[-1])
number = value[:-1]

ponderator = (3, 7, 1, 3, 7, 1, 3, 7, 1, 3, 7, 1, 3, 7, 1, 3, 7)

sum_remainder = sum(x * int(y) % 10 for x, y in zip(ponderator, number)) % 10

return verification_digit == (10 - sum_remainder) % 10

def clean(self, value):
value = super(MXCLABEField, self).clean(value)
if value in EMPTY_VALUES:
return ''
if not value.isdigit():
raise ValidationError(self.error_messages['invalid'])
if not self._checksum(value):
raise ValidationError(self.error_messages['invalid_checksum'])

return value


class MXCURPField(RegexField):
"""
A field that validates a Mexican Clave Única de Registro de Población.
Expand Down
23 changes: 23 additions & 0 deletions localflavor/mx/models.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from django.db.models import CharField
from django.utils.translation import ugettext_lazy as _

from .forms import MXCLABEField as MXCLABEFormField
from .forms import MXCURPField as MXCURPFormField
from .forms import MXRFCField as MXRFCFormField
from .forms import MXSocialSecurityNumberField as MXSocialSecurityNumberFormField
Expand Down Expand Up @@ -71,6 +72,28 @@ def formfield(self, **kwargs):
return super(MXRFCField, self).formfield(**defaults)


class MXCLABEField(CharField):
"""
A model field that forms represent as a forms.MXCURPField field and
stores the value of a valid Mexican CLABE.
"""
description = _("Mexican CLABE")

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

def deconstruct(self):
name, path, args, kwargs = super(MXCLABEField, self).deconstruct()
del kwargs['max_length']
return name, path, args, kwargs

def formfield(self, **kwargs):
defaults = {'form_class': MXCLABEFormField}
defaults.update(kwargs)
return super(MXCLABEField, self).formfield(**defaults)


class MXCURPField(CharField):
"""
A model field that forms represent as a forms.MXCURPField field and
Expand Down
2 changes: 1 addition & 1 deletion tests/test_mx/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,4 @@ class MXPersonProfileForm(ModelForm):

class Meta:
model = MXPersonProfile
fields = ('state', 'rfc', 'curp', 'zip_code', 'ssn')
fields = ('state', 'rfc', 'curp', 'zip_code', 'ssn', 'clabe')
5 changes: 3 additions & 2 deletions tests/test_mx/models.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from django.db import models

from localflavor.mx.models import MXCURPField, MXRFCField, MXSocialSecurityNumberField, MXStateField, MXZipCodeField
from localflavor.mx.models import (MXCLABEField, MXCURPField, MXRFCField, MXSocialSecurityNumberField, MXStateField,
MXZipCodeField)


class MXPersonProfile(models.Model):
Expand All @@ -9,3 +9,4 @@ class MXPersonProfile(models.Model):
curp = MXCURPField()
zip_code = MXZipCodeField()
ssn = MXSocialSecurityNumberField()
clabe = MXCLABEField()
32 changes: 30 additions & 2 deletions tests/test_mx/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
from __future__ import unicode_literals

from django.test import TestCase

from localflavor.mx.forms import MXCURPField, MXRFCField, MXSocialSecurityNumberField, MXStateSelect, MXZipCodeField
from localflavor.mx.forms import (MXCLABEField, MXCURPField, MXRFCField, MXSocialSecurityNumberField, MXStateSelect,
MXZipCodeField)

from .forms import MXPersonProfileForm

Expand All @@ -17,6 +17,7 @@ def setUp(self):
'curp': 'toma880125hmnrrn02',
'zip_code': '58120',
'ssn': '53987417457',
'clabe': '032180000118359719'
})

def test_get_display_methods(self):
Expand All @@ -32,13 +33,15 @@ def test_errors(self):
'curp': 'invalid curp',
'zip_code': 'xxx',
'ssn': 'invalid ssn',
'clabe': 'invalid clabe'
})
self.assertFalse(form.is_valid())
self.assertEqual(form.errors['state'], ['Select a valid choice. Invalid state is not one of the available choices.'])
self.assertEqual(form.errors['rfc'], ['Ensure this value has at least 12 characters (it has 11).', 'Enter a valid RFC.'])
self.assertEqual(form.errors['curp'], ['Ensure this value has at least 18 characters (it has 12).', 'Enter a valid CURP.'])
self.assertEqual(form.errors['zip_code'], ['Enter a valid zip code in the format XXXXX.'])
self.assertEqual(form.errors['ssn'], ['Enter a valid Social Security Number.'])
self.assertEqual(form.errors['clabe'], ['Ensure this value has at least 18 characters (it has 13).', 'Enter a valid CLABE.'])

def test_field_blank_option(self):
"""Test that the empty option is there."""
Expand Down Expand Up @@ -247,3 +250,28 @@ def test_MXSocialSecurityNumberField(self):
'53563800130': error_checksum,
}
self.assertFieldOutput(MXSocialSecurityNumberField, valid, invalid)

def test_MXCLABEField(self):
error_format = ['Enter a valid CLABE.']
error_checksum = ['Invalid checksum for CLABE.']
valid = {
'032180000118359719': '032180000118359719',
'002115016003269411': '002115016003269411',
'435816798316429530': '435816798316429530',
'102211657483920119': '102211657483920119',
'002846375894578321': '002846375894578321',
'012276385238571288': '012276385238571288',
'633790823578925966': '633790823578925966',
'613137129494921910': '613137129494921910',
'108180637932589295': '108180637932589295',
}

invalid = {
'abc123def456-902-4': error_format,
'123456789123456789': error_checksum,
'123456237454589458': error_checksum,
'098765375925788389': error_checksum,
'042560735684818257': error_checksum,
'037027587179835981': error_checksum,
}
self.assertFieldOutput(MXCLABEField, valid, invalid)

0 comments on commit edc5323

Please sign in to comment.