Skip to content

Django Integration

Setup

pip install yoro[django]

settings.py

INSTALLED_APPS = [
    # ...
    "django.contrib.gis",       # GeoDjango required
    "yoro.django",
]

urls.py (optional, for the API endpoints)

from django.urls import include, path

urlpatterns = [
    path("api/v1/yoro/", include("yoro.django.urls")),
]

Migrate

python manage.py migrate

YoroField — The Simplest Way

YoroField is a Django model field that stores a Yoro code as a database column. It's the recommended way to add geographic addressing to your models.

Basic usage

from django.db import models
from yoro.django.fields import YoroField

class Shop(models.Model):
    name = models.CharField(max_length=100)
    address = YoroField(domain="CI", precision=12)

That's it. Your model now has a address column that:

  • Stores Yoro codes as strings ("CI-PV9XD")
  • Auto-encodes from (lat, lon) tuples
  • Validates codes on form submission
  • Renders an interactive map picker in forms and Django admin

Assigning values

# Direct code assignment
shop = Shop(name="Boutique Abidjan")
shop.address = "CI-PV9XD"
shop.save()

# Auto-encode from GPS coordinates
shop.address = (5.345, -4.028)   # tuple (lat, lon)
shop.save()
print(shop.address)  # "CI-PV9XD" — encoded automatically

# Also works with lists
shop.address = [5.345, -4.028]
shop.save()

Reading and decoding

# The field stores a string
print(shop.address)           # "CI-PV9XD"
print(type(shop.address))     # <class 'str'>

# Decode to get coordinates and cell bounds
import yoro
decoded = yoro.decode(shop.address)
print(decoded["lat"])          # 5.34519...
print(decoded["lon"])          # -4.02868...
print(decoded["bounds"])       # cell bounding box
print(decoded["precision"])    # 12

Field options

class DeliveryPoint(models.Model):
    address = YoroField(
        domain="CI",           # ISO country code (default: "CI")
        precision=14,          # Hilbert order (default: 12 → ~173m)
        map_height=400,        # Widget map height in px (default: 300)
        verbose_name="Delivery address",
        help_text="Click on the map or enter a Yoro code",
        blank=True,            # Optional field (default: True)
    )

Available precision levels

Precision Code Length Resolution (CI) Use Case
7 3 chars ~5.5 km City/district
9 4 chars ~1.4 km Neighborhood
12 5 chars ~173 m Block (default)
14 6 chars ~43 m Building
17 7 chars ~5.4 m Entrance
19 8 chars ~1.35 m Meter-level

Querying

Since YoroField is a CharField, you can use standard Django lookups:

# Exact match
Shop.objects.filter(address="CI-PV9XD")

# All shops in Cote d'Ivoire
Shop.objects.filter(address__startswith="CI-")

# All shops with an address assigned
Shop.objects.exclude(address="")

# Find nearby: encode, get neighbors, query
import yoro
neighbors = yoro.neighbors("CI-PV9XD")
Shop.objects.filter(address__in=["CI-PV9XD"] + neighbors)

Spatial proximity queries

For efficient spatial queries, combine YoroField with Yoro's neighbors() function. Since nearby locations share similar codes (Hilbert curve property), querying for a code and its 8 neighbors gives you a ~500m radius search at precision 12.


YoroWidget — Interactive Map Picker

Every YoroField automatically uses the YoroWidget in forms. But you can also use the widget independently on any CharField.

What the widget provides

The widget renders:

  1. A monospace text input — for typing/pasting Yoro codes
  2. A Leaflet map — click anywhere to encode that location
  3. Cell visualization — the Hilbert cell is drawn as an orange dashed rectangle
  4. Info display — resolution in meters, GPS coordinates, precision level

All encoding/decoding happens client-side in the browser (via yoro-codec.min.js). No AJAX calls, no server round-trips, no latency.

In Django Admin

The widget works in Django admin out of the box. Just register your model:

from django.contrib import admin
from myapp.models import Shop

@admin.register(Shop)
class ShopAdmin(admin.ModelAdmin):
    list_display = ("name", "address")
    search_fields = ("name", "address")

The admin form will show the map picker for the address field automatically.

In custom forms

from django import forms
from yoro.django.widgets import YoroWidget

class ShopForm(forms.Form):
    name = forms.CharField(max_length=100)
    address = forms.CharField(
        widget=YoroWidget(
            domain="CI",
            precision=12,
            map_height=350,
        ),
        required=False,
    )

In templates

The widget includes its own CSS and JS via Django's Media framework. Make sure your template renders form media:

<head>
    {{ form.media }}
</head>
<body>
    <form method="post">
        {% csrf_token %}
        {{ form.as_div }}
        <button type="submit">Save</button>
    </form>
</body>

The widget automatically loads:

  • Leaflet CSS + JS (from CDN)
  • yoro-codec.min.js (client-side Hilbert codec)
  • yoro-widget.js (map interaction logic)
  • yoro-widget.css (styling)

Customizing the tile provider

# Satellite tiles
address = YoroField(
    domain="CI",
    map_attrs={
        "tile_url": "https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}",
        "tile_attribution": "&copy; Esri",
    },
)

# Or on the widget directly
widget = YoroWidget(
    tile_url="https://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}{r}.png",
    tile_attribution="&copy; CARTO",
)

Customizing the CSS

The widget uses minimal, scoped CSS classes that you can override:

/* Make the code input larger */
.yoro-code-input {
    font-size: 20px;
    width: 200px;
}

/* Change the map height */
.yoro-map {
    min-height: 400px;
}

/* Style the info text */
.yoro-info {
    color: #333;
    font-size: 14px;
}

No Bootstrap, Tailwind, or any CSS framework is imposed. The widget works with any design system.


Model: GeoAltiusCode

For advanced use cases (storing cell geometry, spatial queries on cell polygons), Yoro also provides the GeoAltiusCode model with GeoDjango fields:

Field Type Description
code CharField(20) Yoro code (unique, indexed)
domain CharField(5) ISO domain prefix
location PointField GPS center point (SRID 4326)
cell PolygonField Hilbert cell bounding polygon
precision SmallIntegerField Hilbert order
resolution_m FloatField Cell size in meters
label CharField(255) Optional human label
created_at DateTimeField Auto-set on creation

YoroField vs GeoAltiusCode

Use YoroField when you just need a Yoro code as an address (most cases). It's a simple CharField — no PostGIS needed for the field itself.

Use GeoAltiusCode when you need to store the cell geometry for spatial queries (e.g. "find all codes that intersect this polygon"). It requires GeoDjango + PostGIS.

Services

get_or_create_yoro

from yoro.django.services import get_or_create_yoro

obj = get_or_create_yoro(lat=5.345, lon=-4.028, domain="CI")
print(obj.code)       # "CI-PV9XD"
print(obj.cell)       # Polygon geometry

assign_yoro

Assigns a Yoro code to any model instance that has location (PointField) and yoro (ForeignKey) fields. Modifies the instance in place but does not save.

from yoro.django.services import assign_yoro

class MyLocation(models.Model):
    location = gis_models.PointField(srid=4326)
    yoro = models.ForeignKey(
        "yoro.GeoAltiusCode",
        null=True, blank=True,
        on_delete=models.SET_NULL,
    )

    def save(self, **kwargs):
        if self.location and not self.yoro_id:
            assign_yoro(self, domain="CI")
        super().save(**kwargs)

API Endpoints

Method URL Description
GET /encode/?lat=6.8&lon=-5.3&domain=CI Encode GPS to code
GET /decode/?code=CI-PV9XD Decode code to GPS
GET /precisions/?domain=CI List canonical precisions
GET /domains/ List all domains

Admin

GeoAltiusCode is automatically registered with the GIS admin, showing the map widget for location and cell fields.