Merge branch 'master' into fork/justin-pinheiro/pr-trophies

# Conflicts:
#	settings/settings_global.py
This commit is contained in:
Roland Geider
2026-01-15 11:09:39 +01:00
32 changed files with 1366 additions and 1412 deletions

View File

@@ -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

View File

@@ -1,4 +1,4 @@
name: Build and push base Docker images
name: Build base Docker images
# Only build when the dockerfile has changed, otherwise scheduled on the first
# of each month

View File

@@ -1,4 +1,4 @@
name: Build and push demo Docker image
name: Build demo Docker image
# Only build when the dockerfile has changed, otherwise scheduled every two weeks
# (on the 1st and 15th every month) since it's not so important to keep this image

View File

@@ -1,5 +1,5 @@
# https://docs.docker.com/build/ci/github-actions/multi-platform/
name: Build and push production Docker images
name: Build production Docker images
on:
workflow_dispatch:

2
.gitignore vendored
View File

@@ -46,7 +46,7 @@ coverage.xml
# Django stuff:
*.log
settings*.py
settings/*_extra.py
*.sqlite
/CACHE

View File

@@ -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
@@ -101,19 +104,24 @@ USER wger
WORKDIR /home/wger/src
RUN python3 -m venv /home/wger/venv
# Change permissions of some files and folders so the apache process
# can access them.
# Configure the application
ENV PYTHONPATH=/home/wger/src
ENV DJANGO_SETTINGS_MODULE=settings.main
ENV MEDIA_ROOT=/home/wger/media
ENV STATIC_ROOT=/home/wger/static
ENV DJANGO_DB_DATABASE=/home/wger/db/database.sqlite
ENV DJANGO_CACHE_BACKEND=django.core.cache.backends.locmem.LocMemCache
# Change permissions of some files and folders so the apache process can access them.
RUN mkdir -p ~/static/CACHE ~/media \
&& ln -s /home/wger/static/CACHE /home/wger/src/CACHE \
&& chmod g+w /home/wger/static/CACHE
RUN --mount=type=bind,from=builder,source=/wheels,target=/wheels . /home/wger/venv/bin/activate \
&& 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 \
&& mkdir -p /home/wger/db \
&& . /home/wger/venv/bin/activate \
&& wger bootstrap --no-process-static \
&& python3 manage.py sync-exercises \
&& wger load-online-fixtures \

View File

@@ -79,6 +79,8 @@ ARG BUILD_DATE
ENV PATH="/home/wger/.local/bin:$PATH"
ENV APP_BUILD_COMMIT=$BUILD_COMMIT
ENV APP_BUILD_DATE=$BUILD_DATE
ENV PYTHONPATH=/home/wger/src
ENV DJANGO_SETTINGS_MODULE=settings.main
WORKDIR /home/wger/src
EXPOSE 8000
@@ -88,8 +90,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

View File

@@ -65,8 +65,9 @@ fi
# Sync ingredients
if [[ "$SYNC_INGREDIENTS_ON_STARTUP" == "True" ]];
then
echo "Syncing ingredients"
python3 manage.py sync-ingredients
echo "The option SYNC_INGREDIENTS_ON_STARTUP is not supported anymore as it needs several hours to complete."
echo "Please start the process manually with: docker compose exec web python3 manage.py sync-ingredients"
exit 1
fi
# Set the site URL

View File

@@ -1,229 +0,0 @@
#!/usr/bin/env python
# Third Party
import environ
# wger
from wger.settings_global import *
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.
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@example.com>")
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,
},
}
}

View File

@@ -1,24 +1,13 @@
#!/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)

145
package-lock.json generated
View File

@@ -1,17 +1,17 @@
{
"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",
"bootstrap": "5.3.8",
"datatables.net-bs5": "^2.3.5",
"datatables.net-bs5": "^2.3.6",
"devbridge-autocomplete": "^1.5.0",
"htmx.org": "^2.0.8",
"jquery": "^3.7.1",
@@ -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",
@@ -2461,21 +2503,19 @@
}
},
"node_modules/datatables.net": {
"version": "2.3.5",
"resolved": "https://registry.npmjs.org/datatables.net/-/datatables.net-2.3.5.tgz",
"integrity": "sha512-Qrwc+vuw8GHo42u1usWTuriNAMW0VvLPSW3j8g3GxvatiD8wS/ZGW32VAYLLfmF4Hz0C/fo2KB3xZBfcpqqVTQ==",
"license": "MIT",
"version": "2.3.6",
"resolved": "https://registry.npmjs.org/datatables.net/-/datatables.net-2.3.6.tgz",
"integrity": "sha512-xQ/dCxrjfxM0XY70wSIzakkTZ6ghERwlLmAPyCnu8Sk5cyt9YvOVyOsFNOa/BZ/lM63Q3i2YSSvp/o7GXZGsbg==",
"dependencies": {
"jquery": ">=1.7"
}
},
"node_modules/datatables.net-bs5": {
"version": "2.3.5",
"resolved": "https://registry.npmjs.org/datatables.net-bs5/-/datatables.net-bs5-2.3.5.tgz",
"integrity": "sha512-2JA2WZz1tBxdVpYAspiqI8POdqEoAZZzqp7tISKaof2P5ufBJb+OLaahxwuB0sF9qcQh1azlU+JH1zsLBXVwXg==",
"license": "MIT",
"version": "2.3.6",
"resolved": "https://registry.npmjs.org/datatables.net-bs5/-/datatables.net-bs5-2.3.6.tgz",
"integrity": "sha512-oUNGjZrpNC2fY3l/6V4ijTC9kyVKU4Raons+RFmq2J7590rPn0c+5WAYKBx0evgW/CW7WfhStGBrU7+WJig6Og==",
"dependencies": {
"datatables.net": "2.3.5",
"datatables.net": "2.3.6",
"jquery": ">=1.7"
}
},
@@ -2649,6 +2689,7 @@
"dev": true,
"hasInstallScript": true,
"license": "MIT",
"peer": true,
"bin": {
"esbuild": "bin/esbuild"
},
@@ -2733,6 +2774,7 @@
"integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
"dev": true,
"license": "MIT",
"peer": true,
"engines": {
"node": ">=12.0.0"
},
@@ -2836,6 +2878,7 @@
"os": [
"darwin"
],
"peer": true,
"engines": {
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
}
@@ -3037,7 +3080,6 @@
}
],
"license": "MIT",
"peer": true,
"dependencies": {
"@babel/runtime": "^7.28.4"
},
@@ -3135,8 +3177,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 +3269,6 @@
"integrity": "sha512-vtEhXh/gNjI9Yg1u4jX/0YVPMvxzHuGgCm6tC5kZyb08yjGWGnqAjGJvcXbqQR2P3MyMEFnRbpcdFS6PBcLqew==",
"dev": true,
"license": "MIT",
"peer": true,
"engines": {
"node": ">=12"
}
@@ -3305,6 +3345,7 @@
}
],
"license": "MIT",
"peer": true,
"bin": {
"nanoid": "bin/nanoid.cjs"
},
@@ -3451,6 +3492,7 @@
}
],
"license": "MIT",
"peer": true,
"dependencies": {
"nanoid": "^3.3.11",
"picocolors": "^1.1.1",
@@ -3516,7 +3558,6 @@
"integrity": "sha512-ibrK8llX2a4eOskq1mXKu/TGZj9qzomO+sNfO98M6d9zIPOEhlBkMkBUBLd1vgS0gQsLDBzA+8jJBVXDnfHmJg==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"scheduler": "^0.27.0"
},
@@ -3598,8 +3639,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 +3811,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 +3874,7 @@
"integrity": "sha512-w8GmOxZfBmKknvdXU1sdM9NHcoQejwF/4mNgj2JuEEdRaHwwF12K7e9eXn1nLZ07ad+du76mkVsyeb2rKGllsA==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@types/estree": "1.0.8"
},
@@ -3928,6 +3968,7 @@
"integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
"dev": true,
"license": "BSD-3-Clause",
"peer": true,
"engines": {
"node": ">=0.10.0"
}
@@ -3979,6 +4020,7 @@
"integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"fdir": "^6.5.0",
"picomatch": "^4.0.3"
@@ -4051,7 +4093,6 @@
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
"dev": true,
"license": "Apache-2.0",
"peer": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"

View File

@@ -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 <hello@wger.de>",
@@ -12,7 +12,7 @@
"dependencies": {
"@popperjs/core": "^2.11.8",
"bootstrap": "5.3.8",
"datatables.net-bs5": "^2.3.5",
"datatables.net-bs5": "^2.3.6",
"devbridge-autocomplete": "^1.5.0",
"htmx.org": "^2.0.8",
"jquery": "^3.7.1",

View File

@@ -29,12 +29,11 @@ classifiers = [
dependencies = [
"bleach[css]~=6.3",
"celery[redis]~=5.5.3",
"crispy-bootstrap5==2025.6",
"celery[redis]~=5.6.0",
"crispy-bootstrap5~=2025.6",
"django-activity-stream~=2.0.0",
"django-axes[ipware]~=8.0.0",
"django-bootstrap-breadcrumbs2==1.0.0",
"django-compressor~=4.6.0",
"django-cors-headers~=4.9.0",
"django-crispy-forms~=2.5",
"django-email-verification~=0.3.3",
@@ -44,7 +43,7 @@ dependencies = [
"django-prometheus~=2.4.1",
"django-recaptcha~=4.1.0",
"django-redis~=6.0.0",
"django-simple-history~=3.10.1",
"django-simple-history~=3.11.0",
"django-sortedm2m~=4.0.0",
"django-storages~=1.14.6",
"djangorestframework-simplejwt[crypto]~=5.5.1",
@@ -61,20 +60,20 @@ dependencies = [
"openfoodfacts~=3.3.0",
"packaging~=25.0",
"pillow~=12.0.0",
"psycopg~=3.2.12",
"psycopg~=3.3.2",
"reportlab~=4.4.5",
"requests~=2.32.5",
"tqdm~=4.67.1",
"tzdata~=2025.2",
"tzdata~=2025.3",
]
[dependency-groups]
dev = [
"coverage~=7.12.0",
"coverage~=7.13.0",
"django-debug-toolbar~=6.1.0",
"django-extensions~=4.1",
"faker~=38.2.0",
"hatchling~=1.27.0",
"hatchling~=1.28.0",
"isort~=7.0.0",
"ruff~=0.14.2",
"tblib~=3.2.0",
@@ -165,7 +164,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

15
settings/README.md Normal file
View File

@@ -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.

0
settings/__init__.py Normal file
View File

View File

@@ -1,31 +1,48 @@
#!/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
# Application settings
WGER_SETTINGS['EMAIL_FROM'] = 'wger Workout Manager <wger@example.com>'
WGER_SETTINGS["ALLOW_REGISTRATION"] = True
WGER_SETTINGS["ALLOW_GUEST_USERS"] = True
WGER_SETTINGS["ALLOW_UPLOAD_VIDEOS"] = False
WGER_SETTINGS["MIN_ACCOUNT_AGE_TO_TRUST"] = 21 # in days
WGER_SETTINGS["EXERCISE_CACHE_TTL"] = 3600 # in seconds
WGER_SETTINGS['ALLOW_REGISTRATION'] = True
WGER_SETTINGS['ALLOW_GUEST_USERS'] = True
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"

125
settings/local_dev.py Normal file
View File

@@ -0,0 +1,125 @@
"""Local development settings for wger"""
# ruff: noqa: F405
# ruff: noqa: F403
# wger
from .settings_global import *
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@example.com>'
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 *
except ImportError:
pass

277
settings/main.py Normal file
View File

@@ -0,0 +1,277 @@
# 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.
For a more commented version of the options used here, please refer to
https://github.com/wger-project/docker/blob/master/config/prod.env
"""
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@example.com>')
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', 300),
'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
#
# Django Compressor
# Consult https://django-compressor.readthedocs.io/en/stable/ for more information
# (specially the offline compression part)
#
COMPRESS_ROOT = STATIC_ROOT
COMPRESS_ENABLED = env.bool('COMPRESS_ENABLED', not DEBUG)
COMPRESS_OFFLINE = env.bool('COMPRESS_OFFLINE', False)
# 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,
},
},
}
#
# Storage options
#
STORAGES = {
'default': {
'BACKEND': env.str(
'DJANGO_STORAGES_DEFAULT_BACKEND', 'django.core.files.storage.FileSystemStorage'
),
},
# django.contrib.staticfiles.storage.StaticFilesStorage
'staticfiles': {
'BACKEND': env.str(
'DJANGO_STORAGES_STATICFILES_BACKEND',
'wger.core.storage.LenientManifestStaticFilesStorage',
),
},
}

View File

@@ -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.parent / 'media'
STATIC_ROOT = BASE_DIR.parent / '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',
@@ -48,10 +56,8 @@ INSTALLED_APPS = [
'django.contrib.sites',
'django.contrib.staticfiles',
'storages',
# Uncomment the next line to enable the admin:
# 'django.contrib.admin',
# Apps from wger proper
'wger.config',
'wger.core',
@@ -69,20 +75,13 @@ INSTALLED_APPS = [
# reCaptcha support, see https://github.com/praekelt/django-recaptcha
'django_recaptcha',
# The sitemaps app
'django.contrib.sitemaps',
# thumbnails
'easy_thumbnails',
# CSS/JS compressor
'compressor',
# Form renderer helper
'crispy_forms',
'crispy_bootstrap5',
# REST-API
'rest_framework',
'rest_framework.authtoken',
@@ -90,28 +89,20 @@ INSTALLED_APPS = [
'rest_framework_simplejwt',
'drf_spectacular',
'drf_spectacular_sidecar',
# Breadcrumbs
'django_bootstrap_breadcrumbs',
# CORS
'corsheaders',
# Django Axes
'axes',
# History keeping
'simple_history',
# Django email verification
'django_email_verification',
# Activity stream
'actstream',
# Fontawesome
'fontawesomefree',
# Prometheus
'django_prometheus',
]
@@ -119,43 +110,33 @@ INSTALLED_APPS = [
MIDDLEWARE = [
# Prometheus
'django_prometheus.middleware.PrometheusBeforeMiddleware',
'corsheaders.middleware.CorsMiddleware',
'django.middleware.common.CommonMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
# Django Admin
'django.contrib.auth.middleware.AuthenticationMiddleware',
# Auth proxy middleware
'wger.core.middleware.AuthProxyHeaderMiddleware',
# Javascript Header. Sends helper headers for AJAX
'wger.utils.middleware.JavascriptAJAXRedirectionMiddleware',
# Custom authentication middleware. Creates users on-the-fly for certain paths
'wger.utils.middleware.WgerAuthenticationMiddleware',
# Send an appropriate Header so search engines don't index pages
'wger.utils.middleware.RobotsExclusionMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
'django.middleware.locale.LocaleMiddleware',
# History keeping
'simple_history.middleware.HistoryRequestMiddleware',
# Prometheus
'django_prometheus.middleware.PrometheusAfterMiddleware',
# Django Axes
'axes.middleware.AxesMiddleware', # should be the last one in the list
]
AUTHENTICATION_BACKENDS = (
'axes.backends.AxesStandaloneBackend', # should be the first one in the list
'wger.core.backends.AuthProxyUserBackend',
'django.contrib.auth.backends.ModelBackend',
'wger.utils.helpers.EmailAuthBackend',
@@ -167,7 +148,6 @@ TEMPLATES = [
'OPTIONS': {
'context_processors': [
'wger.utils.context_processor.processor',
# Django
'django.contrib.auth.context_processors.auth',
'django.template.context_processors.debug',
@@ -176,15 +156,14 @@ TEMPLATES = [
'django.template.context_processors.static',
'django.template.context_processors.tz',
'django.contrib.messages.context_processors.messages',
# Breadcrumbs
'django.template.context_processors.request'
'django.template.context_processors.request',
],
'loaders': [
'django.template.loaders.filesystem.Loader',
'django.template.loaders.app_directories.Loader',
],
'debug': False
'debug': False,
},
},
]
@@ -192,18 +171,15 @@ TEMPLATES = [
# Store the user messages in the session
MESSAGE_STORAGE = 'django.contrib.messages.storage.session.SessionStorage'
# Static files
# https://docs.djangoproject.com/en/6.0/ref/contrib/staticfiles/
STATICFILES_FINDERS = (
'django.contrib.staticfiles.finders.FileSystemFinder',
'django.contrib.staticfiles.finders.AppDirectoriesFinder',
# Django compressor
'compressor.finders.CompressorFinder',
)
# Additional places to copy to static files
STATICFILES_DIRS = (
('node', os.path.join(BASE_DIR, '..', 'node_modules')),
)
STATICFILES_DIRS = (('node', os.path.join(BASE_DIR, '..', 'node_modules')),)
#
# Email
@@ -295,23 +271,17 @@ LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'formatters': {
'simple': {
'format': '%(levelname)s %(asctime)s %(module)s %(message)s'
},
'simple': {'format': '%(levelname)s %(asctime)s %(module)s %(message)s'},
},
'handlers': {
'console': {
'level': 'DEBUG',
'class': 'logging.StreamHandler',
'formatter': 'simple'
},
'console': {'level': 'DEBUG', 'class': 'logging.StreamHandler', 'formatter': 'simple'},
},
'loggers': {
'wger': {
'handlers': ['console'],
'level': 'INFO',
},
}
},
}
#
@@ -350,7 +320,7 @@ AXES_CACHE = 'default'
#
# Django Crispy Templates
#
CRISPY_ALLOWED_TEMPLATE_PACKS = "bootstrap5"
CRISPY_ALLOWED_TEMPLATE_PACKS = 'bootstrap5'
CRISPY_TEMPLATE_PACK = 'bootstrap5'
#
@@ -358,47 +328,19 @@ CRISPY_TEMPLATE_PACK = 'bootstrap5'
#
THUMBNAIL_ALIASES = {
'': {
'micro': {
'size': (30, 30)
},
'micro_cropped': {
'size': (30, 30),
'crop': 'smart'
},
'thumbnail': {
'size': (80, 80)
},
'thumbnail_cropped': {
'size': (80, 80),
'crop': 'smart'
},
'small': {
'size': (200, 200)
},
'small_cropped': {
'size': (200, 200),
'crop': 'smart'
},
'medium': {
'size': (400, 400)
},
'medium_cropped': {
'size': (400, 400),
'crop': 'smart'
},
'large': {
'size': (800, 800),
'quality': 90
},
'large_cropped': {
'size': (800, 800),
'crop': 'smart',
'quality': 90
},
'micro': {'size': (30, 30)},
'micro_cropped': {'size': (30, 30), 'crop': 'smart'},
'thumbnail': {'size': (80, 80)},
'thumbnail_cropped': {'size': (80, 80), 'crop': 'smart'},
'small': {'size': (200, 200)},
'small_cropped': {'size': (200, 200), 'crop': 'smart'},
'medium': {'size': (400, 400)},
'medium_cropped': {'size': (400, 400), 'crop': 'smart'},
'large': {'size': (800, 800), 'quality': 90},
'large_cropped': {'size': (800, 800), 'crop': 'smart', 'quality': 90},
},
}
STATIC_ROOT = ''
USE_S3 = os.getenv('USE_S3') == 'TRUE'
if USE_S3:
@@ -413,49 +355,14 @@ if USE_S3:
AWS_LOCATION = 'static'
STATIC_URL = 'https://%s/%s/' % (AWS_S3_CUSTOM_DOMAIN, AWS_LOCATION)
STATICFILES_STORAGE = 'storages.backends.s3boto3.S3Boto3Storage'
COMPRESS_URL = STATIC_URL
COMPRESS_STORAGE = 'storages.backends.s3boto3.S3Boto3Storage'
COMPRESS_OFFLINE = True
COMPRESS_OFFLINE_CONTEXT = [
{
'request': {
'user_agent': {
'is_mobile': True
}
},
'STATIC_URL': STATIC_URL
}, {
'request': {
'user_agent': {
'is_mobile': False
}
},
'STATIC_URL': STATIC_URL
}
]
else:
STATIC_URL = '/static/'
#
# Django compressor
#
# The default is not DEBUG, override if needed
# COMPRESS_ENABLED = True
COMPRESS_CSS_FILTERS = (
'compressor.filters.css_default.CssAbsoluteFilter',
'compressor.filters.cssmin.rCSSMinFilter',
)
COMPRESS_JS_FILTERS = [
'compressor.filters.jsmin.JSMinFilter',
'compressor.filters.template.TemplateFilter',
]
COMPRESS_ROOT = STATIC_ROOT
#
# Django Rest Framework
# https://www.django-rest-framework.org/
#
# yapf: disable
REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': ('wger.utils.permissions.WgerPermission',),
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.LimitOffsetPagination',
@@ -472,16 +379,14 @@ REST_FRAMEWORK = {
'rest_framework.filters.OrderingFilter',
),
'DEFAULT_THROTTLE_CLASSES': ['rest_framework.throttling.ScopedRateThrottle'],
'DEFAULT_THROTTLE_RATES': {
'login': '10/min',
'registration': '5/min'
},
'DEFAULT_THROTTLE_RATES': {'login': '10/min', 'registration': '5/min'},
'DEFAULT_SCHEMA_CLASS': 'drf_spectacular.openapi.AutoSchema',
}
# yapf: enable
# Api docs
# yapf: disable
#
# API docs
# https://drf-spectacular.readthedocs.io/en/latest/
#
SPECTACULAR_SETTINGS = {
'TITLE': 'wger',
'SERVERS': [
@@ -495,9 +400,8 @@ SPECTACULAR_SETTINGS = {
'SWAGGER_UI_DIST': 'SIDECAR',
'SWAGGER_UI_FAVICON_HREF': 'SIDECAR',
'REDOC_DIST': 'SIDECAR',
'COMPONENT_SPLIT_REQUEST': True
'COMPONENT_SPLIT_REQUEST': True,
}
# yapf: enable
#
# Django Rest Framework SimpleJWT
@@ -569,7 +473,6 @@ WGER_SETTINGS = {
'USE_CELERY': False,
'USE_RECAPTCHA': False,
'WGER_INSTANCE': 'https://wger.de',
# Trophy system settings
'TROPHIES_ENABLED': True, # Global toggle to enable/disable trophy system
'TROPHIES_INACTIVE_USER_DAYS': 30, # Days of inactivity before skipping trophy evaluation
@@ -620,3 +523,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

1026
uv.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -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

55
wger/core/storage.py Normal file
View File

@@ -0,0 +1,55 @@
# 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
# Django
from django.contrib.staticfiles.storage import ManifestStaticFilesStorage
class LenientManifestStaticFilesStorage(ManifestStaticFilesStorage):
"""
Like ManifestStaticFilesStorage but does not raise when an entry or target file
referenced from CSS/JS is missing and simply returns the original name in that case.
This is needed for some node packages that reference files not present (e.g. source maps).
"""
def stored_name(self, name):
try:
return super().stored_name(name)
except Exception:
# If the manifest lookup or hashing fails (missing .map etc.), fall back
# to the original reference so post-processing doesn't raise.
print(
f"Can't find name for static file reference: {name}. "
f'This is expected in some node packages.'
)
return name
def url_converter(self, name, hashed_files, template=None):
# Wrap the base converter to ignore any errors during conversion and
# return the original matched text unchanged.
base_converter = super().url_converter(name, hashed_files, template)
def converter(matchobj):
try:
return base_converter(matchobj)
except Exception:
print(
f"Can't find name for static file reference: {matchobj.group(0)}. "
f'This is expected in some node packages.'
)
return matchobj.groupdict().get('matched', matchobj.group(0))
return converter

View File

@@ -16,7 +16,7 @@
along with Workout Manager. If not, see <http://www.gnu.org/licenses/>.
-->
{% load i18n static wger_extras compress django_bootstrap_breadcrumbs %}
{% load i18n static wger_extras django_bootstrap_breadcrumbs %}
{% block breadcrumbs %}
{% clear_breadcrumbs %}
{% endblock %}
@@ -27,6 +27,8 @@
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" href="{% static 'images/favicon.png' %}" type="image/png">
<!-- OpenGraph -->
{% block opengraph %}
<meta property="og:url" content="{{ request_absolute_path }}">
@@ -44,48 +46,28 @@
{% endif %}
{% endblock %}
<link rel="stylesheet" type="text/css" href="{% static 'css/workout-manager.css' %}">
<link rel="stylesheet" type="text/css" href="{% static 'bootstrap-compiled.css' %}">
<link rel="stylesheet" type="text/css" href="{% static 'css/bootstrap-custom.css' %}">
<link rel="stylesheet" type="text/css" href="{% static 'fontawesomefree/css/all.min.css' %}">
<link rel="stylesheet" type="text/css" href="{% static 'node/@wger-project/react-components/build/assets/index.css' %}" id="react-css">
<link rel="stylesheet" type="text/css" href="{% static 'css/language-menu.css' %}">
{% compress css %}
<link rel="stylesheet" type="text/css" href="{% static 'css/workout-manager.css' %}">
<link rel="stylesheet" type="text/css" href="{% static 'bootstrap-compiled.css' %}">
<link rel="stylesheet" type="text/css" href="{% static 'css/bootstrap-custom.css' %}">
<link rel="stylesheet" type="text/css" href="{% static 'fontawesomefree/css/all.min.css' %}">
<link rel="stylesheet" type="text/css" href="{% static 'node/@wger-project/react-components/build/assets/index.css' %}" id="react-css">
{% endcompress %}
<link rel="stylesheet" href="{% static 'css/language-menu.css' %}">
<link rel="icon" href="{% static 'images/favicon.png' %}" type="image/png">
{% compress js %}
<script src="{% static 'node/jquery/dist/jquery.js' %}"></script>
<script src="{% static 'node/bootstrap/dist/js/bootstrap.bundle.min.js' %}"></script>
<script src="{% static 'node/htmx.org/dist/htmx.min.js' %}"></script>
<script src="{% static 'node/@popperjs/core/dist/umd/popper.js' %}"></script>
<script src="{% static 'node/devbridge-autocomplete/dist/jquery.autocomplete.min.js' %}">
</script>
<script src="{% static 'node/masonry-layout/dist/masonry.pkgd.min.js' %}"></script>
<script src="{% static 'js/nutrition.js' %}"></script>
{% endcompress %}
{# this needs to be outside of the compress block! #}
<script src="{% static 'node/jquery/dist/jquery.js' %}"></script>
<script src="{% static 'node/bootstrap/dist/js/bootstrap.bundle.min.js' %}"></script>
<script src="{% static 'node/htmx.org/dist/htmx.min.js' %}"></script>
<script src="{% static 'node/@popperjs/core/dist/umd/popper.js' %}"></script>
<script src="{% static 'node/devbridge-autocomplete/dist/jquery.autocomplete.min.js' %}"></script>
<script src="{% static 'node/masonry-layout/dist/masonry.pkgd.min.js' %}"></script>
<script src="{% static 'js/nutrition.js' %}"></script>
<script src="{% static 'node/@wger-project/react-components/build/main.js' %}" type="module" defer="defer"></script>
{% block header %}{% endblock %}
<script>
$(document).ready(function () {
if (typeof wgerCustomPageInit !== "undefined") {
wgerCustomPageInit();
}
});
</script>
<title>{% block title %}{% endblock %}</title>
</head>
<body>
{# #}
{# Navigation #}
{# #}

View File

@@ -17,14 +17,14 @@
-->
{% load i18n static compress %}
{% load i18n static %}
<html lang="{{ language.short_name }}">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<meta charset="utf-8">
<meta name="author" content="wger team">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" href="{% static 'images/favicon.png' %}" type="image/png">
<!-- OpenGraph -->
{% block opengraph %}
@@ -43,19 +43,12 @@
{% endif %}
{% endblock %}
<link rel="stylesheet" type="text/css" href="{% static 'bootstrap-compiled.css' %}">
<link rel="stylesheet" type="text/css" href="{% static 'fontawesomefree/css/all.css' %}">
<link rel="stylesheet" type="text/css" href="{% static 'css/landing_page.css' %}">
{% compress css %}
<link rel="stylesheet" type="text/css" href="{% static 'bootstrap-compiled.css' %}">
<link rel="stylesheet" type="text/css" href="{% static 'fontawesomefree/css/all.css' %}">
<link rel="stylesheet" type="text/css" href="{% static 'css/landing_page.css' %}">
{% endcompress %}
<link rel="icon" href="{% static 'images/favicon.png' %}" type="image/png">
{% compress js %}
<script src="{% static 'node/bootstrap/dist/js/bootstrap.bundle.min.js' %}"></script>
<script src="{% static 'node/@popperjs/core/dist/umd/popper.js' %}"></script>
{% endcompress %}
<script src="{% static 'node/bootstrap/dist/js/bootstrap.bundle.min.js' %}"></script>
<script src="{% static 'node/@popperjs/core/dist/umd/popper.js' %}"></script>
<title>wger Workout Manager - {% translate "Features" %}</title>
</head>

View File

@@ -16,7 +16,7 @@
along with Workout Manager. If not, see <http://www.gnu.org/licenses/>.
-->
{% load i18n static wger_extras compress django_bootstrap_breadcrumbs %}
{% load i18n static wger_extras django_bootstrap_breadcrumbs %}
<html lang="de">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>

View File

@@ -9,8 +9,8 @@ msgstr ""
"Project-Id-Version: wger Workout Manager\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-10-01 16:48+0530\n"
"PO-Revision-Date: 2023-06-24 20:53+0000\n"
"Last-Translator: SlyBat <aw9gm3@gmail.com>\n"
"PO-Revision-Date: 2025-12-31 21:36+0000\n"
"Last-Translator: MR <msrhy131@gmail.com>\n"
"Language-Team: Arabic (Saudi Arabia) <https://hosted.weblate.org/projects/"
"wger/web/ar_SA/>\n"
"Language: ar_SA\n"
@@ -19,7 +19,7 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=6; plural=n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 "
"&& n%100<=10 ? 3 : n%100>=11 ? 4 : 5;\n"
"X-Generator: Weblate 4.18.1\n"
"X-Generator: Weblate 5.15.1\n"
#: config/models/gym_config.py:46 gym/templates/gym/list.html:56
msgid "Default gym"
@@ -148,6 +148,8 @@ msgstr "الرمز غير صحيح"
#: core/forms.py:271 core/forms.py:346
msgid "The form is secured with reCAPTCHA"
msgstr ""
"يتم تامين النموذج مع استخدام خدمه تمييز البشر عن ريبوت في الفحص للدخول الى "
"اي موقع او اي شيء يحتاج الى تمييزك كانسان"
#: core/forms.py:287 core/forms.py:309 core/templates/navigation.html:272
#: core/templates/navigation.html:285 core/templates/template.html:150
@@ -182,10 +184,8 @@ msgid "Language full name"
msgstr "الاسم الكامل للغة"
#: core/models/language.py:45 core/templates/language/view.html:23
#, fuzzy
#| msgid "Language full name"
msgid "Language full name in English"
msgstr "الاسم الكامل للغة"
msgstr "الاسم الكامل للغة الانكليزية"
#: core/models/license.py:29
msgid "Full name"
@@ -196,6 +196,8 @@ msgid ""
"If a license has been localized, e.g. the Creative Commons licenses for the "
"different countries, add them as separate entries here."
msgstr ""
"إذا تم توطين الترخيص، على سبيل المثال تراخيص المشاع الإبداعي لمختلف البلدان، "
"أضفها كإدخالات منفصلة هنا."
#: core/models/license.py:40
msgid "Short name, e.g. CC-BY-SA 3"
@@ -207,7 +209,7 @@ msgstr "الرابط"
#: core/models/license.py:46
msgid "Link to license text or other information"
msgstr ""
msgstr "رابط لنص الترخيص أو معلومات أخرى"
#: core/models/profile.py:57
#, python-format
@@ -248,7 +250,7 @@ msgstr "اظهار ملاحظات التدريب"
#: core/models/profile.py:120
msgid "Check to show exercise comments on the workout view"
msgstr ""
msgstr "تحقق لإظهار تعليقات التمرين على عرض التمرين"
#: core/models/profile.py:130
msgid "Also use ingredients in English"
@@ -261,28 +263,39 @@ msgid ""
"by the US Department of Agriculture. It is extremely complete, with around\n"
"7000 entries, but can be somewhat overwhelming and make the search difficult."
msgstr ""
"تحقق أيضا لإظهار المكونات باللغة الإنجليزية أثناء الإنشاء\n"
"\n"
"خطة غذائية. يتم استخراج هذه المكونات من قائمة مقدمة\n"
"\n"
"من قبل وزارة الزراعة الأمريكية. إنه كامل للغاية ، مع حول\n"
"\n"
"7000 إدخالات، ولكن يمكن أن تكون ساحقة إلى حد ما وجعل البحث صعبا."
#: core/models/profile.py:141
msgid "Activate workout reminders"
msgstr ""
msgstr "تفعيل تذكيرات التمرين"
#: core/models/profile.py:143
msgid ""
"Check to activate automatic reminders for workouts. You need to provide a "
"valid email for this to work."
msgstr ""
"تحقق لتفعيل التذكيرات التلقائية للتدريبات. تحتاج إلى تقديم بريد إلكتروني "
"صالح حتى يعمل هذا."
#: core/models/profile.py:152
msgid "Remind before expiration"
msgstr ""
msgstr "تذكير قبل انتهاء الصلاحية"
#: core/models/profile.py:153
msgid "The number of days you want to be reminded before a workout expires."
msgstr ""
msgstr "عدد الأيام التي تريد أن يتم تذكيرك بها قبل انتهاء التمرين."
#: core/models/profile.py:159
msgid "Default duration of workouts"
msgstr ""
"المدة الافتراضية للتدريبات\n"
"Arabic (Sudish)"
#: core/models/profile.py:161
msgid ""
@@ -302,32 +315,32 @@ msgstr ""
#: core/models/profile.py:214 gym/views/export.py:64
msgid "Age"
msgstr ""
msgstr "العمر"
#: core/models/profile.py:230 nutrition/forms.py:117
msgid "Height (cm)"
msgstr ""
msgstr "الطول (سنتيمتر)"
#: core/models/profile.py:247
msgid "Hours of sleep"
msgstr ""
msgstr "ساعات النMم"
#: core/models/profile.py:248
msgid "The average hours of sleep per day"
msgstr ""
msgstr "معدل ساعات النوم خلال اليوم"
#: core/models/profile.py:257
msgid "Work"
msgstr ""
msgstr "العمل"
#: core/models/profile.py:258 core/models/profile.py:300
msgid "Average hours per day"
msgstr ""
msgstr "معدل الساعات خلال اليوم"
#: core/models/profile.py:267 core/models/profile.py:288
#: core/models/profile.py:309
msgid "Physical intensity"
msgstr ""
msgstr "الكثافة البدنية"
#: core/models/profile.py:268 core/models/profile.py:289
#: core/models/profile.py:310

View File

@@ -22,15 +22,15 @@ msgstr ""
"Project-Id-Version: wger Workout Manager\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-10-01 16:48+0530\n"
"PO-Revision-Date: 2025-10-24 12:54+0000\n"
"Last-Translator: Paul Bonneau <bonneau-paul@hotmail.fr>\n"
"PO-Revision-Date: 2025-12-20 11:00+0000\n"
"Last-Translator: Justin Pinheiro <justin.pinheiro.pro@gmail.com>\n"
"Language-Team: French <https://hosted.weblate.org/projects/wger/web/fr/>\n"
"Language: fr\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n > 1;\n"
"X-Generator: Weblate 5.14.1-dev\n"
"X-Generator: Weblate 5.15.1\n"
#: config/models/gym_config.py:46 gym/templates/gym/list.html:56
msgid "Default gym"
@@ -41,9 +41,9 @@ msgid ""
"Select the default gym for this installation. This will assign all new "
"registered users to this gym and update all existing users without a gym."
msgstr ""
"Sélectionner l'activité par défaut pour cette installation. Celle-ci sera "
"assigner à chaque utilisateur non enregistré ainsi qu'aux utilisateurs sans "
"activités."
"Sélectionner la salle de sport par défaut pour cette installation. Celle-ci "
"sera assignée à chaque utilisateur non enregistré ainsi qu'aux utilisateurs "
"sans salle de sport."
#: config/views/gym_config.py:42 core/templates/language/view.html:64
#: core/templates/license/list.html:17

View File

@@ -15,7 +15,7 @@ msgstr ""
"Project-Id-Version: wger Workout Manager\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-10-01 16:48+0530\n"
"PO-Revision-Date: 2025-12-13 15:00+0000\n"
"PO-Revision-Date: 2025-12-20 11:00+0000\n"
"Last-Translator: Floris C <floris.contactmail@gmail.com>\n"
"Language-Team: Dutch <https://hosted.weblate.org/projects/wger/web/nl/>\n"
"Language: nl\n"
@@ -23,7 +23,7 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Weblate 5.15-dev\n"
"X-Generator: Weblate 5.15.1\n"
#: config/models/gym_config.py:46 gym/templates/gym/list.html:56
msgid "Default gym"
@@ -88,7 +88,7 @@ msgstr "E-mail"
#: core/forms.py:112
msgid "Used for password resets and, optionally, e-mail reminders."
msgstr ""
"Gebruikt voor het her-instellen van wachtwoorden en, optioneel, voor e-"
"Gebruikt voor het opnieuw-instellen van wachtwoorden en, optioneel, e-"
"mailherinneringen."
#: core/forms.py:116 core/models/profile.py:222
@@ -170,7 +170,7 @@ msgstr "Contact"
#: core/forms.py:324
msgid "Some way of answering you (e-mail, etc.)"
msgstr "Een manier om je te beantwoorden (e-mail, etc.)"
msgstr "Een manier om je van antwoorden te voorzien (e-mail, etc.)"
#: core/forms.py:332 exercises/models/comment.py:55 manager/models/slot.py:40
#: nutrition/models/log.py:77
@@ -202,13 +202,13 @@ msgid ""
"If a license has been localized, e.g. the Creative Commons licenses for the "
"different countries, add them as separate entries here."
msgstr ""
"Als een licentie werd gelokaliseerd, zoals bvb. de Creative Commons-"
"Als een licentie werd gelokaliseerd, zoals bijv. de Creative Commons-"
"licenties voor verschillende landen, voeg ze ze dan hier toe als aparte "
"ingaven."
#: core/models/license.py:40
msgid "Short name, e.g. CC-BY-SA 3"
msgstr "Korte naam, bvb. CC-BY-SA 3"
msgstr "Korte naam, bijv. CC-BY-SA 3"
#: core/models/license.py:45 nutrition/models/ingredient.py:223
msgid "Link"
@@ -258,11 +258,11 @@ msgstr "Toon opmerkingen oefening"
#: core/models/profile.py:120
msgid "Check to show exercise comments on the workout view"
msgstr ""
"Vink aan om begeleiding voor de oefeningen te tonen in de workout view."
"Vink aan om de opmerkingen bij de oefeningen te tonen in de workout view"
#: core/models/profile.py:130
msgid "Also use ingredients in English"
msgstr "Gebruik ook engelstalige ingrediënten"
msgstr "Gebruik ook Engelstalige ingrediënten"
#: core/models/profile.py:132
msgid ""
@@ -272,10 +272,11 @@ msgid ""
"7000 entries, but can be somewhat overwhelming and make the search difficult."
msgstr ""
"Vink aan om ook ingrediënten in het Engels te tonen bij het aanmaken van\n"
"een voedingsschema. Deze ingrediënten worden geëxtraheerd uit een lijst\n"
"aangeboden door het Amerikaanse Departement voor Landbouw. Ze is bijzonder\n"
"compleet, met ongeveer 7000 elementen, maar kan ook overweldigend zijn en je "
"zoektocht bemoeilijken"
"een voedingsschema. Deze ingrediënten worden uit een lijst gehaald die\n"
"aangeboden wordt door het Amerikaanse Departement voor Landbouw. Deze lijst "
"is bijzonder\n"
"compleet, met ongeveer 7000 ingrediënten, maar kan ook overweldigend zijn en "
"je zoektocht bemoeilijken"
#: core/models/profile.py:141
msgid "Activate workout reminders"
@@ -483,12 +484,13 @@ msgstr ""
" "
#: core/templates/base_wide.html:12
#, fuzzy, python-format
#| msgid "Back to \"%(target)s\""
#, python-format
msgid ""
"Back to \"%(target)s\n"
" \""
msgstr "Terug naar \"%(target)s\""
msgstr ""
"Terug naar \"%(target)s\n"
" \""
#: core/templates/delete.html:4
msgid "Are you sure you want to delete this? This action cannot be undone."
@@ -511,7 +513,7 @@ msgstr "Foutmelding, foutieve verificatie token"
#: core/templates/email_verification/email_body_html.tpl:6
msgid "Email Confirmation"
msgstr "Email bevestiging"
msgstr "Email Bevestiging"
#: core/templates/email_verification/email_body_html.tpl:9
#: core/templates/email_verification/email_body_txt.tpl:3
@@ -577,7 +579,7 @@ msgstr "Voeg er een toe."
#: mailer/templates/mailer/gym/overview.html:43
#: nutrition/templates/ingredient/overview.html:60
msgid "Add"
msgstr "Aanmaken"
msgstr "Toevoegen"
#: core/templates/language/view.html:61 core/templates/user/api_key.html:41
#: core/templates/user/preferences.html:50 gym/models/contract.py:203
@@ -640,7 +642,7 @@ msgstr "Training schema's"
#: core/templates/navigation.html:79
msgid "Your templates"
msgstr "Je sjablonen"
msgstr "Je templates"
#: core/templates/navigation.html:88
msgid "Public templates"
@@ -658,7 +660,7 @@ msgstr "Administratie"
#: core/templates/navigation.html:106
msgid "Exercise edit history"
msgstr "Oefeningswijzigings geschiedenis"
msgstr "Oefening bewerkings historie"
#: core/templates/navigation.html:112 exercises/models/base.py:107
msgid "Equipment"
@@ -1116,7 +1118,7 @@ msgstr "Opnemen in inactief overzicht"
#: core/templates/user/overview.html:510
#, python-format
msgid "Admin login is only available for users in %(gym_name)s"
msgstr "Inloggen voor beheerders is enkel beschikbaar in %(gym_name)s"
msgstr "Admin login is alleen beschikbaar voor gebruikers %(gym_name)s"
#: core/templates/user/overview.html:514
msgid "Log in as this user"
@@ -1128,19 +1130,19 @@ msgstr "Voorkeuren"
#: core/templates/user/preferences.html:19
msgid "Verified email"
msgstr "e-mail is gevalideerd"
msgstr "Geverifieerd email adres"
#: core/templates/user/preferences.html:24
msgid "Unverified email"
msgstr "Onbevestigde email"
msgstr "Niet geverifieerde email"
#: core/templates/user/preferences.html:26
msgid "Send verification email"
msgstr "Verstuur bevestigingsemail"
msgstr "Verzend verificatie email"
#: core/templates/user/preferences.html:31
msgid "You need to verify your email to contribute exercises"
msgstr "Je moet jouw email bevestigen om bij te dragen aan oefeningen"
msgstr "Je moet jouw email nog bevestigen om oefeningen te plaatsen"
#: core/templates/user/preferences.html:55 core/views/user.py:598
msgid "Change password"
@@ -1266,7 +1268,7 @@ msgstr "Gym"
#: core/views/user.py:647
#, python-format
msgid "A verification email was sent to %(email)s"
msgstr "Een bevestigingsmail is verstuurd naar %(email)s"
msgstr "Een verificatie email is verstuurd naar %(email)s"
#: exercises/models/base.py:86 nutrition/models/ingredient.py:245
msgid "Category"
@@ -1300,7 +1302,7 @@ msgstr "Een opmerking over hoe je deze oefening correct kan uitvoeren."
#: exercises/models/exercise_alias.py:56
msgid "Alias for an exercise"
msgstr "Andere naam voor een oefening"
msgstr "Alias voor een oefening"
#: exercises/models/image.py:58
msgid "Line"
@@ -1312,7 +1314,7 @@ msgstr "3D"
#: exercises/models/image.py:60
msgid "Low-poly"
msgstr ""
msgstr "Lage-poly"
#: exercises/models/image.py:61
msgid "Photo"
@@ -1341,7 +1343,7 @@ msgstr "Alternatieve naam"
#: exercises/models/muscle.py:48
msgid "A more basic name for the muscle"
msgstr "Een bekendere naam voor deze spier"
msgstr "Een vereenvoudigde naam voor de spier"
#: exercises/models/translation.py:74 nutrition/models/ingredient.py:81
#: nutrition/models/weight_unit.py:32
@@ -1358,14 +1360,12 @@ msgid "File type is not supported"
msgstr "Bestandstype wordt niet ondersteund"
#: exercises/models/video.py:72
#, fuzzy
msgid "File is not a valid video"
msgstr "Bestand is geen geldige video"
#: exercises/models/video.py:104
#, fuzzy
msgid "Main video"
msgstr "Hoofdvideo"
msgstr "Hoofd video"
#: exercises/models/video.py:110
msgid "Video"
@@ -1401,12 +1401,11 @@ msgstr "Materiaallijst"
#: exercises/templates/history/overview.html:11
msgid "Exercise admin history"
msgstr "Oefening admin geschiedenis"
msgstr "Admin oefening geschiedenis"
#: exercises/templates/history/overview.html:27
#, fuzzy
msgid "Action"
msgstr "Acties"
msgstr "Actie"
#: exercises/templates/history/overview.html:28 gallery/models/image.py:46
#: gym/forms.py:58 gym/templates/gym/email_inactive_members.html:4
@@ -1419,17 +1418,16 @@ msgid "User"
msgstr "Gebruiker"
#: exercises/templates/history/overview.html:29
#, fuzzy
msgid "Object"
msgstr "Onderwerp"
#: exercises/templates/history/overview.html:31
msgid "Changes"
msgstr "Veranderingen"
msgstr "Wijzigingen"
#: exercises/templates/history/overview.html:32
msgid "Revert"
msgstr "Terug"
msgstr "Terugzetten"
#: exercises/templates/history/overview.html:64
msgid "View Object"
@@ -1437,7 +1435,7 @@ msgstr "Bekijk Object"
#: exercises/templates/history/overview.html:75
msgid "View changes"
msgstr "Bekijk veranderingen"
msgstr "Bekijk wijzigingen"
#: exercises/templates/history/overview.html:86
msgid "Revert changes"
@@ -1472,7 +1470,6 @@ msgid "Only PNG and JPEG formats are supported"
msgstr "Enkel PNG en JPEG-formaten worden ondersteund"
#: gallery/templates/images/overview.html:72
#, fuzzy
msgid "Add image"
msgstr "Voeg afbeelding toe"
@@ -1832,7 +1829,7 @@ msgstr "Vrije halter"
#: i18n.tpl:13
msgid "Glutes"
msgstr "Bilspieren"
msgstr "Bil spieren"
#: i18n.tpl:14
msgid "Gym mat"
@@ -1904,7 +1901,7 @@ msgstr "Herhalingen"
#: i18n.tpl:31
msgid "Resistance band"
msgstr ""
msgstr "Weerstandsband"
#: i18n.tpl:32
msgid "SZ-Bar"
@@ -1995,7 +1992,6 @@ msgid "Routine will expire soon"
msgstr "Routine zal spoedig vervallen"
#: manager/models/day.py:52
#, fuzzy
msgid "Routine"
msgstr "Routine"
@@ -2031,16 +2027,16 @@ msgid ""
"Marking a workout as a template will freeze it and allow you to make copies "
"of it"
msgstr ""
"Een training markeren als een sjabloon zal het bevriezen en je toelaten om "
"er kopieën van te maken"
"Door een workout als template te markeren zal deze niet meer aan te passen "
"zijn en je in staat stellen om er kopieën van te maken"
#: manager/models/routine.py:116
msgid "Public template"
msgstr "Publieke sjabloon"
msgstr "Publieke template"
#: manager/models/routine.py:117
msgid "A public template is available to other users"
msgstr "Een publieke sjabloon is beschikbaar voor andere gebruikers"
msgstr "Een publieke template is beschikbaar voor andere gebruikers"
#: manager/models/routine.py:155 manager/models/session.py:147
msgid "The start time cannot be after the end time."
@@ -2129,11 +2125,16 @@ msgid ""
"Your last weight entry is from %(date)s (%(days)s days ago).\n"
"Please click the link to access the application and enter a new one."
msgstr ""
"Je laatste gewichtsinvoer dateert van %(date)s (%(days)s dagen geleden).\n"
"Klik op de link om het aanvraagformulier te openen en een nieuw gewicht in "
"te voeren."
#: manager/templates/workout/email_weight_reminder.tpl:8
msgid ""
"You will regularly receive such reminders till you enter your current weight."
msgstr ""
"Je zal regelmatig dergelijke herinneringen totdat je je huidige gewicht hebt "
"ingevoerd."
#: manager/views/pdf.py:77 manager/views/pdf.py:146
#, python-format
@@ -2142,22 +2143,19 @@ msgstr "Workout voor %s"
#: measurements/models/measurement.py:51
msgid "Value"
msgstr ""
msgstr "Waarde"
#: nutrition/forms.py:117
#, fuzzy
msgid "Height (in)"
msgstr "Hoogte (cm)"
msgstr "Hoogte (in)"
#: nutrition/forms.py:120
#, fuzzy
msgid "Weight (kg)"
msgstr "Gewichtlog"
msgstr "Gewicht (kg)"
#: nutrition/forms.py:120
#, fuzzy
msgid "Weight (lbs)"
msgstr "Gewichtlog"
msgstr "Gewicht (lbs)"
#: nutrition/forms.py:133
msgid "Calculate"
@@ -2216,7 +2214,7 @@ msgstr "Verzadigde vetinhoud in vetten"
#: nutrition/models/ingredient.py:184
#: nutrition/templates/ingredient/view.html:108 nutrition/views/plan.py:287
msgid "Fiber"
msgstr ""
msgstr "Vezels"
#: nutrition/models/ingredient.py:194
#: nutrition/templates/ingredient/view.html:118 nutrition/views/plan.py:293
@@ -2225,20 +2223,21 @@ msgstr "Natrium"
#: nutrition/models/ingredient.py:224
msgid "Link to product"
msgstr ""
msgstr "Link naar product"
#: nutrition/models/ingredient.py:253
msgid "Brand name of product"
msgstr ""
msgstr "Merknaam van product"
#: nutrition/models/ingredient.py:320
#, python-brace-format
msgid "The total energy ({self.energy}kcal) is not the approximate sum of the "
msgstr ""
"De totale energie ({self.energy}kcal) is niet de benaderende som van de "
#: nutrition/models/ingredient_category.py:31
msgid "Ingredient Categories"
msgstr ""
msgstr "Ingrediënt Categorieën"
#: nutrition/models/ingredient_weight_unit.py:49
msgid "Amount in grams"
@@ -2258,9 +2257,8 @@ msgid "Meal"
msgstr "Maaltijd"
#: nutrition/models/log.py:71
#, fuzzy
msgid "Date and Time (Approx.)"
msgstr "Tijd (ongeveer)"
msgstr "Datum en Tijd (Ongeveer.)"
#: nutrition/models/meal.py:60
msgid "Time (approx)"
@@ -2271,6 +2269,8 @@ msgid ""
"Give meals a textual description / name such as \"Breakfast\" or \"after "
"workout\""
msgstr ""
"Geef maaltijden een tekstuele beschrijving / naam, zoals 'Ontbijt' of 'na de "
"workout'."
#: nutrition/models/plan.py:78
msgid ""
@@ -2357,7 +2357,7 @@ msgstr "Macrovoedingstoffen"
#: nutrition/templates/ingredient/view.html:54
msgid "kJ"
msgstr ""
msgstr "kJ"
#: nutrition/templates/ingredient/view.html:75
#: nutrition/templates/ingredient/view.html:91
@@ -2703,66 +2703,72 @@ msgid "exercises <br> for workout creation"
msgstr "oefeningen <br> voor het maken van workouts"
#: software/templates/features.html:141
#, fuzzy
#| msgid "foods <br> for meal planing"
msgid "foods <br> for meal planning"
msgstr "voeding <br> voor maaltijdplanning"
#: software/templates/features.html:145
msgid "stars <br> on GitHub"
msgstr ""
msgstr "sterren <br> op Github"
#: software/templates/features.html:173
msgid "Craft your perfect <span>workout routines</span>"
msgstr ""
msgstr "Stel je perfecte <span>trainingsschema's</span> samen"
#: software/templates/features.html:177
msgid "Design a weekly workout plan from more than 100 exercises"
msgstr ""
msgstr "Stel een wekelijks trainingsschema samen uit meer dan 100 oefeningen"
#: software/templates/features.html:180
msgid "Set reps and sets, duration, or distance goals for each exercise"
msgstr ""
"Stel doelen vast voor het aantal herhalingen en sets, de duur of de afstand "
"van elke oefening"
#: software/templates/features.html:183
msgid "Get guided through your workouts step by step"
msgstr ""
msgstr "Laat je stap voor stap begeleiden tijdens je trainingen"
#: software/templates/features.html:186
msgid ""
"Keep track of the workouts you've done, including how you felt you performed"
msgstr ""
"Houd bij welke trainingen je hebt gedaan, inclusief hoe je je prestatie hebt "
"ervaren"
#: software/templates/features.html:211
msgid "Plan your <span>daily meals</span>"
msgstr ""
msgstr "Plan je <span>dagelijkse maaltijden</span>"
#: software/templates/features.html:215
msgid "Fill your week with nutritious meals that help you perform better"
msgstr ""
msgstr "Vul je week met voedzame maaltijden die je helpen beter te presteren"
#: software/templates/features.html:218
msgid "Build meals from a database of more than 2 million foods"
msgstr ""
"Stel maaltijden samen uit een database met meer dan 2 miljoen "
"voedingsmiddelen"
#: software/templates/features.html:221
msgid ""
"Get automatically calculated nutritional values (energy, protein, "
"carbs, ...) for both individual meals and your entire weekly plan"
msgstr ""
"Ontvang automatisch berekende voedingswaarden (energie, eiwitten, "
"koolhydraten, ...) voor zowel individuele maaltijden als je volledige "
"weekplan"
#: software/templates/features.html:251
msgid "Keep track of <span>your progress</span>"
msgstr ""
msgstr "Houd je <span>voortgang</span> bij"
#: software/templates/features.html:256
msgid "Track and annotate everything about your meals and workouts"
msgstr ""
msgstr "Houd alles bij en maak aantekeningen over je maaltijden en trainingen"
#: software/templates/features.html:259
#, fuzzy
msgid "Add custom notes"
msgstr "Voeg dagboekitem op maat toe"
msgstr "Voeg custom notities toe"
#: software/templates/features.html:262
msgid "Record your weight"
@@ -2774,7 +2780,7 @@ msgstr "Houd een fotologboek bij om je vooruitgang te zien"
#: software/templates/features.html:268
msgid "Use the calendar view to see past entries"
msgstr ""
msgstr "Gebruik de kalenderweergave om eerdere berichten te bekijken"
#: software/templates/features.html:287
msgid "Try wger now"
@@ -2798,10 +2804,12 @@ msgid ""
"We don't use ads, don't sell your personal info, and we share the code under "
"a free and open-source license."
msgstr ""
"We gebruiken geen advertenties, verkopen uw persoonlijke gegevens niet en "
"delen de code onder een gratis en open-source licentie."
#: software/templates/features.html:322
msgid "Pitch in and help make wger even better!"
msgstr ""
msgstr "Doe mee en help wger nog beter te maken!"
#: software/templates/features.html:331
msgid "Contribute"
@@ -2809,55 +2817,55 @@ msgstr "Draag bij"
#: software/templates/features.html:355
msgid "Sign Up Now For Free"
msgstr ""
msgstr "Meld je nu gratis aan"
#: software/templates/features.html:369
msgid "Web version not enough?"
msgstr ""
msgstr "Is de webversie niet voldoende?"
#: software/templates/features.html:371
#, fuzzy
msgid "Get the code, too!"
msgstr "Download de code"
msgstr "Download de code ook!"
#: software/templates/features.html:382
msgid "Set up a private server for your gym"
msgstr ""
msgstr "Zet een privéserver op voor je sportschool"
#: software/templates/features.html:385
msgid ""
"Don't want your data on an online service? Set up a wger instance on your "
"own server for free!"
msgstr ""
"Wil je je gegevens niet op een online service opslaan? Zet dan gratis een "
"wger-instantie op je eigen server op!"
#: software/templates/features.html:399
msgid "Integrate with your own app"
msgstr ""
msgstr "Integreer met je eigen app"
#: software/templates/features.html:402
msgid "wger offers an open API that you can use for your app."
msgstr ""
msgstr "wger biedt een open API die je voor je app kunt gebruiken."
#: software/templates/features.html:413
#, fuzzy
msgid "Create your own version"
msgstr "Huidige versie"
msgstr "Creëer je eigen versie"
#: software/templates/features.html:416
msgid ""
"The source code is available under the GNU AGPLv3 license! Feel free to fork "
"it on GitHub."
msgstr ""
"De broncode is beschikbaar onder de GNU AGPLv3-licentie! Je kunt de code "
"gerust forken op GitHub."
#: software/templates/features.html:437
#, fuzzy
msgid "Account"
msgstr "Hoeveelheid"
msgstr "Account"
#: software/templates/features.html:485
#, fuzzy
msgid "Workout routines"
msgstr "Workout-herinneringen"
msgstr "Workout routines"
#: software/templates/features.html:491
msgid "Discord"
@@ -2865,20 +2873,19 @@ msgstr "Discord"
#: software/templates/features.html:506
msgid "Software"
msgstr ""
msgstr "Software"
#: software/templates/features.html:512
msgid "Report an issue"
msgstr ""
msgstr "Rapporteer een probleem"
#: software/templates/features.html:518
msgid "Source Code"
msgstr ""
msgstr "Broncode"
#: software/templates/features.html:524
#, fuzzy
msgid "Translation"
msgstr "Vertaal"
msgstr "Vertaling"
#: utils/generic_views.py:263
msgid "Yes, delete"
@@ -2886,31 +2893,27 @@ msgstr "Ja, verwijder"
#: utils/models.py:51
msgid "The original title of this object, if available"
msgstr ""
msgstr "De oorspronkelijke titel van dit object, indien beschikbaar"
#: utils/models.py:57
msgid "Link to original object, if available"
msgstr ""
msgstr "Link naar het originele object, indien beschikbaar"
#: utils/models.py:63
#, fuzzy
msgid "Author(s)"
msgstr "Auteur"
msgstr "Auteur(s)"
#: utils/models.py:67
#, fuzzy
msgid "If you are not the author, enter the name or source here."
msgstr ""
"Als je niet de auteur bent, voeg dan de naam of bron hier toe. Dit is nodig "
"voor sommige licenties, oa. de CC-BY-SA."
msgstr "Als je niet de auteur bent, voeg dan de naam of bron hier toe."
#: utils/models.py:71
msgid "Link to author profile, if available"
msgstr ""
msgstr "Link naar het profiel van de auteur, indien beschikbaar"
#: utils/models.py:77
msgid "Link to the original source, if this is a derivative work"
msgstr ""
msgstr "Link naar de originele bron als dit een afgeleid werk is"
#: utils/models.py:79
msgid ""
@@ -2918,6 +2921,9 @@ msgid ""
"work, but which also contains sufficient new, creative content to entitle it "
"to its own copyright."
msgstr ""
"Een afgeleid werk is niet alleen gebaseerd op een eerder werk, maar bevat "
"ook voldoende nieuwe, creatieve inhoud om recht te geven op een eigen "
"auteursrecht."
#: weight/forms.py:53
msgid "Input"
@@ -2956,6 +2962,13 @@ msgid ""
"<strong>weight</strong>.\n"
"All further columns are ignored. There is a 1000 row limit.\n"
msgstr ""
"Je kunt tekst kopiëren en plakken vanuit je spreadsheet in het tekst "
"invoerveld.\n"
"Het systeem zal het importformaat proberen te raden. Het enige waar je "
"rekening mee moet houden is:\n"
"dat de eerste kolom de <strong>datum</strong> is en de tweede het <strong>"
"gewicht</strong>.\n"
"Alle overige kolommen worden genegeerd. Er geldt een limiet van 1000 rijen.\n"
#: weight/templates/import_csv_form.html:24
msgid ""

View File

@@ -15,8 +15,8 @@ msgstr ""
"Project-Id-Version: wger Workout Manager\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-10-01 16:48+0530\n"
"PO-Revision-Date: 2025-09-16 20:01+0000\n"
"Last-Translator: Иван Редун <ivanredun@gmail.com>\n"
"PO-Revision-Date: 2026-01-03 23:01+0000\n"
"Last-Translator: Gevorg Danielyan <forza-dgn@ya.ru>\n"
"Language-Team: Russian <https://hosted.weblate.org/projects/wger/web/ru/>\n"
"Language: ru\n"
"MIME-Version: 1.0\n"
@@ -25,7 +25,7 @@ msgstr ""
"Plural-Forms: nplurals=4; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && "
"n%10<=4 && (n%100<12 || n%100>14) ? 1 : n%10==0 || (n%10>=5 && n%10<=9) || "
"(n%100>=11 && n%100<=14)? 2 : 3);\n"
"X-Generator: Weblate 5.14-dev\n"
"X-Generator: Weblate 5.15.1\n"
#: config/models/gym_config.py:46 gym/templates/gym/list.html:56
msgid "Default gym"
@@ -1911,7 +1911,7 @@ msgstr "Повторений"
#: i18n.tpl:31
msgid "Resistance band"
msgstr ""
msgstr "Эластичная лента"
#: i18n.tpl:32
msgid "SZ-Bar"

View File

@@ -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,

View File

@@ -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"""

View File

@@ -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