Skip to main content

Django address autocomplete and reverse geocoding: Google plus free national-register providers (CZ RÚIAN, SK Register adries).

Project description

harriers-django-geocoding

A self-contained Django app for address autocomplete and reverse geocoding behind a provider-agnostic interface. Ships a metered Google provider (Places Autocomplete New + Geocoding API) as the default for any country, plus free local providers backed by national address registers — Czech (ČÚZK RÚIAN) and Slovak (ZBGIS Register adries) today.

Requirements

  • Python 3.10–3.14, Django ≥ 5.0.

  • PostgreSQL with PostGIS, reached through GeoDjango and psycopg 3 ("ENGINE": "django.contrib.gis.db.backends.postgis"). Migration 0001 creates the postgis and pg_trgm extensions and LIST-partitions the Address table by country, so the database user must be allowed to create extensions.

  • DRF is a dependency. drf-spectacular is not, but is used when the host has it: the views then carry full @extend_schema annotations. Two extras:

    Extra Adds Without it
    unfold django-unfold admin: country dropdown filter, one-click import buttons plain django.contrib.admin registration
    celery the import tasks in harriers_django_geocoding.tasks run the import commands from cron or by hand

Install

  1. pip install harriers-django-geocoding — or harriers-django-geocoding[unfold] for the rich django-unfold admin (plain admin fallback otherwise).

  2. Add "harriers_django_geocoding" to INSTALLED_APPS and configure. The app label is geocoding — that, not the import path, is what names the table, the migrations and the admin URLs. With the [unfold] extra, also add "unfold" and "unfold.contrib.filters" (the dropdown filter's templates live there) above django.contrib.admin:

    GEOCODING = {
        "GOOGLE_MAPS_API_KEY": os.environ.get("GOOGLE_MAPS_API_KEY", ""),
        # DRF permission classes as dotted paths. Default: [IsAuthenticated].
        "PERMISSION_CLASSES": ["myapp.permissions.IsStaff"],
        # Language for Google responses. Default: "en".
        "LANGUAGE": "cs",
        # How far (m) a reverse-geocoded match may lie from the queried point;
        # farther means 204 "no address here". Default: 500.
        "REVERSE_MAX_DISTANCE_M": 500,
        # How many addresses a country must have imported before its local provider
        # takes over from Google, so a partial load does not serve the country from
        # a handful of rows. Default: 10000.
        "LOCAL_MIN_ADDRESSES": 10000,
        # Country a request without a `country` parameter is served as. Unset, such a
        # request goes to Google whatever is imported. Default: "" (none).
        "DEFAULT_COUNTRY": "cz",
        # Shortest word the local providers search a register for; below it the
        # trigram index cannot serve the query and every keystroke would scan the
        # country's whole partition. Default: 3.
        "LOCAL_MIN_QUERY_LENGTH": 3,
    }
    
  3. Mount the URLs (they define geocode, geocode/details, geocode/reverse):

    path("", include("harriers_django_geocoding.urls")),
    
  4. python manage.py migrate.

HTTP API

All endpoints are GET and permissioned by GEOCODING["PERMISSION_CLASSES"].

Endpoint Query params Returns
…/geocode q, country (ISO alpha-2) [{ id, label }] autocomplete predictions
…/geocode/details placeId, country one resolved address (below), or 204
…/geocode/reverse lat, lng, country one resolved address (below), or 204

Resolved address (GeocodeResult):

{ "label": "...", "address": "...", "address_line_2": "...", "city": "...",
  "postcode": "...", "country_code": "cz", "latitude": 0.0, "longitude": 0.0,
  "source": "local:cz" }

Serializers use snake_case; with djangorestframework-camel-case the responses are camelCased, and the details view accepts both placeId spellings either way.

Address data import

A local provider serves a country only once its addresses are imported — at least GEOCODING["LOCAL_MIN_ADDRESSES"] of them, so a partial load does not take the country off Google and then answer from a handful of rows. Until then the country falls back to Google automatically. Both registers are free and keyless.

Country Source Command / Celery task
cz ČÚZK RÚIAN monthly bulk CSV (~3M addresses) import_cz_addresses
sk ZBGIS Register adries feature service import_sk_addresses
python manage.py import_cz_addresses

Every command takes --limit N for testing; the CZ one takes --url to override dataset auto-detection. A limited run disables pruning and writes only to a country whose partition is still empty — it holds an arbitrary slice of the register, which merged into a populated partition would overwrite the labels a full run disambiguated.

Each run stages the whole snapshot and merges the partition from there: only changed rows are written, missing rows are pruned (skipped if the run collected suspiciously few rows), and the live partition is untouched until the snapshot is complete. Imports are idempotent, keyed on the register's own identifier, so retries and overlapping runs are safe.

With the [celery] extra the same imports run as tasks, scheduled via CELERY_BEAT_SCHEDULE. RÚIAN publishes monthly a few days after month end, Register adries updates daily, harvested weekly because a full pass takes hours:

CELERY_BEAT_SCHEDULE = {
    "import-cz-addresses": {
        "task": "geocoding.import_cz_addresses",
        "schedule": crontab(day_of_month=6, hour=3, minute=0),
    },
    "import-sk-addresses": {
        "task": "geocoding.import_sk_addresses",
        "schedule": crontab(day_of_week=0, hour=4, minute=0),
    },
}

Run imports in a Celery worker or stable container, not a process that can be killed mid-run. CZ takes a few minutes; SK is paced by the remote service (1000 records per response, with pauses and backoff).

Google setup

Enable both the Geocoding API and the Places API (New) on the key's project. Both are billed per request — check Google's current pricing before pointing a busy picker at them. A country whose register is imported is served for free instead.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

harriers_django_geocoding-0.1.0.tar.gz (53.9 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

harriers_django_geocoding-0.1.0-py3-none-any.whl (48.2 kB view details)

Uploaded Python 3

File details

Details for the file harriers_django_geocoding-0.1.0.tar.gz.

File metadata

  • Download URL: harriers_django_geocoding-0.1.0.tar.gz
  • Upload date:
  • Size: 53.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.9.30 {"installer":{"name":"uv","version":"0.9.30","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Debian GNU/Linux","version":"12","id":"bookworm","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for harriers_django_geocoding-0.1.0.tar.gz
Algorithm Hash digest
SHA256 8e507a4242aa982580f8238a3db9d506532d3925d0c4d8cd38a139baaae16409
MD5 f8eab0f6a4489ab43c1d16a8dfb4aa7b
BLAKE2b-256 3678ed02afe5b6ac2fd6cc925f8941c9aca409713f0d6c112f285e0dbdb61c14

See more details on using hashes here.

File details

Details for the file harriers_django_geocoding-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: harriers_django_geocoding-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 48.2 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.9.30 {"installer":{"name":"uv","version":"0.9.30","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Debian GNU/Linux","version":"12","id":"bookworm","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for harriers_django_geocoding-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 96ec9998d9ac57d8e11f58d1dfbef8fb63e65835cb3cd24438672b6a56191238
MD5 c20c6325baa5b65c1a4d081ee177a693
BLAKE2b-256 4140e16ab48f8fb333eaece334a7d8d65d38d3ff47eb1ad917dfe684261cdabe

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page