Troubleshooting¶
Parcel Versioning Deploy Runbook¶
Pre-Deploy¶
- Take a PostgreSQL backup/snapshot (or ensure point-in-time restore is available).
- Confirm app build includes migration
parcels.0007_parceltransaction_and_more. - Confirm maintenance window for schema migration + smoke tests.
Deploy¶
- Deploy application code.
- Run migrations:
./.venv/bin/python manage.py migrate- Run Django checks:
./.venv/bin/python manage.py check
Post-Deploy Verification¶
- Validate versioning invariants:
./.venv/bin/python manage.py verify_parcel_versioning- Run parcel test suite (or smoke subset in production-like env):
./.venv/bin/python manage.py test apps.parcels.tests --verbosity=1- UI smoke checks:
- Parcel update creates a new active version and supersedes prior row.
- Map inline geometry edit creates a new version.
- Batch delete marks rows as
void(no hard delete). - Parcel detail page shows version history.
Rollback Strategy¶
- Preferred: forward-fix with a hotfix release after
0007is applied. - If full rollback is required, restore DB from pre-deploy backup and redeploy the matching pre-versioning app build.
- Do not use ad-hoc manual SQL rollback in production unless tested on a restore copy.
Upload Error: Duplicate Key on (dataset_id, parcel_id)¶
Symptom:
duplicate key value violates unique constraint "parcels_dataset_parcel_id_unique"
Meaning:
- The upload is trying to create/replace a parcel ID that already exists in the target dataset.
Actions:
- Verify mapped parcel ID source field
- Confirm target active dataset is expected
- Use preview step to inspect matched records before apply
Upload Form Says File Required After Selecting a File¶
If file input appears cleared during submit, verify form submit lock logic is active and no custom JS resets file fields.
Current upload form uses data-disable-on-submit="true" to prevent duplicate submits.
make check Appears to Hang on Black¶
Potential causes:
- Running formatter on very large trees (including temporary directories)
- Slow filesystem/mounted drive performance
Current Makefile excludes tmp, .venv, venv, .git, and staticfiles from formatter/lint scans.
ARIA Warning About aria-hidden and Focus¶
Warning example:
Blocked aria-hidden on an element because its descendant retained focus
Meaning:
- A focused element was inside a container that became
aria-hidden="true".
Fix direction:
- Move focus before hiding, or use
inertto both hide and prevent focus.
Workspace Map Tools Do Not Load¶
- Confirm
basemap_runtime_js.htmlloads Proj4 before the shared inspection and workspace modules. - Check that the raster catalog and sample endpoints return authenticated responses.
- If surrounding parcels fail, verify the workspace subject is accessible and the request bbox contains WGS84 longitude/latitude values.
- Fresh create intentionally disables surroundings when no active Editor dataset is selected.
- Run Playwright through guarded
make e2e; the fixture command refuses non-debug or non-opted-in environments.
Black or make check Hangs on /mnt/e¶
Black has repeatedly stalled on this workspace, both when run directly and when invoked by make check. Single-worker settings reduce parallelism but do not reliably prevent the stall.
- Automated agents must not run Black or
make checkwithout explicit project-owner confirmation. - If approved, run with
BLACK_NUM_WORKERS=1, direct Black--workers 1, and a bounded timeout. - Stop the process when it stalls. Record it as a formatter/environment issue, not as a test failure.
- Run independent syntax, critical flake8, Node, and
git diff --checkvalidation where appropriate; these do not replace an owner-approved formatter gate.
Parcel Overlap Enforcement Migration Is Blocked¶
- Run
./.venv/bin/python manage.py audit_parcel_overlaps --format json --include-permitted --fail-on-unresolved. - The activation scan and audit cover only present or future authoritative intervals. Expired historical overlaps remain immutable history and do not block activation.
- A missing
ParcelTopologySettingsrow must be restored before stage 2. Stage 1 seeds rowid=1; if it was manually removed, open Topology Settings in admin or callParcelTopologySettings.get_or_create_singleton()in a controlled Django shell. - Resolve active drafts reported with
MISSING_PROJECTED_GEOMETRY; ambiguous SRID-free legacy WKT is intentionally not assumed to use the storage CRS. - Correct blocking authoritative pairs or create a bounded exemption through Django admin, then retry migration
0024and continue immediately through0025. - If a historical import or versioning operation failed while the database was at
0024, first apply0025, then retry the entire failed operation. The rejected transaction did not persist a partial parcel version. Do not grandfather an expired historical overlap solely to make that retry pass. - To roll back enforcement, migrate to
parcels.0023_overlap_governance_schemabefore changing stage-1 schema. Do not remove audit records first.
Export Fails with ogr2ogr Error¶
Checklist:
- Confirm
ogr2ogris installed and in PATH - Confirm PostGIS backend is in use for export
- Check DB connection settings in environment
- Review server response body for exact command stderr
Dokku: Persistent Storage for Media + Raster¶
Use persistent mounts, not git-tracked media/ files and not re-ingest on every deploy.
Recommended Workflow¶
- Create storage directories on Dokku host and mount them.
- Set
MEDIA_ROOT,RASTER_TILES_ROOT, andRASTER_QUERY_ROOTto mounted paths. - Deploy app code.
- Run
migrateandcollectstatic. - Ingest rasters only when data changes.
One-line helper script example:
./scripts/setup_dokku_raster_storage.sh srv.wg lmstool data/raster_inputs/dem01 data/raster_inputs/landcover01
Commands¶
APP=lmstool2
# Ensure app storage root exists
dokku storage:ensure-directory "$APP"
# Host directories
mkdir -p /var/lib/dokku/data/storage/$APP/staticfiles
mkdir -p /var/lib/dokku/data/storage/$APP/media
# Mount paths
dokku storage:mount "$APP" /var/lib/dokku/data/storage/$APP/staticfiles:/app/staticfiles
dokku storage:mount "$APP" /var/lib/dokku/data/storage/$APP/media:/app/media
# Runtime paths
dokku config:set "$APP" STATIC_ROOT=/app/staticfiles STATIC_URL=/static/
dokku config:set "$APP" MEDIA_ROOT=/app/media MEDIA_URL=/media/
dokku config:set "$APP" RASTER_TILES_ROOT=/app/media/raster_tiles
dokku config:set "$APP" RASTER_QUERY_ROOT=/app/media/raster_query
dokku config:set "$APP" RASTER_TILES_URL=/raster-tiles/
# Deploy + bootstrap
git push dokku main
dokku run "$APP" python manage.py migrate
dokku run "$APP" python manage.py collectstatic --noinput
# Ingest when raster data changes
dokku run "$APP" python manage.py ingest_raster --dataset-dir data/raster_inputs/<dem-folder> --set-default --overwrite
dokku run "$APP" python manage.py ingest_raster --dataset-dir data/raster_inputs/<landcover-folder> --set-default --overwrite
Quick Verification¶
APP=lmstool2
dokku run "$APP" sh -lc 'ls -lah /app/media/raster_tiles'
dokku run "$APP" sh -lc 'ls -lah /app/media/raster_query'