Code

Ticket #4036: 4036_2.diff

File 4036_2.diff, 9.1 KB (added by garcia_marc, 7 years ago)

spanish localflavor files

Line 
1Index: django/contrib/localflavor/es/es_regions.py
2===================================================================
3--- django/contrib/localflavor/es/es_regions.py (revision 0)
4+++ django/contrib/localflavor/es/es_regions.py (revision 0)
5@@ -0,0 +1,23 @@
6+# -*- coding: utf-8 -*-
7+from django.utils.translation import ugettext_lazy as _
8+
9+REGION_CHOICES = (
10+       ('AN', _('Andalusia')),
11+       ('AR', _('Aragon')),
12+       ('O', _('Principality of Asturias')),
13+       ('IB', _('Balearic Islands')),
14+       ('PV', _('Basque Country')),
15+       ('CN', _('Canary Islands')),
16+       ('S', _('Cantabria')),
17+       ('CM', _('Castile-La Mancha')),
18+       ('CL', _('Castile and León')),
19+       ('CT', _('Catalonia')),
20+       ('EX', _('Extremadura')),
21+       ('GA', _('Galicia')),
22+       ('LO', _('La Rioja')),
23+       ('M', _('Madrid')),
24+       ('MU', _('Region of Murcia')),
25+       ('NA', _('Foral Community of Navarre')),
26+       ('VC', _('Valencian Community')),
27+)
28+
29Index: django/contrib/localflavor/es/es_provinces.py
30===================================================================
31--- django/contrib/localflavor/es/es_provinces.py       (revision 0)
32+++ django/contrib/localflavor/es/es_provinces.py       (revision 0)
33@@ -0,0 +1,58 @@
34+# -*- coding: utf-8 -*-
35+from django.utils.translation import ugettext_lazy as _
36+
37+PROVINCE_CHOICES = (
38+       ('01', _('Arava')),
39+       ('02', _('Albacete')),
40+       ('03', _('Alacant')),
41+       ('04', _('Almería')),
42+       ('05', _('Ávila')),
43+       ('06', _('Badajoz')),
44+       ('07', _('Illes Balears')),
45+       ('08', _('Barcelona')),
46+       ('09', _('Burgos')),
47+       ('10', _('Cáceres')),
48+       ('11', _('Cadiz')),
49+       ('12', _('Castelló')),
50+       ('13', _('Ciudad Real')),
51+       ('14', _('Córdoba')),
52+       ('15', _('A Coruña')),
53+       ('16', _('Cuenca')),
54+       ('17', _('Girona')),
55+       ('18', _('Granada')),
56+       ('19', _('Guadalajara')),
57+       ('20', _('Guipuzkoa')),
58+       ('21', _('Huelva')),
59+       ('22', _('Huesca')),
60+       ('23', _('Jaén')),
61+       ('24', _('León')),
62+       ('25', _('Lleida')),
63+       ('26', _('La Rioja')),
64+       ('27', _('Lugo')),
65+       ('28', _('Madrid')),
66+       ('29', _('Málaga')),
67+       ('30', _('Murcia')),
68+       ('31', _('Navarre')),
69+       ('32', _('Ourense')),
70+       ('33', _('Asturias')),
71+       ('34', _('Palencia')),
72+       ('35', _('Las Palmas')),
73+       ('36', _('Pontevedra')),
74+       ('37', _('Salamanca')),
75+       ('38', _('Santa Cruz de Tenerife')),
76+       ('39', _('Cantabria')),
77+       ('40', _('Segovia')),
78+       ('41', _('Seville')),
79+       ('42', _('Soria')),
80+       ('43', _('Tarragona')),
81+       ('44', _('Teruel')),
82+       ('45', _('Toledo')),
83+       ('46', _('València')),
84+       ('47', _('Valladolid')),
85+       ('48', _('Bizkaia')),
86+       ('49', _('Zamora')),
87+       ('50', _('Zaragoza')),
88+       ('51', _('Ceuta')),
89+       ('52', _('Melilla')),
90+)
91+
92Index: django/contrib/localflavor/es/__init__.py
93===================================================================
94Index: django/contrib/localflavor/es/forms.py
95===================================================================
96--- django/contrib/localflavor/es/forms.py      (revision 0)
97+++ django/contrib/localflavor/es/forms.py      (revision 0)
98@@ -0,0 +1,154 @@
99+# -*- coding: utf-8 -*-
100+"""
101+Spanish-specific Form helpers
102+"""
103+
104+from django.newforms import ValidationError
105+from django.newforms.fields import RegexField, Select, EMPTY_VALUES
106+from django.utils.translation import ugettext as _
107+import re
108+
109+class ESPostalCodeField(RegexField):
110+    """
111+    A form field that validates its input as a spanish postal code.
112+   
113+    Spanish postal code is a five digits string, with two first digits
114+    between 01 and 52, assigned to provinces code.
115+    """
116+    def __init__(self, *args, **kwargs):
117+        super(ESPostalCodeField, self).__init__(r'^(0[1-9]|[1-4][0-9]|5[0-2])\d{3}$',
118+            max_length=None, min_length=None,
119+            error_message=_('Enter a valid postal code in the range and format 01XXX - 52XXX.'),
120+            *args, **kwargs)
121+
122+class ESPhoneNumberField(RegexField):
123+       """
124+       A form field that validates its input as a spanish Phone Number. Information numbers are ommited.
125+
126+       Spanish phone numbers are 9 digit numbers, where first digit is 6 (for cell phones), 8 (for special
127+       phones), or 9 (for landlines and special phones)
128+
129+       TODO: accept and strip characters like dot, hyphen... in phone number
130+       """
131+       def __init__(self, *args, **kwargs):
132+               super(ESPhoneNumberField, self).__init__(r'^(6|8|9)\d{8}$',
133+                               max_length=None, min_length=None,
134+                               error_message=_('Enter a valid phone number in one of the formats 6XXXXXXXX, 8XXXXXXXX or 9XXXXXXXX.'),
135+                               *args, **kwargs)
136+
137+class ESIdentityCardNumberField(RegexField):
138+       """
139+       Spanish NIF/NIE/CIF (Fiscal Identification Number) code.
140+
141+       Validates three diferent formats,
142+               NIF (individuals): 12345678A
143+               CIF (companies): A12345678
144+               NIE (foreigners): X12345678A
145+       according to a couple of simple checksum algorithms.
146+       Value can include a space or hyphen separator between number and letters.
147+       Number length is not checked for nif (or nie), old values started by 1, and future values can be more than 8.
148+       CIF control digit can be a number or a letter depending on company type. Algorithm is not public, and different
149+       authors have different opinions on which ones allows letters, so both validations are assumed true for all types.
150+       """
151+       def __init__(self, only_nif=False, *args, **kwargs):
152+               self.only_nif = only_nif
153+               self.nif_control = 'TRWAGMYFPDXBNJZSQVHLCKE'
154+               self.cif_control = 'JABCDEFGHI'
155+               self.cif_types = 'ABCDEFGHKLMNPQS'
156+               self.nie_types = 'XT'
157+               if self.only_nif:
158+                       self.id_types = 'NIF or NIE'
159+               else:
160+                       self.id_types = 'NIF, NIE, or CIF'
161+               super(ESIdentityCardNumberField, self).__init__(r'^([%s]?)[ -]?(\d+)[ -]?([%s]?)$' % (self.cif_types + self.nie_types, self.nif_control),
162+                               max_length=None, min_length=None,
163+                               error_message=_('Please enter a valid %s.' % self.id_types),
164+                               *args, **kwargs)
165+
166+       def clean(self, value):
167+               super(ESIdentityCardNumberField, self).clean(value)       
168+               if value in EMPTY_VALUES:
169+                       return u''
170+               nif_get_checksum = lambda d: self.nif_control[int(d)%23]
171+               def cif_get_checksum(number):
172+                       s1 = sum([int(digit) for pos, digit in enumerate(number) if int(pos) % 2])
173+                       s2 = sum([sum([int(unit) for unit in str(int(digit) * 2)]) for pos, digit in enumerate(number) if not int(pos) % 2])
174+                       return 10 - ((s1 + s2) % 10)
175+               #cif_get_checksum = lambda d: d
176+
177+               m = re.match(r'^([%s]?)[ -]?(\d+)[ -]?([%s]?)$' % (self.cif_types + self.nie_types, self.nif_control), value)
178+               letter1, number, letter2 = m.groups()
179+
180+               if not letter1 and letter2: # NIF
181+                       if letter2 == nif_get_checksum(number):
182+                               return value
183+                       else:
184+                               raise ValidationError, _('Invalid checksum for NIF.')
185+               elif letter1 in self.nie_types and letter2: # NIE
186+                       if letter2 == nif_get_checksum(number):
187+                               return value
188+                       else:
189+                               raise ValidationError, _('Invalid checksum for NIE.')
190+               elif not self.only_nif and letter1 in self.cif_types and len(number) in [7, 8]: # CIF
191+                       if not letter2:
192+                               number, letter2 = number[:-1], int(number[-1])
193+                       checksum = cif_get_checksum(number)
194+                       if letter2 in [checksum, self.cif_control[checksum]]:
195+                               return value
196+                       else:
197+                               raise ValidationError, _('Invalid checksum for CIF.')
198+               else:
199+                       raise ValidationError, _('Please enter a valid %s.' % self.id_types)
200+
201+class ESCCCField(RegexField):
202+       """
203+    A form field that validates its input as a spanish bank account or CCC (Código Cuenta Cliente)
204+
205+               Spanish CCC is in format EEEE-OOOO-CC-AAAAAAAAAA where
206+                       E = entity
207+                       O = office
208+                       C = checksum
209+                       A = account
210+               It's also valid using space as delimiter, or using no delimiter
211+
212+               First checksum digit validates entity and office, and last one validates account. Validation is
213+               done multiplying every digit of 10 digit value (with leading 0 if necessary) by number in its
214+               position in string 1, 2, 4, 8, 5, 10, 9, 7, 3, 6. Sum resulting numbers and extract it from 11.
215+               Result is checksum except when 10 then is 1, or when 11 then is 0.
216+
217+               TODO: allow IBAN validation too
218+       """
219+       def __init__(self, nif=True, cif=True, *args, **kwargs):
220+               super(ESCCCField, self).__init__(r'^\d{4}[ -]?\d{4}[ -]?\d{2}[ -]?\d{10}$',
221+                       max_length=None, min_length=None,
222+                       error_message=_('Please enter a valid bank account number in format XXXX-XXXX-XX-XXXXXXXXXX.'),
223+                       *args, **kwargs)
224+       def clean(self, value):
225+               super(ESCCCField, self).clean(value)       
226+               if value in EMPTY_VALUES:
227+                       return u''
228+               control_str = [1, 2, 4, 8, 5, 10, 9, 7, 3, 6]
229+               m = re.match(r'^(\d{4})[ -]?(\d{4})[ -]?(\d{2})[ -]?(\d{10})$', value)
230+               entity, office, checksum, account = m.groups()
231+               get_checksum = lambda d: str(11 - sum([int(digit) * int(control) for digit, control in zip(d, control_str)]) % 11).replace('10', '1').replace('11', '0')
232+               if get_checksum('00' + entity + office) + get_checksum(account) == checksum:
233+                       return value
234+               else:
235+                       raise ValidationError, _('Invalid checksum for bank account number.')
236+
237+class ESRegionSelect(Select):
238+    """
239+    A Select widget that uses a list of spanish regions as its choices.
240+    """
241+    def __init__(self, attrs=None):
242+        from es_regions import REGION_CHOICES
243+        super(ESRegionSelect, self).__init__(attrs, choices=REGION_CHOICES)
244+
245+class ESProvinceSelect(Select):
246+    """
247+    A Select widget that uses a list of spanish provinces as its choices.
248+    """
249+    def __init__(self, attrs=None):
250+        from es_provinces import PROVINCE_CHOICES
251+        super(ESProvinceSelect, self).__init__(attrs, choices=PROVINCE_CHOICES)
252+