|
1 # -*- coding: utf-8 -*- |
|
2 """ |
|
3 BR-specific Form helpers |
|
4 """ |
|
5 |
|
6 from django.newforms import ValidationError |
|
7 from django.newforms.fields import Field, RegexField, CharField, Select, EMPTY_VALUES |
|
8 from django.utils.encoding import smart_unicode |
|
9 from django.utils.translation import ugettext 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 |