diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 64e6d1020..1768a8584 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,6 +16,11 @@ on: jobs: ci-job: runs-on: ubuntu-latest + + env: + DJANGO_SETTINGS_MODULE: settings.ci + DJANGO_MEDIA_ROOT: /tmp/wger-test + strategy: matrix: # Note: removed 3.14 because lingua-language-detector has no wheels yet @@ -41,22 +46,19 @@ jobs: - name: Install dependencies run: | uv sync --group dev + mkdir /tmp/wger-test # Only run the tests with coverage for one version of python - name: Test the application with coverage if: matrix.python-version == 3.13 run: | - source .venv/bin/activate - wger create-settings - coverage run --source='.' ./manage.py test - coverage lcov + uv run coverage run --source='.' ./manage.py test + uv run coverage lcov - name: Test the application if: matrix.python-version != 3.13 run: | - source .venv/bin/activate - wger create-settings - python manage.py test + uv run ./manage.py test - name: Coveralls if: matrix.python-version == 3.13 diff --git a/.gitignore b/.gitignore index 20828510b..3ec7fe7ad 100644 --- a/.gitignore +++ b/.gitignore @@ -46,7 +46,7 @@ coverage.xml # Django stuff: *.log -settings*.py +settings/*_extra.py *.sqlite /CACHE diff --git a/extras/docker/demo/Dockerfile b/extras/docker/demo/Dockerfile index 5ee073fbb..7859f744a 100644 --- a/extras/docker/demo/Dockerfile +++ b/extras/docker/demo/Dockerfile @@ -41,15 +41,18 @@ RUN wget -O- https://deb.nodesource.com/setup_22.x | bash - \ WORKDIR /home/wger/src COPY README.md pyproject.toml package.json package-lock.json package-lock.json /home/wger/src/ COPY wger/version.py wger/__init__.py /home/wger/src/wger/ -COPY wger/core/static /root/src/wger/core/static +COPY wger/core/static /home/wger/src/wger/core/static RUN npm ci --production \ && npm run build:css:sass \ && PACKAGE_NAME="@wger-project/react-components" \ && PACKAGE_VERSION=$(node -p "require('./package.json').devDependencies['$PACKAGE_NAME']") \ && TARBALL=$(npm pack ${PACKAGE_NAME}@${PACKAGE_VERSION}) \ && mkdir -p node_modules/${PACKAGE_NAME} \ - && tar -xzf ${TARBALL} -C node_modules/${PACKAGE_NAME} --strip-components=1 - + && tar -xzf ${TARBALL} -C node_modules/${PACKAGE_NAME} --strip-components=1 \ + && pip3 wheel \ + --no-cache-dir \ + --wheel-dir /wheels \ + --group docker . ######## # Final @@ -111,9 +114,8 @@ RUN --mount=type=bind,from=builder,source=/wheels,target=/wheels . /home/wger/v && pip install --upgrade pip \ && pip install --no-cache /wheels/* \ && pip install -e . \ - && wger create-settings --database-path /home/wger/db/database.sqlite \ - && sed -i "/^MEDIA_ROOT/c\MEDIA_ROOT='\/home\/wger\/media'" settings.py \ - && echo STATIC_ROOT=\'/home/wger/static\' >> settings.py \ + && sed -i "/^MEDIA_ROOT/c\MEDIA_ROOT='\/home\/wger\/media'" settings/production.py \ + && echo STATIC_ROOT=\'/home/wger/static\' >> settings/production.py \ && wger bootstrap --no-process-static \ && python3 manage.py sync-exercises \ && wger load-online-fixtures \ diff --git a/extras/docker/production/Dockerfile b/extras/docker/production/Dockerfile index bca131a52..4c056d45e 100644 --- a/extras/docker/production/Dockerfile +++ b/extras/docker/production/Dockerfile @@ -79,6 +79,7 @@ ARG BUILD_DATE ENV PATH="/home/wger/.local/bin:$PATH" ENV APP_BUILD_COMMIT=$BUILD_COMMIT ENV APP_BUILD_DATE=$BUILD_DATE +ENV DJANGO_SETTINGS_MODULE='settings.main' WORKDIR /home/wger/src EXPOSE 8000 @@ -88,8 +89,6 @@ COPY --chown=wger:wger . /home/wger/src COPY --chown=wger:wger --from=builder /root/src/node_modules /home/wger/src/node_modules COPY --chown=wger:wger --from=builder /root/src/wger/core/static/bootstrap-compiled.css /home/wger/src/wger/core/static/bootstrap-compiled.css COPY --chown=wger:wger --from=builder /root/src/wger/core/static/bootstrap-compiled.css.map /home/wger/src/wger/core/static/bootstrap-compiled.css.map -COPY ${DOCKER_DIR}/settings.py /home/wger/src -COPY ${DOCKER_DIR}/settings.py /tmp/ COPY ${DOCKER_DIR}/entrypoint.sh /home/wger/entrypoint.sh COPY ${DOCKER_DIR}/celery/start-beat /start-beat COPY ${DOCKER_DIR}/celery/start-worker /start-worker diff --git a/manage.py b/manage.py index 873291be6..720ca5c44 100755 --- a/manage.py +++ b/manage.py @@ -1,24 +1,12 @@ #!/usr/bin/env python3 # Standard Library +import os import sys # Django from django.core.management import execute_from_command_line -# wger -from wger.tasks import ( - get_path, - setup_django_environment, -) - - if __name__ == '__main__': - # If user passed the settings flag ignore the default wger settings - if not any('--settings' in s for s in sys.argv): - setup_django_environment(get_path('settings.py')) - - # Alternative to above - # os.environ.setdefault("DJANGO_SETTINGS_MODULE", "settings") - + os.environ.setdefault("DJANGO_SETTINGS_MODULE", "settings.main") execute_from_command_line(sys.argv) diff --git a/package-lock.json b/package-lock.json index 9cfec63cc..7d8141e2c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "wger", - "version": "2.4.alpha1", + "version": "2.4.alpha3", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "wger", - "version": "2.4.alpha1", + "version": "2.4.alpha3", "license": "AGPL-3.0", "dependencies": { "@popperjs/core": "^2.11.8", @@ -52,7 +52,6 @@ "integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", @@ -385,7 +384,6 @@ "integrity": "sha512-O000MLDBDdk/EohJPFUqvnp4qnHeYkVP5B0xEG0D/L7cOKP9kefu2DXn8dj74cQfsEzUqh+sr1RzFqiL1o+PpA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/runtime": "^7.18.3", "@emotion/babel-plugin": "^11.13.5", @@ -432,7 +430,6 @@ "integrity": "sha512-qEEJt42DuToa3gurlH4Qqc1kVpNq8wO8cJtDzU46TjlzWjDlsVyevtYCRijVq3SrHsROS+gVQ8Fnea108GnKzw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/runtime": "^7.18.3", "@emotion/babel-plugin": "^11.13.5", @@ -495,6 +492,7 @@ "os": [ "aix" ], + "peer": true, "engines": { "node": ">=18" } @@ -512,6 +510,7 @@ "os": [ "android" ], + "peer": true, "engines": { "node": ">=18" } @@ -529,6 +528,7 @@ "os": [ "android" ], + "peer": true, "engines": { "node": ">=18" } @@ -546,6 +546,7 @@ "os": [ "android" ], + "peer": true, "engines": { "node": ">=18" } @@ -563,6 +564,7 @@ "os": [ "darwin" ], + "peer": true, "engines": { "node": ">=18" } @@ -580,6 +582,7 @@ "os": [ "darwin" ], + "peer": true, "engines": { "node": ">=18" } @@ -597,6 +600,7 @@ "os": [ "freebsd" ], + "peer": true, "engines": { "node": ">=18" } @@ -614,6 +618,7 @@ "os": [ "freebsd" ], + "peer": true, "engines": { "node": ">=18" } @@ -631,6 +636,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">=18" } @@ -648,6 +654,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">=18" } @@ -665,6 +672,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">=18" } @@ -682,6 +690,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">=18" } @@ -699,6 +708,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">=18" } @@ -716,6 +726,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">=18" } @@ -733,6 +744,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">=18" } @@ -750,6 +762,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">=18" } @@ -767,6 +780,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">=18" } @@ -784,6 +798,7 @@ "os": [ "netbsd" ], + "peer": true, "engines": { "node": ">=18" } @@ -801,6 +816,7 @@ "os": [ "netbsd" ], + "peer": true, "engines": { "node": ">=18" } @@ -818,6 +834,7 @@ "os": [ "openbsd" ], + "peer": true, "engines": { "node": ">=18" } @@ -835,6 +852,7 @@ "os": [ "openbsd" ], + "peer": true, "engines": { "node": ">=18" } @@ -852,6 +870,7 @@ "os": [ "openharmony" ], + "peer": true, "engines": { "node": ">=18" } @@ -869,6 +888,7 @@ "os": [ "sunos" ], + "peer": true, "engines": { "node": ">=18" } @@ -886,6 +906,7 @@ "os": [ "win32" ], + "peer": true, "engines": { "node": ">=18" } @@ -903,6 +924,7 @@ "os": [ "win32" ], + "peer": true, "engines": { "node": ">=18" } @@ -920,6 +942,7 @@ "os": [ "win32" ], + "peer": true, "engines": { "node": ">=18" } @@ -1081,7 +1104,6 @@ "integrity": "sha512-R4DaYF3dgCQCUAkr4wW1w26GHXcf5rCmBRHVBuuvJvaGLmZdD8EjatP80Nz5JCw0KxORAzwftnHzXVnjR8HnFw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/runtime": "^7.28.4", "@mui/core-downloads-tracker": "^7.3.6", @@ -1195,7 +1217,6 @@ "integrity": "sha512-8fehAazkHNP1imMrdD2m2hbA9sl7Ur6jfuNweh5o4l9YPty4iaZzRXqYvBCWQNwFaSHmMEj2KPbyXGp7Bt73Rg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/runtime": "^7.28.4", "@mui/private-theming": "^7.3.6", @@ -1437,7 +1458,6 @@ "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==", "license": "MIT", - "peer": true, "funding": { "type": "opencollective", "url": "https://opencollective.com/popperjs" @@ -1500,7 +1520,8 @@ "optional": true, "os": [ "android" - ] + ], + "peer": true }, "node_modules/@rollup/rollup-android-arm64": { "version": "4.53.3", @@ -1514,7 +1535,8 @@ "optional": true, "os": [ "android" - ] + ], + "peer": true }, "node_modules/@rollup/rollup-darwin-arm64": { "version": "4.53.3", @@ -1528,7 +1550,8 @@ "optional": true, "os": [ "darwin" - ] + ], + "peer": true }, "node_modules/@rollup/rollup-darwin-x64": { "version": "4.53.3", @@ -1542,7 +1565,8 @@ "optional": true, "os": [ "darwin" - ] + ], + "peer": true }, "node_modules/@rollup/rollup-freebsd-arm64": { "version": "4.53.3", @@ -1556,7 +1580,8 @@ "optional": true, "os": [ "freebsd" - ] + ], + "peer": true }, "node_modules/@rollup/rollup-freebsd-x64": { "version": "4.53.3", @@ -1570,7 +1595,8 @@ "optional": true, "os": [ "freebsd" - ] + ], + "peer": true }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { "version": "4.53.3", @@ -1584,7 +1610,8 @@ "optional": true, "os": [ "linux" - ] + ], + "peer": true }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { "version": "4.53.3", @@ -1598,7 +1625,8 @@ "optional": true, "os": [ "linux" - ] + ], + "peer": true }, "node_modules/@rollup/rollup-linux-arm64-gnu": { "version": "4.53.3", @@ -1612,7 +1640,8 @@ "optional": true, "os": [ "linux" - ] + ], + "peer": true }, "node_modules/@rollup/rollup-linux-arm64-musl": { "version": "4.53.3", @@ -1626,7 +1655,8 @@ "optional": true, "os": [ "linux" - ] + ], + "peer": true }, "node_modules/@rollup/rollup-linux-loong64-gnu": { "version": "4.53.3", @@ -1640,7 +1670,8 @@ "optional": true, "os": [ "linux" - ] + ], + "peer": true }, "node_modules/@rollup/rollup-linux-ppc64-gnu": { "version": "4.53.3", @@ -1654,7 +1685,8 @@ "optional": true, "os": [ "linux" - ] + ], + "peer": true }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { "version": "4.53.3", @@ -1668,7 +1700,8 @@ "optional": true, "os": [ "linux" - ] + ], + "peer": true }, "node_modules/@rollup/rollup-linux-riscv64-musl": { "version": "4.53.3", @@ -1682,7 +1715,8 @@ "optional": true, "os": [ "linux" - ] + ], + "peer": true }, "node_modules/@rollup/rollup-linux-s390x-gnu": { "version": "4.53.3", @@ -1696,7 +1730,8 @@ "optional": true, "os": [ "linux" - ] + ], + "peer": true }, "node_modules/@rollup/rollup-linux-x64-gnu": { "version": "4.53.3", @@ -1710,7 +1745,8 @@ "optional": true, "os": [ "linux" - ] + ], + "peer": true }, "node_modules/@rollup/rollup-linux-x64-musl": { "version": "4.53.3", @@ -1724,7 +1760,8 @@ "optional": true, "os": [ "linux" - ] + ], + "peer": true }, "node_modules/@rollup/rollup-openharmony-arm64": { "version": "4.53.3", @@ -1738,7 +1775,8 @@ "optional": true, "os": [ "openharmony" - ] + ], + "peer": true }, "node_modules/@rollup/rollup-win32-arm64-msvc": { "version": "4.53.3", @@ -1752,7 +1790,8 @@ "optional": true, "os": [ "win32" - ] + ], + "peer": true }, "node_modules/@rollup/rollup-win32-ia32-msvc": { "version": "4.53.3", @@ -1766,7 +1805,8 @@ "optional": true, "os": [ "win32" - ] + ], + "peer": true }, "node_modules/@rollup/rollup-win32-x64-gnu": { "version": "4.53.3", @@ -1780,7 +1820,8 @@ "optional": true, "os": [ "win32" - ] + ], + "peer": true }, "node_modules/@rollup/rollup-win32-x64-msvc": { "version": "4.53.3", @@ -1794,7 +1835,8 @@ "optional": true, "os": [ "win32" - ] + ], + "peer": true }, "node_modules/@standard-schema/spec": { "version": "1.0.0", @@ -1960,7 +2002,8 @@ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/@types/hoist-non-react-statics": { "version": "3.3.7", @@ -2163,7 +2206,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", @@ -2649,6 +2691,7 @@ "dev": true, "hasInstallScript": true, "license": "MIT", + "peer": true, "bin": { "esbuild": "bin/esbuild" }, @@ -2733,6 +2776,7 @@ "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12.0.0" }, @@ -2836,6 +2880,7 @@ "os": [ "darwin" ], + "peer": true, "engines": { "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } @@ -3037,7 +3082,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "@babel/runtime": "^7.28.4" }, @@ -3135,8 +3179,7 @@ "version": "3.7.1", "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.7.1.tgz", "integrity": "sha512-m4avr8yL8kmFN8psrbFFFmB/If14iN5o9nw/NgnnM+kybDJpRsAynV2BsfpTYrTRysYUdADVD7CkUUizgkpLfg==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/js-tokens": { "version": "4.0.0", @@ -3228,7 +3271,6 @@ "integrity": "sha512-vtEhXh/gNjI9Yg1u4jX/0YVPMvxzHuGgCm6tC5kZyb08yjGWGnqAjGJvcXbqQR2P3MyMEFnRbpcdFS6PBcLqew==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12" } @@ -3305,6 +3347,7 @@ } ], "license": "MIT", + "peer": true, "bin": { "nanoid": "bin/nanoid.cjs" }, @@ -3451,6 +3494,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", @@ -3516,7 +3560,6 @@ "integrity": "sha512-ibrK8llX2a4eOskq1mXKu/TGZj9qzomO+sNfO98M6d9zIPOEhlBkMkBUBLd1vgS0gQsLDBzA+8jJBVXDnfHmJg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "scheduler": "^0.27.0" }, @@ -3598,8 +3641,7 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.2.1.tgz", "integrity": "sha512-L7BnWgRbMwzMAubQcS7sXdPdNLmKlucPlopgAzx7FtYbksWZgEWiuYM5x9T6UqS2Ne0rsgQTq5kY2SGqpzUkYA==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/react-redux": { "version": "9.2.0", @@ -3771,8 +3813,7 @@ "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz", "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/redux-thunk": { "version": "3.1.0", @@ -3835,6 +3876,7 @@ "integrity": "sha512-w8GmOxZfBmKknvdXU1sdM9NHcoQejwF/4mNgj2JuEEdRaHwwF12K7e9eXn1nLZ07ad+du76mkVsyeb2rKGllsA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@types/estree": "1.0.8" }, @@ -3928,6 +3970,7 @@ "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", "dev": true, "license": "BSD-3-Clause", + "peer": true, "engines": { "node": ">=0.10.0" } @@ -3979,6 +4022,7 @@ "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" @@ -4051,7 +4095,6 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" diff --git a/package.json b/package.json index 30f7586e9..0663b07ba 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "wger", - "version": "2.4.alpha1", + "version": "2.4.alpha3", "description": "Self hosted FLOSS fitness/workout and weight tracker", "repository": "github:wger-project/wger", "author": "wger team ", diff --git a/pyproject.toml b/pyproject.toml index 95d58ee9e..51f5f1684 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -165,7 +165,7 @@ line-ending = "auto" src_paths = ["wger", "extras"] sections = ["FUTURE", "STDLIB", "DJANGO", "THIRDPARTY", "FIRSTPARTY", "LOCALFOLDER"] -skip = ["extras", "build", "dist", "node_modules", "migrations", "docs", "settings.py", "apps.py"] +skip = ["extras", "build", "dist", "node_modules", "migrations", "docs", "settings", "apps.py"] # If set to true - ensures that if a star import is present, nothing else is # imported from that namespace. combine_star = false diff --git a/settings/README.md b/settings/README.md new file mode 100644 index 000000000..5ed92f404 --- /dev/null +++ b/settings/README.md @@ -0,0 +1,15 @@ +# Settings + +This directory contains configuration files and settings for the project. + +You can add your own configuration files here, e.g. for development. Set +the `DJANGO_SETTINGS_MODULE` environment variable to point to the new settings +file. E.g.: + +```bash +export DJANGO_SETTINGS_MODULE=settings.local_dev +python manage.py runserver +``` + +If you want to add settings that are not tracked by git, you can create a +`local_dev_extra.py` file, this will be imported by `local_dev.py` if it exists. diff --git a/settings/__init__.py b/settings/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/wger/settings.tpl b/settings/ci.py similarity index 60% rename from wger/settings.tpl rename to settings/ci.py index b57ffa858..64f552ef2 100644 --- a/wger/settings.tpl +++ b/settings/ci.py @@ -1,9 +1,26 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- +# This file is part of wger Workout Manager. +# +# wger Workout Manager is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# wger Workout Manager is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License + +# ruff: noqa: F405 + +# Third Party +import environ # wger -from wger.settings_global import * +from .settings_global import * # noqa: F403 +env = environ.Env() # Use 'DEBUG = True' to get more details for server errors DEBUG = True @@ -16,16 +33,16 @@ WGER_SETTINGS["ALLOW_UPLOAD_VIDEOS"] = False WGER_SETTINGS["MIN_ACCOUNT_AGE_TO_TRUST"] = 21 # in days WGER_SETTINGS["EXERCISE_CACHE_TTL"] = 3600 # in seconds -DATABASES = {{ - 'default': {{ - 'ENGINE': 'django.db.backends.{dbengine}', - 'NAME': '{dbname}', - 'USER': '{dbuser}', - 'PASSWORD': '{dbpassword}', - 'HOST': '{dbhost}', - 'PORT': '{dbport}', - }} -}} # yapf: disable +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.sqlite3', + 'NAME': '/Users/roland/Entwicklung/wger/server/database.sqlite', + 'USER': '', + 'PASSWORD': '', + 'HOST': '', + 'PORT': '', + } +} # yapf: disable # List of administrations ADMINS = (('Your name', 'your_email@example.com'),) @@ -39,7 +56,7 @@ MANAGERS = ADMINS TIME_ZONE = 'Europe/Berlin' # Make this unique, and don't share it with anybody. -SECRET_KEY = '{default_key}' +SECRET_KEY = '61fxc$k%9nj!be-_up9%xzm(z)9l7$h33b1!@bf9581=c-03%p' # Your reCaptcha keys RECAPTCHA_PUBLIC_KEY = '' @@ -49,14 +66,14 @@ USE_RECAPTCHA = False # The site's URL (e.g. http://www.my-local-gym.com or http://localhost:8000) # This is needed for uploaded files and images (exercise images, etc.) to be # properly served. -SITE_URL = '{siteurl}' +SITE_URL = 'http://localhost:8000' # Path to uploaded files # Absolute filesystem path to the directory that will hold user-uploaded files. -MEDIA_ROOT = '{media_folder_path}' +MEDIA_ROOT = env.str("DJANGO_MEDIA_ROOT", '/tmp/') MEDIA_URL = '/media/' -# Allow all hosts to access the application. Change if used in production. +# Allow all hosts to access the application. ALLOWED_HOSTS = [ '*', ] @@ -78,23 +95,3 @@ EMAIL_PAGE_DOMAIN = SITE_URL # https://django-axes.readthedocs.io/en/latest/ # AXES_ENABLED = False -# AXES_FAILURE_LIMIT = 10 -# AXES_COOLOFF_TIME = timedelta(minutes=30) -# AXES_HANDLER = 'axes.handlers.cache.AxesCacheHandler' - -# -# Sometimes needed if deployed behind a proxy with HTTPS enabled: -# https://docs.djangoproject.com/en/4.1/ref/csrf/ -# -# CSRF_TRUSTED_ORIGINS = ['http://127.0.0.1', 'https://my.domain.example.com'] - -# Alternative to above, needs changes to the reverse proxy's config -# https://docs.djangoproject.com/en/4.1/ref/settings/#secure-proxy-ssl-header -# -# SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO, 'https') - -# -# Celery -# Needed if you plan to use celery for background tasks -# CELERY_BROKER_URL = "redis://localhost:6379/2" -# CELERY_RESULT_BACKEND = "redis://localhost:6379/2" diff --git a/settings/local_dev.py b/settings/local_dev.py new file mode 100644 index 000000000..4e42693e2 --- /dev/null +++ b/settings/local_dev.py @@ -0,0 +1,124 @@ +"""Local development settings for wger""" + +# ruff: noqa: F405 + +# wger +from .settings_global import * # noqa: F403 + +DEBUG = True + +# List of administrators +ADMINS = (('Your name', 'your_email@example.com'),) +MANAGERS = ADMINS + +# Don't use this key in production! +SECRET_KEY = 'wger-local-development-supersecret-key-1234567890!' + +# Allow all hosts to access the application. +ALLOWED_HOSTS = ['*', ] + +EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend' + +# WGER application +WGER_SETTINGS['ALLOW_UPLOAD_VIDEOS'] = True +WGER_SETTINGS['ALLOW_GUEST_USERS'] = True +WGER_SETTINGS['ALLOW_REGISTRATION'] = True +WGER_SETTINGS['DOWNLOAD_INGREDIENTS_FROM'] = 'WGER' # or 'None' to disable +WGER_SETTINGS['EMAIL_FROM'] = 'wger Workout Manager ' +WGER_SETTINGS['EXERCISE_CACHE_TTL'] = 500 +WGER_SETTINGS['INGREDIENT_CACHE_TTL'] = 500 +WGER_SETTINGS['SYNC_EXERCISES_CELERY'] = False +WGER_SETTINGS['SYNC_EXERCISE_IMAGES_CELERY'] = True +WGER_SETTINGS['SYNC_EXERCISE_VIDEOS_CELERY'] = False +WGER_SETTINGS['SYNC_INGREDIENTS_CELERY'] = True +WGER_SETTINGS['USE_CELERY'] = False +WGER_SETTINGS['CACHE_API_EXERCISES_CELERY'] = True +WGER_SETTINGS['CACHE_API_EXERCISES_CELERY_FORCE_UPDATE'] = True +WGER_SETTINGS['ROUTINE_CACHE_TTL'] = 500 +DEFAULT_FROM_EMAIL = WGER_SETTINGS['EMAIL_FROM'] + + +# CELERY_BROKER_URL = "redis://localhost:6379/2" +# CELERY_RESULT_BACKEND = "redis://localhost:6379/2" + +CSRF_TRUSTED_ORIGINS = ['http://localhost:8000', 'http://127.0.0.1:8000'] + +EXPOSE_PROMETHEUS_METRICS = True + +COMPRESS_ENABLED = False +AXES_ENABLED = False + + +# Does not really cache anything +CACHES_DUMMY = { + "default": { + "BACKEND": "django.core.cache.backends.dummy.DummyCache", + 'TIMEOUT': 100, + } +} + +# In-memory cache, resets when the server restarts +CACHE_LOCMEM = { + 'default': { + 'BACKEND': 'django.core.cache.backends.locmem.LocMemCache', + 'LOCATION': 'wger-cache', + 'TIMEOUT': 100, + } +} + +# Redis cache +CACHE_REDIS = { + 'default': { + 'BACKEND': 'django_redis.cache.RedisCache', + 'LOCATION': 'redis://localhost:6379/1', + 'TIMEOUT': 5000, + 'OPTIONS': { + 'CLIENT_CLASS': 'django_redis.client.DefaultClient' + } + } +} + + +# CACHES = CACHE_REDIS +# CACHES = CACHE_LOCMEM +CACHES = CACHES_DUMMY + + +# Django Debug Toolbar +# INSTALLED_APPS += ['django_extensions', 'debug_toolbar'] +# MIDDLEWARE += ["debug_toolbar.middleware.DebugToolbarMiddleware", ] +# INTERNAL_IPS = ["127.0.0.1", ] + +# AUTH_PROXY_HEADER = 'HTTP_X_REMOTE_USER' +# AUTH_PROXY_USER_EMAIL_HEADER = 'HTTP_X_REMOTE_USER_EMAIL' +# AUTH_PROXY_USER_NAME_HEADER = 'HTTP_X_REMOTE_USER_NAME' +# AUTH_PROXY_TRUSTED_IPS = ['127.0.0.1', ] +# AUTH_PROXY_CREATE_UNKNOWN_USER = True + + +DBCONFIG_PG = { + 'ENGINE': 'django_prometheus.db.backends.postgresql', + 'NAME': 'wger', + 'USER': 'wger', + 'PASSWORD': 'wger', + 'HOST': 'localhost', + 'PORT': '5432', +} + + +DBCONFIG_SQLITE = { + 'ENGINE': 'django_prometheus.db.backends.sqlite3', + 'NAME': BASE_DIR.parent / 'db' / 'database.sqlite', +} + +DATABASES = { + # 'default': DBCONFIG_PG, + 'default': DBCONFIG_SQLITE, +} + + +# Import other local settings that are not in version control +try: + from .local_dev_extra import * # noqa: F403 +except ImportError: + pass diff --git a/settings/main.py b/settings/main.py new file mode 100644 index 000000000..b7b647530 --- /dev/null +++ b/settings/main.py @@ -0,0 +1,247 @@ +# This file is part of wger Workout Manager. +# +# wger Workout Manager is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# wger Workout Manager is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License + +# ruff: noqa: F405 + +# Third Party +import environ + +# wger +from .settings_global import * # noqa: F403 + +"""Main settings file for a production deployment of wger.""" + +env = environ.Env( + # set casting, default value + DJANGO_DEBUG=(bool, False) +) + +# Use 'DEBUG = True' to get more details for server errors +DEBUG = env("DJANGO_DEBUG") + +if os.environ.get('DJANGO_ADMINS'): + ADMINS = [env.tuple('DJANGO_ADMINS'), ] + MANAGERS = ADMINS + +if os.environ.get("DJANGO_DB_ENGINE"): + DATABASES = { + 'default': { + 'ENGINE': env.str("DJANGO_DB_ENGINE"), + 'NAME': env.str("DJANGO_DB_DATABASE"), + 'USER': env.str("DJANGO_DB_USER"), + 'PASSWORD': env.str("DJANGO_DB_PASSWORD"), + 'HOST': env.str("DJANGO_DB_HOST"), + 'PORT': env.int("DJANGO_DB_PORT"), + } + } +else: + DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.sqlite3', + 'NAME': env.str('DJANGO_DB_DATABASE', '/home/wger/db/database.sqlite'), + } + } + +# Timezone for this installation. Consult settings_global.py for more information +TIME_ZONE = env.str("TIME_ZONE", 'Europe/Berlin') + +# Make this unique, and don't share it with anybody. +# Generate e.g. with: python -c "import secrets; print(secrets.token_urlsafe(50))" or https://djecrety.ir/ +SECRET_KEY = env.str("SECRET_KEY", 'wger-docker-supersecret-key-1234567890!@#$%^&*(-_)') + +# Your reCaptcha keys +RECAPTCHA_PUBLIC_KEY = env.str('RECAPTCHA_PUBLIC_KEY', '') +RECAPTCHA_PRIVATE_KEY = env.str('RECAPTCHA_PRIVATE_KEY', '') + +# The site's URL (e.g. http://www.my-local-gym.com or http://localhost:8000) +# This is needed for uploaded files and images (exercise images, etc.) to be +# properly served. +SITE_URL = env.str('SITE_URL', 'http://localhost:8000') + +# Path to uploaded files +# Absolute filesystem path to the directory that will hold user-uploaded files. +MEDIA_ROOT = env.str("DJANGO_MEDIA_ROOT", '/home/wger/media') +STATIC_ROOT = env.str("DJANGO_STATIC_ROOT", '/home/wger/static') + +# If you change these, adjust nginx alias definitions as well +MEDIA_URL = env.str('MEDIA_URL', '/media/') +STATIC_URL = env.str('STATIC_URL', '/static/') + +LOGIN_REDIRECT_URL = env.str('LOGIN_REDIRECT_URL', '/') + +# Allow all hosts to access the application. Change if used in production. +ALLOWED_HOSTS = ['*', ] + +SESSION_ENGINE = "django.contrib.sessions.backends.cache" + +# Configure a real backend in production +if DEBUG: + EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend' + +if env.bool("ENABLE_EMAIL", False): + EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend' + EMAIL_HOST = env.str("EMAIL_HOST") + EMAIL_PORT = env.int("EMAIL_PORT") + EMAIL_HOST_USER = env.str("EMAIL_HOST_USER") + EMAIL_HOST_PASSWORD = env.str("EMAIL_HOST_PASSWORD") + EMAIL_USE_TLS = env.bool("EMAIL_USE_TLS", True) + EMAIL_USE_SSL = env.bool("EMAIL_USE_SSL", False) + EMAIL_TIMEOUT = 60 + +# Sender address used for sent emails +DEFAULT_FROM_EMAIL = env.str("FROM_EMAIL", "wger Workout Manager ") +WGER_SETTINGS['EMAIL_FROM'] = DEFAULT_FROM_EMAIL +SERVER_EMAIL = DEFAULT_FROM_EMAIL +EMAIL_FROM_ADDRESS = DEFAULT_FROM_EMAIL + +# Management +WGER_SETTINGS["ALLOW_GUEST_USERS"] = env.bool("ALLOW_GUEST_USERS", True) +WGER_SETTINGS["ALLOW_REGISTRATION"] = env.bool("ALLOW_REGISTRATION", True) +WGER_SETTINGS["ALLOW_UPLOAD_VIDEOS"] = env.bool("ALLOW_UPLOAD_VIDEOS", True) +WGER_SETTINGS["DOWNLOAD_INGREDIENTS_FROM"] = env.str("DOWNLOAD_INGREDIENTS_FROM", "WGER") +WGER_SETTINGS["EXERCISE_CACHE_TTL"] = env.int("EXERCISE_CACHE_TTL", 3600) +WGER_SETTINGS["MIN_ACCOUNT_AGE_TO_TRUST"] = env.int("MIN_ACCOUNT_AGE_TO_TRUST", 21) # in days +WGER_SETTINGS["SYNC_EXERCISES_CELERY"] = env.bool("SYNC_EXERCISES_CELERY", False) +WGER_SETTINGS["SYNC_EXERCISE_IMAGES_CELERY"] = env.bool("SYNC_EXERCISE_IMAGES_CELERY", False) +WGER_SETTINGS["SYNC_EXERCISE_VIDEOS_CELERY"] = env.bool("SYNC_EXERCISE_VIDEOS_CELERY", False) +WGER_SETTINGS["SYNC_INGREDIENTS_CELERY"] = env.bool("SYNC_INGREDIENTS_CELERY", False) +WGER_SETTINGS["SYNC_OFF_DAILY_DELTA_CELERY"] = env.bool("SYNC_OFF_DAILY_DELTA_CELERY", False) +WGER_SETTINGS["USE_RECAPTCHA"] = env.bool("USE_RECAPTCHA", False) +WGER_SETTINGS["USE_CELERY"] = env.bool("USE_CELERY", False) +WGER_SETTINGS["CACHE_API_EXERCISES_CELERY"] = env.bool("CACHE_API_EXERCISES_CELERY", False) +WGER_SETTINGS["CACHE_API_EXERCISES_CELERY_FORCE_UPDATE"] = env.bool("CACHE_API_EXERCISES_CELERY_FORCE_UPDATE", False) + +# +# Auth Proxy Authentication +# https://wger.readthedocs.io/en/latest/administration/auth_proxy.html +AUTH_PROXY_HEADER = env.str("AUTH_PROXY_HEADER", '') +AUTH_PROXY_TRUSTED_IPS = env.list("AUTH_PROXY_TRUSTED_IPS", default=[]) +AUTH_PROXY_CREATE_UNKNOWN_USER = env.bool("AUTH_PROXY_CREATE_UNKNOWN_USER", False) +AUTH_PROXY_USER_EMAIL_HEADER = env.str("AUTH_PROXY_USER_EMAIL_HEADER", '') +AUTH_PROXY_USER_NAME_HEADER = env.str("AUTH_PROXY_USER_NAME_HEADER", '') + +# Cache +if os.environ.get("DJANGO_CACHE_BACKEND"): + CACHES = { + 'default': { + 'BACKEND': env.str("DJANGO_CACHE_BACKEND"), + 'LOCATION': env.str("DJANGO_CACHE_LOCATION"), + 'TIMEOUT': env.int("DJANGO_CACHE_TIMEOUT"), + 'OPTIONS': { + 'CLIENT_CLASS': env.str("DJANGO_CACHE_CLIENT_CLASS") + } + } + } + + if os.environ.get('DJANGO_CACHE_CLIENT_PASSWORD'): + CACHES['default']['OPTIONS']['PASSWORD'] = env.str('DJANGO_CACHE_CLIENT_PASSWORD') + + CONNECTION_POOL_KWARGS = dict() + if "DJANGO_CACHE_CLIENT_SSL_KEYFILE" in os.environ: + CONNECTION_POOL_KWARGS['ssl_keyfile'] = env.str("DJANGO_CACHE_CLIENT_SSL_KEYFILE") + + if "DJANGO_CACHE_CLIENT_SSL_CERTFILE" in os.environ: + CONNECTION_POOL_KWARGS['ssl_certfile'] = env.str("DJANGO_CACHE_CLIENT_SSL_CERTFILE") + + if "DJANGO_CACHE_CLIENT_SSL_CERT_REQS" in os.environ: + CONNECTION_POOL_KWARGS['ssl_cert_reqs'] = env.str("DJANGO_CACHE_CLIENT_SSL_CERT_REQS") + + if "DJANGO_CACHE_CLIENT_SSL_CHECK_HOSTNAME" in os.environ: + CONNECTION_POOL_KWARGS['ssl_check_hostname'] = env.bool( + "DJANGO_CACHE_CLIENT_SSL_CHECK_HOSTNAME") + + if CONNECTION_POOL_KWARGS: + CACHES["default"]["OPTIONS"]["CONNECTION_POOL_KWARGS"] = CONNECTION_POOL_KWARGS + +# Folder for compressed CSS and JS files +COMPRESS_ROOT = STATIC_ROOT +COMPRESS_ENABLED = env.bool('COMPRESS_ENABLED', not DEBUG) + +# The site's domain as used by the email verification workflow +EMAIL_PAGE_DOMAIN = SITE_URL + +# +# Django Axes +# +AXES_ENABLED = env.bool('AXES_ENABLED', True) +AXES_LOCKOUT_PARAMETERS = env.list('AXES_LOCKOUT_PARAMETERS', default=['ip_address']) +AXES_FAILURE_LIMIT = env.int('AXES_FAILURE_LIMIT', 10) +AXES_COOLOFF_TIME = timedelta(minutes=env.float('AXES_COOLOFF_TIME', 30)) +AXES_HANDLER = env.str('AXES_HANDLER', 'axes.handlers.cache.AxesCacheHandler') +AXES_IPWARE_PROXY_COUNT = env.int('AXES_IPWARE_PROXY_COUNT', 0) +AXES_IPWARE_META_PRECEDENCE_ORDER = env.list('AXES_IPWARE_META_PRECEDENCE_ORDER', + default=['REMOTE_ADDR']) + +# +# Django Rest Framework SimpleJWT +# +SIMPLE_JWT['ACCESS_TOKEN_LIFETIME'] = timedelta(minutes=env.int("ACCESS_TOKEN_LIFETIME", 15)) +SIMPLE_JWT['REFRESH_TOKEN_LIFETIME'] = timedelta(hours=env.int("REFRESH_TOKEN_LIFETIME", 24)) +SIMPLE_JWT['SIGNING_KEY'] = env.str("SIGNING_KEY", SECRET_KEY) + +# +# https://docs.djangoproject.com/en/4.1/ref/csrf/ +# +CSRF_TRUSTED_ORIGINS = env.list( + "CSRF_TRUSTED_ORIGINS", + default=['http://127.0.0.1', 'http://localhost', 'https://localhost'], +) + +if env.bool('X_FORWARDED_PROTO_HEADER_SET', False): + SECURE_PROXY_SSL_HEADER = ( + env.str('SECURE_PROXY_SSL_HEADER', 'HTTP_X_FORWARDED_PROTO'), + 'https' + ) + +REST_FRAMEWORK['NUM_PROXIES'] = env.int('NUMBER_OF_PROXIES', 1) + +# +# Celery message queue configuration +# +CELERY_BROKER_URL = env.str("CELERY_BROKER", "redis://cache:6379/2") +CELERY_RESULT_BACKEND = env.str("CELERY_BACKEND", "redis://cache:6379/2") + +# +# Prometheus metrics +# +EXPOSE_PROMETHEUS_METRICS = env.bool('EXPOSE_PROMETHEUS_METRICS', False) +PROMETHEUS_URL_PATH = env.str('PROMETHEUS_URL_PATH', 'super-secret-path') + +# +# Logging +# +LOGGING = { + 'version': 1, + 'disable_existing_loggers': False, + 'formatters': { + 'simple': { + 'format': 'level={levelname} ts={asctime} module={module} path={pathname} line={lineno} message={message}', + 'style': '{', + }, + }, + 'handlers': { + 'console': { + 'level': 'DEBUG', + 'class': 'logging.StreamHandler', + 'formatter': 'simple' + }, + }, + 'loggers': { + '': { + 'handlers': ['console'], + 'level': env.str('LOG_LEVEL_PYTHON', 'INFO').upper(), + 'propagate': True, + }, + } +} diff --git a/wger/settings_global.py b/settings/settings_global.py similarity index 97% rename from wger/settings_global.py rename to settings/settings_global.py index e42391fb1..cae1ab5cc 100644 --- a/wger/settings_global.py +++ b/settings/settings_global.py @@ -18,20 +18,27 @@ import os import re import sys from datetime import timedelta +from pathlib import Path # wger from wger.utils.constants import DOWNLOAD_INGREDIENT_WGER from wger.version import get_version - """ This file contains the global settings that don't usually need to be changed. For a full list of options, visit: https://docs.djangoproject.com/en/dev/ref/settings/ """ -BASE_DIR = os.path.abspath(os.path.dirname(__file__)) -SITE_ROOT = os.path.realpath(os.path.dirname(__file__)) +BASE_DIR = Path(__file__).resolve().parent.parent / 'wger' +SITE_ROOT = Path(__file__).resolve().parent.parent / 'wger' + + +# Static and media files (only during development) +MEDIA_ROOT = BASE_DIR / 'media' +STATIC_ROOT = BASE_DIR / 'static' +MEDIA_URL = '/media/' +STATIC_URL = '/static/' # # Application definition @@ -40,6 +47,7 @@ SITE_ID = 1 ROOT_URLCONF = 'wger.urls' WSGI_APPLICATION = 'wger.wsgi.application' + INSTALLED_APPS = [ 'django.contrib.auth', 'django.contrib.contenttypes', @@ -437,7 +445,7 @@ else: STATIC_URL = '/static/' # -# Django compressor +# Django compressor for CSS and JS files # # The default is not DEBUG, override if needed @@ -620,3 +628,8 @@ ACTSTREAM_SETTINGS = { # Whether the application is being run regularly or during tests TESTING = len(sys.argv) > 1 and sys.argv[1] == 'test' + +# Your reCaptcha keys +RECAPTCHA_PUBLIC_KEY = '' +RECAPTCHA_PRIVATE_KEY = '' +NOCAPTCHA = True diff --git a/wger/celery_configuration.py b/wger/celery_configuration.py index 31abeb634..a17d833c1 100644 --- a/wger/celery_configuration.py +++ b/wger/celery_configuration.py @@ -21,7 +21,7 @@ import os from celery import Celery -os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'settings') +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'settings.main') app = Celery('wger') # read config from Django settings, the CELERY namespace would make celery diff --git a/wger/tasks.py b/wger/tasks.py index 9efa37f12..106328927 100644 --- a/wger/tasks.py +++ b/wger/tasks.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- - # This file is part of wger Workout Manager. # # wger Workout Manager is free software: you can redistribute it and/or modify @@ -17,7 +15,6 @@ # Standard Library import logging import os -import pathlib import sys import tempfile @@ -25,9 +22,9 @@ import tempfile import django from django.core.management import ( call_command, + color_style, execute_from_command_line, ) -from django.utils.crypto import get_random_string # Third Party import requests @@ -42,6 +39,8 @@ from tqdm import tqdm logger = logging.getLogger(__name__) FIXTURE_URL = 'https://github.com/wger-project/data/raw/master/fixtures/' +style = color_style() + @task( help={ @@ -56,8 +55,6 @@ def start(context, address='localhost', port=8000, settings_path=None, extra_arg """ Start the application using django's built in webserver """ - - # Find the path to the settings and setup the django environment setup_django_environment(settings_path) argv = ['', 'runserver', '--noreload'] @@ -70,22 +67,14 @@ def start(context, address='localhost', port=8000, settings_path=None, extra_arg @task( help={ - 'settings-path': 'Path to settings file (absolute path). Leave empty for default', - 'database-path': 'Path to sqlite database (absolute path). Leave empty for default', + 'settings-path': 'Path to settings file. Leave empty for default (settings.main)', + 'process-static': 'Whether to process static files (install npm packages and process css). Default: True', } ) -def bootstrap(context, settings_path=None, database_path=None, process_static=True): +def bootstrap(context, settings_path=None, process_static=True): """ Performs all steps necessary to bootstrap the application """ - - # Create settings if necessary - if settings_path is None: - settings_path = get_path('settings.py') - if not os.path.exists(settings_path): - create_settings(context, settings_path=settings_path, database_path=database_path) - - # Find the path to the settings and setup the django environment setup_django_environment(settings_path) # Create Database if necessary @@ -101,93 +90,11 @@ def bootstrap(context, settings_path=None, database_path=None, process_static=Tr context.run('npm run build:css:sass') -@task( - help={ - 'settings-path': 'Path to settings file (absolute path). Leave empty for default', - 'database-path': 'Path to sqlite database (absolute path). Leave empty for default', - 'database-type': 'Database type to use. Supported: sqlite3, postgresql. Default: sqlite3', - 'key-length': 'Length of the generated secret key. Default: 50', - } -) -def create_settings( - context, - settings_path=None, - database_path=None, - database_type='sqlite3', - key_length=50, -): - """ - Creates a local settings file - """ - if settings_path is None: - settings_path = get_path('settings.py') - - settings_module = os.path.dirname(settings_path) - print(f'*** Creating settings file at {settings_module}') - - if database_path is None: - database_path = get_path('database.sqlite').as_posix() - dbpath_value = database_path - - media_folder_path = get_path('media').as_posix() - - # Use localhost with default django port if no URL given - url = 'http://localhost:8000' - - # Fill in the config file template - settings_template = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'settings.tpl') - with open(settings_template, 'r') as settings_file: - settings_content = settings_file.read() - - if database_type == 'postgresql': - dbengine = 'postgresql' - dbname = 'wger' - dbuser = 'wger' - dbpassword = 'wger' - dbhost = 'localhost' - dbport = 5432 - elif database_type == 'sqlite3': - dbengine = 'sqlite3' - dbname = dbpath_value - dbuser = '' - dbpassword = '' - dbhost = '' - dbport = '' - - # Create a random SECRET_KEY to put it in the settings. - # from django.core.management.commands.startproject - secret_key = get_random_string(key_length, 'abcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*(-_=+)') - - settings_content = settings_content.format( - dbname=dbname, - dbpath=dbpath_value, - dbengine=dbengine, - dbuser=dbuser, - dbpassword=dbpassword, - dbhost=dbhost, - dbport=dbport, - default_key=secret_key, - siteurl=url, - media_folder_path=media_folder_path, - ) - - if not os.path.exists(settings_module): - os.makedirs(settings_module) - - if not os.path.exists(os.path.dirname(database_path)): - os.makedirs(os.path.dirname(database_path)) - - with open(settings_path, 'w') as settings_file: - settings_file.write(settings_content) - - @task(help={'settings-path': 'Path to settings file (absolute path). Leave empty for default'}) -def create_or_reset_admin(context, settings_path=None): +def create_or_reset_admin(context, settings_path: str = None): """ Creates an admin user or resets the password for an existing one """ - - # Find the path to the settings and setup the django environment setup_django_environment(settings_path) # can't be imported in global scope as it already requires @@ -207,25 +114,21 @@ def create_or_reset_admin(context, settings_path=None): call_command('loaddata', path + 'users.json') -@task(help={'settings-path': 'Path to settings file (absolute path). Leave empty for default'}) +@task(help={'settings-path': 'Path to settings file. Leave empty for default (settings.main)'}) def migrate_db(context, settings_path=None): """ Run all database migrations """ - - # Find the path to the settings and setup the django environment setup_django_environment(settings_path) call_command('migrate') -@task(help={'settings-path': 'Path to settings file (absolute path). Leave empty for default'}) -def load_fixtures(context, settings_path=None): +@task(help={'settings-path': 'Path to settings file. Leave empty for default (settings.main)'}) +def load_fixtures(context, settings_path: str = None): """ Loads all fixtures """ - - # Find the path to the settings and setup the django environment setup_django_environment(settings_path) # Gym @@ -257,13 +160,11 @@ def load_fixtures(context, settings_path=None): call_command('loaddata', 'gym-adminconfig.json') -@task(help={'settings-path': 'Path to settings file (absolute path). Leave empty for default'}) -def load_online_fixtures(context, settings_path=None): +@task(help={'settings-path': 'Path to settings file. Leave empty for default (settings.main)'}) +def load_online_fixtures(context, settings_path: str = None): """ Downloads fixtures from server and installs them (at the moment only ingredients) """ - - # Find the path to the settings and set up the django environment setup_django_environment(settings_path) # Prepare the download @@ -291,57 +192,28 @@ def load_online_fixtures(context, settings_path=None): os.unlink(f.name) -@task -def config_location(context): - """ - Returns the default location for the settings file and the data folder - """ - print('Default locations:') - print(f'* settings: {get_path("settings.py")}') - print(f'* media folder: {get_path("media")}') - print(f'* database path: {get_path("database.sqlite")}') - - # # # Helper functions # -# Note: these functions were originally in wger/utils/main.py but were moved -# here because of different import problems (the packaged pip-installed -# packaged has a different sys path than the local one) -# - - -def get_path(file='settings.py') -> pathlib.Path: - """ - Return the path of the given file relatively to the wger source folder - - Note: one parent is the step from e.g. some-checkout/wger/settings.py - to some-checkout/wger, the second one to get to the source folder - itself. - """ - return (pathlib.Path(__file__).parent.parent / file).resolve() - - -def setup_django_environment(settings_path): +def setup_django_environment(settings_path: str = None): """ Setup the django environment """ + if settings_path is not None: + print(f'*** Using settings from argument: {settings_path}') + os.environ['DJANGO_SETTINGS_MODULE'] = settings_path + elif os.environ.get('DJANGO_SETTINGS_MODULE') is not None: + print(f'*** Using settings from env: {os.environ.get("DJANGO_SETTINGS_MODULE")}') + else: + os.environ['DJANGO_SETTINGS_MODULE'] = 'settings.main' + print('*** No settings given, using settings.main') - # Use default settings if the user didn't specify something else - if settings_path is None: - settings_path = get_path('settings.py').as_posix() - print(f'*** No settings given, using {settings_path}') - - # Find out file path and fine name of settings and setup django - settings_file = os.path.basename(settings_path) - settings_module_name = ''.join(settings_file.split('.')[:-1]) - if '.' in settings_module_name: - print("'.' is not an allowed character in the settings-file") + # Check if we are in the wger source folder + if not os.path.isfile('manage.py'): + print(style.ERROR('Error: please run this script from the wger checkout folder')) sys.exit(1) - settings_module_dir = os.path.dirname(settings_path) - sys.path.append(settings_module_dir) - os.environ[django.conf.ENVIRONMENT_VARIABLE] = '%s' % settings_module_name + django.setup() @@ -356,12 +228,11 @@ def database_exists(): from django.db import DatabaseError try: - # TODO: Use another model, the User could be deactivated User.objects.count() except DatabaseError: return False - except ImproperlyConfigured: - print('Your settings file seems broken') + except ImproperlyConfigured as e: + print(style.ERROR('Your settings file seems broken: '), e) sys.exit(0) else: return True @@ -369,9 +240,7 @@ def database_exists(): def make_program(): ns = Collection( - start, bootstrap, - create_settings, create_or_reset_admin, migrate_db, load_fixtures, diff --git a/wger/version.py b/wger/version.py index b3079b8c1..a3e6583ad 100644 --- a/wger/version.py +++ b/wger/version.py @@ -35,7 +35,7 @@ Always use versions in the x.y.z format, without any suffixes like "beta1" or su MIN_SERVER_VERSION = Version('2.4.0-alpha2') """Minimum version of the server required to run sync commands on this server""" -VERSION = Version('2.4.0-alpha2') +VERSION = Version('2.4.0-alpha3') """Current version of the app""" diff --git a/wger/wsgi.py b/wger/wsgi.py index 66e7a914f..ee8e05834 100644 --- a/wger/wsgi.py +++ b/wger/wsgi.py @@ -21,7 +21,7 @@ import os from django.core.wsgi import get_wsgi_application -os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'settings') +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'settings.main') # This application object is used by any WSGI server configured to use this # file. This includes Django's development server, if the WSGI_APPLICATION