diff --git a/address/models.py b/address/models.py index 3847555..08e68e5 100644 --- a/address/models.py +++ b/address/models.py @@ -10,6 +10,9 @@ ReverseSingleRelatedObjectDescriptor as ForwardManyToOneDescriptor, ) +from . import settings + + logger = logging.getLogger(__name__) __all__ = ["Country", "State", "Locality", "Address", "AddressField"] @@ -26,6 +29,7 @@ def _to_python(value): state = value.get("state", "") state_code = value.get("state_code", "") locality = value.get("locality", "") + administrative_area_level_3 = value.get('administrative_area_level_3', None) sublocality = value.get("sublocality", "") postal_town = value.get("postal_town", "") postal_code = value.get("postal_code", "") @@ -48,6 +52,21 @@ def _to_python(value): if not locality and postal_town: locality = postal_town + # Some locations doesn't have locality but having administrative_area_level_3, which both are representing city + # according to google: https://developers.google.com/maps/documentation/places/web-service/supported_types#table3 + if not locality and administrative_area_level_3: + locality = administrative_area_level_3 + + if settings.ALLOW_UNKNOWN_STATES and country and locality and not state: + # address geocoded as Country and City without State + # handling case like "Copenhagen, Denmark" + state = 'UNKNOWN' + value['state_code'] = 'UKWN' + + # This can happen if the user types in a value into the input then enter without selecting from the list. + if not settings.ALLOW_DUMMY_ADDRESSES and not country and not state_code and not locality: + raise ValidationError("Please choose from one of the location options in the dropdown.") + # If we have an inconsistent set of value bail out now. if (country or state or locality) and not (country and state and locality): raise InconsistentDictError @@ -70,10 +89,13 @@ def _to_python(value): state_obj = State.objects.get(name=state, country=country_obj) except State.DoesNotExist: if state: - if len(state_code) > State._meta.get_field("code").max_length: - if state_code != state: - raise ValueError("Invalid state code (too long): %s" % state_code) - state_code = "" + state_code_max_length = State._meta.get_field("code").max_length + if len(state_code) > state_code_max_length: + logger.warning(f'Trimming too long state_code {state_code} to {state_code_max_length} char only' + f' for raw value: {raw}') + # This is very odd case, raising error will not help as user has no choice to fix data + # best is to trim value to max allowed, example for this case: "Chum Phae Khon Kaen Thailand" + state_code = state_code[:state_code_max_length] state_obj = State.objects.create(name=state, code=state_code, country=country_obj) else: state_obj = None @@ -147,7 +169,8 @@ def to_python(value): try: return _to_python(value) except InconsistentDictError: - return Address.objects.create(raw=value["raw"]) + if settings.ALLOW_DUMMY_ADDRESSES: + return Address.objects.create(raw=value["raw"]) # Not in any of the formats I recognise. raise ValidationError("Invalid address value.") diff --git a/address/settings.py b/address/settings.py new file mode 100644 index 0000000..8ce1a8b --- /dev/null +++ b/address/settings.py @@ -0,0 +1,9 @@ +from django.conf import settings + +# Some counties doesn't have "state" value so google return locality but not no state +# Set this to true and it will create dummy state +ALLOW_UNKNOWN_STATES = getattr(settings, 'ALLOW_UNKNOWN_STATES', False) + +# Set this to false so only valid address is saved, allowing dummy addresses means +# accepting Address with only `raw` value is filled +ALLOW_DUMMY_ADDRESSES = getattr(settings, 'ALLOW_UNKNOWN_STATES', True) diff --git a/address/static/address/js/address.js b/address/static/address/js/address.js index 3aa84bf..4ecb6ee 100644 --- a/address/static/address/js/address.js +++ b/address/static/address/js/address.js @@ -12,6 +12,7 @@ $(function () { 'country', 'country_code', 'locality', + 'administrative_area_level_3', 'postal_code', 'postal_town', 'route', @@ -29,4 +30,4 @@ $(function () { } }); }); -}); \ No newline at end of file +}); diff --git a/address/tests/test_models.py b/address/tests/test_models.py index d59648d..d4e6111 100644 --- a/address/tests/test_models.py +++ b/address/tests/test_models.py @@ -304,7 +304,8 @@ def test_assignment_from_dict_invalid_state_code(self): "country": "Australia", } # This is invalid because state codes are expected to have a max of 8 characters - self.assertRaises(ValueError, to_python, ad) + address = to_python(ad) + self.assertEqual(address.locality.state.code, 'Somethin') def test_assignment_from_string(self): self.test.address = to_python(self.ad1_dict["raw"]) diff --git a/address/widgets.py b/address/widgets.py index 570141c..3a089b3 100644 --- a/address/widgets.py +++ b/address/widgets.py @@ -26,6 +26,7 @@ class AddressWidget(forms.TextInput): ("street_number", "street_number"), ("state", "administrative_area_level_1"), ("state_code", "administrative_area_level_1_short"), + ('administrative_area_level_3', 'administrative_area_level_3'), ("formatted", "formatted_address"), ("latitude", "lat"), ("longitude", "lng"), diff --git a/pyproject.toml b/pyproject.toml index 9df7923..22026a8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,8 +1,13 @@ [tool.poetry] name = "django-address" -version = "0.2.5" +version = "0.2.6" description = "A django application for describing addresses." authors = ["Luke Hodkinson "] +packages = [ + { include = "address" }, + { include = "address/*.py" }, + { include = "address/**/*.py" }, +] [tool.poetry.dependencies] python = ">=3.5" diff --git a/setup.py b/setup.py index 688f927..ae4b95e 100644 --- a/setup.py +++ b/setup.py @@ -3,7 +3,7 @@ from setuptools import find_packages, setup -version = "0.2.5" +version = "0.2.6" if sys.argv[-1] == "tag": print("Tagging the version on github:")