diff --git a/django/contrib/localflavor/usa/forms.py b/django/contrib/localflavor/usa/forms.py
index 9461f4fe80..e61c03b702 100644
--- a/django/contrib/localflavor/usa/forms.py
+++ b/django/contrib/localflavor/usa/forms.py
@@ -9,6 +9,7 @@ from django.utils.translation import gettext
import re
phone_digits_re = re.compile(r'^(?:1-?)?(\d{3})[-\.]?(\d{3})[-\.]?(\d{4})$')
+ssn_re = re.compile(r"^(?P\d{3})[-\ ]?(?P\d{2})[-\ ]?(?P\d{4})$")
class USZipCodeField(RegexField):
def __init__(self, *args, **kwargs):
@@ -28,6 +29,47 @@ class USPhoneNumberField(Field):
return u'%s-%s-%s' % (m.group(1), m.group(2), m.group(3))
raise ValidationError(u'Phone numbers must be in XXX-XXX-XXXX format.')
+class USSocialSecurityNumberField(Field):
+ """
+ A United States Social Security number.
+
+ Checks the following rules to determine whether the number is valid:
+
+ * Conforms to the XXX-XX-XXXX format.
+ * No group consists entirely of zeroes.
+ * The leading group is not "666" (block "666" will never be allocated).
+ * The number is not in the promotional block 987-65-4320 through 987-65-4329,
+ which are permanently invalid.
+ * The number is not one known to be invalid due to otherwise widespread
+ promotional use or distribution (e.g., the Woolworth's number or the 1962
+ promotional number).
+
+ """
+ def clean(self, value):
+ super(USSocialSecurityNumberField, self).clean(value)
+ if value in EMPTY_VALUES:
+ return u''
+ msg = gettext(u'Enter a valid US Social Security number in XXX-XX-XXXX format')
+ match = re.match(ssn_re, value)
+ if not match:
+ raise ValidationError(msg)
+ area, group, serial = match.groupdict()['area'], match.groupdict()['group'], match.groupdict()['serial']
+
+ # First pass: no blocks of all zeroes.
+ if area == '000' or \
+ group == '00' or \
+ serial == '0000':
+ raise ValidationError(msg)
+
+ # Second pass: promotional and otherwise permanently invalid numbers.
+ if area == '666' or \
+ (area == '987' and group == '65' and \
+ 4320 <= int(serial) <= 4329) or \
+ value == '078-05-1120' or \
+ value == '219-09-9999':
+ raise ValidationError(msg)
+ return u'%s-%s-%s' % (area, group, serial)
+
class USStateField(Field):
"""
A form field that validates its input is a U.S. state name or abbreviation.
diff --git a/tests/regressiontests/forms/localflavor.py b/tests/regressiontests/forms/localflavor.py
index 8e3d9dff09..e05078ea46 100644
--- a/tests/regressiontests/forms/localflavor.py
+++ b/tests/regressiontests/forms/localflavor.py
@@ -246,6 +246,18 @@ as its choices.
+# USSocialSecurityNumberField #################################################
+>>> from django.contrib.localflavor.usa.forms import USSocialSecurityNumberField
+>>> f = USSocialSecurityNumberField()
+>>> f.clean('987-65-4330')
+u'987-65-4330'
+>>> f.clean('987654330')
+u'987-65-4330'
+>>> f.clean('078-05-1120')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a valid US Social Security number in XXX-XX-XXXX format']
+
# UKPostcodeField #############################################################
UKPostcodeField validates that the data is a valid UK postcode.