diff --git a/README.rst b/README.rst index 4ea5e79..00f62a0 100644 --- a/README.rst +++ b/README.rst @@ -41,19 +41,29 @@ Dependencies ============ django-countries (http://pypi.python.org/pypi/django-countries) +django-localflavor (http://pypi.python.org/pypi/django-localflavor) Usage ===== -1. Add django-countries and django-postal to your ``INSTALLED_APPS`` in ``settings.py`` +1. Add django-countries, django-localflavor and django-postal to your ``INSTALLED_APPS`` in ``settings.py`` e.g.:: INSTALLED_APPS = ( - "countries", + "django_countries", + "localflavor", "postal", ... ) +2. Include the postal urls in your main ``urls.py`` file:: + + urlpatterns = [ + # ... + url(r'^postal/', include('postal.urls')), + # ... + ] + 3. Add a ``postal_form`` to your templates:: some_template.html @@ -77,7 +87,7 @@ e.g.:: Changing the country in the form above should localise the address form. -3. In your view code add code to save the addressform e.g.:: +4. In your view code add code to save the addressform e.g.:: from postal.forms import PostalAddressForm diff --git a/setup.py b/setup.py index 8299298..ed39817 100644 --- a/setup.py +++ b/setup.py @@ -20,7 +20,7 @@ def read(fname): package_dir={'': 'src'}, package_data={'': ['*.txt', '*.js', '*.html', '*.*', 'templates/postal/*.html']}, - install_requires=['setuptools', 'django-countries'], + install_requires=['setuptools', 'django-countries', 'django-localflavor'], classifiers=[ 'Development Status :: 3 - Alpha', diff --git a/src/postal/api/handlers.py b/src/postal/api/handlers.py index d743118..a0425ba 100644 --- a/src/postal/api/handlers.py +++ b/src/postal/api/handlers.py @@ -5,7 +5,7 @@ class PostalHandler(BaseHandler): allowed_methods = ('GET',) - def read(self, request): + def read(self, request): iso_code = request.GET.get('country', '') json = {} form_class = form_factory(country_code=iso_code) @@ -13,6 +13,6 @@ def read(self, request): for k, v in form_obj.fields.items(): if k not in json.keys(): json[k] = {} - json[k]['label'] = unicode(v.label) + json[k]['label'] = str(v.label) json[k]['widget'] = v.widget.render(k, "", attrs={'id': 'id_' + k}) return json diff --git a/src/postal/forms/__init__.py b/src/postal/forms/__init__.py index 0ac78ab..b1e737b 100644 --- a/src/postal/forms/__init__.py +++ b/src/postal/forms/__init__.py @@ -2,10 +2,30 @@ from django.utils.translation import ugettext_lazy as _ from django_countries import data as country_data + from postal.settings import POSTAL_ADDRESS_LINE1, POSTAL_ADDRESS_LINE2, POSTAL_ADDRESS_CITY, POSTAL_ADDRESS_STATE, \ - POSTAL_ADDRESS_CODE + POSTAL_ADDRESS_CODE, POSTAL_USE_CRISPY_FORMS + +if POSTAL_USE_CRISPY_FORMS: + from crispy_forms.helper import FormHelper + from crispy_forms.layout import Layout, Div, Hidden + + +def country_sort_key(country_data): + if country_data[0] == 'US': + return 'AAA' + if country_data[0] == 'CA': + return 'AAAA' + return country_data[1] + -country_list = [('', '-'*45)] + country_data.COUNTRIES.items() +country_list = sorted([('', '-' * 45)] + list(country_data.COUNTRIES.items()), key=country_sort_key) + +form_helpers = {} + + +def register_postal_form_helper(form_id, form_helper): + form_helpers[form_id] = form_helper class PostalAddressForm(forms.Form): @@ -16,6 +36,33 @@ class PostalAddressForm(forms.Form): code = forms.CharField(label=POSTAL_ADDRESS_CODE[0], required=POSTAL_ADDRESS_CODE[1], max_length=100) country = forms.ChoiceField(label=_(u"Country"), choices=country_list) + def __init__(self, *args, **kwargs): + prefix = kwargs.pop('prefix', None) + postal_form_id = kwargs.pop('postal_form_id', 'postal-address-form') + if POSTAL_USE_CRISPY_FORMS: + css_id = 'postal_address' + if prefix is not None: + css_id = prefix + '-' + css_id + if postal_form_id in form_helpers: + self.helper = form_helpers[postal_form_id] + else: + self.helper = FormHelper() + self.helper.form_tag = False + self.helper.layout = Layout( + Div( + 'country', + 'line1', + 'line2', + 'city', + 'state', + 'code', + css_id=css_id, + css_class='postal_address' + ), + Hidden('postal-form-id', postal_form_id), + ) + super(PostalAddressForm, self).__init__(*args, **kwargs) + def clean_country(self): data = self.cleaned_data['country'] if data not in country_data.COUNTRIES.keys(): diff --git a/src/postal/forms/au/__init__.py b/src/postal/forms/au/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/postal/forms/au/forms.py b/src/postal/forms/au/forms.py new file mode 100644 index 0000000..c3aca28 --- /dev/null +++ b/src/postal/forms/au/forms.py @@ -0,0 +1,17 @@ +""" http://www.bitboost.com/ref/international-address-formats.html """ +from django import forms +from django.utils.translation import ugettext_lazy as _ +from localflavor.au.forms import AUPostCodeField, AUStateSelect + +from postal.forms import PostalAddressForm + +class AUPostalAddressForm(PostalAddressForm): + line1 = forms.CharField(label=_(u"Street"), max_length=50) + line2 = forms.CharField(label=_(u"Street (con\'t)"), required=False, max_length=100) + city = forms.CharField(label=_(u"City"), max_length=50) + state = forms.CharField(label=_(u"US State"), widget=AUStateSelect) + code = AUPostCodeField(label=_(u"Zip Code")) + + def __init__(self, *args, **kwargs): + super(AUPostalAddressForm, self).__init__(*args, **kwargs) + self.fields['country'].initial = "AU" diff --git a/src/postal/forms/ca/__init__.py b/src/postal/forms/ca/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/postal/forms/ca/forms.py b/src/postal/forms/ca/forms.py new file mode 100644 index 0000000..6dca1db --- /dev/null +++ b/src/postal/forms/ca/forms.py @@ -0,0 +1,18 @@ +""" http://www.bitboost.com/ref/international-address-formats.html """ +from django import forms +from django.utils.translation import ugettext_lazy as _ +from localflavor.ca.forms import CAPostalCodeField, CAProvinceField, CAProvinceSelect + +from postal.forms import PostalAddressForm + + +class CAPostalAddressForm(PostalAddressForm): + line1 = forms.CharField(label=_(u"Street Address"), max_length=50) + line2 = forms.CharField(label=_(u"Street Address (con\'t)"), required=False, max_length=100) + city = forms.CharField(label=_(u"City"), max_length=50) + state = CAProvinceField(label=_(u"Province"), widget=CAProvinceSelect) + code = CAPostalCodeField(label=_(u"Postal Code")) + + def __init__(self, *args, **kwargs): + super(CAPostalAddressForm, self).__init__(*args, **kwargs) + self.fields['country'].initial = "CA" diff --git a/src/postal/forms/nl/forms.py b/src/postal/forms/nl/forms.py index 28d43e8..951bc28 100644 --- a/src/postal/forms/nl/forms.py +++ b/src/postal/forms/nl/forms.py @@ -1,14 +1,25 @@ from django import forms from django.utils.translation import ugettext_lazy as _ +from django.utils import six from localflavor.nl.forms import NLZipCodeField from postal.forms import PostalAddressForm +class MyNLZipCodeField(NLZipCodeField): + def clean(self, value): + if isinstance(value, six.string_types): + value = value.upper().replace(' ', '') + + # don't strip the spaces out of the zipcode, it confuses + # the geocoders + return super(NLZipCodeField, self).clean(value) + + class NLPostalAddressForm(PostalAddressForm): - line1 = forms.CharField(label=_(u"Street"), required=False, max_length=100) + line1 = forms.CharField(label=_(u"Street"), max_length=100) line2 = forms.CharField(label=_(u"Area"), required=False, max_length=100) - city = forms.CharField(label=_(u"Town/City"), required=False, max_length=100) - code = NLZipCodeField(label=_(u"Zip Code")) + city = forms.CharField(label=_(u"Town/City"), max_length=100) + code = MyNLZipCodeField(label=_(u"Zip Code")) class Meta: diff --git a/src/postal/forms/us/forms.py b/src/postal/forms/us/forms.py index 8986c2e..b546ebf 100644 --- a/src/postal/forms/us/forms.py +++ b/src/postal/forms/us/forms.py @@ -6,8 +6,8 @@ from postal.forms import PostalAddressForm class USPostalAddressForm(PostalAddressForm): - line1 = forms.CharField(label=_(u"Street"), max_length=50) - line2 = forms.CharField(label=_(u"Area"), required=False, max_length=100) + line1 = forms.CharField(label=_(u"Street Address"), max_length=50) + line2 = forms.CharField(label=_(u"Street Address (con\'t)"), required=False, max_length=100) city = forms.CharField(label=_(u"City"), max_length=50) state = USStateField(label=_(u"US State"), widget=USStateSelect) code = USZipCodeField(label=_(u"Zip Code")) diff --git a/src/postal/library.py b/src/postal/library.py index f8f4f23..3996755 100644 --- a/src/postal/library.py +++ b/src/postal/library.py @@ -1,4 +1,3 @@ -from django import forms from postal import settings as postal_settings from postal.forms import PostalAddressForm from postal.forms.ar.forms import ARPostalAddressForm @@ -13,9 +12,12 @@ from postal.forms.pl.forms import PLPostalAddressForm from postal.forms.ru.forms import RUPostalAddressForm from postal.forms.us.forms import USPostalAddressForm +from postal.forms.ca.forms import CAPostalAddressForm +from postal.forms.au.forms import AUPostalAddressForm # TODO: Auto-import these forms country_map = { + "ca": CAPostalAddressForm, "co": COPostalAddressForm, "cz": CZPostalAddressForm, "de": DEPostalAddressForm, @@ -28,6 +30,7 @@ "ru": RUPostalAddressForm, "us": USPostalAddressForm, "ar": ARPostalAddressForm, + "au": AUPostalAddressForm, } diff --git a/src/postal/resource.py b/src/postal/resource.py index df1fbfb..91deecd 100644 --- a/src/postal/resource.py +++ b/src/postal/resource.py @@ -5,10 +5,10 @@ import inspect import re import sys -from django.core.urlresolvers import NoReverseMatch -from django.db.models import Model, permalink +from django.urls import NoReverseMatch, reverse +from django.db.models import Model from django.http import HttpResponse, HttpResponseNotAllowed, HttpResponseServerError -from django.utils.encoding import smart_unicode +from django.utils.encoding import smart_text from django.views.debug import ExceptionReporter from django.views.decorators.vary import vary_on_headers from django.conf import settings @@ -66,7 +66,7 @@ def construct(self): """ Recursively serialize a lot of types, and in cases where it doesn't recognize the type, - it will fall back to Django's `smart_unicode`. + it will fall back to Django's `smart_text`. Returns `dict`. """ @@ -98,7 +98,7 @@ def _any(thing, fields=None): elif repr(thing).startswith(" +{{form.as_p}} + \ No newline at end of file diff --git a/src/postal/templates/postal/monitor_country_change.html b/src/postal/templates/postal/monitor_country_change.html index e9720ca..52a7f06 100644 --- a/src/postal/templates/postal/monitor_country_change.html +++ b/src/postal/templates/postal/monitor_country_change.html @@ -7,7 +7,7 @@ data: $(form).serialize(), success: function(response) { var data = JSON.parse(response); - holder.html(data['postal_address'] + "{%csrf_token%}"); + $(holder).replaceWith(data['postal_address']); }, error: function(response) { alert(response); @@ -16,11 +16,22 @@ } $.fn.monitor_country_change = function(country_selector){ - var holder = this; - $(country_selector).live('change', function() { + $('body').on('change', country_selector, function() { + var holder = $(this).closest('div.postal_address'); var form = $(this).closest('form'); change_form(form, holder); }); + + window.addEventListener( "pageshow", function ( event ) { + var historyTraversal = event.persisted || + ( typeof window.performance != "undefined" && + window.performance.navigation.type === 2 ); + if ( historyTraversal ) { + var holder = $(country_selector).closest('div.postal_address'); + var form = $(country_selector).closest('form'); + change_form(form, holder); + } + }); } })(jQuery); \ No newline at end of file diff --git a/src/postal/templatetags/postal_tags.py b/src/postal/templatetags/postal_tags.py index b9d9d0b..d3e751a 100644 --- a/src/postal/templatetags/postal_tags.py +++ b/src/postal/templatetags/postal_tags.py @@ -1,5 +1,5 @@ from django import template -from django.core.urlresolvers import reverse +from django.urls import reverse register = template.Library() diff --git a/src/postal/tests/__init__.py b/src/postal/tests/__init__.py index fe9624b..e69de29 100644 --- a/src/postal/tests/__init__.py +++ b/src/postal/tests/__init__.py @@ -1,2 +0,0 @@ -from test_l10n import * -from test_widgets import * \ No newline at end of file diff --git a/src/postal/tests/test_l10n.py b/src/postal/tests/test_l10n.py index 4c23dc9..65b411e 100644 --- a/src/postal/tests/test_l10n.py +++ b/src/postal/tests/test_l10n.py @@ -1,6 +1,7 @@ from django.test import TestCase from django.utils.translation import ugettext from django import forms +from importlib import reload from postal.library import form_factory import postal.settings @@ -46,7 +47,7 @@ def test_get_de_address(self): form = german_form_class(data=test_data) self.assertEqual(form.fields['line1'].label.lower(), "street") - self.assertEqual(form.fields.has_key('line2'), False) + self.assertEqual(('line2' in form.fields), False) self.assertEqual(form.fields['city'].label.lower(), "city") self.assertEqual(form.fields['code'].label.lower(), "zip code") diff --git a/src/postal/utils.py b/src/postal/utils.py index e1f26f4..997be18 100644 --- a/src/postal/utils.py +++ b/src/postal/utils.py @@ -28,7 +28,7 @@ class RcFactory(object): def __getattr__(self, attr): """ - Returns a fresh `HttpResponse` when getting + Returns a fresh `HttpResponse` when getting an "attribute". This is backwards compatible with 0.2, which is important. """ @@ -39,29 +39,39 @@ def __getattr__(self, attr): class HttpResponseWrapper(HttpResponse): """ - Wrap HttpResponse and make sure that the internal _is_string - flag is updated when the _set_content method (via the content + Wrap HttpResponse and make sure that the internal _is_string + flag is updated when the _set_content method (via the content property) is called """ + def _set_content(self, content): """ - Set the _container and _is_string properties based on the + Set the _container and _is_string properties based on the type of the value parameter. This logic is in the construtor - for HttpResponse, but doesn't get repeated when setting + for HttpResponse, but doesn't get repeated when setting HttpResponse.content although this bug report (feature request) - suggests that it should: http://code.djangoproject.com/ticket/9403 + suggests that it should: http://code.djangoproject.com/ticket/9403 """ - if not isinstance(content, basestring) and hasattr(content, '__iter__'): + if not isinstance(content, str) and hasattr(content, '__iter__'): self._container = content self._is_string = False else: - self._container = [content] + self._container = [str.encode(content)] self._is_string = True - content = property(HttpResponse._get_content, _set_content) + try: + # Django version is older than 1.5 + content = property(HttpResponse._get_content, _set_content) + except: + # Django version 1.5 or greater + @HttpResponse.content.setter + def content(self, content): + print("Setting conttent to %s" % content) + self._set_content(content) return HttpResponseWrapper(r, content_type='text/plain', status=c) - + + rc = RcFactory() diff --git a/src/postal/views.py b/src/postal/views.py index be19577..2546489 100644 --- a/src/postal/views.py +++ b/src/postal/views.py @@ -6,39 +6,49 @@ except ImportError: import json as simplejson from postal.library import form_factory +from postal.settings import POSTAL_USE_CRISPY_FORMS def address_inline(request, prefix="", country_code=None, template_name="postal/form.html"): """ Displays postal address with localized fields """ - country_prefix = "country" prefix = request.POST.get('prefix', prefix) - + if prefix: country_prefix = prefix + '-country' - country_code = request.POST.get(country_prefix, country_code) + postal_form_id = request.POST.get('postal-form-id', 'postal-address-form') + form_class = form_factory(country_code=country_code) - + if request.method == "POST": data = {} for (key, val) in request.POST.items(): if val is not None and len(val) > 0: data[key] = val data.update({country_prefix: country_code}) - - form = form_class(prefix=prefix, initial=data) + + form = form_class(prefix=prefix, initial=data, postal_form_id=postal_form_id) else: - form = form_class(prefix=prefix) - - return render_to_string(template_name, RequestContext(request, { - "form": form, - "prefix": prefix, - })) + form = form_class(prefix=prefix, postal_form_id=postal_form_id) + + return render_to_string( + template_name, + context={ + "form": form, + "prefix": prefix, + }, + request=request + ) def changed_country(request): - result = simplejson.dumps({ - "postal_address": address_inline(request), - }) + if POSTAL_USE_CRISPY_FORMS: + result = simplejson.dumps({ + "postal_address": address_inline(request, template_name="postal/crispyform.html") + }) + else: + result = simplejson.dumps({ + "postal_address": address_inline(request), + }) return HttpResponse(result) \ No newline at end of file