Skip to main content

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), Slovak (ZBGIS Register adries) and Polish (GUGiK PRG) 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):

    urlpatterns = [
        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. All three 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
pl GUGiK PRG bulk GML (a ~1.8 GB archive) import_pl_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-pl-addresses": {
        "task": "geocoding.import_pl_addresses",
        "schedule": crontab(day_of_month=8, hour=2, 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.

Metadata

Release files for harriers-django-geocoding 0.2.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for harriers-django-geocoding 0.2.0
File Size Uploaded Provenance Actions
harriers_django_geocoding-0.2.0.tar.gz 74.7 kB Without provenance Details for harriers_django_geocoding-0.2.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for harriers-django-geocoding 0.2.0
File Interpreter ABI Platform Actions
harriers_django_geocoding-0.2.0-py3-none-any.whl Python 3 none any Details for harriers_django_geocoding-0.2.0-py3-none-any.whl

Total release size: 137.2 kB

Release files / harriers_django_geocoding-0.2.0.tar.gz

Download URL harriers_django_geocoding-0.2.0.tar.gz
Size 74.7 kB
Tags Source
SHA-256 checksum
How to use checksums
acea53de550f76258dfa54762972885de32ed3aff2fc5d409bea2a3b4069ee8c
BLAKE2b-256 checksum
How to use checksums
c4394f01b7e4075b9f8aa6a2cdca35fb9c224292d520ebf6a1fbf6a6c398ff76
Upload date
Uploaded using Trusted Publishing?
What is 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}

Release files / harriers_django_geocoding-0.2.0-py3-none-any.whl

Download URL harriers_django_geocoding-0.2.0-py3-none-any.whl
Size 62.5 kB
Tags Python 3
SHA-256 checksum
How to use checksums
1a67f310f9262092df6e0f2e5db5bba047377dba8777cc0b2039e6e55d91a07b
BLAKE2b-256 checksum
How to use checksums
bb98c9dbd807c2a1a72a8a09b45db2747c8900838152c057ae90425ab2261970
Upload date
Uploaded using Trusted Publishing?
What is 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}

Security for harriers-django-geocoding 0.2.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

0.2.0 This release

2 release files No provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page