diff --git a/AUTHORS b/AUTHORS index a2cf8c68cc..ec4a222a4e 100644 --- a/AUTHORS +++ b/AUTHORS @@ -44,6 +44,7 @@ answer newbie questions, and generally made Django that much better: adurdin@gmail.com Andreas andy@jadedplanet.net + Fabrice Aneche ant9000@netwise.it David Ascher Arthur diff --git a/django/contrib/localflavor/fr/__init__.py b/django/contrib/localflavor/fr/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/django/contrib/localflavor/fr/forms.py b/django/contrib/localflavor/fr/forms.py new file mode 100644 index 0000000000..ee87c5cda2 --- /dev/null +++ b/django/contrib/localflavor/fr/forms.py @@ -0,0 +1,44 @@ +""" +FR-specific Form helpers +""" + +from django.newforms import ValidationError +from django.newforms.fields import Field, RegexField, Select, EMPTY_VALUES +from django.newforms.util import smart_unicode +from django.utils.translation import gettext +import re + +phone_digits_re = re.compile(r'^0\d(\s|\.)?(\d{2}(\s|\.)?){3}\d{2}$') + +class FRZipCodeField(RegexField): + def __init__(self, *args, **kwargs): + super(FRZipCodeField, self).__init__(r'^\d{5}$', + max_length=None, min_length=None, + error_message=gettext(u'Enter a zip code in the format XXXXX.'), + *args, **kwargs) + +class FRPhoneNumberField(Field): + """ + Validate local French phone number (not international ones) + The correct format is '0X XX XX XX XX'. + '0X.XX.XX.XX.XX' and '0XXXXXXXXX' validate but are corrected to + '0X XX XX XX XX'. + """ + def clean(self, value): + super(FRPhoneNumberField, self).clean(value) + if value in EMPTY_VALUES: + return u'' + value = re.sub('(\.|\s)', '', smart_unicode(value)) + m = phone_digits_re.search(value) + if m: + return u'%s %s %s %s %s' % (value[0:2], value[2:4], value[4:6], value[6:8], value[8:10]) + raise ValidationError(u'Phone numbers must be in 0X XX XX XX XX format.') + +class FRDepartmentSelect(Select): + """ + A Select widget that uses a list of FR departments as its choices. + """ + def __init__(self, attrs=None): + from fr_department import DEPARTMENT_ASCII_CHOICES # relative import + super(FRDepartmentSelect, self).__init__(attrs, choices=DEPARTMENT_ASCII_CHOICES) + diff --git a/django/contrib/localflavor/fr/fr_department.py b/django/contrib/localflavor/fr/fr_department.py new file mode 100644 index 0000000000..726cc7b13a --- /dev/null +++ b/django/contrib/localflavor/fr/fr_department.py @@ -0,0 +1,113 @@ +# -*- coding: utf-8 -*- + +DEPARTMENT_ASCII_CHOICES = ( +('01', '01 - Ain'), +('02', '02 - Aisne'), +('03', '03 - Allier'), +('04', '04 - Alpes-de-Haute-Provence'), +('05', '05 - Hautes-Alpes'), +('06', '06 - Alpes-Maritimes'), +('07', '07 - Ardeche'), +('08', '08 - Ardennes'), +('09', '09 - Ariege'), +('10', '10 - Aube'), +('11', '11 - Aude'), +('12', '12 - Aveyron'), +('13', '13 - Bouches-du-Rhone'), +('14', '14 - Calvados'), +('15', '15 - Cantal'), +('16', '16 - Charente'), +('17', '17 - Charente-Maritime'), +('18', '18 - Cher'), +('19', '19 - Correze'), +('21', '21 - Cote-d\'Or'), +('22', '22 - Cotes-d\'Armor'), +('23', '23 - Creuse'), +('24', '24 - Dordogne'), +('25', '25 - Doubs'), +('26', '26 - Drome'), +('27', '27 - Eure'), +('28', '28 - Eure-et-Loire'), +('29', '29 - Finistere'), +('2A', '2A - Corse-du-Sud'), +('2B', '2B - Haute-Corse'), +('30', '30 - Gard'), +('31', '31 - Haute-Garonne'), +('32', '32 - Gers'), +('33', '33 - Gironde'), +('34', '34 - Herault'), +('35', '35 - Ille-et-Vilaine'), +('36', '36 - Indre'), +('37', '37 - Indre-et-Loire'), +('38', '38 - Isere'), +('39', '39 - Jura'), +('40', '40 - Landes'), +('41', '41 - Loir-et-Cher'), +('42', '42 - Loire'), +('43', '43 - Haute-Loire'), +('44', '44 - Loire-Atlantique'), +('45', '45 - Loiret'), +('46', '46 - Lot'), +('47', '47 - Lot-et-Garonne'), +('48', '48 - Lozere'), +('49', '49 - Maine-et-Loire'), +('50', '50 - Manche'), +('51', '51 - Marne'), +('52', '52 - Haute-Marne'), +('53', '53 - Mayenne'), +('54', '54 - Meurthe-et-Moselle'), +('55', '55 - Meuse'), +('56', '56 - Morbihan'), +('57', '57 - Moselle'), +('58', '58 - Nievre'), +('59', '59 - Nord'), +('60', '60 - Oise'), +('61', '61 - Orne'), +('62', '62 - Pas-de-Calais'), +('63', '63 - Puy-de-Dome'), +('64', '64 - Pyrenees-Atlantiques'), +('65', '65 - Hautes-Pyrenees'), +('66', '66 - Pyrenees-Orientales'), +('67', '67 - Bas-Rhin'), +('68', '68 - Haut-Rhin'), +('69', '69 - Rhone'), +('70', '70 - Haute-Saone'), +('71', '71 - Saone-et-Loire'), +('72', '72 - Sarthe'), +('73', '73 - Savoie'), +('74', '74 - Haute-Savoie'), +('75', '75 - Paris'), +('76', '76 - Seine-Maritime'), +('77', '77 - Seine-et-Marne'), +('78', '78 - Yvelines'), +('79', '79 - Deux-Sevres'), +('80', '80 - Somme'), +('81', '81 - Tarn'), +('82', '82 - Tarn-et-Garonne'), +('83', '83 - Var'), +('84', '84 - Vaucluse'), +('85', '85 - Vendee'), +('86', '86 - Vienne'), +('87', '87 - Haute-Vienne'), +('88', '88 - Vosges'), +('89', '89 - Yonne'), +('90', '90 - Territoire de Belfort'), +('91', '91 - Essonne'), +('92', '92 - Hauts-de-Seine'), +('93', '93 - Seine-Saint-Denis'), +('94', '94 - Val-de-Marne'), +('95', '95 - Val-d\'Oise'), +('2A', '2A - Corse du sud'), +('2B', '2B - Haute Corse'), +('971', '971 - Guadeloupe'), +('972', '972 - Martinique'), +('973', '973 - Guyane'), +('974', '974 - La Reunion'), +('975', '975 - Saint-Pierre-et-Miquelon'), +('976', '976 - Mayotte'), +('984', '984 - Terres Australes et Antarctiques'), +('986', '986 - Wallis et Futuna'), +('987', '987 - Polynesie Francaise'), +('988', '988 - Nouvelle-Caledonie'), +) + diff --git a/tests/regressiontests/forms/tests.py b/tests/regressiontests/forms/tests.py index a9ce8d23b3..ab10d2f3e3 100644 --- a/tests/regressiontests/forms/tests.py +++ b/tests/regressiontests/forms/tests.py @@ -3556,6 +3556,226 @@ >>> f.clean('') u'' +# FRZipCodeField ############################################################# + +FRZipCodeField validates that the data is a valid FR zipcode. +>>> from django.contrib.localflavor.fr.forms import FRZipCodeField +>>> f = FRZipCodeField() +>>> f.clean('75001') +u'75001' +>>> f.clean('93200') +u'93200' +>>> f.clean('2A200') +Traceback (most recent call last): +... +ValidationError: [u'Enter a zip code in the format XXXXX.'] +>>> f.clean('980001') +Traceback (most recent call last): +... +ValidationError: [u'Enter a zip code in the format XXXXX.'] +>>> f.clean(None) +Traceback (most recent call last): +... +ValidationError: [u'This field is required.'] +>>> f.clean('') +Traceback (most recent call last): +... +ValidationError: [u'This field is required.'] + +>>> f = FRZipCodeField(required=False) +>>> f.clean('75001') +u'75001' +>>> f.clean('93200') +u'93200' +>>> f.clean('2A200') +Traceback (most recent call last): +... +ValidationError: [u'Enter a zip code in the format XXXXX.'] +>>> f.clean('980001') +Traceback (most recent call last): +... +ValidationError: [u'Enter a zip code in the format XXXXX.'] +>>> f.clean(None) +u'' +>>> f.clean('') +u'' + + +# FRPhoneNumberField ########################################################## + +FRPhoneNumberField validates that the data is a valid french phone number. +It's normalized to 0X XX XX XX XX format. Dots are valid too. +>>> from django.contrib.localflavor.fr.forms import FRPhoneNumberField +>>> f = FRPhoneNumberField() +>>> f.clean('01 55 44 58 64') +u'01 55 44 58 64' +>>> f.clean('0155445864') +u'01 55 44 58 64' +>>> f.clean('01 5544 5864') +u'01 55 44 58 64' +>>> f.clean('01 55.44.58.64') +u'01 55 44 58 64' +>>> f.clean('01.55.44.58.64') +u'01 55 44 58 64' +>>> f.clean('01,55,44,58,64') +Traceback (most recent call last): +... +ValidationError: [u'Phone numbers must be in 0X XX XX XX XX format.'] +>>> f.clean('555 015 544') +Traceback (most recent call last): +... +ValidationError: [u'Phone numbers must be in 0X XX XX XX XX format.'] +>>> f.clean(None) +Traceback (most recent call last): +... +ValidationError: [u'This field is required.'] +>>> f.clean('') +Traceback (most recent call last): +... +ValidationError: [u'This field is required.'] + +>>> f = FRPhoneNumberField(required=False) +>>> f.clean('01 55 44 58 64') +u'01 55 44 58 64' +>>> f.clean('0155445864') +u'01 55 44 58 64' +>>> f.clean('01 5544 5864') +u'01 55 44 58 64' +>>> f.clean('01 55.44.58.64') +u'01 55 44 58 64' +>>> f.clean('01.55.44.58.64') +u'01 55 44 58 64' +>>> f.clean('01,55,44,58,64') +Traceback (most recent call last): +... +ValidationError: [u'Phone numbers must be in 0X XX XX XX XX format.'] +>>> f.clean('555 015 544') +Traceback (most recent call last): +... +ValidationError: [u'Phone numbers must be in 0X XX XX XX XX format.'] +>>> f.clean(None) +u'' +>>> f.clean('') +u'' + +# FRDepartmentSelect ############################################################### + +FRDepartmentSelect is a Select widget that uses a list of french departments +including DOM TOM +>>> from django.contrib.localflavor.fr.forms import FRDepartmentSelect +>>> w = FRDepartmentSelect() +>>> print w.render('dep', 'Paris') + + ################################# # Tests of underlying functions # #################################