- Python 3.12.3
- Docker (for PostGIS and Redis)
- Create a virtual environment and activate it
python3 -m venv venv
source venv/bin/activate- Install dependencies
pip3 install -r requirements.txt- Create
.envfile and replace values
cp .env.template .envThe .env file uses plain KEY=value format (no export prefix). It is read directly by Docker Compose and loaded into your shell with set -a && source .env && set +a.
- Run local server
source venv/bin/activate
set -a && source .env && set +a
make startInstead of connecting to preprod, you can run a fully local PostGIS with seed data.
Quick start (pre-generated seed from S3):
# 1. Download the seed file
mkdir -p scripts
# Replace <S3_URL> with the actual seed file URL
curl -o scripts/seed_data.sql <S3_URL>
# 2. Start DB, run migrations, load seed (all-in-one)
make local-setup
# 3. Start the server
make serverGenerate your own seed (requires prod/preprod DB access):
python scripts/extract_dev_data.py \
--host <DB_HOST> --port <DB_PORT> --dbname <DB_NAME> --user <DB_USER>
# Follow interactive prompts to select regions, departments, communes
# Output: scripts/seed_data.sql
make load-seedReset local DB (start fresh):
docker compose down -v
make local-setupDev users (password for all: aigle-dev):
| Role | Group | Rights | |
|---|---|---|---|
super-admin@aigle-dev.local |
SUPER_ADMIN | — | — |
admin@aigle-dev.local |
ADMIN | — | — |
regular@aigle-dev.local |
REGULAR | — | — |
ddtm-rw@aigle-dev.local |
REGULAR | DDTM (department) | read/write/annotate |
ddtm-ro@aigle-dev.local |
REGULAR | DDTM (department) | read only |
ddtm-admin@aigle-dev.local |
ADMIN | DDTM (department) | read/write/annotate |
collectivity-rw@aigle-dev.local |
REGULAR | Collectivity (commune) | read/write/annotate |
collectivity-ro@aigle-dev.local |
REGULAR | Collectivity (commune) | read only |
collectivity-admin@aigle-dev.local |
ADMIN | Collectivity (commune) | read/write/annotate |
Authentication is managed with djoser.
python manage.py create_super_admin --email myemail@email.com --password mypasswordDuring development, Django provides a browsable API at http://127.0.0.1:8000/. Use an extension like Requestly to add the JWT token to the header for protected routes.
# import all collectivites
python manage.py import_georegion
python manage.py import_geodepartment
python manage.py import_geocommune
# import hérault
python manage.py import_georegion --insee-codes 76
python manage.py import_geodepartment --insee-codes 34
python manage.py import_geocommune
# insert tiles: for montpellier and its surroundings
python manage.py create_tile --x-min 265750 --x-max 268364 --y-min 190647 --y-max 192325
# import parcels
python manage.py import_parcelsTests run against a separate PostgreSQL database (aigle-test) on the same server as the main app.
Local setup:
- Set
SQL_DATABASE_TEST=aigle-testin your.envfile (falls back to mainSQL_*connection vars) - Run tests:
make test # run all tests
make test-coverage # with coverage reportCI: Tests run automatically on PRs and pushes to develop/main via GitHub Actions using a PostGIS service container.
To send emails locally, you'll need to install local certificates, here is how to do it in MacOS
For full Docker deployment (not needed for local dev):
- Create
.env.composefrom template (used by theappcontainer):
cp .env.compose.template .env.compose- Build and run:
docker build -f Dockerfile -t aigle_api_app_container .
docker compose up --force-recreate -d db appCustom zones — link detections to custom zones
insert
into
core_detectionobject_geo_custom_zones(
detectionobject_id,
geocustomzone_id
)
select
distinct
dobj.id as detectionobject_id,
{custom_zone_id} as geocustomzone_id
from
core_detectionobject dobj
join core_detection detec on
detec.detection_object_id = dobj.id
where
ST_Within(
detec.geometry,
(
select
geozone.geometry
from
core_geozone geozone
where
id = {custom_zone_id}
)
)
on conflict do nothing;Remove detections from specific batch
delete from core_detection where batch_id = 'sia_2021';
delete
from
core_detectiondata
where id in (
select
core_detectiondata.id
from
core_detectiondata
left join core_detection on
core_detectiondata.id = core_detection.detection_data_id
where
core_detection.detection_data_id is null
);
delete from core_detectionobject_geo_custom_zones where detectionobject_id in (
select
obj.id
from
core_detectionobject as obj
left join core_detection as det on
obj.id = det.detection_object_id
where
det.detection_object_id is null
);
delete
from
core_detectionobject
where id in (
select
obj.id
from
core_detectionobject as obj
left join core_detection as det on
obj.id = det.detection_object_id
where
det.detection_object_id is null
);Extract x and y from geozone (for create_tile command)
WITH bbox AS (
SELECT
ST_XMin(ST_Envelope(geometry)) AS min_lon,
ST_YMin(ST_Envelope(geometry)) AS min_lat,
ST_XMax(ST_Envelope(geometry)) AS max_lon,
ST_YMax(ST_Envelope(geometry)) AS max_lat
FROM core_geozone WHERE uuid = {geozone_uuid}
)
SELECT
FLOOR((min_lon + 180) / 360 * POW(2, 19)) AS min_x_tile,
FLOOR((1 - LN(TAN(RADIANS(max_lat)) + 1 / COS(RADIANS(max_lat))) / PI()) / 2 * POW(2, 19)) AS min_y_tile,
FLOOR((max_lon + 180) / 360 * POW(2, 19)) AS max_x_tile,
FLOOR((1 - LN(TAN(RADIANS(min_lat)) + 1 / COS(RADIANS(min_lat))) / PI()) / 2 * POW(2, 19)) AS max_y_tile
FROM bbox;