From eb18e5251585cfe99ffe2b842c8155f407ba3836 Mon Sep 17 00:00:00 2001 From: Paige Williams Date: Mon, 17 Aug 2026 14:42:15 -0700 Subject: [PATCH 01/19] add .dockerfile --- .dockerignore | 51 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 .dockerignore diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..5a89f46 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,51 @@ +# Python +__pycache__/ +*.py[cod] +*.pyo +*.pyd + +# Virtual environments +env/ +venv/ +.venv/ + +# Editor & OS files +.DS_Store +.vscode/ +.idea/ + +# Git +.git +.gitignore + +# Build/dist +build/ +dist/ +*.egg-info + +# Static/local settings +media/ +static/ +local_settings.py +*.sqlite3 +*.log + +# Node +node_modules/ + +# Docker +Dockerfile +docker-compose*.yml + +# Data directory +data/ + +# Backups +backups/ + +# Vagrant +Vagrantfile +.vagrant/ + +# SQL files +*.sql \ No newline at end of file From 51e0a4d1440ff9785f4cbe1bb46113755edfee2a Mon Sep 17 00:00:00 2001 From: Paige Williams Date: Mon, 17 Aug 2026 14:56:27 -0700 Subject: [PATCH 02/19] add Dockerfile; move requirements.txt to landmapper dir --- landmapper/Dockerfile | 46 +++++++++++++++++++ .../requirements.txt | 0 2 files changed, 46 insertions(+) create mode 100644 landmapper/Dockerfile rename requirements.txt => landmapper/requirements.txt (100%) diff --git a/landmapper/Dockerfile b/landmapper/Dockerfile new file mode 100644 index 0000000..24feecb --- /dev/null +++ b/landmapper/Dockerfile @@ -0,0 +1,46 @@ +FROM python:3.14-slim + +# Prevent Python from writing .pyc files and enable unbuffered stdout/stderr +ENV PYTHONDONTWRITEBYTECODE=1 +ENV PYTHONUNBUFFERED=1 +ENV PIP_NO_CACHE_DIR=1 + +# from doc: +# sudo apt install python3-pip postgresql postgresql-contrib postgresql-server-dev-14 postgis python3-gdal libgdal-dev redis-server -y +# sudo apt install pdftk + +# Install system dependencies +RUN apt-get update \ + && apt-get install -y --no-install-recommends \ + postgresql-client \ + gdal-bin \ + libgdal-dev \ + libgeos-dev \ + pdftk \ + && rm -rf /var/lib/apt/lists/* + +# Set working directory +WORKDIR /usr/src/app + +# Copy requirements first (cache pip install step when dependencies don't change) +COPY requirements.txt /usr/src/app/ + +# Upgrade pip and install Python dependencies +RUN pip install --upgrade pip \ + && pip install -r requirements.txt + +# Copy the application code +COPY . /usr/src/app + +# Copy and make entrypoint executable. The repository contains `docker/entrypoint.sh` +# which runs collectstatic, migrations and launches uWSGI. +COPY entrypoint.sh /usr/local/bin/entrypoint.sh +RUN chmod +x /usr/local/bin/entrypoint.sh + +# Expose the port the app runs on (entrypoint starts django development server or uWSGI on 8000) +EXPOSE 8000 + +# Default settings module (can be overridden at runtime) +ENV DJANGO_SETTINGS_MODULE=landmapper.settings + +ENTRYPOINT ["/usr/local/bin/entrypoint.sh"] \ No newline at end of file diff --git a/requirements.txt b/landmapper/requirements.txt similarity index 100% rename from requirements.txt rename to landmapper/requirements.txt From ccfd57138d1f6cae5b3e53c9f47429dc5039b753 Mon Sep 17 00:00:00 2001 From: Paige Williams Date: Mon, 17 Aug 2026 15:04:20 -0700 Subject: [PATCH 03/19] add entrypoint.sh --- landmapper/entrypoint.sh | 49 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 landmapper/entrypoint.sh diff --git a/landmapper/entrypoint.sh b/landmapper/entrypoint.sh new file mode 100644 index 0000000..8feb6cf --- /dev/null +++ b/landmapper/entrypoint.sh @@ -0,0 +1,49 @@ +#!/bin/sh + +# Exit on errors +set -e + +# If a SQL_HOST is provided, wait for Postgres to become available before running +# migrations. This prevents race conditions when using docker-compose where the +# web container starts before the DB is ready. +if [ -n "$SQL_HOST" ]; then + echo "Waiting for database at ${SQL_HOST}:${SQL_PORT:-5432}..." + # pg_isready is available after installing postgresql-client in the image + until pg_isready -h "$SQL_HOST" -p "${SQL_PORT:-5432}" >/dev/null 2>&1; do + echo "Postgres is unavailable - sleeping" + sleep 1 + done + echo "Postgres is up" +fi + +echo "Collecting static files..." +python manage.py collectstatic --noinput +echo "Applying database migrations..." +python manage.py migrate --noinput +# Load default users only if no users exist +# echo "Checking for existing users..." +# if [ "$(python manage.py shell -c 'from django.contrib.auth import get_user_model; print(get_user_model().objects.count())' 2>/dev/null | tail -1)" = "0" ]; then +# echo "No users found, loading default users fixture..." +# python manage.py loaddata TEKDB/fixtures/default_users_fixture.json +# fi + +# Load default lookups only if no lookups exist. Use LookupPlanningUnit as the check. +echo "Checking for existing initial data..." +if [ "$(python manage.py shell -c 'from app.models import MenuPage; print(MenuPage.objects.count())' 2>/dev/null | tail -1)" = "0" ]; then + echo "No initial data found, loading default initial data fixture..." + python manage.py loaddata app/fixtures/initial_data.json +fi + +# if [ "$1" = "prod" ]; then +# echo "Starting uWSGI (socket) on :8000" +# uwsgi --socket :8000 --master --enable-threads --module TEKDB.wsgi +# elif [ "$1" = "prod-local" ]; then +# echo "Starting uWSGI (http) on :8000 with local settings" +# uwsgi --http :8000 --master --enable-threads --module TEKDB.wsgi +# elif [ "$1" = "dev" ]; then +echo "Starting python development server on :8000" +python manage.py runserver 0.0.0.0:8000 +# else +# # Default to the passed command if not 'prod' or 'dev' +# exec "$@" +# fi \ No newline at end of file From 5574e14d0ec39050f89061bb631168f844218ed2 Mon Sep 17 00:00:00 2001 From: Paige Williams Date: Mon, 17 Aug 2026 15:29:07 -0700 Subject: [PATCH 04/19] add docker-compose.yaml; add TODO comment about madrona packages --- docker/docker-compose.yaml | 74 +++++++++++++++++++++++++++++++++++++ landmapper/requirements.txt | 7 ++-- 2 files changed, 78 insertions(+), 3 deletions(-) create mode 100644 docker/docker-compose.yaml diff --git a/docker/docker-compose.yaml b/docker/docker-compose.yaml new file mode 100644 index 0000000..8fd43bd --- /dev/null +++ b/docker/docker-compose.yaml @@ -0,0 +1,74 @@ +services: + db: + container_name: db + image: postgis/postgis:18-3.6 + restart: unless-stopped + platform: linux/amd64 + environment: + POSTGRES_DB: ${POSTGRES_DB} + POSTGRES_USER: ${POSTGRES_USER} + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} + volumes: + - landmapper_db_data:/var/lib/postgresql + ports: + - "5432:5432" + healthcheck: + test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB} -h localhost -p ${POSTGRES_PORT}"] + interval: 10s + timeout: 5s + retries: 5 + redis: + container_name: redis + image: redis:alpine + landmapper: + container_name: landmapper + build: + context: ../landmapper/ + dockerfile: ../landmapper/Dockerfile + restart: unless-stopped + env_file: + - path: .env + required: true + ports: + - "8000:8000" + healthcheck: + test: ["CMD-SHELL", "python manage.py check --deploy 2>/dev/null || python -c 'import urllib.request; urllib.request.urlopen(\"http://localhost:8000/\")' 2>/dev/null"] + interval: 15s + timeout: 10s + retries: 5 + start_period: 30s + volumes: + - type: bind + source: ../p97-nursery + target: /usr/local/apps/landmapper/apps/p97-nursery + bind: + create_host_path: false + - type: bind + source: ../madrona-features + target: /usr/local/apps/landmapper/apps/madrona-features + bind: + create_host_path: false + - type: bind + source: ../madrona-manipulators + target: /usr/local/apps/landmapper/apps/madrona-manipulators + bind: + create_host_path: false + depends_on: + db: + condition: service_healthy + redis: + condition: service_started + environment: + ALLOWED_HOSTS: ${ALLOWED_HOSTS} + DEBUG: ${DEBUG} + POSTGRES_ENGINE: ${POSTGRES_ENGINE} + POSTGRES_HOST: ${POSTGRES_HOST} + POSTGRES_PORT: ${POSTGRES_PORT} + POSTGRES_DB: ${POSTGRES_DB} + POSTGRES_USER: ${POSTGRES_USER} + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} + SECRET_KEY: ${SECRET_KEY} + + + + diff --git a/landmapper/requirements.txt b/landmapper/requirements.txt index 904140e..5b91877 100644 --- a/landmapper/requirements.txt +++ b/landmapper/requirements.txt @@ -6,12 +6,13 @@ django-flatblocks django-redis # Django-Registration 3.4 does not appear to be sending password reset emails... django-registration<=3.3 +# TODO: figure out if these are needed here, or if having them in the bound volumes is sufficient # -e git+https://github.com/Ecotrust/madrona-features.git@main#egg=madrona_features --e /usr/local/apps/madrona-features +-e /usr/local/apps/landmapper/apps/madrona-features # -e git+https://github.com/Ecotrust/p97-nursery.git@main#egg=p97-nursery --e /usr/local/apps/p97-nursery +-e /usr/local/apps/landmapper/apps/p97-nursery # -e git+https://github.com/Ecotrust/madrona-manipulators.git@main#egg=madrona_manipulators --e /usr/local/apps/madrona-manipulators +-e /usr/local/apps/landmapper/apps/madrona-manipulators django-tinymce geopy ipdb From 25baaa37905e11b7b38ccb3a3eb1a4e7be802572 Mon Sep 17 00:00:00 2001 From: Paige Williams Date: Mon, 17 Aug 2026 15:30:24 -0700 Subject: [PATCH 05/19] ignore .env --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 3e28fd5..831d50e 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,4 @@ landmapper/app/testing_files landmapper/app/static/landmapper/shapefiles/ backups/* src/* +.env \ No newline at end of file From 7c3ef352b42175058facee414c2bea8c66274171 Mon Sep 17 00:00:00 2001 From: Paige Williams Date: Tue, 18 Aug 2026 13:33:32 -0700 Subject: [PATCH 06/19] wip: use env vars in settings.py try to use local madrona libraries --- docker/.env.example | 16 ++++++++ docker/docker-compose.yaml | 37 ++++++++++-------- landmapper/Dockerfile | 11 ++++++ landmapper/entrypoint.sh | 6 ++- landmapper/landmapper/settings.py | 64 +++++++++++++++++++++++-------- landmapper/requirements.txt | 16 ++++---- 6 files changed, 110 insertions(+), 40 deletions(-) create mode 100644 docker/.env.example diff --git a/docker/.env.example b/docker/.env.example new file mode 100644 index 0000000..60b017e --- /dev/null +++ b/docker/.env.example @@ -0,0 +1,16 @@ +ALLOWED_HOSTS=localhost,127.0.0.1,[::1] +DEBUG=1 +POSTGRES_ENGINE=django.contrib.gis.db.backends.postgis +POSTGRES_HOST=db +POSTGRES_PORT=5432 +POSTGRES_DB=landmapper +POSTGRES_USER=postgres +POSTGRES_PASSWORD=postgres_password_change_me +SECRET_KEY=foobar +MAPBOX_TOKEN= +GOOGLE_API_KEY= +GEOAPIFY_API_KEY= +RECAPTCHA_PUBLIC_KEY= +RECAPTCHA_PRIVATE_KEY= +ARCGIS_DEVELOPER_KEY= +GOOGLE_ANALYTICS_KEY= \ No newline at end of file diff --git a/docker/docker-compose.yaml b/docker/docker-compose.yaml index 8fd43bd..80a7586 100644 --- a/docker/docker-compose.yaml +++ b/docker/docker-compose.yaml @@ -38,21 +38,23 @@ services: retries: 5 start_period: 30s volumes: - - type: bind - source: ../p97-nursery - target: /usr/local/apps/landmapper/apps/p97-nursery - bind: - create_host_path: false - - type: bind - source: ../madrona-features - target: /usr/local/apps/landmapper/apps/madrona-features - bind: - create_host_path: false - - type: bind - source: ../madrona-manipulators - target: /usr/local/apps/landmapper/apps/madrona-manipulators - bind: - create_host_path: false + # - type: bind + # source: ../p97-nursery + # target: /usr/local/apps/landmapper/apps/p97-nursery + # bind: + # create_host_path: false + # - type: bind + # source: ../madrona-features + # target: /usr/local/apps/landmapper/apps/madrona-features + # bind: + # create_host_path: false + # - type: bind + # source: ../madrona-manipulators + # target: /usr/local/apps/landmapper/apps/madrona-manipulators + # bind: + # create_host_path: false + - static_volume:/usr/src/app/static + - media_volume:/usr/src/app/media depends_on: db: condition: service_healthy @@ -68,6 +70,11 @@ services: POSTGRES_USER: ${POSTGRES_USER} POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} SECRET_KEY: ${SECRET_KEY} +volumes: + landmapper_db_data: + static_volume: + media_volume: + diff --git a/landmapper/Dockerfile b/landmapper/Dockerfile index 24feecb..dc9d9f8 100644 --- a/landmapper/Dockerfile +++ b/landmapper/Dockerfile @@ -17,6 +17,7 @@ RUN apt-get update \ libgdal-dev \ libgeos-dev \ pdftk \ + git \ && rm -rf /var/lib/apt/lists/* # Set working directory @@ -25,6 +26,12 @@ WORKDIR /usr/src/app # Copy requirements first (cache pip install step when dependencies don't change) COPY requirements.txt /usr/src/app/ +# FileNotFoundError: [Errno 2] No such file or directory: '/usr/src/app/app/static/landmapper/report_pdf' + +# COPY src/madrona-features /usr/local/apps/madrona-features +# COPY src/madrona-manipulators /usr/local/apps/madrona-manipulators +# COPY src/p97-nursery /usr/local/apps/p97-nursery + # Upgrade pip and install Python dependencies RUN pip install --upgrade pip \ && pip install -r requirements.txt @@ -37,6 +44,10 @@ COPY . /usr/src/app COPY entrypoint.sh /usr/local/bin/entrypoint.sh RUN chmod +x /usr/local/bin/entrypoint.sh +# Create directory for PDF reports +# /usr/src/app/app/static/landmapper/report_pdf +# RUN mkdir -p /usr/src/app/app/static/landmapper/report_pdf + # Expose the port the app runs on (entrypoint starts django development server or uWSGI on 8000) EXPOSE 8000 diff --git a/landmapper/entrypoint.sh b/landmapper/entrypoint.sh index 8feb6cf..ffb5d69 100644 --- a/landmapper/entrypoint.sh +++ b/landmapper/entrypoint.sh @@ -41,8 +41,10 @@ fi # echo "Starting uWSGI (http) on :8000 with local settings" # uwsgi --http :8000 --master --enable-threads --module TEKDB.wsgi # elif [ "$1" = "dev" ]; then -echo "Starting python development server on :8000" -python manage.py runserver 0.0.0.0:8000 + +# TODO: bringback dev server +# echo "Starting python development server on :8000" +# python manage.py runserver 0.0.0.0:8000 # else # # Default to the passed command if not 'prod' or 'dev' # exec "$@" diff --git a/landmapper/landmapper/settings.py b/landmapper/landmapper/settings.py index 24ef8e3..bc8dfa8 100644 --- a/landmapper/landmapper/settings.py +++ b/landmapper/landmapper/settings.py @@ -23,14 +23,10 @@ # See https://docs.djangoproject.com/en/dev/howto/deployment/checklist/ # SECURITY WARNING: keep the secret key used in production secret! -SECRET_KEY = 'django-insecure-%v#dz7)z_dt@_*8swh3=-+8w4mg8*w2^wb$zofhqj*-p2b4jb_' +SECRET_KEY = os.environ.get("SECRET_KEY", default="set in .env file") # SECURITY WARNING: don't run with debug turned on in production! -DEBUG = True - -ALLOWED_HOSTS = [] - -SITE_ID = 1 +DEBUG = bool(int(os.environ.get("DEBUG", default=0))) # Application definition @@ -104,13 +100,13 @@ # Database # https://docs.djangoproject.com/en/dev/ref/settings/#databases -DATABASES = { - 'default': { - 'ENGINE': "django.contrib.gis.db.backends.postgis", - 'NAME': "landmapper", - 'USER': 'postgres', - } -} +# DATABASES = { +# 'default': { +# 'ENGINE': "django.contrib.gis.db.backends.postgis", +# 'NAME': "landmapper", +# 'USER': 'postgres', +# } +# } CACHES = { 'default': { @@ -162,7 +158,7 @@ LANGUAGE_CODE = 'en-us' -TIME_ZONE = 'US/Pacific' +TIME_ZONE = 'America/Los_Angeles' USE_I18N = True @@ -1520,7 +1516,8 @@ ########################################### FALLBACK_PROPERTY_REPORT_PDF_TEMPLATE = APP_DIR + '/pdf_templates/LM_Form.pdf' FALLBACK_PROPERTY_REPORT_PDF_TEMPLATE_SANS_FOREST_TYPES = APP_DIR + '/pdf_templates/LM_Form_sans_forests.pdf' -PROPERTY_REPORT_PDF_DIR = APP_DIR + '/static/landmapper/report_pdf/' +# PROPERTY_REPORT_PDF_DIR = APP_DIR + '/static/landmapper/report_pdf/' +PROPERTY_REPORT_PDF_DIR= '/tmp/' # For creating individual PDF exports PDF_PAGE_LOOKUP = { @@ -1553,6 +1550,43 @@ ########## OVERRIDES ################ STUDY_REGION_ID = 'OR' # Default to OR +GOOGLE_API_KEY = os.environ.get("GOOGLE_API_KEY", default="set in .env file") +MAPBOX_TOKEN = os.environ.get("MAPBOX_TOKEN", default="set in .env file") +GEOAPIFY_API_KEY = os.environ.get("GEOAPIFY_API_KEY", default="set in .env file") +RECAPTCHA_PUBLIC_KEY = os.environ.get("RECAPTCHA_PUBLIC_KEY", default="set in .env file") +RECAPTCHA_PRIVATE_KEY = os.environ.get("RECAPTCHA_PRIVATE_KEY", default="set in .env file") +ARCGIS_DEVELOPER_KEY = os.environ.get("ARCGIS_DEVELOPER_KEY", default="set in .env file") +GOOGLE_ANALYTICS_KEY = os.environ.get("GOOGLE_ANALYTICS_KEY", default="set in .env file") + +EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend' + +LOG_FILE = "/usr/local/apps/log.txt" + +STATIC_ROOT = '/usr/local/apps/landmapper/landmapper/app/static' + +STUDY_REGION_ID = 'OR' +STREAMS_SOURCE = "ECOTRUST_{}".format(STUDY_REGION_ID) +SOIL_SOURCE = "ECOTRUST_{}".format(STUDY_REGION_ID) +TAXLOTS_SOURCE = "ECOTRUST_{}".format(STUDY_REGION_ID) +FOREST_TYPES_SOURCE = "ECOTRUST_{}".format(STUDY_REGION_ID) +FOREST_SIZE_SOURCE = "ECOTRUST_{}".format(STUDY_REGION_ID) +FOREST_DENSITY_SOURCE = "ECOTRUST_{}".format(STUDY_REGION_ID) +FOREST_CANOPY_SOURCE = "ECOTRUST_{}".format(STUDY_REGION_ID) + +TAXLOT_IMPORT_TIMESTAMP = 1702590468 +ENFORCE_TIMESTAMP = True + +DATABASES = { + 'default': { + 'ENGINE': 'django.contrib.gis.db.backends.postgis', + 'NAME': os.environ.get("POSTGRES_DB", default="landmapper"), + 'USER': os.environ.get("POSTGRES_USER", default="postgres"), + 'PASSWORD': os.environ.get("POSTGRES_PASSWORD", default="landmapper_password"), + 'HOST': os.environ.get("POSTGRES_HOST", default="db"), + 'PORT': os.environ.get("POSTGRES_PORT", default="5432"), + } +} + try: from .local_settings import * except Exception as e: diff --git a/landmapper/requirements.txt b/landmapper/requirements.txt index 5b91877..283a148 100644 --- a/landmapper/requirements.txt +++ b/landmapper/requirements.txt @@ -7,12 +7,12 @@ django-redis # Django-Registration 3.4 does not appear to be sending password reset emails... django-registration<=3.3 # TODO: figure out if these are needed here, or if having them in the bound volumes is sufficient -# -e git+https://github.com/Ecotrust/madrona-features.git@main#egg=madrona_features --e /usr/local/apps/landmapper/apps/madrona-features -# -e git+https://github.com/Ecotrust/p97-nursery.git@main#egg=p97-nursery --e /usr/local/apps/landmapper/apps/p97-nursery -# -e git+https://github.com/Ecotrust/madrona-manipulators.git@main#egg=madrona_manipulators --e /usr/local/apps/landmapper/apps/madrona-manipulators +-e git+https://github.com/Ecotrust/madrona-features.git@main#egg=madrona_features +# -e /usr/local/apps/landmapper/apps/madrona-features +-e git+https://github.com/Ecotrust/p97-nursery.git@main#egg=p97-nursery +# -e /usr/local/apps/landmapper/apps/p97-nursery +-e git+https://github.com/Ecotrust/madrona-manipulators.git@main#egg=madrona_manipulators +# -e /usr/local/apps/landmapper/apps/madrona-manipulators django-tinymce geopy ipdb @@ -39,7 +39,7 @@ geocoder geopandas imageio scikit-image -pygeos +# pygeos matplotlib # matplotlib_scalebar pypdf2<3.0 @@ -48,7 +48,7 @@ PdfJinja ######## # Georefrence PDFs ######## -GDAL<3.4.2 +# GDAL<3.4.2 ######## # Exporting From 02624825359c571bd377e2a4baa96c0a15a0e605 Mon Sep 17 00:00:00 2001 From: Paige Williams Date: Tue, 18 Aug 2026 14:48:15 -0700 Subject: [PATCH 07/19] misc fixes to get landmapper running locally with docker --- landmapper/Dockerfile | 14 +++++++++---- landmapper/app/migrations/0001_initial.py | 4 ++-- landmapper/entrypoint.sh | 25 ++++++++--------------- landmapper/requirements.txt | 5 ----- 4 files changed, 21 insertions(+), 27 deletions(-) diff --git a/landmapper/Dockerfile b/landmapper/Dockerfile index dc9d9f8..8346bd6 100644 --- a/landmapper/Dockerfile +++ b/landmapper/Dockerfile @@ -18,16 +18,18 @@ RUN apt-get update \ libgeos-dev \ pdftk \ git \ + build-essential \ && rm -rf /var/lib/apt/lists/* +ENV CPLUS_INCLUDE_PATH=/usr/include/gdal +ENV C_INCLUDE_PATH=/usr/include/gdal + # Set working directory WORKDIR /usr/src/app # Copy requirements first (cache pip install step when dependencies don't change) COPY requirements.txt /usr/src/app/ -# FileNotFoundError: [Errno 2] No such file or directory: '/usr/src/app/app/static/landmapper/report_pdf' - # COPY src/madrona-features /usr/local/apps/madrona-features # COPY src/madrona-manipulators /usr/local/apps/madrona-manipulators # COPY src/p97-nursery /usr/local/apps/p97-nursery @@ -36,6 +38,10 @@ COPY requirements.txt /usr/src/app/ RUN pip install --upgrade pip \ && pip install -r requirements.txt +# After installing gdal-bin and libgdal-dev, install matching version of GDAL Python bindings. This is required for Django to use GDAL. +RUN GDAL_VERSION=`gdal-config --version` && \ + pip install GDAL==$GDAL_VERSION + # Copy the application code COPY . /usr/src/app @@ -45,8 +51,8 @@ COPY entrypoint.sh /usr/local/bin/entrypoint.sh RUN chmod +x /usr/local/bin/entrypoint.sh # Create directory for PDF reports -# /usr/src/app/app/static/landmapper/report_pdf -# RUN mkdir -p /usr/src/app/app/static/landmapper/report_pdf +RUN rm -f /usr/src/app/app/static/landmapper/report_pdf \ + && mkdir -p /usr/src/app/app/static/landmapper/report_pdf # Expose the port the app runs on (entrypoint starts django development server or uWSGI on 8000) EXPOSE 8000 diff --git a/landmapper/app/migrations/0001_initial.py b/landmapper/app/migrations/0001_initial.py index dde6813..fe3f480 100644 --- a/landmapper/app/migrations/0001_initial.py +++ b/landmapper/app/migrations/0001_initial.py @@ -1,11 +1,11 @@ # Generated by Django 4.2b1 on 2023-03-17 00:18 import app.models -import ckeditor_uploader.fields from django.conf import settings import django.contrib.gis.db.models.fields from django.db import migrations, models import django.db.models.deletion +import tinymce.models class Migration(migrations.Migration): @@ -38,7 +38,7 @@ class Migration(migrations.Migration): ('order', models.SmallIntegerField(default=10)), ('staff_only', models.BooleanField(default=False)), ('header', models.CharField(blank=True, default=None, max_length=255, null=True, verbose_name='Popup Header')), - ('content', ckeditor_uploader.fields.RichTextUploadingField(blank=True, default=None, null=True, verbose_name='Popup Body')), + ('content', tinymce.models.HTMLField(blank=True, default=None, null=True, verbose_name='Popup Body')), ], ), migrations.CreateModel( diff --git a/landmapper/entrypoint.sh b/landmapper/entrypoint.sh index ffb5d69..83540bb 100644 --- a/landmapper/entrypoint.sh +++ b/landmapper/entrypoint.sh @@ -28,23 +28,16 @@ python manage.py migrate --noinput # fi # Load default lookups only if no lookups exist. Use LookupPlanningUnit as the check. -echo "Checking for existing initial data..." -if [ "$(python manage.py shell -c 'from app.models import MenuPage; print(MenuPage.objects.count())' 2>/dev/null | tail -1)" = "0" ]; then - echo "No initial data found, loading default initial data fixture..." - python manage.py loaddata app/fixtures/initial_data.json -fi - -# if [ "$1" = "prod" ]; then -# echo "Starting uWSGI (socket) on :8000" -# uwsgi --socket :8000 --master --enable-threads --module TEKDB.wsgi -# elif [ "$1" = "prod-local" ]; then -# echo "Starting uWSGI (http) on :8000 with local settings" -# uwsgi --http :8000 --master --enable-threads --module TEKDB.wsgi -# elif [ "$1" = "dev" ]; then +# echo "Checking for existing initial data..." +# if [ "$(python manage.py shell -c 'from app.models import MenuPage; print(MenuPage.objects.count())' 2>/dev/null | tail -1)" = "0" ]; then +# echo "No initial data found, loading default initial data fixture..." +# python manage.py loaddata app/fixtures/initial_data.json +# else +# echo "Initial data already exists, skipping fixture load." +# fi -# TODO: bringback dev server -# echo "Starting python development server on :8000" -# python manage.py runserver 0.0.0.0:8000 +echo "Starting python development server on :8000" +python manage.py runserver 0.0.0.0:8000 # else # # Default to the passed command if not 'prod' or 'dev' # exec "$@" diff --git a/landmapper/requirements.txt b/landmapper/requirements.txt index 283a148..a48f9c5 100644 --- a/landmapper/requirements.txt +++ b/landmapper/requirements.txt @@ -45,11 +45,6 @@ matplotlib pypdf2<3.0 PdfJinja -######## -# Georefrence PDFs -######## -# GDAL<3.4.2 - ######## # Exporting ######## From d1e00ec6d96764fd01f66b47df3f969ddf4e4d56 Mon Sep 17 00:00:00 2001 From: Paige Williams Date: Tue, 18 Aug 2026 15:16:35 -0700 Subject: [PATCH 08/19] add initial data to database --- docker/docker-compose.yaml | 5 +++++ docker/init-db.sh | 24 ++++++++++++++++++++++++ 2 files changed, 29 insertions(+) create mode 100644 docker/init-db.sh diff --git a/docker/docker-compose.yaml b/docker/docker-compose.yaml index 80a7586..4d0640c 100644 --- a/docker/docker-compose.yaml +++ b/docker/docker-compose.yaml @@ -10,6 +10,11 @@ services: POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} volumes: - landmapper_db_data:/var/lib/postgresql + - ./OR_TAXLOTS_2021.sql:/tmp/OR_TAXLOTS_2021.sql:ro + - ./OR_POPULATION_2021.sql:/tmp/OR_POPULATION_2021.sql:ro + - ./FOREST_TYPES_2021.sql:/tmp/FOREST_TYPES_2021.sql:ro + - ./OR_SOIL_2021.sql:/tmp/OR_SOIL_2021.sql:ro + - ./init-db.sh:/docker-entrypoint-initdb.d/init-db.sh ports: - "5432:5432" healthcheck: diff --git a/docker/init-db.sh b/docker/init-db.sh new file mode 100644 index 0000000..16cc0aa --- /dev/null +++ b/docker/init-db.sh @@ -0,0 +1,24 @@ +#!/bin/sh +set -e + +# This script runs automatically when the PostgreSQL container is first initialized +# It will only run if the database is empty (first time setup) +TAXLOTS_FILE="/tmp/OR_TAXLOTS_2021.sql" +POPULATION_GRID_FILE="/tmp/OR_POPULATION_2021.sql" +FOREST_TYPES_FILE="/tmp/FOREST_TYPES_2021.sql" +SOIL_GRID_FILE="/tmp/OR_SOIL_2021.sql" + + +echo "Importing database dump..." + +# Import the SQL dump, ignoring meta-command errors +PGPASSWORD="$POSTGRES_PASSWORD" psql -U "$POSTGRES_USER" -d "$POSTGRES_DB" -v ON_ERROR_STOP=0 < "$TAXLOTS_FILE" + +PGPASSWORD="$POSTGRES_PASSWORD" psql -U "$POSTGRES_USER" -d "$POSTGRES_DB" -v ON_ERROR_STOP=0 < "$POPULATION_GRID_FILE" + +PGPASSWORD="$POSTGRES_PASSWORD" psql -U "$POSTGRES_USER" -d "$POSTGRES_DB" -v ON_ERROR_STOP=0 < "$FOREST_TYPES_FILE" + +PGPASSWORD="$POSTGRES_PASSWORD" psql -U "$POSTGRES_USER" -d "$POSTGRES_DB" -v ON_ERROR_STOP=0 < "$SOIL_GRID_FILE" + + +echo "Database import completed!" \ No newline at end of file From c5253de96047128ada3c9dc4aca32611a547d606 Mon Sep 17 00:00:00 2001 From: Paige Williams Date: Wed, 19 Aug 2026 09:27:27 -0700 Subject: [PATCH 09/19] add initial data fixture in entrypoint.sh --- landmapper/entrypoint.sh | 22 ++++++++-------------- 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/landmapper/entrypoint.sh b/landmapper/entrypoint.sh index 83540bb..613a412 100644 --- a/landmapper/entrypoint.sh +++ b/landmapper/entrypoint.sh @@ -20,21 +20,15 @@ echo "Collecting static files..." python manage.py collectstatic --noinput echo "Applying database migrations..." python manage.py migrate --noinput -# Load default users only if no users exist -# echo "Checking for existing users..." -# if [ "$(python manage.py shell -c 'from django.contrib.auth import get_user_model; print(get_user_model().objects.count())' 2>/dev/null | tail -1)" = "0" ]; then -# echo "No users found, loading default users fixture..." -# python manage.py loaddata TEKDB/fixtures/default_users_fixture.json -# fi -# Load default lookups only if no lookups exist. Use LookupPlanningUnit as the check. -# echo "Checking for existing initial data..." -# if [ "$(python manage.py shell -c 'from app.models import MenuPage; print(MenuPage.objects.count())' 2>/dev/null | tail -1)" = "0" ]; then -# echo "No initial data found, loading default initial data fixture..." -# python manage.py loaddata app/fixtures/initial_data.json -# else -# echo "Initial data already exists, skipping fixture load." -# fi +# Load default lookups only if no lookups exist. Use MenuPage as the check. +echo "Checking for existing initial data..." +if [ "$(python manage.py shell -c 'from app.models import MenuPage; print(MenuPage.objects.count())' 2>/dev/null | tail -1)" = "0" ]; then + echo "No initial data found, loading default initial data fixture..." + python manage.py loaddata app/fixtures/initial_data.json +else + echo "Initial data already exists, skipping fixture load." +fi echo "Starting python development server on :8000" python manage.py runserver 0.0.0.0:8000 From f54b594260af26377e7415ab14aef9a29aeb897d Mon Sep 17 00:00:00 2001 From: Paige Williams Date: Wed, 19 Aug 2026 10:10:53 -0700 Subject: [PATCH 10/19] add sql files after django migrations in entrypoint.sh; remove init-db.sh --- docker/docker-compose.yaml | 9 +++--- docker/init-db.sh | 24 --------------- landmapper/entrypoint.sh | 63 +++++++++++++++++++++++++++++++++++--- 3 files changed, 62 insertions(+), 34 deletions(-) delete mode 100644 docker/init-db.sh diff --git a/docker/docker-compose.yaml b/docker/docker-compose.yaml index 4d0640c..a5088d4 100644 --- a/docker/docker-compose.yaml +++ b/docker/docker-compose.yaml @@ -10,11 +10,6 @@ services: POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} volumes: - landmapper_db_data:/var/lib/postgresql - - ./OR_TAXLOTS_2021.sql:/tmp/OR_TAXLOTS_2021.sql:ro - - ./OR_POPULATION_2021.sql:/tmp/OR_POPULATION_2021.sql:ro - - ./FOREST_TYPES_2021.sql:/tmp/FOREST_TYPES_2021.sql:ro - - ./OR_SOIL_2021.sql:/tmp/OR_SOIL_2021.sql:ro - - ./init-db.sh:/docker-entrypoint-initdb.d/init-db.sh ports: - "5432:5432" healthcheck: @@ -60,6 +55,10 @@ services: # create_host_path: false - static_volume:/usr/src/app/static - media_volume:/usr/src/app/media + - ./OR_TAXLOTS.sql:/tmp/OR_TAXLOTS.sql:ro + - ./OR_POPULATION_2021.sql:/tmp/OR_POPULATION_2021.sql:ro + - ./FOREST_TYPES_2021.sql:/tmp/FOREST_TYPES_2021.sql:ro + - ./OR_SOIL_2021.sql:/tmp/OR_SOIL_2021.sql:ro depends_on: db: condition: service_healthy diff --git a/docker/init-db.sh b/docker/init-db.sh deleted file mode 100644 index 16cc0aa..0000000 --- a/docker/init-db.sh +++ /dev/null @@ -1,24 +0,0 @@ -#!/bin/sh -set -e - -# This script runs automatically when the PostgreSQL container is first initialized -# It will only run if the database is empty (first time setup) -TAXLOTS_FILE="/tmp/OR_TAXLOTS_2021.sql" -POPULATION_GRID_FILE="/tmp/OR_POPULATION_2021.sql" -FOREST_TYPES_FILE="/tmp/FOREST_TYPES_2021.sql" -SOIL_GRID_FILE="/tmp/OR_SOIL_2021.sql" - - -echo "Importing database dump..." - -# Import the SQL dump, ignoring meta-command errors -PGPASSWORD="$POSTGRES_PASSWORD" psql -U "$POSTGRES_USER" -d "$POSTGRES_DB" -v ON_ERROR_STOP=0 < "$TAXLOTS_FILE" - -PGPASSWORD="$POSTGRES_PASSWORD" psql -U "$POSTGRES_USER" -d "$POSTGRES_DB" -v ON_ERROR_STOP=0 < "$POPULATION_GRID_FILE" - -PGPASSWORD="$POSTGRES_PASSWORD" psql -U "$POSTGRES_USER" -d "$POSTGRES_DB" -v ON_ERROR_STOP=0 < "$FOREST_TYPES_FILE" - -PGPASSWORD="$POSTGRES_PASSWORD" psql -U "$POSTGRES_USER" -d "$POSTGRES_DB" -v ON_ERROR_STOP=0 < "$SOIL_GRID_FILE" - - -echo "Database import completed!" \ No newline at end of file diff --git a/landmapper/entrypoint.sh b/landmapper/entrypoint.sh index 613a412..4d737ed 100644 --- a/landmapper/entrypoint.sh +++ b/landmapper/entrypoint.sh @@ -30,9 +30,62 @@ else echo "Initial data already exists, skipping fixture load." fi +# Load geodata SQL dumps if tables are empty +echo "Checking for existing taxlot data..." +if [ "$(python manage.py shell -c 'from app.models import Taxlot; print(Taxlot.objects.count())' 2>/dev/null | tail -1)" = "0" ]; then + echo "No taxlot data found, loading SQL dumps..." + + # Check if SQL files exist and load them + TAXLOTS_FILE="/tmp/OR_TAXLOTS.sql" + if [ -f "$TAXLOTS_FILE" ]; then + echo "Loading taxlots from $TAXLOTS_FILE..." + PGPASSWORD="$POSTGRES_PASSWORD" psql -h "$POSTGRES_HOST" -U "$POSTGRES_USER" -d "$POSTGRES_DB" -f "$TAXLOTS_FILE" + else + echo "WARNING: $TAXLOTS_FILE not found" + fi +else + echo "Taxlot data already exists, skipping SQL dump load." +fi + +echo "Checking for population data..." +if [ "$(python manage.py shell -c 'from app.models import PopulationPoint; print(PopulationPoint.objects.count())' 2>/dev/null | tail -1)" = "0" ]; then + echo "No population data found, loading SQL dump..." + + POPULATION_FILE="/tmp/OR_POPULATION_2021.sql" + if [ -f "$POPULATION_FILE" ]; then + echo "Loading population data from $POPULATION_FILE..." + PGPASSWORD="$POSTGRES_PASSWORD" psql -h "$POSTGRES_HOST" -U "$POSTGRES_USER" -d "$POSTGRES_DB" -f "$POPULATION_FILE" + else + echo "WARNING: $POPULATION_FILE not found" + fi +else + echo "Population data already exists, skipping SQL dump load." +fi + +echo "Checking for Forest Types data..." +if [ "$(python manage.py shell -c 'from app.models import ForestType; print(ForestType.objects.count())' 2>/dev/null | tail -1)" = "0" ]; then + echo "No Forest Types data found, loading SQL dump..." + FOREST_FILE="/tmp/FOREST_TYPES_2021.sql" + if [ -f "$FOREST_FILE" ]; then + echo "Loading Forest Types data from $FOREST_FILE..." + PGPASSWORD="$POSTGRES_PASSWORD" psql -h "$POSTGRES_HOST" -U "$POSTGRES_USER" -d "$POSTGRES_DB" -f "$FOREST_FILE" + else + echo "WARNING: $FOREST_FILE not found" + fi + +fi + +echo "Checking for Soil Types data..." +if [ "$(python manage.py shell -c 'from app.models import SoilType; print(SoilType.objects.count())' 2>/dev/null | tail -1)" = "0" ]; then + echo "No Soil Types data found, loading SQL dump..." + SOIL_FILE="/tmp/OR_SOIL_2021.sql" + if [ -f "$SOIL_FILE" ]; then + echo "Loading Soil Types data from $SOIL_FILE..." + PGPASSWORD="$POSTGRES_PASSWORD" psql -h "$POSTGRES_HOST" -U "$POSTGRES_USER" -d "$POSTGRES_DB" -f "$SOIL_FILE" + else + echo "WARNING: $SOIL_FILE not found" + fi +fi + echo "Starting python development server on :8000" -python manage.py runserver 0.0.0.0:8000 -# else -# # Default to the passed command if not 'prod' or 'dev' -# exec "$@" -# fi \ No newline at end of file +python manage.py runserver 0.0.0.0:8000 \ No newline at end of file From 977a89fb929a10f9b9b533cae62224d1fe27f58f Mon Sep 17 00:00:00 2001 From: Paige Williams Date: Wed, 19 Aug 2026 11:50:53 -0700 Subject: [PATCH 11/19] create Site in entrypoint --- landmapper/entrypoint.sh | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/landmapper/entrypoint.sh b/landmapper/entrypoint.sh index 4d737ed..e33c684 100644 --- a/landmapper/entrypoint.sh +++ b/landmapper/entrypoint.sh @@ -21,6 +21,29 @@ python manage.py collectstatic --noinput echo "Applying database migrations..." python manage.py migrate --noinput +echo "Configuring Site object..." +python manage.py shell << 'EOF' +from django.contrib.sites.models import Site +from django.conf import settings + +site_id = getattr(settings, 'SITE_ID', 1) +domain = getattr(settings, 'ALLOWED_HOSTS', ['localhost'])[0] +if domain == '*': + domain = 'localhost' + +site, created = Site.objects.get_or_create( + id=site_id, + defaults={'domain': domain, 'name': 'Landmapper'} +) +if not created and (site.domain != domain or site.name != 'Landmapper'): + site.domain = domain + site.name = 'Landmapper' + site.save() + print(f"Updated Site: {site.name} ({site.domain})") +else: + print(f"Site configured: {site.name} ({site.domain})") +EOF + # Load default lookups only if no lookups exist. Use MenuPage as the check. echo "Checking for existing initial data..." if [ "$(python manage.py shell -c 'from app.models import MenuPage; print(MenuPage.objects.count())' 2>/dev/null | tail -1)" = "0" ]; then From b47c0957fd44c90b84336e9b4635ebd5407bd5cb Mon Sep 17 00:00:00 2001 From: Paige Williams Date: Wed, 19 Aug 2026 12:08:53 -0700 Subject: [PATCH 12/19] add allowed hosts --- landmapper/landmapper/settings.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/landmapper/landmapper/settings.py b/landmapper/landmapper/settings.py index bc8dfa8..376bad6 100644 --- a/landmapper/landmapper/settings.py +++ b/landmapper/landmapper/settings.py @@ -1587,10 +1587,13 @@ } } -try: - from .local_settings import * -except Exception as e: - pass +ALLOWED_HOSTS = [] +ALLOWED_HOSTS_ENV = os.environ.get("ALLOWED_HOSTS") +if ALLOWED_HOSTS_ENV: + ALLOWED_HOSTS.extend(ALLOWED_HOSTS_ENV.split(",")) + +SITE_ID = 1 + STUDY_REGION = STUDY_REGIONS[STUDY_REGION_ID] # Test PDF template From a9f05d6c72bcab9c0b2bba0e5526698e9a1e3b05 Mon Sep 17 00:00:00 2001 From: Paige Williams Date: Wed, 19 Aug 2026 13:56:50 -0700 Subject: [PATCH 13/19] use redis service name in CACHES setting --- landmapper/landmapper/settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/landmapper/landmapper/settings.py b/landmapper/landmapper/settings.py index 376bad6..61f3a46 100644 --- a/landmapper/landmapper/settings.py +++ b/landmapper/landmapper/settings.py @@ -111,7 +111,7 @@ CACHES = { 'default': { 'BACKEND': 'django_redis.cache.RedisCache', - 'LOCATION': 'redis://127.0.0.1:6379/1', + 'LOCATION': 'redis://redis:6379/1', 'OPTIONS': { 'CLIENT_CLASS': 'django_redis.client.DefaultClient' } From 8e5112a8c6ae22d8ee71ec8da5572bcd05397a2b Mon Sep 17 00:00:00 2001 From: Paige Williams Date: Wed, 19 Aug 2026 15:25:07 -0700 Subject: [PATCH 14/19] define ports for redis service --- docker/docker-compose.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docker/docker-compose.yaml b/docker/docker-compose.yaml index a5088d4..2250024 100644 --- a/docker/docker-compose.yaml +++ b/docker/docker-compose.yaml @@ -20,6 +20,8 @@ services: redis: container_name: redis image: redis:alpine + ports: + - "6379:6379" landmapper: container_name: landmapper build: From f471c11f21f15712a78ab6057acb13a5d3a02707 Mon Sep 17 00:00:00 2001 From: Paige Williams Date: Wed, 19 Aug 2026 16:09:51 -0700 Subject: [PATCH 15/19] revert to python 3.10 --- landmapper/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/landmapper/Dockerfile b/landmapper/Dockerfile index 8346bd6..a9362a8 100644 --- a/landmapper/Dockerfile +++ b/landmapper/Dockerfile @@ -1,4 +1,4 @@ -FROM python:3.14-slim +FROM python:3.10-slim # Prevent Python from writing .pyc files and enable unbuffered stdout/stderr ENV PYTHONDONTWRITEBYTECODE=1 From 0f67b719ed38559407a8fbcf802668b1dc820c59 Mon Sep 17 00:00:00 2001 From: Paige Williams Date: Thu, 20 Aug 2026 13:38:11 -0700 Subject: [PATCH 16/19] bind local madrona repos; add landmapper code as a volume --- docker/docker-compose.yaml | 32 +++++++++++---------- landmapper/Dockerfile | 2 +- landmapper/app/static/landmapper/report_pdf | 1 - 3 files changed, 18 insertions(+), 17 deletions(-) delete mode 120000 landmapper/app/static/landmapper/report_pdf diff --git a/docker/docker-compose.yaml b/docker/docker-compose.yaml index 2250024..0113620 100644 --- a/docker/docker-compose.yaml +++ b/docker/docker-compose.yaml @@ -40,23 +40,24 @@ services: retries: 5 start_period: 30s volumes: - # - type: bind - # source: ../p97-nursery - # target: /usr/local/apps/landmapper/apps/p97-nursery - # bind: - # create_host_path: false - # - type: bind - # source: ../madrona-features - # target: /usr/local/apps/landmapper/apps/madrona-features - # bind: - # create_host_path: false - # - type: bind - # source: ../madrona-manipulators - # target: /usr/local/apps/landmapper/apps/madrona-manipulators - # bind: - # create_host_path: false + - type: bind + source: ../p97-nursery + target: /usr/local/apps/landmapper/apps/p97-nursery + bind: + create_host_path: false + - type: bind + source: ../madrona-features + target: /usr/local/apps/landmapper/apps/madrona-features + bind: + create_host_path: false + - type: bind + source: ../madrona-manipulators + target: /usr/local/apps/landmapper/apps/madrona-manipulators + bind: + create_host_path: false - static_volume:/usr/src/app/static - media_volume:/usr/src/app/media + - ../landmapper:/usr/src/app - ./OR_TAXLOTS.sql:/tmp/OR_TAXLOTS.sql:ro - ./OR_POPULATION_2021.sql:/tmp/OR_POPULATION_2021.sql:ro - ./FOREST_TYPES_2021.sql:/tmp/FOREST_TYPES_2021.sql:ro @@ -76,6 +77,7 @@ services: POSTGRES_USER: ${POSTGRES_USER} POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} SECRET_KEY: ${SECRET_KEY} + PYTHONPATH: /usr/local/apps/landmapper/apps/madrona-features:/usr/local/apps/landmapper/apps/p97-nursery:/usr/local/apps/landmapper/apps/madrona-manipulators:/usr/src/app volumes: landmapper_db_data: static_volume: diff --git a/landmapper/Dockerfile b/landmapper/Dockerfile index a9362a8..d98c6ae 100644 --- a/landmapper/Dockerfile +++ b/landmapper/Dockerfile @@ -51,7 +51,7 @@ COPY entrypoint.sh /usr/local/bin/entrypoint.sh RUN chmod +x /usr/local/bin/entrypoint.sh # Create directory for PDF reports -RUN rm -f /usr/src/app/app/static/landmapper/report_pdf \ +RUN rm -rf /usr/src/app/app/static/landmapper/report_pdf \ && mkdir -p /usr/src/app/app/static/landmapper/report_pdf # Expose the port the app runs on (entrypoint starts django development server or uWSGI on 8000) diff --git a/landmapper/app/static/landmapper/report_pdf b/landmapper/app/static/landmapper/report_pdf deleted file mode 120000 index eddb1cf..0000000 --- a/landmapper/app/static/landmapper/report_pdf +++ /dev/null @@ -1 +0,0 @@ -/usr/local/apps/pdf/ \ No newline at end of file From 086b45a1a7b3c94260fc587bd5eb9f99e5ec4583 Mon Sep 17 00:00:00 2001 From: Paige Williams Date: Mon, 24 Aug 2026 09:53:46 -0700 Subject: [PATCH 17/19] move PYTHONPATH env var to Dockerfile --- docker/docker-compose.yaml | 1 - landmapper/Dockerfile | 2 ++ 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/docker/docker-compose.yaml b/docker/docker-compose.yaml index 0113620..7158e0f 100644 --- a/docker/docker-compose.yaml +++ b/docker/docker-compose.yaml @@ -77,7 +77,6 @@ services: POSTGRES_USER: ${POSTGRES_USER} POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} SECRET_KEY: ${SECRET_KEY} - PYTHONPATH: /usr/local/apps/landmapper/apps/madrona-features:/usr/local/apps/landmapper/apps/p97-nursery:/usr/local/apps/landmapper/apps/madrona-manipulators:/usr/src/app volumes: landmapper_db_data: static_volume: diff --git a/landmapper/Dockerfile b/landmapper/Dockerfile index d98c6ae..46a108c 100644 --- a/landmapper/Dockerfile +++ b/landmapper/Dockerfile @@ -24,6 +24,8 @@ RUN apt-get update \ ENV CPLUS_INCLUDE_PATH=/usr/include/gdal ENV C_INCLUDE_PATH=/usr/include/gdal +ENV PYTHONPATH="/usr/local/apps/landmapper/apps/madrona-features:/usr/local/apps/landmapper/apps/p97-nursery:/usr/local/apps/landmapper/apps/madrona-manipulators:/usr/src/app" + # Set working directory WORKDIR /usr/src/app From ca5758e049a383869b434d7c2fc5920eb13ce539 Mon Sep 17 00:00:00 2001 From: Paige Williams Date: Mon, 24 Aug 2026 09:54:09 -0700 Subject: [PATCH 18/19] git ignore any .sql files --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 831d50e..19dae96 100644 --- a/.gitignore +++ b/.gitignore @@ -11,4 +11,5 @@ landmapper/app/testing_files landmapper/app/static/landmapper/shapefiles/ backups/* src/* -.env \ No newline at end of file +.env +*.sql \ No newline at end of file From fb10aee15e3abfe27f1acae5e1b79e41dbccb780 Mon Sep 17 00:00:00 2001 From: Paige Williams Date: Mon, 24 Aug 2026 14:00:14 -0700 Subject: [PATCH 19/19] add instructions on how to run with Docker to README --- README.md | 35 ++++++++++++++++++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 20b1915..082ae3a 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,40 @@ Simple Woodland Discovery For Land Owners ## Installation -Under construction - Coming Soon! + +### Docker + +To run locally with Docker follow these steps (for Oregon): + +Pre-requisites: +- Docker +- Docker compose +1. Clone the repo +``` +git clone https://github.com/Ecotrust/landmapper.git +``` +2. Check out the `dockerize` branch +``` +git checkout dockerize +``` +3. Clone dependent repos in a location next to the landmapper repo +``` +git clone https://github.com/Ecotrust/madrona-features.git +git clone https://github.com/Ecotrust/madrona-manipulators.git +git clone https://github.com/Ecotrust/p97-nursery.git +``` +4. Add the following files to the `docker/` directory. Ask an Ecotrust Software team member for these files: +- OR_TAXLOTS.sql +- OR_POPULATION_2021.sql +- FOREST_TYPES_2021.sql +- OR_SOIL_2021.sql + +5. Start the docker containers using docker compose +``` +docker compose up -d --build +``` + +6. Application should be live at `https://localhost:8000/` ---