From 6e591c02fe0890afb0d5b1c30aafe4fcf781b00a Mon Sep 17 00:00:00 2001 From: Ntkskwk Date: Mon, 13 Nov 2023 09:13:00 +0800 Subject: [PATCH 01/13] add: new vegetation index by @NtskwK , @QinShengLiangAn --- app/api/formulas.py | 53 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/app/api/formulas.py b/app/api/formulas.py index 320878bf0..f3b6c109c 100644 --- a/app/api/formulas.py +++ b/app/api/formulas.py @@ -120,6 +120,59 @@ 'help': _('Temperature in Centikelvin degrees.') }, + + # new vegetation index + + 'SIPI': { + 'expr': '(N - B) / (N - R)', + 'help': _('Structure-Insensitive Pigment Index.') + }, + 'mNDVI': { + 'expr': '(N - Re) / (N + Re - 2 * B)', + 'help': _('Modified Normalized Difference Vegetation Index.') + }, + 'RENDVI': { + 'expr': '(N - Re) / (N + Re)', + 'help': _('Ratio Enhanced Normalized Difference Vegetation Index.') + }, + 'MTVI_1': { + 'expr': '1.2 * (1.2 * (N - G) - 2.5 * (R - G))', + 'help': _('Modified Transformed Vegetation Index 1.') + }, + 'MTVI_2': { + 'expr': '1.5 * (1.2 * (N - G) - 2.5(R - G)) / (((2 * N + 1) ** 2 - (6 * N - 5 * (R0 ** 0.5)) - 0.5) ** 0.5)', + 'help': _('Modified Transformed Vegetation Index 2.') + }, + 'GI': { + 'expr': 'G / R', + 'help': _('Greenness Index.'), + }, + 'TVI': { + 'expr': '0.5 * (120 * (N - G) - 200 * (R - G))', + 'help': _('Transparency Vegetation Index.') + }, + 'MCARI_1': { + 'expr': '1.2 * (2.5 * (N - R) - 1.3 * (N - G))', + 'help': _('Modified Chlorophyll Absorption Reflectance Index 1.') + }, + 'CI': { + 'expr': 'N / G - 1', + 'help': _('Chlorophyll Index.') + }, + 'SR': { + 'expr': 'N / R', + 'help': _('Simple Ratio.') + }, + 'mSR_2': { + 'expr': '(N - Re) / (N + Re)', + 'help': _('Modified Simple Ratio.') + }, + 'IPVI': { + 'expr': 'N / (N + R)', + 'help': _('Improved Perpendicular Vegetation Index.') + }, + + # more? '_TESTRB': { From e2e4fb36a8aca962d32e9419a9a1e040ea2e419f Mon Sep 17 00:00:00 2001 From: Ntkskwk Date: Tue, 20 Aug 2024 03:06:00 +0800 Subject: [PATCH 02/13] style: Update scss style to fit webpack in new version --- app/static/app/js/css/GCPPopup.scss | 6 +++--- app/static/app/js/css/Map.scss | 17 ++++++++--------- app/static/app/js/css/MapView.scss | 5 +++-- app/static/app/js/css/ModelView.scss | 8 +++----- app/static/app/js/css/Paginator.scss | 2 +- app/static/app/js/css/ProjectListItem.scss | 7 +++---- app/static/app/js/css/SharePopup.scss | 10 +++++----- app/static/app/js/css/TagsField.scss | 8 ++++---- app/static/app/js/css/TaskListItem.scss | 4 ++-- 9 files changed, 32 insertions(+), 35 deletions(-) diff --git a/app/static/app/js/css/GCPPopup.scss b/app/static/app/js/css/GCPPopup.scss index 757f50730..5380cf1df 100644 --- a/app/static/app/js/css/GCPPopup.scss +++ b/app/static/app/js/css/GCPPopup.scss @@ -34,13 +34,13 @@ opacity: 0.2; } &.fullscreen{ - &.loading{ - opacity: 1; - } display: flex; align-items: center; justify-content: center; color: white; + &.loading{ + opacity: 1; + } i{ font-size: 200%; position: absolute; diff --git a/app/static/app/js/css/Map.scss b/app/static/app/js/css/Map.scss index 76492ca58..f73ad46c2 100644 --- a/app/static/app/js/css/Map.scss +++ b/app/static/app/js/css/Map.scss @@ -33,23 +33,22 @@ } .asset-links{ + margin-top: 8px; + padding-left: 16px; + + columns: 2; + -webkit-columns: 2; + -moz-columns: 2; li:first-child{ display: none; } &.loading{ - li{ display: none } + padding-left: 0; + list-style-type: none;li{ display: none } li:first-child{ display: block; } - padding-left: 0; - list-style-type: none; } - margin-top: 8px; - padding-left: 16px; - - columns: 2; - -webkit-columns: 2; - -moz-columns: 2; } .switchModeButton{ bottom: 12px; diff --git a/app/static/app/js/css/MapView.scss b/app/static/app/js/css/MapView.scss index 5f709f601..478563c2c 100644 --- a/app/static/app/js/css/MapView.scss +++ b/app/static/app/js/css/MapView.scss @@ -4,13 +4,14 @@ } .map-view{ + height: calc(100% - 20px); + position: relative; + .map-title{ text-overflow: ellipsis; overflow: hidden; white-space: nowrap; } - height: calc(100% - 20px); - position: relative; input[type="range"]{ margin-left: 4px; diff --git a/app/static/app/js/css/ModelView.scss b/app/static/app/js/css/ModelView.scss index 6cf23f8b7..f24a3a881 100644 --- a/app/static/app/js/css/ModelView.scss +++ b/app/static/app/js/css/ModelView.scss @@ -153,7 +153,9 @@ #sidebar_root{ width: 300px; - + position: absolute; + min-height: 100%; + height: 100%; .pv-menu-list{ padding-right: 12px; @@ -170,10 +172,6 @@ cursor: pointer; } - position: absolute; - min-height: 100%; - height: 100%; - .potree_sidebar_brand{ display: flex; flex-direction: row; diff --git a/app/static/app/js/css/Paginator.scss b/app/static/app/js/css/Paginator.scss index d0f78a19d..f467740b9 100644 --- a/app/static/app/js/css/Paginator.scss +++ b/app/static/app/js/css/Paginator.scss @@ -4,10 +4,10 @@ margin-bottom: 8px; .toolbar{ + margin-right: 8px; i{ opacity: 0.8; } - margin-right: 8px; &.no-margin{ margin-right: 0; } diff --git a/app/static/app/js/css/ProjectListItem.scss b/app/static/app/js/css/ProjectListItem.scss index 4572e4edd..4371b8acb 100644 --- a/app/static/app/js/css/ProjectListItem.scss +++ b/app/static/app/js/css/ProjectListItem.scss @@ -1,5 +1,7 @@ .project-list-item{ min-height: 60px; + -webkit-transition: background-color 1s ease; + transition: background-color 1s ease; .project-name{ font-weight: bold; @@ -40,14 +42,11 @@ background-color: #eee; } - -webkit-transition: background-color 1s ease; - transition: background-color 1s ease; - &.dz-drag-hover{ + background-color: #f7f7f7; .drag-drop-icon{ display: block; } - background-color: #f7f7f7; } .project-links{ diff --git a/app/static/app/js/css/SharePopup.scss b/app/static/app/js/css/SharePopup.scss index e19a9327e..5c3ba25e6 100644 --- a/app/static/app/js/css/SharePopup.scss +++ b/app/static/app/js/css/SharePopup.scss @@ -1,4 +1,6 @@ .sharePopup{ + right: 0; + pointer-events: none; position: absolute; &.top{ bottom: 0; @@ -6,8 +8,6 @@ &.bottom{ top: 0; } - right: 0; - pointer-events: none; .sharePopupContainer{ pointer-events: auto; @@ -52,15 +52,15 @@ } .share-links{ + max-height: 0; + overflow: hidden; + transition: max-height 1s ease-in-out; & > div{ margin-top: 8px; &:first-child{ margin-top: 4px; } } - max-height: 0; - overflow: hidden; - transition: max-height 1s ease-in-out; &.show{ max-height: 800px; diff --git a/app/static/app/js/css/TagsField.scss b/app/static/app/js/css/TagsField.scss index 3c7a07820..dbd55d59b 100644 --- a/app/static/app/js/css/TagsField.scss +++ b/app/static/app/js/css/TagsField.scss @@ -7,9 +7,6 @@ cursor: text; } .tag-badge{ - &:hover{ - cursor: grab; - } display: inline-block; width: auto; padding-left: 6px; @@ -19,7 +16,10 @@ margin-right: 4px; margin-bottom: 8px; border-radius: 6px; - + + &:hover{ + cursor: grab; + } a{ margin-top: 2px; font-weight: bold; diff --git a/app/static/app/js/css/TaskListItem.scss b/app/static/app/js/css/TaskListItem.scss index 2644fbfcb..8a7b474e2 100644 --- a/app/static/app/js/css/TaskListItem.scss +++ b/app/static/app/js/css/TaskListItem.scss @@ -52,10 +52,10 @@ @media screen and (max-width: 576px){ .status-label { + text-align: center; & > span{ display: none; } - text-align: center; } } @@ -70,6 +70,7 @@ padding-right: 16px; .info-table { + margin-bottom: 12px; tr td:first-child { width: 33%; max-width: 150px; @@ -77,7 +78,6 @@ td{ padding: 0; } - margin-bottom: 12px; @media screen and (min-width: 1200px) { tr td:first-child { width: 20%; From edb3b994b11c2285b61332b2325e244c42467ee1 Mon Sep 17 00:00:00 2001 From: Ntkskwk Date: Tue, 20 Aug 2024 04:25:35 +0800 Subject: [PATCH 03/13] feat: add .vscode to .gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index d69cbb8b0..168adfca7 100644 --- a/.gitignore +++ b/.gitignore @@ -95,6 +95,7 @@ node_modules/ webpack-stats.json pip-selfcheck.json .idea/ +.vscode/ package-lock.json .cronenv .initialized From 844297b9e4747ff336db79b2cea70fb7b8e6e7fd Mon Sep 17 00:00:00 2001 From: Ntkskwk Date: Tue, 20 Aug 2024 04:26:28 +0800 Subject: [PATCH 04/13] feat: remove version tag from docker-compose files --- docker-compose.build.yml | 1 - docker-compose.dev.yml | 1 - docker-compose.nodemicmac.yml | 2 -- docker-compose.nodeodm.gpu.intel.yml | 2 -- docker-compose.nodeodm.gpu.nvidia.yml | 2 -- docker-compose.nodeodm.yml | 2 -- docker-compose.settings.yml | 1 - docker-compose.ssl-manual.yml | 1 - docker-compose.ssl.yml | 1 - docker-compose.worker-cpu.yml | 1 - docker-compose.worker-memory.yml | 1 - docker-compose.yml | 1 - 12 files changed, 16 deletions(-) diff --git a/docker-compose.build.yml b/docker-compose.build.yml index 302c8c517..80dd43e09 100644 --- a/docker-compose.build.yml +++ b/docker-compose.build.yml @@ -1,6 +1,5 @@ # This configuration builds from Dockerfiles # instead of pulling from opendronemap hub -version: '2.1' services: db: build: ./db diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index 393fec3ec..b28b90459 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -1,4 +1,3 @@ -version: '2.1' services: webapp: entrypoint: /bin/bash -c "chmod +x /webodm/*.sh && /bin/bash -c \"/webodm/wait-for-postgres.sh db /webodm/wait-for-it.sh -t 0 broker:6379 -- /webodm/start.sh --create-default-pnode --setup-devenv\"" diff --git a/docker-compose.nodemicmac.yml b/docker-compose.nodemicmac.yml index 3e71f2474..26f1ec911 100644 --- a/docker-compose.nodemicmac.yml +++ b/docker-compose.nodemicmac.yml @@ -1,8 +1,6 @@ # Chaining this file to the main docker-compose file adds # a default processing node instance. This is best for users # who are just getting started with WebODM. - -version: '2.1' services: webapp: depends_on: diff --git a/docker-compose.nodeodm.gpu.intel.yml b/docker-compose.nodeodm.gpu.intel.yml index 4e3f72c24..8759ad512 100644 --- a/docker-compose.nodeodm.gpu.intel.yml +++ b/docker-compose.nodeodm.gpu.intel.yml @@ -1,8 +1,6 @@ # Chaining this file to the main docker-compose file adds # a default processing node instance. This is best for users # who are just getting started with WebODM. - -version: '2.1' services: webapp: depends_on: diff --git a/docker-compose.nodeodm.gpu.nvidia.yml b/docker-compose.nodeodm.gpu.nvidia.yml index b16d92011..be4c046e9 100644 --- a/docker-compose.nodeodm.gpu.nvidia.yml +++ b/docker-compose.nodeodm.gpu.nvidia.yml @@ -1,8 +1,6 @@ # Chaining this file to the main docker-compose file adds # a default processing node instance. This is best for users # who are just getting started with WebODM. - -version: '2.1' services: webapp: depends_on: diff --git a/docker-compose.nodeodm.yml b/docker-compose.nodeodm.yml index 08454eff8..29771ad46 100644 --- a/docker-compose.nodeodm.yml +++ b/docker-compose.nodeodm.yml @@ -1,8 +1,6 @@ # Chaining this file to the main docker-compose file adds # a default processing node instance. This is best for users # who are just getting started with WebODM. - -version: '2.1' services: webapp: depends_on: diff --git a/docker-compose.settings.yml b/docker-compose.settings.yml index 16182fb1c..df759d370 100644 --- a/docker-compose.settings.yml +++ b/docker-compose.settings.yml @@ -1,4 +1,3 @@ -version: '2.1' services: webapp: volumes: diff --git a/docker-compose.ssl-manual.yml b/docker-compose.ssl-manual.yml index 6c809a91e..6a57b3980 100644 --- a/docker-compose.ssl-manual.yml +++ b/docker-compose.ssl-manual.yml @@ -1,5 +1,4 @@ # This configuration adds the volumes necessary for SSL manual setup -version: '2.1' services: webapp: volumes: diff --git a/docker-compose.ssl.yml b/docker-compose.ssl.yml index bfa68bd28..67c30000f 100644 --- a/docker-compose.ssl.yml +++ b/docker-compose.ssl.yml @@ -1,5 +1,4 @@ # This configuration adds support for SSL -version: '2.1' volumes: letsencrypt: driver: local diff --git a/docker-compose.worker-cpu.yml b/docker-compose.worker-cpu.yml index b002e0e1d..9afffb5c5 100644 --- a/docker-compose.worker-cpu.yml +++ b/docker-compose.worker-cpu.yml @@ -1,4 +1,3 @@ -version: '2.2' services: worker: cpus: ${WO_WORKER_CPUS} \ No newline at end of file diff --git a/docker-compose.worker-memory.yml b/docker-compose.worker-memory.yml index b2f7b98a0..cbaef4bd0 100644 --- a/docker-compose.worker-memory.yml +++ b/docker-compose.worker-memory.yml @@ -1,4 +1,3 @@ -version: '2.1' services: worker: mem_limit: ${WO_WORKER_MEMORY} \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index 9c460d99b..00eed19b5 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,6 +1,5 @@ # This configuration does not include a processing node # Which makes for faster setup times -version: '2.1' volumes: dbdata: appmedia: From 197e72ccecb0ba8b530d7382333c57fe53151a49 Mon Sep 17 00:00:00 2001 From: Ntkskwk Date: Tue, 20 Aug 2024 04:36:48 +0800 Subject: [PATCH 05/13] feat: update postgresql to 16.1 --- db/Dockerfile | 52 ++++++++++++++++++----------------------- db/docker-entrypoint.sh | 2 ++ 2 files changed, 25 insertions(+), 29 deletions(-) diff --git a/db/Dockerfile b/db/Dockerfile index 13f2b12d8..d5bd85b94 100644 --- a/db/Dockerfile +++ b/db/Dockerfile @@ -1,17 +1,16 @@ FROM ubuntu:20.04 MAINTAINER Piero Toffanin -ENV POSTGRES_PASSWORD postgres -ENV POSTGRES_HOST_AUTH_METHOD trust -ENV POSTGRES_ALLOW_HOST all -ENV GOSU_VERSION 1.12 -ENV PG_MAJOR 9.5 -ENV PG_VERSION 9.5.25 -ENV POSTGIS_VERSION 2.3.2 -ENV PATH $PATH:/usr/local/pgsql/bin +ENV POSTGRES_PASSWORD=postgres +ENV POSTGRES_HOST_AUTH_METHOD=trust +ENV POSTGRES_ALLOW_HOST=all +ENV GOSU_VERSION=1.12 +ENV PG_MAJOR=16.1 +ENV POSTGIS_VERSION=3.3.6 +ENV PATH=$PATH:/usr/local/pgsql/bin ENV DEBIAN_FRONTEND=noninteractive -ENV LANG en_US.utf8 -ENV PGDATA /var/lib/postgresql/data +ENV LANG=en_US.utf8 +ENV PGDATA=/var/lib/postgresql/data RUN mkdir /docker-entrypoint-initdb.d COPY init.sql /docker-entrypoint-initdb.d/init-db.sql @@ -29,30 +28,26 @@ RUN apt-get update; \ chown -R postgres:postgres /var/lib/postgresql; \ # grab gosu for easy step-down from root # https://github.com/tianon/gosu/releases - apt-get update; \ - apt-get install -y --no-install-recommends wget; \ - rm -rf /var/lib/apt/lists/*; \ + apt-get install -y --no-install-recommends wget gcc libreadline-dev > /dev/null; \ dpkgArch="$(dpkg --print-architecture | awk -F- '{ print $NF }')"; \ - wget --no-check-certificate -O /usr/local/bin/gosu "https://github.com/tianon/gosu/releases/download/$GOSU_VERSION/gosu-$dpkgArch"; \ + wget --no-check-certificate -q -O /usr/local/bin/gosu "https://github.com/tianon/gosu/releases/download/$GOSU_VERSION/gosu-$dpkgArch"; \ chmod +x /usr/local/bin/gosu; \ gosu --version; \ gosu nobody true; \ # make the "en_US.UTF-8" locale so postgres will be utf-8 enabled by default - apt-get update; \ - apt-get install -y --no-install-recommends locales; \ + apt-get install -y --no-install-recommends locales > /dev/null; \ localedef -i en_US -c -f UTF-8 -A /usr/share/locale/locale.alias en_US.UTF-8; \ # Build Postgres from source mkdir /staging; \ - apt-get update; \ - apt-get install -y --no-install-recommends wget gcc build-essential libproj-dev libgeos-dev libxml2-dev zlib1g-dev libreadline-dev; \ + apt-get install -y --no-install-recommends build-essential libproj-dev libgeos-dev libxml2-dev zlib1g-dev > /dev/null ; \ cd /staging; \ - wget --no-check-certificate -q https://github.com/OpenDroneMap/WebODM/releases/download/v1.9.2/postgresql-$PG_VERSION.tar.gz; \ + wget --no-check-certificate -q https://ftp.postgresql.org/pub/source/v$PG_MAJOR/postgresql-$PG_MAJOR.tar.gz; \ cd /staging; \ - tar -zxf postgresql-$PG_VERSION.tar.gz; \ - cd postgresql-$PG_VERSION; \ - ./configure; \ - make -j$(nproc); \ - make install; \ + tar -zxf postgresql-$PG_MAJOR.tar.gz; \ + cd postgresql-$PG_MAJOR; \ + ./configure --without-icu > /dev/null; \ + make -j$(nproc) > /dev/null; \ + make install > /dev/null; \ postgres --version; \ sed -ri "s/#autovacuum_max_workers = 3/autovacuum_max_workers = 6/" /usr/local/pgsql/share/postgresql.conf.sample; \ sed -ri "s/#autovacuum_naptime = 1min/autovacuum_naptime = 15s/" /usr/local/pgsql/share/postgresql.conf.sample; \ @@ -69,15 +64,14 @@ RUN apt-get update; \ # this 777 will be replaced by 700 at runtime (allows semi-arbitrary "--user" values) mkdir -p "$PGDATA" && chown -R postgres:postgres "$PGDATA" && chmod 777 "$PGDATA"; \ # Build PostGIS from source - apt-get update; \ - apt-get install -y --no-install-recommends libgdal-dev libjson-c-dev; \ + apt-get install -o Acquire::Retries=3 -y --no-install-recommends libgdal-dev libjson-c-dev protobuf-c-compiler libprotobuf-c-dev; \ cd /staging; \ - wget --no-check-certificate -q https://github.com/OpenDroneMap/WebODM/releases/download/v1.9.2/postgis-$POSTGIS_VERSION.tar.gz; \ - wget --no-check-certificate -q -O /usr/include/json-c/json_object_private.h https://raw.githubusercontent.com/json-c/json-c/json-c-0.13/json_object_private.h; \ + wget --no-check-certificate -q https://download.osgeo.org/postgis/source/postgis-$POSTGIS_VERSION.tar.gz; \ + wget --no-check-certificate -q -O /usr/include/json-c/json_object_private.h https://raw.githubusercontent.com/json-c/json-c/json-c-0.16/json_object_private.h; \ tar -zxf postgis-$POSTGIS_VERSION.tar.gz; \ sed -i 's/#error.*/#define ACCEPT_USE_OF_DEPRECATED_PROJ_API_H 1/' /usr/include/proj_api.h; \ cd /staging/postgis-$POSTGIS_VERSION; \ - ./configure --with-pgconfig=/usr/local/pgsql/bin/pg_config; \ + ./configure --with-pgconfig=/usr/local/pgsql/bin/pg_config --with-gdalconfig=/usr/bin/gdal-config > /dev/null; \ make; \ make install; \ sed -i '1d' /usr/local/pgsql/share/extension/postgis--$POSTGIS_VERSION.sql; \ diff --git a/db/docker-entrypoint.sh b/db/docker-entrypoint.sh index 7adf35c92..eb6d88f86 100755 --- a/db/docker-entrypoint.sh +++ b/db/docker-entrypoint.sh @@ -321,6 +321,8 @@ _main() { fi exec "$@" + docker_process_sql --dbname="$POSTGRES_DB" <<<'CREATE extension postgis_raster CASCADE;' + } if ! _is_sourced; then From e1656cdb12749ab267cd57ff8832d48302f25e41 Mon Sep 17 00:00:00 2001 From: Ntkskwk Date: Tue, 20 Aug 2024 04:45:24 +0800 Subject: [PATCH 06/13] feat: update webpack config for hash filename --- webpack.config.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/webpack.config.js b/webpack.config.js index 5ca2465d1..9070496b4 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -22,7 +22,7 @@ module.exports = { output: { path: path.join(__dirname, './app/static/app/bundles/'), - filename: "[name]-[hash].js", + filename: "[name]-[fullhash].js", publicPath: '/static/app/bundles/' }, @@ -33,7 +33,7 @@ module.exports = { path: path.join(__dirname, './'), }), new MiniCssExtractPlugin({ - filename: "./css/[name]-[hash].css", + filename: "./css/[name]-[fullhash].css", chunkFilename: "[id].css" }), new webpack.ProvidePlugin({ From 83ab17095646d28a0b6440b65032369edcd0c8bc Mon Sep 17 00:00:00 2001 From: Ntkskwk Date: Tue, 20 Aug 2024 04:46:35 +0800 Subject: [PATCH 07/13] refactor: update celery loglevel in worker.sh for new performance --- worker.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/worker.sh b/worker.sh index d471b6231..1b48f09f4 100755 --- a/worker.sh +++ b/worker.sh @@ -52,7 +52,7 @@ start(){ action=$1 echo "Starting worker using broker at $WO_BROKER" - celery -A worker worker --autoscale $(grep -c '^processor' /proc/cpuinfo),2 --max-tasks-per-child 1000 --loglevel=warn > /dev/null + celery -A worker worker --autoscale $(grep -c '^processor' /proc/cpuinfo),2 --max-tasks-per-child 1000 --loglevel=WARNING > /dev/null } start_scheduler(){ From d694ed84a25b6c98b4f823cbc96639af1717b0c4 Mon Sep 17 00:00:00 2001 From: Ntkskwk Date: Tue, 20 Aug 2024 04:47:30 +0800 Subject: [PATCH 08/13] refactor: ingore authentication.py for rest_framework_jwt.authentication --- app/api/authentication.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/app/api/authentication.py b/app/api/authentication.py index c95c14ad4..2dd11b098 100644 --- a/app/api/authentication.py +++ b/app/api/authentication.py @@ -1,6 +1,8 @@ -from rest_framework_jwt.authentication import BaseJSONWebTokenAuthentication - - -class JSONWebTokenAuthenticationQS(BaseJSONWebTokenAuthentication): - def get_jwt_value(self, request): - return request.query_params.get('jwt') \ No newline at end of file +# from rest_framework_jwt.authentication import BaseJSONWebTokenAuthentication +# +# jwt_decode_handler = api_settings.JWT_DECODE_HANDLER +# jwt_get_username_from_payload_handler = api_settings.JWT_PAYLOAD_GET_USERNAME_HANDLER +# +# class JSONWebTokenAuthenticationQS(BaseJSONWebTokenAuthentication): +# def get_jwt_value(self, request): +# return request.query_params.get('jwt') \ No newline at end of file From b103c57fb497fcc61d0b8dcacb5035bd4c08af83 Mon Sep 17 00:00:00 2001 From: Ntkskwk Date: Tue, 20 Aug 2024 04:51:39 +0800 Subject: [PATCH 09/13] feat: add GDAL version check and makemigrations in start.sh --- start.sh | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/start.sh b/start.sh index 4c8763d2d..c47eb8b1d 100755 --- a/start.sh +++ b/start.sh @@ -28,6 +28,7 @@ if [ $? -ne 0 ]; then fi # Check GDAL version +echo $(gdalinfo --version) python -c "import sys;import re;import subprocess;version = subprocess.Popen([\"gdalinfo\", \"--version\"], stdout=subprocess.PIPE).communicate()[0].decode().rstrip();ret = 0 if re.compile('^GDAL [2-9]\.[0-9]+').match(version) else 1; print('Checking GDAL version... ' + ('{}, excellent!'.format(version) if ret == 0 else version));sys.exit(ret);" if [ $? -ne 0 ]; then almost_there @@ -55,6 +56,9 @@ if [ "$1" = "--setup-devenv" ] || [ "$2" = "--setup-devenv" ]; then echo Build translations... python manage.py translate build --safe + echo Running makemigrations + python manage.py makemigrations app + echo Setup webpack watch... webpack --watch & fi From 52c30fe5d8bac9906ed5d4581754ef4d75c33a2c Mon Sep 17 00:00:00 2001 From: Ntkskwk Date: Tue, 20 Aug 2024 04:52:49 +0800 Subject: [PATCH 10/13] refactor: change Docker base image to Ubuntu 20.04 --- Dockerfile | 56 +++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 39 insertions(+), 17 deletions(-) diff --git a/Dockerfile b/Dockerfile index 2f63c0cda..a299c1908 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,33 +1,55 @@ -FROM ubuntu:21.04 +FROM ubuntu:20.04 MAINTAINER Piero Toffanin ARG TEST_BUILD ARG DEBIAN_FRONTEND=noninteractive -ENV PYTHONUNBUFFERED 1 -ENV PYTHONPATH $PYTHONPATH:/webodm -ENV PROJ_LIB=/usr/share/proj +ENV PYTHONUNBUFFERED=1 +ENV PYTHONPATH=$PYTHONPATH:/webodm +ENV NODE_MAJOR=20 +ENV PYTHON_MAJOR=3.9 +ENV GDAL_VERSION=3.8.5 +ENV LD_LIBRARY_PATH=/usr/local/lib -# Prepare directory +# # Prepare directory ADD . /webodm/ WORKDIR /webodm -# Use old-releases for 21.04 -RUN printf "deb http://old-releases.ubuntu.com/ubuntu/ hirsute main restricted\ndeb http://old-releases.ubuntu.com/ubuntu/ hirsute-updates main restricted\ndeb http://old-releases.ubuntu.com/ubuntu/ hirsute universe\ndeb http://old-releases.ubuntu.com/ubuntu/ hirsute-updates universe\ndeb http://old-releases.ubuntu.com/ubuntu/ hirsute multiverse\ndeb http://old-releases.ubuntu.com/ubuntu/ hirsute-updates multiverse\ndeb http://old-releases.ubuntu.com/ubuntu/ hirsute-backports main restricted universe multiverse" > /etc/apt/sources.list +RUN apt-get -o Acquire::Retries=3 -qq update > /dev/null && \ + apt-get -o Acquire::Retries=3 -qq install -y --no-install-recommends wget curl git g++ clang make cmake postgresql-client > /dev/null && \ -# Install Node.js using new Node install method -RUN apt-get -qq update && apt-get -o Acquire::Retries=3 -qq install -y --no-install-recommends wget curl && \ - apt-get -o Acquire::Retries=3 install -y ca-certificates gnupg && \ + # Install PDAL, letsencrypt, psql, cron + apt-get -o Acquire::Retries=3 -qq install -y --no-install-recommends binutils pdal certbot gettext tzdata libproj-dev libpq-dev > /dev/null && \ + + # Install Python in target version + apt-get -qq autoremove -y python3 && \ + apt-get -o Acquire::Retries=3 -qq install -y --no-install-recommends python$PYTHON_MAJOR-dev python$PYTHON_MAJOR-full && \ + ln -s /usr/bin/python$PYTHON_MAJOR /usr/bin/python && \ + curl https://bootstrap.pypa.io/get-pip.py | python && \ + + echo $(pip -V) && \ + echo $(python -V) && \ + + # Build GDAL from source + wget --no-check-certificate -q https://github.com/OSGeo/gdal/releases/download/v$GDAL_VERSION/gdal-$GDAL_VERSION.tar.gz && \ + tar -xzf gdal-$GDAL_VERSION.tar.gz && \ + cd gdal-$GDAL_VERSION && mkdir build && cd build && \ + cmake .. && cmake --build . -j$(nproc) --target install && \ + cd / && rm -rf gdal-$GDAL_VERSION gdal-$GDAL_VERSION.tar.gz && \ + + # Install pip reqs + cd /webodm && \ + pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple && \ + pip install --quiet -U pip && \ + pip install --quiet -r requirements.txt "boto3==1.34.145" && \ + + # Install Node.js using new Node install method + apt-get -o Acquire::Retries=3 -qq install -y ca-certificates gnupg && \ mkdir -p /etc/apt/keyrings && \ curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg && \ - NODE_MAJOR=20 && \ echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_$NODE_MAJOR.x nodistro main" | tee /etc/apt/sources.list.d/nodesource.list && \ apt-get -o Acquire::Retries=3 -qq update && apt-get -o Acquire::Retries=3 -qq install -y nodejs && \ - # Install Python3, GDAL, PDAL, nginx, letsencrypt, psql - apt-get -o Acquire::Retries=3 -qq update && apt-get -o Acquire::Retries=3 -qq install -y --no-install-recommends python3 python3-pip python3-setuptools python3-wheel git g++ python3-dev python2.7-dev libpq-dev binutils libproj-dev gdal-bin pdal libgdal-dev python3-gdal nginx certbot gettext-base cron postgresql-client-13 gettext tzdata && \ - update-alternatives --install /usr/bin/python python /usr/bin/python2.7 1 && update-alternatives --install /usr/bin/python python /usr/bin/python3.9 2 && \ - # Install pip reqs - pip install pip==24.0 && pip install -r requirements.txt "boto3==1.14.14" && \ # Setup cron + apt-get -o Acquire::Retries=3 -qq install -y --no-install-recommends nginx cron && \ ln -s /webodm/nginx/crontab /var/spool/cron/crontabs/root && chmod 0644 /webodm/nginx/crontab && service cron start && chmod +x /webodm/nginx/letsencrypt-autogen.sh && \ /webodm/nodeodm/setup.sh && /webodm/nodeodm/cleanup.sh && cd /webodm && \ npm install --quiet -g webpack@5.89.0 && npm install --quiet -g webpack-cli@5.1.4 && npm install --quiet && webpack --mode production && \ @@ -36,7 +58,7 @@ RUN apt-get -qq update && apt-get -o Acquire::Retries=3 -qq install -y --no-inst python manage.py rebuildplugins && \ python manage.py translate build --safe && \ # Cleanup - apt-get remove -y g++ python3-dev libpq-dev && apt-get autoremove -y && \ + apt-get remove -y g++ python2 && apt-get autoremove -y && \ apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* && \ rm /webodm/webodm/secret_key.py From 20a666a8c70d1fae122e19410156508330f5036e Mon Sep 17 00:00:00 2001 From: Ntkskwk Date: Tue, 20 Aug 2024 04:57:32 +0800 Subject: [PATCH 11/13] chore: update rio_tiler to 6.6.1 --- app/api/tiler.py | 34 ++++++++++++++++++---------------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/app/api/tiler.py b/app/api/tiler.py index a57007721..0e3a38039 100644 --- a/app/api/tiler.py +++ b/app/api/tiler.py @@ -11,12 +11,11 @@ from rio_tiler.errors import TileOutsideBounds from rio_tiler.utils import has_alpha_band, \ non_alpha_indexes, render, create_cutline -from rio_tiler.utils import _stats as raster_stats -from rio_tiler.models import ImageStatistics, ImageData -from rio_tiler.models import Metadata as RioMetadata +from rio_tiler.utils import get_array_statistics +from rio_tiler.models import BandStatistics from rio_tiler.profiles import img_profiles from rio_tiler.colormap import cmap as colormap, apply_cmap -from rio_tiler.io import COGReader +from rio_tiler.io import Reader from rio_tiler.errors import InvalidColorMapName, AlphaBandWarning import numpy as np from .custom_colormaps_helper import custom_colormaps @@ -97,7 +96,7 @@ def get(self, request, pk=None, project_pk=None, tile_type=""): if not os.path.isfile(raster_path): raise exceptions.NotFound() - with COGReader(raster_path) as src: + with Reader(raster_path) as src: minzoom, maxzoom = get_zoom_safe(src) return Response({ @@ -164,7 +163,7 @@ def get(self, request, pk=None, project_pk=None, tile_type=""): if not os.path.isfile(raster_path): raise exceptions.NotFound() try: - with COGReader(raster_path) as src: + with Reader(raster_path) as src: band_count = src.dataset.meta['count'] if boundaries_feature is not None: boundaries_cutline = create_cutline(src.dataset, boundaries_feature, CRS.from_string('EPSG:4326')) @@ -187,18 +186,20 @@ def get(self, request, pk=None, project_pk=None, tile_type=""): data = np.ma.array(data) data.mask = mask == 0 stats = { - str(b + 1): raster_stats(data[b], percentiles=(pmin, pmax), bins=255, range=hrange) + str(b + 1): get_array_statistics(data[b], percentiles=(pmin, pmax), bins=255, range=hrange) for b in range(data.shape[0]) } - stats = {b: ImageStatistics(**s) for b, s in stats.items()} - metadata = RioMetadata(statistics=stats, **src.info().dict()) + stats = {b: BandStatistics(**s[0]) for b, s in stats.items()} else: if (boundaries_cutline is not None) and (boundaries_bbox is not None): - metadata = src.metadata(pmin=pmin, pmax=pmax, hist_options=histogram_options, nodata=nodata - , bounds=boundaries_bbox, vrt_options={'cutline': boundaries_cutline}) + stats = src.statistics(percentiles=(pmin, pmax), hist_options=histogram_options, nodata=nodata, + vrt_options={'cutline': boundaries_cutline}) else: - metadata = src.metadata(pmin=pmin, pmax=pmax, hist_options=histogram_options, nodata=nodata) - info = json.loads(metadata.json()) + stats = src.statistics(percentiles=(pmin, pmax), hist_options=histogram_options, nodata=nodata) + info = src.info().model_dump() + info['statistics']= {} + for k,v in stats.items(): + info['statistics'][k] = v.model_dump() except IndexError as e: # Caught when trying to get an invalid raster metadata raise exceptions.ValidationError("Cannot retrieve raster metadata: %s" % str(e)) @@ -207,8 +208,9 @@ def get(self, request, pk=None, project_pk=None, tile_type=""): for b in info['statistics']: info['statistics'][b]['min'] = hrange[0] info['statistics'][b]['max'] = hrange[1] - info['statistics'][b]['percentiles'][0] = max(hrange[0], info['statistics'][b]['percentiles'][0]) - info['statistics'][b]['percentiles'][1] = min(hrange[1], info['statistics'][b]['percentiles'][1]) + info['statistics'][b]['percentiles'] = [None] * 2 + info['statistics'][b]['percentiles'][0] = max(hrange[0], info['statistics'][b]['percentile_' + str(int(pmax))]) + info['statistics'][b]['percentiles'][1] = min(hrange[1], info['statistics'][b]['percentile_' + str(int(pmin))]) cmap_labels = { "viridis": "Viridis", @@ -351,7 +353,7 @@ def get(self, request, pk=None, project_pk=None, tile_type="", z="", x="", y="", if not os.path.isfile(url): raise exceptions.NotFound() - with COGReader(url) as src: + with Reader(url) as src: if not src.tile_exists(z, x, y): raise exceptions.NotFound(_("Outside of bounds")) From d6f244a597e4ce755642a127d1d728bb6d8f82aa Mon Sep 17 00:00:00 2001 From: Ntkskwk Date: Tue, 20 Aug 2024 04:58:23 +0800 Subject: [PATCH 12/13] refactor: update Django to 4.2 LTS --- app/admin.py | 19 +- app/api/urls.py | 56 +-- app/migrations/0001_initial.py | 201 ++++++--- .../0002_task_auto_processing_node.py | 20 - app/migrations/0003_auto_20170615_1300.py | 20 - app/migrations/0004_auto_20170707_1014.py | 25 -- app/migrations/0005_auto_20170707_1014.py | 46 -- app/migrations/0006_task_available_assets.py | 71 --- app/migrations/0007_auto_20170712_1319.py | 26 -- app/migrations/0008_preset.py | 32 -- app/migrations/0009_auto_20170721_1332.py | 22 - app/migrations/0010_auto_20170725_1324.py | 22 - app/migrations/0011_auto_20171109_1237.py | 57 --- app/migrations/0012_public_task_uuids.py | 60 --- app/migrations/0013_public_task_uuids.py | 62 --- app/migrations/0014_public_task_uuids.py | 40 -- app/migrations/0016_public_task_uuids.py | 21 - app/migrations/0017_auto_20180219_1446.py | 25 -- app/migrations/0018_auto_20180311_1028.py | 19 - .../0019_remove_task_processing_lock.py | 17 - app/migrations/0020_plugindatum.py | 30 -- app/migrations/0021_auto_20180726_1746.py | 20 - app/migrations/0022_auto_20181205_1644.py | 27 -- app/migrations/0023_task_running_progress.py | 18 - app/migrations/0024_update_task_assets.py | 80 ---- app/migrations/0025_auto_20190220_1854.py | 69 --- app/migrations/0026_update_images_count.py | 25 -- app/migrations/0027_plugin.py | 20 - app/migrations/0028_task_partial.py | 18 - app/migrations/0029_auto_20190907_1348.py | 19 - app/migrations/0030_assure_cogeo.py | 28 -- app/migrations/0031_auto_20210610_1850.py | 410 ------------------ app/migrations/0032_task_epsg.py | 72 --- app/migrations/0033_auto_20230307_1532.py | 23 - app/migrations/0034_delete_imageupload.py | 16 - app/migrations/0035_task_orthophoto_bands.py | 44 -- app/migrations/0036_task_size.py | 50 --- app/migrations/0037_profile.py | 35 -- .../0038_remove_task_console_output.py | 42 -- app/migrations/0039_task_orthophoto_bands.py | 43 -- app/models/plugin_datum.py | 5 +- app/models/preset.py | 3 +- app/models/task.py | 6 +- app/plugins/views.py | 10 +- app/urls.py | 38 +- app/views/app.py | 2 +- app/views/dev.py | 2 +- app/views/public.py | 2 +- coreplugins/editshortlinks/api.py | 2 +- locale | 2 +- nodeodm/migrations/0001_initial.py | 54 +-- .../migrations/0002_processingnode_token.py | 18 - nodeodm/migrations/0003_auto_20180625_1230.py | 18 - .../0004_processingnode_max_images.py | 18 - nodeodm/migrations/0005_auto_20190115_1346.py | 23 - nodeodm/migrations/0006_auto_20190220_1842.py | 24 - nodeodm/migrations/0007_auto_20190520_1258.py | 27 -- .../0008_rename_default_odm_node.py | 21 - nodeodm/migrations/0009_auto_20210610_1850.py | 73 ---- nodeodm/models.py | 3 +- requirements.txt | 87 ++-- webodm/settings.py | 17 +- webodm/urls.py | 45 +- 63 files changed, 310 insertions(+), 2140 deletions(-) delete mode 100644 app/migrations/0002_task_auto_processing_node.py delete mode 100644 app/migrations/0003_auto_20170615_1300.py delete mode 100644 app/migrations/0004_auto_20170707_1014.py delete mode 100644 app/migrations/0005_auto_20170707_1014.py delete mode 100644 app/migrations/0006_task_available_assets.py delete mode 100644 app/migrations/0007_auto_20170712_1319.py delete mode 100644 app/migrations/0008_preset.py delete mode 100644 app/migrations/0009_auto_20170721_1332.py delete mode 100644 app/migrations/0010_auto_20170725_1324.py delete mode 100644 app/migrations/0011_auto_20171109_1237.py delete mode 100644 app/migrations/0012_public_task_uuids.py delete mode 100644 app/migrations/0013_public_task_uuids.py delete mode 100644 app/migrations/0014_public_task_uuids.py delete mode 100644 app/migrations/0016_public_task_uuids.py delete mode 100644 app/migrations/0017_auto_20180219_1446.py delete mode 100644 app/migrations/0018_auto_20180311_1028.py delete mode 100644 app/migrations/0019_remove_task_processing_lock.py delete mode 100644 app/migrations/0020_plugindatum.py delete mode 100644 app/migrations/0021_auto_20180726_1746.py delete mode 100644 app/migrations/0022_auto_20181205_1644.py delete mode 100644 app/migrations/0023_task_running_progress.py delete mode 100644 app/migrations/0024_update_task_assets.py delete mode 100644 app/migrations/0025_auto_20190220_1854.py delete mode 100644 app/migrations/0026_update_images_count.py delete mode 100644 app/migrations/0027_plugin.py delete mode 100644 app/migrations/0028_task_partial.py delete mode 100644 app/migrations/0029_auto_20190907_1348.py delete mode 100644 app/migrations/0030_assure_cogeo.py delete mode 100644 app/migrations/0031_auto_20210610_1850.py delete mode 100644 app/migrations/0032_task_epsg.py delete mode 100644 app/migrations/0033_auto_20230307_1532.py delete mode 100644 app/migrations/0034_delete_imageupload.py delete mode 100644 app/migrations/0035_task_orthophoto_bands.py delete mode 100644 app/migrations/0036_task_size.py delete mode 100644 app/migrations/0037_profile.py delete mode 100644 app/migrations/0038_remove_task_console_output.py delete mode 100644 app/migrations/0039_task_orthophoto_bands.py delete mode 100644 nodeodm/migrations/0002_processingnode_token.py delete mode 100644 nodeodm/migrations/0003_auto_20180625_1230.py delete mode 100644 nodeodm/migrations/0004_processingnode_max_images.py delete mode 100644 nodeodm/migrations/0005_auto_20190115_1346.py delete mode 100644 nodeodm/migrations/0006_auto_20190220_1842.py delete mode 100644 nodeodm/migrations/0007_auto_20190520_1258.py delete mode 100644 nodeodm/migrations/0008_rename_default_odm_node.py delete mode 100644 nodeodm/migrations/0009_auto_20210610_1850.py diff --git a/app/admin.py b/app/admin.py index 7bb98a5d4..4c7ac679f 100644 --- a/app/admin.py +++ b/app/admin.py @@ -3,11 +3,10 @@ import zipfile import shutil -from django.conf.urls import url from django.contrib import admin from django.contrib import messages from django.http import HttpResponseRedirect -from django.urls import reverse +from django.urls import reverse, path, re_path from django.utils.html import format_html from guardian.admin import GuardedModelAdmin from django.contrib.auth.admin import UserAdmin as BaseUserAdmin @@ -129,23 +128,23 @@ def author(self, obj): def get_urls(self): urls = super().get_urls() custom_urls = [ - url( - r'^(?P.+)/enable/$', + re_path( + '(?P.+)/enable/', self.admin_site.admin_view(self.plugin_enable), name='plugin-enable', ), - url( - r'^(?P.+)/disable/$', + re_path( + '(?P.+)/disable/', self.admin_site.admin_view(self.plugin_disable), name='plugin-disable', ), - url( - r'^(?P.+)/delete/$', + re_path( + '(?P.+)/delete/', self.admin_site.admin_view(self.plugin_delete), name='plugin-delete', ), - url( - r'^actions/upload/$', + re_path( + 'actions/upload/', self.admin_site.admin_view(self.plugin_upload), name='plugin-upload', ), diff --git a/app/api/urls.py b/app/api/urls.py index 7cf862d45..f4e6c11e1 100644 --- a/app/api/urls.py +++ b/app/api/urls.py @@ -1,4 +1,4 @@ -from django.conf.urls import url, include +from django.urls import include, re_path, path from app.api.presets import PresetViewSet from app.plugins.views import api_view_handler @@ -8,7 +8,8 @@ from .processingnodes import ProcessingNodeViewSet, ProcessingNodeOptionsView from .admin import AdminUserViewSet, AdminGroupViewSet, AdminProfileViewSet from rest_framework_nested import routers -from rest_framework_jwt.views import obtain_jwt_token +from rest_framework_simplejwt.views import TokenObtainPairView, TokenRefreshView + from .tiler import TileJson, Bounds, Metadata, Tiles, Export from .potree import Scene, CameraView from .workers import CheckTask, GetTaskResult @@ -27,44 +28,43 @@ admin_router = routers.DefaultRouter() admin_router.register(r'admin/users', AdminUserViewSet, basename='admin-users') admin_router.register(r'admin/groups', AdminGroupViewSet, basename='admin-groups') -admin_router.register(r'admin/profiles', AdminProfileViewSet, basename='admin-groups') +admin_router.register(r'admin/profiles', AdminProfileViewSet, basename='admin-profiles') urlpatterns = [ - url(r'processingnodes/options/$', ProcessingNodeOptionsView.as_view()), + re_path(r'processingnodes/options/$', ProcessingNodeOptionsView.as_view()), - url(r'^', include(router.urls)), - url(r'^', include(tasks_router.urls)), - url(r'^', include(admin_router.urls)), + re_path(r'^', include(router.urls)), + re_path(r'^', include(tasks_router.urls)), + re_path(r'^', include(admin_router.urls)), - url(r'projects/(?P[^/.]+)/tasks/(?P[^/.]+)/(?Porthophoto|dsm|dtm)/tiles\.json$', TileJson.as_view()), - url(r'projects/(?P[^/.]+)/tasks/(?P[^/.]+)/(?Porthophoto|dsm|dtm)/bounds$', Bounds.as_view()), - url(r'projects/(?P[^/.]+)/tasks/(?P[^/.]+)/(?Porthophoto|dsm|dtm)/metadata$', Metadata.as_view()), - url(r'projects/(?P[^/.]+)/tasks/(?P[^/.]+)/(?Porthophoto|dsm|dtm)/tiles/(?P[\d]+)/(?P[\d]+)/(?P[\d]+)\.?(?Ppng|jpg|webp)?$', Tiles.as_view()), - url(r'projects/(?P[^/.]+)/tasks/(?P[^/.]+)/(?Porthophoto|dsm|dtm)/tiles/(?P[\d]+)/(?P[\d]+)/(?P[\d]+)@(?P[\d]+)x\.?(?Ppng|jpg|webp)?$', Tiles.as_view()), - url(r'projects/(?P[^/.]+)/tasks/(?P[^/.]+)/(?Porthophoto|dsm|dtm|georeferenced_model)/export$', Export.as_view()), + re_path('projects/(?P[^/.]+)/tasks/(?P[^/.]+)/(?Porthophoto|dsm|dtm)/tiles\.json', TileJson.as_view()), + re_path('projects/(?P[^/.]+)/tasks/(?P[^/.]+)/(?Porthophoto|dsm|dtm)/bounds', Bounds.as_view()), + re_path('projects/(?P[^/.]+)/tasks/(?P[^/.]+)/(?Porthophoto|dsm|dtm)/metadata', Metadata.as_view()), + re_path('projects/(?P[^/.]+)/tasks/(?P[^/.]+)/(?Porthophoto|dsm|dtm)/tiles/(?P[\d]+)/(?P[\d]+)/(?P[\d]+)\.?(?Ppng|jpg|webp)?', Tiles.as_view()), + re_path('projects/(?P[^/.]+)/tasks/(?P[^/.]+)/(?Porthophoto|dsm|dtm)/tiles/(?P[\d]+)/(?P[\d]+)/(?P[\d]+)@(?P[\d]+)x\.?(?Ppng|jpg|webp)?', Tiles.as_view()), + re_path('projects/(?P[^/.]+)/tasks/(?P[^/.]+)/(?Porthophoto|dsm|dtm|georeferenced_model)/export', Export.as_view()), - url(r'projects/(?P[^/.]+)/tasks/(?P[^/.]+)/download/(?P.+)$', TaskDownloads.as_view()), - url(r'projects/(?P[^/.]+)/tasks/(?P[^/.]+)/assets/(?P.+)$', TaskAssets.as_view()), - url(r'projects/(?P[^/.]+)/tasks/import$', TaskAssetsImport.as_view()), - url(r'projects/(?P[^/.]+)/tasks/(?P[^/.]+)/backup$', TaskBackup.as_view()), - url(r'projects/(?P[^/.]+)/tasks/(?P[^/.]+)/images/thumbnail/(?P.+)$', Thumbnail.as_view()), - url(r'projects/(?P[^/.]+)/tasks/(?P[^/.]+)/images/download/(?P.+)$', ImageDownload.as_view()), + re_path(r'projects/(?P[^/.]+)/tasks/(?P[^/.]+)/download/(?P.+)$', TaskDownloads.as_view()), + re_path(r'projects/(?P[^/.]+)/tasks/(?P[^/.]+)/assets/(?P.+)$', TaskAssets.as_view()), + re_path(r'projects/(?P[^/.]+)/tasks/import$', TaskAssetsImport.as_view()), + re_path(r'projects/(?P[^/.]+)/tasks/(?P[^/.]+)/backup$', TaskBackup.as_view()), + re_path(r'projects/(?P[^/.]+)/tasks/(?P[^/.]+)/images/thumbnail/(?P.+)$', Thumbnail.as_view()), + re_path(r'projects/(?P[^/.]+)/tasks/(?P[^/.]+)/images/download/(?P.+)$', ImageDownload.as_view()), - url(r'projects/(?P[^/.]+)/tasks/(?P[^/.]+)/3d/scene$', Scene.as_view()), - url(r'projects/(?P[^/.]+)/tasks/(?P[^/.]+)/3d/cameraview$', CameraView.as_view()), + re_path(r'projects/(?P[^/.]+)/tasks/(?P[^/.]+)/3d/scene$', Scene.as_view()), + re_path(r'projects/(?P[^/.]+)/tasks/(?P[^/.]+)/3d/cameraview$', CameraView.as_view()), - url(r'workers/check/(?P.+)', CheckTask.as_view()), - url(r'workers/get/(?P.+)', GetTaskResult.as_view()), + re_path(r'workers/check/(?P.+)', CheckTask.as_view()), + re_path(r'workers/get/(?P.+)', GetTaskResult.as_view()), - url(r'^auth/', include('rest_framework.urls')), - url(r'^token-auth/', obtain_jwt_token), + path(r'auth', include('rest_framework.urls')), - url(r'^plugins/(?P[^/.]+)/(.*)$', api_view_handler), + re_path('plugins/(?P[^/.]+)/(.*)', api_view_handler), ] if settings.ENABLE_USERS_API: - urlpatterns.append(url(r'users', UsersList.as_view())) + urlpatterns.append(path(r'users', UsersList.as_view())) if settings.EXTERNAL_AUTH_ENDPOINT != '': - urlpatterns.append(url(r'^external-token-auth/', ExternalTokenAuth.as_view())) + urlpatterns.append(path(r'external-token-auth', ExternalTokenAuth.as_view())) diff --git a/app/migrations/0001_initial.py b/app/migrations/0001_initial.py index a96fd4040..258956269 100644 --- a/app/migrations/0001_initial.py +++ b/app/migrations/0001_initial.py @@ -1,13 +1,14 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.10 on 2016-12-12 15:35 -from __future__ import unicode_literals +# Generated by Django 4.2.14 on 2024-08-17 07:04 -import app.models +import app.models.task +import colorfield.fields from django.conf import settings -import django.contrib.postgres.fields.jsonb +import django.contrib.gis.db.models.fields +import django.contrib.postgres.fields from django.db import migrations, models import django.db.models.deletion import django.utils.timezone +import uuid class Migration(migrations.Migration): @@ -15,85 +16,185 @@ class Migration(migrations.Migration): initial = True dependencies = [ - ('nodeodm', '0001_initial'), - ('auth', '0008_alter_user_username_max_length'), migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('auth', '0012_alter_user_first_name_max_length'), + ('nodeodm', '__first__'), ] operations = [ migrations.CreateModel( - name='ImageUpload', + name='Plugin', fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('image', models.ImageField(help_text='File uploaded by a user', upload_to=app.models.image_directory_path)), + ('name', models.CharField(help_text='Plugin name', max_length=255, primary_key=True, serialize=False, verbose_name='Name')), + ('enabled', models.BooleanField(db_index=True, default=True, help_text='Whether this plugin is turned on.', verbose_name='Enabled')), ], + options={ + 'verbose_name': 'Plugin', + 'verbose_name_plural': 'Plugins', + }, ), migrations.CreateModel( name='Project', fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('name', models.CharField(help_text='A label used to describe the project', max_length=255)), - ('description', models.TextField(blank=True, help_text='More in-depth description of the project', null=True)), - ('created_at', models.DateTimeField(default=django.utils.timezone.now, help_text='Creation date')), - ('deleting', models.BooleanField(db_index=True, default=False, help_text='Whether this project has been marked for deletion. Projects that have running tasks need to wait for tasks to be properly cleaned up before they can be deleted.')), - ('owner', models.ForeignKey(help_text='The person who created the project', on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL)), + ('name', models.CharField(help_text='A label used to describe the project', max_length=255, verbose_name='Name')), + ('description', models.TextField(blank=True, default='', help_text='More in-depth description of the project', verbose_name='Description')), + ('created_at', models.DateTimeField(default=django.utils.timezone.now, help_text='Creation date', verbose_name='Created at')), + ('deleting', models.BooleanField(db_index=True, default=False, help_text='Whether this project has been marked for deletion. Projects that have running tasks need to wait for tasks to be properly cleaned up before they can be deleted.', verbose_name='Deleting')), + ('tags', models.TextField(blank=True, db_index=True, default='', help_text='Project tags', verbose_name='Tags')), + ('owner', models.ForeignKey(help_text='The person who created the project', on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL, verbose_name='Owner')), ], + options={ + 'verbose_name': 'Project', + 'verbose_name_plural': 'Projects', + }, ), migrations.CreateModel( - name='ProjectGroupObjectPermission', + name='Theme', fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('content_object', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='app.Project')), - ('group', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='auth.Group')), - ('permission', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='auth.Permission')), + ('name', models.CharField(help_text='Name of theme', max_length=255, verbose_name='Name')), + ('primary', colorfield.fields.ColorField(default='#2c3e50', help_text='Most text, icons, and borders.', image_field=None, max_length=25, samples=None, verbose_name='Primary')), + ('secondary', colorfield.fields.ColorField(default='#ffffff', help_text='The main background color, and text color of some buttons.', image_field=None, max_length=25, samples=None, verbose_name='Secondary')), + ('tertiary', colorfield.fields.ColorField(default='#3498db', help_text='Navigation links.', image_field=None, max_length=25, samples=None, verbose_name='Tertiary')), + ('button_primary', colorfield.fields.ColorField(default='#2c3e50', help_text='Primary button color.', image_field=None, max_length=25, samples=None, verbose_name='Button Primary')), + ('button_default', colorfield.fields.ColorField(default='#95a5a6', help_text='Default button color.', image_field=None, max_length=25, samples=None, verbose_name='Button Default')), + ('button_danger', colorfield.fields.ColorField(default='#e74c3c', help_text='Delete button color.', image_field=None, max_length=25, samples=None, verbose_name='Button Danger')), + ('header_background', colorfield.fields.ColorField(default='#3498db', help_text="Background color of the site's header.", image_field=None, max_length=25, samples=None, verbose_name='Header Background')), + ('header_primary', colorfield.fields.ColorField(default='#ffffff', help_text="Text and icons in the site's header.", image_field=None, max_length=25, samples=None, verbose_name='Header Primary')), + ('border', colorfield.fields.ColorField(default='#e7e7e7', help_text='The color of most borders.', image_field=None, max_length=25, samples=None, verbose_name='Border')), + ('highlight', colorfield.fields.ColorField(default='#f7f7f7', help_text='The background color of panels and some borders.', image_field=None, max_length=25, samples=None, verbose_name='Highlight')), + ('dialog_warning', colorfield.fields.ColorField(default='#f39c12', help_text='The border color of warning dialogs.', image_field=None, max_length=25, samples=None, verbose_name='Dialog Warning')), + ('failed', colorfield.fields.ColorField(default='#ffcbcb', help_text='The background color of failed notifications.', image_field=None, max_length=25, samples=None, verbose_name='Failed')), + ('success', colorfield.fields.ColorField(default='#cbffcd', help_text='The background color of success notifications.', image_field=None, max_length=25, samples=None, verbose_name='Success')), + ('css', models.TextField(blank=True, default='', verbose_name='CSS')), + ('html_before_header', models.TextField(blank=True, default='', verbose_name='HTML (before header)')), + ('html_after_header', models.TextField(blank=True, default='', verbose_name='HTML (after header)')), + ('html_after_body', models.TextField(blank=True, default='', verbose_name='HTML (after body)')), + ('html_footer', models.TextField(blank=True, default='', verbose_name='HTML (footer)')), ], options={ - 'abstract': False, + 'verbose_name': 'Theme', + 'verbose_name_plural': 'Theme', }, ), migrations.CreateModel( - name='ProjectUserObjectPermission', + name='Task', + fields=[ + ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False, unique=True, verbose_name='Id')), + ('uuid', models.CharField(blank=True, db_index=True, default='', help_text='Identifier of the task (as returned by NodeODM API)', max_length=255, verbose_name='UUID')), + ('name', models.CharField(blank=True, help_text='A label for the task', max_length=255, null=True, verbose_name='Name')), + ('processing_time', models.IntegerField(default=-1, help_text='Number of milliseconds that elapsed since the beginning of this task (-1 indicates that no information is available)', verbose_name='Processing Time')), + ('auto_processing_node', models.BooleanField(default=True, help_text='A flag indicating whether this task should be automatically assigned a processing node', verbose_name='Auto Processing Node')), + ('status', models.IntegerField(blank=True, choices=[(10, 'QUEUED'), (20, 'RUNNING'), (30, 'FAILED'), (40, 'COMPLETED'), (50, 'CANCELED')], db_index=True, help_text='Current status of the task', null=True, verbose_name='Status')), + ('last_error', models.TextField(blank=True, help_text='The last processing error received', null=True, verbose_name='Last Error')), + ('options', models.JSONField(blank=True, default=dict, help_text='Options that are being used to process this task', validators=[app.models.task.validate_task_options], verbose_name='Options')), + ('available_assets', django.contrib.postgres.fields.ArrayField(base_field=models.CharField(max_length=80), blank=True, default=list, help_text='List of available assets to download', size=None, verbose_name='Available Assets')), + ('orthophoto_extent', django.contrib.gis.db.models.fields.GeometryField(blank=True, help_text='Extent of the orthophoto', null=True, srid=4326, verbose_name='Orthophoto Extent')), + ('dsm_extent', django.contrib.gis.db.models.fields.GeometryField(blank=True, help_text='Extent of the DSM', null=True, srid=4326, verbose_name='DSM Extent')), + ('dtm_extent', django.contrib.gis.db.models.fields.GeometryField(blank=True, help_text='Extent of the DTM', null=True, srid=4326, verbose_name='DTM Extent')), + ('created_at', models.DateTimeField(default=django.utils.timezone.now, help_text='Creation date', verbose_name='Created at')), + ('pending_action', models.IntegerField(blank=True, choices=[(1, 'CANCEL'), (2, 'REMOVE'), (3, 'RESTART'), (4, 'RESIZE'), (5, 'IMPORT')], db_index=True, help_text='A requested action to be performed on the task. The selected action will be performed by the worker at the next iteration.', null=True, verbose_name='Pending Action')), + ('public', models.BooleanField(default=False, help_text='A flag indicating whether this task is available to the public', verbose_name='Public')), + ('resize_to', models.IntegerField(default=-1, help_text='When set to a value different than -1, indicates that the images for this task have been / will be resized to the size specified here before processing.', verbose_name='Resize To')), + ('upload_progress', models.FloatField(blank=True, default=0.0, help_text="Value between 0 and 1 indicating the upload progress of this task's files to the processing node", verbose_name='Upload Progress')), + ('resize_progress', models.FloatField(blank=True, default=0.0, help_text="Value between 0 and 1 indicating the resize progress of this task's images", verbose_name='Resize Progress')), + ('running_progress', models.FloatField(blank=True, default=0.0, help_text='Value between 0 and 1 indicating the running progress (estimated) of this task', verbose_name='Running Progress')), + ('import_url', models.TextField(blank=True, default='', help_text='URL this task is imported from (only for imported tasks)', verbose_name='Import URL')), + ('images_count', models.IntegerField(blank=True, default=0, help_text='Number of images associated with this task', verbose_name='Images Count')), + ('partial', models.BooleanField(default=False, help_text='A flag indicating whether this task is currently waiting for information or files to be uploaded before being considered for processing.', verbose_name='Partial')), + ('potree_scene', models.JSONField(blank=True, default=dict, help_text='Serialized potree scene information used to save/load measurements and camera view angle', verbose_name='Potree Scene')), + ('epsg', models.IntegerField(blank=True, default=None, help_text='EPSG code of the dataset (if georeferenced)', null=True, verbose_name='EPSG')), + ('tags', models.TextField(blank=True, db_index=True, default='', help_text='Task tags', verbose_name='Tags')), + ('orthophoto_bands', models.JSONField(blank=True, default=list, help_text='List of orthophoto bands', verbose_name='Orthophoto Bands')), + ('size', models.FloatField(blank=True, default=0.0, help_text='Size of the task on disk in megabytes', verbose_name='Size')), + ('processing_node', models.ForeignKey(blank=True, help_text='Processing node assigned to this task (or null if this task has not been associated yet)', null=True, on_delete=django.db.models.deletion.SET_NULL, to='nodeodm.processingnode', verbose_name='Processing Node')), + ('project', models.ForeignKey(help_text='Project that this task belongs to', on_delete=django.db.models.deletion.CASCADE, to='app.project', verbose_name='Project')), + ], + options={ + 'verbose_name': 'Task', + 'verbose_name_plural': 'Tasks', + }, + ), + migrations.CreateModel( + name='Setting', fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('content_object', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='app.Project')), - ('permission', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='auth.Permission')), - ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ('app_name', models.CharField(help_text='The name of your application', max_length=255, verbose_name='App name')), + ('app_logo', models.ImageField(help_text='A 512x512 logo of your application (.png or .jpeg)', upload_to='settings/', verbose_name='App logo')), + ('organization_name', models.CharField(blank=True, default='WebODM', help_text='The name of your organization', max_length=255, null=True, verbose_name='Organization name')), + ('organization_website', models.URLField(blank=True, default='https://github.com/OpenDroneMap/WebODM/', help_text='The website URL of your organization', max_length=255, null=True, verbose_name='Organization website')), + ('theme', models.ForeignKey(help_text='Active theme', on_delete=django.db.models.deletion.DO_NOTHING, to='app.theme', verbose_name='Theme')), ], options={ - 'abstract': False, + 'verbose_name': 'Settings', + 'verbose_name_plural': 'Settings', }, ), migrations.CreateModel( - name='Task', + name='Profile', fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('uuid', models.CharField(blank=True, db_index=True, default='', help_text="Identifier of the task (as returned by OpenDroneMap's REST API)", max_length=255)), - ('name', models.CharField(blank=True, help_text='A label for the task', max_length=255, null=True)), - ('processing_lock', models.BooleanField(default=False, help_text='A flag indicating whether this task is currently locked for processing. When this flag is turned on, the task is in the middle of a processing step.')), - ('processing_time', models.IntegerField(default=-1, help_text='Number of milliseconds that elapsed since the beginning of this task (-1 indicates that no information is available)')), - ('status', models.IntegerField(blank=True, choices=[(10, 'QUEUED'), (20, 'RUNNING'), (30, 'FAILED'), (40, 'COMPLETED'), (50, 'CANCELED')], db_index=True, help_text='Current status of the task', null=True)), - ('last_error', models.TextField(blank=True, help_text='The last processing error received', null=True)), - ('options', django.contrib.postgres.fields.jsonb.JSONField(blank=True, default={}, help_text='Options that are being used to process this task', validators=[app.models.validate_task_options])), - ('console_output', models.TextField(blank=True, default='', help_text="Console output of the OpenDroneMap's process")), - ('ground_control_points', models.FileField(blank=True, help_text='Optional Ground Control Points file to use for processing', null=True, upload_to=app.models.gcp_directory_path)), - ('orthophoto', django.contrib.gis.db.models.RasterField(blank=True, help_text='Orthophoto created by OpenDroneMap', null=True, srid=4326)), - ('created_at', models.DateTimeField(default=django.utils.timezone.now, help_text='Creation date')), - ('pending_action', models.IntegerField(blank=True, choices=[(1, 'CANCEL'), (2, 'REMOVE'), (3, 'RESTART')], db_index=True, help_text='A requested action to be performed on the task. The selected action will be performed by the scheduler at the next iteration.', null=True)), - ('processing_node', models.ForeignKey(blank=True, help_text='Processing node assigned to this task (or null if this task has not been associated yet)', null=True, on_delete=django.db.models.deletion.CASCADE, to='nodeodm.ProcessingNode')), - ('project', models.ForeignKey(help_text='Project that this task belongs to', on_delete=django.db.models.deletion.CASCADE, to='app.Project')), + ('quota', models.FloatField(blank=True, default=-1, help_text='Maximum disk quota in megabytes', verbose_name='Quota')), + ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), ], ), - migrations.AddField( - model_name='imageupload', - name='task', - field=models.ForeignKey(help_text='Task this image belongs to', on_delete=django.db.models.deletion.CASCADE, to='app.Task'), + migrations.CreateModel( + name='Preset', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(help_text='A label used to describe the preset', max_length=255, verbose_name='Name')), + ('options', models.JSONField(blank=True, default=list, help_text="Options that define this preset (same format as in a Task's options).", validators=[app.models.task.validate_task_options], verbose_name='Options')), + ('created_at', models.DateTimeField(default=django.utils.timezone.now, help_text='Creation date', verbose_name='Created at')), + ('system', models.BooleanField(db_index=True, default=False, help_text='Whether this preset is available to every user in the system or just to its owner.', verbose_name='System')), + ('owner', models.ForeignKey(blank=True, help_text='The person who owns this preset', null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='Owner')), + ], + options={ + 'verbose_name': 'Preset', + 'verbose_name_plural': 'Presets', + }, + ), + migrations.CreateModel( + name='PluginDatum', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('key', models.CharField(db_index=True, help_text='Setting key', max_length=255, verbose_name='Key')), + ('int_value', models.IntegerField(blank=True, default=None, null=True, verbose_name='Integer value')), + ('float_value', models.FloatField(blank=True, default=None, null=True, verbose_name='Float value')), + ('bool_value', models.BooleanField(blank=True, default=None, null=True, verbose_name='Bool value')), + ('string_value', models.TextField(blank=True, default=None, null=True, verbose_name='String value')), + ('json_value', models.JSONField(blank=True, default=None, null=True, verbose_name='JSON value')), + ('user', models.ForeignKey(blank=True, default=None, help_text='The user this setting belongs to. If NULL, the setting is global.', null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='User')), + ], + options={ + 'verbose_name': 'Plugin Datum', + 'verbose_name_plural': 'Plugin Datum', + }, ), - migrations.AlterUniqueTogether( - name='projectuserobjectpermission', - unique_together=set([('user', 'permission', 'content_object')]), + migrations.CreateModel( + name='ProjectUserObjectPermission', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('content_object', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='app.project')), + ('permission', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='auth.permission')), + ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ], + options={ + 'abstract': False, + 'unique_together': {('user', 'permission', 'content_object')}, + }, ), - migrations.AlterUniqueTogether( - name='projectgroupobjectpermission', - unique_together=set([('group', 'permission', 'content_object')]), + migrations.CreateModel( + name='ProjectGroupObjectPermission', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('content_object', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='app.project')), + ('group', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='auth.group')), + ('permission', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='auth.permission')), + ], + options={ + 'abstract': False, + 'unique_together': {('group', 'permission', 'content_object')}, + }, ), ] diff --git a/app/migrations/0002_task_auto_processing_node.py b/app/migrations/0002_task_auto_processing_node.py deleted file mode 100644 index eb8129ce0..000000000 --- a/app/migrations/0002_task_auto_processing_node.py +++ /dev/null @@ -1,20 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.10.5 on 2017-02-15 15:33 -from __future__ import unicode_literals - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('app', '0001_initial'), - ] - - operations = [ - migrations.AddField( - model_name='task', - name='auto_processing_node', - field=models.BooleanField(default=True, help_text='A flag indicating whether this task should be automatically assigned a processing node'), - ), - ] diff --git a/app/migrations/0003_auto_20170615_1300.py b/app/migrations/0003_auto_20170615_1300.py deleted file mode 100644 index 336528ba3..000000000 --- a/app/migrations/0003_auto_20170615_1300.py +++ /dev/null @@ -1,20 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.11.1 on 2017-06-15 17:00 -from __future__ import unicode_literals - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('app', '0002_task_auto_processing_node'), - ] - - operations = [ - migrations.AlterField( - model_name='project', - name='description', - field=models.TextField(blank=True, default='', help_text='More in-depth description of the project'), - ), - ] diff --git a/app/migrations/0004_auto_20170707_1014.py b/app/migrations/0004_auto_20170707_1014.py deleted file mode 100644 index 4cafe2a02..000000000 --- a/app/migrations/0004_auto_20170707_1014.py +++ /dev/null @@ -1,25 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.11.1 on 2017-07-07 14:14 -from __future__ import unicode_literals - -import django.contrib.gis.db.models.fields -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('app', '0003_auto_20170615_1300'), - ] - - operations = [ - migrations.RemoveField( - model_name='task', - name='orthophoto', - ), - migrations.AddField( - model_name='task', - name='orthophoto_extent', - field=django.contrib.gis.db.models.fields.GeometryField(blank=True, help_text='Extent of the orthophoto created by OpenDroneMap', null=True, srid=4326), - ), - ] diff --git a/app/migrations/0005_auto_20170707_1014.py b/app/migrations/0005_auto_20170707_1014.py deleted file mode 100644 index 9987fa7b6..000000000 --- a/app/migrations/0005_auto_20170707_1014.py +++ /dev/null @@ -1,46 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.11.1 on 2017-07-07 14:14 -from __future__ import unicode_literals - -from django.contrib.gis.gdal import GDALRaster, OGRGeometry -from django.contrib.gis.geos import GEOSGeometry -from django.db import migrations -import os - -from webodm import settings - - -def assets_path(project_id, task_id, *args): - return os.path.join(settings.MEDIA_ROOT, - "project", - str(project_id), - "task", - str(task_id), - "assets", - *args) - -def transfer_existing_orthophoto_extent_values(apps, schema_editor): - Task = apps.get_model('app', 'Task') - - for t in Task.objects.all(): - print("Checking {}".format(t)) - orthophoto_path = assets_path(t.project.id, t.id, "odm_orthophoto", "odm_orthophoto_4326.tif") - if os.path.exists(orthophoto_path): - print("Migrating {}".format(orthophoto_path)) - - raster = GDALRaster(orthophoto_path) - geom = OGRGeometry.from_bbox(raster.extent) - t.orthophoto_extent = GEOSGeometry(geom.wkt) - t.save() - - os.remove(orthophoto_path) - -class Migration(migrations.Migration): - - dependencies = [ - ('app', '0004_auto_20170707_1014'), - ] - - operations = [ - migrations.RunPython(transfer_existing_orthophoto_extent_values), - ] diff --git a/app/migrations/0006_task_available_assets.py b/app/migrations/0006_task_available_assets.py deleted file mode 100644 index fa95c40af..000000000 --- a/app/migrations/0006_task_available_assets.py +++ /dev/null @@ -1,71 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.11.1 on 2017-07-07 18:05 -from __future__ import unicode_literals - -import django.contrib.postgres.fields -import os -from django.db import migrations, models - -from webodm import settings - -ASSETS_MAP = { - 'all.zip': 'all.zip', - 'orthophoto.tif': os.path.join('odm_orthophoto', 'odm_orthophoto.tif'), - 'orthophoto.png': os.path.join('odm_orthophoto', 'odm_orthophoto.png'), - 'orthophoto.mbtiles': os.path.join('odm_orthophoto', 'odm_orthophoto.mbtiles'), - 'georeferenced_model.las': os.path.join('odm_georeferencing', 'odm_georeferenced_model.las'), - 'georeferenced_model.laz': os.path.join('odm_georeferencing', 'odm_georeferenced_model.laz'), - 'georeferenced_model.ply': os.path.join('odm_georeferencing', 'odm_georeferenced_model.ply'), - 'georeferenced_model.csv': os.path.join('odm_georeferencing', 'odm_georeferenced_model.csv'), - 'textured_model.zip': { - 'deferred_path': 'textured_model.zip', - 'deferred_compress_dir': 'odm_texturing' - } -} - -def assets_path(project_id, task_id, *args): - return os.path.join(settings.MEDIA_ROOT, - "project", - str(project_id), - "task", - str(task_id), - "assets", - *args) - -def is_asset_available_slow(t, asset): - if asset in ASSETS_MAP: - value = ASSETS_MAP[asset] - if isinstance(value, str): - return os.path.exists(assets_path(t.project.id, t.id, value)) - elif isinstance(value, dict): - if 'deferred_compress_dir' in value: - return os.path.exists(assets_path(t.project.id, t.id, value['deferred_compress_dir'])) - - return False - - -def detect_available_assets(apps, schema_editor): - Task = apps.get_model('app', 'Task') - - for t in Task.objects.all(): - print("Updating {}".format(t)) - - all_assets = list(ASSETS_MAP.keys()) - t.available_assets = [asset for asset in all_assets if is_asset_available_slow(t, asset)] - t.save() - - -class Migration(migrations.Migration): - - dependencies = [ - ('app', '0005_auto_20170707_1014'), - ] - - operations = [ - migrations.AddField( - model_name='task', - name='available_assets', - field=django.contrib.postgres.fields.ArrayField(base_field=models.CharField(max_length=80), blank=True, default=[], help_text='List of available assets to download', size=None), - ), - migrations.RunPython(detect_available_assets), - ] diff --git a/app/migrations/0007_auto_20170712_1319.py b/app/migrations/0007_auto_20170712_1319.py deleted file mode 100644 index 90f0512a1..000000000 --- a/app/migrations/0007_auto_20170712_1319.py +++ /dev/null @@ -1,26 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.11.1 on 2017-07-12 17:19 -from __future__ import unicode_literals - -import django.contrib.gis.db.models.fields -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('app', '0006_task_available_assets'), - ] - - operations = [ - migrations.AddField( - model_name='task', - name='dsm_extent', - field=django.contrib.gis.db.models.fields.GeometryField(blank=True, help_text='Extent of the DSM created by OpenDroneMap', null=True, srid=4326), - ), - migrations.AddField( - model_name='task', - name='dtm_extent', - field=django.contrib.gis.db.models.fields.GeometryField(blank=True, help_text='Extent of the DTM created by OpenDroneMap', null=True, srid=4326), - ), - ] diff --git a/app/migrations/0008_preset.py b/app/migrations/0008_preset.py deleted file mode 100644 index f57a8be16..000000000 --- a/app/migrations/0008_preset.py +++ /dev/null @@ -1,32 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.11.1 on 2017-07-20 14:19 -from __future__ import unicode_literals - -import app.models.task -from django.conf import settings -import django.contrib.postgres.fields.jsonb -from django.db import migrations, models -import django.db.models.deletion -import django.utils.timezone - - -class Migration(migrations.Migration): - - dependencies = [ - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ('app', '0007_auto_20170712_1319'), - ] - - operations = [ - migrations.CreateModel( - name='Preset', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('name', models.CharField(help_text='A label used to describe the preset', max_length=255)), - ('options', django.contrib.postgres.fields.jsonb.JSONField(blank=True, default={}, help_text="Options that define this preset (same format as in a Task's options).", validators=[app.models.task.validate_task_options])), - ('created_at', models.DateTimeField(default=django.utils.timezone.now, help_text='Creation date')), - ('system', models.BooleanField(db_index=True, default=False, help_text='Whether this preset is available to every user in the system or just to its owner.')), - ('owner', models.ForeignKey(help_text='The person who owns this preset', on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), - ], - ), - ] diff --git a/app/migrations/0009_auto_20170721_1332.py b/app/migrations/0009_auto_20170721_1332.py deleted file mode 100644 index 9076765bc..000000000 --- a/app/migrations/0009_auto_20170721_1332.py +++ /dev/null @@ -1,22 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.11.1 on 2017-07-21 17:32 -from __future__ import unicode_literals - -import app.models.task -import django.contrib.postgres.fields.jsonb -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('app', '0008_preset'), - ] - - operations = [ - migrations.AlterField( - model_name='preset', - name='options', - field=django.contrib.postgres.fields.jsonb.JSONField(blank=True, default=[], help_text="Options that define this preset (same format as in a Task's options).", validators=[app.models.task.validate_task_options]), - ), - ] diff --git a/app/migrations/0010_auto_20170725_1324.py b/app/migrations/0010_auto_20170725_1324.py deleted file mode 100644 index 188b3bf4d..000000000 --- a/app/migrations/0010_auto_20170725_1324.py +++ /dev/null @@ -1,22 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.11.1 on 2017-07-25 17:24 -from __future__ import unicode_literals - -from django.conf import settings -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('app', '0009_auto_20170721_1332'), - ] - - operations = [ - migrations.AlterField( - model_name='preset', - name='owner', - field=models.ForeignKey(blank=True, help_text='The person who owns this preset', null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL), - ), - ] diff --git a/app/migrations/0011_auto_20171109_1237.py b/app/migrations/0011_auto_20171109_1237.py deleted file mode 100644 index 861de9b7d..000000000 --- a/app/migrations/0011_auto_20171109_1237.py +++ /dev/null @@ -1,57 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.11.1 on 2017-11-09 17:37 -from __future__ import unicode_literals - -import colorfield.fields -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('app', '0010_auto_20170725_1324'), - ] - - operations = [ - migrations.CreateModel( - name='Setting', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('app_name', models.CharField(help_text='The name of your application', max_length=255)), - ('app_logo', models.ImageField(help_text='A 512x512 logo of your application (.png or .jpeg)', upload_to='settings/')), - ('organization_name', models.CharField(blank=True, default='WebODM', help_text='The name of your organization', max_length=255, null=True)), - ('organization_website', models.URLField(blank=True, default='https://github.com/OpenDroneMap/WebODM/', help_text='The website URL of your organization', max_length=255, null=True)), - ], - ), - migrations.CreateModel( - name='Theme', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('name', models.CharField(help_text='Name of theme', max_length=255)), - ('primary', colorfield.fields.ColorField(default='#2c3e50', help_text='Most text, icons, and borders.', max_length=18)), - ('secondary', colorfield.fields.ColorField(default='#ffffff', help_text='The main background color, and text color of some buttons.', max_length=18)), - ('tertiary', colorfield.fields.ColorField(default='#18bc9c', help_text='Navigation links.', max_length=18)), - ('button_primary', colorfield.fields.ColorField(default='#2c3e50', help_text='Primary button color.', max_length=18)), - ('button_default', colorfield.fields.ColorField(default='#95a5a6', help_text='Default button color.', max_length=18)), - ('button_danger', colorfield.fields.ColorField(default='#e74c3c', help_text='Delete button color.', max_length=18)), - ('header_background', colorfield.fields.ColorField(default='#18bc9c', help_text="Background color of the site's header.", max_length=18)), - ('header_primary', colorfield.fields.ColorField(default='#ffffff', help_text="Text and icons in the site's header.", max_length=18)), - ('border', colorfield.fields.ColorField(default='#e7e7e7', help_text='The color of most borders.', max_length=18)), - ('highlight', colorfield.fields.ColorField(default='#f7f7f7', help_text='The background color of panels and some borders.', max_length=18)), - ('dialog_warning', colorfield.fields.ColorField(default='#f39c12', help_text='The border color of warning dialogs.', max_length=18)), - ('failed', colorfield.fields.ColorField(default='#ffcbcb', help_text='The background color of failed notifications.', max_length=18)), - ('success', colorfield.fields.ColorField(default='#cbffcd', help_text='The background color of success notifications.', max_length=18)), - ('css', models.TextField(blank=True, default='')), - ('html_before_header', models.TextField(blank=True, default='')), - ('html_after_header', models.TextField(blank=True, default='')), - ('html_after_body', models.TextField(blank=True, default='')), - ('html_footer', models.TextField(blank=True, default='')), - ], - ), - migrations.AddField( - model_name='setting', - name='theme', - field=models.ForeignKey(help_text='Active theme', on_delete=django.db.models.deletion.DO_NOTHING, to='app.Theme'), - ), - ] diff --git a/app/migrations/0012_public_task_uuids.py b/app/migrations/0012_public_task_uuids.py deleted file mode 100644 index 739191784..000000000 --- a/app/migrations/0012_public_task_uuids.py +++ /dev/null @@ -1,60 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.11.1 on 2017-11-30 15:41 -from __future__ import unicode_literals - -from django.db import migrations, models -import uuid, os, pickle, tempfile - -from webodm import settings - -tasks = [] -task_ids = {} # map old task IDs --> new task IDs - -def dump(apps, schema_editor): - global tasks, task_ids - - Task = apps.get_model('app', 'Task') - - tasks = list(Task.objects.all().values('id', 'project')) - - # Generate UUIDs - for task in tasks: - new_id = uuid.uuid4() - - # Save reference to it - task['new_id'] = new_id - - # Populate map - task_ids[task['id']] = new_id - - tmp_path = os.path.join(tempfile.gettempdir(), "public_task_uuids_migration.pickle") - pickle.dump((tasks, task_ids), open(tmp_path, 'wb')) - - if len(tasks) > 0: print("Dumped tasks") - - -class Migration(migrations.Migration): - - dependencies = [ - ('app', '0011_auto_20171109_1237'), - ] - - operations = [ - migrations.AddField( - model_name='task', - name='public', - field=models.BooleanField(default=False, help_text='A flag indicating whether this task is available to the public'), - ), - - migrations.RunPython(dump), - - migrations.RemoveField( - model_name='imageupload', - name='task' - ), - migrations.AddField( - model_name='task', - name='new_id', - field=models.UUIDField(null=True) - ), - ] diff --git a/app/migrations/0013_public_task_uuids.py b/app/migrations/0013_public_task_uuids.py deleted file mode 100644 index 7486a82e0..000000000 --- a/app/migrations/0013_public_task_uuids.py +++ /dev/null @@ -1,62 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.11.1 on 2017-11-30 15:41 -from __future__ import unicode_literals - -from django.db import migrations, models -import uuid, os, pickle, tempfile - -from webodm import settings - -tasks = [] -task_ids = {} # map old task IDs --> new task IDs - -def task_path(project_id, task_id): - return os.path.join(settings.MEDIA_ROOT, - "project", - str(project_id), - "task", - str(task_id)) - -def rename_task_folders(apps, schema_editor): - global tasks, task_ids - - for t in tasks: - print("Checking task {}".format(t['id'])) - current_path = task_path(t['project'], t['id']) - if os.path.exists(current_path): - new_path = task_path(t['project'], task_ids[t['id']]) - print("Migrating {} --> {}".format(current_path, new_path)) - os.rename(current_path, new_path) - -def create_uuids(apps, schema_editor): - global tasks, task_ids - - Task = apps.get_model('app', 'Task') - for task in tasks: - print(task) - - t = Task.objects.get(id=task['id']) - t.new_id = task['new_id'] - t.save() - - if len(tasks) > 0: print("Created UUIDs") - - -def restore(apps, schema_editor): - global tasks, task_ids - - tmp_path = os.path.join(tempfile.gettempdir(), "public_task_uuids_migration.pickle") - tasks, task_ids = pickle.load(open(tmp_path, 'rb')) - - -class Migration(migrations.Migration): - - dependencies = [ - ('app', '0012_public_task_uuids'), - ] - - operations = [ - migrations.RunPython(restore), - migrations.RunPython(create_uuids), - migrations.RunPython(rename_task_folders), - ] diff --git a/app/migrations/0014_public_task_uuids.py b/app/migrations/0014_public_task_uuids.py deleted file mode 100644 index 9846829f2..000000000 --- a/app/migrations/0014_public_task_uuids.py +++ /dev/null @@ -1,40 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.11.1 on 2017-11-30 15:41 -from __future__ import unicode_literals - -from django.db import migrations, models -import uuid - -from webodm import settings - -class Migration(migrations.Migration): - - dependencies = [ - ('app', '0013_public_task_uuids'), - ] - - operations = [ - migrations.AlterField( - model_name='task', - name='new_id', - field=models.UUIDField(default=uuid.uuid4, unique=True, serialize=False, editable=False) - ), - migrations.RemoveField('task', 'id'), - migrations.RenameField( - model_name='task', - old_name='new_id', - new_name='id' - ), - migrations.AlterField( - model_name='task', - name='id', - field=models.UUIDField(primary_key=True, default=uuid.uuid4, unique=True, serialize=False, editable=False) - ), - - migrations.AddField( - model_name='imageupload', - name='task', - field=models.ForeignKey(null=True, on_delete=models.CASCADE, help_text="Task this image belongs to", to='app.Task') - ), - - ] diff --git a/app/migrations/0016_public_task_uuids.py b/app/migrations/0016_public_task_uuids.py deleted file mode 100644 index cc34e4e41..000000000 --- a/app/migrations/0016_public_task_uuids.py +++ /dev/null @@ -1,21 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.11.1 on 2017-11-30 15:41 -from __future__ import unicode_literals - -from django.db import migrations, models - -from webodm import settings - -class Migration(migrations.Migration): - - dependencies = [ - ('app', '0014_public_task_uuids'), - ] - - operations = [ - migrations.AlterField( - model_name='imageupload', - name='task', - field=models.ForeignKey(null=False, on_delete=models.CASCADE, help_text="Task this image belongs to", to='app.Task') - ), - ] diff --git a/app/migrations/0017_auto_20180219_1446.py b/app/migrations/0017_auto_20180219_1446.py deleted file mode 100644 index c97f8e6c2..000000000 --- a/app/migrations/0017_auto_20180219_1446.py +++ /dev/null @@ -1,25 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.11.7 on 2018-02-19 19:46 -from __future__ import unicode_literals - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('app', '0016_public_task_uuids'), - ] - - operations = [ - migrations.AddField( - model_name='task', - name='resize_to', - field=models.IntegerField(default=-1, help_text='When set to a value different than -1, indicates that the images for this task have been / will be resized to the size specified here before processing.'), - ), - migrations.AlterField( - model_name='task', - name='pending_action', - field=models.IntegerField(blank=True, choices=[(1, 'CANCEL'), (2, 'REMOVE'), (3, 'RESTART'), (4, 'RESIZE')], db_index=True, help_text='A requested action to be performed on the task. The selected action will be performed by the worker at the next iteration.', null=True), - ), - ] diff --git a/app/migrations/0018_auto_20180311_1028.py b/app/migrations/0018_auto_20180311_1028.py deleted file mode 100644 index 2a1e34f18..000000000 --- a/app/migrations/0018_auto_20180311_1028.py +++ /dev/null @@ -1,19 +0,0 @@ -# Generated by Django 2.0.3 on 2018-03-11 14:28 - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('app', '0017_auto_20180219_1446'), - ] - - operations = [ - migrations.AlterField( - model_name='task', - name='processing_node', - field=models.ForeignKey(blank=True, help_text='Processing node assigned to this task (or null if this task has not been associated yet)', null=True, on_delete=django.db.models.deletion.SET_NULL, to='nodeodm.ProcessingNode'), - ), - ] diff --git a/app/migrations/0019_remove_task_processing_lock.py b/app/migrations/0019_remove_task_processing_lock.py deleted file mode 100644 index 8b692e5db..000000000 --- a/app/migrations/0019_remove_task_processing_lock.py +++ /dev/null @@ -1,17 +0,0 @@ -# Generated by Django 2.0.3 on 2018-04-08 16:47 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('app', '0018_auto_20180311_1028'), - ] - - operations = [ - migrations.RemoveField( - model_name='task', - name='processing_lock', - ), - ] diff --git a/app/migrations/0020_plugindatum.py b/app/migrations/0020_plugindatum.py deleted file mode 100644 index 5ba16d63c..000000000 --- a/app/migrations/0020_plugindatum.py +++ /dev/null @@ -1,30 +0,0 @@ -# Generated by Django 2.0.3 on 2018-07-24 21:01 - -from django.conf import settings -import django.contrib.postgres.fields.jsonb -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ('app', '0019_remove_task_processing_lock'), - ] - - operations = [ - migrations.CreateModel( - name='PluginDatum', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('key', models.CharField(db_index=True, help_text='Setting key', max_length=255)), - ('int_value', models.IntegerField(blank=True, default=None, help_text='Integer value', null=True)), - ('float_value', models.FloatField(blank=True, default=None, help_text='Float value', null=True)), - ('bool_value', models.NullBooleanField(default=None, help_text='Bool value')), - ('string_value', models.TextField(blank=True, default=None, help_text='String value', null=True)), - ('json_value', django.contrib.postgres.fields.jsonb.JSONField(blank=True, default=None, help_text='JSON value', null=True)), - ('user', models.ForeignKey(help_text='The user this setting belongs to. If NULL, the setting is global.', on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), - ], - ), - ] diff --git a/app/migrations/0021_auto_20180726_1746.py b/app/migrations/0021_auto_20180726_1746.py deleted file mode 100644 index 764cf8c3a..000000000 --- a/app/migrations/0021_auto_20180726_1746.py +++ /dev/null @@ -1,20 +0,0 @@ -# Generated by Django 2.0.3 on 2018-07-26 21:46 - -from django.conf import settings -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('app', '0020_plugindatum'), - ] - - operations = [ - migrations.AlterField( - model_name='plugindatum', - name='user', - field=models.ForeignKey(default=None, help_text='The user this setting belongs to. If NULL, the setting is global.', null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL), - ), - ] diff --git a/app/migrations/0022_auto_20181205_1644.py b/app/migrations/0022_auto_20181205_1644.py deleted file mode 100644 index 37a68b8da..000000000 --- a/app/migrations/0022_auto_20181205_1644.py +++ /dev/null @@ -1,27 +0,0 @@ -# Generated by Django 2.0.3 on 2018-12-05 16:44 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('app', '0021_auto_20180726_1746'), - ] - - operations = [ - migrations.RemoveField( - model_name='task', - name='ground_control_points', - ), - migrations.AddField( - model_name='task', - name='resize_progress', - field=models.FloatField(blank=True, default=0.0, help_text="Value between 0 and 1 indicating the resize progress of this task's images"), - ), - migrations.AddField( - model_name='task', - name='upload_progress', - field=models.FloatField(blank=True, default=0.0, help_text="Value between 0 and 1 indicating the upload progress of this task's files to the processing node"), - ), - ] diff --git a/app/migrations/0023_task_running_progress.py b/app/migrations/0023_task_running_progress.py deleted file mode 100644 index e615b8a71..000000000 --- a/app/migrations/0023_task_running_progress.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 2.0.3 on 2018-12-07 18:53 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('app', '0022_auto_20181205_1644'), - ] - - operations = [ - migrations.AddField( - model_name='task', - name='running_progress', - field=models.FloatField(blank=True, default=0.0, help_text="Value between 0 and 1 indicating the running progress (estimated) of this task"), - ), - ] diff --git a/app/migrations/0024_update_task_assets.py b/app/migrations/0024_update_task_assets.py deleted file mode 100644 index e3cc40b4d..000000000 --- a/app/migrations/0024_update_task_assets.py +++ /dev/null @@ -1,80 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.11.1 on 2017-07-07 18:05 -from __future__ import unicode_literals - -import django.contrib.postgres.fields -import os -from django.db import migrations, models - -from webodm import settings - -ASSETS_MAP = { - 'all.zip': 'all.zip', - 'orthophoto.tif': os.path.join('odm_orthophoto', 'odm_orthophoto.tif'), - 'orthophoto.png': os.path.join('odm_orthophoto', 'odm_orthophoto.png'), - 'orthophoto.mbtiles': os.path.join('odm_orthophoto', 'odm_orthophoto.mbtiles'), - 'georeferenced_model.las': os.path.join('odm_georeferencing', 'odm_georeferenced_model.las'), - 'georeferenced_model.laz': os.path.join('odm_georeferencing', 'odm_georeferenced_model.laz'), - 'georeferenced_model.ply': os.path.join('odm_georeferencing', 'odm_georeferenced_model.ply'), - 'georeferenced_model.csv': os.path.join('odm_georeferencing', 'odm_georeferenced_model.csv'), - 'textured_model.zip': { - 'deferred_path': 'textured_model.zip', - 'deferred_compress_dir': 'odm_texturing' - }, - 'dtm.tif': os.path.join('odm_dem', 'dtm.tif'), - 'dsm.tif': os.path.join('odm_dem', 'dsm.tif'), - 'dtm_tiles.zip': { - 'deferred_path': 'dtm_tiles.zip', - 'deferred_compress_dir': 'dtm_tiles' - }, - 'dsm_tiles.zip': { - 'deferred_path': 'dsm_tiles.zip', - 'deferred_compress_dir': 'dsm_tiles' - }, - 'orthophoto_tiles.zip': { - 'deferred_path': 'orthophoto_tiles.zip', - 'deferred_compress_dir': 'orthophoto_tiles' - }, -} - -def assets_path(project_id, task_id, *args): - return os.path.join(settings.MEDIA_ROOT, - "project", - str(project_id), - "task", - str(task_id), - "assets", - *args) - -def is_asset_available_slow(t, asset): - if asset in ASSETS_MAP: - value = ASSETS_MAP[asset] - if isinstance(value, str): - return os.path.exists(assets_path(t.project.id, t.id, value)) - elif isinstance(value, dict): - if 'deferred_compress_dir' in value: - return os.path.exists(assets_path(t.project.id, t.id, value['deferred_compress_dir'])) - - return False - - -def detect_available_assets(apps, schema_editor): - Task = apps.get_model('app', 'Task') - - for t in Task.objects.all(): - print("Updating {}".format(t)) - - all_assets = list(ASSETS_MAP.keys()) - t.available_assets = [asset for asset in all_assets if is_asset_available_slow(t, asset)] - t.save() - - -class Migration(migrations.Migration): - - dependencies = [ - ('app', '0023_task_running_progress'), - ] - - operations = [ - migrations.RunPython(detect_available_assets), - ] diff --git a/app/migrations/0025_auto_20190220_1854.py b/app/migrations/0025_auto_20190220_1854.py deleted file mode 100644 index ac4d86195..000000000 --- a/app/migrations/0025_auto_20190220_1854.py +++ /dev/null @@ -1,69 +0,0 @@ -# Generated by Django 2.1.5 on 2019-02-20 18:54 - -import app.models.task -import colorfield.fields -import django.contrib.postgres.fields -import django.contrib.postgres.fields.jsonb -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('app', '0024_update_task_assets'), - ] - - operations = [ - migrations.AddField( - model_name='task', - name='import_url', - field=models.TextField(blank=True, default='', - help_text='URL this task is imported from (only for imported tasks)'), - ), - migrations.AlterField( - model_name='preset', - name='options', - field=django.contrib.postgres.fields.jsonb.JSONField(blank=True, default=list, - help_text="Options that define this preset (same format as in a Task's options).", - validators=[app.models.task.validate_task_options]), - ), - migrations.AlterField( - model_name='task', - name='available_assets', - field=django.contrib.postgres.fields.ArrayField(base_field=models.CharField(max_length=80), blank=True, - default=list, - help_text='List of available assets to download', - size=None), - ), - migrations.AlterField( - model_name='task', - name='options', - field=django.contrib.postgres.fields.jsonb.JSONField(blank=True, default=dict, - help_text='Options that are being used to process this task', - validators=[app.models.task.validate_task_options]), - ), - migrations.AlterField( - model_name='task', - name='pending_action', - field=models.IntegerField(blank=True, choices=[(1, 'CANCEL'), (2, 'REMOVE'), (3, 'RESTART'), (4, 'RESIZE'), - (5, 'IMPORT')], db_index=True, - help_text='A requested action to be performed on the task. The selected action will be performed by the worker at the next iteration.', - null=True), - ), - migrations.AlterField( - model_name='theme', - name='header_background', - field=colorfield.fields.ColorField(default='#3498db', help_text="Background color of the site's header.", - max_length=18), - ), - migrations.AlterField( - model_name='theme', - name='tertiary', - field=colorfield.fields.ColorField(default='#3498db', help_text='Navigation links.', max_length=18), - ), - migrations.AddField( - model_name='task', - name='images_count', - field=models.IntegerField(blank=True, default=0, help_text='Number of images associated with this task'), - ), - ] diff --git a/app/migrations/0026_update_images_count.py b/app/migrations/0026_update_images_count.py deleted file mode 100644 index 8cde8ebfa..000000000 --- a/app/migrations/0026_update_images_count.py +++ /dev/null @@ -1,25 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.11.1 on 2017-07-07 18:05 -from __future__ import unicode_literals - -from django.db import migrations - - -def update_images_count(apps, schema_editor): - Task = apps.get_model('app', 'Task') - - for t in Task.objects.all(): - print("Updating {}".format(t)) - t.images_count = len(t.scan_images()) - t.save() - - -class Migration(migrations.Migration): - - dependencies = [ - ('app', '0025_auto_20190220_1854'), - ] - - operations = [ - migrations.RunPython(update_images_count), - ] diff --git a/app/migrations/0027_plugin.py b/app/migrations/0027_plugin.py deleted file mode 100644 index 5349742fd..000000000 --- a/app/migrations/0027_plugin.py +++ /dev/null @@ -1,20 +0,0 @@ -# Generated by Django 2.1.7 on 2019-03-19 16:34 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('app', '0026_update_images_count'), - ] - - operations = [ - migrations.CreateModel( - name='Plugin', - fields=[ - ('name', models.CharField(help_text='Plugin name', max_length=255, primary_key=True, serialize=False)), - ('enabled', models.BooleanField(db_index=True, default=True, help_text='Whether this plugin is enabled.')), - ], - ), - ] diff --git a/app/migrations/0028_task_partial.py b/app/migrations/0028_task_partial.py deleted file mode 100644 index e00d5cb1f..000000000 --- a/app/migrations/0028_task_partial.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 2.1.7 on 2019-06-26 18:31 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('app', '0027_plugin'), - ] - - operations = [ - migrations.AddField( - model_name='task', - name='partial', - field=models.BooleanField(default=False, help_text='A flag indicating whether this task is currently waiting for information or files to be uploaded before being considered for processing.'), - ), - ] diff --git a/app/migrations/0029_auto_20190907_1348.py b/app/migrations/0029_auto_20190907_1348.py deleted file mode 100644 index 9977a4d2f..000000000 --- a/app/migrations/0029_auto_20190907_1348.py +++ /dev/null @@ -1,19 +0,0 @@ -# Generated by Django 2.1.11 on 2019-09-07 13:48 - -import app.models -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('app', '0028_task_partial'), - ] - - operations = [ - migrations.AlterField( - model_name='imageupload', - name='image', - field=models.ImageField(help_text='File uploaded by a user', max_length=512, upload_to=app.models.image_directory_path), - ), - ] diff --git a/app/migrations/0030_assure_cogeo.py b/app/migrations/0030_assure_cogeo.py deleted file mode 100644 index 091f55bff..000000000 --- a/app/migrations/0030_assure_cogeo.py +++ /dev/null @@ -1,28 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - -from app.cogeo import assure_cogeo -from django.db import migrations -import glob -import os -from webodm import settings - -def find_and_assure_cogeo(apps, schema_editor): - for asset_filename in ["odm_orthophoto.tif", "dsm.tif", "dtm.tif"]: - for asset in glob.glob(os.path.join(settings.MEDIA_ROOT, "project", "**", asset_filename), recursive=True): - try: - print("Optimizing %s" % asset) - assure_cogeo(asset) - except Exception as e: - print("WARNING: cannot check/optimize %s (%s), skipping..." % (asset, str(e))) - - -class Migration(migrations.Migration): - - dependencies = [ - ('app', '0029_auto_20190907_1348'), - ] - - operations = [ - migrations.RunPython(find_and_assure_cogeo), - ] diff --git a/app/migrations/0031_auto_20210610_1850.py b/app/migrations/0031_auto_20210610_1850.py deleted file mode 100644 index 9ae9c16c4..000000000 --- a/app/migrations/0031_auto_20210610_1850.py +++ /dev/null @@ -1,410 +0,0 @@ -# Generated by Django 2.1.15 on 2021-06-10 18:50 - -import app.models.task -from app.models import image_directory_path -import colorfield.fields -from django.conf import settings -import django.contrib.gis.db.models.fields -import django.contrib.postgres.fields -import django.contrib.postgres.fields.jsonb -from django.db import migrations, models -import django.db.models.deletion -import django.utils.timezone -import uuid - - -class Migration(migrations.Migration): - - dependencies = [ - ('app', '0030_assure_cogeo'), - ] - - operations = [ - migrations.AlterModelOptions( - name='imageupload', - options={'verbose_name': 'Image Upload', 'verbose_name_plural': 'Image Uploads'}, - ), - migrations.AlterModelOptions( - name='plugin', - options={'verbose_name': 'Plugin', 'verbose_name_plural': 'Plugins'}, - ), - migrations.AlterModelOptions( - name='plugindatum', - options={'verbose_name': 'Plugin Datum', 'verbose_name_plural': 'Plugin Datum'}, - ), - migrations.AlterModelOptions( - name='preset', - options={'verbose_name': 'Preset', 'verbose_name_plural': 'Presets'}, - ), - migrations.AlterModelOptions( - name='project', - options={'verbose_name': 'Project', 'verbose_name_plural': 'Projects'}, - ), - migrations.AlterModelOptions( - name='setting', - options={'verbose_name': 'Settings', 'verbose_name_plural': 'Settings'}, - ), - migrations.AlterModelOptions( - name='task', - options={'verbose_name': 'Task', 'verbose_name_plural': 'Tasks'}, - ), - migrations.AlterModelOptions( - name='theme', - options={'verbose_name': 'Theme', 'verbose_name_plural': 'Theme'}, - ), - migrations.AddField( - model_name='task', - name='potree_scene', - field=django.contrib.postgres.fields.jsonb.JSONField(blank=True, default=dict, help_text='Serialized potree scene information used to save/load measurements and camera view angle', verbose_name='Potree Scene'), - ), - migrations.AlterField( - model_name='imageupload', - name='image', - field=models.ImageField(help_text='File uploaded by a user', max_length=512, upload_to=image_directory_path, verbose_name='Image'), - ), - migrations.AlterField( - model_name='imageupload', - name='task', - field=models.ForeignKey(help_text='Task this image belongs to', on_delete=django.db.models.deletion.CASCADE, to='app.Task', verbose_name='Task'), - ), - migrations.AlterField( - model_name='plugin', - name='enabled', - field=models.BooleanField(db_index=True, default=True, help_text='Whether this plugin is turned on.', verbose_name='Enabled'), - ), - migrations.AlterField( - model_name='plugin', - name='name', - field=models.CharField(help_text='Plugin name', max_length=255, primary_key=True, serialize=False, verbose_name='Name'), - ), - migrations.AlterField( - model_name='plugindatum', - name='bool_value', - field=models.NullBooleanField(default=None, verbose_name='Bool value'), - ), - migrations.AlterField( - model_name='plugindatum', - name='float_value', - field=models.FloatField(blank=True, default=None, null=True, verbose_name='Float value'), - ), - migrations.AlterField( - model_name='plugindatum', - name='int_value', - field=models.IntegerField(blank=True, default=None, null=True, verbose_name='Integer value'), - ), - migrations.AlterField( - model_name='plugindatum', - name='json_value', - field=django.contrib.postgres.fields.jsonb.JSONField(blank=True, default=None, null=True, verbose_name='JSON value'), - ), - migrations.AlterField( - model_name='plugindatum', - name='key', - field=models.CharField(db_index=True, help_text='Setting key', max_length=255, verbose_name='Key'), - ), - migrations.AlterField( - model_name='plugindatum', - name='string_value', - field=models.TextField(blank=True, default=None, null=True, verbose_name='String value'), - ), - migrations.AlterField( - model_name='plugindatum', - name='user', - field=models.ForeignKey(default=None, help_text='The user this setting belongs to. If NULL, the setting is global.', null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='User'), - ), - migrations.AlterField( - model_name='preset', - name='created_at', - field=models.DateTimeField(default=django.utils.timezone.now, help_text='Creation date', verbose_name='Created at'), - ), - migrations.AlterField( - model_name='preset', - name='name', - field=models.CharField(help_text='A label used to describe the preset', max_length=255, verbose_name='Name'), - ), - migrations.AlterField( - model_name='preset', - name='options', - field=django.contrib.postgres.fields.jsonb.JSONField(blank=True, default=list, help_text="Options that define this preset (same format as in a Task's options).", validators=[app.models.task.validate_task_options], verbose_name='Options'), - ), - migrations.AlterField( - model_name='preset', - name='owner', - field=models.ForeignKey(blank=True, help_text='The person who owns this preset', null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='Owner'), - ), - migrations.AlterField( - model_name='preset', - name='system', - field=models.BooleanField(db_index=True, default=False, help_text='Whether this preset is available to every user in the system or just to its owner.', verbose_name='System'), - ), - migrations.AlterField( - model_name='project', - name='created_at', - field=models.DateTimeField(default=django.utils.timezone.now, help_text='Creation date', verbose_name='Created at'), - ), - migrations.AlterField( - model_name='project', - name='deleting', - field=models.BooleanField(db_index=True, default=False, help_text='Whether this project has been marked for deletion. Projects that have running tasks need to wait for tasks to be properly cleaned up before they can be deleted.', verbose_name='Deleting'), - ), - migrations.AlterField( - model_name='project', - name='description', - field=models.TextField(blank=True, default='', help_text='More in-depth description of the project', verbose_name='Description'), - ), - migrations.AlterField( - model_name='project', - name='name', - field=models.CharField(help_text='A label used to describe the project', max_length=255, verbose_name='Name'), - ), - migrations.AlterField( - model_name='project', - name='owner', - field=models.ForeignKey(help_text='The person who created the project', on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL, verbose_name='Owner'), - ), - migrations.AlterField( - model_name='setting', - name='app_logo', - field=models.ImageField(help_text='A 512x512 logo of your application (.png or .jpeg)', upload_to='settings/', verbose_name='App logo'), - ), - migrations.AlterField( - model_name='setting', - name='app_name', - field=models.CharField(help_text='The name of your application', max_length=255, verbose_name='App name'), - ), - migrations.AlterField( - model_name='setting', - name='organization_name', - field=models.CharField(blank=True, default='WebODM', help_text='The name of your organization', max_length=255, null=True, verbose_name='Organization name'), - ), - migrations.AlterField( - model_name='setting', - name='organization_website', - field=models.URLField(blank=True, default='https://github.com/OpenDroneMap/WebODM/', help_text='The website URL of your organization', max_length=255, null=True, verbose_name='Organization website'), - ), - migrations.AlterField( - model_name='setting', - name='theme', - field=models.ForeignKey(help_text='Active theme', on_delete=django.db.models.deletion.DO_NOTHING, to='app.Theme', verbose_name='Theme'), - ), - migrations.AlterField( - model_name='task', - name='auto_processing_node', - field=models.BooleanField(default=True, help_text='A flag indicating whether this task should be automatically assigned a processing node', verbose_name='Auto Processing Node'), - ), - migrations.AlterField( - model_name='task', - name='available_assets', - field=django.contrib.postgres.fields.ArrayField(base_field=models.CharField(max_length=80), blank=True, default=list, help_text='List of available assets to download', size=None, verbose_name='Available Assets'), - ), - migrations.AlterField( - model_name='task', - name='console_output', - field=models.TextField(blank=True, default='', help_text='Console output of the processing node', verbose_name='Console Output'), - ), - migrations.AlterField( - model_name='task', - name='created_at', - field=models.DateTimeField(default=django.utils.timezone.now, help_text='Creation date', verbose_name='Created at'), - ), - migrations.AlterField( - model_name='task', - name='dsm_extent', - field=django.contrib.gis.db.models.fields.GeometryField(blank=True, help_text='Extent of the DSM', null=True, srid=4326, verbose_name='DSM Extent'), - ), - migrations.AlterField( - model_name='task', - name='dtm_extent', - field=django.contrib.gis.db.models.fields.GeometryField(blank=True, help_text='Extent of the DTM', null=True, srid=4326, verbose_name='DTM Extent'), - ), - migrations.AlterField( - model_name='task', - name='id', - field=models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False, unique=True, verbose_name='Id'), - ), - migrations.AlterField( - model_name='task', - name='images_count', - field=models.IntegerField(blank=True, default=0, help_text='Number of images associated with this task', verbose_name='Images Count'), - ), - migrations.AlterField( - model_name='task', - name='import_url', - field=models.TextField(blank=True, default='', help_text='URL this task is imported from (only for imported tasks)', verbose_name='Import URL'), - ), - migrations.AlterField( - model_name='task', - name='last_error', - field=models.TextField(blank=True, help_text='The last processing error received', null=True, verbose_name='Last Error'), - ), - migrations.AlterField( - model_name='task', - name='name', - field=models.CharField(blank=True, help_text='A label for the task', max_length=255, null=True, verbose_name='Name'), - ), - migrations.AlterField( - model_name='task', - name='options', - field=django.contrib.postgres.fields.jsonb.JSONField(blank=True, default=dict, help_text='Options that are being used to process this task', validators=[app.models.task.validate_task_options], verbose_name='Options'), - ), - migrations.AlterField( - model_name='task', - name='orthophoto_extent', - field=django.contrib.gis.db.models.fields.GeometryField(blank=True, help_text='Extent of the orthophoto', null=True, srid=4326, verbose_name='Orthophoto Extent'), - ), - migrations.AlterField( - model_name='task', - name='partial', - field=models.BooleanField(default=False, help_text='A flag indicating whether this task is currently waiting for information or files to be uploaded before being considered for processing.', verbose_name='Partial'), - ), - migrations.AlterField( - model_name='task', - name='pending_action', - field=models.IntegerField(blank=True, choices=[(1, 'CANCEL'), (2, 'REMOVE'), (3, 'RESTART'), (4, 'RESIZE'), (5, 'IMPORT')], db_index=True, help_text='A requested action to be performed on the task. The selected action will be performed by the worker at the next iteration.', null=True, verbose_name='Pending Action'), - ), - migrations.AlterField( - model_name='task', - name='processing_node', - field=models.ForeignKey(blank=True, help_text='Processing node assigned to this task (or null if this task has not been associated yet)', null=True, on_delete=django.db.models.deletion.SET_NULL, to='nodeodm.ProcessingNode', verbose_name='Processing Node'), - ), - migrations.AlterField( - model_name='task', - name='processing_time', - field=models.IntegerField(default=-1, help_text='Number of milliseconds that elapsed since the beginning of this task (-1 indicates that no information is available)', verbose_name='Processing Time'), - ), - migrations.AlterField( - model_name='task', - name='project', - field=models.ForeignKey(help_text='Project that this task belongs to', on_delete=django.db.models.deletion.CASCADE, to='app.Project', verbose_name='Project'), - ), - migrations.AlterField( - model_name='task', - name='public', - field=models.BooleanField(default=False, help_text='A flag indicating whether this task is available to the public', verbose_name='Public'), - ), - migrations.AlterField( - model_name='task', - name='resize_progress', - field=models.FloatField(blank=True, default=0.0, help_text="Value between 0 and 1 indicating the resize progress of this task's images", verbose_name='Resize Progress'), - ), - migrations.AlterField( - model_name='task', - name='resize_to', - field=models.IntegerField(default=-1, help_text='When set to a value different than -1, indicates that the images for this task have been / will be resized to the size specified here before processing.', verbose_name='Resize To'), - ), - migrations.AlterField( - model_name='task', - name='running_progress', - field=models.FloatField(blank=True, default=0.0, help_text='Value between 0 and 1 indicating the running progress (estimated) of this task', verbose_name='Running Progress'), - ), - migrations.AlterField( - model_name='task', - name='status', - field=models.IntegerField(blank=True, choices=[(10, 'QUEUED'), (20, 'RUNNING'), (30, 'FAILED'), (40, 'COMPLETED'), (50, 'CANCELED')], db_index=True, help_text='Current status of the task', null=True, verbose_name='Status'), - ), - migrations.AlterField( - model_name='task', - name='upload_progress', - field=models.FloatField(blank=True, default=0.0, help_text="Value between 0 and 1 indicating the upload progress of this task's files to the processing node", verbose_name='Upload Progress'), - ), - migrations.AlterField( - model_name='task', - name='uuid', - field=models.CharField(blank=True, db_index=True, default='', help_text='Identifier of the task (as returned by NodeODM API)', max_length=255, verbose_name='UUID'), - ), - migrations.AlterField( - model_name='theme', - name='border', - field=colorfield.fields.ColorField(default='#e7e7e7', help_text='The color of most borders.', max_length=18, verbose_name='Border'), - ), - migrations.AlterField( - model_name='theme', - name='button_danger', - field=colorfield.fields.ColorField(default='#e74c3c', help_text='Delete button color.', max_length=18, verbose_name='Button Danger'), - ), - migrations.AlterField( - model_name='theme', - name='button_default', - field=colorfield.fields.ColorField(default='#95a5a6', help_text='Default button color.', max_length=18, verbose_name='Button Default'), - ), - migrations.AlterField( - model_name='theme', - name='button_primary', - field=colorfield.fields.ColorField(default='#2c3e50', help_text='Primary button color.', max_length=18, verbose_name='Button Primary'), - ), - migrations.AlterField( - model_name='theme', - name='css', - field=models.TextField(blank=True, default='', verbose_name='CSS'), - ), - migrations.AlterField( - model_name='theme', - name='dialog_warning', - field=colorfield.fields.ColorField(default='#f39c12', help_text='The border color of warning dialogs.', max_length=18, verbose_name='Dialog Warning'), - ), - migrations.AlterField( - model_name='theme', - name='failed', - field=colorfield.fields.ColorField(default='#ffcbcb', help_text='The background color of failed notifications.', max_length=18, verbose_name='Failed'), - ), - migrations.AlterField( - model_name='theme', - name='header_background', - field=colorfield.fields.ColorField(default='#3498db', help_text="Background color of the site's header.", max_length=18, verbose_name='Header Background'), - ), - migrations.AlterField( - model_name='theme', - name='header_primary', - field=colorfield.fields.ColorField(default='#ffffff', help_text="Text and icons in the site's header.", max_length=18, verbose_name='Header Primary'), - ), - migrations.AlterField( - model_name='theme', - name='highlight', - field=colorfield.fields.ColorField(default='#f7f7f7', help_text='The background color of panels and some borders.', max_length=18, verbose_name='Highlight'), - ), - migrations.AlterField( - model_name='theme', - name='html_after_body', - field=models.TextField(blank=True, default='', verbose_name='HTML (after body)'), - ), - migrations.AlterField( - model_name='theme', - name='html_after_header', - field=models.TextField(blank=True, default='', verbose_name='HTML (after header)'), - ), - migrations.AlterField( - model_name='theme', - name='html_before_header', - field=models.TextField(blank=True, default='', verbose_name='HTML (before header)'), - ), - migrations.AlterField( - model_name='theme', - name='html_footer', - field=models.TextField(blank=True, default='', verbose_name='HTML (footer)'), - ), - migrations.AlterField( - model_name='theme', - name='name', - field=models.CharField(help_text='Name of theme', max_length=255, verbose_name='Name'), - ), - migrations.AlterField( - model_name='theme', - name='primary', - field=colorfield.fields.ColorField(default='#2c3e50', help_text='Most text, icons, and borders.', max_length=18, verbose_name='Primary'), - ), - migrations.AlterField( - model_name='theme', - name='secondary', - field=colorfield.fields.ColorField(default='#ffffff', help_text='The main background color, and text color of some buttons.', max_length=18, verbose_name='Secondary'), - ), - migrations.AlterField( - model_name='theme', - name='success', - field=colorfield.fields.ColorField(default='#cbffcd', help_text='The background color of success notifications.', max_length=18, verbose_name='Success'), - ), - migrations.AlterField( - model_name='theme', - name='tertiary', - field=colorfield.fields.ColorField(default='#3498db', help_text='Navigation links.', max_length=18, verbose_name='Tertiary'), - ), - ] diff --git a/app/migrations/0032_task_epsg.py b/app/migrations/0032_task_epsg.py deleted file mode 100644 index d4fa7e32a..000000000 --- a/app/migrations/0032_task_epsg.py +++ /dev/null @@ -1,72 +0,0 @@ -# Generated by Django 2.1.15 on 2021-11-01 14:37 - -from django.db import migrations, models -import rasterio -import os -from app.pointcloud_utils import is_pointcloud_georeferenced -from webodm import settings - -def update_epsg_fields(apps, schema_editor): - Task = apps.get_model('app', 'Task') - - for t in Task.objects.all(): - - epsg = None - for asset in [os.path.join('odm_orthophoto', 'odm_orthophoto.tif'), - os.path.join('odm_dem', 'dsm.tif'), - os.path.join('odm_dem', 'dtm.tif')]: - asset_path = os.path.join(settings.MEDIA_ROOT, "project", str(t.project.id), "task", str(t.id), "assets", asset) - if os.path.isfile(asset_path): - try: - with rasterio.open(asset_path) as f: - if f.crs is not None: - epsg = f.crs.to_epsg() - break # We assume all assets are in the same CRS - except Exception as e: - print(e) - - # If point cloud is not georeferenced, dataset is not georeferenced - # (2D assets might be using pseudo-georeferencing) - point_cloud = os.path.join(settings.MEDIA_ROOT, "project", str(t.project.id), "task", str(t.id), "assets", "odm_georeferencing", "odm_georeferenced_model.laz") - - if epsg is not None and os.path.isfile(point_cloud): - if not is_pointcloud_georeferenced(point_cloud): - print("{} is not georeferenced".format(t)) - epsg = None - - print("Updating {} (with epsg: {})".format(t, epsg)) - - t.epsg = epsg - t.save() - -def remove_all_zip(apps, schema_editor): - Task = apps.get_model('app', 'Task') - - for t in Task.objects.all(): - asset = 'all.zip' - asset_path = os.path.join(settings.MEDIA_ROOT, "project", str(t.project.id), "task", str(t.id), "assets", asset) - if os.path.isfile(asset_path): - try: - os.remove(asset_path) - print("Cleaned up {}".format(asset_path)) - except Exception as e: - print(e) - - -class Migration(migrations.Migration): - - dependencies = [ - ('app', '0031_auto_20210610_1850'), - ] - - operations = [ - migrations.AddField( - model_name='task', - name='epsg', - field=models.IntegerField(blank=True, default=None, help_text='EPSG code of the dataset (if georeferenced)', null=True, verbose_name='EPSG'), - ), - - migrations.RunPython(update_epsg_fields), - migrations.RunPython(remove_all_zip), - - ] diff --git a/app/migrations/0033_auto_20230307_1532.py b/app/migrations/0033_auto_20230307_1532.py deleted file mode 100644 index df180c60c..000000000 --- a/app/migrations/0033_auto_20230307_1532.py +++ /dev/null @@ -1,23 +0,0 @@ -# Generated by Django 2.2.27 on 2023-03-07 15:32 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('app', '0032_task_epsg'), - ] - - operations = [ - migrations.AddField( - model_name='project', - name='tags', - field=models.TextField(blank=True, db_index=True, default='', help_text='Project tags', verbose_name='Tags'), - ), - migrations.AddField( - model_name='task', - name='tags', - field=models.TextField(blank=True, db_index=True, default='', help_text='Task tags', verbose_name='Tags'), - ), - ] diff --git a/app/migrations/0034_delete_imageupload.py b/app/migrations/0034_delete_imageupload.py deleted file mode 100644 index d2227feec..000000000 --- a/app/migrations/0034_delete_imageupload.py +++ /dev/null @@ -1,16 +0,0 @@ -# Generated by Django 2.2.27 on 2023-03-23 17:10 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('app', '0033_auto_20230307_1532'), - ] - - operations = [ - migrations.DeleteModel( - name='ImageUpload', - ), - ] diff --git a/app/migrations/0035_task_orthophoto_bands.py b/app/migrations/0035_task_orthophoto_bands.py deleted file mode 100644 index 2a99ee109..000000000 --- a/app/migrations/0035_task_orthophoto_bands.py +++ /dev/null @@ -1,44 +0,0 @@ -# Generated by Django 2.2.27 on 2023-05-19 15:38 - -import rasterio -import os -import django.contrib.postgres.fields.jsonb -from django.db import migrations -from webodm import settings - -def update_orthophoto_bands_fields(apps, schema_editor): - Task = apps.get_model('app', 'Task') - - for t in Task.objects.all(): - - bands = [] - orthophoto_path = os.path.join(settings.MEDIA_ROOT, "project", str(t.project.id), "task", str(t.id), "assets", "odm_orthophoto", "odm_orthophoto.tif") - - if os.path.isfile(orthophoto_path): - try: - with rasterio.open(orthophoto_path) as f: - bands = [c.name for c in f.colorinterp] - except Exception as e: - print(e) - - print("Updating {} (with orthophoto bands: {})".format(t, str(bands))) - - t.orthophoto_bands = bands - t.save() - - -class Migration(migrations.Migration): - - dependencies = [ - ('app', '0034_delete_imageupload'), - ] - - operations = [ - migrations.AddField( - model_name='task', - name='orthophoto_bands', - field=django.contrib.postgres.fields.jsonb.JSONField(blank=True, default=list, help_text='List of orthophoto bands', verbose_name='Orthophoto Bands'), - ), - - migrations.RunPython(update_orthophoto_bands_fields), - ] diff --git a/app/migrations/0036_task_size.py b/app/migrations/0036_task_size.py deleted file mode 100644 index 1fe421953..000000000 --- a/app/migrations/0036_task_size.py +++ /dev/null @@ -1,50 +0,0 @@ -# Generated by Django 2.2.27 on 2023-08-21 14:50 -import os -from django.db import migrations, models -from webodm import settings - -def task_path(project_id, task_id, *args): - return os.path.join(settings.MEDIA_ROOT, - "project", - str(project_id), - "task", - str(task_id), - *args) - -def update_size(task): - try: - total_bytes = 0 - for dirpath, _, filenames in os.walk(task_path(task.project.id, task.id)): - for f in filenames: - fp = os.path.join(dirpath, f) - if not os.path.islink(fp): - total_bytes += os.path.getsize(fp) - task.size = (total_bytes / 1024 / 1024) - task.save() - print("Updated {} with size {}".format(task, task.size)) - except Exception as e: - print("Cannot update size for task {}: {}".format(task, str(e))) - - - -def update_task_sizes(apps, schema_editor): - Task = apps.get_model('app', 'Task') - - for t in Task.objects.all(): - update_size(t) - -class Migration(migrations.Migration): - - dependencies = [ - ('app', '0035_task_orthophoto_bands'), - ] - - operations = [ - migrations.AddField( - model_name='task', - name='size', - field=models.FloatField(blank=True, default=0.0, help_text='Size of the task on disk in megabytes', verbose_name='Size'), - ), - - migrations.RunPython(update_task_sizes), - ] diff --git a/app/migrations/0037_profile.py b/app/migrations/0037_profile.py deleted file mode 100644 index ab7a1fa08..000000000 --- a/app/migrations/0037_profile.py +++ /dev/null @@ -1,35 +0,0 @@ -# Generated by Django 2.2.27 on 2023-08-24 16:35 - -from django.conf import settings -from django.db import migrations, models -import django.db.models.deletion - - -def create_profiles(apps, schema_editor): - User = apps.get_model('auth', 'User') - Profile = apps.get_model('app', 'Profile') - - for u in User.objects.all(): - p = Profile.objects.create(user=u) - p.save() - print("Created user profile for %s" % u.username) - -class Migration(migrations.Migration): - - dependencies = [ - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ('app', '0036_task_size'), - ] - - operations = [ - migrations.CreateModel( - name='Profile', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('quota', models.FloatField(blank=True, default=-1, help_text='Maximum disk quota in megabytes', verbose_name='Quota')), - ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), - ], - ), - - migrations.RunPython(create_profiles), - ] diff --git a/app/migrations/0038_remove_task_console_output.py b/app/migrations/0038_remove_task_console_output.py deleted file mode 100644 index 38a54625c..000000000 --- a/app/migrations/0038_remove_task_console_output.py +++ /dev/null @@ -1,42 +0,0 @@ -# Generated by Django 2.2.27 on 2023-09-11 19:11 -import os -from django.db import migrations -from webodm import settings - -def data_path(project_id, task_id, *args): - return os.path.join(settings.MEDIA_ROOT, - "project", - str(project_id), - "task", - str(task_id), - "data", - *args) - -def dump_console_outputs(apps, schema_editor): - Task = apps.get_model('app', 'Task') - - for t in Task.objects.all(): - if t.console_output is not None and len(t.console_output) > 0: - dp = data_path(t.project.id, t.id) - os.makedirs(dp, exist_ok=True) - outfile = os.path.join(dp, "console_output.txt") - - with open(outfile, "w", encoding="utf-8") as f: - f.write(t.console_output) - print("Wrote console output for %s to %s" % (t, outfile)) - else: - print("No task output for %s" % t) - -class Migration(migrations.Migration): - - dependencies = [ - ('app', '0037_profile'), - ] - - operations = [ - migrations.RunPython(dump_console_outputs), - migrations.RemoveField( - model_name='task', - name='console_output', - ), - ] diff --git a/app/migrations/0039_task_orthophoto_bands.py b/app/migrations/0039_task_orthophoto_bands.py deleted file mode 100644 index c801ab853..000000000 --- a/app/migrations/0039_task_orthophoto_bands.py +++ /dev/null @@ -1,43 +0,0 @@ -# Generated by Django 2.2.27 on 2023-10-02 10:21 - -import rasterio -import os -import django.contrib.postgres.fields.jsonb -from django.db import migrations -from webodm import settings - -def update_orthophoto_bands_fields(apps, schema_editor): - Task = apps.get_model('app', 'Task') - - for t in Task.objects.all(): - - bands = [] - orthophoto_path = os.path.join(settings.MEDIA_ROOT, "project", str(t.project.id), "task", str(t.id), "assets", "odm_orthophoto", "odm_orthophoto.tif") - - if os.path.isfile(orthophoto_path): - try: - with rasterio.open(orthophoto_path) as f: - names = [c.name for c in f.colorinterp] - for i, n in enumerate(names): - bands.append({ - 'name': n, - 'description': f.descriptions[i] - }) - except Exception as e: - print(e) - - print("Updating {} (with orthophoto bands: {})".format(t, str(bands))) - - t.orthophoto_bands = bands - t.save() - - -class Migration(migrations.Migration): - - dependencies = [ - ('app', '0038_remove_task_console_output'), - ] - - operations = [ - migrations.RunPython(update_orthophoto_bands_fields), - ] diff --git a/app/models/plugin_datum.py b/app/models/plugin_datum.py index 2fb67ab09..ce842e79f 100644 --- a/app/models/plugin_datum.py +++ b/app/models/plugin_datum.py @@ -1,5 +1,4 @@ from django.db import models -from django.contrib.postgres import fields from django.conf import settings from django.utils.translation import gettext_lazy as _ @@ -8,9 +7,9 @@ class PluginDatum(models.Model): user = models.ForeignKey(settings.AUTH_USER_MODEL, null=True, blank=True, default=None, on_delete=models.CASCADE, help_text=_("The user this setting belongs to. If NULL, the setting is global."), verbose_name=_("User")) int_value = models.IntegerField(blank=True, null=True, default=None, verbose_name=_("Integer value")) float_value = models.FloatField(blank=True, null=True, default=None, verbose_name=_("Float value")) - bool_value = models.NullBooleanField(blank=True, null=True, default=None, verbose_name=_("Bool value")) + bool_value = models.BooleanField(blank=True, null=True, default=None, verbose_name=_("Bool value")) string_value = models.TextField(blank=True, null=True, default=None, verbose_name=_("String value")) - json_value = fields.JSONField(default=None, blank=True, null=True, verbose_name=_("JSON value")) + json_value = models.JSONField(default=None, blank=True, null=True, verbose_name=_("JSON value")) def __str__(self): return self.key diff --git a/app/models/preset.py b/app/models/preset.py index 5e6612b13..f3488a898 100644 --- a/app/models/preset.py +++ b/app/models/preset.py @@ -1,5 +1,4 @@ from django.conf import settings -from django.contrib.postgres.fields import JSONField from django.db import models from django.utils import timezone from .task import validate_task_options @@ -8,7 +7,7 @@ class Preset(models.Model): owner = models.ForeignKey(settings.AUTH_USER_MODEL, blank=True, null=True, on_delete=models.CASCADE, help_text=_("The person who owns this preset"), verbose_name=_("Owner")) name = models.CharField(max_length=255, blank=False, null=False, help_text=_("A label used to describe the preset"), verbose_name=_("Name")) - options = JSONField(default=list, blank=True, help_text=_("Options that define this preset (same format as in a Task's options)."), verbose_name=_("Options"), + options = models.JSONField(default=list, blank=True, help_text=_("Options that define this preset (same format as in a Task's options)."), verbose_name=_("Options"), validators=[validate_task_options]) created_at = models.DateTimeField(default=timezone.now, help_text=_("Creation date"), verbose_name=_("Created at")) system = models.BooleanField(db_index=True, default=False, help_text=_("Whether this preset is available to every user in the system or just to its owner."), verbose_name=_("System")) diff --git a/app/models/task.py b/app/models/task.py index 44c449eaf..0d7237770 100644 --- a/app/models/task.py +++ b/app/models/task.py @@ -248,7 +248,7 @@ class Task(models.Model): auto_processing_node = models.BooleanField(default=True, help_text=_("A flag indicating whether this task should be automatically assigned a processing node"), verbose_name=_("Auto Processing Node")) status = models.IntegerField(choices=STATUS_CODES, db_index=True, null=True, blank=True, help_text=_("Current status of the task"), verbose_name=_("Status")) last_error = models.TextField(null=True, blank=True, help_text=_("The last processing error received"), verbose_name=_("Last Error")) - options = fields.JSONField(default=dict, blank=True, help_text=_("Options that are being used to process this task"), validators=[validate_task_options], verbose_name=_("Options")) + options = models.JSONField(default=dict, blank=True, help_text=_("Options that are being used to process this task"), validators=[validate_task_options], verbose_name=_("Options")) available_assets = fields.ArrayField(models.CharField(max_length=80), default=list, blank=True, help_text=_("List of available assets to download"), verbose_name=_("Available Assets")) orthophoto_extent = GeometryField(null=True, blank=True, srid=4326, help_text=_("Extent of the orthophoto"), verbose_name=_("Orthophoto Extent")) @@ -277,10 +277,10 @@ class Task(models.Model): import_url = models.TextField(null=False, default="", blank=True, help_text=_("URL this task is imported from (only for imported tasks)"), verbose_name=_("Import URL")) images_count = models.IntegerField(null=False, blank=True, default=0, help_text=_("Number of images associated with this task"), verbose_name=_("Images Count")) partial = models.BooleanField(default=False, help_text=_("A flag indicating whether this task is currently waiting for information or files to be uploaded before being considered for processing."), verbose_name=_("Partial")) - potree_scene = fields.JSONField(default=dict, blank=True, help_text=_("Serialized potree scene information used to save/load measurements and camera view angle"), verbose_name=_("Potree Scene")) + potree_scene = models.JSONField(default=dict, blank=True, help_text=_("Serialized potree scene information used to save/load measurements and camera view angle"), verbose_name=_("Potree Scene")) epsg = models.IntegerField(null=True, default=None, blank=True, help_text=_("EPSG code of the dataset (if georeferenced)"), verbose_name="EPSG") tags = models.TextField(db_index=True, default="", blank=True, help_text=_("Task tags"), verbose_name=_("Tags")) - orthophoto_bands = fields.JSONField(default=list, blank=True, help_text=_("List of orthophoto bands"), verbose_name=_("Orthophoto Bands")) + orthophoto_bands = models.JSONField(default=list, blank=True, help_text=_("List of orthophoto bands"), verbose_name=_("Orthophoto Bands")) size = models.FloatField(default=0.0, blank=True, help_text=_("Size of the task on disk in megabytes"), verbose_name=_("Size")) class Meta: diff --git a/app/plugins/views.py b/app/plugins/views.py index 8944a0a9b..e44b25bc4 100644 --- a/app/plugins/views.py +++ b/app/plugins/views.py @@ -6,7 +6,7 @@ from django.http import HttpResponse, Http404 from .functions import get_plugin_by_name, get_active_plugins -from django.conf.urls import url +from django.urls import re_path from django.views.static import serve from urllib.parse import urlparse @@ -26,7 +26,7 @@ def app_view_handler(request, plugin_name=None): # Try mountpoints first for mount_point in plugin.app_mount_points(): - view, args, kwargs = try_resolve_url(request, url(r'^/plugins/{}/{}'.format(plugin_name, mount_point.url), + view, args, kwargs = try_resolve_url(request, re_path(r'^/plugins/{}/{}'.format(plugin_name, mount_point.url), mount_point.view, *mount_point.args, **mount_point.kwargs)) @@ -35,7 +35,7 @@ def app_view_handler(request, plugin_name=None): # Try public assets if os.path.exists(plugin.get_path("public")) and plugin.serve_public_assets(request): - view, args, kwargs = try_resolve_url(request, url('^/plugins/{}/(.*)'.format(plugin_name), + view, args, kwargs = try_resolve_url(request, re_path('^/plugins/{}/(.*)'.format(plugin_name), serve, {'document_root': plugin.get_path("public")})) if view: @@ -50,7 +50,7 @@ def api_view_handler(request, plugin_name=None): raise Http404("Plugin not found") for mount_point in plugin.api_mount_points(): - view, args, kwargs = try_resolve_url(request, url(r'^/api/plugins/{}/{}'.format(plugin_name, mount_point.url), + view, args, kwargs = try_resolve_url(request, re_path(r'^/api/plugins/{}/{}'.format(plugin_name, mount_point.url), mount_point.view, *mount_point.args, **mount_point.kwargs)) @@ -64,6 +64,6 @@ def root_url_patterns(): result = [] for p in get_active_plugins(): for mount_point in p.root_mount_points(): - result.append(url(mount_point.url, mount_point.view, *mount_point.args, **mount_point.kwargs)) + result.append(path(mount_point.url, mount_point.view, *mount_point.args, **mount_point.kwargs)) return result \ No newline at end of file diff --git a/app/urls.py b/app/urls.py index 97eb9e552..f651ae014 100644 --- a/app/urls.py +++ b/app/urls.py @@ -1,4 +1,4 @@ -from django.conf.urls import url, include +from django.urls import path, re_path, include from django.views.i18n import JavaScriptCatalog from .views import app as app_views, public as public_views, dev as dev_views @@ -20,31 +20,31 @@ sync_plugin_db() urlpatterns = [ - url(r'^$', app_views.index, name='index'), - url(r'^welcome/$', app_views.welcome, name='welcome'), - url(r'^dashboard/$', app_views.dashboard, name='dashboard'), - url(r'^map/project/(?P[^/.]+)/task/(?P[^/.]+)/$', app_views.map, name='map'), - url(r'^map/project/(?P[^/.]+)/$', app_views.map, name='map'), - url(r'^3d/project/(?P[^/.]+)/task/(?P[^/.]+)/$', app_views.model_display, name='model_display'), + path('', app_views.index, name='index'), + path('welcome', app_views.welcome, name='welcome'), + path('dashboard', app_views.dashboard, name='dashboard'), + re_path('map/project/(?P[^/.]+)/task/(?P[^/.]+)/', app_views.map, name='map'), + re_path('map/project/(?P[^/.]+)/', app_views.map, name='map'), + re_path('3d/project/(?P[^/.]+)/task/(?P[^/.]+)/', app_views.model_display, name='model_display'), - url(r'^public/task/(?P[^/.]+)/map/$', public_views.map, name='public_map'), - url(r'^public/task/(?P[^/.]+)/iframe/map/$', public_views.map_iframe, name='public_iframe_map'), - url(r'^public/task/(?P[^/.]+)/3d/$', public_views.model_display, name='public_3d'), - url(r'^public/task/(?P[^/.]+)/iframe/3d/$', public_views.model_display_iframe, name='public_iframe_3d'), - url(r'^public/task/(?P[^/.]+)/json/$', public_views.task_json, name='public_json'), + re_path('public/task/(?P[^/.]+)/map/', public_views.map, name='public_map'), + re_path('public/task/(?P[^/.]+)/iframe/map/', public_views.map_iframe, name='public_iframe_map'), + re_path('public/task/(?P[^/.]+)/3d/', public_views.model_display, name='public_3d'), + re_path('public/task/(?P[^/.]+)/iframe/3d/', public_views.model_display_iframe, name='public_iframe_3d'), + re_path('public/task/(?P[^/.]+)/json/', public_views.task_json, name='public_json'), - url(r'^processingnode/([\d]+)/$', app_views.processing_node, name='processing_node'), + re_path('processingnode/([\d]+)/', app_views.processing_node, name='processing_node'), - url(r'^api/', include("app.api.urls")), + path('api', include("app.api.urls")), - url(r'^plugins/(?P[^/.]+)/(.*)$', app_view_handler), + re_path('plugins/(?P[^/.]+)/(.*)', app_view_handler), - url(r'^about/$', app_views.about, name='about'), - url(r'^dev-tools/(?P.*)$', dev_views.dev_tools, name='dev_tools'), + path('about', app_views.about, name='about'), + re_path('dev-tools/(?P.*)', dev_views.dev_tools, name='dev_tools'), # TODO: add caching: https://docs.djangoproject.com/en/3.1/topics/i18n/translation/#note-on-performance - url(r'^jsi18n/', JavaScriptCatalog.as_view(packages=['app']), name='javascript-catalog'), - url(r'^i18n/', include('django.conf.urls.i18n')), + path('jsi18n', JavaScriptCatalog.as_view(packages=['app']), name='javascript-catalog'), + path('i18n', include('django.conf.urls.i18n')), ] + root_url_patterns() handler404 = app_views.handler404 diff --git a/app/views/app.py b/app/views/app.py index 4256234a0..6ad16abeb 100644 --- a/app/views/app.py +++ b/app/views/app.py @@ -11,7 +11,7 @@ from app.models import Project, Task from django.contrib import messages from django.contrib.auth.decorators import login_required -from django.utils.translation import ugettext as _ +from django.utils.translation import gettext_lazy as _ from django import forms from app.views.utils import get_permissions from webodm import settings diff --git a/app/views/dev.py b/app/views/dev.py index 90e8f1d43..b474cd4ad 100644 --- a/app/views/dev.py +++ b/app/views/dev.py @@ -3,7 +3,7 @@ from django.shortcuts import render from django.contrib import messages -from django.utils.translation import ugettext as _ +from django.utils.translation import gettext_lazy as _ from django import forms from webodm import settings from django.http import JsonResponse diff --git a/app/views/public.py b/app/views/public.py index 13bc5905b..fcfaf2205 100644 --- a/app/views/public.py +++ b/app/views/public.py @@ -2,7 +2,7 @@ from django.http import Http404 from django.http import JsonResponse from django.shortcuts import get_object_or_404 -from django.utils.translation import ugettext as _ +from django.utils.translation import gettext_lazy as _ from django.shortcuts import render from app.api.tasks import TaskSerializer diff --git a/coreplugins/editshortlinks/api.py b/coreplugins/editshortlinks/api.py index 120b43bf4..82d6607a8 100644 --- a/coreplugins/editshortlinks/api.py +++ b/coreplugins/editshortlinks/api.py @@ -9,7 +9,7 @@ from app.plugins import GlobalDataStore from django.http import Http404 from django.shortcuts import redirect -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ import logging diff --git a/locale b/locale index 751d81ab6..599f937f7 160000 --- a/locale +++ b/locale @@ -1 +1 @@ -Subproject commit 751d81ab61a7bf4a335d29b1d129e68be1403d82 +Subproject commit 599f937f7e98064f6c201d01259ec14293f885a5 diff --git a/nodeodm/migrations/0001_initial.py b/nodeodm/migrations/0001_initial.py index 42521dfda..f67dfd603 100644 --- a/nodeodm/migrations/0001_initial.py +++ b/nodeodm/migrations/0001_initial.py @@ -1,9 +1,6 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.10 on 2016-12-06 16:32 -from __future__ import unicode_literals +# Generated by Django 4.2.14 on 2024-08-17 07:04 from django.conf import settings -import django.contrib.postgres.fields.jsonb from django.db import migrations, models import django.db.models.deletion @@ -13,8 +10,8 @@ class Migration(migrations.Migration): initial = True dependencies = [ - ('auth', '0008_alter_user_username_max_length'), migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('auth', '0012_alter_user_first_name_max_length'), ] operations = [ @@ -22,44 +19,47 @@ class Migration(migrations.Migration): name='ProcessingNode', fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('hostname', models.CharField(help_text='Hostname or IP address where the node is located (can be an internal hostname as well). If you are using Docker, this is never 127.0.0.1 or localhost. Find the IP address of your host machine by running ifconfig on Linux or by checking your network settings.', max_length=255)), - ('port', models.PositiveIntegerField(help_text="Port that connects to the node's API")), - ('api_version', models.CharField(help_text='API version used by the node', max_length=32, null=True)), - ('last_refreshed', models.DateTimeField(help_text='When was the information about this node last retrieved?', null=True)), - ('queue_count', models.PositiveIntegerField(default=0, help_text='Number of tasks currently being processed by this node (as reported by the node itself)')), - ('available_options', django.contrib.postgres.fields.jsonb.JSONField(default={}, help_text='Description of the options that can be used for processing')), + ('hostname', models.CharField(help_text='Hostname or IP address where the node is located (can be an internal hostname as well). If you are using Docker, this is never 127.0.0.1 or localhost. Find the IP address of your host machine by running ifconfig on Linux or by checking your network settings.', max_length=255, verbose_name='Hostname')), + ('port', models.PositiveIntegerField(help_text="Port that connects to the node's API", verbose_name='Port')), + ('api_version', models.CharField(help_text='API version used by the node', max_length=32, null=True, verbose_name='API Version')), + ('last_refreshed', models.DateTimeField(help_text='When was the information about this node last retrieved?', null=True, verbose_name='Last Refreshed')), + ('queue_count', models.PositiveIntegerField(default=0, help_text='Number of tasks currently being processed by this node (as reported by the node itself)', verbose_name='Queue Count')), + ('available_options', models.JSONField(default=dict, help_text='Description of the options that can be used for processing', verbose_name='Available Options')), + ('token', models.CharField(blank=True, default='', help_text="Token to use for authentication. If the node doesn't have authentication, you can leave this field blank.", max_length=1024, verbose_name='Token')), + ('max_images', models.PositiveIntegerField(blank=True, help_text='Maximum number of images accepted by this node.', null=True, verbose_name='Max Images')), + ('engine_version', models.CharField(help_text='Engine version used by the node.', max_length=32, null=True, verbose_name='Engine Version')), + ('label', models.CharField(blank=True, default='', help_text='Optional label for this node. When set, this label will be shown instead of the hostname:port name.', max_length=255, verbose_name='Label')), + ('engine', models.CharField(help_text='Engine used by the node.', max_length=255, null=True, verbose_name='Engine')), ], + options={ + 'verbose_name': 'Processing Node', + 'verbose_name_plural': 'Processing Nodes', + }, ), migrations.CreateModel( - name='ProcessingNodeGroupObjectPermission', + name='ProcessingNodeUserObjectPermission', fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('content_object', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='nodeodm.ProcessingNode')), - ('group', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='auth.Group')), - ('permission', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='auth.Permission')), + ('content_object', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='nodeodm.processingnode')), + ('permission', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='auth.permission')), + ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), ], options={ 'abstract': False, + 'unique_together': {('user', 'permission', 'content_object')}, }, ), migrations.CreateModel( - name='ProcessingNodeUserObjectPermission', + name='ProcessingNodeGroupObjectPermission', fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('content_object', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='nodeodm.ProcessingNode')), - ('permission', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='auth.Permission')), - ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ('content_object', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='nodeodm.processingnode')), + ('group', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='auth.group')), + ('permission', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='auth.permission')), ], options={ 'abstract': False, + 'unique_together': {('group', 'permission', 'content_object')}, }, ), - migrations.AlterUniqueTogether( - name='processingnodeuserobjectpermission', - unique_together=set([('user', 'permission', 'content_object')]), - ), - migrations.AlterUniqueTogether( - name='processingnodegroupobjectpermission', - unique_together=set([('group', 'permission', 'content_object')]), - ), ] diff --git a/nodeodm/migrations/0002_processingnode_token.py b/nodeodm/migrations/0002_processingnode_token.py deleted file mode 100644 index 37098eb75..000000000 --- a/nodeodm/migrations/0002_processingnode_token.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 2.0.3 on 2018-06-25 16:27 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('nodeodm', '0001_initial'), - ] - - operations = [ - migrations.AddField( - model_name='processingnode', - name='token', - field=models.CharField(default='', help_text="Token to use for authentication. If the node doesn't have authentication, you can leave this field blank.", max_length=1024, null=True), - ), - ] diff --git a/nodeodm/migrations/0003_auto_20180625_1230.py b/nodeodm/migrations/0003_auto_20180625_1230.py deleted file mode 100644 index 09ab181cf..000000000 --- a/nodeodm/migrations/0003_auto_20180625_1230.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 2.0.3 on 2018-06-25 16:30 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('nodeodm', '0002_processingnode_token'), - ] - - operations = [ - migrations.AlterField( - model_name='processingnode', - name='token', - field=models.CharField(blank=True, default='', help_text="Token to use for authentication. If the node doesn't have authentication, you can leave this field blank.", max_length=1024), - ), - ] diff --git a/nodeodm/migrations/0004_processingnode_max_images.py b/nodeodm/migrations/0004_processingnode_max_images.py deleted file mode 100644 index e67aef6d9..000000000 --- a/nodeodm/migrations/0004_processingnode_max_images.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 2.0.3 on 2018-12-04 17:04 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('nodeodm', '0003_auto_20180625_1230'), - ] - - operations = [ - migrations.AddField( - model_name='processingnode', - name='max_images', - field=models.PositiveIntegerField(blank=True, help_text='Maximum number of images accepted by this node.', null=True), - ), - ] diff --git a/nodeodm/migrations/0005_auto_20190115_1346.py b/nodeodm/migrations/0005_auto_20190115_1346.py deleted file mode 100644 index 2bb4d60ae..000000000 --- a/nodeodm/migrations/0005_auto_20190115_1346.py +++ /dev/null @@ -1,23 +0,0 @@ -# Generated by Django 2.0.3 on 2019-01-15 13:46 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('nodeodm', '0004_processingnode_max_images'), - ] - - operations = [ - migrations.AddField( - model_name='processingnode', - name='label', - field=models.CharField(blank=True, default='', help_text='Optional label for this node. When set, this label will be shown instead of hostname:port.', max_length=255), - ), - migrations.AddField( - model_name='processingnode', - name='odm_version', - field=models.CharField(help_text='ODM version used by the node.', max_length=32, null=True), - ), - ] diff --git a/nodeodm/migrations/0006_auto_20190220_1842.py b/nodeodm/migrations/0006_auto_20190220_1842.py deleted file mode 100644 index 4d9cda0f9..000000000 --- a/nodeodm/migrations/0006_auto_20190220_1842.py +++ /dev/null @@ -1,24 +0,0 @@ -# Generated by Django 2.1.5 on 2019-02-20 18:42 - -import django.contrib.postgres.fields.jsonb -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('nodeodm', '0005_auto_20190115_1346'), - ] - - operations = [ - migrations.AlterField( - model_name='processingnode', - name='available_options', - field=django.contrib.postgres.fields.jsonb.JSONField(default=dict, help_text='Description of the options that can be used for processing'), - ), - migrations.AlterField( - model_name='processingnode', - name='label', - field=models.CharField(blank=True, default='', help_text='Optional label for this node. When set, this label will be shown instead of the hostname:port name.', max_length=255), - ), - ] diff --git a/nodeodm/migrations/0007_auto_20190520_1258.py b/nodeodm/migrations/0007_auto_20190520_1258.py deleted file mode 100644 index 1b36b748a..000000000 --- a/nodeodm/migrations/0007_auto_20190520_1258.py +++ /dev/null @@ -1,27 +0,0 @@ -# Generated by Django 2.1.7 on 2019-05-20 12:58 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('nodeodm', '0006_auto_20190220_1842'), - ] - - operations = [ - migrations.RemoveField( - model_name='processingnode', - name='odm_version', - ), - migrations.AddField( - model_name='processingnode', - name='engine', - field=models.CharField(help_text='Engine used by the node.', max_length=255, null=True), - ), - migrations.AddField( - model_name='processingnode', - name='engine_version', - field=models.CharField(help_text='Engine version used by the node.', max_length=32, null=True), - ), - ] diff --git a/nodeodm/migrations/0008_rename_default_odm_node.py b/nodeodm/migrations/0008_rename_default_odm_node.py deleted file mode 100644 index 258c9ae74..000000000 --- a/nodeodm/migrations/0008_rename_default_odm_node.py +++ /dev/null @@ -1,21 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - -from django.db import migrations -from nodeodm.models import ProcessingNode - -def rename_default_node(apps, schema_editor): - for default_node in ProcessingNode.objects.filter(hostname='node-odm-1'): - default_node.hostname = 'webodm_node-odm_1' - default_node.label = 'node-odm-1' - default_node.save() - -class Migration(migrations.Migration): - - dependencies = [ - ('nodeodm', '0007_auto_20190520_1258'), - ] - - operations = [ - migrations.RunPython(rename_default_node), - ] diff --git a/nodeodm/migrations/0009_auto_20210610_1850.py b/nodeodm/migrations/0009_auto_20210610_1850.py deleted file mode 100644 index dc92348ca..000000000 --- a/nodeodm/migrations/0009_auto_20210610_1850.py +++ /dev/null @@ -1,73 +0,0 @@ -# Generated by Django 2.1.15 on 2021-06-10 18:50 - -import django.contrib.postgres.fields.jsonb -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('nodeodm', '0008_rename_default_odm_node'), - ] - - operations = [ - migrations.AlterModelOptions( - name='processingnode', - options={'verbose_name': 'Processing Node', 'verbose_name_plural': 'Processing Nodes'}, - ), - migrations.AlterField( - model_name='processingnode', - name='api_version', - field=models.CharField(help_text='API version used by the node', max_length=32, null=True, verbose_name='API Version'), - ), - migrations.AlterField( - model_name='processingnode', - name='available_options', - field=django.contrib.postgres.fields.jsonb.JSONField(default=dict, help_text='Description of the options that can be used for processing', verbose_name='Available Options'), - ), - migrations.AlterField( - model_name='processingnode', - name='engine', - field=models.CharField(help_text='Engine used by the node.', max_length=255, null=True, verbose_name='Engine'), - ), - migrations.AlterField( - model_name='processingnode', - name='engine_version', - field=models.CharField(help_text='Engine version used by the node.', max_length=32, null=True, verbose_name='Engine Version'), - ), - migrations.AlterField( - model_name='processingnode', - name='hostname', - field=models.CharField(help_text='Hostname or IP address where the node is located (can be an internal hostname as well). If you are using Docker, this is never 127.0.0.1 or localhost. Find the IP address of your host machine by running ifconfig on Linux or by checking your network settings.', max_length=255, verbose_name='Hostname'), - ), - migrations.AlterField( - model_name='processingnode', - name='label', - field=models.CharField(blank=True, default='', help_text='Optional label for this node. When set, this label will be shown instead of the hostname:port name.', max_length=255, verbose_name='Label'), - ), - migrations.AlterField( - model_name='processingnode', - name='last_refreshed', - field=models.DateTimeField(help_text='When was the information about this node last retrieved?', null=True, verbose_name='Last Refreshed'), - ), - migrations.AlterField( - model_name='processingnode', - name='max_images', - field=models.PositiveIntegerField(blank=True, help_text='Maximum number of images accepted by this node.', null=True, verbose_name='Max Images'), - ), - migrations.AlterField( - model_name='processingnode', - name='port', - field=models.PositiveIntegerField(help_text="Port that connects to the node's API", verbose_name='Port'), - ), - migrations.AlterField( - model_name='processingnode', - name='queue_count', - field=models.PositiveIntegerField(default=0, help_text='Number of tasks currently being processed by this node (as reported by the node itself)', verbose_name='Queue Count'), - ), - migrations.AlterField( - model_name='processingnode', - name='token', - field=models.CharField(blank=True, default='', help_text="Token to use for authentication. If the node doesn't have authentication, you can leave this field blank.", max_length=1024, verbose_name='Token'), - ), - ] diff --git a/nodeodm/models.py b/nodeodm/models.py index 270abf036..a06f55b8f 100644 --- a/nodeodm/models.py +++ b/nodeodm/models.py @@ -1,7 +1,6 @@ from __future__ import unicode_literals from django.db import models -from django.contrib.postgres import fields from django.utils import timezone from django.dispatch import receiver from guardian.models import GroupObjectPermissionBase @@ -25,7 +24,7 @@ class ProcessingNode(models.Model): api_version = models.CharField(verbose_name=_("API Version"), max_length=32, null=True, help_text=_("API version used by the node")) last_refreshed = models.DateTimeField(verbose_name=_("Last Refreshed"), null=True, help_text=_("When was the information about this node last retrieved?")) queue_count = models.PositiveIntegerField(verbose_name=_("Queue Count"), default=0, help_text=_("Number of tasks currently being processed by this node (as reported by the node itself)")) - available_options = fields.JSONField(verbose_name=_("Available Options"), default=dict, help_text=_("Description of the options that can be used for processing")) + available_options = models.JSONField(verbose_name=_("Available Options"), default=dict, help_text=_("Description of the options that can be used for processing")) token = models.CharField(verbose_name=_("Token"), max_length=1024, blank=True, default="", help_text=_("Token to use for authentication. If the node doesn't have authentication, you can leave this field blank.")) max_images = models.PositiveIntegerField(verbose_name=_("Max Images"), help_text=_("Maximum number of images accepted by this node."), blank=True, null=True) engine_version = models.CharField(verbose_name=_("Engine Version"), max_length=32, null=True, help_text=_("Engine version used by the node.")) diff --git a/requirements.txt b/requirements.txt index 10900789c..ca95efee2 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,64 +1,31 @@ -amqp==2.5.2 -appdirs==1.4.0 -APScheduler==3.2.0 -billiard==3.6.3.0 -celery==4.4.0 -coreapi>=2.3.3 -Django==2.2.27 -django-appconf==1.0.2 +celery==5.4.0 +numpy==1.24.4 +scipy==1.10.1 +Django==4.2.14 django-codemirror2==0.2 -django-colorfield==0.1.15 -django-cors-headers==3.0.2 -django-filter==2.4.0 -django-guardian==1.4.9 -django-imagekit==4.0.1 -django-redis==4.12.1 -django-webpack-loader==0.6.0 -djangorestframework==3.13.1 -djangorestframework-jwt==1.9.0 -djangorestframework-guardian==0.3.0 -drf-nested-routers==0.11.1 -funcsigs==1.0.2 -futures==3.1.1 -gunicorn==19.7.1 -itypes==1.1.0 -kombu==4.6.7 -Markdown==3.3.4 -olefile==0.44 -openapi-codec==1.1.7 -packaging==16.8 -piexif==1.0.13 -pilkit==2.0 -Pillow==8.3.2 -pip-autoremove==0.9.0 -psycopg2-binary==2.8.6 -PyJWT==1.5.3 -pydantic==1.10.8 +django-colorfield==0.11.0 +django_filter==2.4.0 +django-guardian==2.4.0 +django_imagekit==5.0.0 +django_redis==4.12.1 +django-webpack-loader==0.7.0 +djangorestframework==3.15.2 +djangorestframework-simplejwt==5.3.1 +drf_nested_routers==0.94.1 +django-cors-headers==4.4.0 +drf_yasg==1.21.7 +piexif==1.1.3 pyodm==1.5.11 -pyparsing==2.4.7 -pytz==2020.1 -rcssmin==1.0.6 -redis==3.2.0 -requests-toolbelt==0.9.1 -requests>=2.21.0 -rfc3987==1.3.7 -rjsmin==1.0.12 -simplejson==3.10.0 -six==1.11.0 -strict-rfc3339==0.7 +pytz==2024.1 +requests==2.32.3 tzlocal==1.3 -uritemplate==3.0.0 -vine==1.3.0 -webcolors==1.5 -rio-tiler==2.1.2 -rio-color==1.0.4 -rio-cogeo==2.3.1 -rasterio==1.2.9 ; sys_platform == 'linux' or sys_platform == 'darwin' -https://github.com/OpenDroneMap/WebODM/releases/download/v1.9.7/rasterio-1.2.10-cp39-cp39-win_amd64.whl ; sys_platform == "win32" +morecantile==5.3.0 +rio_tiler==6.6.1 +rio_cogeo==5.3.3 +rasterio==1.3.10 +urllib3==1.26.19 +psycopg2==2.9.9 +# GDAL-python will be installed if Python3 is found during the process of building GDAL from source. +GDAL==3.8.5 ; sys_platform == 'darwin' https://github.com/OpenDroneMap/WebODM/releases/download/v1.9.7/GDAL-3.3.3-cp39-cp39-win_amd64.whl ; sys_platform == "win32" -Shapely==1.7.0 ; sys_platform == "win32" -eventlet==0.32.0 ; sys_platform == "win32" -pyopenssl==19.1.0 ; sys_platform == "win32" -numpy==1.26.2 -scipy==1.11.3 -drf-yasg==1.20.0 +pyproj==3.5.0 diff --git a/webodm/settings.py b/webodm/settings.py index 112b9d520..bb2d97824 100644 --- a/webodm/settings.py +++ b/webodm/settings.py @@ -15,6 +15,7 @@ import datetime import tzlocal +from datetime import timedelta from django.contrib.messages import constants as messages # Build paths inside the project like this: os.path.join(BASE_DIR, ...) @@ -108,7 +109,7 @@ 'guardian', 'rest_framework', 'rest_framework_nested', - 'drf_yasg', + # 'drf_yasg', 'webpack_loader', 'corsheaders', 'colorfield', @@ -137,6 +138,8 @@ # Database # https://docs.djangoproject.com/en/1.10/ref/settings/#databases +DEFAULT_AUTO_FIELD = 'django.db.models.AutoField' + DATABASES = { 'default': { 'ENGINE': os.environ.get('WO_DATABASE_ENGINE', 'django.contrib.gis.db.backends.postgis'), @@ -314,24 +317,24 @@ 'rest_framework.permissions.DjangoObjectPermissions', ], 'DEFAULT_FILTER_BACKENDS': [ - 'rest_framework_guardian.filters.ObjectPermissionsFilter', 'django_filters.rest_framework.DjangoFilterBackend', 'rest_framework.filters.OrderingFilter', ], 'DEFAULT_AUTHENTICATION_CLASSES': ( - 'rest_framework.authentication.SessionAuthentication', 'rest_framework.authentication.BasicAuthentication', - 'rest_framework_jwt.authentication.JSONWebTokenAuthentication', - 'app.api.authentication.JSONWebTokenAuthenticationQS', + 'rest_framework_simplejwt.authentication.JWTAuthentication', ), 'PAGE_SIZE': 10, 'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination', } -JWT_AUTH = { - 'JWT_EXPIRATION_DELTA': datetime.timedelta(hours=6), +SIMPLE_JWT = { + "ACCESS_TOKEN_LIFETIME": timedelta(days=7), + "REFRESH_TOKEN_LIFETIME": timedelta(days=1), + "UPDATE_LAST_LOGIN": True } + # Celery CELERY_BROKER_URL = os.environ.get('WO_BROKER', 'redis://localhost') CELERY_RESULT_BACKEND = os.environ.get('WO_BROKER', 'redis://localhost') diff --git a/webodm/urls.py b/webodm/urls.py index d92938ffb..f42b9ff6f 100644 --- a/webodm/urls.py +++ b/webodm/urls.py @@ -15,46 +15,45 @@ """ import os -from django.conf.urls import include, url -from django.urls import re_path +from django.urls import re_path, include, path from django.contrib import admin from . import settings from django.views.static import serve from rest_framework import permissions -from drf_yasg.views import get_schema_view -from drf_yasg import openapi +# from drf_yasg.views import get_schema_view +# from drf_yasg import openapi admin.site.site_header = 'WebODM Administration' -schema_view = get_schema_view( - openapi.Info( - title="WebODM API", - default_version='v1.0.0', - description="WebODM API", - #terms_of_service="", - #contact=openapi.Contact(email=""), - ), - public=True, - permission_classes=[permissions.AllowAny], -) +# schema_view = get_schema_view( +# openapi.Info( +# title="WebODM API", +# default_version='v1.0.0', +# description="WebODM API", +# #terms_of_service="", +# #contact=openapi.Contact(email=""), +# ), +# public=True, +# permission_classes=[permissions.AllowAny], +# ) urlpatterns = [ - url(r'^', include('app.urls')), - url(r'^', include('django.contrib.auth.urls')), - url(r'^admin/', admin.site.urls), - re_path(r'^swagger(?P\.json|\.yaml)$', schema_view.without_ui(cache_timeout=0), name='schema-json'), - re_path(r'^swagger/$', schema_view.with_ui('swagger', cache_timeout=0), name='schema-swagger-ui'), - re_path(r'^redoc/$', schema_view.with_ui('redoc', cache_timeout=0), name='schema-redoc'), + path('', include('app.urls')), + path('', include('django.contrib.auth.urls')), + path('admin/', admin.site.urls), + # re_path(r'^swagger(?P\.json|\.yaml)$', schema_view.without_ui(cache_timeout=0), name='schema-json'), + # re_path(r'^swagger/$', schema_view.with_ui('swagger', cache_timeout=0), name='schema-swagger-ui'), + # re_path(r'^redoc/$', schema_view.with_ui('redoc', cache_timeout=0), name='schema-redoc'), ] if settings.DEBUG or settings.FORCE_MEDIA_STATICFILES: urlpatterns += [ # Expose imagekit generated files and settings file uploads - url(r'^media/CACHE/(?P.*)$', serve, { + re_path(r'^media/CACHE/(?P.*)$', serve, { 'document_root': os.path.join(settings.MEDIA_ROOT, 'CACHE') }), - url(r'^media/settings/(?P.*)$', serve, { + re_path(r'^media/settings/(?P.*)$', serve, { 'document_root': os.path.join(settings.MEDIA_ROOT, 'settings') }), From 074ed57c7f9999efeaa0fe0c5cb7812a0d926c87 Mon Sep 17 00:00:00 2001 From: Ntkskwk Date: Tue, 20 Aug 2024 13:07:10 +0800 Subject: [PATCH 13/13] revert: Recover deleted migrations files --- app/migrations/0001_initial.py | 201 +++------ .../0002_task_auto_processing_node.py | 20 + app/migrations/0003_auto_20170615_1300.py | 20 + app/migrations/0004_auto_20170707_1014.py | 25 ++ app/migrations/0005_auto_20170707_1014.py | 46 ++ app/migrations/0006_task_available_assets.py | 71 +++ app/migrations/0007_auto_20170712_1319.py | 26 ++ app/migrations/0008_preset.py | 32 ++ app/migrations/0009_auto_20170721_1332.py | 22 + app/migrations/0010_auto_20170725_1324.py | 22 + app/migrations/0011_auto_20171109_1237.py | 57 +++ app/migrations/0012_public_task_uuids.py | 60 +++ app/migrations/0013_public_task_uuids.py | 62 +++ app/migrations/0014_public_task_uuids.py | 40 ++ app/migrations/0016_public_task_uuids.py | 21 + app/migrations/0017_auto_20180219_1446.py | 25 ++ app/migrations/0018_auto_20180311_1028.py | 19 + .../0019_remove_task_processing_lock.py | 17 + app/migrations/0020_plugindatum.py | 30 ++ app/migrations/0021_auto_20180726_1746.py | 20 + app/migrations/0022_auto_20181205_1644.py | 27 ++ app/migrations/0023_task_running_progress.py | 18 + app/migrations/0024_update_task_assets.py | 80 ++++ app/migrations/0025_auto_20190220_1854.py | 69 +++ app/migrations/0026_update_images_count.py | 25 ++ app/migrations/0027_plugin.py | 20 + app/migrations/0028_task_partial.py | 18 + app/migrations/0029_auto_20190907_1348.py | 19 + app/migrations/0030_assure_cogeo.py | 28 ++ app/migrations/0031_auto_20210610_1850.py | 410 ++++++++++++++++++ app/migrations/0032_task_epsg.py | 72 +++ app/migrations/0033_auto_20230307_1532.py | 23 + app/migrations/0034_delete_imageupload.py | 16 + app/migrations/0035_task_orthophoto_bands.py | 44 ++ app/migrations/0036_task_size.py | 50 +++ app/migrations/0037_profile.py | 35 ++ .../0038_remove_task_console_output.py | 42 ++ app/migrations/0039_task_orthophoto_bands.py | 43 ++ ...0_alter_plugindatum_bool_value_and_more.py | 118 +++++ nodeodm/migrations/0001_initial.py | 54 +-- .../migrations/0002_processingnode_token.py | 18 + nodeodm/migrations/0003_auto_20180625_1230.py | 18 + .../0004_processingnode_max_images.py | 18 + nodeodm/migrations/0005_auto_20190115_1346.py | 23 + nodeodm/migrations/0006_auto_20190220_1842.py | 24 + nodeodm/migrations/0007_auto_20190520_1258.py | 27 ++ .../0008_rename_default_odm_node.py | 21 + nodeodm/migrations/0009_auto_20210610_1850.py | 73 ++++ ..._alter_processingnode_available_options.py | 18 + webodm/settings.py | 3 +- 50 files changed, 2111 insertions(+), 179 deletions(-) create mode 100644 app/migrations/0002_task_auto_processing_node.py create mode 100644 app/migrations/0003_auto_20170615_1300.py create mode 100644 app/migrations/0004_auto_20170707_1014.py create mode 100644 app/migrations/0005_auto_20170707_1014.py create mode 100644 app/migrations/0006_task_available_assets.py create mode 100644 app/migrations/0007_auto_20170712_1319.py create mode 100644 app/migrations/0008_preset.py create mode 100644 app/migrations/0009_auto_20170721_1332.py create mode 100644 app/migrations/0010_auto_20170725_1324.py create mode 100644 app/migrations/0011_auto_20171109_1237.py create mode 100644 app/migrations/0012_public_task_uuids.py create mode 100644 app/migrations/0013_public_task_uuids.py create mode 100644 app/migrations/0014_public_task_uuids.py create mode 100644 app/migrations/0016_public_task_uuids.py create mode 100644 app/migrations/0017_auto_20180219_1446.py create mode 100644 app/migrations/0018_auto_20180311_1028.py create mode 100644 app/migrations/0019_remove_task_processing_lock.py create mode 100644 app/migrations/0020_plugindatum.py create mode 100644 app/migrations/0021_auto_20180726_1746.py create mode 100644 app/migrations/0022_auto_20181205_1644.py create mode 100644 app/migrations/0023_task_running_progress.py create mode 100644 app/migrations/0024_update_task_assets.py create mode 100644 app/migrations/0025_auto_20190220_1854.py create mode 100644 app/migrations/0026_update_images_count.py create mode 100644 app/migrations/0027_plugin.py create mode 100644 app/migrations/0028_task_partial.py create mode 100644 app/migrations/0029_auto_20190907_1348.py create mode 100644 app/migrations/0030_assure_cogeo.py create mode 100644 app/migrations/0031_auto_20210610_1850.py create mode 100644 app/migrations/0032_task_epsg.py create mode 100644 app/migrations/0033_auto_20230307_1532.py create mode 100644 app/migrations/0034_delete_imageupload.py create mode 100644 app/migrations/0035_task_orthophoto_bands.py create mode 100644 app/migrations/0036_task_size.py create mode 100644 app/migrations/0037_profile.py create mode 100644 app/migrations/0038_remove_task_console_output.py create mode 100644 app/migrations/0039_task_orthophoto_bands.py create mode 100644 app/migrations/0040_alter_plugindatum_bool_value_and_more.py create mode 100644 nodeodm/migrations/0002_processingnode_token.py create mode 100644 nodeodm/migrations/0003_auto_20180625_1230.py create mode 100644 nodeodm/migrations/0004_processingnode_max_images.py create mode 100644 nodeodm/migrations/0005_auto_20190115_1346.py create mode 100644 nodeodm/migrations/0006_auto_20190220_1842.py create mode 100644 nodeodm/migrations/0007_auto_20190520_1258.py create mode 100644 nodeodm/migrations/0008_rename_default_odm_node.py create mode 100644 nodeodm/migrations/0009_auto_20210610_1850.py create mode 100644 nodeodm/migrations/0010_alter_processingnode_available_options.py diff --git a/app/migrations/0001_initial.py b/app/migrations/0001_initial.py index 258956269..a96fd4040 100644 --- a/app/migrations/0001_initial.py +++ b/app/migrations/0001_initial.py @@ -1,14 +1,13 @@ -# Generated by Django 4.2.14 on 2024-08-17 07:04 +# -*- coding: utf-8 -*- +# Generated by Django 1.10 on 2016-12-12 15:35 +from __future__ import unicode_literals -import app.models.task -import colorfield.fields +import app.models from django.conf import settings -import django.contrib.gis.db.models.fields -import django.contrib.postgres.fields +import django.contrib.postgres.fields.jsonb from django.db import migrations, models import django.db.models.deletion import django.utils.timezone -import uuid class Migration(migrations.Migration): @@ -16,185 +15,85 @@ class Migration(migrations.Migration): initial = True dependencies = [ + ('nodeodm', '0001_initial'), + ('auth', '0008_alter_user_username_max_length'), migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ('auth', '0012_alter_user_first_name_max_length'), - ('nodeodm', '__first__'), ] operations = [ migrations.CreateModel( - name='Plugin', - fields=[ - ('name', models.CharField(help_text='Plugin name', max_length=255, primary_key=True, serialize=False, verbose_name='Name')), - ('enabled', models.BooleanField(db_index=True, default=True, help_text='Whether this plugin is turned on.', verbose_name='Enabled')), - ], - options={ - 'verbose_name': 'Plugin', - 'verbose_name_plural': 'Plugins', - }, - ), - migrations.CreateModel( - name='Project', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('name', models.CharField(help_text='A label used to describe the project', max_length=255, verbose_name='Name')), - ('description', models.TextField(blank=True, default='', help_text='More in-depth description of the project', verbose_name='Description')), - ('created_at', models.DateTimeField(default=django.utils.timezone.now, help_text='Creation date', verbose_name='Created at')), - ('deleting', models.BooleanField(db_index=True, default=False, help_text='Whether this project has been marked for deletion. Projects that have running tasks need to wait for tasks to be properly cleaned up before they can be deleted.', verbose_name='Deleting')), - ('tags', models.TextField(blank=True, db_index=True, default='', help_text='Project tags', verbose_name='Tags')), - ('owner', models.ForeignKey(help_text='The person who created the project', on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL, verbose_name='Owner')), - ], - options={ - 'verbose_name': 'Project', - 'verbose_name_plural': 'Projects', - }, - ), - migrations.CreateModel( - name='Theme', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('name', models.CharField(help_text='Name of theme', max_length=255, verbose_name='Name')), - ('primary', colorfield.fields.ColorField(default='#2c3e50', help_text='Most text, icons, and borders.', image_field=None, max_length=25, samples=None, verbose_name='Primary')), - ('secondary', colorfield.fields.ColorField(default='#ffffff', help_text='The main background color, and text color of some buttons.', image_field=None, max_length=25, samples=None, verbose_name='Secondary')), - ('tertiary', colorfield.fields.ColorField(default='#3498db', help_text='Navigation links.', image_field=None, max_length=25, samples=None, verbose_name='Tertiary')), - ('button_primary', colorfield.fields.ColorField(default='#2c3e50', help_text='Primary button color.', image_field=None, max_length=25, samples=None, verbose_name='Button Primary')), - ('button_default', colorfield.fields.ColorField(default='#95a5a6', help_text='Default button color.', image_field=None, max_length=25, samples=None, verbose_name='Button Default')), - ('button_danger', colorfield.fields.ColorField(default='#e74c3c', help_text='Delete button color.', image_field=None, max_length=25, samples=None, verbose_name='Button Danger')), - ('header_background', colorfield.fields.ColorField(default='#3498db', help_text="Background color of the site's header.", image_field=None, max_length=25, samples=None, verbose_name='Header Background')), - ('header_primary', colorfield.fields.ColorField(default='#ffffff', help_text="Text and icons in the site's header.", image_field=None, max_length=25, samples=None, verbose_name='Header Primary')), - ('border', colorfield.fields.ColorField(default='#e7e7e7', help_text='The color of most borders.', image_field=None, max_length=25, samples=None, verbose_name='Border')), - ('highlight', colorfield.fields.ColorField(default='#f7f7f7', help_text='The background color of panels and some borders.', image_field=None, max_length=25, samples=None, verbose_name='Highlight')), - ('dialog_warning', colorfield.fields.ColorField(default='#f39c12', help_text='The border color of warning dialogs.', image_field=None, max_length=25, samples=None, verbose_name='Dialog Warning')), - ('failed', colorfield.fields.ColorField(default='#ffcbcb', help_text='The background color of failed notifications.', image_field=None, max_length=25, samples=None, verbose_name='Failed')), - ('success', colorfield.fields.ColorField(default='#cbffcd', help_text='The background color of success notifications.', image_field=None, max_length=25, samples=None, verbose_name='Success')), - ('css', models.TextField(blank=True, default='', verbose_name='CSS')), - ('html_before_header', models.TextField(blank=True, default='', verbose_name='HTML (before header)')), - ('html_after_header', models.TextField(blank=True, default='', verbose_name='HTML (after header)')), - ('html_after_body', models.TextField(blank=True, default='', verbose_name='HTML (after body)')), - ('html_footer', models.TextField(blank=True, default='', verbose_name='HTML (footer)')), - ], - options={ - 'verbose_name': 'Theme', - 'verbose_name_plural': 'Theme', - }, - ), - migrations.CreateModel( - name='Task', - fields=[ - ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False, unique=True, verbose_name='Id')), - ('uuid', models.CharField(blank=True, db_index=True, default='', help_text='Identifier of the task (as returned by NodeODM API)', max_length=255, verbose_name='UUID')), - ('name', models.CharField(blank=True, help_text='A label for the task', max_length=255, null=True, verbose_name='Name')), - ('processing_time', models.IntegerField(default=-1, help_text='Number of milliseconds that elapsed since the beginning of this task (-1 indicates that no information is available)', verbose_name='Processing Time')), - ('auto_processing_node', models.BooleanField(default=True, help_text='A flag indicating whether this task should be automatically assigned a processing node', verbose_name='Auto Processing Node')), - ('status', models.IntegerField(blank=True, choices=[(10, 'QUEUED'), (20, 'RUNNING'), (30, 'FAILED'), (40, 'COMPLETED'), (50, 'CANCELED')], db_index=True, help_text='Current status of the task', null=True, verbose_name='Status')), - ('last_error', models.TextField(blank=True, help_text='The last processing error received', null=True, verbose_name='Last Error')), - ('options', models.JSONField(blank=True, default=dict, help_text='Options that are being used to process this task', validators=[app.models.task.validate_task_options], verbose_name='Options')), - ('available_assets', django.contrib.postgres.fields.ArrayField(base_field=models.CharField(max_length=80), blank=True, default=list, help_text='List of available assets to download', size=None, verbose_name='Available Assets')), - ('orthophoto_extent', django.contrib.gis.db.models.fields.GeometryField(blank=True, help_text='Extent of the orthophoto', null=True, srid=4326, verbose_name='Orthophoto Extent')), - ('dsm_extent', django.contrib.gis.db.models.fields.GeometryField(blank=True, help_text='Extent of the DSM', null=True, srid=4326, verbose_name='DSM Extent')), - ('dtm_extent', django.contrib.gis.db.models.fields.GeometryField(blank=True, help_text='Extent of the DTM', null=True, srid=4326, verbose_name='DTM Extent')), - ('created_at', models.DateTimeField(default=django.utils.timezone.now, help_text='Creation date', verbose_name='Created at')), - ('pending_action', models.IntegerField(blank=True, choices=[(1, 'CANCEL'), (2, 'REMOVE'), (3, 'RESTART'), (4, 'RESIZE'), (5, 'IMPORT')], db_index=True, help_text='A requested action to be performed on the task. The selected action will be performed by the worker at the next iteration.', null=True, verbose_name='Pending Action')), - ('public', models.BooleanField(default=False, help_text='A flag indicating whether this task is available to the public', verbose_name='Public')), - ('resize_to', models.IntegerField(default=-1, help_text='When set to a value different than -1, indicates that the images for this task have been / will be resized to the size specified here before processing.', verbose_name='Resize To')), - ('upload_progress', models.FloatField(blank=True, default=0.0, help_text="Value between 0 and 1 indicating the upload progress of this task's files to the processing node", verbose_name='Upload Progress')), - ('resize_progress', models.FloatField(blank=True, default=0.0, help_text="Value between 0 and 1 indicating the resize progress of this task's images", verbose_name='Resize Progress')), - ('running_progress', models.FloatField(blank=True, default=0.0, help_text='Value between 0 and 1 indicating the running progress (estimated) of this task', verbose_name='Running Progress')), - ('import_url', models.TextField(blank=True, default='', help_text='URL this task is imported from (only for imported tasks)', verbose_name='Import URL')), - ('images_count', models.IntegerField(blank=True, default=0, help_text='Number of images associated with this task', verbose_name='Images Count')), - ('partial', models.BooleanField(default=False, help_text='A flag indicating whether this task is currently waiting for information or files to be uploaded before being considered for processing.', verbose_name='Partial')), - ('potree_scene', models.JSONField(blank=True, default=dict, help_text='Serialized potree scene information used to save/load measurements and camera view angle', verbose_name='Potree Scene')), - ('epsg', models.IntegerField(blank=True, default=None, help_text='EPSG code of the dataset (if georeferenced)', null=True, verbose_name='EPSG')), - ('tags', models.TextField(blank=True, db_index=True, default='', help_text='Task tags', verbose_name='Tags')), - ('orthophoto_bands', models.JSONField(blank=True, default=list, help_text='List of orthophoto bands', verbose_name='Orthophoto Bands')), - ('size', models.FloatField(blank=True, default=0.0, help_text='Size of the task on disk in megabytes', verbose_name='Size')), - ('processing_node', models.ForeignKey(blank=True, help_text='Processing node assigned to this task (or null if this task has not been associated yet)', null=True, on_delete=django.db.models.deletion.SET_NULL, to='nodeodm.processingnode', verbose_name='Processing Node')), - ('project', models.ForeignKey(help_text='Project that this task belongs to', on_delete=django.db.models.deletion.CASCADE, to='app.project', verbose_name='Project')), - ], - options={ - 'verbose_name': 'Task', - 'verbose_name_plural': 'Tasks', - }, - ), - migrations.CreateModel( - name='Setting', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('app_name', models.CharField(help_text='The name of your application', max_length=255, verbose_name='App name')), - ('app_logo', models.ImageField(help_text='A 512x512 logo of your application (.png or .jpeg)', upload_to='settings/', verbose_name='App logo')), - ('organization_name', models.CharField(blank=True, default='WebODM', help_text='The name of your organization', max_length=255, null=True, verbose_name='Organization name')), - ('organization_website', models.URLField(blank=True, default='https://github.com/OpenDroneMap/WebODM/', help_text='The website URL of your organization', max_length=255, null=True, verbose_name='Organization website')), - ('theme', models.ForeignKey(help_text='Active theme', on_delete=django.db.models.deletion.DO_NOTHING, to='app.theme', verbose_name='Theme')), - ], - options={ - 'verbose_name': 'Settings', - 'verbose_name_plural': 'Settings', - }, - ), - migrations.CreateModel( - name='Profile', + name='ImageUpload', fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('quota', models.FloatField(blank=True, default=-1, help_text='Maximum disk quota in megabytes', verbose_name='Quota')), - ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ('image', models.ImageField(help_text='File uploaded by a user', upload_to=app.models.image_directory_path)), ], ), migrations.CreateModel( - name='Preset', + name='Project', fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('name', models.CharField(help_text='A label used to describe the preset', max_length=255, verbose_name='Name')), - ('options', models.JSONField(blank=True, default=list, help_text="Options that define this preset (same format as in a Task's options).", validators=[app.models.task.validate_task_options], verbose_name='Options')), - ('created_at', models.DateTimeField(default=django.utils.timezone.now, help_text='Creation date', verbose_name='Created at')), - ('system', models.BooleanField(db_index=True, default=False, help_text='Whether this preset is available to every user in the system or just to its owner.', verbose_name='System')), - ('owner', models.ForeignKey(blank=True, help_text='The person who owns this preset', null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='Owner')), + ('name', models.CharField(help_text='A label used to describe the project', max_length=255)), + ('description', models.TextField(blank=True, help_text='More in-depth description of the project', null=True)), + ('created_at', models.DateTimeField(default=django.utils.timezone.now, help_text='Creation date')), + ('deleting', models.BooleanField(db_index=True, default=False, help_text='Whether this project has been marked for deletion. Projects that have running tasks need to wait for tasks to be properly cleaned up before they can be deleted.')), + ('owner', models.ForeignKey(help_text='The person who created the project', on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL)), ], - options={ - 'verbose_name': 'Preset', - 'verbose_name_plural': 'Presets', - }, ), migrations.CreateModel( - name='PluginDatum', + name='ProjectGroupObjectPermission', fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('key', models.CharField(db_index=True, help_text='Setting key', max_length=255, verbose_name='Key')), - ('int_value', models.IntegerField(blank=True, default=None, null=True, verbose_name='Integer value')), - ('float_value', models.FloatField(blank=True, default=None, null=True, verbose_name='Float value')), - ('bool_value', models.BooleanField(blank=True, default=None, null=True, verbose_name='Bool value')), - ('string_value', models.TextField(blank=True, default=None, null=True, verbose_name='String value')), - ('json_value', models.JSONField(blank=True, default=None, null=True, verbose_name='JSON value')), - ('user', models.ForeignKey(blank=True, default=None, help_text='The user this setting belongs to. If NULL, the setting is global.', null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='User')), + ('content_object', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='app.Project')), + ('group', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='auth.Group')), + ('permission', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='auth.Permission')), ], options={ - 'verbose_name': 'Plugin Datum', - 'verbose_name_plural': 'Plugin Datum', + 'abstract': False, }, ), migrations.CreateModel( name='ProjectUserObjectPermission', fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('content_object', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='app.project')), - ('permission', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='auth.permission')), + ('content_object', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='app.Project')), + ('permission', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='auth.Permission')), ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), ], options={ 'abstract': False, - 'unique_together': {('user', 'permission', 'content_object')}, }, ), migrations.CreateModel( - name='ProjectGroupObjectPermission', + name='Task', fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('content_object', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='app.project')), - ('group', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='auth.group')), - ('permission', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='auth.permission')), + ('uuid', models.CharField(blank=True, db_index=True, default='', help_text="Identifier of the task (as returned by OpenDroneMap's REST API)", max_length=255)), + ('name', models.CharField(blank=True, help_text='A label for the task', max_length=255, null=True)), + ('processing_lock', models.BooleanField(default=False, help_text='A flag indicating whether this task is currently locked for processing. When this flag is turned on, the task is in the middle of a processing step.')), + ('processing_time', models.IntegerField(default=-1, help_text='Number of milliseconds that elapsed since the beginning of this task (-1 indicates that no information is available)')), + ('status', models.IntegerField(blank=True, choices=[(10, 'QUEUED'), (20, 'RUNNING'), (30, 'FAILED'), (40, 'COMPLETED'), (50, 'CANCELED')], db_index=True, help_text='Current status of the task', null=True)), + ('last_error', models.TextField(blank=True, help_text='The last processing error received', null=True)), + ('options', django.contrib.postgres.fields.jsonb.JSONField(blank=True, default={}, help_text='Options that are being used to process this task', validators=[app.models.validate_task_options])), + ('console_output', models.TextField(blank=True, default='', help_text="Console output of the OpenDroneMap's process")), + ('ground_control_points', models.FileField(blank=True, help_text='Optional Ground Control Points file to use for processing', null=True, upload_to=app.models.gcp_directory_path)), + ('orthophoto', django.contrib.gis.db.models.RasterField(blank=True, help_text='Orthophoto created by OpenDroneMap', null=True, srid=4326)), + ('created_at', models.DateTimeField(default=django.utils.timezone.now, help_text='Creation date')), + ('pending_action', models.IntegerField(blank=True, choices=[(1, 'CANCEL'), (2, 'REMOVE'), (3, 'RESTART')], db_index=True, help_text='A requested action to be performed on the task. The selected action will be performed by the scheduler at the next iteration.', null=True)), + ('processing_node', models.ForeignKey(blank=True, help_text='Processing node assigned to this task (or null if this task has not been associated yet)', null=True, on_delete=django.db.models.deletion.CASCADE, to='nodeodm.ProcessingNode')), + ('project', models.ForeignKey(help_text='Project that this task belongs to', on_delete=django.db.models.deletion.CASCADE, to='app.Project')), ], - options={ - 'abstract': False, - 'unique_together': {('group', 'permission', 'content_object')}, - }, + ), + migrations.AddField( + model_name='imageupload', + name='task', + field=models.ForeignKey(help_text='Task this image belongs to', on_delete=django.db.models.deletion.CASCADE, to='app.Task'), + ), + migrations.AlterUniqueTogether( + name='projectuserobjectpermission', + unique_together=set([('user', 'permission', 'content_object')]), + ), + migrations.AlterUniqueTogether( + name='projectgroupobjectpermission', + unique_together=set([('group', 'permission', 'content_object')]), ), ] diff --git a/app/migrations/0002_task_auto_processing_node.py b/app/migrations/0002_task_auto_processing_node.py new file mode 100644 index 000000000..eb8129ce0 --- /dev/null +++ b/app/migrations/0002_task_auto_processing_node.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.5 on 2017-02-15 15:33 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('app', '0001_initial'), + ] + + operations = [ + migrations.AddField( + model_name='task', + name='auto_processing_node', + field=models.BooleanField(default=True, help_text='A flag indicating whether this task should be automatically assigned a processing node'), + ), + ] diff --git a/app/migrations/0003_auto_20170615_1300.py b/app/migrations/0003_auto_20170615_1300.py new file mode 100644 index 000000000..336528ba3 --- /dev/null +++ b/app/migrations/0003_auto_20170615_1300.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.1 on 2017-06-15 17:00 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('app', '0002_task_auto_processing_node'), + ] + + operations = [ + migrations.AlterField( + model_name='project', + name='description', + field=models.TextField(blank=True, default='', help_text='More in-depth description of the project'), + ), + ] diff --git a/app/migrations/0004_auto_20170707_1014.py b/app/migrations/0004_auto_20170707_1014.py new file mode 100644 index 000000000..4cafe2a02 --- /dev/null +++ b/app/migrations/0004_auto_20170707_1014.py @@ -0,0 +1,25 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.1 on 2017-07-07 14:14 +from __future__ import unicode_literals + +import django.contrib.gis.db.models.fields +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('app', '0003_auto_20170615_1300'), + ] + + operations = [ + migrations.RemoveField( + model_name='task', + name='orthophoto', + ), + migrations.AddField( + model_name='task', + name='orthophoto_extent', + field=django.contrib.gis.db.models.fields.GeometryField(blank=True, help_text='Extent of the orthophoto created by OpenDroneMap', null=True, srid=4326), + ), + ] diff --git a/app/migrations/0005_auto_20170707_1014.py b/app/migrations/0005_auto_20170707_1014.py new file mode 100644 index 000000000..9987fa7b6 --- /dev/null +++ b/app/migrations/0005_auto_20170707_1014.py @@ -0,0 +1,46 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.1 on 2017-07-07 14:14 +from __future__ import unicode_literals + +from django.contrib.gis.gdal import GDALRaster, OGRGeometry +from django.contrib.gis.geos import GEOSGeometry +from django.db import migrations +import os + +from webodm import settings + + +def assets_path(project_id, task_id, *args): + return os.path.join(settings.MEDIA_ROOT, + "project", + str(project_id), + "task", + str(task_id), + "assets", + *args) + +def transfer_existing_orthophoto_extent_values(apps, schema_editor): + Task = apps.get_model('app', 'Task') + + for t in Task.objects.all(): + print("Checking {}".format(t)) + orthophoto_path = assets_path(t.project.id, t.id, "odm_orthophoto", "odm_orthophoto_4326.tif") + if os.path.exists(orthophoto_path): + print("Migrating {}".format(orthophoto_path)) + + raster = GDALRaster(orthophoto_path) + geom = OGRGeometry.from_bbox(raster.extent) + t.orthophoto_extent = GEOSGeometry(geom.wkt) + t.save() + + os.remove(orthophoto_path) + +class Migration(migrations.Migration): + + dependencies = [ + ('app', '0004_auto_20170707_1014'), + ] + + operations = [ + migrations.RunPython(transfer_existing_orthophoto_extent_values), + ] diff --git a/app/migrations/0006_task_available_assets.py b/app/migrations/0006_task_available_assets.py new file mode 100644 index 000000000..fa95c40af --- /dev/null +++ b/app/migrations/0006_task_available_assets.py @@ -0,0 +1,71 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.1 on 2017-07-07 18:05 +from __future__ import unicode_literals + +import django.contrib.postgres.fields +import os +from django.db import migrations, models + +from webodm import settings + +ASSETS_MAP = { + 'all.zip': 'all.zip', + 'orthophoto.tif': os.path.join('odm_orthophoto', 'odm_orthophoto.tif'), + 'orthophoto.png': os.path.join('odm_orthophoto', 'odm_orthophoto.png'), + 'orthophoto.mbtiles': os.path.join('odm_orthophoto', 'odm_orthophoto.mbtiles'), + 'georeferenced_model.las': os.path.join('odm_georeferencing', 'odm_georeferenced_model.las'), + 'georeferenced_model.laz': os.path.join('odm_georeferencing', 'odm_georeferenced_model.laz'), + 'georeferenced_model.ply': os.path.join('odm_georeferencing', 'odm_georeferenced_model.ply'), + 'georeferenced_model.csv': os.path.join('odm_georeferencing', 'odm_georeferenced_model.csv'), + 'textured_model.zip': { + 'deferred_path': 'textured_model.zip', + 'deferred_compress_dir': 'odm_texturing' + } +} + +def assets_path(project_id, task_id, *args): + return os.path.join(settings.MEDIA_ROOT, + "project", + str(project_id), + "task", + str(task_id), + "assets", + *args) + +def is_asset_available_slow(t, asset): + if asset in ASSETS_MAP: + value = ASSETS_MAP[asset] + if isinstance(value, str): + return os.path.exists(assets_path(t.project.id, t.id, value)) + elif isinstance(value, dict): + if 'deferred_compress_dir' in value: + return os.path.exists(assets_path(t.project.id, t.id, value['deferred_compress_dir'])) + + return False + + +def detect_available_assets(apps, schema_editor): + Task = apps.get_model('app', 'Task') + + for t in Task.objects.all(): + print("Updating {}".format(t)) + + all_assets = list(ASSETS_MAP.keys()) + t.available_assets = [asset for asset in all_assets if is_asset_available_slow(t, asset)] + t.save() + + +class Migration(migrations.Migration): + + dependencies = [ + ('app', '0005_auto_20170707_1014'), + ] + + operations = [ + migrations.AddField( + model_name='task', + name='available_assets', + field=django.contrib.postgres.fields.ArrayField(base_field=models.CharField(max_length=80), blank=True, default=[], help_text='List of available assets to download', size=None), + ), + migrations.RunPython(detect_available_assets), + ] diff --git a/app/migrations/0007_auto_20170712_1319.py b/app/migrations/0007_auto_20170712_1319.py new file mode 100644 index 000000000..90f0512a1 --- /dev/null +++ b/app/migrations/0007_auto_20170712_1319.py @@ -0,0 +1,26 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.1 on 2017-07-12 17:19 +from __future__ import unicode_literals + +import django.contrib.gis.db.models.fields +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('app', '0006_task_available_assets'), + ] + + operations = [ + migrations.AddField( + model_name='task', + name='dsm_extent', + field=django.contrib.gis.db.models.fields.GeometryField(blank=True, help_text='Extent of the DSM created by OpenDroneMap', null=True, srid=4326), + ), + migrations.AddField( + model_name='task', + name='dtm_extent', + field=django.contrib.gis.db.models.fields.GeometryField(blank=True, help_text='Extent of the DTM created by OpenDroneMap', null=True, srid=4326), + ), + ] diff --git a/app/migrations/0008_preset.py b/app/migrations/0008_preset.py new file mode 100644 index 000000000..f57a8be16 --- /dev/null +++ b/app/migrations/0008_preset.py @@ -0,0 +1,32 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.1 on 2017-07-20 14:19 +from __future__ import unicode_literals + +import app.models.task +from django.conf import settings +import django.contrib.postgres.fields.jsonb +from django.db import migrations, models +import django.db.models.deletion +import django.utils.timezone + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('app', '0007_auto_20170712_1319'), + ] + + operations = [ + migrations.CreateModel( + name='Preset', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(help_text='A label used to describe the preset', max_length=255)), + ('options', django.contrib.postgres.fields.jsonb.JSONField(blank=True, default={}, help_text="Options that define this preset (same format as in a Task's options).", validators=[app.models.task.validate_task_options])), + ('created_at', models.DateTimeField(default=django.utils.timezone.now, help_text='Creation date')), + ('system', models.BooleanField(db_index=True, default=False, help_text='Whether this preset is available to every user in the system or just to its owner.')), + ('owner', models.ForeignKey(help_text='The person who owns this preset', on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ], + ), + ] diff --git a/app/migrations/0009_auto_20170721_1332.py b/app/migrations/0009_auto_20170721_1332.py new file mode 100644 index 000000000..9076765bc --- /dev/null +++ b/app/migrations/0009_auto_20170721_1332.py @@ -0,0 +1,22 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.1 on 2017-07-21 17:32 +from __future__ import unicode_literals + +import app.models.task +import django.contrib.postgres.fields.jsonb +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('app', '0008_preset'), + ] + + operations = [ + migrations.AlterField( + model_name='preset', + name='options', + field=django.contrib.postgres.fields.jsonb.JSONField(blank=True, default=[], help_text="Options that define this preset (same format as in a Task's options).", validators=[app.models.task.validate_task_options]), + ), + ] diff --git a/app/migrations/0010_auto_20170725_1324.py b/app/migrations/0010_auto_20170725_1324.py new file mode 100644 index 000000000..188b3bf4d --- /dev/null +++ b/app/migrations/0010_auto_20170725_1324.py @@ -0,0 +1,22 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.1 on 2017-07-25 17:24 +from __future__ import unicode_literals + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('app', '0009_auto_20170721_1332'), + ] + + operations = [ + migrations.AlterField( + model_name='preset', + name='owner', + field=models.ForeignKey(blank=True, help_text='The person who owns this preset', null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL), + ), + ] diff --git a/app/migrations/0011_auto_20171109_1237.py b/app/migrations/0011_auto_20171109_1237.py new file mode 100644 index 000000000..861de9b7d --- /dev/null +++ b/app/migrations/0011_auto_20171109_1237.py @@ -0,0 +1,57 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.1 on 2017-11-09 17:37 +from __future__ import unicode_literals + +import colorfield.fields +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('app', '0010_auto_20170725_1324'), + ] + + operations = [ + migrations.CreateModel( + name='Setting', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('app_name', models.CharField(help_text='The name of your application', max_length=255)), + ('app_logo', models.ImageField(help_text='A 512x512 logo of your application (.png or .jpeg)', upload_to='settings/')), + ('organization_name', models.CharField(blank=True, default='WebODM', help_text='The name of your organization', max_length=255, null=True)), + ('organization_website', models.URLField(blank=True, default='https://github.com/OpenDroneMap/WebODM/', help_text='The website URL of your organization', max_length=255, null=True)), + ], + ), + migrations.CreateModel( + name='Theme', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(help_text='Name of theme', max_length=255)), + ('primary', colorfield.fields.ColorField(default='#2c3e50', help_text='Most text, icons, and borders.', max_length=18)), + ('secondary', colorfield.fields.ColorField(default='#ffffff', help_text='The main background color, and text color of some buttons.', max_length=18)), + ('tertiary', colorfield.fields.ColorField(default='#18bc9c', help_text='Navigation links.', max_length=18)), + ('button_primary', colorfield.fields.ColorField(default='#2c3e50', help_text='Primary button color.', max_length=18)), + ('button_default', colorfield.fields.ColorField(default='#95a5a6', help_text='Default button color.', max_length=18)), + ('button_danger', colorfield.fields.ColorField(default='#e74c3c', help_text='Delete button color.', max_length=18)), + ('header_background', colorfield.fields.ColorField(default='#18bc9c', help_text="Background color of the site's header.", max_length=18)), + ('header_primary', colorfield.fields.ColorField(default='#ffffff', help_text="Text and icons in the site's header.", max_length=18)), + ('border', colorfield.fields.ColorField(default='#e7e7e7', help_text='The color of most borders.', max_length=18)), + ('highlight', colorfield.fields.ColorField(default='#f7f7f7', help_text='The background color of panels and some borders.', max_length=18)), + ('dialog_warning', colorfield.fields.ColorField(default='#f39c12', help_text='The border color of warning dialogs.', max_length=18)), + ('failed', colorfield.fields.ColorField(default='#ffcbcb', help_text='The background color of failed notifications.', max_length=18)), + ('success', colorfield.fields.ColorField(default='#cbffcd', help_text='The background color of success notifications.', max_length=18)), + ('css', models.TextField(blank=True, default='')), + ('html_before_header', models.TextField(blank=True, default='')), + ('html_after_header', models.TextField(blank=True, default='')), + ('html_after_body', models.TextField(blank=True, default='')), + ('html_footer', models.TextField(blank=True, default='')), + ], + ), + migrations.AddField( + model_name='setting', + name='theme', + field=models.ForeignKey(help_text='Active theme', on_delete=django.db.models.deletion.DO_NOTHING, to='app.Theme'), + ), + ] diff --git a/app/migrations/0012_public_task_uuids.py b/app/migrations/0012_public_task_uuids.py new file mode 100644 index 000000000..739191784 --- /dev/null +++ b/app/migrations/0012_public_task_uuids.py @@ -0,0 +1,60 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.1 on 2017-11-30 15:41 +from __future__ import unicode_literals + +from django.db import migrations, models +import uuid, os, pickle, tempfile + +from webodm import settings + +tasks = [] +task_ids = {} # map old task IDs --> new task IDs + +def dump(apps, schema_editor): + global tasks, task_ids + + Task = apps.get_model('app', 'Task') + + tasks = list(Task.objects.all().values('id', 'project')) + + # Generate UUIDs + for task in tasks: + new_id = uuid.uuid4() + + # Save reference to it + task['new_id'] = new_id + + # Populate map + task_ids[task['id']] = new_id + + tmp_path = os.path.join(tempfile.gettempdir(), "public_task_uuids_migration.pickle") + pickle.dump((tasks, task_ids), open(tmp_path, 'wb')) + + if len(tasks) > 0: print("Dumped tasks") + + +class Migration(migrations.Migration): + + dependencies = [ + ('app', '0011_auto_20171109_1237'), + ] + + operations = [ + migrations.AddField( + model_name='task', + name='public', + field=models.BooleanField(default=False, help_text='A flag indicating whether this task is available to the public'), + ), + + migrations.RunPython(dump), + + migrations.RemoveField( + model_name='imageupload', + name='task' + ), + migrations.AddField( + model_name='task', + name='new_id', + field=models.UUIDField(null=True) + ), + ] diff --git a/app/migrations/0013_public_task_uuids.py b/app/migrations/0013_public_task_uuids.py new file mode 100644 index 000000000..7486a82e0 --- /dev/null +++ b/app/migrations/0013_public_task_uuids.py @@ -0,0 +1,62 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.1 on 2017-11-30 15:41 +from __future__ import unicode_literals + +from django.db import migrations, models +import uuid, os, pickle, tempfile + +from webodm import settings + +tasks = [] +task_ids = {} # map old task IDs --> new task IDs + +def task_path(project_id, task_id): + return os.path.join(settings.MEDIA_ROOT, + "project", + str(project_id), + "task", + str(task_id)) + +def rename_task_folders(apps, schema_editor): + global tasks, task_ids + + for t in tasks: + print("Checking task {}".format(t['id'])) + current_path = task_path(t['project'], t['id']) + if os.path.exists(current_path): + new_path = task_path(t['project'], task_ids[t['id']]) + print("Migrating {} --> {}".format(current_path, new_path)) + os.rename(current_path, new_path) + +def create_uuids(apps, schema_editor): + global tasks, task_ids + + Task = apps.get_model('app', 'Task') + for task in tasks: + print(task) + + t = Task.objects.get(id=task['id']) + t.new_id = task['new_id'] + t.save() + + if len(tasks) > 0: print("Created UUIDs") + + +def restore(apps, schema_editor): + global tasks, task_ids + + tmp_path = os.path.join(tempfile.gettempdir(), "public_task_uuids_migration.pickle") + tasks, task_ids = pickle.load(open(tmp_path, 'rb')) + + +class Migration(migrations.Migration): + + dependencies = [ + ('app', '0012_public_task_uuids'), + ] + + operations = [ + migrations.RunPython(restore), + migrations.RunPython(create_uuids), + migrations.RunPython(rename_task_folders), + ] diff --git a/app/migrations/0014_public_task_uuids.py b/app/migrations/0014_public_task_uuids.py new file mode 100644 index 000000000..9846829f2 --- /dev/null +++ b/app/migrations/0014_public_task_uuids.py @@ -0,0 +1,40 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.1 on 2017-11-30 15:41 +from __future__ import unicode_literals + +from django.db import migrations, models +import uuid + +from webodm import settings + +class Migration(migrations.Migration): + + dependencies = [ + ('app', '0013_public_task_uuids'), + ] + + operations = [ + migrations.AlterField( + model_name='task', + name='new_id', + field=models.UUIDField(default=uuid.uuid4, unique=True, serialize=False, editable=False) + ), + migrations.RemoveField('task', 'id'), + migrations.RenameField( + model_name='task', + old_name='new_id', + new_name='id' + ), + migrations.AlterField( + model_name='task', + name='id', + field=models.UUIDField(primary_key=True, default=uuid.uuid4, unique=True, serialize=False, editable=False) + ), + + migrations.AddField( + model_name='imageupload', + name='task', + field=models.ForeignKey(null=True, on_delete=models.CASCADE, help_text="Task this image belongs to", to='app.Task') + ), + + ] diff --git a/app/migrations/0016_public_task_uuids.py b/app/migrations/0016_public_task_uuids.py new file mode 100644 index 000000000..cc34e4e41 --- /dev/null +++ b/app/migrations/0016_public_task_uuids.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.1 on 2017-11-30 15:41 +from __future__ import unicode_literals + +from django.db import migrations, models + +from webodm import settings + +class Migration(migrations.Migration): + + dependencies = [ + ('app', '0014_public_task_uuids'), + ] + + operations = [ + migrations.AlterField( + model_name='imageupload', + name='task', + field=models.ForeignKey(null=False, on_delete=models.CASCADE, help_text="Task this image belongs to", to='app.Task') + ), + ] diff --git a/app/migrations/0017_auto_20180219_1446.py b/app/migrations/0017_auto_20180219_1446.py new file mode 100644 index 000000000..c97f8e6c2 --- /dev/null +++ b/app/migrations/0017_auto_20180219_1446.py @@ -0,0 +1,25 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.7 on 2018-02-19 19:46 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('app', '0016_public_task_uuids'), + ] + + operations = [ + migrations.AddField( + model_name='task', + name='resize_to', + field=models.IntegerField(default=-1, help_text='When set to a value different than -1, indicates that the images for this task have been / will be resized to the size specified here before processing.'), + ), + migrations.AlterField( + model_name='task', + name='pending_action', + field=models.IntegerField(blank=True, choices=[(1, 'CANCEL'), (2, 'REMOVE'), (3, 'RESTART'), (4, 'RESIZE')], db_index=True, help_text='A requested action to be performed on the task. The selected action will be performed by the worker at the next iteration.', null=True), + ), + ] diff --git a/app/migrations/0018_auto_20180311_1028.py b/app/migrations/0018_auto_20180311_1028.py new file mode 100644 index 000000000..2a1e34f18 --- /dev/null +++ b/app/migrations/0018_auto_20180311_1028.py @@ -0,0 +1,19 @@ +# Generated by Django 2.0.3 on 2018-03-11 14:28 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('app', '0017_auto_20180219_1446'), + ] + + operations = [ + migrations.AlterField( + model_name='task', + name='processing_node', + field=models.ForeignKey(blank=True, help_text='Processing node assigned to this task (or null if this task has not been associated yet)', null=True, on_delete=django.db.models.deletion.SET_NULL, to='nodeodm.ProcessingNode'), + ), + ] diff --git a/app/migrations/0019_remove_task_processing_lock.py b/app/migrations/0019_remove_task_processing_lock.py new file mode 100644 index 000000000..8b692e5db --- /dev/null +++ b/app/migrations/0019_remove_task_processing_lock.py @@ -0,0 +1,17 @@ +# Generated by Django 2.0.3 on 2018-04-08 16:47 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('app', '0018_auto_20180311_1028'), + ] + + operations = [ + migrations.RemoveField( + model_name='task', + name='processing_lock', + ), + ] diff --git a/app/migrations/0020_plugindatum.py b/app/migrations/0020_plugindatum.py new file mode 100644 index 000000000..5ba16d63c --- /dev/null +++ b/app/migrations/0020_plugindatum.py @@ -0,0 +1,30 @@ +# Generated by Django 2.0.3 on 2018-07-24 21:01 + +from django.conf import settings +import django.contrib.postgres.fields.jsonb +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('app', '0019_remove_task_processing_lock'), + ] + + operations = [ + migrations.CreateModel( + name='PluginDatum', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('key', models.CharField(db_index=True, help_text='Setting key', max_length=255)), + ('int_value', models.IntegerField(blank=True, default=None, help_text='Integer value', null=True)), + ('float_value', models.FloatField(blank=True, default=None, help_text='Float value', null=True)), + ('bool_value', models.NullBooleanField(default=None, help_text='Bool value')), + ('string_value', models.TextField(blank=True, default=None, help_text='String value', null=True)), + ('json_value', django.contrib.postgres.fields.jsonb.JSONField(blank=True, default=None, help_text='JSON value', null=True)), + ('user', models.ForeignKey(help_text='The user this setting belongs to. If NULL, the setting is global.', on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ], + ), + ] diff --git a/app/migrations/0021_auto_20180726_1746.py b/app/migrations/0021_auto_20180726_1746.py new file mode 100644 index 000000000..764cf8c3a --- /dev/null +++ b/app/migrations/0021_auto_20180726_1746.py @@ -0,0 +1,20 @@ +# Generated by Django 2.0.3 on 2018-07-26 21:46 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('app', '0020_plugindatum'), + ] + + operations = [ + migrations.AlterField( + model_name='plugindatum', + name='user', + field=models.ForeignKey(default=None, help_text='The user this setting belongs to. If NULL, the setting is global.', null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL), + ), + ] diff --git a/app/migrations/0022_auto_20181205_1644.py b/app/migrations/0022_auto_20181205_1644.py new file mode 100644 index 000000000..37a68b8da --- /dev/null +++ b/app/migrations/0022_auto_20181205_1644.py @@ -0,0 +1,27 @@ +# Generated by Django 2.0.3 on 2018-12-05 16:44 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('app', '0021_auto_20180726_1746'), + ] + + operations = [ + migrations.RemoveField( + model_name='task', + name='ground_control_points', + ), + migrations.AddField( + model_name='task', + name='resize_progress', + field=models.FloatField(blank=True, default=0.0, help_text="Value between 0 and 1 indicating the resize progress of this task's images"), + ), + migrations.AddField( + model_name='task', + name='upload_progress', + field=models.FloatField(blank=True, default=0.0, help_text="Value between 0 and 1 indicating the upload progress of this task's files to the processing node"), + ), + ] diff --git a/app/migrations/0023_task_running_progress.py b/app/migrations/0023_task_running_progress.py new file mode 100644 index 000000000..e615b8a71 --- /dev/null +++ b/app/migrations/0023_task_running_progress.py @@ -0,0 +1,18 @@ +# Generated by Django 2.0.3 on 2018-12-07 18:53 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('app', '0022_auto_20181205_1644'), + ] + + operations = [ + migrations.AddField( + model_name='task', + name='running_progress', + field=models.FloatField(blank=True, default=0.0, help_text="Value between 0 and 1 indicating the running progress (estimated) of this task"), + ), + ] diff --git a/app/migrations/0024_update_task_assets.py b/app/migrations/0024_update_task_assets.py new file mode 100644 index 000000000..e3cc40b4d --- /dev/null +++ b/app/migrations/0024_update_task_assets.py @@ -0,0 +1,80 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.1 on 2017-07-07 18:05 +from __future__ import unicode_literals + +import django.contrib.postgres.fields +import os +from django.db import migrations, models + +from webodm import settings + +ASSETS_MAP = { + 'all.zip': 'all.zip', + 'orthophoto.tif': os.path.join('odm_orthophoto', 'odm_orthophoto.tif'), + 'orthophoto.png': os.path.join('odm_orthophoto', 'odm_orthophoto.png'), + 'orthophoto.mbtiles': os.path.join('odm_orthophoto', 'odm_orthophoto.mbtiles'), + 'georeferenced_model.las': os.path.join('odm_georeferencing', 'odm_georeferenced_model.las'), + 'georeferenced_model.laz': os.path.join('odm_georeferencing', 'odm_georeferenced_model.laz'), + 'georeferenced_model.ply': os.path.join('odm_georeferencing', 'odm_georeferenced_model.ply'), + 'georeferenced_model.csv': os.path.join('odm_georeferencing', 'odm_georeferenced_model.csv'), + 'textured_model.zip': { + 'deferred_path': 'textured_model.zip', + 'deferred_compress_dir': 'odm_texturing' + }, + 'dtm.tif': os.path.join('odm_dem', 'dtm.tif'), + 'dsm.tif': os.path.join('odm_dem', 'dsm.tif'), + 'dtm_tiles.zip': { + 'deferred_path': 'dtm_tiles.zip', + 'deferred_compress_dir': 'dtm_tiles' + }, + 'dsm_tiles.zip': { + 'deferred_path': 'dsm_tiles.zip', + 'deferred_compress_dir': 'dsm_tiles' + }, + 'orthophoto_tiles.zip': { + 'deferred_path': 'orthophoto_tiles.zip', + 'deferred_compress_dir': 'orthophoto_tiles' + }, +} + +def assets_path(project_id, task_id, *args): + return os.path.join(settings.MEDIA_ROOT, + "project", + str(project_id), + "task", + str(task_id), + "assets", + *args) + +def is_asset_available_slow(t, asset): + if asset in ASSETS_MAP: + value = ASSETS_MAP[asset] + if isinstance(value, str): + return os.path.exists(assets_path(t.project.id, t.id, value)) + elif isinstance(value, dict): + if 'deferred_compress_dir' in value: + return os.path.exists(assets_path(t.project.id, t.id, value['deferred_compress_dir'])) + + return False + + +def detect_available_assets(apps, schema_editor): + Task = apps.get_model('app', 'Task') + + for t in Task.objects.all(): + print("Updating {}".format(t)) + + all_assets = list(ASSETS_MAP.keys()) + t.available_assets = [asset for asset in all_assets if is_asset_available_slow(t, asset)] + t.save() + + +class Migration(migrations.Migration): + + dependencies = [ + ('app', '0023_task_running_progress'), + ] + + operations = [ + migrations.RunPython(detect_available_assets), + ] diff --git a/app/migrations/0025_auto_20190220_1854.py b/app/migrations/0025_auto_20190220_1854.py new file mode 100644 index 000000000..ac4d86195 --- /dev/null +++ b/app/migrations/0025_auto_20190220_1854.py @@ -0,0 +1,69 @@ +# Generated by Django 2.1.5 on 2019-02-20 18:54 + +import app.models.task +import colorfield.fields +import django.contrib.postgres.fields +import django.contrib.postgres.fields.jsonb +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('app', '0024_update_task_assets'), + ] + + operations = [ + migrations.AddField( + model_name='task', + name='import_url', + field=models.TextField(blank=True, default='', + help_text='URL this task is imported from (only for imported tasks)'), + ), + migrations.AlterField( + model_name='preset', + name='options', + field=django.contrib.postgres.fields.jsonb.JSONField(blank=True, default=list, + help_text="Options that define this preset (same format as in a Task's options).", + validators=[app.models.task.validate_task_options]), + ), + migrations.AlterField( + model_name='task', + name='available_assets', + field=django.contrib.postgres.fields.ArrayField(base_field=models.CharField(max_length=80), blank=True, + default=list, + help_text='List of available assets to download', + size=None), + ), + migrations.AlterField( + model_name='task', + name='options', + field=django.contrib.postgres.fields.jsonb.JSONField(blank=True, default=dict, + help_text='Options that are being used to process this task', + validators=[app.models.task.validate_task_options]), + ), + migrations.AlterField( + model_name='task', + name='pending_action', + field=models.IntegerField(blank=True, choices=[(1, 'CANCEL'), (2, 'REMOVE'), (3, 'RESTART'), (4, 'RESIZE'), + (5, 'IMPORT')], db_index=True, + help_text='A requested action to be performed on the task. The selected action will be performed by the worker at the next iteration.', + null=True), + ), + migrations.AlterField( + model_name='theme', + name='header_background', + field=colorfield.fields.ColorField(default='#3498db', help_text="Background color of the site's header.", + max_length=18), + ), + migrations.AlterField( + model_name='theme', + name='tertiary', + field=colorfield.fields.ColorField(default='#3498db', help_text='Navigation links.', max_length=18), + ), + migrations.AddField( + model_name='task', + name='images_count', + field=models.IntegerField(blank=True, default=0, help_text='Number of images associated with this task'), + ), + ] diff --git a/app/migrations/0026_update_images_count.py b/app/migrations/0026_update_images_count.py new file mode 100644 index 000000000..8cde8ebfa --- /dev/null +++ b/app/migrations/0026_update_images_count.py @@ -0,0 +1,25 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.1 on 2017-07-07 18:05 +from __future__ import unicode_literals + +from django.db import migrations + + +def update_images_count(apps, schema_editor): + Task = apps.get_model('app', 'Task') + + for t in Task.objects.all(): + print("Updating {}".format(t)) + t.images_count = len(t.scan_images()) + t.save() + + +class Migration(migrations.Migration): + + dependencies = [ + ('app', '0025_auto_20190220_1854'), + ] + + operations = [ + migrations.RunPython(update_images_count), + ] diff --git a/app/migrations/0027_plugin.py b/app/migrations/0027_plugin.py new file mode 100644 index 000000000..5349742fd --- /dev/null +++ b/app/migrations/0027_plugin.py @@ -0,0 +1,20 @@ +# Generated by Django 2.1.7 on 2019-03-19 16:34 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('app', '0026_update_images_count'), + ] + + operations = [ + migrations.CreateModel( + name='Plugin', + fields=[ + ('name', models.CharField(help_text='Plugin name', max_length=255, primary_key=True, serialize=False)), + ('enabled', models.BooleanField(db_index=True, default=True, help_text='Whether this plugin is enabled.')), + ], + ), + ] diff --git a/app/migrations/0028_task_partial.py b/app/migrations/0028_task_partial.py new file mode 100644 index 000000000..e00d5cb1f --- /dev/null +++ b/app/migrations/0028_task_partial.py @@ -0,0 +1,18 @@ +# Generated by Django 2.1.7 on 2019-06-26 18:31 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('app', '0027_plugin'), + ] + + operations = [ + migrations.AddField( + model_name='task', + name='partial', + field=models.BooleanField(default=False, help_text='A flag indicating whether this task is currently waiting for information or files to be uploaded before being considered for processing.'), + ), + ] diff --git a/app/migrations/0029_auto_20190907_1348.py b/app/migrations/0029_auto_20190907_1348.py new file mode 100644 index 000000000..9977a4d2f --- /dev/null +++ b/app/migrations/0029_auto_20190907_1348.py @@ -0,0 +1,19 @@ +# Generated by Django 2.1.11 on 2019-09-07 13:48 + +import app.models +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('app', '0028_task_partial'), + ] + + operations = [ + migrations.AlterField( + model_name='imageupload', + name='image', + field=models.ImageField(help_text='File uploaded by a user', max_length=512, upload_to=app.models.image_directory_path), + ), + ] diff --git a/app/migrations/0030_assure_cogeo.py b/app/migrations/0030_assure_cogeo.py new file mode 100644 index 000000000..091f55bff --- /dev/null +++ b/app/migrations/0030_assure_cogeo.py @@ -0,0 +1,28 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from app.cogeo import assure_cogeo +from django.db import migrations +import glob +import os +from webodm import settings + +def find_and_assure_cogeo(apps, schema_editor): + for asset_filename in ["odm_orthophoto.tif", "dsm.tif", "dtm.tif"]: + for asset in glob.glob(os.path.join(settings.MEDIA_ROOT, "project", "**", asset_filename), recursive=True): + try: + print("Optimizing %s" % asset) + assure_cogeo(asset) + except Exception as e: + print("WARNING: cannot check/optimize %s (%s), skipping..." % (asset, str(e))) + + +class Migration(migrations.Migration): + + dependencies = [ + ('app', '0029_auto_20190907_1348'), + ] + + operations = [ + migrations.RunPython(find_and_assure_cogeo), + ] diff --git a/app/migrations/0031_auto_20210610_1850.py b/app/migrations/0031_auto_20210610_1850.py new file mode 100644 index 000000000..9ae9c16c4 --- /dev/null +++ b/app/migrations/0031_auto_20210610_1850.py @@ -0,0 +1,410 @@ +# Generated by Django 2.1.15 on 2021-06-10 18:50 + +import app.models.task +from app.models import image_directory_path +import colorfield.fields +from django.conf import settings +import django.contrib.gis.db.models.fields +import django.contrib.postgres.fields +import django.contrib.postgres.fields.jsonb +from django.db import migrations, models +import django.db.models.deletion +import django.utils.timezone +import uuid + + +class Migration(migrations.Migration): + + dependencies = [ + ('app', '0030_assure_cogeo'), + ] + + operations = [ + migrations.AlterModelOptions( + name='imageupload', + options={'verbose_name': 'Image Upload', 'verbose_name_plural': 'Image Uploads'}, + ), + migrations.AlterModelOptions( + name='plugin', + options={'verbose_name': 'Plugin', 'verbose_name_plural': 'Plugins'}, + ), + migrations.AlterModelOptions( + name='plugindatum', + options={'verbose_name': 'Plugin Datum', 'verbose_name_plural': 'Plugin Datum'}, + ), + migrations.AlterModelOptions( + name='preset', + options={'verbose_name': 'Preset', 'verbose_name_plural': 'Presets'}, + ), + migrations.AlterModelOptions( + name='project', + options={'verbose_name': 'Project', 'verbose_name_plural': 'Projects'}, + ), + migrations.AlterModelOptions( + name='setting', + options={'verbose_name': 'Settings', 'verbose_name_plural': 'Settings'}, + ), + migrations.AlterModelOptions( + name='task', + options={'verbose_name': 'Task', 'verbose_name_plural': 'Tasks'}, + ), + migrations.AlterModelOptions( + name='theme', + options={'verbose_name': 'Theme', 'verbose_name_plural': 'Theme'}, + ), + migrations.AddField( + model_name='task', + name='potree_scene', + field=django.contrib.postgres.fields.jsonb.JSONField(blank=True, default=dict, help_text='Serialized potree scene information used to save/load measurements and camera view angle', verbose_name='Potree Scene'), + ), + migrations.AlterField( + model_name='imageupload', + name='image', + field=models.ImageField(help_text='File uploaded by a user', max_length=512, upload_to=image_directory_path, verbose_name='Image'), + ), + migrations.AlterField( + model_name='imageupload', + name='task', + field=models.ForeignKey(help_text='Task this image belongs to', on_delete=django.db.models.deletion.CASCADE, to='app.Task', verbose_name='Task'), + ), + migrations.AlterField( + model_name='plugin', + name='enabled', + field=models.BooleanField(db_index=True, default=True, help_text='Whether this plugin is turned on.', verbose_name='Enabled'), + ), + migrations.AlterField( + model_name='plugin', + name='name', + field=models.CharField(help_text='Plugin name', max_length=255, primary_key=True, serialize=False, verbose_name='Name'), + ), + migrations.AlterField( + model_name='plugindatum', + name='bool_value', + field=models.NullBooleanField(default=None, verbose_name='Bool value'), + ), + migrations.AlterField( + model_name='plugindatum', + name='float_value', + field=models.FloatField(blank=True, default=None, null=True, verbose_name='Float value'), + ), + migrations.AlterField( + model_name='plugindatum', + name='int_value', + field=models.IntegerField(blank=True, default=None, null=True, verbose_name='Integer value'), + ), + migrations.AlterField( + model_name='plugindatum', + name='json_value', + field=django.contrib.postgres.fields.jsonb.JSONField(blank=True, default=None, null=True, verbose_name='JSON value'), + ), + migrations.AlterField( + model_name='plugindatum', + name='key', + field=models.CharField(db_index=True, help_text='Setting key', max_length=255, verbose_name='Key'), + ), + migrations.AlterField( + model_name='plugindatum', + name='string_value', + field=models.TextField(blank=True, default=None, null=True, verbose_name='String value'), + ), + migrations.AlterField( + model_name='plugindatum', + name='user', + field=models.ForeignKey(default=None, help_text='The user this setting belongs to. If NULL, the setting is global.', null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='User'), + ), + migrations.AlterField( + model_name='preset', + name='created_at', + field=models.DateTimeField(default=django.utils.timezone.now, help_text='Creation date', verbose_name='Created at'), + ), + migrations.AlterField( + model_name='preset', + name='name', + field=models.CharField(help_text='A label used to describe the preset', max_length=255, verbose_name='Name'), + ), + migrations.AlterField( + model_name='preset', + name='options', + field=django.contrib.postgres.fields.jsonb.JSONField(blank=True, default=list, help_text="Options that define this preset (same format as in a Task's options).", validators=[app.models.task.validate_task_options], verbose_name='Options'), + ), + migrations.AlterField( + model_name='preset', + name='owner', + field=models.ForeignKey(blank=True, help_text='The person who owns this preset', null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='Owner'), + ), + migrations.AlterField( + model_name='preset', + name='system', + field=models.BooleanField(db_index=True, default=False, help_text='Whether this preset is available to every user in the system or just to its owner.', verbose_name='System'), + ), + migrations.AlterField( + model_name='project', + name='created_at', + field=models.DateTimeField(default=django.utils.timezone.now, help_text='Creation date', verbose_name='Created at'), + ), + migrations.AlterField( + model_name='project', + name='deleting', + field=models.BooleanField(db_index=True, default=False, help_text='Whether this project has been marked for deletion. Projects that have running tasks need to wait for tasks to be properly cleaned up before they can be deleted.', verbose_name='Deleting'), + ), + migrations.AlterField( + model_name='project', + name='description', + field=models.TextField(blank=True, default='', help_text='More in-depth description of the project', verbose_name='Description'), + ), + migrations.AlterField( + model_name='project', + name='name', + field=models.CharField(help_text='A label used to describe the project', max_length=255, verbose_name='Name'), + ), + migrations.AlterField( + model_name='project', + name='owner', + field=models.ForeignKey(help_text='The person who created the project', on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL, verbose_name='Owner'), + ), + migrations.AlterField( + model_name='setting', + name='app_logo', + field=models.ImageField(help_text='A 512x512 logo of your application (.png or .jpeg)', upload_to='settings/', verbose_name='App logo'), + ), + migrations.AlterField( + model_name='setting', + name='app_name', + field=models.CharField(help_text='The name of your application', max_length=255, verbose_name='App name'), + ), + migrations.AlterField( + model_name='setting', + name='organization_name', + field=models.CharField(blank=True, default='WebODM', help_text='The name of your organization', max_length=255, null=True, verbose_name='Organization name'), + ), + migrations.AlterField( + model_name='setting', + name='organization_website', + field=models.URLField(blank=True, default='https://github.com/OpenDroneMap/WebODM/', help_text='The website URL of your organization', max_length=255, null=True, verbose_name='Organization website'), + ), + migrations.AlterField( + model_name='setting', + name='theme', + field=models.ForeignKey(help_text='Active theme', on_delete=django.db.models.deletion.DO_NOTHING, to='app.Theme', verbose_name='Theme'), + ), + migrations.AlterField( + model_name='task', + name='auto_processing_node', + field=models.BooleanField(default=True, help_text='A flag indicating whether this task should be automatically assigned a processing node', verbose_name='Auto Processing Node'), + ), + migrations.AlterField( + model_name='task', + name='available_assets', + field=django.contrib.postgres.fields.ArrayField(base_field=models.CharField(max_length=80), blank=True, default=list, help_text='List of available assets to download', size=None, verbose_name='Available Assets'), + ), + migrations.AlterField( + model_name='task', + name='console_output', + field=models.TextField(blank=True, default='', help_text='Console output of the processing node', verbose_name='Console Output'), + ), + migrations.AlterField( + model_name='task', + name='created_at', + field=models.DateTimeField(default=django.utils.timezone.now, help_text='Creation date', verbose_name='Created at'), + ), + migrations.AlterField( + model_name='task', + name='dsm_extent', + field=django.contrib.gis.db.models.fields.GeometryField(blank=True, help_text='Extent of the DSM', null=True, srid=4326, verbose_name='DSM Extent'), + ), + migrations.AlterField( + model_name='task', + name='dtm_extent', + field=django.contrib.gis.db.models.fields.GeometryField(blank=True, help_text='Extent of the DTM', null=True, srid=4326, verbose_name='DTM Extent'), + ), + migrations.AlterField( + model_name='task', + name='id', + field=models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False, unique=True, verbose_name='Id'), + ), + migrations.AlterField( + model_name='task', + name='images_count', + field=models.IntegerField(blank=True, default=0, help_text='Number of images associated with this task', verbose_name='Images Count'), + ), + migrations.AlterField( + model_name='task', + name='import_url', + field=models.TextField(blank=True, default='', help_text='URL this task is imported from (only for imported tasks)', verbose_name='Import URL'), + ), + migrations.AlterField( + model_name='task', + name='last_error', + field=models.TextField(blank=True, help_text='The last processing error received', null=True, verbose_name='Last Error'), + ), + migrations.AlterField( + model_name='task', + name='name', + field=models.CharField(blank=True, help_text='A label for the task', max_length=255, null=True, verbose_name='Name'), + ), + migrations.AlterField( + model_name='task', + name='options', + field=django.contrib.postgres.fields.jsonb.JSONField(blank=True, default=dict, help_text='Options that are being used to process this task', validators=[app.models.task.validate_task_options], verbose_name='Options'), + ), + migrations.AlterField( + model_name='task', + name='orthophoto_extent', + field=django.contrib.gis.db.models.fields.GeometryField(blank=True, help_text='Extent of the orthophoto', null=True, srid=4326, verbose_name='Orthophoto Extent'), + ), + migrations.AlterField( + model_name='task', + name='partial', + field=models.BooleanField(default=False, help_text='A flag indicating whether this task is currently waiting for information or files to be uploaded before being considered for processing.', verbose_name='Partial'), + ), + migrations.AlterField( + model_name='task', + name='pending_action', + field=models.IntegerField(blank=True, choices=[(1, 'CANCEL'), (2, 'REMOVE'), (3, 'RESTART'), (4, 'RESIZE'), (5, 'IMPORT')], db_index=True, help_text='A requested action to be performed on the task. The selected action will be performed by the worker at the next iteration.', null=True, verbose_name='Pending Action'), + ), + migrations.AlterField( + model_name='task', + name='processing_node', + field=models.ForeignKey(blank=True, help_text='Processing node assigned to this task (or null if this task has not been associated yet)', null=True, on_delete=django.db.models.deletion.SET_NULL, to='nodeodm.ProcessingNode', verbose_name='Processing Node'), + ), + migrations.AlterField( + model_name='task', + name='processing_time', + field=models.IntegerField(default=-1, help_text='Number of milliseconds that elapsed since the beginning of this task (-1 indicates that no information is available)', verbose_name='Processing Time'), + ), + migrations.AlterField( + model_name='task', + name='project', + field=models.ForeignKey(help_text='Project that this task belongs to', on_delete=django.db.models.deletion.CASCADE, to='app.Project', verbose_name='Project'), + ), + migrations.AlterField( + model_name='task', + name='public', + field=models.BooleanField(default=False, help_text='A flag indicating whether this task is available to the public', verbose_name='Public'), + ), + migrations.AlterField( + model_name='task', + name='resize_progress', + field=models.FloatField(blank=True, default=0.0, help_text="Value between 0 and 1 indicating the resize progress of this task's images", verbose_name='Resize Progress'), + ), + migrations.AlterField( + model_name='task', + name='resize_to', + field=models.IntegerField(default=-1, help_text='When set to a value different than -1, indicates that the images for this task have been / will be resized to the size specified here before processing.', verbose_name='Resize To'), + ), + migrations.AlterField( + model_name='task', + name='running_progress', + field=models.FloatField(blank=True, default=0.0, help_text='Value between 0 and 1 indicating the running progress (estimated) of this task', verbose_name='Running Progress'), + ), + migrations.AlterField( + model_name='task', + name='status', + field=models.IntegerField(blank=True, choices=[(10, 'QUEUED'), (20, 'RUNNING'), (30, 'FAILED'), (40, 'COMPLETED'), (50, 'CANCELED')], db_index=True, help_text='Current status of the task', null=True, verbose_name='Status'), + ), + migrations.AlterField( + model_name='task', + name='upload_progress', + field=models.FloatField(blank=True, default=0.0, help_text="Value between 0 and 1 indicating the upload progress of this task's files to the processing node", verbose_name='Upload Progress'), + ), + migrations.AlterField( + model_name='task', + name='uuid', + field=models.CharField(blank=True, db_index=True, default='', help_text='Identifier of the task (as returned by NodeODM API)', max_length=255, verbose_name='UUID'), + ), + migrations.AlterField( + model_name='theme', + name='border', + field=colorfield.fields.ColorField(default='#e7e7e7', help_text='The color of most borders.', max_length=18, verbose_name='Border'), + ), + migrations.AlterField( + model_name='theme', + name='button_danger', + field=colorfield.fields.ColorField(default='#e74c3c', help_text='Delete button color.', max_length=18, verbose_name='Button Danger'), + ), + migrations.AlterField( + model_name='theme', + name='button_default', + field=colorfield.fields.ColorField(default='#95a5a6', help_text='Default button color.', max_length=18, verbose_name='Button Default'), + ), + migrations.AlterField( + model_name='theme', + name='button_primary', + field=colorfield.fields.ColorField(default='#2c3e50', help_text='Primary button color.', max_length=18, verbose_name='Button Primary'), + ), + migrations.AlterField( + model_name='theme', + name='css', + field=models.TextField(blank=True, default='', verbose_name='CSS'), + ), + migrations.AlterField( + model_name='theme', + name='dialog_warning', + field=colorfield.fields.ColorField(default='#f39c12', help_text='The border color of warning dialogs.', max_length=18, verbose_name='Dialog Warning'), + ), + migrations.AlterField( + model_name='theme', + name='failed', + field=colorfield.fields.ColorField(default='#ffcbcb', help_text='The background color of failed notifications.', max_length=18, verbose_name='Failed'), + ), + migrations.AlterField( + model_name='theme', + name='header_background', + field=colorfield.fields.ColorField(default='#3498db', help_text="Background color of the site's header.", max_length=18, verbose_name='Header Background'), + ), + migrations.AlterField( + model_name='theme', + name='header_primary', + field=colorfield.fields.ColorField(default='#ffffff', help_text="Text and icons in the site's header.", max_length=18, verbose_name='Header Primary'), + ), + migrations.AlterField( + model_name='theme', + name='highlight', + field=colorfield.fields.ColorField(default='#f7f7f7', help_text='The background color of panels and some borders.', max_length=18, verbose_name='Highlight'), + ), + migrations.AlterField( + model_name='theme', + name='html_after_body', + field=models.TextField(blank=True, default='', verbose_name='HTML (after body)'), + ), + migrations.AlterField( + model_name='theme', + name='html_after_header', + field=models.TextField(blank=True, default='', verbose_name='HTML (after header)'), + ), + migrations.AlterField( + model_name='theme', + name='html_before_header', + field=models.TextField(blank=True, default='', verbose_name='HTML (before header)'), + ), + migrations.AlterField( + model_name='theme', + name='html_footer', + field=models.TextField(blank=True, default='', verbose_name='HTML (footer)'), + ), + migrations.AlterField( + model_name='theme', + name='name', + field=models.CharField(help_text='Name of theme', max_length=255, verbose_name='Name'), + ), + migrations.AlterField( + model_name='theme', + name='primary', + field=colorfield.fields.ColorField(default='#2c3e50', help_text='Most text, icons, and borders.', max_length=18, verbose_name='Primary'), + ), + migrations.AlterField( + model_name='theme', + name='secondary', + field=colorfield.fields.ColorField(default='#ffffff', help_text='The main background color, and text color of some buttons.', max_length=18, verbose_name='Secondary'), + ), + migrations.AlterField( + model_name='theme', + name='success', + field=colorfield.fields.ColorField(default='#cbffcd', help_text='The background color of success notifications.', max_length=18, verbose_name='Success'), + ), + migrations.AlterField( + model_name='theme', + name='tertiary', + field=colorfield.fields.ColorField(default='#3498db', help_text='Navigation links.', max_length=18, verbose_name='Tertiary'), + ), + ] diff --git a/app/migrations/0032_task_epsg.py b/app/migrations/0032_task_epsg.py new file mode 100644 index 000000000..d4fa7e32a --- /dev/null +++ b/app/migrations/0032_task_epsg.py @@ -0,0 +1,72 @@ +# Generated by Django 2.1.15 on 2021-11-01 14:37 + +from django.db import migrations, models +import rasterio +import os +from app.pointcloud_utils import is_pointcloud_georeferenced +from webodm import settings + +def update_epsg_fields(apps, schema_editor): + Task = apps.get_model('app', 'Task') + + for t in Task.objects.all(): + + epsg = None + for asset in [os.path.join('odm_orthophoto', 'odm_orthophoto.tif'), + os.path.join('odm_dem', 'dsm.tif'), + os.path.join('odm_dem', 'dtm.tif')]: + asset_path = os.path.join(settings.MEDIA_ROOT, "project", str(t.project.id), "task", str(t.id), "assets", asset) + if os.path.isfile(asset_path): + try: + with rasterio.open(asset_path) as f: + if f.crs is not None: + epsg = f.crs.to_epsg() + break # We assume all assets are in the same CRS + except Exception as e: + print(e) + + # If point cloud is not georeferenced, dataset is not georeferenced + # (2D assets might be using pseudo-georeferencing) + point_cloud = os.path.join(settings.MEDIA_ROOT, "project", str(t.project.id), "task", str(t.id), "assets", "odm_georeferencing", "odm_georeferenced_model.laz") + + if epsg is not None and os.path.isfile(point_cloud): + if not is_pointcloud_georeferenced(point_cloud): + print("{} is not georeferenced".format(t)) + epsg = None + + print("Updating {} (with epsg: {})".format(t, epsg)) + + t.epsg = epsg + t.save() + +def remove_all_zip(apps, schema_editor): + Task = apps.get_model('app', 'Task') + + for t in Task.objects.all(): + asset = 'all.zip' + asset_path = os.path.join(settings.MEDIA_ROOT, "project", str(t.project.id), "task", str(t.id), "assets", asset) + if os.path.isfile(asset_path): + try: + os.remove(asset_path) + print("Cleaned up {}".format(asset_path)) + except Exception as e: + print(e) + + +class Migration(migrations.Migration): + + dependencies = [ + ('app', '0031_auto_20210610_1850'), + ] + + operations = [ + migrations.AddField( + model_name='task', + name='epsg', + field=models.IntegerField(blank=True, default=None, help_text='EPSG code of the dataset (if georeferenced)', null=True, verbose_name='EPSG'), + ), + + migrations.RunPython(update_epsg_fields), + migrations.RunPython(remove_all_zip), + + ] diff --git a/app/migrations/0033_auto_20230307_1532.py b/app/migrations/0033_auto_20230307_1532.py new file mode 100644 index 000000000..df180c60c --- /dev/null +++ b/app/migrations/0033_auto_20230307_1532.py @@ -0,0 +1,23 @@ +# Generated by Django 2.2.27 on 2023-03-07 15:32 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('app', '0032_task_epsg'), + ] + + operations = [ + migrations.AddField( + model_name='project', + name='tags', + field=models.TextField(blank=True, db_index=True, default='', help_text='Project tags', verbose_name='Tags'), + ), + migrations.AddField( + model_name='task', + name='tags', + field=models.TextField(blank=True, db_index=True, default='', help_text='Task tags', verbose_name='Tags'), + ), + ] diff --git a/app/migrations/0034_delete_imageupload.py b/app/migrations/0034_delete_imageupload.py new file mode 100644 index 000000000..d2227feec --- /dev/null +++ b/app/migrations/0034_delete_imageupload.py @@ -0,0 +1,16 @@ +# Generated by Django 2.2.27 on 2023-03-23 17:10 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('app', '0033_auto_20230307_1532'), + ] + + operations = [ + migrations.DeleteModel( + name='ImageUpload', + ), + ] diff --git a/app/migrations/0035_task_orthophoto_bands.py b/app/migrations/0035_task_orthophoto_bands.py new file mode 100644 index 000000000..2a99ee109 --- /dev/null +++ b/app/migrations/0035_task_orthophoto_bands.py @@ -0,0 +1,44 @@ +# Generated by Django 2.2.27 on 2023-05-19 15:38 + +import rasterio +import os +import django.contrib.postgres.fields.jsonb +from django.db import migrations +from webodm import settings + +def update_orthophoto_bands_fields(apps, schema_editor): + Task = apps.get_model('app', 'Task') + + for t in Task.objects.all(): + + bands = [] + orthophoto_path = os.path.join(settings.MEDIA_ROOT, "project", str(t.project.id), "task", str(t.id), "assets", "odm_orthophoto", "odm_orthophoto.tif") + + if os.path.isfile(orthophoto_path): + try: + with rasterio.open(orthophoto_path) as f: + bands = [c.name for c in f.colorinterp] + except Exception as e: + print(e) + + print("Updating {} (with orthophoto bands: {})".format(t, str(bands))) + + t.orthophoto_bands = bands + t.save() + + +class Migration(migrations.Migration): + + dependencies = [ + ('app', '0034_delete_imageupload'), + ] + + operations = [ + migrations.AddField( + model_name='task', + name='orthophoto_bands', + field=django.contrib.postgres.fields.jsonb.JSONField(blank=True, default=list, help_text='List of orthophoto bands', verbose_name='Orthophoto Bands'), + ), + + migrations.RunPython(update_orthophoto_bands_fields), + ] diff --git a/app/migrations/0036_task_size.py b/app/migrations/0036_task_size.py new file mode 100644 index 000000000..1fe421953 --- /dev/null +++ b/app/migrations/0036_task_size.py @@ -0,0 +1,50 @@ +# Generated by Django 2.2.27 on 2023-08-21 14:50 +import os +from django.db import migrations, models +from webodm import settings + +def task_path(project_id, task_id, *args): + return os.path.join(settings.MEDIA_ROOT, + "project", + str(project_id), + "task", + str(task_id), + *args) + +def update_size(task): + try: + total_bytes = 0 + for dirpath, _, filenames in os.walk(task_path(task.project.id, task.id)): + for f in filenames: + fp = os.path.join(dirpath, f) + if not os.path.islink(fp): + total_bytes += os.path.getsize(fp) + task.size = (total_bytes / 1024 / 1024) + task.save() + print("Updated {} with size {}".format(task, task.size)) + except Exception as e: + print("Cannot update size for task {}: {}".format(task, str(e))) + + + +def update_task_sizes(apps, schema_editor): + Task = apps.get_model('app', 'Task') + + for t in Task.objects.all(): + update_size(t) + +class Migration(migrations.Migration): + + dependencies = [ + ('app', '0035_task_orthophoto_bands'), + ] + + operations = [ + migrations.AddField( + model_name='task', + name='size', + field=models.FloatField(blank=True, default=0.0, help_text='Size of the task on disk in megabytes', verbose_name='Size'), + ), + + migrations.RunPython(update_task_sizes), + ] diff --git a/app/migrations/0037_profile.py b/app/migrations/0037_profile.py new file mode 100644 index 000000000..ab7a1fa08 --- /dev/null +++ b/app/migrations/0037_profile.py @@ -0,0 +1,35 @@ +# Generated by Django 2.2.27 on 2023-08-24 16:35 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +def create_profiles(apps, schema_editor): + User = apps.get_model('auth', 'User') + Profile = apps.get_model('app', 'Profile') + + for u in User.objects.all(): + p = Profile.objects.create(user=u) + p.save() + print("Created user profile for %s" % u.username) + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('app', '0036_task_size'), + ] + + operations = [ + migrations.CreateModel( + name='Profile', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('quota', models.FloatField(blank=True, default=-1, help_text='Maximum disk quota in megabytes', verbose_name='Quota')), + ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ], + ), + + migrations.RunPython(create_profiles), + ] diff --git a/app/migrations/0038_remove_task_console_output.py b/app/migrations/0038_remove_task_console_output.py new file mode 100644 index 000000000..38a54625c --- /dev/null +++ b/app/migrations/0038_remove_task_console_output.py @@ -0,0 +1,42 @@ +# Generated by Django 2.2.27 on 2023-09-11 19:11 +import os +from django.db import migrations +from webodm import settings + +def data_path(project_id, task_id, *args): + return os.path.join(settings.MEDIA_ROOT, + "project", + str(project_id), + "task", + str(task_id), + "data", + *args) + +def dump_console_outputs(apps, schema_editor): + Task = apps.get_model('app', 'Task') + + for t in Task.objects.all(): + if t.console_output is not None and len(t.console_output) > 0: + dp = data_path(t.project.id, t.id) + os.makedirs(dp, exist_ok=True) + outfile = os.path.join(dp, "console_output.txt") + + with open(outfile, "w", encoding="utf-8") as f: + f.write(t.console_output) + print("Wrote console output for %s to %s" % (t, outfile)) + else: + print("No task output for %s" % t) + +class Migration(migrations.Migration): + + dependencies = [ + ('app', '0037_profile'), + ] + + operations = [ + migrations.RunPython(dump_console_outputs), + migrations.RemoveField( + model_name='task', + name='console_output', + ), + ] diff --git a/app/migrations/0039_task_orthophoto_bands.py b/app/migrations/0039_task_orthophoto_bands.py new file mode 100644 index 000000000..c801ab853 --- /dev/null +++ b/app/migrations/0039_task_orthophoto_bands.py @@ -0,0 +1,43 @@ +# Generated by Django 2.2.27 on 2023-10-02 10:21 + +import rasterio +import os +import django.contrib.postgres.fields.jsonb +from django.db import migrations +from webodm import settings + +def update_orthophoto_bands_fields(apps, schema_editor): + Task = apps.get_model('app', 'Task') + + for t in Task.objects.all(): + + bands = [] + orthophoto_path = os.path.join(settings.MEDIA_ROOT, "project", str(t.project.id), "task", str(t.id), "assets", "odm_orthophoto", "odm_orthophoto.tif") + + if os.path.isfile(orthophoto_path): + try: + with rasterio.open(orthophoto_path) as f: + names = [c.name for c in f.colorinterp] + for i, n in enumerate(names): + bands.append({ + 'name': n, + 'description': f.descriptions[i] + }) + except Exception as e: + print(e) + + print("Updating {} (with orthophoto bands: {})".format(t, str(bands))) + + t.orthophoto_bands = bands + t.save() + + +class Migration(migrations.Migration): + + dependencies = [ + ('app', '0038_remove_task_console_output'), + ] + + operations = [ + migrations.RunPython(update_orthophoto_bands_fields), + ] diff --git a/app/migrations/0040_alter_plugindatum_bool_value_and_more.py b/app/migrations/0040_alter_plugindatum_bool_value_and_more.py new file mode 100644 index 000000000..2e1dbba5c --- /dev/null +++ b/app/migrations/0040_alter_plugindatum_bool_value_and_more.py @@ -0,0 +1,118 @@ +# Generated by Django 4.2.14 on 2024-08-20 05:00 + +import app.models.task +import colorfield.fields +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('app', '0039_task_orthophoto_bands'), + ] + + operations = [ + migrations.AlterField( + model_name='plugindatum', + name='bool_value', + field=models.BooleanField(blank=True, default=None, null=True, verbose_name='Bool value'), + ), + migrations.AlterField( + model_name='plugindatum', + name='json_value', + field=models.JSONField(blank=True, default=None, null=True, verbose_name='JSON value'), + ), + migrations.AlterField( + model_name='plugindatum', + name='user', + field=models.ForeignKey(blank=True, default=None, help_text='The user this setting belongs to. If NULL, the setting is global.', null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='User'), + ), + migrations.AlterField( + model_name='preset', + name='options', + field=models.JSONField(blank=True, default=list, help_text="Options that define this preset (same format as in a Task's options).", validators=[app.models.task.validate_task_options], verbose_name='Options'), + ), + migrations.AlterField( + model_name='task', + name='options', + field=models.JSONField(blank=True, default=dict, help_text='Options that are being used to process this task', validators=[app.models.task.validate_task_options], verbose_name='Options'), + ), + migrations.AlterField( + model_name='task', + name='orthophoto_bands', + field=models.JSONField(blank=True, default=list, help_text='List of orthophoto bands', verbose_name='Orthophoto Bands'), + ), + migrations.AlterField( + model_name='task', + name='potree_scene', + field=models.JSONField(blank=True, default=dict, help_text='Serialized potree scene information used to save/load measurements and camera view angle', verbose_name='Potree Scene'), + ), + migrations.AlterField( + model_name='theme', + name='border', + field=colorfield.fields.ColorField(default='#e7e7e7', help_text='The color of most borders.', image_field=None, max_length=25, samples=None, verbose_name='Border'), + ), + migrations.AlterField( + model_name='theme', + name='button_danger', + field=colorfield.fields.ColorField(default='#e74c3c', help_text='Delete button color.', image_field=None, max_length=25, samples=None, verbose_name='Button Danger'), + ), + migrations.AlterField( + model_name='theme', + name='button_default', + field=colorfield.fields.ColorField(default='#95a5a6', help_text='Default button color.', image_field=None, max_length=25, samples=None, verbose_name='Button Default'), + ), + migrations.AlterField( + model_name='theme', + name='button_primary', + field=colorfield.fields.ColorField(default='#2c3e50', help_text='Primary button color.', image_field=None, max_length=25, samples=None, verbose_name='Button Primary'), + ), + migrations.AlterField( + model_name='theme', + name='dialog_warning', + field=colorfield.fields.ColorField(default='#f39c12', help_text='The border color of warning dialogs.', image_field=None, max_length=25, samples=None, verbose_name='Dialog Warning'), + ), + migrations.AlterField( + model_name='theme', + name='failed', + field=colorfield.fields.ColorField(default='#ffcbcb', help_text='The background color of failed notifications.', image_field=None, max_length=25, samples=None, verbose_name='Failed'), + ), + migrations.AlterField( + model_name='theme', + name='header_background', + field=colorfield.fields.ColorField(default='#3498db', help_text="Background color of the site's header.", image_field=None, max_length=25, samples=None, verbose_name='Header Background'), + ), + migrations.AlterField( + model_name='theme', + name='header_primary', + field=colorfield.fields.ColorField(default='#ffffff', help_text="Text and icons in the site's header.", image_field=None, max_length=25, samples=None, verbose_name='Header Primary'), + ), + migrations.AlterField( + model_name='theme', + name='highlight', + field=colorfield.fields.ColorField(default='#f7f7f7', help_text='The background color of panels and some borders.', image_field=None, max_length=25, samples=None, verbose_name='Highlight'), + ), + migrations.AlterField( + model_name='theme', + name='primary', + field=colorfield.fields.ColorField(default='#2c3e50', help_text='Most text, icons, and borders.', image_field=None, max_length=25, samples=None, verbose_name='Primary'), + ), + migrations.AlterField( + model_name='theme', + name='secondary', + field=colorfield.fields.ColorField(default='#ffffff', help_text='The main background color, and text color of some buttons.', image_field=None, max_length=25, samples=None, verbose_name='Secondary'), + ), + migrations.AlterField( + model_name='theme', + name='success', + field=colorfield.fields.ColorField(default='#cbffcd', help_text='The background color of success notifications.', image_field=None, max_length=25, samples=None, verbose_name='Success'), + ), + migrations.AlterField( + model_name='theme', + name='tertiary', + field=colorfield.fields.ColorField(default='#3498db', help_text='Navigation links.', image_field=None, max_length=25, samples=None, verbose_name='Tertiary'), + ), + ] diff --git a/nodeodm/migrations/0001_initial.py b/nodeodm/migrations/0001_initial.py index f67dfd603..42521dfda 100644 --- a/nodeodm/migrations/0001_initial.py +++ b/nodeodm/migrations/0001_initial.py @@ -1,6 +1,9 @@ -# Generated by Django 4.2.14 on 2024-08-17 07:04 +# -*- coding: utf-8 -*- +# Generated by Django 1.10 on 2016-12-06 16:32 +from __future__ import unicode_literals from django.conf import settings +import django.contrib.postgres.fields.jsonb from django.db import migrations, models import django.db.models.deletion @@ -10,8 +13,8 @@ class Migration(migrations.Migration): initial = True dependencies = [ + ('auth', '0008_alter_user_username_max_length'), migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ('auth', '0012_alter_user_first_name_max_length'), ] operations = [ @@ -19,47 +22,44 @@ class Migration(migrations.Migration): name='ProcessingNode', fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('hostname', models.CharField(help_text='Hostname or IP address where the node is located (can be an internal hostname as well). If you are using Docker, this is never 127.0.0.1 or localhost. Find the IP address of your host machine by running ifconfig on Linux or by checking your network settings.', max_length=255, verbose_name='Hostname')), - ('port', models.PositiveIntegerField(help_text="Port that connects to the node's API", verbose_name='Port')), - ('api_version', models.CharField(help_text='API version used by the node', max_length=32, null=True, verbose_name='API Version')), - ('last_refreshed', models.DateTimeField(help_text='When was the information about this node last retrieved?', null=True, verbose_name='Last Refreshed')), - ('queue_count', models.PositiveIntegerField(default=0, help_text='Number of tasks currently being processed by this node (as reported by the node itself)', verbose_name='Queue Count')), - ('available_options', models.JSONField(default=dict, help_text='Description of the options that can be used for processing', verbose_name='Available Options')), - ('token', models.CharField(blank=True, default='', help_text="Token to use for authentication. If the node doesn't have authentication, you can leave this field blank.", max_length=1024, verbose_name='Token')), - ('max_images', models.PositiveIntegerField(blank=True, help_text='Maximum number of images accepted by this node.', null=True, verbose_name='Max Images')), - ('engine_version', models.CharField(help_text='Engine version used by the node.', max_length=32, null=True, verbose_name='Engine Version')), - ('label', models.CharField(blank=True, default='', help_text='Optional label for this node. When set, this label will be shown instead of the hostname:port name.', max_length=255, verbose_name='Label')), - ('engine', models.CharField(help_text='Engine used by the node.', max_length=255, null=True, verbose_name='Engine')), + ('hostname', models.CharField(help_text='Hostname or IP address where the node is located (can be an internal hostname as well). If you are using Docker, this is never 127.0.0.1 or localhost. Find the IP address of your host machine by running ifconfig on Linux or by checking your network settings.', max_length=255)), + ('port', models.PositiveIntegerField(help_text="Port that connects to the node's API")), + ('api_version', models.CharField(help_text='API version used by the node', max_length=32, null=True)), + ('last_refreshed', models.DateTimeField(help_text='When was the information about this node last retrieved?', null=True)), + ('queue_count', models.PositiveIntegerField(default=0, help_text='Number of tasks currently being processed by this node (as reported by the node itself)')), + ('available_options', django.contrib.postgres.fields.jsonb.JSONField(default={}, help_text='Description of the options that can be used for processing')), ], - options={ - 'verbose_name': 'Processing Node', - 'verbose_name_plural': 'Processing Nodes', - }, ), migrations.CreateModel( - name='ProcessingNodeUserObjectPermission', + name='ProcessingNodeGroupObjectPermission', fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('content_object', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='nodeodm.processingnode')), - ('permission', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='auth.permission')), - ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ('content_object', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='nodeodm.ProcessingNode')), + ('group', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='auth.Group')), + ('permission', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='auth.Permission')), ], options={ 'abstract': False, - 'unique_together': {('user', 'permission', 'content_object')}, }, ), migrations.CreateModel( - name='ProcessingNodeGroupObjectPermission', + name='ProcessingNodeUserObjectPermission', fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('content_object', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='nodeodm.processingnode')), - ('group', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='auth.group')), - ('permission', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='auth.permission')), + ('content_object', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='nodeodm.ProcessingNode')), + ('permission', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='auth.Permission')), + ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), ], options={ 'abstract': False, - 'unique_together': {('group', 'permission', 'content_object')}, }, ), + migrations.AlterUniqueTogether( + name='processingnodeuserobjectpermission', + unique_together=set([('user', 'permission', 'content_object')]), + ), + migrations.AlterUniqueTogether( + name='processingnodegroupobjectpermission', + unique_together=set([('group', 'permission', 'content_object')]), + ), ] diff --git a/nodeodm/migrations/0002_processingnode_token.py b/nodeodm/migrations/0002_processingnode_token.py new file mode 100644 index 000000000..37098eb75 --- /dev/null +++ b/nodeodm/migrations/0002_processingnode_token.py @@ -0,0 +1,18 @@ +# Generated by Django 2.0.3 on 2018-06-25 16:27 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('nodeodm', '0001_initial'), + ] + + operations = [ + migrations.AddField( + model_name='processingnode', + name='token', + field=models.CharField(default='', help_text="Token to use for authentication. If the node doesn't have authentication, you can leave this field blank.", max_length=1024, null=True), + ), + ] diff --git a/nodeodm/migrations/0003_auto_20180625_1230.py b/nodeodm/migrations/0003_auto_20180625_1230.py new file mode 100644 index 000000000..09ab181cf --- /dev/null +++ b/nodeodm/migrations/0003_auto_20180625_1230.py @@ -0,0 +1,18 @@ +# Generated by Django 2.0.3 on 2018-06-25 16:30 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('nodeodm', '0002_processingnode_token'), + ] + + operations = [ + migrations.AlterField( + model_name='processingnode', + name='token', + field=models.CharField(blank=True, default='', help_text="Token to use for authentication. If the node doesn't have authentication, you can leave this field blank.", max_length=1024), + ), + ] diff --git a/nodeodm/migrations/0004_processingnode_max_images.py b/nodeodm/migrations/0004_processingnode_max_images.py new file mode 100644 index 000000000..e67aef6d9 --- /dev/null +++ b/nodeodm/migrations/0004_processingnode_max_images.py @@ -0,0 +1,18 @@ +# Generated by Django 2.0.3 on 2018-12-04 17:04 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('nodeodm', '0003_auto_20180625_1230'), + ] + + operations = [ + migrations.AddField( + model_name='processingnode', + name='max_images', + field=models.PositiveIntegerField(blank=True, help_text='Maximum number of images accepted by this node.', null=True), + ), + ] diff --git a/nodeodm/migrations/0005_auto_20190115_1346.py b/nodeodm/migrations/0005_auto_20190115_1346.py new file mode 100644 index 000000000..2bb4d60ae --- /dev/null +++ b/nodeodm/migrations/0005_auto_20190115_1346.py @@ -0,0 +1,23 @@ +# Generated by Django 2.0.3 on 2019-01-15 13:46 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('nodeodm', '0004_processingnode_max_images'), + ] + + operations = [ + migrations.AddField( + model_name='processingnode', + name='label', + field=models.CharField(blank=True, default='', help_text='Optional label for this node. When set, this label will be shown instead of hostname:port.', max_length=255), + ), + migrations.AddField( + model_name='processingnode', + name='odm_version', + field=models.CharField(help_text='ODM version used by the node.', max_length=32, null=True), + ), + ] diff --git a/nodeodm/migrations/0006_auto_20190220_1842.py b/nodeodm/migrations/0006_auto_20190220_1842.py new file mode 100644 index 000000000..4d9cda0f9 --- /dev/null +++ b/nodeodm/migrations/0006_auto_20190220_1842.py @@ -0,0 +1,24 @@ +# Generated by Django 2.1.5 on 2019-02-20 18:42 + +import django.contrib.postgres.fields.jsonb +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('nodeodm', '0005_auto_20190115_1346'), + ] + + operations = [ + migrations.AlterField( + model_name='processingnode', + name='available_options', + field=django.contrib.postgres.fields.jsonb.JSONField(default=dict, help_text='Description of the options that can be used for processing'), + ), + migrations.AlterField( + model_name='processingnode', + name='label', + field=models.CharField(blank=True, default='', help_text='Optional label for this node. When set, this label will be shown instead of the hostname:port name.', max_length=255), + ), + ] diff --git a/nodeodm/migrations/0007_auto_20190520_1258.py b/nodeodm/migrations/0007_auto_20190520_1258.py new file mode 100644 index 000000000..1b36b748a --- /dev/null +++ b/nodeodm/migrations/0007_auto_20190520_1258.py @@ -0,0 +1,27 @@ +# Generated by Django 2.1.7 on 2019-05-20 12:58 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('nodeodm', '0006_auto_20190220_1842'), + ] + + operations = [ + migrations.RemoveField( + model_name='processingnode', + name='odm_version', + ), + migrations.AddField( + model_name='processingnode', + name='engine', + field=models.CharField(help_text='Engine used by the node.', max_length=255, null=True), + ), + migrations.AddField( + model_name='processingnode', + name='engine_version', + field=models.CharField(help_text='Engine version used by the node.', max_length=32, null=True), + ), + ] diff --git a/nodeodm/migrations/0008_rename_default_odm_node.py b/nodeodm/migrations/0008_rename_default_odm_node.py new file mode 100644 index 000000000..258c9ae74 --- /dev/null +++ b/nodeodm/migrations/0008_rename_default_odm_node.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations +from nodeodm.models import ProcessingNode + +def rename_default_node(apps, schema_editor): + for default_node in ProcessingNode.objects.filter(hostname='node-odm-1'): + default_node.hostname = 'webodm_node-odm_1' + default_node.label = 'node-odm-1' + default_node.save() + +class Migration(migrations.Migration): + + dependencies = [ + ('nodeodm', '0007_auto_20190520_1258'), + ] + + operations = [ + migrations.RunPython(rename_default_node), + ] diff --git a/nodeodm/migrations/0009_auto_20210610_1850.py b/nodeodm/migrations/0009_auto_20210610_1850.py new file mode 100644 index 000000000..dc92348ca --- /dev/null +++ b/nodeodm/migrations/0009_auto_20210610_1850.py @@ -0,0 +1,73 @@ +# Generated by Django 2.1.15 on 2021-06-10 18:50 + +import django.contrib.postgres.fields.jsonb +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('nodeodm', '0008_rename_default_odm_node'), + ] + + operations = [ + migrations.AlterModelOptions( + name='processingnode', + options={'verbose_name': 'Processing Node', 'verbose_name_plural': 'Processing Nodes'}, + ), + migrations.AlterField( + model_name='processingnode', + name='api_version', + field=models.CharField(help_text='API version used by the node', max_length=32, null=True, verbose_name='API Version'), + ), + migrations.AlterField( + model_name='processingnode', + name='available_options', + field=django.contrib.postgres.fields.jsonb.JSONField(default=dict, help_text='Description of the options that can be used for processing', verbose_name='Available Options'), + ), + migrations.AlterField( + model_name='processingnode', + name='engine', + field=models.CharField(help_text='Engine used by the node.', max_length=255, null=True, verbose_name='Engine'), + ), + migrations.AlterField( + model_name='processingnode', + name='engine_version', + field=models.CharField(help_text='Engine version used by the node.', max_length=32, null=True, verbose_name='Engine Version'), + ), + migrations.AlterField( + model_name='processingnode', + name='hostname', + field=models.CharField(help_text='Hostname or IP address where the node is located (can be an internal hostname as well). If you are using Docker, this is never 127.0.0.1 or localhost. Find the IP address of your host machine by running ifconfig on Linux or by checking your network settings.', max_length=255, verbose_name='Hostname'), + ), + migrations.AlterField( + model_name='processingnode', + name='label', + field=models.CharField(blank=True, default='', help_text='Optional label for this node. When set, this label will be shown instead of the hostname:port name.', max_length=255, verbose_name='Label'), + ), + migrations.AlterField( + model_name='processingnode', + name='last_refreshed', + field=models.DateTimeField(help_text='When was the information about this node last retrieved?', null=True, verbose_name='Last Refreshed'), + ), + migrations.AlterField( + model_name='processingnode', + name='max_images', + field=models.PositiveIntegerField(blank=True, help_text='Maximum number of images accepted by this node.', null=True, verbose_name='Max Images'), + ), + migrations.AlterField( + model_name='processingnode', + name='port', + field=models.PositiveIntegerField(help_text="Port that connects to the node's API", verbose_name='Port'), + ), + migrations.AlterField( + model_name='processingnode', + name='queue_count', + field=models.PositiveIntegerField(default=0, help_text='Number of tasks currently being processed by this node (as reported by the node itself)', verbose_name='Queue Count'), + ), + migrations.AlterField( + model_name='processingnode', + name='token', + field=models.CharField(blank=True, default='', help_text="Token to use for authentication. If the node doesn't have authentication, you can leave this field blank.", max_length=1024, verbose_name='Token'), + ), + ] diff --git a/nodeodm/migrations/0010_alter_processingnode_available_options.py b/nodeodm/migrations/0010_alter_processingnode_available_options.py new file mode 100644 index 000000000..bb5ee85dc --- /dev/null +++ b/nodeodm/migrations/0010_alter_processingnode_available_options.py @@ -0,0 +1,18 @@ +# Generated by Django 4.2.14 on 2024-08-20 05:00 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('nodeodm', '0009_auto_20210610_1850'), + ] + + operations = [ + migrations.AlterField( + model_name='processingnode', + name='available_options', + field=models.JSONField(default=dict, help_text='Description of the options that can be used for processing', verbose_name='Available Options'), + ), + ] diff --git a/webodm/settings.py b/webodm/settings.py index bb2d97824..1799b0197 100644 --- a/webodm/settings.py +++ b/webodm/settings.py @@ -146,7 +146,8 @@ 'NAME': os.environ.get('WO_DATABASE_NAME', 'webodm_dev'), 'USER': os.environ.get('WO_DATABASE_USER', 'postgres'), 'PASSWORD': os.environ.get('WO_DATABASE_PASSWORD', 'postgres'), - 'HOST': os.environ.get('WO_DATABASE_HOST', 'db'), + # 'HOST': os.environ.get('WO_DATABASE_HOST', 'db'), + 'HOST': os.environ.get('WO_DATABASE_HOST', 'localhost'), 'PORT': os.environ.get('WO_DATABASE_PORT', '5432'), } }