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
This commit is contained in:
James Bennett 2010-12-23 02:56:31 +00:00
parent f117b91b41
commit 5ed6e7a4d5
7 changed files with 312 additions and 15 deletions

View File

@ -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)

View File

@ -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")

View File

@ -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',

View File

@ -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``)
================

View File

@ -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
======================

View File

@ -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'

View File

@ -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):
<option value="">---------</option>
<option value="AL">Alabama</option>
<option value="AK">Alaska</option>
<option value="AS">American Samoa</option>
<option value="AZ">Arizona</option>
<option value="AR">Arkansas</option>
<option value="CA">California</option>
@ -34,7 +33,6 @@ class USLocalflavorTests(TestCase):
<option value="DC">District of Columbia</option>
<option value="FL">Florida</option>
<option value="GA" selected="selected">Georgia</option>
<option value="GU">Guam</option>
<option value="HI">Hawaii</option>
<option value="ID">Idaho</option>
<option value="IL">Illinois</option>
@ -59,12 +57,10 @@ class USLocalflavorTests(TestCase):
<option value="NY">New York</option>
<option value="NC">North Carolina</option>
<option value="ND">North Dakota</option>
<option value="MP">Northern Mariana Islands</option>
<option value="OH">Ohio</option>
<option value="OK">Oklahoma</option>
<option value="OR">Oregon</option>
<option value="PA">Pennsylvania</option>
<option value="PR">Puerto Rico</option>
<option value="RI">Rhode Island</option>
<option value="SC">South Carolina</option>
<option value="SD">South Dakota</option>
@ -72,11 +68,88 @@ class USLocalflavorTests(TestCase):
<option value="TX">Texas</option>
<option value="UT">Utah</option>
<option value="VT">Vermont</option>
<option value="VI">Virgin Islands</option>
<option value="VA">Virginia</option>
<option value="WA">Washington</option>
<option value="WV">West Virginia</option>
<option value="WI">Wisconsin</option>
<option value="WY">Wyoming</option>
<option value="AS">American Samoa</option>
<option value="GU">Guam</option>
<option value="MP">Northern Mariana Islands</option>
<option value="PR">Puerto Rico</option>
<option value="VI">Virgin Islands</option>
<option value="AA">Armed Forces Americas</option>
<option value="AE">Armed Forces Europe</option>
<option value="AP">Armed Forces Pacific</option>
</select>"""
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 = """\
<select name="postal_code" id="id_postal_code">
<option value="">---------</option>
<option value="AL">Alabama</option>
<option value="AK">Alaska</option>
<option value="AZ">Arizona</option>
<option value="AR">Arkansas</option>
<option value="CA">California</option>
<option value="CO">Colorado</option>
<option value="CT">Connecticut</option>
<option value="DE">Delaware</option>
<option value="DC">District of Columbia</option>
<option value="FL">Florida</option>
<option value="GA" selected="selected">Georgia</option>
<option value="HI">Hawaii</option>
<option value="ID">Idaho</option>
<option value="IL">Illinois</option>
<option value="IN">Indiana</option>
<option value="IA">Iowa</option>
<option value="KS">Kansas</option>
<option value="KY">Kentucky</option>
<option value="LA">Louisiana</option>
<option value="ME">Maine</option>
<option value="MD">Maryland</option>
<option value="MA">Massachusetts</option>
<option value="MI">Michigan</option>
<option value="MN">Minnesota</option>
<option value="MS">Mississippi</option>
<option value="MO">Missouri</option>
<option value="MT">Montana</option>
<option value="NE">Nebraska</option>
<option value="NV">Nevada</option>
<option value="NH">New Hampshire</option>
<option value="NJ">New Jersey</option>
<option value="NM">New Mexico</option>
<option value="NY">New York</option>
<option value="NC">North Carolina</option>
<option value="ND">North Dakota</option>
<option value="OH">Ohio</option>
<option value="OK">Oklahoma</option>
<option value="OR">Oregon</option>
<option value="PA">Pennsylvania</option>
<option value="RI">Rhode Island</option>
<option value="SC">South Carolina</option>
<option value="SD">South Dakota</option>
<option value="TN">Tennessee</option>
<option value="TX">Texas</option>
<option value="UT">Utah</option>
<option value="VT">Vermont</option>
<option value="VA">Virginia</option>
<option value="WA">Washington</option>
<option value="WV">West Virginia</option>
<option value="WI">Wisconsin</option>
<option value="WY">Wyoming</option>
<option value="AS">American Samoa</option>
<option value="GU">Guam</option>
<option value="MP">Northern Mariana Islands</option>
<option value="PR">Puerto Rico</option>
<option value="VI">Virgin Islands</option>
<option value="AA">Armed Forces Americas</option>
<option value="AE">Armed Forces Europe</option>
<option value="AP">Armed Forces Pacific</option>
<option value="FM">Federated States of Micronesia</option>
<option value="MH">Marshall Islands</option>
<option value="PW">Palau</option>
</select>"""
self.assertEqual(str(self.form['postal_code']), usps_select_html)