diff --git a/django/contrib/localflavor/usa/forms.py b/django/contrib/localflavor/usa/forms.py index 22e9f68a37..e56087545a 100644 --- a/django/contrib/localflavor/usa/forms.py +++ b/django/contrib/localflavor/usa/forms.py @@ -2,7 +2,8 @@ USA-specific Form helpers """ -from django.newforms.fields import RegexField +from django.newforms import ValidationError +from django.newforms.fields import Field, RegexField, EMPTY_VALUES from django.utils.translation import gettext class USZipCodeField(RegexField): @@ -11,3 +12,25 @@ class USZipCodeField(RegexField): max_length=None, min_length=None, error_message=gettext(u'Enter a zip code in the format XXXXX or XXXXX-XXXX.'), *args, **kwargs) + +class USStateField(Field): + """ + A form field that validates its input is a U.S. state name or abbreviation. + It normalizes the input to the standard two-leter postal service + abbreviation for the given state. + """ + def clean(self, value): + from us_states import STATES_NORMALIZED # relative import + super(USStateField, self).clean(value) + if value in EMPTY_VALUES: + return u'' + try: + value = value.strip().lower() + except AttributeError: + pass + else: + try: + return STATES_NORMALIZED[value.strip().lower()].decode('ascii') + except KeyError: + pass + raise ValidationError(u'Enter a U.S. state or territory.') diff --git a/django/contrib/localflavor/usa/us_states.py b/django/contrib/localflavor/usa/us_states.py new file mode 100644 index 0000000000..46bd0019e5 --- /dev/null +++ b/django/contrib/localflavor/usa/us_states.py @@ -0,0 +1,176 @@ +""" +A mapping of state misspellings/abbreviations to normalized abbreviations. + +This exists in this standalone file so that it's only imported into memory +when explicitly needed. +""" + +STATES_NORMALIZED = { + 'ak': 'AK', + 'al': 'AL', + 'ala': 'AL', + 'alabama': 'AL', + 'alaska': 'AK', + 'american samao': 'AS', + 'american samoa': 'AS', + 'ar': 'AR', + 'ariz': 'AZ', + 'arizona': 'AZ', + 'ark': 'AR', + 'arkansas': 'AR', + 'as': 'AS', + 'az': 'AZ', + 'ca': 'CA', + 'calf': 'CA', + 'calif': 'CA', + 'california': 'CA', + 'co': 'CO', + 'colo': 'CO', + 'colorado': 'CO', + 'conn': 'CT', + 'connecticut': 'CT', + 'ct': 'CT', + 'dc': 'DC', + 'de': 'DE', + 'del': 'DE', + 'delaware': 'DE', + 'district of columbia': 'DC', + 'federated states of micronesia': 'FM', + 'fl': 'FL', + 'fla': 'FL', + 'florida': 'FL', + 'fm': 'FM', + 'ga': 'GA', + 'georgia': 'GA', + 'gu': 'GU', + 'guam': 'GU', + 'hawaii': 'HI', + 'hi': 'HI', + 'ia': 'IA', + 'id': 'ID', + 'idaho': 'ID', + 'il': 'IL', + 'ill': 'IL', + 'illinois': 'IL', + 'in': 'IN', + 'ind': 'IN', + 'indiana': 'IN', + 'iowa': 'IA', + 'kan': 'KS', + 'kans': 'KS', + 'kansas': 'KS', + 'kentucky': 'KY', + 'ks': 'KS', + 'ky': 'KY', + 'la': 'LA', + 'louisiana': 'LA', + 'ma': 'MA', + 'maine': 'ME', + 'marianas islands': 'MP', + 'marianas islands of the pacific': 'MP', + 'marinas islands of the pacific': 'MP', + 'maryland': 'MD', + 'mass': 'MA', + 'massachusetts': 'MA', + 'massachussetts': 'MA', + 'md': 'MD', + 'me': 'ME', + 'mi': 'MI', + 'mich': 'MI', + 'michigan': 'MI', + 'micronesia': 'FM', + 'minn': 'MN', + 'minnesota': 'MN', + 'miss': 'MS', + 'mississippi': 'MS', + 'missouri': 'MO', + 'mn': 'MN', + 'mo': 'MO', + 'mont': 'MT', + 'montana': 'MT', + 'mp': 'MP', + 'ms': 'MS', + 'mt': 'MT', + 'n d': 'ND', + 'n dak': 'ND', + 'n h': 'NH', + 'n j': 'NJ', + 'n m': 'NM', + 'n mex': 'NM', + 'nc': 'NC', + 'nd': 'ND', + 'ne': 'NE', + 'neb': 'NE', + 'nebr': 'NE', + 'nebraska': 'NE', + 'nev': 'NV', + 'nevada': 'NV', + 'new hampshire': 'NH', + 'new jersey': 'NJ', + 'new mexico': 'NM', + 'new york': 'NY', + 'nh': 'NH', + 'nj': 'NJ', + 'nm': 'NM', + 'nmex': 'NM', + 'north carolina': 'NC', + 'north dakota': 'ND', + 'northern mariana islands': 'MP', + 'nv': 'NV', + 'ny': 'NY', + 'oh': 'OH', + 'ohio': 'OH', + 'ok': 'OK', + 'okla': 'OK', + 'oklahoma': 'OK', + 'or': 'OR', + 'ore': 'OR', + 'oreg': 'OR', + 'oregon': 'OR', + 'pa': 'PA', + 'penn': 'PA', + 'pennsylvania': 'PA', + 'pr': 'PR', + 'puerto rico': 'PR', + 'rhode island': 'RI', + 'ri': 'RI', + 's dak': 'SD', + 'sc': 'SC', + 'sd': 'SD', + 'sdak': 'SD', + 'south carolina': 'SC', + 'south dakota': 'SD', + 'tenn': 'TN', + 'tennessee': 'TN', + 'territory of hawaii': 'HI', + 'tex': 'TX', + 'texas': 'TX', + 'tn': 'TN', + 'tx': 'TX', + 'us virgin islands': 'VI', + 'usvi': 'VI', + 'ut': 'UT', + 'utah': 'UT', + 'va': 'VA', + 'vermont': 'VT', + 'vi': 'VI', + 'viginia': 'VA', + 'virgin islands': 'VI', + 'virgina': 'VA', + 'virginia': 'VA', + 'vt': 'VT', + 'w va': 'WV', + 'wa': 'WA', + 'wash': 'WA', + 'washington': 'WA', + 'west virginia': 'WV', + 'wi': 'WI', + 'wis': 'WI', + 'wisc': 'WI', + 'wisconsin': 'WI', + 'wv': 'WV', + 'wva': 'WV', + 'wy': 'WY', + 'wyo': 'WY', + 'wyoming': 'WY', +} \ No newline at end of file diff --git a/tests/regressiontests/forms/tests.py b/tests/regressiontests/forms/tests.py index 5c360a7272..63cb2f927e 100644 --- a/tests/regressiontests/forms/tests.py +++ b/tests/regressiontests/forms/tests.py @@ -3282,6 +3282,51 @@ u'' >>> f.clean('') u'' +# USStateField ################################################################ + +USStateField validates that the data is either an abbreviation or name of a +U.S. state. +>>> from django.contrib.localflavor.usa.forms import USStateField +>>> f = USStateField() +>>> f.clean('il') +u'IL' +>>> f.clean('IL') +u'IL' +>>> f.clean('illinois') +u'IL' +>>> f.clean(' illinois ') +u'IL' +>>> f.clean(60606) +Traceback (most recent call last): +... +ValidationError: [u'Enter a U.S. state or territory.'] +>>> 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 = USStateField(required=False) +>>> f.clean('il') +u'IL' +>>> f.clean('IL') +u'IL' +>>> f.clean('illinois') +u'IL' +>>> f.clean(' illinois ') +u'IL' +>>> f.clean(60606) +Traceback (most recent call last): +... +ValidationError: [u'Enter a U.S. state or territory.'] +>>> f.clean(None) +u'' +>>> f.clean('') +u'' + ################################# # Tests of underlying functions # #################################