Ticket #17591: us_ssnfield_fix.patch

File us_ssnfield_fix.patch, 6.9 KB (added by aaron.l.madison@…, 12 years ago)

us ssn field patch

  • django/contrib/localflavor/us/forms.py

    IDEA additional info:
    Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
    <+>UTF-8
     
    1515
    1616phone_digits_re = re.compile(r'^(?:1-?)?(\d{3})[-\.]?(\d{3})[-\.]?(\d{4})$')
    1717ssn_re = re.compile(r"^(?P<area>\d{3})[-\ ]?(?P<group>\d{2})[-\ ]?(?P<serial>\d{4})$")
     18zeros_re = re.compile('^0+$')
    1819
    1920class USZipCodeField(RegexField):
    2021    default_error_messages = {
     
    5556          promotional use or distribution (e.g., the Woolworth's number or the
    5657          1962 promotional number).
    5758    """
     59
    5860    default_error_messages = {
    5961        'invalid': _('Enter a valid U.S. Social Security number in XXX-XX-XXXX format.'),
    6062    }
    6163
     64    def __init__(self, *args, **kwargs):
     65        self.no_hyphens = kwargs.pop("no_hyphens", False)
     66        super(USSocialSecurityNumberField, self).__init__(*args, **kwargs)
     67
    6268    def clean(self, value):
    63         super(USSocialSecurityNumberField, self).clean(value)
     69        value = super(USSocialSecurityNumberField, self).clean(value)
    6470        if value in EMPTY_VALUES:
    65             return u''
    66         match = re.match(ssn_re, value)
    67         if not match:
    68             raise ValidationError(self.error_messages['invalid'])
    69         area, group, serial = match.groupdict()['area'], match.groupdict()['group'], match.groupdict()['serial']
     71            return ''
    7072
    71         # First pass: no blocks of all zeroes.
    72         if area == '000' or \
    73            group == '00' or \
    74            serial == '0000':
    75             raise ValidationError(self.error_messages['invalid'])
     73        ssn_parts = self._get_ssn_parts(value)
     74        if self._is_invalid_ssn(ssn_parts):
     75            self._raise_invalid()
     76        return self._get_formatted_ssn(ssn_parts)
    7677
    77         # Second pass: promotional and otherwise permanently invalid numbers.
    78         if area == '666' or \
    79            (area == '987' and group == '65' and 4320 <= int(serial) <= 4329) or \
    80            value == '078-05-1120' or \
    81            value == '219-09-9999':
    82             raise ValidationError(self.error_messages['invalid'])
    83         return u'%s-%s-%s' % (area, group, serial)
     78    def _is_invalid_ssn(self, ssn_parts):
     79        return any([
     80            self._has_zero_blocks(**ssn_parts),
     81            self._is_invalid_area(ssn_parts['area']),
     82            self._is_in_promotional_range(**ssn_parts),
     83            self._is_known_invalid(**ssn_parts),
     84        ])
     85
     86    def _get_formatted_ssn(self, ssn_parts):
     87        ssn = "{area}-{group}-{serial}".format(**ssn_parts)
     88        if self.no_hyphens:
     89            ssn = ssn.replace('-', '')
     90        return ssn
     91
     92    def _get_ssn_parts(self, value):
     93        m = ssn_re.match(value)
     94        if not m:
     95            self._raise_invalid()
     96        return m.groupdict()
     97
     98    def _raise_invalid(self):
     99        raise ValidationError(self.error_messages['invalid'])
     100
     101    def _has_zero_blocks(self, area, group, serial):
     102        return any([self._is_all_zeros(p) for p in (area, group, serial)])
     103
     104    def _is_all_zeros(self, part):
     105        return zeros_re.match(part)
     106
     107    def _is_invalid_area(self, area):
     108        return bool(area == '666')
     109
     110    def _is_in_promotional_range(self, area, group, serial):
     111        return bool(area == '987' and group == '65' and 4320 <= int(serial) <= 4329)
     112
     113    def _is_known_invalid(self, area, group, serial):
     114        return any([
     115            bool(area == '078' and group == '05' and serial == '1120'),
     116            bool(area == '219' and group == '09' and serial == '9999'),
     117        ])
    84118
    85119class USStateField(Field):
    86120    """
  • tests/regressiontests/localflavor/us/tests.py

    IDEA additional info:
    Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
    <+>UTF-8
     
    44    USPhoneNumberField, USStateField, USStateSelect,
    55    USSocialSecurityNumberField)
    66from django.test import SimpleTestCase
     7from django.forms import ValidationError
    78
    89from .forms import USPlaceForm
    910
     
    1112class USLocalFlavorTests(SimpleTestCase):
    1213
    1314    def setUp(self):
     15        self.ssn_field = USSocialSecurityNumberField()
    1416        self.form = USPlaceForm({'state':'GA', 'state_req':'NC', 'postal_code': 'GA', 'name':'impossible'})
    1517
    1618    def test_get_display_methods(self):
     
    278280
    279281        valid = {
    280282            '987-65-4330': '987-65-4330',
    281             '987654330': '987-65-4330',
     283            '987654330':   '987-65-4330',
    282284        }
    283285        invalid = {
    284286            '078-05-1120': error_invalid,
     287            '078051120':   error_invalid,
     288            '219-09-9999': error_invalid,
     289            '219099999':   error_invalid,
     290            '000-12-1234': error_invalid,
     291            '123-00-1234': error_invalid,
     292            '123-12-0000': error_invalid,
    285293        }
    286294        self.assertFieldOutput(USSocialSecurityNumberField, valid, invalid)
     295
     296    def test_returns_value_when_valid_social_security_number(self):
     297        ssn = "123-45-6789"
     298        self.assertEqual(ssn, self.ssn_field.clean(ssn))
     299
     300    def test_forces_number_to_dashes_when_valid_and_not_remove_dashes(self):
     301        ssn = "123456789"
     302        self.assertEqual("123-45-6789", self.ssn_field.clean(ssn))
     303
     304    def test_forces_number_to_no_dashes_when_valid_and_remove_dashes(self):
     305        field = USSocialSecurityNumberField(no_hyphens=True)
     306        ssn = "123-45-6789"
     307        self.assertEqual("123456789", field.clean(ssn))
     308
     309    def test_is_not_valid_when_any_blocks_are_all_zero(self):
     310        with self.assertRaises(ValidationError):
     311            self.ssn_field.clean("000-12-1234")
     312
     313        with self.assertRaises(ValidationError):
     314            self.ssn_field.clean("123-00-1234")
     315
     316        with self.assertRaises(ValidationError):
     317            self.ssn_field.clean("123-12-0000")
     318
     319    def test_is_not_valid_when_area_is_666(self):
     320        with self.assertRaises(ValidationError):
     321            self.ssn_field.clean("666-12-1234")
     322
     323    def test_is_not_valid_when_in_promotional_range(self):
     324        # 987-65-(4320 - 4329) is invalid
     325        for x in range(4320, 4330):
     326            with self.assertRaises(ValidationError):
     327                self.ssn_field.clean("987-65-{area}".format(area=x))
     328        self.assertEqual("987-65-4330", self.ssn_field.clean("987-65-4330"))
     329
     330    def test_078_05_1120_is_invalid(self):
     331        with self.assertRaises(ValidationError):
     332            self.ssn_field.clean("078-05-1120")
     333
     334    def test_219_09_9999_is_invalid(self):
     335        with self.assertRaises(ValidationError):
     336            self.ssn_field.clean("219-09-9999")
Back to Top