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 May 6, 2016
1 parent fe3ea0f commit 8978e2f
Show file tree
Hide file tree
Showing 6 changed files with 114 additions and 10 deletions.
5 changes: 3 additions & 2 deletions docs/changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,14 @@ New flavors:

New fields for existing flavors:

- None
- Added MXCLABEField model and form fields.
(`gh-227 <https://github.com/django/django-localflavor/pull/227>`_).

Modifications to existing flavors:

- None

1.3 (2016-04-06)
1.3 (2016-05-06)
------------------

New flavors:
Expand Down
43 changes: 43 additions & 0 deletions localflavor/mx/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,49 @@ 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://en.wikipedia.org/wiki/CLABE
.. versionadded:: 1.4
"""

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}$'
super(MXCLABEField, self).__init__(clabe_re, min_length, max_length, *args, **kwargs)

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

weight_factor = (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(weight_factor, 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
25 changes: 25 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,30 @@ 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.
.. versionadded:: 1.4
"""
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()
44 changes: 39 additions & 5 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,21 @@ def test_errors(self):
'curp': 'invalid curp',
'zip_code': 'xxx',
'ssn': 'invalid ssn',
'clabe': 'invalid clabexxxxx'
})
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['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'], ['Enter a valid CLABE.'])

def test_field_blank_option(self):
"""Test that the empty option is there."""
Expand Down Expand Up @@ -247,3 +256,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 8978e2f

Please sign in to comment.