diff --git a/.env.example b/.env.example new file mode 100644 index 00000000..a0253933 --- /dev/null +++ b/.env.example @@ -0,0 +1,11 @@ +SECRET_KEY=secret-key + +DATABASE_URL=psql://postgres:postgres@db:5432/postgres +CACHE_DEFAULT=redis://redis:6379/0 +STATIC_URL=/static/ +STATIC_ROOT=/var/static/ + +POSTGRES_DB=postgres +POSTGRES_USER=postgres +POSTGRES_PASSWORD=postgres +POSTGRES_HOST=db \ No newline at end of file diff --git a/.flake8 b/.flake8 index 628307c6..ef07025b 100644 --- a/.flake8 +++ b/.flake8 @@ -1,7 +1,18 @@ [flake8] max-complexity = 20 max-line-length = 120 -exclude = ~* +exclude = + ~* + .venv, + venv, + .git, + __pycache__, + build, + dist, + migrations, + snapshots, + __pypackages__, + ignore = E401,W391,E128,E261,E731,Q000,W504,W606,W503,E203 ;putty-ignore = ; tests/test_choice_as_instance.py : E501 diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml deleted file mode 100644 index a32c8dec..00000000 --- a/.github/workflows/docker.yml +++ /dev/null @@ -1,28 +0,0 @@ -on: - push: - branches: ['release'] - -env: - REGISTRY: ghcr.io - IMAGE_NAME: ${{ github.repository }} - -jobs: - build-base: - runs-on: ubuntu-latest - permissions: - contents: read - packages: write - - steps: - - name: Checkout repository - uses: actions/checkout@v4 - - - name: Setup Python - uses: actions/setup-python@v4 - with: - python-version: '3.11' - - - name: Build and push Docker image - run: > - echo `pwd`/docker -# docker build . --file Dockerfile --tag my-image-name:$(date +%s) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 924d1e61..77895c41 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -8,18 +8,6 @@ on: pull_request: jobs: -# lint: -# runs-on: ubuntu-latest -# steps: -# - uses: actions/checkout@v2 -# - uses: actions/setup-python@v2 -# -# - name: Install dependencies -# run: | -# python -m pip install --upgrade pip pre-commit -# - name: -# run: pre-commit run --all-files - test: runs-on: ubuntu-latest services: @@ -36,7 +24,7 @@ jobs: fail-fast: false matrix: django-version: [ "4.2", ] - python-version: [ "3.11", ] + python-version: [ "3.12", ] experimental: [ false ] # include: # - django-version: "5.0" @@ -68,8 +56,24 @@ jobs: pdm sync - name: Run tests - run: pdm run pytest tests/ --create-db + run: pdm run pytest tests/ --create-db --cov --cov-report xml --junit-xml junit.xml + + - name: Upload pytest test results + uses: actions/upload-artifact@v4 + with: + name: pytest-results + path: junit.xml + if: ${{ always() }} - - uses: codecov/codecov-action@v4 + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v4 + if: matrix.python-version == 3.12 + continue-on-error: true with: - verbose: false # optional (default = false) + env_vars: OS,PYTHON + fail_ci_if_error: true + flags: unittests + files: ./coverage.xml + verbose: false + token: ${{ secrets.CODECOV_TOKEN }} + name: codecov-${{env.GITHUB_REF_NAME}} diff --git a/.github/workflows/trivy.yml b/.github/workflows/trivy.yml index 8d647440..b10d37e2 100644 --- a/.github/workflows/trivy.yml +++ b/.github/workflows/trivy.yml @@ -24,7 +24,7 @@ jobs: - name: Setup Python uses: actions/setup-python@v4 with: - python-version: '3.11' + python-version: '3.12' - name: Install pdm run: python -m pip install --upgrade pdm diff --git a/.gitignore b/.gitignore index 124ce5e9..709cb443 100644 --- a/.gitignore +++ b/.gitignore @@ -24,4 +24,4 @@ docker/conf/nginx.conf docker/conf/redis.conf src/aurora/staticfiles -.pdm-python +.pdm-python \ No newline at end of file diff --git a/README.md b/README.md index 5c6a1ed0..4890c81e 100644 --- a/README.md +++ b/README.md @@ -1,31 +1,10 @@ # Aurora -Aurora is an open source project to collect and register data. -It is focused mainly on performance and security. +[![Test](https://github.com/unicef/hope-aurora/actions/workflows/test.yml/badge.svg)](https://github.com/unicef/hope-aurora/actions/workflows/test.yml) -### Run the code +Aurora is the official HOPE online registration tool, it is released as open source project, feel free to contribute and use it. -- Option 1: with local machine services (redis, postgres) with `direnv` +It has be development taking performance and security as main key points -First configure your `.envrc` and run - -```shell - python manage.py runserver -```` - -- Option 2: using docker-composer - -For the first time you need to run in root project directory - -```shell -./manage env --comment --defaults > .env -docker-compose build -docker-compose up -``` - -each next time - -```shell -docker-compose up -``` +Please read more about Aurora and HOPE in the [official documentation](https://unicef.github.io/hope-documentation/) diff --git a/compose.yml b/compose.yml new file mode 100644 index 00000000..1fe8f9ce --- /dev/null +++ b/compose.yml @@ -0,0 +1,60 @@ +volumes: + db: + +services: + app: + stdin_open: true + tty: true + environment: + - ADMIN_EMAIL=admin@example.com + - ADMIN_PASSWORD=password + - ALLOWED_HOSTS=app,localhost,127.0.0.1 + - CACHE_URL=redis://redis:6379/1?client_class=django_redis.client.DefaultClient + - CACHE_DEFAULT=redis://redis:6379/2 + - CELERY_BROKER_URL=redis://redis:6379/9 + - CSRF_COOKIE_SECURE=False + - CSRF_TRUSTED_ORIGINS=http://localhost:8000,http://localhost + - DATABASE_URL=postgres://aurora:password@db:5432/aurora + - DEBUG=true + - DJANGO_ADMIN_URL=admin/ + - FERNET_KEY=3bfbbad7d5e149e9b313fd47d33db5e6 + - MEDIA_ROOT=/var/storage/media/ + - SECRET_KEY=super_secret_key_just_for_development_that_needs_to_be_more_than_fifty_characters + - SECURE_HSTS_PRELOAD=0 + - SECURE_SSL_REDIRECT=False + - SESSION_COOKIE_DOMAIN=localhost:8000 + - SESSION_COOKIE_SECURE=False + - SOCIAL_AUTH_REDIRECT_IS_HTTPS=False + - STORAGE_STATIC=django.core.files.storage.FileSystemStorage + - STORAGE_MEDIA=django.core.files.storage.FileSystemStorage + - STORAGE_DEFAULT=django.core.files.storage.FileSystemStorage + - STATIC_ROOT=/var/storage/static/ + - STATIC_URL=/static/ + - USE_HTTPS=false + - USE_X_FORWARDED_HOST=false + build: + context: ./ + dockerfile: ./docker/Dockerfile + target: dev + ports: + - "8000:80" # expose nginx here +# - "8000:8000" + depends_on: + - db + - redis + + db: + image: postgres:16 + environment: + - PGUSER=aurora + - POSTGRES_USER=aurora + - POSTGRES_PASSWORD=password + - POSTGRES_DB=aurora + volumes: + - db:/var/lib/postgresql/data + + redis: + image: redis:7 + restart: unless-stopped +# expose: +# - "6379" diff --git a/docker-compose.yml b/docker-compose.yml deleted file mode 100644 index 671bca04..00000000 --- a/docker-compose.yml +++ /dev/null @@ -1,32 +0,0 @@ -version: '3.7' - -volumes: - db: - -services: - backend: - stdin_open: true - tty: true - env_file: - - .env - build: - context: ./ - dockerfile: ./docker/Dockerfile - ports: - - "8000:8000" - volumes: - - ./:/code/ - command: "dev" - depends_on: - - db - db: - image: mdillon/postgis:11-alpine - volumes: - - db:/var/lib/postgresql/data - env_file: - - .env - redis: - image: redis:4.0.11-alpine3.8 - restart: unless-stopped - expose: - - "6379" diff --git a/docker/Dockerfile b/docker/Dockerfile index d2675e5c..d819a48f 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -1,4 +1,4 @@ -FROM python:3.11-slim-bullseye +FROM python:3.12-slim-bookworm AS base ARG BUILD_DATE ARG VERSION @@ -24,13 +24,16 @@ RUN apt-get update \ && locale-gen --no-purge uk_UA.UTF-8 \ && apt-get clean -ENV PATH="${PATH}:/root/.local/bin:/code/__pypackages__/3.11/bin" \ +ENV PATH="${PATH}:/root/.local/bin:/code/__pypackages__/3.12/bin" \ ADMINS="" \ BUILD_DATE=${BUILD_DATE} \ - CACHE_DEFAULT="redis://127.0.0.1/0" \ CONSTANCE_DATABASE_CACHE_BACKEND="" \ + CSRF_TRUSTED_ORIGINS="" \ + CSRF_COOKIE_NAME="aurora" \ + CSRF_COOKIE_SECURE="true" \ DATABASE_URL="" \ DEFAULT_ORGANIZATION="UNICEF"\ + DEBUG="false"\ DJANGO_SETTINGS_MODULE="aurora.config.settings" \ IPSTACK_KEY="" \ LOG_LEVEL="ERROR" \ @@ -40,7 +43,7 @@ ENV PATH="${PATH}:/root/.local/bin:/code/__pypackages__/3.11/bin" \ REDIS_LOGLEVEL="warning" \ REDIS_MAXMEMORY="100Mb" \ REDIS_MAXMEMORY_POLICY="volatile-ttl" \ - PYTHONPATH="/code/src/:/code/__pypackages__/3.11/lib" \ + PYTHONPATH="/code/src/:/code/__pypackages__/3.12/lib" \ PYTHONUNBUFFERED=1 \ SECRET_KEY="secret-key-just-for-build" \ SENTRY_DSN="" \ @@ -63,4 +66,12 @@ ADD ./docker/conf/* /conf/ ADD ./docker/bin/* /usr/local/bin/ ENTRYPOINT ["/usr/local/bin/entrypoint.sh"] +CMD ["run"] + +EXPOSE 80 EXPOSE 8000 + +FROM base AS dev +RUN pdm sync --dev + +FROM base AS dist diff --git a/docker/Dockerfile.test b/docker/Dockerfile.test index 926861ec..e117c5ef 100644 --- a/docker/Dockerfile.test +++ b/docker/Dockerfile.test @@ -1,4 +1,4 @@ -FROM python:3.11-slim-bullseye +FROM python:3.12-slim-bookworm ARG BUILD_DATE ARG VERSION diff --git a/docker/Makefile b/docker/Makefile index 4b875868..e92df5b5 100644 --- a/docker/Makefile +++ b/docker/Makefile @@ -17,8 +17,8 @@ WORKER?='??' CONTAINER_NAME?=flex-form-cnt LAZO?=$(shell which lazo) -DOCKER_REGISTRY=ghcr.io -DOCKER_IMAGE_NAME=saxix/aurora +DOCKER_REGISTRY?=ghcr.io +DOCKER_IMAGE_NAME?=saxix/aurora DOCKER_IMAGE=${DOCKER_IMAGE_NAME}:${VERSION} DOCKER_TARGET=${DOCKER_REGISTRY}/${DOCKER_IMAGE} DOCKERFILE?=Dockerfile @@ -91,9 +91,8 @@ dev: -e ALLOWED_HOSTS="*" \ -e DATABASE_URL="${DATABASE_URL}" \ -e DEBUG="0" \ - -e DJANGO_SETTINGS_MODULE="aurora.config.settings" \ -e SENTRY_DSN="${SENTRY_DSN}" \ - -e REDIS_CONNSTR="192.168.66.66" \ + -e REDIS_CONNSTR="127.0.0.1" \ -e VERSION="${VERSION}" \ -v ${PWD}/conf/:/conf/ \ -v ${PWD}/bin/entrypoint.sh:/usr/local/bin/entrypoint.sh \ diff --git a/docker/bin/entrypoint.sh b/docker/bin/entrypoint.sh index 09cc929c..25d1f57b 100755 --- a/docker/bin/entrypoint.sh +++ b/docker/bin/entrypoint.sh @@ -7,34 +7,38 @@ export REDIS_MAXMEMORY="${REDIS_MAXMEMORY:-100Mb}" export REDIS_MAXMEMORY_POLICY="${REDIS_MAXMEMORY_POLICY:-volatile-ttl}" export AURORA_VERSION=${VERSION} export AURORA_BUILD=${BUILD_DATE} -export PYTHONPATH="/code/src/:/code/__pypackages__/3.11/lib" +#export PYTHONPATH="/code/src/:/code/__pypackages__/3.12/lib" export DOLLAR='$' mkdir -p /var/run /var/nginx ${NGINX_CACHE_DIR} ${MEDIA_ROOT} ${STATIC_ROOT} echo "created support dirs /var/run ${MEDIA_ROOT} ${STATIC_ROOT}" -if [ $# -eq 0 ]; then - envsubst < /conf/nginx.conf.tpl > /conf/nginx.conf && nginx -tc /conf/nginx.conf - envsubst < /conf/redis.conf.tpl > /conf/redis.conf +case "$1" in + "run") + envsubst < /conf/nginx.conf.tpl > /conf/nginx.conf && nginx -tc /conf/nginx.conf + envsubst < /conf/redis.conf.tpl > /conf/redis.conf - django-admin upgrade --no-input + django-admin upgrade --no-input - nginx -c /conf/nginx.conf - redis-server /conf/redis.conf - exec uwsgi --ini /conf/uwsgi.ini -# exec gunicorn aurora.config.wsgi -c /conf/gunicorn_config.py -else - case "$1" in - "dev") + nginx -c /conf/nginx.conf + redis-server /conf/redis.conf + exec uwsgi --ini /conf/uwsgi.ini + + ;; + "dev") until pg_isready -h db -p 5432; do echo "waiting for database"; sleep 2; done; django-admin collectstatic --no-input django-admin migrate django-admin runserver 0.0.0.0:8000 - ;; - *) - exec "$@" ;; - esac -fi + "setup") + until pg_isready -h db -p 5432; + do echo "waiting for database"; sleep 2; done; + django-admin upgrade --no-input + ;; +*) +exec "$@" +;; +esac diff --git a/docker/conf/.dockerignore b/docker/conf/.dockerignore new file mode 100644 index 00000000..6f720dc4 --- /dev/null +++ b/docker/conf/.dockerignore @@ -0,0 +1,2 @@ +nginx.conf +redis.conf diff --git a/docker/conf/circus.conf b/docker/conf/circus.conf deleted file mode 100644 index 5c97e05c..00000000 --- a/docker/conf/circus.conf +++ /dev/null @@ -1,34 +0,0 @@ -[circus] -check_delay = 5 -# endpoint = tcp://127.0.0.1:5555 -# pubsub_endpoint = tcp://127.0.0.1:5556 -umask = 002 -working_dir = $(CIRCUS.ENV.PWD) -debug = false -stdout_stream.class = StdoutStream -stderr_stream.class = StdoutStream - -[watcher:web] -cmd = nginx -args = -c /etc/nginx.conf -user = www -use_sockets = True -copy_env = true -autostart = true - -[watcher:app] -cmd = uwsgi -args = --ini /etc/uwsgi.ini -user = www -use_sockets = True -copy_env = true -autostart = true - - -[watcher:daphne] -cmd = uwsgi -args = --ini /etc/uwsgi.ini -user = www -use_sockets = True -copy_env = true -autostart = true diff --git a/docker/conf/gunicorn_config.py b/docker/conf/gunicorn_config.py deleted file mode 100644 index 69d3cb4f..00000000 --- a/docker/conf/gunicorn_config.py +++ /dev/null @@ -1,62 +0,0 @@ -bind = "0.0.0.0:8000" -backlog = 512 - -workers = 4 -worker_class = "sync" -# worker_connections = 1000 # This setting only affects the Eventlet and Gevent worker types. -max_requests = 1000 -timeout = 30 -keepalive = 2 - -proc_name = None -daemon = False -pidfile = None -umask = 0 -user = None -group = None -tmp_upload_dir = None - - -errorlog = "-" -loglevel = "error" -accesslog = "-" -access_log_format = '%(h)s %(l)s %(u)s %(t)s "%(r)s" %(s)s %(b)s "%(f)s" "%(a)s"' - - -def post_fork(server, worker): - server.log.info("Worker spawned (pid: %s)", worker.pid) - - -def pre_fork(server, worker): - pass - - -def pre_exec(server): - server.log.info("Forked child, re-executing.") - - -def when_ready(server): - server.log.info("Server is ready. Spawning workers") - - -def worker_int(worker): - worker.log.info("Worker received INT or QUIT signal") - - # get traceback info - import sys - import threading - import traceback - - id2name = {th.ident: th.name for th in threading.enumerate()} - code = [] - for threadId, stack in sys._current_frames().items(): - code.append("\n# Thread: %s(%d)" % (id2name.get(threadId, ""), threadId)) - for filename, lineno, name, line in traceback.extract_stack(stack): - code.append('File: "%s", line %d, in %s' % (filename, lineno, name)) - if line: - code.append(" %s" % (line.strip())) - worker.log.debug("\n".join(code)) - - -def worker_abort(worker): - worker.log.info("worker received SIGABRT signal") diff --git a/docker/conf/uwsgi.ini b/docker/conf/uwsgi.ini index 0da0ee79..635ebd9e 100644 --- a/docker/conf/uwsgi.ini +++ b/docker/conf/uwsgi.ini @@ -1,7 +1,7 @@ [uwsgi] http-socket=0.0.0.0:8000 -env=PYTHONPATH=/code/src/:/code/__pypackages__/3.11/lib +env=PYTHONPATH=/code/src/:/code/__pypackages__/3.12/lib enable-threads=0 honour-range=1 master=1 diff --git a/pdm.lock b/pdm.lock index 0982f447..6535501e 100644 --- a/pdm.lock +++ b/pdm.lock @@ -4,8 +4,11 @@ [metadata] groups = ["default", "dev"] strategy = ["cross_platform", "inherit_metadata"] -lock_version = "4.4.1" -content_hash = "sha256:39e122b9afb1d962f4f8912ded272b0fa1589ce71337daf978fdca3519691ef1" +lock_version = "4.5.0" +content_hash = "sha256:58011123ac3e44ce5316c34acbd013ee904353da5a23df1a3cc838cbdfd44503" + +[[metadata.targets]] +requires_python = ">=3.12" [[package]] name = "amqp" @@ -27,6 +30,9 @@ version = "3.7.2" requires_python = ">=3.7" summary = "ASGI specs, helper code, and adapters" groups = ["default", "dev"] +dependencies = [ + "typing-extensions>=4; python_version < \"3.11\"", +] files = [ {file = "asgiref-3.7.2-py3-none-any.whl", hash = "sha256:89b2ef2247e3b562a16eef663bc0e2e703ec6468e2fa8a5cd61cd449786d4f6e"}, {file = "asgiref-3.7.2.tar.gz", hash = "sha256:9e0ce3aa93a819ba5b45120216b23878cf6e8525eb3848653452b4192b92afed"}, @@ -39,30 +45,22 @@ summary = "Annotate AST trees with source code positions" groups = ["dev"] dependencies = [ "six>=1.12.0", + "typing; python_version < \"3.5\"", ] files = [ {file = "asttokens-2.4.1-py2.py3-none-any.whl", hash = "sha256:051ed49c3dcae8913ea7cd08e46a606dba30b79993209636c4875bc1d637bc24"}, {file = "asttokens-2.4.1.tar.gz", hash = "sha256:b03869718ba9a6eb027e134bfdf69f38a236d681c83c160d510768af11254ba0"}, ] -[[package]] -name = "async-timeout" -version = "4.0.3" -requires_python = ">=3.7" -summary = "Timeout context manager for asyncio programs" -groups = ["default"] -marker = "python_full_version < \"3.11.3\"" -files = [ - {file = "async-timeout-4.0.3.tar.gz", hash = "sha256:4640d96be84d82d02ed59ea2b7105a0f7b33abe8703703cd0ab0bf87c427522f"}, - {file = "async_timeout-4.0.3-py3-none-any.whl", hash = "sha256:7405140ff1230c310e51dc27b3145b9092d659ce68ff733fb0cefe3ee42be028"}, -] - [[package]] name = "attrs" version = "23.2.0" requires_python = ">=3.7" summary = "Classes Without Boilerplate" groups = ["default", "dev"] +dependencies = [ + "importlib-metadata; python_version < \"3.8\"", +] files = [ {file = "attrs-23.2.0-py3-none-any.whl", hash = "sha256:99b87a485a5820b23b879f04c2305b44b951b502fd64be915879d77a7e8fc6f1"}, {file = "attrs-23.2.0.tar.gz", hash = "sha256:935dc3b529c262f6cf76e50877d35a4bd3c1de194fd41f47a2b7ae8f19971f30"}, @@ -134,6 +132,9 @@ dependencies = [ "mypy-extensions>=0.4.3", "pathspec>=0.9.0", "platformdirs>=2", + "tomli>=1.1.0; python_full_version < \"3.11.0a7\"", + "typed-ast>=1.4.2; python_version < \"3.8\" and implementation_name == \"cpython\"", + "typing-extensions>=3.10.0.0; python_version < \"3.10\"", ] files = [ {file = "black-22.12.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d30b212bffeb1e252b31dd269dfae69dd17e06d92b87ad26e23890f3efea366f"}, @@ -160,11 +161,13 @@ requires_python = ">=3.8" summary = "Distributed Task Queue." groups = ["default"] dependencies = [ + "backports-zoneinfo>=0.2.1; python_version < \"3.9\"", "billiard<5.0,>=4.2.0", "click-didyoumean>=0.3.0", "click-plugins>=1.1.1", "click-repl>=0.2.0", "click<9.0,>=8.1.2", + "importlib-metadata>=3.6; python_version < \"3.8\"", "kombu<6.0,>=5.3.4", "python-dateutil>=2.8.2", "tzdata>=2022.7", @@ -340,6 +343,7 @@ summary = "Composable command line interface toolkit" groups = ["default", "dev"] dependencies = [ "colorama; platform_system == \"Windows\"", + "importlib-metadata; python_version < \"3.8\"", ] files = [ {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"}, @@ -450,6 +454,7 @@ summary = "Code coverage measurement for Python" groups = ["dev"] dependencies = [ "coverage==7.4.4", + "tomli; python_full_version <= \"3.11.0a6\"", ] files = [ {file = "coverage-7.4.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0f9f50e7ef2a71e2fae92774c99170eb8304e3fdf9c8c3c7ae9bab3e7229c5cf"}, @@ -598,6 +603,7 @@ summary = "A high-level Python web framework that encourages rapid development a groups = ["default", "dev"] dependencies = [ "asgiref<4,>=3.6.0", + "backports-zoneinfo; python_version < \"3.9\"", "sqlparse>=0.3.1", "tzdata; sys_platform == \"win32\"", ] @@ -1066,6 +1072,20 @@ files = [ {file = "django-smart-admin-2.6.0.tar.gz", hash = "sha256:9ac878433c57eb285360e0c019258fc7fef9d5557805dafb55d2f965e4fe02e2"}, ] +[[package]] +name = "django-smart-env" +version = "0.1.0" +requires_python = ">=3.12" +summary = "Add your description here" +groups = ["default"] +dependencies = [ + "django-environ>=0.11.2", +] +files = [ + {file = "django_smart_env-0.1.0-py3-none-any.whl", hash = "sha256:ffcbc03ab2b28808d1ac80b5165543549396dde4a24107e969a9635ba9321849"}, + {file = "django_smart_env-0.1.0.tar.gz", hash = "sha256:09ef06a2ae9223c68ba893dae2b6188938f41e464cb38e4714c341950fc1caf3"}, +] + [[package]] name = "django-strategy-field" version = "3.1.0" @@ -1231,6 +1251,7 @@ summary = "A versatile test fixtures replacement based on thoughtbot's factory_b groups = ["dev"] dependencies = [ "Faker>=0.7.0", + "importlib-metadata; python_version < \"3.8\"", ] files = [ {file = "factory_boy-3.3.0-py2.py3-none-any.whl", hash = "sha256:a2cdbdb63228177aa4f1c52f4b6d83fab2b8623bf602c7dedd7eb83c0f69c04c"}, @@ -1245,6 +1266,7 @@ summary = "Faker is a Python package that generates fake data for you." groups = ["default", "dev"] dependencies = [ "python-dateutil>=2.4", + "typing-extensions>=3.10.0.1; python_version <= \"3.8\"", ] files = [ {file = "Faker-24.2.0-py3-none-any.whl", hash = "sha256:dce4754921f9fa7e2003c26834093361b8f45072e0f46f172d6ca1234774ecd4"}, @@ -1299,6 +1321,7 @@ summary = "Generate HTML reports of flake8 violations" groups = ["dev"] dependencies = [ "flake8>=3.3.0", + "importlib-metadata; python_version < \"3.8\"", "jinja2>=3.1.0", "pygments>=2.2.0", ] @@ -1327,6 +1350,9 @@ version = "0.14.0" requires_python = ">=3.7" summary = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" groups = ["dev"] +dependencies = [ + "typing-extensions; python_version < \"3.8\"", +] files = [ {file = "h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761"}, {file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"}, @@ -1359,6 +1385,7 @@ summary = "A featureful, immutable, and correct URL for Python." groups = ["default"] dependencies = [ "idna>=2.5", + "typing; python_version < \"3.5\"", ] files = [ {file = "hyperlink-21.0.0-py2.py3-none-any.whl", hash = "sha256:e6b14c37ecb73e89c77d78cdb4c2cc8f3fb59a885c5b3f819ff4ed80f25af1b4"}, @@ -1417,6 +1444,7 @@ groups = ["dev"] dependencies = [ "colorama; sys_platform == \"win32\"", "decorator", + "exceptiongroup; python_version < \"3.11\"", "jedi>=0.16", "matplotlib-inline", "pexpect>4.3; sys_platform != \"win32\" and sys_platform != \"emscripten\"", @@ -1424,6 +1452,7 @@ dependencies = [ "pygments>=2.4.0", "stack-data", "traitlets>=5.13.0", + "typing-extensions; python_version < \"3.10\"", ] files = [ {file = "ipython-8.22.2-py3-none-any.whl", hash = "sha256:3c86f284c8f3d8f2b6c662f885c4889a91df7cd52056fd02b7d8d6195d7f56e9"}, @@ -1486,6 +1515,9 @@ version = "3.0.3" requires_python = ">=3.7" summary = "Python library for serializing any arbitrary object graph into JSON" groups = ["default"] +dependencies = [ + "importlib-metadata; python_version < \"3.8\"", +] files = [ {file = "jsonpickle-3.0.3-py3-none-any.whl", hash = "sha256:e8d6dcc58f6722bea0321cd328fbda81c582461185688a535df02be0f699afb4"}, {file = "jsonpickle-3.0.3.tar.gz", hash = "sha256:5691f44495327858ab3a95b9c440a79b41e35421be1a6e09a47b6c9b9421fd06"}, @@ -1499,6 +1531,8 @@ summary = "Messaging library for Python." groups = ["default"] dependencies = [ "amqp<6.0.0,>=5.1.1", + "backports-zoneinfo[tzdata]>=0.2.1; python_version < \"3.9\"", + "typing-extensions; python_version < \"3.10\"", "vine", ] files = [ @@ -1554,6 +1588,9 @@ version = "3.6" requires_python = ">=3.8" summary = "Python implementation of John Gruber's Markdown." groups = ["default"] +dependencies = [ + "importlib-metadata>=4.4; python_version < \"3.10\"", +] files = [ {file = "Markdown-3.6-py3-none-any.whl", hash = "sha256:48f276f4d8cfb8ce6527c8f79e2ee29708508bf4d40aa410fbc3b4ee832c850f"}, {file = "Markdown-3.6.tar.gz", hash = "sha256:ed4f41f6daecbeeb96e576ce414c41d2d876daa9a16cb35fa8ed8c2ddfad0224"}, @@ -1688,6 +1725,8 @@ summary = "Optional static typing for Python" groups = ["dev"] dependencies = [ "mypy-extensions>=1.0.0", + "tomli>=1.1.0; python_version < \"3.11\"", + "typed-ast<2,>=1.4.0; python_version < \"3.8\"", "typing-extensions>=3.10", ] files = [ @@ -2151,6 +2190,9 @@ version = "2.8.0" requires_python = ">=3.7" summary = "JSON Web Token implementation in Python" groups = ["default"] +dependencies = [ + "typing-extensions; python_version <= \"3.7\"", +] files = [ {file = "PyJWT-2.8.0-py3-none-any.whl", hash = "sha256:59127c392cc44c2da5bb3192169a91f429924e17aff6534d70fdc02ab3e04320"}, {file = "PyJWT-2.8.0.tar.gz", hash = "sha256:57e28d156e3d5c10088e0c68abb90bfac3df82b40a71bd0daa20c65ccd5c23de"}, @@ -2188,6 +2230,7 @@ summary = "API to interact with the python pyproject.toml based projects" groups = ["dev"] dependencies = [ "packaging>=23.1", + "tomli>=2.0.1; python_version < \"3.11\"", ] files = [ {file = "pyproject_api-1.6.1-py3-none-any.whl", hash = "sha256:4c0116d60476b0786c88692cf4e325a9814965e2469c5998b830bba16b183675"}, @@ -2247,6 +2290,7 @@ groups = ["dev"] dependencies = [ "attrs>=19.2.0", "colorama; sys_platform == \"win32\"", + "importlib-metadata>=0.12; python_version < \"3.8\"", "iniconfig", "packaging", "pluggy<2.0,>=0.12", @@ -2519,6 +2563,8 @@ summary = "Python client for Redis database and key-value store" groups = ["default"] dependencies = [ "async-timeout>=4.0.3; python_full_version < \"3.11.3\"", + "importlib-metadata>=1.0; python_version < \"3.8\"", + "typing-extensions; python_version < \"3.8\"", ] files = [ {file = "redis-5.0.3-py3-none-any.whl", hash = "sha256:5da9b8fe9e1254293756c16c008e8620b3d15fcc6dde6babde9541850e72a32d"}, @@ -2566,6 +2612,7 @@ groups = ["default"] dependencies = [ "markdown-it-py>=2.2.0", "pygments<3.0.0,>=2.13.0", + "typing-extensions<5.0,>=4.0.0; python_version < \"3.9\"", ] files = [ {file = "rich-13.7.1-py3-none-any.whl", hash = "sha256:4edbae314f59eb482f54e9e30bf00d33350aaa94f4bfcd4e9e3110e64d0d7222"}, @@ -2597,7 +2644,9 @@ summary = "Python client for Sentry (https://sentry.io)" groups = ["default"] dependencies = [ "certifi", + "urllib3>=1.25.7; python_version <= \"3.4\"", "urllib3>=1.26.11; python_version >= \"3.6\"", + "urllib3>=1.26.9; python_version == \"3.5\"", ] files = [ {file = "sentry-sdk-1.42.0.tar.gz", hash = "sha256:4a8364b8f7edbf47f95f7163e48334c96100d9c098f0ae6606e2e18183c223e6"}, @@ -2774,7 +2823,10 @@ version = "6.3.1" summary = "Retry code until it succeeds" groups = ["dev"] dependencies = [ + "futures>=3.0; python_version == \"2.7\"", + "monotonic>=0.6; python_version == \"2.7\"", "six>=1.9.0", + "typing>=3.7.4.1; python_version == \"2.7\"", ] files = [ {file = "tenacity-6.3.1-py2.py3-none-any.whl", hash = "sha256:baed357d9f35ec64264d8a4bbf004c35058fad8795c5b0d8a7dc77ecdcbb8f39"}, @@ -2803,10 +2855,13 @@ dependencies = [ "chardet>=5.2", "colorama>=0.4.6", "filelock>=3.13.1", + "importlib-metadata>=7.0.1; python_version < \"3.8\"", "packaging>=23.2", "platformdirs>=4.1", "pluggy>=1.3", "pyproject-api>=1.6.1", + "tomli>=2.0.1; python_version < \"3.11\"", + "typing-extensions>=4.9; python_version < \"3.8\"", "virtualenv>=20.25", ] files = [ @@ -2834,6 +2889,7 @@ groups = ["dev"] dependencies = [ "attrs>=20.1.0", "cffi>=1.14; os_name == \"nt\" and implementation_name != \"pypy\"", + "exceptiongroup; python_version < \"3.11\"", "idna", "outcome", "sniffio>=1.3.0", @@ -2851,6 +2907,7 @@ requires_python = ">=3.7" summary = "WebSocket library for Trio" groups = ["dev"] dependencies = [ + "exceptiongroup; python_version < \"3.11\"", "trio>=0.11", "wsproto>=0.14", ] @@ -3027,6 +3084,7 @@ groups = ["dev"] dependencies = [ "distlib<1,>=0.3.7", "filelock<4,>=3.12.2", + "importlib-metadata>=6.6; python_version < \"3.8\"", "platformdirs<5,>=3.9.1", ] files = [ @@ -3076,6 +3134,9 @@ name = "wcwidth" version = "0.2.13" summary = "Measures the displayed width of unicode strings in a terminal" groups = ["default", "dev"] +dependencies = [ + "backports-functools-lru-cache>=1.2.1; python_version < \"3.2\"", +] files = [ {file = "wcwidth-0.2.13-py2.py3-none-any.whl", hash = "sha256:3da69048e4540d84af32131829ff948f1e022c1c6bdb8d6102117aac784f6859"}, {file = "wcwidth-0.2.13.tar.gz", hash = "sha256:72ea0c06399eb286d978fdedb6923a9eb47e1c486ce63e9b4e64fc18303972b5"}, diff --git a/pyproject.toml b/pyproject.toml index 7cc2c681..bc671874 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -3,7 +3,7 @@ authors = [ {name = "sax", email = "s.apostolico@gmail.com"}, {name = "Domenico DiNicola", email = "dom.dinicola@gmail.com"}, ] -requires-python = ">=3.11" +requires-python = ">=3.12" dependencies = [ "Faker", "Markdown", @@ -68,6 +68,7 @@ dependencies = [ "beautifulsoup4", "django-anymail[mailjet]", "uwsgi", + "django-smart-env>=0.1.0", ] name = "Aurora" version = "0.1" @@ -124,6 +125,7 @@ exclude = ''' | dist | migrations | snapshots + | __pypackages__ )/ ''' diff --git a/src/aurora/__init__.py b/src/aurora/__init__.py index 78cc22f5..7c89e1fd 100644 --- a/src/aurora/__init__.py +++ b/src/aurora/__init__.py @@ -1,8 +1,7 @@ -import os from functools import lru_cache from subprocess import STDOUT -VERSION = os.environ.get("VERSION", "") +VERSION = "2.0.0" @lru_cache(1) diff --git a/src/aurora/config/__init__.py b/src/aurora/config/__init__.py index 0fc25070..98f08d8d 100644 --- a/src/aurora/config/__init__.py +++ b/src/aurora/config/__init__.py @@ -2,6 +2,7 @@ from urllib.parse import urlencode, urlparse from environ import Env +from smart_env import SmartEnv from aurora.core.flags import parse_bool @@ -16,24 +17,12 @@ def parse_emails(value): return v -MANDATORY = { - "CACHE_DEFAULT": (str, "locmemcache://"), - "CHANNEL_LAYER": (str, "locmemcache://"), - "DATABASE_URL": (str, "psql://postgres:@postgres:5432/aurora"), - "DJANGO_ADMIN_URL": (str, f"{uuid.uuid4().hex}/"), - "EMAIL_FROM_EMAIL": (str, ""), - "EMAIL_HOST": (str, ""), - "EMAIL_HOST_PASSWORD": (str, ""), - "EMAIL_HOST_USER": (str, ""), - "EMAIL_SUBJECT_PREFIX": (str, "[Aurora]"), - "FERNET_KEY": (str, uuid.uuid4().hex), - "MEDIA_ROOT": (str, "/tmp/media/"), - "STATIC_ROOT": (str, "/tmp/static/"), -} - OPTIONS = { "ADMINS": (parse_emails, ""), "ADMIN_SYNC_CONFIG": (str, "admin_sync.conf.DjangoConstance"), + "ADMIN_SYNC_LOCAL_ADMIN_URL": (str, ""), + "ADMIN_SYNC_REMOTE_ADMIN_URL": (str, ""), + "ADMIN_SYNC_REMOTE_SERVER": (str, ""), "ALLOWED_HOSTS": (list, ["*"]), "AUTHENTICATION_BACKENDS": (list, []), "AZURE_AUTHORITY_HOST": (str, ""), @@ -43,63 +32,73 @@ def parse_emails(value): "AZURE_POLICY_NAME": (str, ""), "AZURE_TENANT_ID": (str, ""), "AZURE_TENANT_KEY": (str, ""), - "CAPTCHA_TEST_MODE": (bool, "false"), - "TRANSLATOR_SERVICE": (str, ""), "AZURE_TRANSLATOR_KEY": (str, ""), "AZURE_TRANSLATOR_LOCATION": (str, ""), + "CACHE_DEFAULT": (str, "locmemcache://", "", True), + "CAPTCHA_TEST_MODE": (bool, "false"), + "CHANNEL_LAYER": (str, "locmemcache://", True), "CONSTANCE_DATABASE_CACHE_BACKEND": (str, ""), "CORS_ALLOWED_ORIGINS": (list, []), - "CSP_REPORT_ONLY": (bool, True), + "CSP_REPORT_ONLY": (bool, False, True), "CSRF_COOKIE_NAME": (str, "aurora"), + "CSRF_COOKIE_SECURE": (bool, True, False), + "CSRF_TRUSTED_ORIGINS": (list, [], []), + "DATABASE_URL": (str, "psql://postgres:@postgres:5432/aurora", True), "DEBUG": (bool, False), "DEBUG_PROPAGATE_EXCEPTIONS": (bool, False), "DEFAULT_FILE_STORAGE": (str, "django.core.files.storage.FileSystemStorage"), - "DJANGO_ADMIN_TITLE": (str, "Aurora"), + "DJANGO_ADMIN_URL": (str, f"{uuid.uuid4().hex}/", True), "EMAIL_BACKEND": (str, "anymail.backends.mailjet.EmailBackend"), - "MAILJET_API_KEY": (str, ""), - "MAILJET_SECRET_KEY": (str, ""), + "EMAIL_FROM_EMAIL": (str, ""), + "EMAIL_HOST": (str, ""), + "EMAIL_HOST_PASSWORD": (str, ""), + "EMAIL_HOST_USER": (str, ""), "EMAIL_PORT": (int, 587), - # "EMAIL_SUBJECT_PREFIX": (str, "[Aurora]"), + "EMAIL_SUBJECT_PREFIX": (str, "[Aurora]"), "EMAIL_TIMEOUT": (int, 30), "EMAIL_USE_LOCALTIME": (bool, False), "EMAIL_USE_SSL": (bool, False), "EMAIL_USE_TLS": (bool, True), - "FRONT_DOOR_ENABLED": (bool, False), + "FERNET_KEY": (str, "", uuid.uuid4().hex, True), "FRONT_DOOR_ALLOWED_PATHS": (str, ".*"), - "FRONT_DOOR_TOKEN": (str, uuid.uuid4()), + "FRONT_DOOR_ENABLED": (bool, False), "FRONT_DOOR_LOG_LEVEL": (str, "ERROR"), - # "FERNET_KEY": (str, "2jQklRvSAZUdsVOKH-521Wbf_p5t2nTDA0LgD9sgim4="), - "INTERNAL_IPS": (list, ["127.0.0.1", "localhost"]), + "FRONT_DOOR_TOKEN": (str, uuid.uuid4()), + "INTERNAL_IPS": (list, [], ["127.0.0.1", "localhost"]), + "JWT_LEEWAY": (int, 0), "LANGUAGE_CODE": (str, "en-us"), "LOG_LEVEL": (str, "ERROR"), + "MAILJET_API_KEY": (str, ""), + "MAILJET_SECRET_KEY": (str, ""), + "MATOMO_ID": (str, ""), + "MATOMO_SITE": (str, ""), + "MEDIA_ROOT": (str, "/tmp/media/"), "MIGRATION_LOCK_KEY": (str, "django-migrations"), "PRODUCTION_SERVER": (str, ""), "PRODUCTION_TOKEN": (str, ""), "REDIS_CONNSTR": (str, ""), - "ROOT_KEY": (str, uuid.uuid4().hex), - "ROOT_TOKEN": (str, uuid.uuid4().hex), - "SECRET_KEY": (str, ""), + "ROOT_KEY": (str, ""), + "ROOT_TOKEN": (str, ""), + "SECRET_KEY": (str, "", "", True), "SENTRY_DSN": (str, ""), "SENTRY_PROJECT": (str, ""), "SENTRY_SECURITY_TOKEN": (str, ""), "SENTRY_SECURITY_TOKEN_HEADER": (str, "X-Sentry-Token"), - "SESSION_COOKIE_DOMAIN": (str, "localhost"), + "SESSION_COOKIE_DOMAIN": (str, "", "", True), "SESSION_COOKIE_NAME": (str, "aurora_id"), - "SESSION_COOKIE_SECURE": (bool, "false"), + "SESSION_COOKIE_SECURE": (bool, True, False, True), + "SITE_ID": (int, 1), "SMART_ADMIN_BOOKMARKS": (parse_bookmarks, ""), "STATICFILES_STORAGE": (str, "aurora.web.storage.ForgivingManifestStaticFilesStorage"), + "STATIC_ROOT": (str, "/tmp/static/"), + "STATIC_URL": (str, "static/"), + "TRANSLATOR_SERVICE": (str, ""), "USE_HTTPS": (bool, False), "USE_X_FORWARDED_HOST": (bool, "false"), - "SITE_ID": (int, 1), - # "CSP_DEFAULT_SRC": (list, ), - # "CSP_SCRIPT_SRC": (str, None), - # "FERNET_KEY": (str, "Nl_puP2z0-OKVNKMtPXx4jEI-ox7sKLM7CgnGT-yAug="), - # "STATIC_ROOT": (str, "/tmp/static/"), - # Sentry - see CONTRIBUTING.md } -class SmartEnv(Env): +class SmartEnv2(SmartEnv): def cache_url(self, var=Env.DEFAULT_CACHE_ENV, default=Env.NOTSET, backend=None): v = self.str(var, default) if v.startswith("redisraw://"): @@ -119,4 +118,4 @@ def cache_url(self, var=Env.DEFAULT_CACHE_ENV, default=Env.NOTSET, backend=None) return super().cache_url(var, default, backend) -env = SmartEnv(**MANDATORY, **OPTIONS) +env = SmartEnv2(**OPTIONS) diff --git a/src/aurora/config/fragments/smart_admin.py b/src/aurora/config/fragments/smart_admin.py index ed42ca4e..3ff4d41e 100644 --- a/src/aurora/config/fragments/smart_admin.py +++ b/src/aurora/config/fragments/smart_admin.py @@ -13,7 +13,7 @@ "_hidden_": [], } SMART_ADMIN_TITLE = "=" -SMART_ADMIN_HEADER = env("DJANGO_ADMIN_TITLE") +SMART_ADMIN_HEADER = "Aurora" SMART_ADMIN_BOOKMARKS = "aurora.core.utils.get_bookmarks" SMART_ADMIN_PROFILE_LINK = True diff --git a/src/aurora/config/fragments/social_auth.py b/src/aurora/config/fragments/social_auth.py index a9ff2736..4f2624b8 100644 --- a/src/aurora/config/fragments/social_auth.py +++ b/src/aurora/config/fragments/social_auth.py @@ -40,4 +40,4 @@ ] SOCIAL_AUTH_SANITIZE_REDIRECTS = True -SOCIAL_AUTH_JWT_LEEWAY = env.int("JWT_LEEWAY", 0) +SOCIAL_AUTH_JWT_LEEWAY = env("JWT_LEEWAY") diff --git a/src/aurora/config/settings.py b/src/aurora/config/settings.py index caa2a2ce..3eaf7eb7 100644 --- a/src/aurora/config/settings.py +++ b/src/aurora/config/settings.py @@ -31,6 +31,7 @@ SITE_ID = env("SITE_ID") INSTALLED_APPS = [ "daphne", + "smart_env", "django.contrib.auth", "django.contrib.contenttypes", "django.contrib.sessions", @@ -291,7 +292,9 @@ CSRF_COOKIE_NAME = env("CSRF_COOKIE_NAME") CSRF_HEADER_NAME = "HTTP_X_CSRFTOKEN" -CSRF_COOKIE_SECURE = False +CSRF_TRUSTED_ORIGINS = env("CSRF_TRUSTED_ORIGINS") +CSRF_COOKIE_SECURE = env("CSRF_COOKIE_SECURE") + SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https") USE_X_FORWARDED_HOST = env("USE_X_FORWARDED_HOST") diff --git a/src/aurora/management/commands/env.py b/src/aurora/management/commands/env.py deleted file mode 100644 index 5d8b3d6b..00000000 --- a/src/aurora/management/commands/env.py +++ /dev/null @@ -1,54 +0,0 @@ -import os - -from django.core.management import BaseCommand - - -class Command(BaseCommand): - def add_arguments(self, parser): - parser.add_argument( - "--no-mandatory", action="store_false", dest="mandatory", default=True, help="Do not dump mandatory" - ) - parser.add_argument( - "--no-optional", action="store_false", dest="optional", default=True, help="Do not dump optional" - ) - parser.add_argument("--no-values", action="store_false", dest="values", default=True, help="Do not dump values") - parser.add_argument( - "--comment-optional", action="store_true", dest="comment", default=False, help="Comment optional" - ) - parser.add_argument("--current", action="store_true", dest="current", default=False, help="Dump current values") - parser.add_argument("--vars", action="store_true", dest="vars", default=False, help="Dump current values") - parser.add_argument( - "--defaults", action="store_true", dest="defaults", default=False, help="Dump default values" - ) - parser.add_argument( - "--no-empty", action="store_true", dest="no_empty", default=False, help="Do not dump empty values" - ) - - def handle(self, *args, **options): - from aurora.config import env, MANDATORY, OPTIONS, SmartEnv - - if options["defaults"]: - EE = type("SmartEnv", (SmartEnv,), {"ENVIRON": {}}) - ee = EE(**MANDATORY, **OPTIONS) - - environment = {} - if options["mandatory"]: - environment.update(**MANDATORY) - if options["optional"]: - environment.update(**OPTIONS) - for k, v in sorted(environment.items()): - if options["defaults"]: - value = ee(k) - elif options["vars"]: - value = "${%s}" % k - elif options["current"]: - value = os.environ.get(k, "") - elif options["values"]: - value = env(k) - else: - value = "" - if value or not options["no_empty"]: - if options["comment"] and k in OPTIONS.keys(): - self.stdout.write(f"#{k}={value}") - else: - self.stdout.write(f"{k}={value}") diff --git a/src/aurora/management/commands/upgrade.py b/src/aurora/management/commands/upgrade.py index 58c0550e..8551ed5a 100644 --- a/src/aurora/management/commands/upgrade.py +++ b/src/aurora/management/commands/upgrade.py @@ -54,7 +54,7 @@ def upgrade(admin_email, admin_password, static, migrate, prompt, verbosity, org # ensure project/org click.echo("Set default Org/Project") - UNICEF, __ = Organization.objects.get_or_create(slug="unicef", defaults={"name": "UNICEF"}) + UNICEF, __ = Organization.objects.get_or_create(slug="unicef", defaults={"name": organization}) DEF, __ = Project.objects.get_or_create(slug="default-project", organization=UNICEF) Project.objects.filter(organization__isnull=True).update(organization=UNICEF) diff --git a/src/aurora/web/middlewares/admin.py b/src/aurora/web/middlewares/admin.py index 5ed69461..e7cf01ec 100644 --- a/src/aurora/web/middlewares/admin.py +++ b/src/aurora/web/middlewares/admin.py @@ -27,9 +27,7 @@ def __init__(self, get_response): self.get_response = get_response def __call__(self, request): - if is_root(request) or request.user.is_staff: - return self.get_response(request) - else: + if config.WAF_REGISTRATION_ALLOWED_HOSTNAMES: parts = urlparse(request.build_absolute_uri()) try: if parts.path.startswith(f"/{settings.DJANGO_ADMIN_URL}"): @@ -42,3 +40,5 @@ def __call__(self, request): logging.exception(e) ret = self.get_response(request) return ret + else: + return self.get_response(request) diff --git a/src/aurora/web/templates/_footer.html b/src/aurora/web/templates/_footer.html index 118f36f2..336a0510 100644 --- a/src/aurora/web/templates/_footer.html +++ b/src/aurora/web/templates/_footer.html @@ -1,7 +1,7 @@ {% load matomo %} {% block footer %}
{% include "_matomo.html" %} {% endblock footer %} diff --git a/src/aurora/web/templates/base.html b/src/aurora/web/templates/base.html index 3f4a8251..ed4df8c7 100644 --- a/src/aurora/web/templates/base.html +++ b/src/aurora/web/templates/base.html @@ -72,214 +72,11 @@ {% block body %}{% endblock body %} {% block body_bottom %}{% endblock %} -{% block footer %}{% include "_footer.html" %}{% endblock %} - -{##} - +{% block footer %} + + {% include "_matomo.html" %} +{% endblock footer %}