diff --git a/AUTHORS b/AUTHORS
index 3ed70d2cce..1c33bb9a7b 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -168,6 +168,7 @@ answer newbie questions, and generally made Django that much better:
Sam Newman
Neal Norwitz
oggie rob
+ onaiort@gmail.com
Jay Parlar
pavithran s
Barry Pederson
diff --git a/django/contrib/localflavor/br/forms.py b/django/contrib/localflavor/br/forms.py
index 29ad4df53d..0ec370546f 100644
--- a/django/contrib/localflavor/br/forms.py
+++ b/django/contrib/localflavor/br/forms.py
@@ -4,7 +4,7 @@ BR-specific Form helpers
"""
from django.newforms import ValidationError
-from django.newforms.fields import Field, RegexField, Select, EMPTY_VALUES
+from django.newforms.fields import Field, RegexField, CharField, Select, EMPTY_VALUES
from django.utils.encoding import smart_unicode
from django.utils.translation import gettext
import re
@@ -15,7 +15,7 @@ class BRZipCodeField(RegexField):
def __init__(self, *args, **kwargs):
super(BRZipCodeField, self).__init__(r'^\d{5}-\d{3}$',
max_length=None, min_length=None,
- error_message=gettext(u'Enter a zip code in the format XXXXX-XXX.'),
+ error_message=gettext('Enter a zip code in the format XXXXX-XXX.'),
*args, **kwargs)
class BRPhoneNumberField(Field):
@@ -37,3 +37,83 @@ class BRStateSelect(Select):
def __init__(self, attrs=None):
from br_states import STATE_CHOICES # relative import
super(BRStateSelect, self).__init__(attrs, choices=STATE_CHOICES)
+
+
+def DV_maker(v):
+ if v >= 2:
+ return 11 - v
+ return 0
+
+class BRCPFField(CharField):
+ """
+ This field validate a CPF number or a CPF string. A CPF number is
+ compounded by XXX.XXX.XXX-VD, the two last digits are check digits.
+
+ More information:
+ http://en.wikipedia.org/wiki/Cadastro_de_Pessoas_F%C3%ADsicas
+ """
+ def __init__(self, *args, **kwargs):
+ super(BRCPFField, self).__init__(max_length=14, min_length=11, *args, **kwargs)
+
+ def clean(self, value):
+ """
+ Value can be either a string in the format XXX.XXX.XXX-XX or an
+ 11-digit number.
+ """
+ value = super(BRCPFField, self).clean(value)
+ if value in EMPTY_VALUES:
+ return u''
+ orig_value = value[:]
+ if not value.isdigit():
+ value = re.sub("[-\.]", "", value)
+ try:
+ int(value)
+ except ValueError:
+ raise ValidationError(gettext("This field requires only numbers"))
+ if len(value) != 11:
+ raise ValidationError(gettext("This field requires at most 11 digits or 14 characters."))
+ orig_dv = value[-2:]
+
+ new_1dv = sum([i * int(value[idx]) for idx, i in enumerate(range(10, 1, -1))])
+ new_1dv = DV_maker(new_1dv % 11)
+ value = value[:-2] + str(new_1dv) + value[-1]
+ new_2dv = sum([i * int(value[idx]) for idx, i in enumerate(range(11, 1, -1))])
+ new_2dv = DV_maker(new_2dv % 11)
+ value = value[:-1] + str(new_2dv)
+ if value[-2:] != orig_dv:
+ raise ValidationError(gettext("Invalid CPF number."))
+
+ return orig_value
+
+class BRCNPJField(Field):
+ def clean(self, value):
+ """
+ Value can be either a string in the format XX.XXX.XXX/XXXX-XX or a
+ group of 14 characters.
+ """
+ value = super(BRCNPJField, self).clean(value)
+ if value in EMPTY_VALUES:
+ return u''
+ orig_value = value[:]
+ if not value.isdigit():
+ value = re.sub("[-/\.]", "", value)
+ try:
+ int(value)
+ except ValueError:
+ raise ValidationError("This field requires only numbers")
+ if len(value) != 14:
+ raise ValidationError(
+ gettext("This field requires at least 14 digits"))
+ orig_dv = value[-2:]
+
+ new_1dv = sum([i * int(value[idx]) for idx, i in enumerate(range(5, 1, -1) + range(9, 1, -1))])
+ new_1dv = DV_maker(new_1dv % 11)
+ value = value[:-2] + str(new_1dv) + value[-1]
+ new_2dv = sum([i * int(value[idx]) for idx, i in enumerate(range(6, 1, -1) + range(9, 1, -1))])
+ new_2dv = DV_maker(new_2dv % 11)
+ value = value[:-1] + str(new_2dv)
+ if value[-2:] != orig_dv:
+ raise ValidationError(gettext("Invalid CNPJ number"))
+
+ return orig_value
+
diff --git a/tests/regressiontests/forms/localflavor.py b/tests/regressiontests/forms/localflavor.py
index 1747a0a61c..d339eb089d 100644
--- a/tests/regressiontests/forms/localflavor.py
+++ b/tests/regressiontests/forms/localflavor.py
@@ -840,6 +840,40 @@ ValidationError: [u'Enter a zip code in the format XXXXX-XXX.']
>>> f.clean('12345-123')
u'12345-123'
+# BRCNPJField ############################################################
+
+>>> from django.contrib.localflavor.br.forms import BRCNPJField
+>>> f = BRCNPJField(required=True)
+>>> f.clean('')
+Traceback (most recent call last):
+...
+ValidationError: [u'This field is required.']
+>>> f.clean('12-345-678/9012-10')
+Traceback (most recent call last):
+...
+ValidationError: [u'Invalid CNPJ number']
+>>> f.clean('12.345.678/9012-10')
+Traceback (most recent call last):
+...
+ValidationError: [u'Invalid CNPJ number']
+>>> f.clean('12345678/9012-10')
+Traceback (most recent call last):
+...
+ValidationError: [u'Invalid CNPJ number']
+>>> f.clean('64.132.916/0001-88')
+'64.132.916/0001-88'
+>>> f.clean('64-132-916/0001-88')
+'64-132-916/0001-88'
+>>> f.clean('64132916/0001-88')
+'64132916/0001-88'
+>>> f.clean('64.132.916/0001-XX')
+Traceback (most recent call last):
+...
+ValidationError: [u'This field requires only numbers']
+>>> f = BRCNPJField(required=False)
+>>> f.clean('')
+u''
+
# BRPhoneNumberField #########################################################
>>> from django.contrib.localflavor.br.forms import BRPhoneNumberField