From 5ed6e7a4d50eb5a2193133c67fd6fe8f6bbd0e5d Mon Sep 17 00:00:00 2001 From: James Bennett Date: Thu, 23 Dec 2010 02:56:31 +0000 Subject: [PATCH] Refactor the choices for localflavor's USStateField, and add new US postal code support. Fixes #14937 and #9022, refs #10308 and #8425. git-svn-id: http://code.djangoproject.com/svn/django/trunk@15029 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/contrib/localflavor/us/forms.py | 9 ++ django/contrib/localflavor/us/models.py | 10 ++ django/contrib/localflavor/us/us_states.py | 110 ++++++++++++++++-- docs/ref/contrib/localflavor.txt | 65 +++++++++++ docs/releases/1.3-beta-1.txt | 46 ++++++++ .../regressiontests/localflavor/us/models.py | 2 + tests/regressiontests/localflavor/us/tests.py | 85 +++++++++++++- 7 files changed, 312 insertions(+), 15 deletions(-) diff --git a/django/contrib/localflavor/us/forms.py b/django/contrib/localflavor/us/forms.py index c426d36c66..a41f7ab72f 100644 --- a/django/contrib/localflavor/us/forms.py +++ b/django/contrib/localflavor/us/forms.py @@ -111,3 +111,12 @@ class USStateSelect(Select): def __init__(self, attrs=None): from us_states import STATE_CHOICES super(USStateSelect, self).__init__(attrs, choices=STATE_CHOICES) + +class USPSSelect(Select): + """ + A Select widget that uses a list of US Postal Service codes as its + choices. + """ + def __init__(self, attrs=None): + from us_states import USPS_CHOICES + super(USPSSelect, self).__init__(attrs, choices=USPS_CHOICES) diff --git a/django/contrib/localflavor/us/models.py b/django/contrib/localflavor/us/models.py index 1f78e4504a..d5f797665a 100644 --- a/django/contrib/localflavor/us/models.py +++ b/django/contrib/localflavor/us/models.py @@ -2,6 +2,7 @@ from django.conf import settings from django.utils.translation import ugettext_lazy as _ from django.db.models.fields import CharField from django.contrib.localflavor.us.us_states import STATE_CHOICES +from django.contrib.localflavor.us.us_states import USPS_CHOICES class USStateField(CharField): @@ -12,6 +13,15 @@ class USStateField(CharField): kwargs['max_length'] = 2 super(USStateField, self).__init__(*args, **kwargs) +class USPostalCodeField(CharField): + + description = _("U.S. postal code (two uppercase letters)") + + def __init__(self, *args, **kwargs): + kwargs['choices'] = USPS_CHOICES + kwargs['max_length'] = 2 + super(USPostalCodeField, self).__init__(*args, **kwargs) + class PhoneNumberField(CharField): description = _("Phone number") diff --git a/django/contrib/localflavor/us/us_states.py b/django/contrib/localflavor/us/us_states.py index 59c48bb4d3..1174218cc0 100644 --- a/django/contrib/localflavor/us/us_states.py +++ b/django/contrib/localflavor/us/us_states.py @@ -1,15 +1,16 @@ """ -A mapping of state misspellings/abbreviations to normalized abbreviations, and -an alphabetical list of states for use as `choices` in a formfield. +A mapping of state misspellings/abbreviations to normalized +abbreviations, and alphabetical lists of US states, territories, +military mail regions and non-US states to which the US provides +postal service. This exists in this standalone file so that it's only imported into memory when explicitly needed. """ -STATE_CHOICES = ( +# The 48 contiguous states, plus the District of Columbia. +CONTIGUOUS_STATES = ( ('AL', 'Alabama'), - ('AK', 'Alaska'), - ('AS', 'American Samoa'), ('AZ', 'Arizona'), ('AR', 'Arkansas'), ('CA', 'California'), @@ -19,7 +20,60 @@ STATE_CHOICES = ( ('DC', 'District of Columbia'), ('FL', 'Florida'), ('GA', 'Georgia'), - ('GU', 'Guam'), + ('ID', 'Idaho'), + ('IL', 'Illinois'), + ('IN', 'Indiana'), + ('IA', 'Iowa'), + ('KS', 'Kansas'), + ('KY', 'Kentucky'), + ('LA', 'Louisiana'), + ('ME', 'Maine'), + ('MD', 'Maryland'), + ('MA', 'Massachusetts'), + ('MI', 'Michigan'), + ('MN', 'Minnesota'), + ('MS', 'Mississippi'), + ('MO', 'Missouri'), + ('MT', 'Montana'), + ('NE', 'Nebraska'), + ('NV', 'Nevada'), + ('NH', 'New Hampshire'), + ('NJ', 'New Jersey'), + ('NM', 'New Mexico'), + ('NY', 'New York'), + ('NC', 'North Carolina'), + ('ND', 'North Dakota'), + ('OH', 'Ohio'), + ('OK', 'Oklahoma'), + ('OR', 'Oregon'), + ('PA', 'Pennsylvania'), + ('RI', 'Rhode Island'), + ('SC', 'South Carolina'), + ('SD', 'South Dakota'), + ('TN', 'Tennessee'), + ('TX', 'Texas'), + ('UT', 'Utah'), + ('VT', 'Vermont'), + ('VA', 'Virginia'), + ('WA', 'Washington'), + ('WV', 'West Virginia'), + ('WI', 'Wisconsin'), + ('WY', 'Wyoming'), +) + +# All 50 states, plus the District of Columbia. +US_STATES = ( + ('AL', 'Alabama'), + ('AK', 'Alaska'), + ('AZ', 'Arizona'), + ('AR', 'Arkansas'), + ('CA', 'California'), + ('CO', 'Colorado'), + ('CT', 'Connecticut'), + ('DE', 'Delaware'), + ('DC', 'District of Columbia'), + ('FL', 'Florida'), + ('GA', 'Georgia'), ('HI', 'Hawaii'), ('ID', 'Idaho'), ('IL', 'Illinois'), @@ -44,12 +98,10 @@ STATE_CHOICES = ( ('NY', 'New York'), ('NC', 'North Carolina'), ('ND', 'North Dakota'), - ('MP', 'Northern Mariana Islands'), ('OH', 'Ohio'), ('OK', 'Oklahoma'), ('OR', 'Oregon'), ('PA', 'Pennsylvania'), - ('PR', 'Puerto Rico'), ('RI', 'Rhode Island'), ('SC', 'South Carolina'), ('SD', 'South Dakota'), @@ -57,7 +109,6 @@ STATE_CHOICES = ( ('TX', 'Texas'), ('UT', 'Utah'), ('VT', 'Vermont'), - ('VI', 'Virgin Islands'), ('VA', 'Virginia'), ('WA', 'Washington'), ('WV', 'West Virginia'), @@ -65,6 +116,47 @@ STATE_CHOICES = ( ('WY', 'Wyoming'), ) +# Non-state territories. +US_TERRITORIES = ( + ('AS', 'American Samoa'), + ('GU', 'Guam'), + ('MP', 'Northern Mariana Islands'), + ('PR', 'Puerto Rico'), + ('VI', 'Virgin Islands'), +) + +# Military postal "states". Note that 'AE' actually encompasses +# Europe, Canada, Africa and the Middle East. +ARMED_FORCES_STATES = ( + ('AA', 'Armed Forces Americas'), + ('AE', 'Armed Forces Europe'), + ('AP', 'Armed Forces Pacific'), +) + +# Non-US locations serviced by USPS (under Compact of Free +# Association). +COFA_STATES = ( + ('FM', 'Federated States of Micronesia'), + ('MH', 'Marshall Islands'), + ('PW', 'Palau'), +) + +# Obsolete abbreviations (no longer US territories/USPS service, or +# code changed). +OBSOLETE_STATES = ( + ('CM', 'Commonwealth of the Northern Mariana Islands'), # Is now 'MP' + ('CZ', 'Panama Canal Zone'), # Reverted to Panama 1979 + ('PI', 'Philippine Islands'), # Philippine independence 1946 + ('TT', 'Trust Territory of the Pacific Islands'), # Became the independent COFA states + Northern Mariana Islands 1979-1994 +) + + +# All US states and territories plus DC and military mail. +STATE_CHOICES = US_STATES + US_TERRITORIES + ARMED_FORCES_STATES + +# All US Postal Service locations. +USPS_CHOICES = US_STATES + US_TERRITORIES + ARMED_FORCES_STATES + COFA_STATES + STATES_NORMALIZED = { 'ak': 'AK', 'al': 'AL', diff --git a/docs/ref/contrib/localflavor.txt b/docs/ref/contrib/localflavor.txt index aabc98c711..f54341ee6e 100644 --- a/docs/ref/contrib/localflavor.txt +++ b/docs/ref/contrib/localflavor.txt @@ -937,6 +937,11 @@ United States of America (``us``) A form ``Select`` widget that uses a list of U.S. states/territories as its choices. +.. class:: us.forms.USPSSelect + + A form ``Select`` widget that uses a list of U.S Postal Service + state, territory and country abbreviations as its choices. + .. class:: us.models.PhoneNumberField A :class:`CharField` that checks that the value is a valid U.S.A.-style phone @@ -947,6 +952,66 @@ United States of America (``us``) A model field that forms represent as a ``forms.USStateField`` field and stores the two-letter U.S. state abbreviation in the database. +.. class:: us.models.USPostalCodeField + + A model field that forms represent as a ``forms.USPSSelect`` field + and stores the two-letter U.S Postal Service abbreviation in the + database. + +Additionally, a variety of choice tuples are provided in +``django.contrib.localflavor.us.us_states``, allowing customized model +and form fields, and form presentations, for subsets of U.S states, +territories and U.S Postal Service abbreviations: + +.. data:: us.us_states.CONTIGUOUS_STATES + + A tuple of choices of the postal abbreviations for the + contiguous or "lower 48" states (i.e., all except Alaska and + Hawaii), plus the District of Columbia. + +.. data:: us.us_states.US_STATES + + A tuple of choices of the postal abbreviations for all + 50 U.S. states, plus the District of Columbia. + +.. data:: us.us_states.US_TERRITORIES + + A tuple of choices of the postal abbreviations for U.S + territories: American Samoa, Guam, the Northern Mariana Islands, + Puerto Rico and the U.S. Virgin Islands. + +.. data:: us.us_states.ARMED_FORCES_STATES + + A tuple of choices of the postal abbreviations of the three U.S + military postal "states": Armed Forces Americas, Armed Forces + Europe and Armed Forces Pacific. + +.. data:: us.us_states.COFA_STATES + + A tuple of choices of the postal abbreviations of the three + independent nations which, under the Compact of Free Association, + are served by the U.S. Postal Service: the Federated States of + Micronesia, the Marshall Islands and Palau. + +.. data:: us.us_states.OBSOLETE_STATES + + A tuple of choices of obsolete U.S Postal Service state + abbreviations: the former abbreviation for the Northern Mariana + Islands, plus the Panama Canal Zone, the Philippines and the + former Pacific trust territories. + +.. data:: us.us_states.STATE_CHOICES + + A tuple of choices of all postal abbreviations corresponding to U.S states or + territories, and the District of Columbia.. + +.. data:: us.us_states.USPS_CHOICES + + A tuple of choices of all postal abbreviations recognized by the + U.S Postal Service (including all states and territories, the + District of Columbia, armed forces "states" and independent + nations serviced by USPS). + Uruguay (``uy``) ================ diff --git a/docs/releases/1.3-beta-1.txt b/docs/releases/1.3-beta-1.txt index 342136cd2f..749a68b14a 100644 --- a/docs/releases/1.3-beta-1.txt +++ b/docs/releases/1.3-beta-1.txt @@ -120,6 +120,52 @@ attribute. .. _r12634: http://code.djangoproject.com/changeset/12634 +Changes to ``USStateField`` +=========================== + +The :mod:`django.contrib.localflavor` application contains collections +of code relevant to specific countries or cultures. One such is +:class:`~django.contrib.localflavor.us.models.USStateField`, which +provides a field for storing the two-letter postal abbreviation of a +U.S. state. This field has consistently caused problems, however, +because it is often used to store the state portion of a U.S postal +address, but not all "states" recognized by the U.S Postal Service are +actually states of the U.S. or even U.S. territory. Several +compromises over the list of choices resulted in some users feeling +the field supported too many locations, while others felt it supported +too few. + +In Django 1.3 we're taking a new approach to this problem, implemented +as a pair of changes: + +* The choice list for `USStateField` has changed. Previously, it + consisted of the 50 U.S. states, the District of Columbia and + U.S. overseas territories. As of Django 1.3 it includes all previous + choices, plus the U.S. Armed Forces postal codes. + +* A new model field, + :class:`django.contrib.localflavor.us.models.USPostalCodeField`, has + been added which draws its choices from a list of all postal + abbreviations recognized by the U.S Postal Service. This includes + all abbreviations recognized by `USStateField`, plus three + independent nations -- the Federated States of Micronesia, the + Republic of the Marshall Islands and the Republic of Palau -- which + are serviced under treaty by the U.S. postal system. A new form + widget, :class:`django.contrib.localflavor.us.forms.USPSSelect`, is + also available and provides the same set of choices. + +Additionally, several finer-grained choice tuples are provided which +allow mixing and matching of subsets of the U.S. states and +territories, and other locations serviced by the U.S. postal +system. Consult the :mod:`django.contrib.localflavor` documentation +for more details. + +The change to `USStateField` is technically backwards-incompatible for +users who expect this field to exclude Armed Forces locations. If you +need to support U.S. mailing addresses without Armed Forces locations, +see the list of choice tuples available in the localflavor +documentation. + The Django 1.3 roadmap ====================== diff --git a/tests/regressiontests/localflavor/us/models.py b/tests/regressiontests/localflavor/us/models.py index a8a4cf0f50..7ac3826b2f 100644 --- a/tests/regressiontests/localflavor/us/models.py +++ b/tests/regressiontests/localflavor/us/models.py @@ -1,5 +1,6 @@ from django.db import models from django.contrib.localflavor.us.models import USStateField +from django.contrib.localflavor.us.models import USPostalCodeField # When creating models you need to remember to add a app_label as # 'localflavor', so your model can be found @@ -8,6 +9,7 @@ class USPlace(models.Model): state = USStateField(blank=True) state_req = USStateField() state_default = USStateField(default="CA", blank=True) + postal_code = USPostalCodeField(blank=True) name = models.CharField(max_length=20) class Meta: app_label = 'localflavor' diff --git a/tests/regressiontests/localflavor/us/tests.py b/tests/regressiontests/localflavor/us/tests.py index 07fe057833..8df541a8cc 100644 --- a/tests/regressiontests/localflavor/us/tests.py +++ b/tests/regressiontests/localflavor/us/tests.py @@ -3,7 +3,7 @@ from forms import USPlaceForm class USLocalflavorTests(TestCase): def setUp(self): - self.form = USPlaceForm({'state':'GA', 'state_req':'NC', 'name':'impossible'}) + self.form = USPlaceForm({'state':'GA', 'state_req':'NC', 'postal_code': 'GA', 'name':'impossible'}) def test_get_display_methods(self): """Test that the get_*_display() methods are added to the model instances.""" @@ -24,7 +24,6 @@ class USLocalflavorTests(TestCase): - @@ -34,7 +33,6 @@ class USLocalflavorTests(TestCase): - @@ -59,12 +57,10 @@ class USLocalflavorTests(TestCase): - - @@ -72,11 +68,88 @@ class USLocalflavorTests(TestCase): - + + + + + + + + """ self.assertEqual(str(self.form['state']), state_select_html) + + def test_full_postal_code_list(self): + """Test that the full USPS code field is really the full list.""" + usps_select_html = """\ +""" + self.assertEqual(str(self.form['postal_code']), usps_select_html)