| 1 |
# -*- coding: utf-8 -*- |
|---|
| 2 |
""" |
|---|
| 3 |
BR-specific Form helpers |
|---|
| 4 |
""" |
|---|
| 5 |
|
|---|
| 6 |
from django.forms import ValidationError |
|---|
| 7 |
from django.forms.fields import Field, RegexField, CharField, Select, EMPTY_VALUES |
|---|
| 8 |
from django.utils.encoding import smart_unicode |
|---|
| 9 |
from django.utils.translation import ugettext_lazy as _ |
|---|
| 10 |
import re |
|---|
| 11 |
|
|---|
| 12 |
try: |
|---|
| 13 |
set |
|---|
| 14 |
except NameError: |
|---|
| 15 |
from sets import Set as set # For Python 2.3 |
|---|
| 16 |
|
|---|
| 17 |
phone_digits_re = re.compile(r'^(\d{2})[-\.]?(\d{4})[-\.]?(\d{4})$') |
|---|
| 18 |
|
|---|
| 19 |
class BRZipCodeField(RegexField): |
|---|
| 20 |
default_error_messages = { |
|---|
| 21 |
'invalid': _('Enter a zip code in the format XXXXX-XXX.'), |
|---|
| 22 |
} |
|---|
| 23 |
|
|---|
| 24 |
def __init__(self, *args, **kwargs): |
|---|
| 25 |
super(BRZipCodeField, self).__init__(r'^\d{5}-\d{3}$', |
|---|
| 26 |
max_length=None, min_length=None, *args, **kwargs) |
|---|
| 27 |
|
|---|
| 28 |
class BRPhoneNumberField(Field): |
|---|
| 29 |
default_error_messages = { |
|---|
| 30 |
'invalid': _('Phone numbers must be in XX-XXXX-XXXX format.'), |
|---|
| 31 |
} |
|---|
| 32 |
|
|---|
| 33 |
def clean(self, value): |
|---|
| 34 |
super(BRPhoneNumberField, self).clean(value) |
|---|
| 35 |
if value in EMPTY_VALUES: |
|---|
| 36 |
return u'' |
|---|
| 37 |
value = re.sub('(\(|\)|\s+)', '', smart_unicode(value)) |
|---|
| 38 |
m = phone_digits_re.search(value) |
|---|
| 39 |
if m: |
|---|
| 40 |
return u'%s-%s-%s' % (m.group(1), m.group(2), m.group(3)) |
|---|
| 41 |
raise ValidationError(self.error_messages['invalid']) |
|---|
| 42 |
|
|---|
| 43 |
class BRStateSelect(Select): |
|---|
| 44 |
""" |
|---|
| 45 |
A Select widget that uses a list of Brazilian states/territories |
|---|
| 46 |
as its choices. |
|---|
| 47 |
""" |
|---|
| 48 |
def __init__(self, attrs=None): |
|---|
| 49 |
from br_states import STATE_CHOICES |
|---|
| 50 |
super(BRStateSelect, self).__init__(attrs, choices=STATE_CHOICES) |
|---|
| 51 |
|
|---|
| 52 |
class BRStateChoiceField(Field): |
|---|
| 53 |
""" |
|---|
| 54 |
A choice field that uses a list of Brazilian states as its choices. |
|---|
| 55 |
""" |
|---|
| 56 |
widget = Select |
|---|
| 57 |
default_error_messages = { |
|---|
| 58 |
'invalid': _(u'Select a valid brazilian state. That state is not one of the available states.'), |
|---|
| 59 |
} |
|---|
| 60 |
|
|---|
| 61 |
def __init__(self, required=True, widget=None, label=None, |
|---|
| 62 |
initial=None, help_text=None): |
|---|
| 63 |
super(BRStateChoiceField, self).__init__(required, widget, label, |
|---|
| 64 |
initial, help_text) |
|---|
| 65 |
from br_states import STATE_CHOICES |
|---|
| 66 |
self.widget.choices = STATE_CHOICES |
|---|
| 67 |
|
|---|
| 68 |
def clean(self, value): |
|---|
| 69 |
value = super(BRStateChoiceField, self).clean(value) |
|---|
| 70 |
if value in EMPTY_VALUES: |
|---|
| 71 |
value = u'' |
|---|
| 72 |
value = smart_unicode(value) |
|---|
| 73 |
if value == u'': |
|---|
| 74 |
return value |
|---|
| 75 |
valid_values = set([smart_unicode(k) for k, v in self.widget.choices]) |
|---|
| 76 |
if value not in valid_values: |
|---|
| 77 |
raise ValidationError(self.error_messages['invalid']) |
|---|
| 78 |
return value |
|---|
| 79 |
|
|---|
| 80 |
def DV_maker(v): |
|---|
| 81 |
if v >= 2: |
|---|
| 82 |
return 11 - v |
|---|
| 83 |
return 0 |
|---|
| 84 |
|
|---|
| 85 |
class BRCPFField(CharField): |
|---|
| 86 |
""" |
|---|
| 87 |
This field validate a CPF number or a CPF string. A CPF number is |
|---|
| 88 |
compounded by XXX.XXX.XXX-VD. The two last digits are check digits. |
|---|
| 89 |
|
|---|
| 90 |
More information: |
|---|
| 91 |
http://en.wikipedia.org/wiki/Cadastro_de_Pessoas_F%C3%ADsicas |
|---|
| 92 |
""" |
|---|
| 93 |
default_error_messages = { |
|---|
| 94 |
'invalid': _("Invalid CPF number."), |
|---|
| 95 |
'max_digits': _("This field requires at most 11 digits or 14 characters."), |
|---|
| 96 |
'digits_only': _("This field requires only numbers."), |
|---|
| 97 |
} |
|---|
| 98 |
|
|---|
| 99 |
def __init__(self, *args, **kwargs): |
|---|
| 100 |
super(BRCPFField, self).__init__(max_length=14, min_length=11, *args, **kwargs) |
|---|
| 101 |
|
|---|
| 102 |
def clean(self, value): |
|---|
| 103 |
""" |
|---|
| 104 |
Value can be either a string in the format XXX.XXX.XXX-XX or an |
|---|
| 105 |
11-digit number. |
|---|
| 106 |
""" |
|---|
| 107 |
value = super(BRCPFField, self).clean(value) |
|---|
| 108 |
if value in EMPTY_VALUES: |
|---|
| 109 |
return u'' |
|---|
| 110 |
orig_value = value[:] |
|---|
| 111 |
if not value.isdigit(): |
|---|
| 112 |
value = re.sub("[-\.]", "", value) |
|---|
| 113 |
try: |
|---|
| 114 |
int(value) |
|---|
| 115 |
except ValueError: |
|---|
| 116 |
raise ValidationError(self.error_messages['digits_only']) |
|---|
| 117 |
if len(value) != 11: |
|---|
| 118 |
raise ValidationError(self.error_messages['max_digits']) |
|---|
| 119 |
orig_dv = value[-2:] |
|---|
| 120 |
|
|---|
| 121 |
new_1dv = sum([i * int(value[idx]) for idx, i in enumerate(range(10, 1, -1))]) |
|---|
| 122 |
new_1dv = DV_maker(new_1dv % 11) |
|---|
| 123 |
value = value[:-2] + str(new_1dv) + value[-1] |
|---|
| 124 |
new_2dv = sum([i * int(value[idx]) for idx, i in enumerate(range(11, 1, -1))]) |
|---|
| 125 |
new_2dv = DV_maker(new_2dv % 11) |
|---|
| 126 |
value = value[:-1] + str(new_2dv) |
|---|
| 127 |
if value[-2:] != orig_dv: |
|---|
| 128 |
raise ValidationError(self.error_messages['invalid']) |
|---|
| 129 |
|
|---|
| 130 |
return orig_value |
|---|
| 131 |
|
|---|
| 132 |
class BRCNPJField(Field): |
|---|
| 133 |
default_error_messages = { |
|---|
| 134 |
'invalid': _("Invalid CNPJ number."), |
|---|
| 135 |
'digits_only': _("This field requires only numbers."), |
|---|
| 136 |
'max_digits': _("This field requires at least 14 digits"), |
|---|
| 137 |
} |
|---|
| 138 |
|
|---|
| 139 |
def clean(self, value): |
|---|
| 140 |
""" |
|---|
| 141 |
Value can be either a string in the format XX.XXX.XXX/XXXX-XX or a |
|---|
| 142 |
group of 14 characters. |
|---|
| 143 |
""" |
|---|
| 144 |
value = super(BRCNPJField, self).clean(value) |
|---|
| 145 |
if value in EMPTY_VALUES: |
|---|
| 146 |
return u'' |
|---|
| 147 |
orig_value = value[:] |
|---|
| 148 |
if not value.isdigit(): |
|---|
| 149 |
value = re.sub("[-/\.]", "", value) |
|---|
| 150 |
try: |
|---|
| 151 |
int(value) |
|---|
| 152 |
except ValueError: |
|---|
| 153 |
raise ValidationError(self.error_messages['digits_only']) |
|---|
| 154 |
if len(value) != 14: |
|---|
| 155 |
raise ValidationError(self.error_messages['max_digits']) |
|---|
| 156 |
orig_dv = value[-2:] |
|---|
| 157 |
|
|---|
| 158 |
new_1dv = sum([i * int(value[idx]) for idx, i in enumerate(range(5, 1, -1) + range(9, 1, -1))]) |
|---|
| 159 |
new_1dv = DV_maker(new_1dv % 11) |
|---|
| 160 |
value = value[:-2] + str(new_1dv) + value[-1] |
|---|
| 161 |
new_2dv = sum([i * int(value[idx]) for idx, i in enumerate(range(6, 1, -1) + range(9, 1, -1))]) |
|---|
| 162 |
new_2dv = DV_maker(new_2dv % 11) |
|---|
| 163 |
value = value[:-1] + str(new_2dv) |
|---|
| 164 |
if value[-2:] != orig_dv: |
|---|
| 165 |
raise ValidationError(self.error_messages['invalid']) |
|---|
| 166 |
|
|---|
| 167 |
return orig_value |
|---|