django-osm-widgets 0.1.0
Improved widgets for Django's PointField
License: BSD-3-Clause
Requires Python: >=3.10

# django-osm-widgets

Improved widgets for Django's PointField.

`LatLonOpenlayersOSMWidget` handles latitude and longitude inputs synced with the point on the map.


![Example of the LatLonOpenlayersOSMWidget on a page](https://gitlab.com/kapt/open-source/django-osm-widgets/-/raw/main/preview.jpg)

# Requirements

- Python 3.10+
- Django >=3.0, <5.2

# Installation

- run `pip install django-osm-widgets`
- add `django_osm_widgets` to your `INSTALLED_APPS`

# Usage

In your forms, use the widget like this:

```py
from django.contrib.gis.forms.fields import PointField
from django_osm_widgets.widgets import LatLonOpenlayersOSMWidget


class MyForm(forms.Form):
  location = PointField(widget=LatLonOpenlayersOSMWidget)
```

The latitute and longitude fields will be automatically added in your page.

Currently `django-osm-widgets` supports only a unique instance of the `LatLonOpenlayersOSMWidget` in a page.

# Customizations

You can define some options as in the example below.

When using `"must_display_latlon_fields": False,` your are responsible for providing two input fields in your page. These fields must have ids corresponding to `latitude_field_id` and `longitude_field_id` values (defaults to `id_osm_widget_latitude` and `id_osm_widget_longitude`) and must appear in the DOM before the LatLonOpenlayersOSMWidget. To achieve that, you may find useful to override the `latlon-openlayers-osm.html` template as follow:

```html
{% extends "django_osm_widgets/latlon-openlayers-osm.html" %}

{% block map_wrapper %}
  {{ block.super }}
  <label for="{{ latitude_field_id }}" class="form-label">Latitude</label>
  <input type="number" step="0.0001" min="-90" max="90" name="latitude" id="{{ latitude_field_id }}" placeholder="for example: 45.123456" class="form-control">
  <label for="{{ longitude_field_id }}" class="form-label">Longitude</label>
  <input type="number" step="0.0001" min="-180" max="180" name="longitude" id="{{ longitude_field_id }}" placeholder="for example: 2.123456" class="form-control">
{% endblock map_wrapper %}
```

You can override some attributes when instantiating the widget class in your form. Below are all the attributes and their default values.

```py
from django.contrib.gis.forms.fields import PointField
from django_osm_widgets.widgets import LatLonOpenlayersOSMWidget


class MyForm(forms.Form):
  location = PointField(
    widget=LatLonOpenlayersOSMWidget(
      attrs={
        "must_display_latlon_fields": True,
        "map_width": "auto",
        "map_height": "auto",
        "default_lat": 45,
        "default_lon": 5,
        "default_zoom": 8,
        "latitude_field_id": "id_osm_widget_latitude",
        "longitude_field_id": "id_osm_widget_longitude",
        "listened_events": "input",
        "marker_options": {
          "src": "https://cdn.jsdelivr.net/npm/leaflet@1.9.4/dist/images/marker-icon.png",
          "scale": 1,
          "anchor": [0.5, 1],
        },
        "precision": 4,
        "geocoder_address_field_ids": {
          "street": "id_adresse",
          "postal_code": "id_code_postal",
          "city": "id_localite",
          "country": "id_pays",
        },
        "geocoder_provider": "nominatim",
        "clear_features_label": "Delete all Features",
        "geocoder_button_label": "Geocode from address",
        "geocoder_message_timeout": 5000,
      }
    )
  )
```

Projects can override button labels and the status message timeout:

- **Via widget attrs**: Pass `clear_features_label`, `geocoder_button_label`, `geocoder_message_timeout` when instantiating the widget.
- **Via template blocks**: Extend `latlon-openlayers-osm.html` and override `{% block clear_features_label %}` or `{% block geocoder_button_label %}` for full control.

Labels support translation when using Django's i18n. The status message (e.g. "Coordinates updated.") disappears automatically after `geocoder_message_timeout` milliseconds (default 5 seconds).

# Geocoding from address

When `geocoder_address_field_ids` is provided, a "Geocode from address" button appears next to the map. It maps your form's address fields (street, postal_code, city, country) to DOM element IDs. When clicked, the widget fetches coordinates and updates the map.

The geocoding provider is configurable via `geocoder_provider`:

- **`nominatim`** (default): [OpenStreetMap Nominatim](https://nominatim.openstreetmap.org/). Works worldwide with international addresses.
- **`ign`**: [IGN Géoplateforme](https://geoservices.ign.fr/documentation/services/services-geoplateforme/geocodage). Optimized for French addresses (BAN, BD TOPO®, Parcellaire Express). 50 requests/second limit per IP.

Example for French addresses:

```py
"geocoder_provider": "ign",
```

You can omit any of the four address keys; at least one address field must be filled.