- Author:
- nathan-reynolds
- Posted:
- November 20, 2008
- Language:
- Python
- Version:
- 1.0
- Score:
- 1 (after 1 ratings)
Validates and cleans UK telephone numbers. Number length is checked, and numbers are cleaned into a common format. For example, "+44 (0)1234 567890" will be stored as "01234 567890"
Can reject premium numbers (09123 123123) or service numbers (1471, 118 118) with UKPhoneNumberField(reject=('premium', 'service'))
Uses info from Wikipedia
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 | from django.forms import ValidationError
from django.forms.fields import Field, EMPTY_VALUES
from django.utils.encoding import smart_unicode
from django.utils.translation import ugettext_lazy as _
import re
import types
class UKPhoneNumberField(Field):
default_error_messages = {
'partial': _('Phone number must include an area code.'),
'non_uk': _('Phone number must be a UK number.'),
'length_range': _('Phone number must be between %d and %d digits'),
'length': _('Phone number must be %d digits'),
'reject_premium': _('Phone number can\'t be premium rate.'),
'reject_service': _('Phone number can\'t be a service number.')
}
number_specs = (
(r'^01(1[^1]|[^1]1)', None, (4, 3, 4)),
(r'^01', None, (5, (5, 6))),
(r'^0500', None, (4, 6)),
(r'^0[235]', None, (3, 4, 4)),
(r'^07', None, (5, 6)),
(r'^(08001111|08454647)$', None, (4, 4)),
(r'^08', None, (4, 7)),
(r'^09', 'premium', (4, 6)),
(r'^118', 'service', (3, 3)),
(r'^999$', 'service', (3,)),
(r'^1', 'service', None),
)
def __init__(self, *args, **kwargs):
self.reject = set(kwargs.pop('reject', ()))
super(UKPhoneNumberField, self).__init__(*args, **kwargs)
def clean(self, value):
super(UKPhoneNumberField, self).clean(value)
value = smart_unicode(value)
if value in EMPTY_VALUES:
return u''
value = re.sub(r'[^0-9+]', r'', value)
value = re.sub(r'(?<!^)\+', r'', value)
value = re.sub(r'^\+44(?=[1-9])', r'0', value)
value = re.sub(r'^\+44(?=0)', r'', value)
if re.match(r'^(\+(?!44)|00)', value):
raise ValidationError(self.error_messages['non_uk'])
number_spec = self.get_number_spec(value)
if not number_spec:
raise ValidationError(self.error_messages['partial'])
if number_spec[0] in self.reject:
raise ValidationError(self.error_messages['reject_%s' % number_spec[0]])
if not self.valid_length(value, number_spec):
min_length, max_length = self.spec_lengths(number_spec)
if min_length == max_length:
raise ValidationError(self.error_messages['length']
% min_length)
else:
raise ValidationError(self.error_messages['length_range']
% (min_length, max_length))
return self.format_number(value, number_spec)
def get_number_spec(self, value):
for number_spec in self.number_specs:
if re.match(number_spec[0], value):
return number_spec[1:]
return None
def spec_lengths(self, number_spec):
if not number_spec[1]:
return None, None
if type(number_spec[1][-1]) == types.TupleType:
min_length, max_length = number_spec[1][-1]
total = sum(number_spec[1][:-1])
min_length += total
max_length += total
else:
min_length = max_length = sum(number_spec[1])
return min_length, max_length
def valid_length(self, value, number_spec):
min_length, max_length = self.spec_lengths(number_spec)
if min_length is not None and len(value) < min_length: return False
if max_length is not None and len(value) > max_length: return False
return True
def format_number(self, value, number_spec):
if number_spec[1] is None:
components = (value,)
else:
components = []
position = 0
last_index = len(number_spec) - 1
for index, chunk in enumerate(number_spec[1]):
if index == last_index:
components.append(value[position:])
else:
components.append(value[position:position+chunk])
position += chunk
return ' '.join(components)
|
More like this
- Template tag - list punctuation for a list of items by shapiromatron 10 months, 1 week ago
- JSONRequestMiddleware adds a .json() method to your HttpRequests by cdcarter 10 months, 2 weeks ago
- Serializer factory with Django Rest Framework by julio 1 year, 5 months ago
- Image compression before saving the new model / work with JPG, PNG by Schleidens 1 year, 6 months ago
- Help text hyperlinks by sa2812 1 year, 6 months ago
Comments
This is quite impressive. I would recommend posting this to a Django ticket for inclusion in django.contrib.localflavor.uk.forms
I'm sure this would be a welcome addition for many, and would help bring the UK localflavor more into line with the US
#
I added:
So I could do things like:
reject=('premium', 'service', 'geo', 'nongeo')
to only get mobiles. Not perfect but serviceable. :)
Excellent snippet - thanks!
#
If you will try to use custom error messages, exception could be raised because of hardcoded string formatting. You should change this "default_error_messages":
'length_range': _('Phone number must be between %(min)d and %(max)d digits'),
'length': _('Phone number must be %(min)d digits'),
and in the "clean" method:
raise ValidationError(self.error_messages['length'] % {'min':min_length})
and:
raise ValidationError(self.error_messages['length_range'] % {'min':min_length, 'max':max_length})
#
Please login first before commenting.