# Django Integration

## Setup

```bash
pip install yoro[django]
```

### settings.py

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

### urls.py (optional, for the API endpoints)

```python
from django.urls import include, path

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

### Migrate

```bash
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

```python
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

```python
# 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

```python
# 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

```python
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:

```python
# 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)
```

!!! tip "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:

```python
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

```python
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:

```html
<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

```python
# 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:

```css
/* 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 |

!!! note "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

```python
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.

```python
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.
