From 423672a0df0559b4a9dad9fd3bcdd8dc603c2bbe Mon Sep 17 00:00:00 2001 From: JensDiemer Date: Mon, 11 Jul 2022 20:34:24 +0200 Subject: [PATCH] init --- .editorconfig | 21 + .flake8 | 7 + .gitignore | 10 + LICENSE | 8 +- Makefile | 59 ++ README.md | 194 ++++- README_fr.md | 188 +++++ check_process | 33 + conf/django-fmd.service | 17 + conf/gunicorn.conf.py | 20 + conf/local_settings.py | 8 + conf/manage.py | 15 + conf/nginx.conf | 34 + conf/requirements.txt | 166 +++++ conf/settings.py | 174 +++++ conf/setup_user.py | 8 + conf/urls.py | 22 + conf/wsgi.py | 12 + config_panel.toml | 20 + doc/DESCRIPTION.md | 14 + doc/DISCLAIMER.md | 128 ++++ local_test.py | 29 + manifest.json | 46 ++ poetry.lock | 1327 ++++++++++++++++++++++++++++++++++ pyproject.toml | 112 +++ run_pytest.py | 25 + scripts/_common.sh | 90 +++ scripts/backup | 69 ++ scripts/change_url | 159 ++++ scripts/install | 249 +++++++ scripts/remove | 103 +++ scripts/restore | 173 +++++ scripts/upgrade | 244 +++++++ test_requirements.sh | 17 + tests/__init__.py | 0 tests/test_django_project.py | 176 +++++ tests/test_project_setup.py | 89 +++ tests/test_utils.py | 8 + 38 files changed, 4068 insertions(+), 6 deletions(-) create mode 100644 .editorconfig create mode 100644 .flake8 create mode 100644 .gitignore create mode 100644 Makefile create mode 100644 README_fr.md create mode 100644 check_process create mode 100644 conf/django-fmd.service create mode 100644 conf/gunicorn.conf.py create mode 100644 conf/local_settings.py create mode 100755 conf/manage.py create mode 100644 conf/nginx.conf create mode 100644 conf/requirements.txt create mode 100644 conf/settings.py create mode 100644 conf/setup_user.py create mode 100644 conf/urls.py create mode 100644 conf/wsgi.py create mode 100644 config_panel.toml create mode 100644 doc/DESCRIPTION.md create mode 100644 doc/DISCLAIMER.md create mode 100644 local_test.py create mode 100644 manifest.json create mode 100644 poetry.lock create mode 100644 pyproject.toml create mode 100644 run_pytest.py create mode 100644 scripts/_common.sh create mode 100755 scripts/backup create mode 100644 scripts/change_url create mode 100755 scripts/install create mode 100755 scripts/remove create mode 100755 scripts/restore create mode 100755 scripts/upgrade create mode 100755 test_requirements.sh create mode 100644 tests/__init__.py create mode 100644 tests/test_django_project.py create mode 100644 tests/test_project_setup.py create mode 100644 tests/test_utils.py diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..0f4b35f --- /dev/null +++ b/.editorconfig @@ -0,0 +1,21 @@ +# see http://editorconfig.org +root = true + +[*] +indent_style = space +indent_size = 4 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +[*.py] +line_length = 119 + +[{Makefile,**.mk}] +indent_style = tab +insert_final_newline = false + +[*.yml] +indent_style = tab +indent_size = 4 diff --git a/.flake8 b/.flake8 new file mode 100644 index 0000000..6ad2ff9 --- /dev/null +++ b/.flake8 @@ -0,0 +1,7 @@ +# +# Move to pyproject.toml after: https://gitlab.com/pycqa/flake8/-/issues/428 +# +[flake8] +exclude = .pytest_cache, .tox, dist, htmlcov, local_test +ignore = F405 +max-line-length = 119 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d4aff27 --- /dev/null +++ b/.gitignore @@ -0,0 +1,10 @@ +.* +!.github +!.editorconfig +!.flake8 +!.gitignore +__pycache__ +secret.txt +/local_test/ +/coverage.xml +/htmlcov/ diff --git a/LICENSE b/LICENSE index f288702..94a9ed0 100644 --- a/LICENSE +++ b/LICENSE @@ -1,7 +1,7 @@ GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 - Copyright (C) 2007 Free Software Foundation, Inc. + Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. @@ -645,7 +645,7 @@ the "copyright" line and a pointer to where the full notice is found. GNU General Public License for more details. You should have received a copy of the GNU General Public License - along with this program. If not, see . + along with this program. If not, see . Also add information on how to contact you by electronic and paper mail. @@ -664,11 +664,11 @@ might be different; for a GUI interface, you would use an "about box". You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU GPL, see -. +. The GNU General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read -. +. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..650fd20 --- /dev/null +++ b/Makefile @@ -0,0 +1,59 @@ +SHELL := /bin/bash +MAX_LINE_LENGTH := 119 + +all: help + +help: + @awk 'BEGIN {FS = ":.*?## "} /^[a-zA-Z0-9 -]+:.*?## / {printf "\033[36m%-22s\033[0m %s\n", $$1, $$2}' $(MAKEFILE_LIST) + +check-poetry: + @if [[ "$(shell poetry --version 2>/dev/null)" == *"Poetry"* ]] ; \ + then \ + echo "Poetry found, ok." ; \ + else \ + echo 'Please install poetry first, with e.g.:' ; \ + echo 'make install-poetry' ; \ + exit 1 ; \ + fi + +install-poetry: ## install or update poetry + pip3 install -U pip + pip3 install -U poetry + +install: check-poetry ## install project via poetry + poetry install + +update: install-poetry ## update the sources and installation and generate "conf/requirements.txt" + poetry run pip install -U pip + poetry update + poetry export -f requirements.txt --output conf/requirements.txt + +lint: ## Run code formatters and linter + poetry run flynt --fail-on-change --line-length=${MAX_LINE_LENGTH} . + poetry run isort --check-only . + poetry run flake8 . + +fix-code-style: ## Fix code formatting + poetry run flynt --line-length=${MAX_LINE_LENGTH} . + poetry run black --verbose --safe --line-length=${MAX_LINE_LENGTH} --skip-string-normalization . + poetry run isort . + +tox-listenvs: check-poetry ## List all tox test environments + poetry run tox --listenvs + +tox: check-poetry ## Run pytest via tox with all environments + poetry run tox + +pytest: install ## Run pytest + poetry run python3 ./run_pytest.py + +local-test: install ## Run local_test.py to run the project locally + poetry run python3 ./local_test.py + +local-diff-settings: ## Run "manage.py diffsettings" with local test + poetry run python3 local_test/opt_yunohost/manage.py diffsettings + + +############################################################################## + +.PHONY: help check-poetry install-poetry install update local-test \ No newline at end of file diff --git a/README.md b/README.md index 974bd16..8948533 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,192 @@ -# django-fmd_ynh -Find My Device Server -> https://gitlab.com/jedie/django-find-my-device + + +# django-fmd for YunoHost + +[![Integration level](https://dash.yunohost.org/integration/django-fmd.svg)](https://dash.yunohost.org/appci/app/django-fmd) ![](https://ci-apps.yunohost.org/ci/badges/django-fmd.status.svg) ![](https://ci-apps.yunohost.org/ci/badges/django-fmd.maintain.svg) +[![Install django-fmd with YunoHost](https://install-app.yunohost.org/install-with-yunohost.svg)](https://install-app.yunohost.org/?app=django-fmd) + +*[Lire ce readme en français.](./README_fr.md)* + +> *This package allows you to install django-fmd quickly and simply on a YunoHost server. +If you don't have YunoHost, please consult [the guide](https://yunohost.org/#/install) to learn how to install it.* + +## Overview + +Web based FritzBox management using Python/Django and the great [fritzconnection](https://github.com/kbr/fritzconnection) library. + +The basic idea is to block/unblock Internet access to a group of devices as easily as possible. + + +[![pytest](https://github.com/YunoHost-Apps/django-fmd_ynh/actions/workflows/pytest.yml/badge.svg?branch=master)](https://github.com/YunoHost-Apps/django-fmd_ynh/actions/workflows/pytest.yml) [![YunoHost apps package linter](https://github.com/YunoHost-Apps/django-fmd_ynh/actions/workflows/package_linter.yml/badge.svg)](https://github.com/YunoHost-Apps/django-fmd_ynh/actions/workflows/package_linter.yml) [![Coverage Status on codecov.io](https://codecov.io/gh/YunoHost-Apps/django-fmd_ynh/branch/master/graph/badge.svg)](https://codecov.io/gh/YunoHost-Apps/django-fmd_ynh) + +![django-fmd @ PyPi](https://img.shields.io/pypi/v/django-fmd?label=django-fmd%20%40%20PyPi) +![Python Versions](https://img.shields.io/pypi/pyversions/django-fmd) +![License GPL V3+](https://img.shields.io/pypi/l/django-fmd) + +Pull requests welcome ;) + +This package for YunoHost used [django-yunohost-integration](https://github.com/YunoHost-Apps/django_yunohost_integration) + + +**Shipped version:** 0.2.0~ynh1 + + + +## Screenshots + +![](./doc/screenshots/v002_hosts_change_list.png) +![](./doc/screenshots/v010rc1_group_management.png) + +## Disclaimers / important information + +## Settings and upgrades + +Almost everything related to django-fmd's configuration is handled in a `"../conf/settings.py"` file. +You can edit the file `/opt/yunohost/django-fmd/local_settings.py` to enable or disable features. + +Test sending emails: + +```bash +ssh admin@yourdomain.tld +root@yunohost:~# cd /opt/yunohost/django-fmd/ +root@yunohost:/opt/yunohost/django-fmd# source venv/bin/activate +(venv) root@yunohost:/opt/yunohost/django-fmd# ./manage.py sendtestemail --admins +``` + +Background info: Error mails are send to all [settings.ADMINS](https://docs.djangoproject.com/en/2.2/ref/settings/#std:setting-ADMINS). By default the YunoHost admin is inserted here. +To check current ADMINS run: + +```bash +(venv) root@yunohost:/opt/yunohost/django-fmd# ./manage.py sendtestemail --admins +``` + +If you prefere to send error emails to a extrnal email address, just do something like this: + +```bash +echo "ADMINS = (('Your Name', 'example@domain.tld'),)" >> /opt/yunohost/django-fmd/local_settings.py +``` + +To check the effective settings, run this: +```bash +(venv) root@yunohost:/opt/yunohost/django-fmd# ./manage.py diffsettings +``` + + +# Miscellaneous + + +## SSO authentication + +[SSOwat](https://github.com/YunoHost/SSOwat) is fully supported via [django-yunohost-integration](https://github.com/YunoHost-Apps/django_yunohost_integration): + +* First user (`$YNH_APP_ARG_ADMIN`) will be created as Django's super user +* All new users will be created as normal users +* Login via SSO is fully supported +* User Email, First / Last name will be updated from SSO data + + +--- + +# Yunohost developer commands + +To remove call e.g.: +```bash +sudo yunohost app remove django-fmd +``` + +Backup / remove / restore cycle, e.g.: +```bash +yunohost backup create --apps django-fmd +yunohost backup list +archives: + - django-fmd-pre-upgrade1 + - 20201223-163434 +yunohost app remove django-fmd +yunohost backup restore 20201223-163434 --apps django-fmd +``` + +Debug installation, e.g.: +```bash +root@yunohost:~# ls -la /var/www/django-fmd/ +total 18 +drwxr-xr-x 4 root root 4 Dec 8 08:36 . +drwxr-xr-x 6 root root 6 Dec 8 08:36 .. +drwxr-xr-x 2 root root 2 Dec 8 08:36 media +drwxr-xr-x 7 root root 8 Dec 8 08:40 static + +root@yunohost:~# ls -la /opt/yunohost/django-fmd/ +total 58 +drwxr-xr-x 5 django-fmd django-fmd 11 Dec 8 08:39 . +drwxr-xr-x 3 root root 3 Dec 8 08:36 .. +-rw-r--r-- 1 django-fmd django-fmd 460 Dec 8 08:39 gunicorn.conf.py +-rw-r--r-- 1 django-fmd django-fmd 0 Dec 8 08:39 local_settings.py +-rwxr-xr-x 1 django-fmd django-fmd 274 Dec 8 08:39 manage.py +-rw-r--r-- 1 django-fmd django-fmd 171 Dec 8 08:39 secret.txt +drwxr-xr-x 6 django-fmd django-fmd 6 Dec 8 08:37 venv +-rw-r--r-- 1 django-fmd django-fmd 115 Dec 8 08:39 wsgi.py +-rw-r--r-- 1 django-fmd django-fmd 4737 Dec 8 08:39 settings.py + +root@yunohost:~# cd /opt/yunohost/django-fmd/ +root@yunohost:/opt/yunohost/django-fmd# source venv/bin/activate +(venv) root@yunohost:/opt/yunohost/django-fmd# ./manage.py check +django-fmd v0.8.2 (Django v2.2.17) +DJANGO_SETTINGS_MODULE='settings' +PROJECT_PATH:/opt/yunohost/django-fmd/venv/lib/python3.7/site-packages +BASE_PATH:/opt/yunohost/django-fmd +System check identified no issues (0 silenced). + +root@yunohost:~# tail -f /var/log/django-fmd/django-fmd.log +root@yunohost:~# cat /etc/systemd/system/django-fmd.service + +root@yunohost:~# systemctl reload-or-restart django-fmd +root@yunohost:~# journalctl --unit=django-fmd --follow +``` + +## local test + +For quicker developing of django-fmd in the context of YunoHost app, +it's possible to run the Django developer server with the settings +and urls made for YunoHost installation. + +e.g.: +```bash +~$ git clone https://github.com/YunoHost-Apps/django-fmd_ynh.git +~$ cd django-fmd_ynh/ +~/django-fmd_ynh$ make +install-poetry install or update poetry +install install django-fmd via poetry +update update the sources and installation +local-test Run local_test.py to run django-fmd_ynh locally +~/django-fmd_ynh$ make install-poetry +~/django-fmd_ynh$ make install +~/django-fmd_ynh$ make local-test +``` + +Notes: + +* SQlite database will be used +* A super user with username `test` and password `test` is created +* The page is available under `http://127.0.0.1:8000/app_path/` + +## Documentation and resources + +* Official app website: https://gitlab.com/jedie/django-find-my-device +* Upstream app code repository: https://gitlab.com/jedie/django-find-my-device +* YunoHost documentation for this app: https://yunohost.org/app_django-fmd +* Report a bug: https://github.com/YunoHost-Apps/django-fmd_ynh/issues + +## Developer info + +Please send your pull request to the [testing branch](https://github.com/YunoHost-Apps/django-fmd_ynh/tree/testing). + +To try the testing branch, please proceed like that. +``` +sudo yunohost app install https://github.com/YunoHost-Apps/django-fmd_ynh/tree/testing --debug +or +sudo yunohost app upgrade django-fmd -u https://github.com/YunoHost-Apps/django-fmd_ynh/tree/testing --debug +``` + +**More info regarding app packaging:** https://yunohost.org/packaging_apps diff --git a/README_fr.md b/README_fr.md new file mode 100644 index 0000000..a766233 --- /dev/null +++ b/README_fr.md @@ -0,0 +1,188 @@ +# django-fmd pour YunoHost + +[![Niveau d'intégration](https://dash.yunohost.org/integration/django-fmd.svg)](https://dash.yunohost.org/appci/app/django-fmd) ![](https://ci-apps.yunohost.org/ci/badges/django-fmd.status.svg) ![](https://ci-apps.yunohost.org/ci/badges/django-fmd.maintain.svg) +[![Installer django-fmd avec YunoHost](https://install-app.yunohost.org/install-with-yunohost.svg)](https://install-app.yunohost.org/?app=django-fmd) + +*[Read this readme in english.](./README.md)* +*[Lire ce readme en français.](./README_fr.md)* + +> *Ce package vous permet d'installer django-fmd rapidement et simplement sur un serveur YunoHost. +Si vous n'avez pas YunoHost, regardez [ici](https://yunohost.org/#/install) pour savoir comment l'installer et en profiter.* + +## Vue d'ensemble + +Web based FritzBox management using Python/Django and the great [fritzconnection](https://github.com/kbr/fritzconnection) library. + +The basic idea is to block/unblock Internet access to a group of devices as easily as possible. + + +[![pytest](https://github.com/YunoHost-Apps/django-fmd_ynh/actions/workflows/pytest.yml/badge.svg?branch=master)](https://github.com/YunoHost-Apps/django-fmd_ynh/actions/workflows/pytest.yml) [![YunoHost apps package linter](https://github.com/YunoHost-Apps/django-fmd_ynh/actions/workflows/package_linter.yml/badge.svg)](https://github.com/YunoHost-Apps/django-fmd_ynh/actions/workflows/package_linter.yml) [![Coverage Status on codecov.io](https://codecov.io/gh/YunoHost-Apps/django-fmd_ynh/branch/master/graph/badge.svg)](https://codecov.io/gh/YunoHost-Apps/django-fmd_ynh) + +![django-fmd @ PyPi](https://img.shields.io/pypi/v/django-fmd?label=django-fmd%20%40%20PyPi) +![Python Versions](https://img.shields.io/pypi/pyversions/django-fmd) +![License GPL V3+](https://img.shields.io/pypi/l/django-fmd) + +Pull requests welcome ;) + +This package for YunoHost used [django-yunohost-integration](https://github.com/YunoHost-Apps/django_yunohost_integration) + + +**Version incluse :** 0.2.0~ynh1 + + + +## Captures d'écran + +![](./doc/screenshots/v002_hosts_change_list.png) +![](./doc/screenshots/v010rc1_group_management.png) + +## Avertissements / informations importantes + +## Settings and upgrades + +Almost everything related to django-fmd's configuration is handled in a `"../conf/settings.py"` file. +You can edit the file `/opt/yunohost/django-fmd/local_settings.py` to enable or disable features. + +Test sending emails: + +```bash +ssh admin@yourdomain.tld +root@yunohost:~# cd /opt/yunohost/django-fmd/ +root@yunohost:/opt/yunohost/django-fmd# source venv/bin/activate +(venv) root@yunohost:/opt/yunohost/django-fmd# ./manage.py sendtestemail --admins +``` + +Background info: Error mails are send to all [settings.ADMINS](https://docs.djangoproject.com/en/2.2/ref/settings/#std:setting-ADMINS). By default the YunoHost admin is inserted here. +To check current ADMINS run: + +```bash +(venv) root@yunohost:/opt/yunohost/django-fmd# ./manage.py sendtestemail --admins +``` + +If you prefere to send error emails to a extrnal email address, just do something like this: + +```bash +echo "ADMINS = (('Your Name', 'example@domain.tld'),)" >> /opt/yunohost/django-fmd/local_settings.py +``` + +To check the effective settings, run this: +```bash +(venv) root@yunohost:/opt/yunohost/django-fmd# ./manage.py diffsettings +``` + + +# Miscellaneous + + +## SSO authentication + +[SSOwat](https://github.com/YunoHost/SSOwat) is fully supported via [django-yunohost-integration](https://github.com/YunoHost-Apps/django_yunohost_integration): + +* First user (`$YNH_APP_ARG_ADMIN`) will be created as Django's super user +* All new users will be created as normal users +* Login via SSO is fully supported +* User Email, First / Last name will be updated from SSO data + + +--- + +# Yunohost developer commands + +To remove call e.g.: +```bash +sudo yunohost app remove django-fmd +``` + +Backup / remove / restore cycle, e.g.: +```bash +yunohost backup create --apps django-fmd +yunohost backup list +archives: + - django-fmd-pre-upgrade1 + - 20201223-163434 +yunohost app remove django-fmd +yunohost backup restore 20201223-163434 --apps django-fmd +``` + +Debug installation, e.g.: +```bash +root@yunohost:~# ls -la /var/www/django-fmd/ +total 18 +drwxr-xr-x 4 root root 4 Dec 8 08:36 . +drwxr-xr-x 6 root root 6 Dec 8 08:36 .. +drwxr-xr-x 2 root root 2 Dec 8 08:36 media +drwxr-xr-x 7 root root 8 Dec 8 08:40 static + +root@yunohost:~# ls -la /opt/yunohost/django-fmd/ +total 58 +drwxr-xr-x 5 django-fmd django-fmd 11 Dec 8 08:39 . +drwxr-xr-x 3 root root 3 Dec 8 08:36 .. +-rw-r--r-- 1 django-fmd django-fmd 460 Dec 8 08:39 gunicorn.conf.py +-rw-r--r-- 1 django-fmd django-fmd 0 Dec 8 08:39 local_settings.py +-rwxr-xr-x 1 django-fmd django-fmd 274 Dec 8 08:39 manage.py +-rw-r--r-- 1 django-fmd django-fmd 171 Dec 8 08:39 secret.txt +drwxr-xr-x 6 django-fmd django-fmd 6 Dec 8 08:37 venv +-rw-r--r-- 1 django-fmd django-fmd 115 Dec 8 08:39 wsgi.py +-rw-r--r-- 1 django-fmd django-fmd 4737 Dec 8 08:39 settings.py + +root@yunohost:~# cd /opt/yunohost/django-fmd/ +root@yunohost:/opt/yunohost/django-fmd# source venv/bin/activate +(venv) root@yunohost:/opt/yunohost/django-fmd# ./manage.py check +django-fmd v0.8.2 (Django v2.2.17) +DJANGO_SETTINGS_MODULE='settings' +PROJECT_PATH:/opt/yunohost/django-fmd/venv/lib/python3.7/site-packages +BASE_PATH:/opt/yunohost/django-fmd +System check identified no issues (0 silenced). + +root@yunohost:~# tail -f /var/log/django-fmd/django-fmd.log +root@yunohost:~# cat /etc/systemd/system/django-fmd.service + +root@yunohost:~# systemctl reload-or-restart django-fmd +root@yunohost:~# journalctl --unit=django-fmd --follow +``` + +## local test + +For quicker developing of django-fmd in the context of YunoHost app, +it's possible to run the Django developer server with the settings +and urls made for YunoHost installation. + +e.g.: +```bash +~$ git clone https://github.com/YunoHost-Apps/django-fmd_ynh.git +~$ cd django-fmd_ynh/ +~/django-fmd_ynh$ make +install-poetry install or update poetry +install install django-fmd via poetry +update update the sources and installation +local-test Run local_test.py to run django-fmd_ynh locally +~/django-fmd_ynh$ make install-poetry +~/django-fmd_ynh$ make install +~/django-fmd_ynh$ make local-test +``` + +Notes: + +* SQlite database will be used +* A super user with username `test` and password `test` is created +* The page is available under `http://127.0.0.1:8000/app_path/` + +## Documentations et ressources + +* Site officiel de l'app : https://gitlab.com/jedie/django-find-my-device +* Dépôt de code officiel de l'app : https://gitlab.com/jedie/django-find-my-device +* Documentation YunoHost pour cette app : https://yunohost.org/app_django-fmd +* Signaler un bug : https://github.com/YunoHost-Apps/django-fmd_ynh/issues + +## Informations pour les développeurs + +Merci de faire vos pull request sur la [branche testing](https://github.com/YunoHost-Apps/django-fmd_ynh/tree/testing). + +Pour essayer la branche testing, procédez comme suit. +``` +sudo yunohost app install https://github.com/YunoHost-Apps/django-fmd_ynh/tree/testing --debug +ou +sudo yunohost app upgrade django-fmd -u https://github.com/YunoHost-Apps/django-fmd_ynh/tree/testing --debug +``` + +**Plus d'infos sur le packaging d'applications :** https://yunohost.org/packaging_apps diff --git a/check_process b/check_process new file mode 100644 index 0000000..2757e35 --- /dev/null +++ b/check_process @@ -0,0 +1,33 @@ +# See here for more information +# https://github.com/YunoHost/package_check#syntax-check_process-file + +# Move this file from check_process.default to check_process when you have filled it. + +;; Test complet + ; Manifest + domain="domain.tld" (DOMAIN) + path="/path" (PATH) + admin="john" (USER) + is_public=1 (PUBLIC|public=1|private=0) + password="pass" + port="666" (PORT) + ; Checks + pkg_linter=1 + setup_sub_dir=1 + setup_root=1 + setup_nourl=0 + setup_private=1 + setup_public=1 + upgrade=1 + backup_restore=1 + multi_instance=1 + port_already_use=0 + change_url=1 +;;; Options +Email= +Notification=none +;;; Upgrade options + ; commit=CommitHash + name=Name and date of the commit. + manifest_arg=domain=DOMAIN&path=PATH&admin=USER&language=fr&is_public=1&password=pass&port=666& + diff --git a/conf/django-fmd.service b/conf/django-fmd.service new file mode 100644 index 0000000..f81544f --- /dev/null +++ b/conf/django-fmd.service @@ -0,0 +1,17 @@ +[Unit] +Description=django-fmd application server +After=redis.service postgresql.service + +[Service] +User=__APP__ +Group=__APP__ +WorkingDirectory=__FINALPATH__/ + +ExecStart=__FINALPATH__/venv/bin/gunicorn --config __FINALPATH__/gunicorn.conf.py wsgi + +StandardOutput=syslog +StandardError=syslog +SyslogIdentifier=__APP__-server + +[Install] +WantedBy=multi-user.target diff --git a/conf/gunicorn.conf.py b/conf/gunicorn.conf.py new file mode 100644 index 0000000..bcab967 --- /dev/null +++ b/conf/gunicorn.conf.py @@ -0,0 +1,20 @@ +""" + Configuration for Gunicorn +""" +import multiprocessing + + +bind = '127.0.0.1:__PORT__' + +# https://docs.gunicorn.org/en/latest/settings.html#workers +workers = multiprocessing.cpu_count() * 2 + 1 + +# https://docs.gunicorn.org/en/latest/settings.html#logging +loglevel = 'info' + +# https://docs.gunicorn.org/en/latest/settings.html#logging +accesslog = '__LOG_FILE__' +errorlog = '__LOG_FILE__' + +# https://docs.gunicorn.org/en/latest/settings.html#pidfile +pidfile = '__FINAL_HOME_PATH__/gunicorn.pid' diff --git a/conf/local_settings.py b/conf/local_settings.py new file mode 100644 index 0000000..6e3b208 --- /dev/null +++ b/conf/local_settings.py @@ -0,0 +1,8 @@ +""" + Here it's possible to overwrite everything in settings.py + + Note: + Used for YunoHost config and will be **overwritten** on every config change! +""" + +DEBUG = __DJANGO_DEBUG__ # Don't turn DEBUG on in production! diff --git a/conf/manage.py b/conf/manage.py new file mode 100755 index 0000000..9962cbd --- /dev/null +++ b/conf/manage.py @@ -0,0 +1,15 @@ +#!__FINAL_HOME_PATH__/venv/bin/python + +import os +import sys + + +def main(): + os.environ['DJANGO_SETTINGS_MODULE'] = 'settings' + from django.core.management import execute_from_command_line + + execute_from_command_line(sys.argv) + + +if __name__ == '__main__': + main() diff --git a/conf/nginx.conf b/conf/nginx.conf new file mode 100644 index 0000000..729b72d --- /dev/null +++ b/conf/nginx.conf @@ -0,0 +1,34 @@ + +location __PATH__/static/ { + # Django static files + alias __PUBLIC_PATH__/static/; + expires 30d; +} + +# TODO: django-sendfile2: +#location __PATH__/media/ { +# # DATA_DIR/media/ +# alias __PUBLIC_PATH__/media/; +# expires 30d; +#} + +location __PATH__/ { + # https://github.com/benoitc/gunicorn/blob/master/examples/nginx.conf + + # this is needed if you have file import via upload enabled + client_max_body_size 100M; + + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Protocol $scheme; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Scheme $scheme; + + proxy_read_timeout 30; + proxy_send_timeout 30; + proxy_connect_timeout 30; + proxy_redirect off; + + proxy_pass http://127.0.0.1:__PORT__; +} diff --git a/conf/requirements.txt b/conf/requirements.txt new file mode 100644 index 0000000..7d08eac --- /dev/null +++ b/conf/requirements.txt @@ -0,0 +1,166 @@ +asgiref==3.5.2; python_version >= "3.7" and python_full_version < "4.0.0" \ + --hash=sha256:1d2880b792ae8757289136f1db2b7b99100ce959b2aa57fd69dab783d05afac4 \ + --hash=sha256:4a29362a6acebe09bf1d6640db38c1dc3d9217c68e6f9f6204d72667fc19a424 +async-timeout==4.0.2; python_version >= "3.7" and python_full_version < "4.0.0" \ + --hash=sha256:2163e1640ddb52b7a8c80d0a67a08587e5d245cc9c553a74a847056bc2976b15 \ + --hash=sha256:8ca1e4fcf50d07413d66d1a5e416e42cfdf5851c981d679a09851a6853383b3c +bleach==5.0.1; python_version >= "3.7" and python_full_version < "4.0.0" \ + --hash=sha256:085f7f33c15bd408dd9b17a4ad77c577db66d76203e5984b1bd59baeee948b2a \ + --hash=sha256:0d03255c47eb9bd2f26aa9bb7f2107732e7e8fe195ca2f64709fcf3b0a4a085c +bx-django-utils==26; python_version >= "3.7" and python_full_version < "4.0.0" \ + --hash=sha256:c04776c4c6b275ddcadae79fd1578e6ae9ba92d8cf752a9ee4f2b70a22f4236e \ + --hash=sha256:19349d0a8fa165d87b4fd544efb61418f7d6c41cfabaa46d0153bb6d844262a1 +bx-py-utils==66; python_version >= "3.6" and python_full_version < "4.0.0" \ + --hash=sha256:00ab6dfc063e8431d195c71c43e1063e3e28790205324fe4ceca676a11fd281a \ + --hash=sha256:c6a9f4f11e3465de676f09f2c6bd888afa6d8ab23a030e1bd9ff2a1697fddcf2 +colorama==0.4.5; python_version >= "3.7" and python_full_version < "3.0.0" and sys_platform == "win32" or python_version >= "3.7" and python_full_version < "4.0.0" and sys_platform == "win32" and python_full_version >= "3.5.0" \ + --hash=sha256:854bf444933e37f5824ae7bfc1e98d5bce2ebe4160d46b5edf346a89358e99da \ + --hash=sha256:e6c6b4334fc50988a639d9b98aa429a0b57da6e17b9a44f0451f930b6967b7a4 +colorlog==6.6.0; python_version >= "3.7" and python_full_version < "4.0.0" \ + --hash=sha256:351c51e866c86c3217f08e4b067a7974a678be78f07f85fc2d55b8babde6d94e \ + --hash=sha256:344f73204009e4c83c5b6beb00b3c45dc70fcdae3c80db919e0a4171d006fde8 +deprecated==1.2.13; python_version >= "3.7" and python_full_version < "3.0.0" or python_version >= "3.7" and python_full_version < "4.0.0" and python_full_version >= "3.4.0" \ + --hash=sha256:64756e3e14c8c5eea9795d93c524551432a0be75629f8f29e67ab8caf076c76d \ + --hash=sha256:43ac5335da90c31c24ba028af536a91d41d53f9e6901ddb021bcc572ce44e38d +django-axes==5.35.0; python_version >= "3.7" and python_full_version < "4.0.0" \ + --hash=sha256:bbe08d72defe8cadab4af12c6dc72c3b40e0e0a771fdeb37c20a3e20a5fc2aec \ + --hash=sha256:f74c096ffff3133a49247bab9299f5280647b70bb27784475d49cbf19672458e +django-debug-toolbar==3.5.0; python_version >= "3.7" and python_full_version < "4.0.0" \ + --hash=sha256:97965f2630692de316ea0c1ca5bfa81660d7ba13146dbc6be2059cf55b35d0e5 \ + --hash=sha256:89a52128309eb4da12738801ff0c202d2ff8730d1c3225fac6acf630c303e661 +django-fmd==0.0.1; python_version >= "3.7" and python_full_version < "4.0.0" \ + --hash=sha256:7518fda3aa09eb8f0779dae04949dc3a22663da2c37f98cb1280c52a3ff1ce1b \ + --hash=sha256:74c7205f49eb0e076ecb96284da1d9e59ff1a7e5549d4ece32ab67a4e135d5de +django-ipware==4.0.2; python_version >= "3.7" and python_full_version < "3.0.0" or python_version >= "3.7" and python_full_version < "4.0.0" and python_full_version >= "3.6.0" \ + --hash=sha256:602a58325a4808bd19197fef2676a0b2da2df40d0ecf21be414b2ff48c72ad05 \ + --hash=sha256:878dbb06a87e25550798e9ef3204ed70a200dd8b15e47dcef848cf08244f04c9 +django-redis==5.2.0; python_version >= "3.7" and python_full_version < "4.0.0" \ + --hash=sha256:8a99e5582c79f894168f5865c52bd921213253b7fd64d16733ae4591564465de \ + --hash=sha256:1d037dc02b11ad7aa11f655d26dac3fb1af32630f61ef4428860a2e29ff92026 +django-tools==0.50.0; python_version >= "3.7" and python_full_version < "4.0.0" \ + --hash=sha256:c53805b60f6745d9d3a729d2b0add72eb27284362e0aef73d72b1a69704e4357 \ + --hash=sha256:4ce9f524ba08d2071a762dc66e7f85ac1664b41cc8fa0ce686a651c1610b3d67 +django-yunohost-integration==0.2.4; python_version >= "3.7" and python_full_version < "4.0.0" \ + --hash=sha256:f5cdb5480025e90de0221d2b1c6282f517fd0c903702563cccb771cb3e1d9417 \ + --hash=sha256:a4b3617a3b39225d6162fa88827e9fe8b65388e26a0bbc23ea665c62aa8cb044 +django==3.2.14; python_version >= "3.7" and python_full_version < "4.0.0" \ + --hash=sha256:a8681e098fa60f7c33a4b628d6fcd3fe983a0939ff1301ecacac21d0b38bad56 \ + --hash=sha256:677182ba8b5b285a4e072f3ac17ceee6aff1b5ce77fd173cc5b6a2d3dc022fcf +gunicorn==20.1.0; python_version >= "3.7" and python_full_version < "4.0.0" \ + --hash=sha256:9dcc4547dbb1cb284accfb15ab5667a0e5d1881cc443e0677b4882a4067a807e \ + --hash=sha256:e0a968b5ba15f8a328fdfd7ab1fcb5af4470c28aaf7e55df02a99bc13138e6e8 +icdiff==2.0.5; python_version >= "3.7" and python_full_version < "4.0.0" \ + --hash=sha256:35d24b728e48b7e0a12bdb69386d3bfc7eef4fe922d0ac1cd70d6e5c11630bae +importlib-metadata==4.2.0; python_version >= "3.7" and python_full_version < "4.0.0" and python_version < "3.8" \ + --hash=sha256:057e92c15bc8d9e8109738a48db0ccb31b4d9d5cfbee5a8670879a30be66304b \ + --hash=sha256:b7e52a1f8dec14a75ea73e0891f3060099ca1d8e6a462a4dff11c3e119ea1b31 +packaging==21.3; python_version >= "3.7" and python_full_version < "4.0.0" \ + --hash=sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522 \ + --hash=sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb +pprintpp==0.4.0; python_version >= "3.7" and python_full_version < "4.0.0" \ + --hash=sha256:b6b4dcdd0c0c0d75e4d7b2f21a9e933e5b2ce62b26e1a54537f9651ae5a5c01d \ + --hash=sha256:ea826108e2c7f49dc6d66c752973c3fc9749142a798d6b254e1e301cfdbc6403 +psycopg2==2.9.3; python_version >= "3.6" \ + --hash=sha256:083707a696e5e1c330af2508d8fab36f9700b26621ccbcb538abe22e15485362 \ + --hash=sha256:d3ca6421b942f60c008f81a3541e8faf6865a28d5a9b48544b0ee4f40cac7fca \ + --hash=sha256:9572e08b50aed176ef6d66f15a21d823bb6f6d23152d35e8451d7d2d18fdac56 \ + --hash=sha256:a81e3866f99382dfe8c15a151f1ca5fde5815fde879348fe5a9884a7c092a305 \ + --hash=sha256:cb10d44e6694d763fa1078a26f7f6137d69f555a78ec85dc2ef716c37447e4b2 \ + --hash=sha256:4295093a6ae3434d33ec6baab4ca5512a5082cc43c0505293087b8a46d108461 \ + --hash=sha256:34b33e0162cfcaad151f249c2649fd1030010c16f4bbc40a604c1cb77173dcf7 \ + --hash=sha256:0762c27d018edbcb2d34d51596e4346c983bd27c330218c56c4dc25ef7e819bf \ + --hash=sha256:8cf3878353cc04b053822896bc4922b194792df9df2f1ad8da01fb3043602126 \ + --hash=sha256:06f32425949bd5fe8f625c49f17ebb9784e1e4fe928b7cce72edc36fb68e4c0c \ + --hash=sha256:8e841d1bf3434da985cc5ef13e6f75c8981ced601fd70cc6bf33351b91562981 +pyparsing==3.0.9; python_version >= "3.7" and python_full_version < "4.0.0" and python_full_version >= "3.6.8" \ + --hash=sha256:5026bae9a10eeaefb61dab2f09052b9f4307d44aee4eda64b309723d8d206bbc \ + --hash=sha256:2b020ecf7d21b687f219b71ecad3631f644a47f01403fa1d1036b0c6416d70fb +python-stdnum==1.17; python_version >= "3.7" and python_full_version < "4.0.0" \ + --hash=sha256:374e2b5e13912ccdbf50b0b23fca2c3e0531174805c32d74e145f37756328340 \ + --hash=sha256:a46e6cf9652807314d369b654b255c86a59f93d18be2834f3d567ed1a346c547 +pytz==2022.1; python_version >= "3.7" and python_full_version < "4.0.0" \ + --hash=sha256:e68985985296d9a66a881eb3193b0906246245294a881e7c8afe623866ac6a5c \ + --hash=sha256:1e760e2fe6a8163bc0b3d9a19c4f84342afa0a2affebfaa84b01b978a02ecaa7 +redis==4.3.4; python_version >= "3.7" and python_full_version < "4.0.0" \ + --hash=sha256:a52d5694c9eb4292770084fa8c863f79367ca19884b329ab574d5cb2036b3e54 \ + --hash=sha256:ddf27071df4adf3821c4f2ca59d67525c3a82e5f268bed97b813cb4fabf87880 +six==1.16.0; python_version >= "3.7" and python_full_version < "3.0.0" or python_version >= "3.7" and python_full_version < "4.0.0" and python_full_version >= "3.3.0" \ + --hash=sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254 \ + --hash=sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926 +sqlparse==0.4.2; python_version >= "3.7" and python_full_version < "4.0.0" \ + --hash=sha256:48719e356bb8b42991bdbb1e8b83223757b93789c00910a616a071910ca4a64d \ + --hash=sha256:0c00730c74263a94e5a9919ade150dfc3b19c574389985446148402998287dae +typing-extensions==4.3.0; python_version >= "3.7" and python_full_version < "4.0.0" and python_version < "3.8" \ + --hash=sha256:25642c956049920a5aa49edcdd6ab1e06d7e5d467fc00e0506c44ac86fbfca02 \ + --hash=sha256:e6d2677a32f47fc7eb2795db1dd15c1f34eff616bcaf2cfb5e997f854fa1c4a6 +webencodings==0.5.1; python_version >= "3.7" and python_full_version < "4.0.0" \ + --hash=sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78 \ + --hash=sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923 +wrapt==1.14.1; python_version >= "3.7" and python_full_version < "3.0.0" or python_version >= "3.7" and python_full_version < "4.0.0" and python_full_version >= "3.5.0" \ + --hash=sha256:1b376b3f4896e7930f1f772ac4b064ac12598d1c38d04907e696cc4d794b43d3 \ + --hash=sha256:903500616422a40a98a5a3c4ff4ed9d0066f3b4c951fa286018ecdf0750194ef \ + --hash=sha256:5a9a0d155deafd9448baff28c08e150d9b24ff010e899311ddd63c45c2445e28 \ + --hash=sha256:ddaea91abf8b0d13443f6dac52e89051a5063c7d014710dcb4d4abb2ff811a59 \ + --hash=sha256:36f582d0c6bc99d5f39cd3ac2a9062e57f3cf606ade29a0a0d6b323462f4dd87 \ + --hash=sha256:7ef58fb89674095bfc57c4069e95d7a31cfdc0939e2a579882ac7d55aadfd2a1 \ + --hash=sha256:e2f83e18fe2f4c9e7db597e988f72712c0c3676d337d8b101f6758107c42425b \ + --hash=sha256:ee2b1b1769f6707a8a445162ea16dddf74285c3964f605877a20e38545c3c462 \ + --hash=sha256:833b58d5d0b7e5b9832869f039203389ac7cbf01765639c7309fd50ef619e0b1 \ + --hash=sha256:80bb5c256f1415f747011dc3604b59bc1f91c6e7150bd7db03b19170ee06b320 \ + --hash=sha256:07f7a7d0f388028b2df1d916e94bbb40624c59b48ecc6cbc232546706fac74c2 \ + --hash=sha256:02b41b633c6261feff8ddd8d11c711df6842aba629fdd3da10249a53211a72c4 \ + --hash=sha256:2fe803deacd09a233e4762a1adcea5db5d31e6be577a43352936179d14d90069 \ + --hash=sha256:257fd78c513e0fb5cdbe058c27a0624c9884e735bbd131935fd49e9fe719d310 \ + --hash=sha256:4fcc4649dc762cddacd193e6b55bc02edca674067f5f98166d7713b193932b7f \ + --hash=sha256:11871514607b15cfeb87c547a49bca19fde402f32e2b1c24a632506c0a756656 \ + --hash=sha256:8ad85f7f4e20964db4daadcab70b47ab05c7c1cf2a7c1e51087bfaa83831854c \ + --hash=sha256:a9a52172be0b5aae932bef82a79ec0a0ce87288c7d132946d645eba03f0ad8a8 \ + --hash=sha256:6d323e1554b3d22cfc03cd3243b5bb815a51f5249fdcbb86fda4bf62bab9e164 \ + --hash=sha256:43ca3bbbe97af00f49efb06e352eae40434ca9d915906f77def219b88e85d907 \ + --hash=sha256:6b1a564e6cb69922c7fe3a678b9f9a3c54e72b469875aa8018f18b4d1dd1adf3 \ + --hash=sha256:00b6d4ea20a906c0ca56d84f93065b398ab74b927a7a3dbd470f6fc503f95dc3 \ + --hash=sha256:a85d2b46be66a71bedde836d9e41859879cc54a2a04fad1191eb50c2066f6e9d \ + --hash=sha256:dbcda74c67263139358f4d188ae5faae95c30929281bc6866d00573783c422b7 \ + --hash=sha256:b21bb4c09ffabfa0e85e3a6b623e19b80e7acd709b9f91452b8297ace2a8ab00 \ + --hash=sha256:9e0fd32e0148dd5dea6af5fee42beb949098564cc23211a88d799e434255a1f4 \ + --hash=sha256:9736af4641846491aedb3c3f56b9bc5568d92b0692303b5a305301a95dfd38b1 \ + --hash=sha256:5b02d65b9ccf0ef6c34cba6cf5bf2aab1bb2f49c6090bafeecc9cd81ad4ea1c1 \ + --hash=sha256:21ac0156c4b089b330b7666db40feee30a5d52634cc4560e1905d6529a3897ff \ + --hash=sha256:9f3e6f9e05148ff90002b884fbc2a86bd303ae847e472f44ecc06c2cd2fcdb2d \ + --hash=sha256:6e743de5e9c3d1b7185870f480587b75b1cb604832e380d64f9504a0535912d1 \ + --hash=sha256:d79d7d5dc8a32b7093e81e97dad755127ff77bcc899e845f41bf71747af0c569 \ + --hash=sha256:81b19725065dcb43df02b37e03278c011a09e49757287dca60c5aecdd5a0b8ed \ + --hash=sha256:b014c23646a467558be7da3d6b9fa409b2c567d2110599b7cf9a0c5992b3b471 \ + --hash=sha256:88bd7b6bd70a5b6803c1abf6bca012f7ed963e58c68d76ee20b9d751c74a3248 \ + --hash=sha256:b5901a312f4d14c59918c221323068fad0540e34324925c8475263841dbdfe68 \ + --hash=sha256:d77c85fedff92cf788face9bfa3ebaa364448ebb1d765302e9af11bf449ca36d \ + --hash=sha256:8d649d616e5c6a678b26d15ece345354f7c2286acd6db868e65fcc5ff7c24a77 \ + --hash=sha256:7d2872609603cb35ca513d7404a94d6d608fc13211563571117046c9d2bcc3d7 \ + --hash=sha256:ee6acae74a2b91865910eef5e7de37dc6895ad96fa23603d1d27ea69df545015 \ + --hash=sha256:2b39d38039a1fdad98c87279b48bc5dce2c0ca0d73483b12cb72aa9609278e8a \ + --hash=sha256:60db23fa423575eeb65ea430cee741acb7c26a1365d103f7b0f6ec412b893853 \ + --hash=sha256:709fe01086a55cf79d20f741f39325018f4df051ef39fe921b1ebe780a66184c \ + --hash=sha256:8c0ce1e99116d5ab21355d8ebe53d9460366704ea38ae4d9f6933188f327b456 \ + --hash=sha256:e3fb1677c720409d5f671e39bac6c9e0e422584e5f518bfd50aa4cbbea02433f \ + --hash=sha256:642c2e7a804fcf18c222e1060df25fc210b9c58db7c91416fb055897fc27e8cc \ + --hash=sha256:7b7c050ae976e286906dd3f26009e117eb000fb2cf3533398c5ad9ccc86867b1 \ + --hash=sha256:ef3f72c9666bba2bab70d2a8b79f2c6d2c1a42a7f7e2b0ec83bb2f9e383950af \ + --hash=sha256:01c205616a89d09827986bc4e859bcabd64f5a0662a7fe95e0d359424e0e071b \ + --hash=sha256:5a0f54ce2c092aaf439813735584b9537cad479575a09892b8352fea5e988dc0 \ + --hash=sha256:2cf71233a0ed05ccdabe209c606fe0bac7379fdcf687f39b944420d2a09fdb57 \ + --hash=sha256:aa31fdcc33fef9eb2552cbcbfee7773d5a6792c137b359e82879c101e98584c5 \ + --hash=sha256:d1967f46ea8f2db647c786e78d8cc7e4313dbd1b0aca360592d8027b8508e24d \ + --hash=sha256:3232822c7d98d23895ccc443bbdf57c7412c5a65996c30442ebe6ed3df335383 \ + --hash=sha256:988635d122aaf2bdcef9e795435662bcd65b02f4f4c1ae37fbee7401c440b3a7 \ + --hash=sha256:9cca3c2cdadb362116235fdbd411735de4328c61425b0aa9f872fd76d02c4e86 \ + --hash=sha256:d52a25136894c63de15a35bc0bdc5adb4b0e173b9c0d07a2be9d3ca64a332735 \ + --hash=sha256:40e7bc81c9e2b2734ea4bc1aceb8a8f0ceaac7c5299bc5d69e37c44d9081d43b \ + --hash=sha256:b9b7a708dd92306328117d8c4b62e2194d00c365f18eff11a9b53c6f923b01e3 \ + --hash=sha256:6a9a25751acb379b466ff6be78a315e2b439d4c94c1e99cb7266d40a537995d3 \ + --hash=sha256:34aa51c45f28ba7f12accd624225e2b1e5a3a45206aa191f6f9aac931d9d56fe \ + --hash=sha256:dee0ce50c6a2dd9056c20db781e9c1cfd33e77d2d569f5d1d9321c641bb903d5 \ + --hash=sha256:dee60e1de1898bde3b238f18340eec6148986da0455d8ba7848d50470a7a32fb \ + --hash=sha256:380a85cf89e0e69b7cfbe2ea9f765f004ff419f34194018a6827ac0e3edfed4d +zipp==3.8.0; python_version >= "3.7" and python_full_version < "4.0.0" and python_version < "3.8" \ + --hash=sha256:c4f6e5bbf48e74f7a38e7cc5b0480ff42b0ae5178957d564d18932525d5cf099 \ + --hash=sha256:56bf8aadb83c24db6c4b577e13de374ccfb67da2078beba1d037c17980bf43ad diff --git a/conf/settings.py b/conf/settings.py new file mode 100644 index 0000000..27be13e --- /dev/null +++ b/conf/settings.py @@ -0,0 +1,174 @@ +################################################################################ +################################################################################ + +# Please do not modify this file, it will be reset at the next update. +# You can edit the file __FINAL_HOME_PATH__/local_settings.py and add/modify the settings you need. +# The parameters you add in local_settings.py will overwrite these, +# but you can use the options and documentation in this file to find out what can be done. + +################################################################################ +################################################################################ + +from pathlib import Path as __Path + +from django_yunohost_integration.base_settings import * # noqa +from django_yunohost_integration.secret_key import get_or_create_secret as __get_or_create_secret + +from findmydevice_project.settings.base import * # noqa + + +# ----------------------------------------------------------------------------- + +FINAL_HOME_PATH = __Path('__FINAL_HOME_PATH__') # /opt/yunohost/$app +assert FINAL_HOME_PATH.is_dir(), f'Directory not exists: {FINAL_HOME_PATH}' + +FINAL_WWW_PATH = __Path('__FINAL_WWW_PATH__') # /var/www/$app +assert FINAL_WWW_PATH.is_dir(), f'Directory not exists: {FINAL_WWW_PATH}' + +LOG_FILE = __Path('__LOG_FILE__') # /var/log/$app/django-fmd.log +assert LOG_FILE.is_file(), f'File not exists: {LOG_FILE}' + +PATH_URL = '__PATH_URL__' # $YNH_APP_ARG_PATH +PATH_URL = PATH_URL.strip('/') + +# ----------------------------------------------------------------------------- + +ROOT_URLCONF = 'urls' # /opt/yunohost/django-fmd/ynh_urls.py + +# Function that will be called to finalize a user profile: +YNH_SETUP_USER = 'setup_user.setup_project_user' + +SECRET_KEY = __get_or_create_secret(FINAL_HOME_PATH / 'secret.txt') # /opt/yunohost/$app/secret.txt + +INSTALLED_APPS.append('django_yunohost_integration') + +MIDDLEWARE.insert( + MIDDLEWARE.index('django.contrib.auth.middleware.AuthenticationMiddleware') + 1, + # login a user via HTTP_REMOTE_USER header from SSOwat: + 'django_yunohost_integration.sso_auth.auth_middleware.SSOwatRemoteUserMiddleware', +) + +# Keep ModelBackend around for per-user permissions and superuser +AUTHENTICATION_BACKENDS = ( + # Authenticate via SSO and nginx 'HTTP_REMOTE_USER' header: + 'django_yunohost_integration.sso_auth.auth_backend.SSOwatUserBackend', + # + # Fallback to normal Django model backend: + 'django.contrib.auth.backends.ModelBackend', +) + +LOGIN_REDIRECT_URL = None +LOGIN_URL = '/yunohost/sso/' +LOGOUT_REDIRECT_URL = '/yunohost/sso/' +# /yunohost/sso/?action=logout + +# ----------------------------------------------------------------------------- + + +ADMINS = (('__ADMIN__', '__ADMINMAIL__'),) + +MANAGERS = ADMINS + +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.postgresql', + 'NAME': '__DB_NAME__', + 'USER': '__DB_USER__', + 'PASSWORD': '__DB_PWD__', + 'HOST': '127.0.0.1', + 'PORT': '5432', # Default Postgres Port + 'CONN_MAX_AGE': 600, + } +} + +# Title of site to use +SITE_TITLE = '__APP__' + +# Site domain +SITE_DOMAIN = '__DOMAIN__' + +# Subject of emails includes site title +EMAIL_SUBJECT_PREFIX = f'[{SITE_TITLE}] ' + + +# E-mail address that error messages come from. +SERVER_EMAIL = 'noreply@__DOMAIN__' + +# Default email address to use for various automated correspondence from +# the site managers. Used for registration emails. +DEFAULT_FROM_EMAIL = '__ADMINMAIL__' + +# List of URLs your site is supposed to serve +ALLOWED_HOSTS = ['__DOMAIN__'] + +# _____________________________________________________________________________ +# Configuration for caching +CACHES = { + 'default': { + 'BACKEND': 'django_redis.cache.RedisCache', + 'LOCATION': 'redis://127.0.0.1:6379/__REDIS_DB__', + # If redis is running on same host as django-fmd, you might + # want to use unix sockets instead: + # 'LOCATION': 'unix:///var/run/redis/redis.sock?db=1', + 'OPTIONS': { + 'CLIENT_CLASS': 'django_redis.client.DefaultClient', + }, + 'KEY_PREFIX': '__APP__', + }, +} + +# _____________________________________________________________________________ +# Static files (CSS, JavaScript, Images) + +if PATH_URL: + STATIC_URL = f'/{PATH_URL}/static/' + MEDIA_URL = f'/{PATH_URL}/media/' +else: + # Installed to domain root, without a path prefix? + STATIC_URL = '/static/' + MEDIA_URL = '/media/' + +STATIC_ROOT = str(FINAL_WWW_PATH / 'static') +MEDIA_ROOT = str(FINAL_WWW_PATH / 'media') + +# ----------------------------------------------------------------------------- + +LOGGING = { + 'version': 1, + 'disable_existing_loggers': True, + 'formatters': { + 'verbose': { + 'format': '{asctime} {levelname} {name} {module}.{funcName} {message}', + 'style': '{', + }, + }, + 'handlers': { + 'mail_admins': { + 'level': 'ERROR', + 'formatter': 'verbose', + 'class': 'django.utils.log.AdminEmailHandler', + 'include_html': True, + }, + 'log_file': { + 'level': 'DEBUG', + 'class': 'logging.handlers.WatchedFileHandler', + 'formatter': 'verbose', + 'filename': str(LOG_FILE), + }, + }, + 'loggers': { + '': {'handlers': ['log_file', 'mail_admins'], 'level': 'INFO', 'propagate': False}, + 'django': {'handlers': ['log_file', 'mail_admins'], 'level': 'INFO', 'propagate': False}, + 'axes': {'handlers': ['log_file', 'mail_admins'], 'level': 'WARNING', 'propagate': False}, + 'django_tools': {'handlers': ['log_file', 'mail_admins'], 'level': 'INFO', 'propagate': False}, + 'django_yunohost_integration': {'handlers': ['log_file', 'mail_admins'], 'level': 'INFO', 'propagate': False}, + 'findmydevice': {'handlers': ['log_file', 'mail_admins'], 'level': 'INFO', 'propagate': False}, + }, +} + +# ----------------------------------------------------------------------------- + +try: + from local_settings import * # noqa +except ImportError: + pass diff --git a/conf/setup_user.py b/conf/setup_user.py new file mode 100644 index 0000000..d838d3e --- /dev/null +++ b/conf/setup_user.py @@ -0,0 +1,8 @@ +def setup_project_user(user): + """ + All users used the Django admin, so we need to set the "staff" user flag. + Called from django_yunohost_integration.sso_auth + """ + user.is_staff = True + user.save() + return user diff --git a/conf/urls.py b/conf/urls.py new file mode 100644 index 0000000..2d9ad4c --- /dev/null +++ b/conf/urls.py @@ -0,0 +1,22 @@ +from django.conf import settings +from django.conf.urls import static +from django.urls import include, path + + +# from django_yunohost_integration.views import request_media_debug_view + + +if settings.PATH_URL: + # settings.PATH_URL is the $YNH_APP_ARG_PATH + # Prefix all urls with "PATH_URL": + urlpatterns = [ + # path(f'{settings.PATH_URL}/debug/', request_media_debug_view), + + path(f'{settings.PATH_URL}/', include('findmydevice_project.urls')), + ] + if settings.SERVE_FILES: + urlpatterns += static.static(settings.STATIC_URL, document_root=settings.STATIC_ROOT) +else: + # Installed to domain root, without a path prefix + # Just use the default project urls.py + from findmydevice_project.urls import urlpatterns # noqa diff --git a/conf/wsgi.py b/conf/wsgi.py new file mode 100644 index 0000000..018a0cc --- /dev/null +++ b/conf/wsgi.py @@ -0,0 +1,12 @@ +""" + WSGI config +""" +import os + + +os.environ['DJANGO_SETTINGS_MODULE'] = 'settings' + +from django.core.wsgi import get_wsgi_application # noqa + + +application = get_wsgi_application() diff --git a/config_panel.toml b/config_panel.toml new file mode 100644 index 0000000..565701f --- /dev/null +++ b/config_panel.toml @@ -0,0 +1,20 @@ +# https://github.com/YunoHost/example_ynh/blob/master/config_panel.toml.example + +version = "1.0" + +[main] +name = "Settings" + + [main.findmydevice_config] + name = "django-fmd configuration" + + # Trigger a service reload-or-restart after the user change a question in this panel + services = ["__APP__"] + + [main.findmydevice_config.django_debug] + ask = "debug mode" + help = "Important: Never activate this settings.DEBUG on production!" + type = "boolean" + yes = "True" + no = "False" + bind = "DJANGO_DEBUG:__FINALPATH__/local_settings.py" diff --git a/doc/DESCRIPTION.md b/doc/DESCRIPTION.md new file mode 100644 index 0000000..e3aef51 --- /dev/null +++ b/doc/DESCRIPTION.md @@ -0,0 +1,14 @@ +Find My Device Server implemented in Python using Django. +Usable for the Andorid App [**FindMyDevice**](https://gitlab.com/Nulide/findmydevice/) by [Nnulide](https://nulide.de/): + +[Get FindMyDevice on F-Droid](https://f-droid.org/packages/de.nulide.findmydevice/) + +[![pytest](https://github.com/YunoHost-Apps/django-fmd_ynh/actions/workflows/pytest.yml/badge.svg?branch=master)](https://github.com/YunoHost-Apps/django-fmd_ynh/actions/workflows/pytest.yml) [![YunoHost apps package linter](https://github.com/YunoHost-Apps/django-fmd_ynh/actions/workflows/package_linter.yml/badge.svg)](https://github.com/YunoHost-Apps/django-fmd_ynh/actions/workflows/package_linter.yml) [![Coverage Status on codecov.io](https://codecov.io/gh/YunoHost-Apps/django-fmd_ynh/branch/master/graph/badge.svg)](https://codecov.io/gh/YunoHost-Apps/django-fmd_ynh) + +![django-fmd @ PyPi](https://img.shields.io/pypi/v/django-fmd?label=django-fmd%20%40%20PyPi) +![Python Versions](https://img.shields.io/pypi/pyversions/django-fmd) +![License GPL V3+](https://img.shields.io/pypi/l/django-fmd) + +Pull requests welcome ;) + +This package for YunoHost used [django-yunohost-integration](https://github.com/YunoHost-Apps/django_yunohost_integration) diff --git a/doc/DISCLAIMER.md b/doc/DISCLAIMER.md new file mode 100644 index 0000000..c7cd6cc --- /dev/null +++ b/doc/DISCLAIMER.md @@ -0,0 +1,128 @@ +## Settings and upgrades + +Almost everything related to django-fmd's configuration is handled in a `"../conf/settings.py"` file. +You can edit the file `/opt/yunohost/django-fmd/local_settings.py` to enable or disable features. + +Test sending emails: + +```bash +ssh admin@yourdomain.tld +root@yunohost:~# cd /opt/yunohost/django-fmd/ +root@yunohost:/opt/yunohost/django-fmd# source venv/bin/activate +(venv) root@yunohost:/opt/yunohost/django-fmd# ./manage.py sendtestemail --admins +``` + +Background info: Error mails are send to all [settings.ADMINS](https://docs.djangoproject.com/en/2.2/ref/settings/#std:setting-ADMINS). By default the YunoHost admin is inserted here. +To check current ADMINS run: + +```bash +(venv) root@yunohost:/opt/yunohost/django-fmd# ./manage.py sendtestemail --admins +``` + +If you prefere to send error emails to a extrnal email address, just do something like this: + +```bash +echo "ADMINS = (('Your Name', 'example@domain.tld'),)" >> /opt/yunohost/django-fmd/local_settings.py +``` + +To check the effective settings, run this: +```bash +(venv) root@yunohost:/opt/yunohost/django-fmd# ./manage.py diffsettings +``` + + +# Miscellaneous + + +## SSO authentication + +[SSOwat](https://github.com/YunoHost/SSOwat) is fully supported via [django-yunohost-integration](https://github.com/YunoHost-Apps/django_yunohost_integration): + +* First user (`$YNH_APP_ARG_ADMIN`) will be created as Django's super user +* All new users will be created as normal users +* Login via SSO is fully supported +* User Email, First / Last name will be updated from SSO data + + +--- + +# Yunohost developer commands + +To remove call e.g.: +```bash +sudo yunohost app remove django-fmd +``` + +Backup / remove / restore cycle, e.g.: +```bash +yunohost backup create --apps django-fmd +yunohost backup list +archives: + - django-fmd-pre-upgrade1 + - 20201223-163434 +yunohost app remove django-fmd +yunohost backup restore 20201223-163434 --apps django-fmd +``` + +Debug installation, e.g.: +```bash +root@yunohost:~# ls -la /var/www/django-fmd/ +total 18 +drwxr-xr-x 4 root root 4 Dec 8 08:36 . +drwxr-xr-x 6 root root 6 Dec 8 08:36 .. +drwxr-xr-x 2 root root 2 Dec 8 08:36 media +drwxr-xr-x 7 root root 8 Dec 8 08:40 static + +root@yunohost:~# ls -la /opt/yunohost/django-fmd/ +total 58 +drwxr-xr-x 5 django-fmd django-fmd 11 Dec 8 08:39 . +drwxr-xr-x 3 root root 3 Dec 8 08:36 .. +-rw-r--r-- 1 django-fmd django-fmd 460 Dec 8 08:39 gunicorn.conf.py +-rw-r--r-- 1 django-fmd django-fmd 0 Dec 8 08:39 local_settings.py +-rwxr-xr-x 1 django-fmd django-fmd 274 Dec 8 08:39 manage.py +-rw-r--r-- 1 django-fmd django-fmd 171 Dec 8 08:39 secret.txt +drwxr-xr-x 6 django-fmd django-fmd 6 Dec 8 08:37 venv +-rw-r--r-- 1 django-fmd django-fmd 115 Dec 8 08:39 wsgi.py +-rw-r--r-- 1 django-fmd django-fmd 4737 Dec 8 08:39 settings.py + +root@yunohost:~# cd /opt/yunohost/django-fmd/ +root@yunohost:/opt/yunohost/django-fmd# source venv/bin/activate +(venv) root@yunohost:/opt/yunohost/django-fmd# ./manage.py check +django-fmd v0.8.2 (Django v2.2.17) +DJANGO_SETTINGS_MODULE='settings' +PROJECT_PATH:/opt/yunohost/django-fmd/venv/lib/python3.7/site-packages +BASE_PATH:/opt/yunohost/django-fmd +System check identified no issues (0 silenced). + +root@yunohost:~# tail -f /var/log/django-fmd/django-fmd.log +root@yunohost:~# cat /etc/systemd/system/django-fmd.service + +root@yunohost:~# systemctl reload-or-restart django-fmd +root@yunohost:~# journalctl --unit=django-fmd --follow +``` + +## local test + +For quicker developing of django-fmd in the context of YunoHost app, +it's possible to run the Django developer server with the settings +and urls made for YunoHost installation. + +e.g.: +```bash +~$ git clone https://github.com/YunoHost-Apps/django-fmd_ynh.git +~$ cd django-fmd_ynh/ +~/django-fmd_ynh$ make +install-poetry install or update poetry +install install django-fmd via poetry +update update the sources and installation +local-test Run local_test.py to run django-fmd_ynh locally +~/django-fmd_ynh$ make install-poetry +~/django-fmd_ynh$ make install +~/django-fmd_ynh$ make local-test +``` + +Notes: + +* SQlite database will be used +* A super user with username `test` and password `test` is created +* The page is available under `http://127.0.0.1:8000/app_path/` diff --git a/local_test.py b/local_test.py new file mode 100644 index 0000000..e2664e5 --- /dev/null +++ b/local_test.py @@ -0,0 +1,29 @@ +""" + Build a "local_test" YunoHost installation and start the Django dev. server against it. + + Run via: + make local-test + + see README for details ;) +""" +from pathlib import Path + + +try: + from django_yunohost_integration.local_test import create_local_test +except ImportError as err: + raise ImportError('Did you forget to activate a virtual environment?') from err + +BASE_PATH = Path(__file__).parent + + +def main(): + create_local_test( + django_settings_path=BASE_PATH / 'conf' / 'settings.py', + destination=BASE_PATH / 'local_test', + runserver=True, + ) + + +if __name__ == '__main__': + main() diff --git a/manifest.json b/manifest.json new file mode 100644 index 0000000..1be635c --- /dev/null +++ b/manifest.json @@ -0,0 +1,46 @@ +{ + "name": "django-fmd", + "id": "django-fmd", + "packaging_format": 1, + "description": { + "en": "Web based FritzBox management using Python/Django." + }, + "version": "0.0.1~ynh1", + "url": "https://gitlab.com/jedie/django-find-my-device", + "upstream": { + "license": "GPL-3.0", + "website": "https://gitlab.com/jedie/django-find-my-device", + "code": "https://gitlab.com/jedie/django-find-my-device" + }, + "license": "GPL-3.0", + "maintainer": { + "name": "Jens Diemer", + "email": "django-fmd_ynh@jensdiemer.de" + }, + "previous_maintainers": [], + "requirements": { + "yunohost": ">= 4.3.0" + }, + "multi_instance": true, + "services": [ + "nginx" + ], + "arguments": { + "install" : [ + { + "name": "domain", + "type": "domain" + }, + { + "name": "path", + "type": "path", + "example": "/django-fmd", + "default": "/django-fmd" + }, + { + "name": "admin", + "type": "user" + } + ] + } +} diff --git a/poetry.lock b/poetry.lock new file mode 100644 index 0000000..e213442 --- /dev/null +++ b/poetry.lock @@ -0,0 +1,1327 @@ +[[package]] +name = "asgiref" +version = "3.5.2" +description = "ASGI specs, helper code, and adapters" +category = "main" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +typing-extensions = {version = "*", markers = "python_version < \"3.8\""} + +[package.extras] +tests = ["pytest", "pytest-asyncio", "mypy (>=0.800)"] + +[[package]] +name = "astor" +version = "0.8.1" +description = "Read/rewrite/write Python ASTs" +category = "dev" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7" + +[[package]] +name = "async-timeout" +version = "4.0.2" +description = "Timeout context manager for asyncio programs" +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +typing-extensions = {version = ">=3.6.5", markers = "python_version < \"3.8\""} + +[[package]] +name = "atomicwrites" +version = "1.4.1" +description = "Atomic file writes." +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[[package]] +name = "attrs" +version = "21.4.0" +description = "Classes Without Boilerplate" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[package.extras] +dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "furo", "sphinx", "sphinx-notfound-page", "pre-commit", "cloudpickle"] +docs = ["furo", "sphinx", "zope.interface", "sphinx-notfound-page"] +tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "cloudpickle"] +tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "cloudpickle"] + +[[package]] +name = "black" +version = "22.6.0" +description = "The uncompromising code formatter." +category = "dev" +optional = false +python-versions = ">=3.6.2" + +[package.dependencies] +click = ">=8.0.0" +mypy-extensions = ">=0.4.3" +pathspec = ">=0.9.0" +platformdirs = ">=2" +tomli = {version = ">=1.1.0", markers = "python_full_version < \"3.11.0a7\""} +typed-ast = {version = ">=1.4.2", markers = "python_version < \"3.8\" and implementation_name == \"cpython\""} +typing-extensions = {version = ">=3.10.0.0", markers = "python_version < \"3.10\""} + +[package.extras] +colorama = ["colorama (>=0.4.3)"] +d = ["aiohttp (>=3.7.4)"] +jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] +uvloop = ["uvloop (>=0.15.2)"] + +[[package]] +name = "bleach" +version = "5.0.1" +description = "An easy safelist-based HTML-sanitizing tool." +category = "main" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +six = ">=1.9.0" +webencodings = "*" + +[package.extras] +css = ["tinycss2 (>=1.1.0,<1.2)"] +dev = ["build (==0.8.0)", "flake8 (==4.0.1)", "hashin (==0.17.0)", "pip-tools (==6.6.2)", "pytest (==7.1.2)", "Sphinx (==4.3.2)", "tox (==3.25.0)", "twine (==4.0.1)", "wheel (==0.37.1)", "black (==22.3.0)", "mypy (==0.961)"] + +[[package]] +name = "bx-django-utils" +version = "26" +description = "Various Django utility functions" +category = "main" +optional = false +python-versions = ">=3.7,<4.0.0" + +[package.dependencies] +bx_py_utils = ">=52" +django = "*" +python-stdnum = "*" + +[[package]] +name = "bx-py-utils" +version = "66" +description = "Various Python utility functions" +category = "main" +optional = false +python-versions = ">=3.6,<4.0.0" + +[[package]] +name = "certifi" +version = "2022.6.15" +description = "Python package for providing Mozilla's CA Bundle." +category = "dev" +optional = false +python-versions = ">=3.6" + +[[package]] +name = "charset-normalizer" +version = "2.1.0" +description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." +category = "dev" +optional = false +python-versions = ">=3.6.0" + +[package.extras] +unicode_backport = ["unicodedata2"] + +[[package]] +name = "click" +version = "8.1.3" +description = "Composable command line interface toolkit" +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} +importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} + +[[package]] +name = "colorama" +version = "0.4.5" +description = "Cross-platform colored terminal text." +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[[package]] +name = "colorlog" +version = "6.6.0" +description = "Add colours to the output of Python's logging module." +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +colorama = {version = "*", markers = "sys_platform == \"win32\""} + +[package.extras] +development = ["black", "flake8", "mypy", "pytest", "types-colorama"] + +[[package]] +name = "coverage" +version = "6.4.1" +description = "Code coverage measurement for Python" +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.11.0a6\" and extra == \"toml\""} + +[package.extras] +toml = ["tomli"] + +[[package]] +name = "coveralls" +version = "3.3.1" +description = "Show coverage stats online via coveralls.io" +category = "dev" +optional = false +python-versions = ">= 3.5" + +[package.dependencies] +coverage = ">=4.1,<6.0.0 || >6.1,<6.1.1 || >6.1.1,<7.0" +docopt = ">=0.6.1" +requests = ">=1.0.0" + +[package.extras] +yaml = ["PyYAML (>=3.10)"] + +[[package]] +name = "darker" +version = "1.5.0" +description = "Apply Black formatting only in regions changed since last commit" +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +black = ">=21.5b1" +toml = ">=0.10.0" +typing-extensions = {version = "*", markers = "python_version < \"3.8\""} + +[package.extras] +color = ["Pygments (>=2.4.0)"] +isort = ["isort (>=5.0.1)"] +release = ["airium (>=0.2.3)", "click (>=8.0.0)", "defusedxml (>=0.7.1)", "requests-cache (>=0.7)"] +test = ["airium (>=0.2.3)", "bandit (>=1.7.1)", "black (>=21.7b1)", "codespell (>=2.1.0)", "defusedxml (>=0.7.1)", "flake8 (<4)", "flake8-2020 (>=1.6.1)", "flake8-bugbear (>=22.1.11)", "flake8-comprehensions (>=3.7.0)", "mypy (>=0.940)", "pygments", "pylint", "pytest (>=6.2.0)", "pytest-darker", "pytest-flake8 (>=1.0.6)", "pytest-isort (>=1.1.0)", "pytest-kwparametrize (>=0.0.3)", "pytest-mypy", "pyupgrade (>=2.31.0)", "regex (>=2021.4.4)", "requests-cache (>=0.7)", "ruamel.yaml (>=0.17.21)", "safety (>=1.10.3)", "twine (>=2.0.0)", "types-requests (>=2.27.9)", "types-toml (>=0.10.4)", "wheel (>=0.21.0)"] + +[[package]] +name = "deprecated" +version = "1.2.13" +description = "Python @deprecated decorator to deprecate old python classes, functions or methods." +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[package.dependencies] +wrapt = ">=1.10,<2" + +[package.extras] +dev = ["tox", "bump2version (<1)", "sphinx (<2)", "importlib-metadata (<3)", "importlib-resources (<4)", "configparser (<5)", "sphinxcontrib-websupport (<2)", "zipp (<2)", "PyTest (<5)", "PyTest-Cov (<2.6)", "pytest", "pytest-cov"] + +[[package]] +name = "distlib" +version = "0.3.4" +description = "Distribution utilities" +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "django" +version = "3.2.14" +description = "A high-level Python Web framework that encourages rapid development and clean, pragmatic design." +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +asgiref = ">=3.3.2,<4" +pytz = "*" +sqlparse = ">=0.2.2" + +[package.extras] +argon2 = ["argon2-cffi (>=19.1.0)"] +bcrypt = ["bcrypt"] + +[[package]] +name = "django-axes" +version = "5.35.0" +description = "Keep track of failed login attempts in Django-powered sites." +category = "main" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +django = ">=3.2" +django-ipware = ">=3" + +[[package]] +name = "django-debug-toolbar" +version = "3.5.0" +description = "A configurable set of panels that display various debug information about the current request/response." +category = "main" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +Django = ">=3.2" +sqlparse = ">=0.2.0" + +[[package]] +name = "django-fmd" +version = "0.0.1" +description = "Server for 'Find My Device' android app, implemented in Django/Python" +category = "main" +optional = false +python-versions = ">=3.7,<4.0.0" + +[package.dependencies] +bx_django_utils = "*" +bx_py_utils = "*" +colorlog = "*" +django = "*" +django-debug-toolbar = "*" +django-tools = "*" + +[[package]] +name = "django-ipware" +version = "4.0.2" +description = "A Django application to retrieve user's IP address" +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" + +[[package]] +name = "django-redis" +version = "5.2.0" +description = "Full featured redis cache backend for Django." +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +Django = ">=2.2" +redis = ">=3,<4.0.0 || >4.0.0,<4.0.1 || >4.0.1" + +[package.extras] +hiredis = ["redis[hiredis] (>=3,!=4.0.0,!=4.0.1)"] + +[[package]] +name = "django-tools" +version = "0.50.0" +description = "miscellaneous tools for Django based projects" +category = "main" +optional = false +python-versions = ">=3.7,<4.0.0" + +[package.dependencies] +bleach = "*" +bx_py_utils = "*" +django = "*" +icdiff = "*" +pprintpp = "*" + +[[package]] +name = "django-yunohost-integration" +version = "0.2.4" +description = "Glue code to package django projects as yunohost apps." +category = "main" +optional = false +python-versions = ">=3.7,<4.0.0" + +[package.dependencies] +django = "*" +django-axes = {version = "*", optional = true, markers = "extra == \"ynh\""} +django-redis = {version = "*", optional = true, markers = "extra == \"ynh\""} +gunicorn = {version = "*", optional = true, markers = "extra == \"ynh\""} +psycopg2 = {version = "*", optional = true, markers = "extra == \"ynh\""} + +[package.extras] +ynh = ["django-axes", "django-redis", "gunicorn", "psycopg2"] + +[[package]] +name = "docopt" +version = "0.6.2" +description = "Pythonic argument parser, that will make you smile" +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "filelock" +version = "3.7.1" +description = "A platform independent file lock." +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.extras] +docs = ["furo (>=2021.8.17b43)", "sphinx (>=4.1)", "sphinx-autodoc-typehints (>=1.12)"] +testing = ["covdefaults (>=1.2.0)", "coverage (>=4)", "pytest (>=4)", "pytest-cov", "pytest-timeout (>=1.4.2)"] + +[[package]] +name = "flake8" +version = "4.0.1" +description = "the modular source code checker: pep8 pyflakes and co" +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +importlib-metadata = {version = "<4.3", markers = "python_version < \"3.8\""} +mccabe = ">=0.6.0,<0.7.0" +pycodestyle = ">=2.8.0,<2.9.0" +pyflakes = ">=2.4.0,<2.5.0" + +[[package]] +name = "flynt" +version = "0.76" +description = "CLI tool to convert a python project's %-formatted strings to f-strings." +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +astor = "*" +tomli = ">=1.1.0" + +[[package]] +name = "gunicorn" +version = "20.1.0" +description = "WSGI HTTP Server for UNIX" +category = "main" +optional = false +python-versions = ">=3.5" + +[package.extras] +eventlet = ["eventlet (>=0.24.1)"] +gevent = ["gevent (>=1.4.0)"] +setproctitle = ["setproctitle"] +tornado = ["tornado (>=0.2)"] + +[[package]] +name = "icdiff" +version = "2.0.5" +description = "improved colored diff" +category = "main" +optional = false +python-versions = "*" + +[[package]] +name = "idna" +version = "3.3" +description = "Internationalized Domain Names in Applications (IDNA)" +category = "dev" +optional = false +python-versions = ">=3.5" + +[[package]] +name = "importlib-metadata" +version = "4.2.0" +description = "Read metadata from Python packages" +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +typing-extensions = {version = ">=3.6.4", markers = "python_version < \"3.8\""} +zipp = ">=0.5" + +[package.extras] +docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] +testing = ["pytest (>=4.6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "packaging", "pep517", "pyfakefs", "flufl.flake8", "pytest-black (>=0.3.7)", "pytest-mypy", "importlib-resources (>=1.3)"] + +[[package]] +name = "iniconfig" +version = "1.1.1" +description = "iniconfig: brain-dead simple config-ini parsing" +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "isort" +version = "5.10.1" +description = "A Python utility / library to sort Python imports." +category = "dev" +optional = false +python-versions = ">=3.6.1,<4.0" + +[package.extras] +pipfile_deprecated_finder = ["pipreqs", "requirementslib"] +requirements_deprecated_finder = ["pipreqs", "pip-api"] +colors = ["colorama (>=0.4.3,<0.5.0)"] +plugins = ["setuptools"] + +[[package]] +name = "mccabe" +version = "0.6.1" +description = "McCabe checker, plugin for flake8" +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "mypy-extensions" +version = "0.4.3" +description = "Experimental type system extensions for programs checked with the mypy typechecker." +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "packaging" +version = "21.3" +description = "Core utilities for Python packages" +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +pyparsing = ">=2.0.2,<3.0.5 || >3.0.5" + +[[package]] +name = "pathspec" +version = "0.9.0" +description = "Utility library for gitignore style pattern matching of file paths." +category = "dev" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" + +[[package]] +name = "platformdirs" +version = "2.5.2" +description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.extras] +docs = ["furo (>=2021.7.5b38)", "proselint (>=0.10.2)", "sphinx-autodoc-typehints (>=1.12)", "sphinx (>=4)"] +test = ["appdirs (==1.4.4)", "pytest-cov (>=2.7)", "pytest-mock (>=3.6)", "pytest (>=6)"] + +[[package]] +name = "pluggy" +version = "1.0.0" +description = "plugin and hook calling mechanisms for python" +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} + +[package.extras] +dev = ["pre-commit", "tox"] +testing = ["pytest", "pytest-benchmark"] + +[[package]] +name = "pprintpp" +version = "0.4.0" +description = "A drop-in replacement for pprint that's actually pretty" +category = "main" +optional = false +python-versions = "*" + +[[package]] +name = "psycopg2" +version = "2.9.3" +description = "psycopg2 - Python-PostgreSQL Database Adapter" +category = "main" +optional = false +python-versions = ">=3.6" + +[[package]] +name = "py" +version = "1.11.0" +description = "library with cross-python path, ini-parsing, io, code, log facilities" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[[package]] +name = "pycodestyle" +version = "2.8.0" +description = "Python style guide checker" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[[package]] +name = "pyflakes" +version = "2.4.0" +description = "passive checker of Python programs" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[[package]] +name = "pyparsing" +version = "3.0.9" +description = "pyparsing module - Classes and methods to define and execute parsing grammars" +category = "main" +optional = false +python-versions = ">=3.6.8" + +[package.extras] +diagrams = ["railroad-diagrams", "jinja2"] + +[[package]] +name = "pytest" +version = "7.1.2" +description = "pytest: simple powerful testing with Python" +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +atomicwrites = {version = ">=1.0", markers = "sys_platform == \"win32\""} +attrs = ">=19.2.0" +colorama = {version = "*", markers = "sys_platform == \"win32\""} +importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} +iniconfig = "*" +packaging = "*" +pluggy = ">=0.12,<2.0" +py = ">=1.8.2" +tomli = ">=1.0.0" + +[package.extras] +testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "xmlschema"] + +[[package]] +name = "pytest-cov" +version = "3.0.0" +description = "Pytest plugin for measuring coverage." +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +coverage = {version = ">=5.2.1", extras = ["toml"]} +pytest = ">=4.6" + +[package.extras] +testing = ["fields", "hunter", "process-tests", "six", "pytest-xdist", "virtualenv"] + +[[package]] +name = "pytest-darker" +version = "0.1.2" +description = "A pytest plugin for checking of modified code using Darker" +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +darker = ">=1.1.0" +typing-extensions = {version = "*", markers = "python_version < \"3.8\""} + +[package.extras] +test = ["mypy (>=0.782)", "pytest (>=6.0.1)", "pytest-black", "pytest-isort (>=1.1.0)", "pytest-mypy"] + +[[package]] +name = "pytest-django" +version = "4.5.2" +description = "A Django plugin for pytest." +category = "dev" +optional = false +python-versions = ">=3.5" + +[package.dependencies] +pytest = ">=5.4.0" + +[package.extras] +docs = ["sphinx", "sphinx-rtd-theme"] +testing = ["django", "django-configurations (>=2.0)"] + +[[package]] +name = "pytest-flake8" +version = "1.1.1" +description = "pytest plugin to check FLAKE8 requirements" +category = "dev" +optional = false +python-versions = "*" + +[package.dependencies] +flake8 = ">=4.0" +pytest = ">=7.0" + +[[package]] +name = "pytest-isort" +version = "3.0.0" +description = "py.test plugin to check import ordering using isort" +category = "dev" +optional = false +python-versions = ">=3.6,<4" + +[package.dependencies] +importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} +isort = ">=4.0" +pytest = ">=5.0" + +[[package]] +name = "python-stdnum" +version = "1.17" +description = "Python module to handle standardized numbers and codes" +category = "main" +optional = false +python-versions = "*" + +[package.extras] +soap = ["zeep"] +soap-alt = ["suds"] +soap-fallback = ["pysimplesoap"] + +[[package]] +name = "pytz" +version = "2022.1" +description = "World timezone definitions, modern and historical" +category = "main" +optional = false +python-versions = "*" + +[[package]] +name = "pyupgrade" +version = "2.37.1" +description = "A tool to automatically upgrade syntax for newer versions." +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +tokenize-rt = ">=3.2.0" + +[[package]] +name = "redis" +version = "4.3.4" +description = "Python client for Redis database and key-value store" +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +async-timeout = ">=4.0.2" +deprecated = ">=1.2.3" +importlib-metadata = {version = ">=1.0", markers = "python_version < \"3.8\""} +packaging = ">=20.4" +typing-extensions = {version = "*", markers = "python_version < \"3.8\""} + +[package.extras] +hiredis = ["hiredis (>=1.0.0)"] +ocsp = ["cryptography (>=36.0.1)", "pyopenssl (==20.0.1)", "requests (>=2.26.0)"] + +[[package]] +name = "requests" +version = "2.28.1" +description = "Python HTTP for Humans." +category = "dev" +optional = false +python-versions = ">=3.7, <4" + +[package.dependencies] +certifi = ">=2017.4.17" +charset-normalizer = ">=2,<3" +idna = ">=2.5,<4" +urllib3 = ">=1.21.1,<1.27" + +[package.extras] +socks = ["PySocks (>=1.5.6,!=1.5.7)"] +use_chardet_on_py3 = ["chardet (>=3.0.2,<6)"] + +[[package]] +name = "six" +version = "1.16.0" +description = "Python 2 and 3 compatibility utilities" +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" + +[[package]] +name = "sqlparse" +version = "0.4.2" +description = "A non-validating SQL parser." +category = "main" +optional = false +python-versions = ">=3.5" + +[[package]] +name = "tokenize-rt" +version = "4.2.1" +description = "A wrapper around the stdlib `tokenize` which roundtrips." +category = "dev" +optional = false +python-versions = ">=3.6.1" + +[[package]] +name = "toml" +version = "0.10.2" +description = "Python Library for Tom's Obvious, Minimal Language" +category = "dev" +optional = false +python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" + +[[package]] +name = "tomli" +version = "2.0.1" +description = "A lil' TOML parser" +category = "dev" +optional = false +python-versions = ">=3.7" + +[[package]] +name = "tox" +version = "3.25.1" +description = "tox is a generic virtualenv management and test command line tool" +category = "dev" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" + +[package.dependencies] +colorama = {version = ">=0.4.1", markers = "platform_system == \"Windows\""} +filelock = ">=3.0.0" +importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} +packaging = ">=14" +pluggy = ">=0.12.0" +py = ">=1.4.17" +six = ">=1.14.0" +toml = ">=0.9.4" +virtualenv = ">=16.0.0,<20.0.0 || >20.0.0,<20.0.1 || >20.0.1,<20.0.2 || >20.0.2,<20.0.3 || >20.0.3,<20.0.4 || >20.0.4,<20.0.5 || >20.0.5,<20.0.6 || >20.0.6,<20.0.7 || >20.0.7" + +[package.extras] +docs = ["pygments-github-lexers (>=0.0.5)", "sphinx (>=2.0.0)", "sphinxcontrib-autoprogram (>=0.1.5)", "towncrier (>=18.5.0)"] +testing = ["flaky (>=3.4.0)", "freezegun (>=0.3.11)", "pytest (>=4.0.0)", "pytest-cov (>=2.5.1)", "pytest-mock (>=1.10.0)", "pytest-randomly (>=1.0.0)", "psutil (>=5.6.1)", "pathlib2 (>=2.3.3)"] + +[[package]] +name = "typed-ast" +version = "1.5.4" +description = "a fork of Python 2 and 3 ast modules with type comment support" +category = "dev" +optional = false +python-versions = ">=3.6" + +[[package]] +name = "typing-extensions" +version = "4.3.0" +description = "Backported and Experimental Type Hints for Python 3.7+" +category = "main" +optional = false +python-versions = ">=3.7" + +[[package]] +name = "urllib3" +version = "1.26.10" +description = "HTTP library with thread-safe connection pooling, file post, and more." +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, <4" + +[package.extras] +brotli = ["brotlicffi (>=0.8.0)", "brotli (>=1.0.9)", "brotlipy (>=0.6.0)"] +secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"] +socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] + +[[package]] +name = "virtualenv" +version = "20.15.1" +description = "Virtual Python Environment builder" +category = "dev" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" + +[package.dependencies] +distlib = ">=0.3.1,<1" +filelock = ">=3.2,<4" +importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} +platformdirs = ">=2,<3" +six = ">=1.9.0,<2" + +[package.extras] +docs = ["proselint (>=0.10.2)", "sphinx (>=3)", "sphinx-argparse (>=0.2.5)", "sphinx-rtd-theme (>=0.4.3)", "towncrier (>=21.3)"] +testing = ["coverage (>=4)", "coverage-enable-subprocess (>=1)", "flaky (>=3)", "pytest (>=4)", "pytest-env (>=0.6.2)", "pytest-freezegun (>=0.4.1)", "pytest-mock (>=2)", "pytest-randomly (>=1)", "pytest-timeout (>=1)", "packaging (>=20.0)"] + +[[package]] +name = "webencodings" +version = "0.5.1" +description = "Character encoding aliases for legacy web content" +category = "main" +optional = false +python-versions = "*" + +[[package]] +name = "wrapt" +version = "1.14.1" +description = "Module for decorators, wrappers and monkey patching." +category = "main" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" + +[[package]] +name = "zipp" +version = "3.8.0" +description = "Backport of pathlib-compatible object wrapper for zip files" +category = "main" +optional = false +python-versions = ">=3.7" + +[package.extras] +docs = ["sphinx", "jaraco.packaging (>=9)", "rst.linker (>=1.9)"] +testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy (>=0.9.1)"] + +[metadata] +lock-version = "1.1" +python-versions = ">=3.7,<4.0.0" +content-hash = "4e689069507f524cb2f23859dda19063221cfb3e5bbfaa07379f687f64e7b37f" + +[metadata.files] +asgiref = [ + {file = "asgiref-3.5.2-py3-none-any.whl", hash = "sha256:1d2880b792ae8757289136f1db2b7b99100ce959b2aa57fd69dab783d05afac4"}, + {file = "asgiref-3.5.2.tar.gz", hash = "sha256:4a29362a6acebe09bf1d6640db38c1dc3d9217c68e6f9f6204d72667fc19a424"}, +] +astor = [ + {file = "astor-0.8.1-py2.py3-none-any.whl", hash = "sha256:070a54e890cefb5b3739d19f30f5a5ec840ffc9c50ffa7d23cc9fc1a38ebbfc5"}, + {file = "astor-0.8.1.tar.gz", hash = "sha256:6a6effda93f4e1ce9f618779b2dd1d9d84f1e32812c23a29b3fff6fd7f63fa5e"}, +] +async-timeout = [ + {file = "async-timeout-4.0.2.tar.gz", hash = "sha256:2163e1640ddb52b7a8c80d0a67a08587e5d245cc9c553a74a847056bc2976b15"}, + {file = "async_timeout-4.0.2-py3-none-any.whl", hash = "sha256:8ca1e4fcf50d07413d66d1a5e416e42cfdf5851c981d679a09851a6853383b3c"}, +] +atomicwrites = [ + {file = "atomicwrites-1.4.1.tar.gz", hash = "sha256:81b2c9071a49367a7f770170e5eec8cb66567cfbbc8c73d20ce5ca4a8d71cf11"}, +] +attrs = [ + {file = "attrs-21.4.0-py2.py3-none-any.whl", hash = "sha256:2d27e3784d7a565d36ab851fe94887c5eccd6a463168875832a1be79c82828b4"}, + {file = "attrs-21.4.0.tar.gz", hash = "sha256:626ba8234211db98e869df76230a137c4c40a12d72445c45d5f5b716f076e2fd"}, +] +black = [ + {file = "black-22.6.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:f586c26118bc6e714ec58c09df0157fe2d9ee195c764f630eb0d8e7ccce72e69"}, + {file = "black-22.6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b270a168d69edb8b7ed32c193ef10fd27844e5c60852039599f9184460ce0807"}, + {file = "black-22.6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6797f58943fceb1c461fb572edbe828d811e719c24e03375fd25170ada53825e"}, + {file = "black-22.6.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c85928b9d5f83b23cee7d0efcb310172412fbf7cb9d9ce963bd67fd141781def"}, + {file = "black-22.6.0-cp310-cp310-win_amd64.whl", hash = "sha256:f6fe02afde060bbeef044af7996f335fbe90b039ccf3f5eb8f16df8b20f77666"}, + {file = "black-22.6.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:cfaf3895a9634e882bf9d2363fed5af8888802d670f58b279b0bece00e9a872d"}, + {file = "black-22.6.0-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:94783f636bca89f11eb5d50437e8e17fbc6a929a628d82304c80fa9cd945f256"}, + {file = "black-22.6.0-cp36-cp36m-win_amd64.whl", hash = "sha256:2ea29072e954a4d55a2ff58971b83365eba5d3d357352a07a7a4df0d95f51c78"}, + {file = "black-22.6.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:e439798f819d49ba1c0bd9664427a05aab79bfba777a6db94fd4e56fae0cb849"}, + {file = "black-22.6.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:187d96c5e713f441a5829e77120c269b6514418f4513a390b0499b0987f2ff1c"}, + {file = "black-22.6.0-cp37-cp37m-win_amd64.whl", hash = "sha256:074458dc2f6e0d3dab7928d4417bb6957bb834434516f21514138437accdbe90"}, + {file = "black-22.6.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:a218d7e5856f91d20f04e931b6f16d15356db1c846ee55f01bac297a705ca24f"}, + {file = "black-22.6.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:568ac3c465b1c8b34b61cd7a4e349e93f91abf0f9371eda1cf87194663ab684e"}, + {file = "black-22.6.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:6c1734ab264b8f7929cef8ae5f900b85d579e6cbfde09d7387da8f04771b51c6"}, + {file = "black-22.6.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c9a3ac16efe9ec7d7381ddebcc022119794872abce99475345c5a61aa18c45ad"}, + {file = "black-22.6.0-cp38-cp38-win_amd64.whl", hash = "sha256:b9fd45787ba8aa3f5e0a0a98920c1012c884622c6c920dbe98dbd05bc7c70fbf"}, + {file = "black-22.6.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7ba9be198ecca5031cd78745780d65a3f75a34b2ff9be5837045dce55db83d1c"}, + {file = "black-22.6.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a3db5b6409b96d9bd543323b23ef32a1a2b06416d525d27e0f67e74f1446c8f2"}, + {file = "black-22.6.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:560558527e52ce8afba936fcce93a7411ab40c7d5fe8c2463e279e843c0328ee"}, + {file = "black-22.6.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b154e6bbde1e79ea3260c4b40c0b7b3109ffcdf7bc4ebf8859169a6af72cd70b"}, + {file = "black-22.6.0-cp39-cp39-win_amd64.whl", hash = "sha256:4af5bc0e1f96be5ae9bd7aaec219c901a94d6caa2484c21983d043371c733fc4"}, + {file = "black-22.6.0-py3-none-any.whl", hash = "sha256:ac609cf8ef5e7115ddd07d85d988d074ed00e10fbc3445aee393e70164a2219c"}, + {file = "black-22.6.0.tar.gz", hash = "sha256:6c6d39e28aed379aec40da1c65434c77d75e65bb59a1e1c283de545fb4e7c6c9"}, +] +bleach = [ + {file = "bleach-5.0.1-py3-none-any.whl", hash = "sha256:085f7f33c15bd408dd9b17a4ad77c577db66d76203e5984b1bd59baeee948b2a"}, + {file = "bleach-5.0.1.tar.gz", hash = "sha256:0d03255c47eb9bd2f26aa9bb7f2107732e7e8fe195ca2f64709fcf3b0a4a085c"}, +] +bx-django-utils = [ + {file = "bx_django_utils-26-py3-none-any.whl", hash = "sha256:c04776c4c6b275ddcadae79fd1578e6ae9ba92d8cf752a9ee4f2b70a22f4236e"}, + {file = "bx_django_utils-26.tar.gz", hash = "sha256:19349d0a8fa165d87b4fd544efb61418f7d6c41cfabaa46d0153bb6d844262a1"}, +] +bx-py-utils = [ + {file = "bx_py_utils-66-py3-none-any.whl", hash = "sha256:00ab6dfc063e8431d195c71c43e1063e3e28790205324fe4ceca676a11fd281a"}, + {file = "bx_py_utils-66.tar.gz", hash = "sha256:c6a9f4f11e3465de676f09f2c6bd888afa6d8ab23a030e1bd9ff2a1697fddcf2"}, +] +certifi = [ + {file = "certifi-2022.6.15-py3-none-any.whl", hash = "sha256:fe86415d55e84719d75f8b69414f6438ac3547d2078ab91b67e779ef69378412"}, + {file = "certifi-2022.6.15.tar.gz", hash = "sha256:84c85a9078b11105f04f3036a9482ae10e4621616db313fe045dd24743a0820d"}, +] +charset-normalizer = [ + {file = "charset-normalizer-2.1.0.tar.gz", hash = "sha256:575e708016ff3a5e3681541cb9d79312c416835686d054a23accb873b254f413"}, + {file = "charset_normalizer-2.1.0-py3-none-any.whl", hash = "sha256:5189b6f22b01957427f35b6a08d9a0bc45b46d3788ef5a92e978433c7a35f8a5"}, +] +click = [ + {file = "click-8.1.3-py3-none-any.whl", hash = "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"}, + {file = "click-8.1.3.tar.gz", hash = "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e"}, +] +colorama = [ + {file = "colorama-0.4.5-py2.py3-none-any.whl", hash = "sha256:854bf444933e37f5824ae7bfc1e98d5bce2ebe4160d46b5edf346a89358e99da"}, + {file = "colorama-0.4.5.tar.gz", hash = "sha256:e6c6b4334fc50988a639d9b98aa429a0b57da6e17b9a44f0451f930b6967b7a4"}, +] +colorlog = [ + {file = "colorlog-6.6.0-py2.py3-none-any.whl", hash = "sha256:351c51e866c86c3217f08e4b067a7974a678be78f07f85fc2d55b8babde6d94e"}, + {file = "colorlog-6.6.0.tar.gz", hash = "sha256:344f73204009e4c83c5b6beb00b3c45dc70fcdae3c80db919e0a4171d006fde8"}, +] +coverage = [ + {file = "coverage-6.4.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f1d5aa2703e1dab4ae6cf416eb0095304f49d004c39e9db1d86f57924f43006b"}, + {file = "coverage-6.4.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4ce1b258493cbf8aec43e9b50d89982346b98e9ffdfaae8ae5793bc112fb0068"}, + {file = "coverage-6.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:83c4e737f60c6936460c5be330d296dd5b48b3963f48634c53b3f7deb0f34ec4"}, + {file = "coverage-6.4.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:84e65ef149028516c6d64461b95a8dbcfce95cfd5b9eb634320596173332ea84"}, + {file = "coverage-6.4.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f69718750eaae75efe506406c490d6fc5a6161d047206cc63ce25527e8a3adad"}, + {file = "coverage-6.4.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e57816f8ffe46b1df8f12e1b348f06d164fd5219beba7d9433ba79608ef011cc"}, + {file = "coverage-6.4.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:01c5615d13f3dd3aa8543afc069e5319cfa0c7d712f6e04b920431e5c564a749"}, + {file = "coverage-6.4.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:75ab269400706fab15981fd4bd5080c56bd5cc07c3bccb86aab5e1d5a88dc8f4"}, + {file = "coverage-6.4.1-cp310-cp310-win32.whl", hash = "sha256:a7f3049243783df2e6cc6deafc49ea123522b59f464831476d3d1448e30d72df"}, + {file = "coverage-6.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:ee2ddcac99b2d2aec413e36d7a429ae9ebcadf912946b13ffa88e7d4c9b712d6"}, + {file = "coverage-6.4.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:fb73e0011b8793c053bfa85e53129ba5f0250fdc0392c1591fd35d915ec75c46"}, + {file = "coverage-6.4.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:106c16dfe494de3193ec55cac9640dd039b66e196e4641fa8ac396181578b982"}, + {file = "coverage-6.4.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:87f4f3df85aa39da00fd3ec4b5abeb7407e82b68c7c5ad181308b0e2526da5d4"}, + {file = "coverage-6.4.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:961e2fb0680b4f5ad63234e0bf55dfb90d302740ae9c7ed0120677a94a1590cb"}, + {file = "coverage-6.4.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:cec3a0f75c8f1031825e19cd86ee787e87cf03e4fd2865c79c057092e69e3a3b"}, + {file = "coverage-6.4.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:129cd05ba6f0d08a766d942a9ed4b29283aff7b2cccf5b7ce279d50796860bb3"}, + {file = "coverage-6.4.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:bf5601c33213d3cb19d17a796f8a14a9eaa5e87629a53979a5981e3e3ae166f6"}, + {file = "coverage-6.4.1-cp37-cp37m-win32.whl", hash = "sha256:269eaa2c20a13a5bf17558d4dc91a8d078c4fa1872f25303dddcbba3a813085e"}, + {file = "coverage-6.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:f02cbbf8119db68455b9d763f2f8737bb7db7e43720afa07d8eb1604e5c5ae28"}, + {file = "coverage-6.4.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ffa9297c3a453fba4717d06df579af42ab9a28022444cae7fa605af4df612d54"}, + {file = "coverage-6.4.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:145f296d00441ca703a659e8f3eb48ae39fb083baba2d7ce4482fb2723e050d9"}, + {file = "coverage-6.4.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d67d44996140af8b84284e5e7d398e589574b376fb4de8ccd28d82ad8e3bea13"}, + {file = "coverage-6.4.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2bd9a6fc18aab8d2e18f89b7ff91c0f34ff4d5e0ba0b33e989b3cd4194c81fd9"}, + {file = "coverage-6.4.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3384f2a3652cef289e38100f2d037956194a837221edd520a7ee5b42d00cc605"}, + {file = "coverage-6.4.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:9b3e07152b4563722be523e8cd0b209e0d1a373022cfbde395ebb6575bf6790d"}, + {file = "coverage-6.4.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:1480ff858b4113db2718848d7b2d1b75bc79895a9c22e76a221b9d8d62496428"}, + {file = "coverage-6.4.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:865d69ae811a392f4d06bde506d531f6a28a00af36f5c8649684a9e5e4a85c83"}, + {file = "coverage-6.4.1-cp38-cp38-win32.whl", hash = "sha256:664a47ce62fe4bef9e2d2c430306e1428ecea207ffd68649e3b942fa8ea83b0b"}, + {file = "coverage-6.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:26dff09fb0d82693ba9e6231248641d60ba606150d02ed45110f9ec26404ed1c"}, + {file = "coverage-6.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d9c80df769f5ec05ad21ea34be7458d1dc51ff1fb4b2219e77fe24edf462d6df"}, + {file = "coverage-6.4.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:39ee53946bf009788108b4dd2894bf1349b4e0ca18c2016ffa7d26ce46b8f10d"}, + {file = "coverage-6.4.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f5b66caa62922531059bc5ac04f836860412f7f88d38a476eda0a6f11d4724f4"}, + {file = "coverage-6.4.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fd180ed867e289964404051a958f7cccabdeed423f91a899829264bb7974d3d3"}, + {file = "coverage-6.4.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:84631e81dd053e8a0d4967cedab6db94345f1c36107c71698f746cb2636c63e3"}, + {file = "coverage-6.4.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:8c08da0bd238f2970230c2a0d28ff0e99961598cb2e810245d7fc5afcf1254e8"}, + {file = "coverage-6.4.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:d42c549a8f41dc103a8004b9f0c433e2086add8a719da00e246e17cbe4056f72"}, + {file = "coverage-6.4.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:309ce4a522ed5fca432af4ebe0f32b21d6d7ccbb0f5fcc99290e71feba67c264"}, + {file = "coverage-6.4.1-cp39-cp39-win32.whl", hash = "sha256:fdb6f7bd51c2d1714cea40718f6149ad9be6a2ee7d93b19e9f00934c0f2a74d9"}, + {file = "coverage-6.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:342d4aefd1c3e7f620a13f4fe563154d808b69cccef415415aece4c786665397"}, + {file = "coverage-6.4.1-pp36.pp37.pp38-none-any.whl", hash = "sha256:4803e7ccf93230accb928f3a68f00ffa80a88213af98ed338a57ad021ef06815"}, + {file = "coverage-6.4.1.tar.gz", hash = "sha256:4321f075095a096e70aff1d002030ee612b65a205a0a0f5b815280d5dc58100c"}, +] +coveralls = [ + {file = "coveralls-3.3.1-py2.py3-none-any.whl", hash = "sha256:f42015f31d386b351d4226389b387ae173207058832fbf5c8ec4b40e27b16026"}, + {file = "coveralls-3.3.1.tar.gz", hash = "sha256:b32a8bb5d2df585207c119d6c01567b81fba690c9c10a753bfe27a335bfc43ea"}, +] +darker = [ + {file = "darker-1.5.0-py3-none-any.whl", hash = "sha256:effd451a364d603578c9df569cc4ab72900b4bb3ffb7b918160ff559ef8af892"}, + {file = "darker-1.5.0.tar.gz", hash = "sha256:a23a917b9abee0725ba68da5f564ed25dd4e63eab6dc5a43d9e2f88b7c19899e"}, +] +deprecated = [ + {file = "Deprecated-1.2.13-py2.py3-none-any.whl", hash = "sha256:64756e3e14c8c5eea9795d93c524551432a0be75629f8f29e67ab8caf076c76d"}, + {file = "Deprecated-1.2.13.tar.gz", hash = "sha256:43ac5335da90c31c24ba028af536a91d41d53f9e6901ddb021bcc572ce44e38d"}, +] +distlib = [ + {file = "distlib-0.3.4-py2.py3-none-any.whl", hash = "sha256:6564fe0a8f51e734df6333d08b8b94d4ea8ee6b99b5ed50613f731fd4089f34b"}, + {file = "distlib-0.3.4.zip", hash = "sha256:e4b58818180336dc9c529bfb9a0b58728ffc09ad92027a3f30b7cd91e3458579"}, +] +django = [ + {file = "Django-3.2.14-py3-none-any.whl", hash = "sha256:a8681e098fa60f7c33a4b628d6fcd3fe983a0939ff1301ecacac21d0b38bad56"}, + {file = "Django-3.2.14.tar.gz", hash = "sha256:677182ba8b5b285a4e072f3ac17ceee6aff1b5ce77fd173cc5b6a2d3dc022fcf"}, +] +django-axes = [ + {file = "django-axes-5.35.0.tar.gz", hash = "sha256:bbe08d72defe8cadab4af12c6dc72c3b40e0e0a771fdeb37c20a3e20a5fc2aec"}, + {file = "django_axes-5.35.0-py3-none-any.whl", hash = "sha256:f74c096ffff3133a49247bab9299f5280647b70bb27784475d49cbf19672458e"}, +] +django-debug-toolbar = [ + {file = "django-debug-toolbar-3.5.0.tar.gz", hash = "sha256:97965f2630692de316ea0c1ca5bfa81660d7ba13146dbc6be2059cf55b35d0e5"}, + {file = "django_debug_toolbar-3.5.0-py3-none-any.whl", hash = "sha256:89a52128309eb4da12738801ff0c202d2ff8730d1c3225fac6acf630c303e661"}, +] +django-fmd = [ + {file = "django-fmd-0.0.1.tar.gz", hash = "sha256:7518fda3aa09eb8f0779dae04949dc3a22663da2c37f98cb1280c52a3ff1ce1b"}, + {file = "django_fmd-0.0.1-py3-none-any.whl", hash = "sha256:74c7205f49eb0e076ecb96284da1d9e59ff1a7e5549d4ece32ab67a4e135d5de"}, +] +django-ipware = [ + {file = "django-ipware-4.0.2.tar.gz", hash = "sha256:602a58325a4808bd19197fef2676a0b2da2df40d0ecf21be414b2ff48c72ad05"}, + {file = "django_ipware-4.0.2-py2.py3-none-any.whl", hash = "sha256:878dbb06a87e25550798e9ef3204ed70a200dd8b15e47dcef848cf08244f04c9"}, +] +django-redis = [ + {file = "django-redis-5.2.0.tar.gz", hash = "sha256:8a99e5582c79f894168f5865c52bd921213253b7fd64d16733ae4591564465de"}, + {file = "django_redis-5.2.0-py3-none-any.whl", hash = "sha256:1d037dc02b11ad7aa11f655d26dac3fb1af32630f61ef4428860a2e29ff92026"}, +] +django-tools = [ + {file = "django-tools-0.50.0.tar.gz", hash = "sha256:c53805b60f6745d9d3a729d2b0add72eb27284362e0aef73d72b1a69704e4357"}, + {file = "django_tools-0.50.0-py3-none-any.whl", hash = "sha256:4ce9f524ba08d2071a762dc66e7f85ac1664b41cc8fa0ce686a651c1610b3d67"}, +] +django-yunohost-integration = [ + {file = "django_yunohost_integration-0.2.4-py3-none-any.whl", hash = "sha256:f5cdb5480025e90de0221d2b1c6282f517fd0c903702563cccb771cb3e1d9417"}, + {file = "django_yunohost_integration-0.2.4.tar.gz", hash = "sha256:a4b3617a3b39225d6162fa88827e9fe8b65388e26a0bbc23ea665c62aa8cb044"}, +] +docopt = [ + {file = "docopt-0.6.2.tar.gz", hash = "sha256:49b3a825280bd66b3aa83585ef59c4a8c82f2c8a522dbe754a8bc8d08c85c491"}, +] +filelock = [ + {file = "filelock-3.7.1-py3-none-any.whl", hash = "sha256:37def7b658813cda163b56fc564cdc75e86d338246458c4c28ae84cabefa2404"}, + {file = "filelock-3.7.1.tar.gz", hash = "sha256:3a0fd85166ad9dbab54c9aec96737b744106dc5f15c0b09a6744a445299fcf04"}, +] +flake8 = [ + {file = "flake8-4.0.1-py2.py3-none-any.whl", hash = "sha256:479b1304f72536a55948cb40a32dce8bb0ffe3501e26eaf292c7e60eb5e0428d"}, + {file = "flake8-4.0.1.tar.gz", hash = "sha256:806e034dda44114815e23c16ef92f95c91e4c71100ff52813adf7132a6ad870d"}, +] +flynt = [ + {file = "flynt-0.76-py3-none-any.whl", hash = "sha256:fc122c5f589b0c4d019d7d33597f4925fd886a8e6fb3cbadb918e4baa3661687"}, + {file = "flynt-0.76.tar.gz", hash = "sha256:7a99c5a550ea9e8c21203f6999ed8ce69cbad7bc8465268469777cf06413193a"}, +] +gunicorn = [ + {file = "gunicorn-20.1.0-py3-none-any.whl", hash = "sha256:9dcc4547dbb1cb284accfb15ab5667a0e5d1881cc443e0677b4882a4067a807e"}, + {file = "gunicorn-20.1.0.tar.gz", hash = "sha256:e0a968b5ba15f8a328fdfd7ab1fcb5af4470c28aaf7e55df02a99bc13138e6e8"}, +] +icdiff = [ + {file = "icdiff-2.0.5.tar.gz", hash = "sha256:35d24b728e48b7e0a12bdb69386d3bfc7eef4fe922d0ac1cd70d6e5c11630bae"}, +] +idna = [ + {file = "idna-3.3-py3-none-any.whl", hash = "sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff"}, + {file = "idna-3.3.tar.gz", hash = "sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d"}, +] +importlib-metadata = [ + {file = "importlib_metadata-4.2.0-py3-none-any.whl", hash = "sha256:057e92c15bc8d9e8109738a48db0ccb31b4d9d5cfbee5a8670879a30be66304b"}, + {file = "importlib_metadata-4.2.0.tar.gz", hash = "sha256:b7e52a1f8dec14a75ea73e0891f3060099ca1d8e6a462a4dff11c3e119ea1b31"}, +] +iniconfig = [ + {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"}, + {file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"}, +] +isort = [ + {file = "isort-5.10.1-py3-none-any.whl", hash = "sha256:6f62d78e2f89b4500b080fe3a81690850cd254227f27f75c3a0c491a1f351ba7"}, + {file = "isort-5.10.1.tar.gz", hash = "sha256:e8443a5e7a020e9d7f97f1d7d9cd17c88bcb3bc7e218bf9cf5095fe550be2951"}, +] +mccabe = [ + {file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"}, + {file = "mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"}, +] +mypy-extensions = [ + {file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"}, + {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"}, +] +packaging = [ + {file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"}, + {file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"}, +] +pathspec = [ + {file = "pathspec-0.9.0-py2.py3-none-any.whl", hash = "sha256:7d15c4ddb0b5c802d161efc417ec1a2558ea2653c2e8ad9c19098201dc1c993a"}, + {file = "pathspec-0.9.0.tar.gz", hash = "sha256:e564499435a2673d586f6b2130bb5b95f04a3ba06f81b8f895b651a3c76aabb1"}, +] +platformdirs = [ + {file = "platformdirs-2.5.2-py3-none-any.whl", hash = "sha256:027d8e83a2d7de06bbac4e5ef7e023c02b863d7ea5d079477e722bb41ab25788"}, + {file = "platformdirs-2.5.2.tar.gz", hash = "sha256:58c8abb07dcb441e6ee4b11d8df0ac856038f944ab98b7be6b27b2a3c7feef19"}, +] +pluggy = [ + {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"}, + {file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"}, +] +pprintpp = [ + {file = "pprintpp-0.4.0-py2.py3-none-any.whl", hash = "sha256:b6b4dcdd0c0c0d75e4d7b2f21a9e933e5b2ce62b26e1a54537f9651ae5a5c01d"}, + {file = "pprintpp-0.4.0.tar.gz", hash = "sha256:ea826108e2c7f49dc6d66c752973c3fc9749142a798d6b254e1e301cfdbc6403"}, +] +psycopg2 = [ + {file = "psycopg2-2.9.3-cp310-cp310-win32.whl", hash = "sha256:083707a696e5e1c330af2508d8fab36f9700b26621ccbcb538abe22e15485362"}, + {file = "psycopg2-2.9.3-cp310-cp310-win_amd64.whl", hash = "sha256:d3ca6421b942f60c008f81a3541e8faf6865a28d5a9b48544b0ee4f40cac7fca"}, + {file = "psycopg2-2.9.3-cp36-cp36m-win32.whl", hash = "sha256:9572e08b50aed176ef6d66f15a21d823bb6f6d23152d35e8451d7d2d18fdac56"}, + {file = "psycopg2-2.9.3-cp36-cp36m-win_amd64.whl", hash = "sha256:a81e3866f99382dfe8c15a151f1ca5fde5815fde879348fe5a9884a7c092a305"}, + {file = "psycopg2-2.9.3-cp37-cp37m-win32.whl", hash = "sha256:cb10d44e6694d763fa1078a26f7f6137d69f555a78ec85dc2ef716c37447e4b2"}, + {file = "psycopg2-2.9.3-cp37-cp37m-win_amd64.whl", hash = "sha256:4295093a6ae3434d33ec6baab4ca5512a5082cc43c0505293087b8a46d108461"}, + {file = "psycopg2-2.9.3-cp38-cp38-win32.whl", hash = "sha256:34b33e0162cfcaad151f249c2649fd1030010c16f4bbc40a604c1cb77173dcf7"}, + {file = "psycopg2-2.9.3-cp38-cp38-win_amd64.whl", hash = "sha256:0762c27d018edbcb2d34d51596e4346c983bd27c330218c56c4dc25ef7e819bf"}, + {file = "psycopg2-2.9.3-cp39-cp39-win32.whl", hash = "sha256:8cf3878353cc04b053822896bc4922b194792df9df2f1ad8da01fb3043602126"}, + {file = "psycopg2-2.9.3-cp39-cp39-win_amd64.whl", hash = "sha256:06f32425949bd5fe8f625c49f17ebb9784e1e4fe928b7cce72edc36fb68e4c0c"}, + {file = "psycopg2-2.9.3.tar.gz", hash = "sha256:8e841d1bf3434da985cc5ef13e6f75c8981ced601fd70cc6bf33351b91562981"}, +] +py = [ + {file = "py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378"}, + {file = "py-1.11.0.tar.gz", hash = "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719"}, +] +pycodestyle = [ + {file = "pycodestyle-2.8.0-py2.py3-none-any.whl", hash = "sha256:720f8b39dde8b293825e7ff02c475f3077124006db4f440dcbc9a20b76548a20"}, + {file = "pycodestyle-2.8.0.tar.gz", hash = "sha256:eddd5847ef438ea1c7870ca7eb78a9d47ce0cdb4851a5523949f2601d0cbbe7f"}, +] +pyflakes = [ + {file = "pyflakes-2.4.0-py2.py3-none-any.whl", hash = "sha256:3bb3a3f256f4b7968c9c788781e4ff07dce46bdf12339dcda61053375426ee2e"}, + {file = "pyflakes-2.4.0.tar.gz", hash = "sha256:05a85c2872edf37a4ed30b0cce2f6093e1d0581f8c19d7393122da7e25b2b24c"}, +] +pyparsing = [ + {file = "pyparsing-3.0.9-py3-none-any.whl", hash = "sha256:5026bae9a10eeaefb61dab2f09052b9f4307d44aee4eda64b309723d8d206bbc"}, + {file = "pyparsing-3.0.9.tar.gz", hash = "sha256:2b020ecf7d21b687f219b71ecad3631f644a47f01403fa1d1036b0c6416d70fb"}, +] +pytest = [ + {file = "pytest-7.1.2-py3-none-any.whl", hash = "sha256:13d0e3ccfc2b6e26be000cb6568c832ba67ba32e719443bfe725814d3c42433c"}, + {file = "pytest-7.1.2.tar.gz", hash = "sha256:a06a0425453864a270bc45e71f783330a7428defb4230fb5e6a731fde06ecd45"}, +] +pytest-cov = [ + {file = "pytest-cov-3.0.0.tar.gz", hash = "sha256:e7f0f5b1617d2210a2cabc266dfe2f4c75a8d32fb89eafb7ad9d06f6d076d470"}, + {file = "pytest_cov-3.0.0-py3-none-any.whl", hash = "sha256:578d5d15ac4a25e5f961c938b85a05b09fdaae9deef3bb6de9a6e766622ca7a6"}, +] +pytest-darker = [ + {file = "pytest_darker-0.1.2-py3-none-any.whl", hash = "sha256:ec7bad719510f0ac2d3d9aeb698cf6d5fb88b76e43b0bc1ee0cfb125332abcfd"}, + {file = "pytest_darker-0.1.2.tar.gz", hash = "sha256:79d725b55e346bfb00304485184ba978723d99c4e475d73547074303255f7544"}, +] +pytest-django = [ + {file = "pytest-django-4.5.2.tar.gz", hash = "sha256:d9076f759bb7c36939dbdd5ae6633c18edfc2902d1a69fdbefd2426b970ce6c2"}, + {file = "pytest_django-4.5.2-py3-none-any.whl", hash = "sha256:c60834861933773109334fe5a53e83d1ef4828f2203a1d6a0fa9972f4f75ab3e"}, +] +pytest-flake8 = [ + {file = "pytest-flake8-1.1.1.tar.gz", hash = "sha256:ba4f243de3cb4c2486ed9e70752c80dd4b636f7ccb27d4eba763c35ed0cd316e"}, + {file = "pytest_flake8-1.1.1-py2.py3-none-any.whl", hash = "sha256:e0661a786f8cbf976c185f706fdaf5d6df0b1667c3bcff8e823ba263618627e7"}, +] +pytest-isort = [ + {file = "pytest-isort-3.0.0.tar.gz", hash = "sha256:4fe4b26ead2af776730ec23f5870d7421f35aace22a41c4e938586ef4d8787cb"}, + {file = "pytest_isort-3.0.0-py3-none-any.whl", hash = "sha256:2d96a25a135d6fd084ac36878e7d54f26f27c6987c2c65f0d12809bffade9cb9"}, +] +python-stdnum = [ + {file = "python-stdnum-1.17.tar.gz", hash = "sha256:374e2b5e13912ccdbf50b0b23fca2c3e0531174805c32d74e145f37756328340"}, + {file = "python_stdnum-1.17-py2.py3-none-any.whl", hash = "sha256:a46e6cf9652807314d369b654b255c86a59f93d18be2834f3d567ed1a346c547"}, +] +pytz = [ + {file = "pytz-2022.1-py2.py3-none-any.whl", hash = "sha256:e68985985296d9a66a881eb3193b0906246245294a881e7c8afe623866ac6a5c"}, + {file = "pytz-2022.1.tar.gz", hash = "sha256:1e760e2fe6a8163bc0b3d9a19c4f84342afa0a2affebfaa84b01b978a02ecaa7"}, +] +pyupgrade = [ + {file = "pyupgrade-2.37.1-py2.py3-none-any.whl", hash = "sha256:dd2a32628d6d2a7dd6c086d98420e234b9e60c1e1d4c55431578491703e762a5"}, + {file = "pyupgrade-2.37.1.tar.gz", hash = "sha256:3d9cbd88507a0f3d7397c46870617f0d073d61401c451c08a06763d6235d9e7d"}, +] +redis = [ + {file = "redis-4.3.4-py3-none-any.whl", hash = "sha256:a52d5694c9eb4292770084fa8c863f79367ca19884b329ab574d5cb2036b3e54"}, + {file = "redis-4.3.4.tar.gz", hash = "sha256:ddf27071df4adf3821c4f2ca59d67525c3a82e5f268bed97b813cb4fabf87880"}, +] +requests = [ + {file = "requests-2.28.1-py3-none-any.whl", hash = "sha256:8fefa2a1a1365bf5520aac41836fbee479da67864514bdb821f31ce07ce65349"}, + {file = "requests-2.28.1.tar.gz", hash = "sha256:7c5599b102feddaa661c826c56ab4fee28bfd17f5abca1ebbe3e7f19d7c97983"}, +] +six = [ + {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, + {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, +] +sqlparse = [ + {file = "sqlparse-0.4.2-py3-none-any.whl", hash = "sha256:48719e356bb8b42991bdbb1e8b83223757b93789c00910a616a071910ca4a64d"}, + {file = "sqlparse-0.4.2.tar.gz", hash = "sha256:0c00730c74263a94e5a9919ade150dfc3b19c574389985446148402998287dae"}, +] +tokenize-rt = [ + {file = "tokenize_rt-4.2.1-py2.py3-none-any.whl", hash = "sha256:08a27fa032a81cf45e8858d0ac706004fcd523e8463415ddf1442be38e204ea8"}, + {file = "tokenize_rt-4.2.1.tar.gz", hash = "sha256:0d4f69026fed520f8a1e0103aa36c406ef4661417f20ca643f913e33531b3b94"}, +] +toml = [ + {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, + {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, +] +tomli = [ + {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, + {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, +] +tox = [ + {file = "tox-3.25.1-py2.py3-none-any.whl", hash = "sha256:c38e15f4733683a9cc0129fba078633e07eb0961f550a010ada879e95fb32632"}, + {file = "tox-3.25.1.tar.gz", hash = "sha256:c138327815f53bc6da4fe56baec5f25f00622ae69ef3fe4e1e385720e22486f9"}, +] +typed-ast = [ + {file = "typed_ast-1.5.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:669dd0c4167f6f2cd9f57041e03c3c2ebf9063d0757dc89f79ba1daa2bfca9d4"}, + {file = "typed_ast-1.5.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:211260621ab1cd7324e0798d6be953d00b74e0428382991adfddb352252f1d62"}, + {file = "typed_ast-1.5.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:267e3f78697a6c00c689c03db4876dd1efdfea2f251a5ad6555e82a26847b4ac"}, + {file = "typed_ast-1.5.4-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:c542eeda69212fa10a7ada75e668876fdec5f856cd3d06829e6aa64ad17c8dfe"}, + {file = "typed_ast-1.5.4-cp310-cp310-win_amd64.whl", hash = "sha256:a9916d2bb8865f973824fb47436fa45e1ebf2efd920f2b9f99342cb7fab93f72"}, + {file = "typed_ast-1.5.4-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:79b1e0869db7c830ba6a981d58711c88b6677506e648496b1f64ac7d15633aec"}, + {file = "typed_ast-1.5.4-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a94d55d142c9265f4ea46fab70977a1944ecae359ae867397757d836ea5a3f47"}, + {file = "typed_ast-1.5.4-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:183afdf0ec5b1b211724dfef3d2cad2d767cbefac291f24d69b00546c1837fb6"}, + {file = "typed_ast-1.5.4-cp36-cp36m-win_amd64.whl", hash = "sha256:639c5f0b21776605dd6c9dbe592d5228f021404dafd377e2b7ac046b0349b1a1"}, + {file = "typed_ast-1.5.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:cf4afcfac006ece570e32d6fa90ab74a17245b83dfd6655a6f68568098345ff6"}, + {file = "typed_ast-1.5.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ed855bbe3eb3715fca349c80174cfcfd699c2f9de574d40527b8429acae23a66"}, + {file = "typed_ast-1.5.4-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:6778e1b2f81dfc7bc58e4b259363b83d2e509a65198e85d5700dfae4c6c8ff1c"}, + {file = "typed_ast-1.5.4-cp37-cp37m-win_amd64.whl", hash = "sha256:0261195c2062caf107831e92a76764c81227dae162c4f75192c0d489faf751a2"}, + {file = "typed_ast-1.5.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2efae9db7a8c05ad5547d522e7dbe62c83d838d3906a3716d1478b6c1d61388d"}, + {file = "typed_ast-1.5.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7d5d014b7daa8b0bf2eaef684295acae12b036d79f54178b92a2b6a56f92278f"}, + {file = "typed_ast-1.5.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:370788a63915e82fd6f212865a596a0fefcbb7d408bbbb13dea723d971ed8bdc"}, + {file = "typed_ast-1.5.4-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:4e964b4ff86550a7a7d56345c7864b18f403f5bd7380edf44a3c1fb4ee7ac6c6"}, + {file = "typed_ast-1.5.4-cp38-cp38-win_amd64.whl", hash = "sha256:683407d92dc953c8a7347119596f0b0e6c55eb98ebebd9b23437501b28dcbb8e"}, + {file = "typed_ast-1.5.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4879da6c9b73443f97e731b617184a596ac1235fe91f98d279a7af36c796da35"}, + {file = "typed_ast-1.5.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3e123d878ba170397916557d31c8f589951e353cc95fb7f24f6bb69adc1a8a97"}, + {file = "typed_ast-1.5.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ebd9d7f80ccf7a82ac5f88c521115cc55d84e35bf8b446fcd7836eb6b98929a3"}, + {file = "typed_ast-1.5.4-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:98f80dee3c03455e92796b58b98ff6ca0b2a6f652120c263efdba4d6c5e58f72"}, + {file = "typed_ast-1.5.4-cp39-cp39-win_amd64.whl", hash = "sha256:0fdbcf2fef0ca421a3f5912555804296f0b0960f0418c440f5d6d3abb549f3e1"}, + {file = "typed_ast-1.5.4.tar.gz", hash = "sha256:39e21ceb7388e4bb37f4c679d72707ed46c2fbf2a5609b8b8ebc4b067d977df2"}, +] +typing-extensions = [ + {file = "typing_extensions-4.3.0-py3-none-any.whl", hash = "sha256:25642c956049920a5aa49edcdd6ab1e06d7e5d467fc00e0506c44ac86fbfca02"}, + {file = "typing_extensions-4.3.0.tar.gz", hash = "sha256:e6d2677a32f47fc7eb2795db1dd15c1f34eff616bcaf2cfb5e997f854fa1c4a6"}, +] +urllib3 = [ + {file = "urllib3-1.26.10-py2.py3-none-any.whl", hash = "sha256:8298d6d56d39be0e3bc13c1c97d133f9b45d797169a0e11cdd0e0489d786f7ec"}, + {file = "urllib3-1.26.10.tar.gz", hash = "sha256:879ba4d1e89654d9769ce13121e0f94310ea32e8d2f8cf587b77c08bbcdb30d6"}, +] +virtualenv = [ + {file = "virtualenv-20.15.1-py2.py3-none-any.whl", hash = "sha256:b30aefac647e86af6d82bfc944c556f8f1a9c90427b2fb4e3bfbf338cb82becf"}, + {file = "virtualenv-20.15.1.tar.gz", hash = "sha256:288171134a2ff3bfb1a2f54f119e77cd1b81c29fc1265a2356f3e8d14c7d58c4"}, +] +webencodings = [ + {file = "webencodings-0.5.1-py2.py3-none-any.whl", hash = "sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78"}, + {file = "webencodings-0.5.1.tar.gz", hash = "sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923"}, +] +wrapt = [ + {file = "wrapt-1.14.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:1b376b3f4896e7930f1f772ac4b064ac12598d1c38d04907e696cc4d794b43d3"}, + {file = "wrapt-1.14.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:903500616422a40a98a5a3c4ff4ed9d0066f3b4c951fa286018ecdf0750194ef"}, + {file = "wrapt-1.14.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:5a9a0d155deafd9448baff28c08e150d9b24ff010e899311ddd63c45c2445e28"}, + {file = "wrapt-1.14.1-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:ddaea91abf8b0d13443f6dac52e89051a5063c7d014710dcb4d4abb2ff811a59"}, + {file = "wrapt-1.14.1-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:36f582d0c6bc99d5f39cd3ac2a9062e57f3cf606ade29a0a0d6b323462f4dd87"}, + {file = "wrapt-1.14.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:7ef58fb89674095bfc57c4069e95d7a31cfdc0939e2a579882ac7d55aadfd2a1"}, + {file = "wrapt-1.14.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:e2f83e18fe2f4c9e7db597e988f72712c0c3676d337d8b101f6758107c42425b"}, + {file = "wrapt-1.14.1-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:ee2b1b1769f6707a8a445162ea16dddf74285c3964f605877a20e38545c3c462"}, + {file = "wrapt-1.14.1-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:833b58d5d0b7e5b9832869f039203389ac7cbf01765639c7309fd50ef619e0b1"}, + {file = "wrapt-1.14.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:80bb5c256f1415f747011dc3604b59bc1f91c6e7150bd7db03b19170ee06b320"}, + {file = "wrapt-1.14.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:07f7a7d0f388028b2df1d916e94bbb40624c59b48ecc6cbc232546706fac74c2"}, + {file = "wrapt-1.14.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:02b41b633c6261feff8ddd8d11c711df6842aba629fdd3da10249a53211a72c4"}, + {file = "wrapt-1.14.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2fe803deacd09a233e4762a1adcea5db5d31e6be577a43352936179d14d90069"}, + {file = "wrapt-1.14.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:257fd78c513e0fb5cdbe058c27a0624c9884e735bbd131935fd49e9fe719d310"}, + {file = "wrapt-1.14.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:4fcc4649dc762cddacd193e6b55bc02edca674067f5f98166d7713b193932b7f"}, + {file = "wrapt-1.14.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:11871514607b15cfeb87c547a49bca19fde402f32e2b1c24a632506c0a756656"}, + {file = "wrapt-1.14.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8ad85f7f4e20964db4daadcab70b47ab05c7c1cf2a7c1e51087bfaa83831854c"}, + {file = "wrapt-1.14.1-cp310-cp310-win32.whl", hash = "sha256:a9a52172be0b5aae932bef82a79ec0a0ce87288c7d132946d645eba03f0ad8a8"}, + {file = "wrapt-1.14.1-cp310-cp310-win_amd64.whl", hash = "sha256:6d323e1554b3d22cfc03cd3243b5bb815a51f5249fdcbb86fda4bf62bab9e164"}, + {file = "wrapt-1.14.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:43ca3bbbe97af00f49efb06e352eae40434ca9d915906f77def219b88e85d907"}, + {file = "wrapt-1.14.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:6b1a564e6cb69922c7fe3a678b9f9a3c54e72b469875aa8018f18b4d1dd1adf3"}, + {file = "wrapt-1.14.1-cp35-cp35m-manylinux2010_i686.whl", hash = "sha256:00b6d4ea20a906c0ca56d84f93065b398ab74b927a7a3dbd470f6fc503f95dc3"}, + {file = "wrapt-1.14.1-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:a85d2b46be66a71bedde836d9e41859879cc54a2a04fad1191eb50c2066f6e9d"}, + {file = "wrapt-1.14.1-cp35-cp35m-win32.whl", hash = "sha256:dbcda74c67263139358f4d188ae5faae95c30929281bc6866d00573783c422b7"}, + {file = "wrapt-1.14.1-cp35-cp35m-win_amd64.whl", hash = "sha256:b21bb4c09ffabfa0e85e3a6b623e19b80e7acd709b9f91452b8297ace2a8ab00"}, + {file = "wrapt-1.14.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:9e0fd32e0148dd5dea6af5fee42beb949098564cc23211a88d799e434255a1f4"}, + {file = "wrapt-1.14.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9736af4641846491aedb3c3f56b9bc5568d92b0692303b5a305301a95dfd38b1"}, + {file = "wrapt-1.14.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5b02d65b9ccf0ef6c34cba6cf5bf2aab1bb2f49c6090bafeecc9cd81ad4ea1c1"}, + {file = "wrapt-1.14.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21ac0156c4b089b330b7666db40feee30a5d52634cc4560e1905d6529a3897ff"}, + {file = "wrapt-1.14.1-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:9f3e6f9e05148ff90002b884fbc2a86bd303ae847e472f44ecc06c2cd2fcdb2d"}, + {file = "wrapt-1.14.1-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:6e743de5e9c3d1b7185870f480587b75b1cb604832e380d64f9504a0535912d1"}, + {file = "wrapt-1.14.1-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:d79d7d5dc8a32b7093e81e97dad755127ff77bcc899e845f41bf71747af0c569"}, + {file = "wrapt-1.14.1-cp36-cp36m-win32.whl", hash = "sha256:81b19725065dcb43df02b37e03278c011a09e49757287dca60c5aecdd5a0b8ed"}, + {file = "wrapt-1.14.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b014c23646a467558be7da3d6b9fa409b2c567d2110599b7cf9a0c5992b3b471"}, + {file = "wrapt-1.14.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:88bd7b6bd70a5b6803c1abf6bca012f7ed963e58c68d76ee20b9d751c74a3248"}, + {file = "wrapt-1.14.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b5901a312f4d14c59918c221323068fad0540e34324925c8475263841dbdfe68"}, + {file = "wrapt-1.14.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d77c85fedff92cf788face9bfa3ebaa364448ebb1d765302e9af11bf449ca36d"}, + {file = "wrapt-1.14.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d649d616e5c6a678b26d15ece345354f7c2286acd6db868e65fcc5ff7c24a77"}, + {file = "wrapt-1.14.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:7d2872609603cb35ca513d7404a94d6d608fc13211563571117046c9d2bcc3d7"}, + {file = "wrapt-1.14.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:ee6acae74a2b91865910eef5e7de37dc6895ad96fa23603d1d27ea69df545015"}, + {file = "wrapt-1.14.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:2b39d38039a1fdad98c87279b48bc5dce2c0ca0d73483b12cb72aa9609278e8a"}, + {file = "wrapt-1.14.1-cp37-cp37m-win32.whl", hash = "sha256:60db23fa423575eeb65ea430cee741acb7c26a1365d103f7b0f6ec412b893853"}, + {file = "wrapt-1.14.1-cp37-cp37m-win_amd64.whl", hash = "sha256:709fe01086a55cf79d20f741f39325018f4df051ef39fe921b1ebe780a66184c"}, + {file = "wrapt-1.14.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8c0ce1e99116d5ab21355d8ebe53d9460366704ea38ae4d9f6933188f327b456"}, + {file = "wrapt-1.14.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:e3fb1677c720409d5f671e39bac6c9e0e422584e5f518bfd50aa4cbbea02433f"}, + {file = "wrapt-1.14.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:642c2e7a804fcf18c222e1060df25fc210b9c58db7c91416fb055897fc27e8cc"}, + {file = "wrapt-1.14.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7b7c050ae976e286906dd3f26009e117eb000fb2cf3533398c5ad9ccc86867b1"}, + {file = "wrapt-1.14.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ef3f72c9666bba2bab70d2a8b79f2c6d2c1a42a7f7e2b0ec83bb2f9e383950af"}, + {file = "wrapt-1.14.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:01c205616a89d09827986bc4e859bcabd64f5a0662a7fe95e0d359424e0e071b"}, + {file = "wrapt-1.14.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:5a0f54ce2c092aaf439813735584b9537cad479575a09892b8352fea5e988dc0"}, + {file = "wrapt-1.14.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:2cf71233a0ed05ccdabe209c606fe0bac7379fdcf687f39b944420d2a09fdb57"}, + {file = "wrapt-1.14.1-cp38-cp38-win32.whl", hash = "sha256:aa31fdcc33fef9eb2552cbcbfee7773d5a6792c137b359e82879c101e98584c5"}, + {file = "wrapt-1.14.1-cp38-cp38-win_amd64.whl", hash = "sha256:d1967f46ea8f2db647c786e78d8cc7e4313dbd1b0aca360592d8027b8508e24d"}, + {file = "wrapt-1.14.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3232822c7d98d23895ccc443bbdf57c7412c5a65996c30442ebe6ed3df335383"}, + {file = "wrapt-1.14.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:988635d122aaf2bdcef9e795435662bcd65b02f4f4c1ae37fbee7401c440b3a7"}, + {file = "wrapt-1.14.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cca3c2cdadb362116235fdbd411735de4328c61425b0aa9f872fd76d02c4e86"}, + {file = "wrapt-1.14.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d52a25136894c63de15a35bc0bdc5adb4b0e173b9c0d07a2be9d3ca64a332735"}, + {file = "wrapt-1.14.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:40e7bc81c9e2b2734ea4bc1aceb8a8f0ceaac7c5299bc5d69e37c44d9081d43b"}, + {file = "wrapt-1.14.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b9b7a708dd92306328117d8c4b62e2194d00c365f18eff11a9b53c6f923b01e3"}, + {file = "wrapt-1.14.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:6a9a25751acb379b466ff6be78a315e2b439d4c94c1e99cb7266d40a537995d3"}, + {file = "wrapt-1.14.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:34aa51c45f28ba7f12accd624225e2b1e5a3a45206aa191f6f9aac931d9d56fe"}, + {file = "wrapt-1.14.1-cp39-cp39-win32.whl", hash = "sha256:dee0ce50c6a2dd9056c20db781e9c1cfd33e77d2d569f5d1d9321c641bb903d5"}, + {file = "wrapt-1.14.1-cp39-cp39-win_amd64.whl", hash = "sha256:dee60e1de1898bde3b238f18340eec6148986da0455d8ba7848d50470a7a32fb"}, + {file = "wrapt-1.14.1.tar.gz", hash = "sha256:380a85cf89e0e69b7cfbe2ea9f765f004ff419f34194018a6827ac0e3edfed4d"}, +] +zipp = [ + {file = "zipp-3.8.0-py3-none-any.whl", hash = "sha256:c4f6e5bbf48e74f7a38e7cc5b0480ff42b0ae5178957d564d18932525d5cf099"}, + {file = "zipp-3.8.0.tar.gz", hash = "sha256:56bf8aadb83c24db6c4b577e13de374ccfb67da2078beba1d037c17980bf43ad"}, +] diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..d7751d9 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,112 @@ +[tool.poetry] +name = "django-fmd_ynh" +version = "0.0.1~ynh1" +description = "Test django-fmd_ynh via local_test.py" +authors = ["JensDiemer "] +license = "GPL" + +[tool.poetry.dependencies] +# Keep Python 3.7 until Yunohost contains a newer Python Version ;) +python = ">=3.7,<4.0.0" +django-fmd = ">=0.0.1" # https://gitlab.com/jedie/django-find-my-device + +# Note: "ynh" extras will install gunicorn, psycopg2, django-redis and django-axes +django_yunohost_integration = {version = ">=v0.2.0", extras = ["ynh"]} + +psycopg2 = "*" # https://www.psycopg.org/docs/install.html#build-prerequisites + +[tool.poetry.dev-dependencies] +bx_py_utils = "*" +tox = "*" +pytest = "*" +pytest-cov = "*" +pytest-django = "*" +pytest-darker = "*" # https://github.com/akaihola/pytest-darker +pytest-flake8 = "*" +pytest-isort = "*" +coveralls = "*" +isort = "*" +flake8 = "*" +flynt = "*" +darker = "*" # https://github.com/akaihola/darker +pyupgrade = "*" + +[build-system] +requires = ["poetry-core>=1.0.0"] +build-backend = "poetry.core.masonry.api" + + +[tool.darker] +src = ['.'] +revision = "origin/master..." +line_length = 100 +verbose = true +skip_string_normalization = true +diff = false +check = false +stdout = false +isort = true +lint = [ + "flake8", +] +log_level = "INFO" + + +[tool.flynt] +line_length = 100 + + +[tool.isort] +# https://pycqa.github.io/isort/docs/configuration/config_files/#pyprojecttoml-preferred-format +atomic=true +profile='black' +line_length=100 +skip_glob=["*/htmlcov/*","*/migrations/*"] +known_first_party=["findmydevice","findmydevice_project","findmydevice_tests"] +lines_after_imports=2 + + +[tool.pytest.ini_options] +# https://docs.pytest.org/en/latest/customize.html#pyproject-toml +minversion = "6.0" +norecursedirs = ".* .git __pycache__ conf coverage* dist htmlcov" +# sometimes helpfull "addopts" arguments: +# -vv +# --verbose +# --capture=no +# --trace-config +# --full-trace +# -p no:warnings +addopts = """ + --reuse-db + --nomigrations + --cov=. + --cov-report term-missing + --cov-report html + --cov-report xml + --no-cov-on-fail + --showlocals + --darker + --flake8 + --isort + --doctest-modules + --failed-first + --last-failed-no-failures all + --new-first +""" +# TODO: --mypy + +[tool.tox] +# https://tox.readthedocs.io/en/latest/example/basic.html#pyproject-toml-tox-legacy-ini +legacy_tox_ini = """ +[tox] +isolated_build = True +envlist = px310,py39,py38,py37 +skip_missing_interpreters = True + +[testenv] +passenv = * +whitelist_externals = make +commands = + pytest findmydevice findmydevice_project +""" diff --git a/run_pytest.py b/run_pytest.py new file mode 100644 index 0000000..086b5ef --- /dev/null +++ b/run_pytest.py @@ -0,0 +1,25 @@ +""" + Run pytest against local test creation +""" + +from pathlib import Path + + +try: + from django_yunohost_integration.pytest_helper import run_pytest +except ImportError as err: + raise ImportError('Did you forget to activate a virtual environment?') from err + + +BASE_PATH = Path(__file__).parent + + +def main(): + run_pytest( + django_settings_path=BASE_PATH / 'conf' / 'settings.py', + destination=BASE_PATH / 'local_test', + ) + + +if __name__ == '__main__': + main() diff --git a/scripts/_common.sh b/scripts/_common.sh new file mode 100644 index 0000000..e348462 --- /dev/null +++ b/scripts/_common.sh @@ -0,0 +1,90 @@ +#!/bin/bash + +#================================================= +# RETRIEVE ARGUMENTS FROM THE MANIFEST +#================================================= + +domain=$YNH_APP_ARG_DOMAIN +path_url=$YNH_APP_ARG_PATH + +admin=$YNH_APP_ARG_ADMIN +app=$YNH_APP_INSTANCE_NAME + +# Currently not used: django-fmd has no public pages, yet! +is_public=$YNH_APP_ARG_IS_PUBLIC + +#================================================= +# SET CONSTANTS +#================================================= + +public_path=/var/www/$app +final_path=/opt/yunohost/$app +log_path=/var/log/$app +log_file="${log_path}/django-fmd.log" + +# Default: settings.DEBUG=False +django_debug="False" + +#================================================= +# COMMON VARIABLES +#================================================= + +# dependencies used by the app +pkg_dependencies="build-essential python3-dev python3-pip python3-venv git libpq-dev postgresql postgresql-contrib libjpeg-dev" + +#================================================= +# Redis HELPERS +#================================================= + +# get the first available redis database +# +# usage: ynh_redis_get_free_db +# | returns: the database number to use +ynh_redis_get_free_db() { + local result max db + result=$(redis-cli INFO keyspace) + + # get the num + max=$(cat /etc/redis/redis.conf | grep ^databases | grep -Eow "[0-9]+") + + db=0 + # default Debian setting is 15 databases + for i in $(seq 0 "$max") + do + if ! echo "$result" | grep -q "db$i" + then + db=$i + break 1 + fi + db=-1 + done + + test "$db" -eq -1 && ynh_die "No available Redis databases..." + + echo "$db" +} + +# Create a master password and set up global settings +# Please always call this script in install and restore scripts +# +# usage: ynh_redis_remove_db database +# | arg: database - the database to erase +ynh_redis_remove_db() { + local db=$1 + redis-cli -n "$db" flushall +} + +#================================================= + +# Execute a command as another user +# usage: ynh_exec_as USER COMMAND [ARG ...] +ynh_exec_as() { + local USER=$1 + shift 1 + + if [[ $USER = $(whoami) ]]; then + eval "$@" + else + sudo -u "$USER" "$@" + fi +} diff --git a/scripts/backup b/scripts/backup new file mode 100755 index 0000000..308ba1c --- /dev/null +++ b/scripts/backup @@ -0,0 +1,69 @@ +#!/bin/bash + +#================================================= +# GENERIC START +#================================================= +# IMPORT GENERIC HELPERS +#================================================= + +source ../settings/scripts/_common.sh +source /usr/share/yunohost/helpers + +ynh_abort_if_errors + +#================================================= +# LOAD SETTINGS +#================================================= +ynh_print_info --message="Loading installation settings..." + +app=$YNH_APP_INSTANCE_NAME + +public_path=$(ynh_app_setting_get --app="$app" --key=public_path) +final_path=$(ynh_app_setting_get --app="$app" --key=final_path) +db_name=$(ynh_app_setting_get --app="$app" --key=db_name) + +domain=$(ynh_app_setting_get --app="$app" --key=domain) + +#================================================= +# DECLARE DATA AND CONF FILES TO BACKUP +#================================================= +ynh_print_info --message="Declaring files to be backed up..." + +#================================================= +# BACKUP THE APP MAIN DIR +#================================================= + +ynh_backup --src_path="$final_path" +ynh_backup --src_path="$public_path" + +#================================================= +# BACKUP THE NGINX CONFIGURATION +#================================================= + +ynh_backup --src_path="/etc/nginx/conf.d/$domain.d/$app.conf" + +#================================================= +# BACKUP THE PostgreSQL DATABASE +#================================================= + +ynh_psql_dump_db --database="$db_name" > db.sql + +#================================================= +# SPECIFIC BACKUP +#================================================= +# BACKUP LOGROTATE +#================================================= + +ynh_backup --src_path="/etc/logrotate.d/$app" + +#================================================= +# BACKUP SYSTEMD +#================================================= + +ynh_backup --src_path="/etc/systemd/system/$app.service" + +#================================================= +# END OF SCRIPT +#================================================= + +ynh_print_info --message="Backup script completed for $app. (YunoHost will then actually copy those files to the archive)." diff --git a/scripts/change_url b/scripts/change_url new file mode 100644 index 0000000..eaad24b --- /dev/null +++ b/scripts/change_url @@ -0,0 +1,159 @@ +#!/bin/bash + +#================================================= +# GENERIC STARTING +#================================================= +# IMPORT GENERIC HELPERS +#================================================= + +source _common.sh +source /usr/share/yunohost/helpers + +#================================================= +# RETRIEVE ARGUMENTS +#================================================= + +old_domain=$YNH_APP_OLD_DOMAIN +old_path=$YNH_APP_OLD_PATH + +new_domain=$YNH_APP_NEW_DOMAIN +new_path=$YNH_APP_NEW_PATH + +#================================================= +# LOAD SETTINGS +#================================================= +ynh_script_progression --message="Loading installation settings..." + +admin=$(ynh_app_setting_get --app="$app" --key=admin) +public_path=$(ynh_app_setting_get --app="$app" --key=public_path) +final_path=$(ynh_app_setting_get --app="$app" --key=final_path) +log_path=$(ynh_app_setting_get --app="$app" --key=log_path) + +port=$(ynh_app_setting_get --app="$app" --key=port) +db_pwd=$(ynh_app_setting_get --app="$app" --key=psqlpwd) +admin_mail=$(ynh_user_get_info "$admin" mail) +redis_db=$(ynh_app_setting_get --app="$app" --key=redis_db) + +#================================================= +# BACKUP BEFORE UPGRADE THEN ACTIVE TRAP +#================================================= +ynh_script_progression --message="Backing up the app before changing its URL (may take a while)..." --weight=40 + +# Backup the current version of the app +ynh_backup_before_upgrade +ynh_clean_setup () { + # Remove the new domain config file, the remove script won't do it as it doesn't know yet its location. + ynh_secure_remove --file="/etc/nginx/conf.d/$new_domain.d/$app.conf" + + # restore it if the upgrade fails + ynh_restore_upgradebackup +} +# Exit if an error occurs during the execution of the script +ynh_abort_if_errors + +#================================================= +# CHECK WHICH PARTS SHOULD BE CHANGED +#================================================= + +change_domain=0 +if [ "$old_domain" != "$new_domain" ] +then + change_domain=1 +fi + +change_path=0 +if [ "$old_path" != "$new_path" ] +then + change_path=1 +fi + +#================================================= +# STANDARD MODIFICATIONS +#================================================= +# STOP SYSTEMD SERVICE +#================================================= +ynh_script_progression --message="Stopping systemd services..." + +ynh_systemd_action --service_name="$app" --action="stop" + +#================================================= +# STANDARD MODIFICATIONS +#================================================= +# MODIFY URL IN NGINX CONF +#================================================= +ynh_script_progression --message="Updating nginx web server configuration..." + +nginx_conf_path=/etc/nginx/conf.d/$old_domain.d/$app.conf + +# Change the path in the nginx config file +if [ $change_path -eq 1 ] +then + # Make a backup of the original nginx config file if modified + ynh_backup_if_checksum_is_different --file="$nginx_conf_path" + # Set global variables for nginx helper + domain="$old_domain" + path_url="$new_path" + # Create a dedicated nginx config + ynh_add_nginx_config "public_path" "port" +fi + +# Change the domain for nginx +if [ $change_domain -eq 1 ] +then + # Delete file checksum for the old conf file location + ynh_delete_file_checksum --file="$nginx_conf_path" + mv $nginx_conf_path /etc/nginx/conf.d/$new_domain.d/$app.conf + # Store file checksum for the new config file location + ynh_store_file_checksum --file="/etc/nginx/conf.d/$new_domain.d/$app.conf" +fi + +#================================================= +# SPECIFIC MODIFICATIONS +#================================================= +# MODIFY SETTINGS +#================================================= +ynh_script_progression --message="Modify django-fmd's config file..." + +# save old settings file +settings="$final_path/settings.py" +ynh_backup_if_checksum_is_different --file="$settings" + +cp "../conf/settings.py" "$settings" + +ynh_replace_string --match_string="__APP__" --replace_string="$app" --target_file="$settings" +ynh_replace_string --match_string="__DB_PWD__" --replace_string="$db_pwd" --target_file="$settings" +ynh_replace_string --match_string="__ADMIN__" --replace_string="$admin" --target_file="$settings" +ynh_replace_string --match_string="__ADMINMAIL__" --replace_string="$admin_mail" --target_file="$settings" +ynh_replace_string --match_string="__FINAL_HOME_PATH__" --replace_string="$final_path" --target_file="$settings" +ynh_replace_string --match_string="__FINAL_WWW_PATH__" --replace_string="$public_path" --target_file="$settings" +ynh_replace_string --match_string="__LOG_FILE__" --replace_string="$log_file" --target_file="$settings" +ynh_replace_string --match_string="__REDIS_DB__" --replace_string="$redis_db" --target_file="$settings" + +# Difference to install/upgrade scripts: Use $new_domain and $new_path here: +ynh_replace_string --match_string="__DOMAIN__" --replace_string="$new_domain" --target_file="$settings" +ynh_replace_string --match_string="__PATH_URL__" --replace_string="$new_path" --target_file="$settings" + +# Recalculate and store the config file checksum into the app settings +ynh_store_file_checksum --file="$settings" + +#================================================= +# GENERIC FINALISATION +#================================================= +# START SYSTEMD SERVICE +#================================================= +ynh_script_progression --message="Starting systemd services..." --weight=5 + +ynh_systemd_action --service_name="$app" --action="start" + +#================================================= +# RELOAD NGINX +#================================================= +ynh_script_progression --message="Reloading nginx web server..." + +ynh_systemd_action --service_name=nginx --action=reload + +#================================================= +# END OF SCRIPT +#================================================= + +ynh_script_progression --message="Change of URL completed for $app" --last diff --git a/scripts/install b/scripts/install new file mode 100755 index 0000000..029e772 --- /dev/null +++ b/scripts/install @@ -0,0 +1,249 @@ +#!/bin/bash + +#================================================= +# IMPORT GENERIC HELPERS +#================================================= + +source _common.sh +source /usr/share/yunohost/helpers + +#================================================= +# MANAGE SCRIPT FAILURE +#================================================= + +# Exit if an error occurs during the execution of the script +ynh_abort_if_errors + +#================================================= +# CHECK IF THE APP CAN BE INSTALLED WITH THESE ARGS +#================================================= +ynh_script_progression --message="Validating installation parameters..." + +# Path for e.g. "static" files, served by nginx: +test ! -e "$public_path" || ynh_die --message="This path already contains a folder" + +# Path for own config files, e.g.: Django's settings.py: +test ! -e "$final_path" || ynh_die --message="This path already contains a folder" + +# Register (book) web path +ynh_webpath_register --app="$app" --domain="$domain" --path_url="$path_url" + +mkdir -p "$public_path/media" "$public_path/static" +mkdir -p "$final_path" + +mkdir -p "$log_path" +touch "${log_file}" + +#================================================= +# STORE SETTINGS FROM MANIFEST +#================================================= +ynh_script_progression --message="Storing installation settings..." + +ynh_app_setting_set --app="$app" --key=admin --value="$admin" +ynh_app_setting_set --app="$app" --key=public_path --value="$public_path" +ynh_app_setting_set --app="$app" --key=final_path --value="$final_path" +ynh_app_setting_set --app="$app" --key=log_path --value="$log_file" + +ynh_app_setting_set --app="$app" --key=domain --value="$domain" +ynh_app_setting_set --app="$app" --key=path --value="$path_url" + +# Find a free port +port=$(ynh_find_port --port=8000) +# Set port as application setting +# https://github.com/YunoHost/yunohost/blob/dev/data/helpers.d/setting +ynh_app_setting_set --app="$app" --key=port --value="$port" + +db_pwd=$(ynh_app_setting_get --app="$app" --key=psqlpwd) +admin_mail=$(ynh_user_get_info --username="$admin" --key=mail) +redis_db=$(ynh_redis_get_free_db) + +# Always deactivate settings.DEBUG: +ynh_app_setting_set --app=$app --key=django_debug --value="False" + +#================================================= +# STANDARD MODIFICATIONS +#================================================= +# INSTALL DEPENDENCIES +#================================================= +ynh_script_progression --message="Installing dependencies..." --weight=20 + +ynh_exec_warn_less ynh_install_app_dependencies "$pkg_dependencies" + +#================================================= +# CREATE A PostgreSQL DATABASE +#================================================= +ynh_script_progression --message="Creating a PostgreSQL database..." + +db_name=$(ynh_sanitize_dbid --db_name="$app") +db_user=$db_name +ynh_app_setting_set --app="$app" --key=db_name --value="$db_name" + +ynh_psql_test_if_first_run + +# Initialize database and store postgres password for upgrade +ynh_psql_setup_db --db_user="$db_user" --db_name="$db_name" + +#================================================= +# NGINX CONFIGURATION +#================================================= +ynh_script_progression --message="Configuring nginx web server..." + +# Create a dedicated nginx config +# https://github.com/YunoHost/yunohost/blob/dev/data/helpers.d/nginx +ynh_add_nginx_config "public_path" "port" + +#================================================= +# CREATE DEDICATED USER +#================================================= +ynh_script_progression --message="Configuring system user..." + +# A home directory for venv and settings etc. +ynh_system_user_create --username="$app" --home_dir="$final_path" --use_shell + +#================================================= +# PIP INSTALLATION +#================================================= +ynh_script_progression --message="Install project via pip..." --weight=50 + +# Always recreate everything fresh with current python version +ynh_secure_remove "${final_path}/venv" + +# Skip pip because of: https://github.com/YunoHost/issues/issues/1960 +python3 -m venv --without-pip "${final_path}/venv" + +cp ../conf/requirements.txt "$final_path/requirements.txt" +chown -R "$app:" "$final_path" + +#run source in a 'sub shell' +( + set +o nounset + source "${final_path}/venv/bin/activate" + set -o nounset + ynh_exec_as $app $final_path/venv/bin/python3 -m ensurepip + ynh_exec_as $app $final_path/venv/bin/pip3 install --upgrade wheel pip setuptools + ynh_exec_as $app $final_path/venv/bin/pip3 install --no-deps -r "$final_path/requirements.txt" +) + +#================================================= +# copy config files +# ================================================ +ynh_script_progression --message="Create project configuration files..." + +gunicorn_conf="$final_path/gunicorn.conf.py" +cp "../conf/gunicorn.conf.py" "$gunicorn_conf" +ynh_replace_string --match_string="__FINAL_HOME_PATH__" --replace_string="$final_path" --target_file="$gunicorn_conf" +ynh_replace_string --match_string="__LOG_FILE__" --replace_string="$log_file" --target_file="$gunicorn_conf" +ynh_replace_string --match_string="__PORT__" --replace_string="$port" --target_file="$gunicorn_conf" +ynh_store_file_checksum --file="$gunicorn_conf" + +cp ../conf/manage.py "$final_path/manage.py" +ynh_replace_string --match_string="__FINAL_HOME_PATH__" --replace_string="$final_path" --target_file="$final_path/manage.py" +ynh_store_file_checksum --file="$final_path/manage.py" +chmod +x "$final_path/manage.py" + +settings="$final_path/settings.py" +cp "../conf/settings.py" "$settings" + +ynh_replace_string --match_string="__APP__" --replace_string="$app" --target_file="$settings" +ynh_replace_string --match_string="__DB_NAME__" --replace_string="$db_name" --target_file="$settings" +ynh_replace_string --match_string="__DB_USER__" --replace_string="$db_user" --target_file="$settings" +ynh_replace_string --match_string="__DB_PWD__" --replace_string="$db_pwd" --target_file="$settings" +ynh_replace_string --match_string="__ADMIN__" --replace_string="$admin" --target_file="$settings" +ynh_replace_string --match_string="__ADMINMAIL__" --replace_string="$admin_mail" --target_file="$settings" +ynh_replace_string --match_string="__FINAL_HOME_PATH__" --replace_string="$final_path" --target_file="$settings" +ynh_replace_string --match_string="__FINAL_WWW_PATH__" --replace_string="$public_path" --target_file="$settings" +ynh_replace_string --match_string="__LOG_FILE__" --replace_string="$log_file" --target_file="$settings" +ynh_replace_string --match_string="__REDIS_DB__" --replace_string="$redis_db" --target_file="$settings" + +ynh_replace_string --match_string="__DOMAIN__" --replace_string="$domain" --target_file="$settings" +ynh_replace_string --match_string="__PATH_URL__" --replace_string="$path_url" --target_file="$settings" + +# Calculate and store the config file checksum into the app settings +ynh_store_file_checksum --file="$settings" + +ynh_app_setting_set --app="$app" --key=redis_db --value="$redis_db" + +cp ../conf/setup_user.py "$final_path/setup_user.py" +cp ../conf/urls.py "$final_path/urls.py" +cp ../conf/wsgi.py "$final_path/wsgi.py" + +ynh_add_config --template="local_settings.py" --destination="$final_path/local_settings.py" + +#================================================= +# MIGRATE / COLLECTSTATIC / CREATEADMIN +#================================================= +ynh_script_progression --message="migrate/collectstatic/createadmin..." --weight=10 + +cd "$final_path" || exit + +# Just for debugging: +./manage.py diffsettings + +./manage.py migrate --no-input +./manage.py collectstatic --no-input + +# Create/update Django superuser (set unusable password, because auth done via SSOwat): +./manage.py create_superuser --username="$admin" --email="$admin_mail" + +# Check the configuration +# This may fail in some cases with errors, etc., but the app works and the user can fix issues later. +./manage.py check --deploy || true + + +#================================================= +# SETUP LOGROTATE +#================================================= +ynh_script_progression --message="Configuring log rotation..." + +# Use logrotate to manage app-specific logfile(s) +ynh_use_logrotate "$log_file" + +#================================================= +# INTEGRATE SERVICE IN YUNOHOST +#================================================= +ynh_script_progression --message="Integrating service in YunoHost..." + +yunohost service add $app --description="Web based management to catalog things" --log="${log_file}" + +#================================================= +# GENERIC FINALIZATION +#================================================= +# SECURE FILES AND DIRECTORIES +#================================================= + +# Set permissions to app files +chown -R "$app:" "$log_path" +chown -R "$app:www-data" "$public_path" +chown -R "$app:" "$final_path" + +chmod o-rwx "$log_path" +chmod o-rwx "$public_path" +chmod o-rwx "$final_path" + +#================================================= +# SETUP SYSTEMD +#================================================= +ynh_script_progression --message="Configuring a systemd service..." + +# https://github.com/YunoHost/yunohost/blob/dev/data/helpers.d/systemd +ynh_add_systemd_config --service="$app" --template="django-fmd.service" + +#================================================= +# Start django-fmd via systemd +#================================================= +ynh_script_progression --message="Starting django-fmd's services..." --weight=5 + +ynh_systemd_action --service_name="$app" --action="start" + +#================================================= +# RELOAD NGINX +#================================================= +ynh_script_progression --message="Reloading nginx web server..." + +ynh_systemd_action --service_name="nginx" --action="reload" + +#================================================= +# END OF SCRIPT +#================================================= + +ynh_script_progression --message="Installation of $app completed" --last diff --git a/scripts/remove b/scripts/remove new file mode 100755 index 0000000..5e630a4 --- /dev/null +++ b/scripts/remove @@ -0,0 +1,103 @@ +#!/bin/bash + +#================================================= +# GENERIC START +#================================================= +# IMPORT GENERIC HELPERS +#================================================= + +source _common.sh +source /usr/share/yunohost/helpers + +#================================================= +# LOAD SETTINGS +#================================================= +ynh_script_progression --message="Loading installation settings..." + +domain=$(ynh_app_setting_get --app="$app" --key=domain) +db_name=$(ynh_app_setting_get --app="$app" --key=db_name) +db_user=$db_name +public_path=$(ynh_app_setting_get --app="$app" --key=public_path) +final_path=$(ynh_app_setting_get --app="$app" --key=final_path) + +#================================================= +# STANDARD REMOVE +#================================================= +# REMOVE SERVICE FROM ADMIN PANEL +#================================================= + +# Remove a service from the admin panel, added by `yunohost service add` +if yunohost service status "$app" >/dev/null 2>&1 +then + ynh_script_progression --message="Removing $app service integration..." + yunohost service remove "$app" +fi + +#================================================= +# STOP PYINVENTORY'S SERVICES +#================================================= +ynh_script_progression --message="Stopping and removing systemd services..." --weight=5 + +ynh_remove_systemd_config --service="$app" + +#================================================= +# REMOVE THE PostgreSQL DATABASE +#================================================= +ynh_script_progression --message="Removing the PostgreSQL database..." + +# Remove a database if it exists, along with the associated user +ynh_psql_remove_db --db_user=$db_user --db_name=$db_name + +##================================================= +## REMOVE REDIS DB +##================================================= + +ynh_redis_remove_db + +#================================================= +# REMOVE DEPENDENCIES +#================================================= +ynh_script_progression --message="Removing dependencies..." --weight=10 + +# Remove metapackage and its dependencies +ynh_exec_warn_less ynh_remove_app_dependencies + +#================================================= +# REMOVE APP MAIN DIR +#================================================= +ynh_script_progression --message="Removing app main directory..." + +# Remove the app directory securely +ynh_secure_remove --file="$public_path" +ynh_secure_remove --file="$final_path" + +#================================================= +# REMOVE NGINX CONFIGURATION +#================================================= +ynh_script_progression --message="Removing nginx web server configuration..." + +# Remove the dedicated nginx config +ynh_remove_nginx_config + +#================================================= +# REMOVE LOGROTATE CONFIGURATION +#================================================= +ynh_script_progression --message="Removing logrotate configuration..." + +# Remove the app-specific logrotate config +ynh_remove_logrotate + +#================================================= +# GENERIC FINALIZATION +#================================================= +# REMOVE DEDICATED USER +#================================================= +ynh_script_progression --message="Removing the dedicated system user..." + +# Delete a system user +ynh_system_user_delete --username="$app" + +#================================================= +# END OF SCRIPT +#================================================= +ynh_script_progression --message="Removal of $app completed" --last diff --git a/scripts/restore b/scripts/restore new file mode 100755 index 0000000..43fa5f8 --- /dev/null +++ b/scripts/restore @@ -0,0 +1,173 @@ +#!/bin/bash + +#================================================= +# GENERIC START +#================================================= +# IMPORT GENERIC HELPERS +#================================================= + +source ../settings/scripts/_common.sh +source /usr/share/yunohost/helpers + +#================================================= +# MANAGE SCRIPT FAILURE +#================================================= + +ynh_abort_if_errors + +#================================================= +# LOAD SETTINGS +#================================================= +ynh_script_progression --message="Loading settings..." + +public_path=$(ynh_app_setting_get --app="$app" --key=public_path) +final_path=$(ynh_app_setting_get --app="$app" --key=final_path) +db_name=$(ynh_app_setting_get --app="$app" --key=db_name) +db_user=$db_name +db_pwd=$(ynh_app_setting_get --app="$app" --key=psqlpwd) + +domain=$(ynh_app_setting_get --app="$app" --key=domain) +path_url=$(ynh_app_setting_get --app="$app" --key=path) + +#================================================= +# CHECK IF THE APP CAN BE RESTORED +#================================================= +ynh_script_progression --message="Validating restoration parameters..." + +ynh_webpath_available --domain=$domain --path_url=$path_url \ + || ynh_die --message="Path not available: ${domain}${path_url}" +test ! -d $final_path \ + || ynh_die --message="There is already a directory: $final_path " + +#================================================= +# STANDARD RESTORATION STEPS +#================================================= +# RESTORE THE NGINX CONFIGURATION +#================================================= + +ynh_restore_file --origin_path="/etc/nginx/conf.d/$domain.d/$app.conf" + +#================================================= +# RESTORE THE APP MAIN DIR +#================================================= +ynh_script_progression --message="Restoring the app main directory..." + +ynh_restore_file --origin_path="$public_path" +ynh_restore_file --origin_path="$final_path" + +#================================================= +# RECREATE THE DEDICATED USER +#================================================= +ynh_script_progression --message="Recreating the dedicated system user..." + +# Create the dedicated user (if not existing) +ynh_system_user_create --username=$app --home_dir="$final_path" --use_shell + +#================================================= +# RESTORE USER RIGHTS +#================================================= + +# Restore permissions on app files +chown -R "$app:www-data" "$public_path" +chown -R "$app:" "$final_path" + +#================================================= +# SPECIFIC RESTORATION +#================================================= +# REINSTALL DEPENDENCIES +#================================================= +ynh_script_progression --message="Reinstalling dependencies..." --weight=20 + +ynh_exec_warn_less ynh_install_app_dependencies "$pkg_dependencies" + +#================================================= +# REINSTALL PYTHON VIRTUALENV +# Maybe the backup contains a other Python version +#================================================= +ynh_script_progression --message="Upgrade Python virtualenv..." --weight=50 + +# Always recreate everything fresh with current python version +ynh_secure_remove "${final_path}/venv" + +# Skip pip because of: https://github.com/YunoHost/issues/issues/1960 +python3 -m venv --without-pip "${final_path}/venv" +chown -R "$app:" "$final_path" + +#run source in a 'sub shell' +( + set +o nounset + source "${final_path}/venv/bin/activate" + set -o nounset + ynh_exec_as $app $final_path/venv/bin/python3 -m ensurepip + ynh_exec_as $app $final_path/venv/bin/pip3 install --upgrade wheel pip setuptools + ynh_exec_as $app $final_path/venv/bin/pip3 install --no-deps -r "$final_path/requirements.txt" +) + +#================================================= +# RESTORE THE PostgreSQL DATABASE +#================================================= +ynh_script_progression --message="Restoring the PostgreSQL database..." --weight=5 + +ynh_psql_test_if_first_run +ynh_psql_setup_db --db_user="$db_user" --db_name="$db_name" --db_pwd="$db_pwd" +ynh_psql_connect_as --user=$db_user --password=$db_pwd --database=$db_name < ./db.sql + +#================================================= +# RESTORE SYSTEMD +#================================================= +ynh_script_progression --message="Restoring the systemd configuration..." + +ynh_restore_file --origin_path="/etc/systemd/system/$app.service" +systemctl enable $app.service --quiet + +#================================================= +# INTEGRATE SERVICE IN YUNOHOST +#================================================= +ynh_script_progression --message="Integrating service in YunoHost..." + +yunohost service add $app --description="Web based management to catalog things" --log="${log_file}" + +#================================================= +# RESTORE THE LOGROTATE CONFIGURATION +#================================================= + +mkdir -p "$log_path" +touch "${log_file}" +chown -R "$app:" "$log_path" +ynh_restore_file --origin_path="/etc/logrotate.d/$app" + +#================================================= +# GENERIC FINALIZATION +#================================================= +# SECURE FILES AND DIRECTORIES +#================================================= + +# Set permissions to app files +chown -R "$app:" "$log_path" +chown -R "$app:www-data" "$public_path" +chown -R "$app:" "$final_path" + +chmod o-rwx "$log_path" +chmod o-rwx "$public_path" +chmod o-rwx "$final_path" + +#================================================= +# GENERIC FINALIZATION +#================================================= +# START PYINVENTORY +#================================================= +ynh_script_progression --message="Starting a systemd service..." --weight=5 + +ynh_systemd_action --service_name="$app" --action="start" + +#================================================= +# RELOAD NGINX +#================================================= +ynh_script_progression --message="Reloading nginx web server..." + +ynh_systemd_action --service_name="nginx" --action="reload" + +#================================================= +# END OF SCRIPT +#================================================= +ynh_script_progression --message="Restoration completed for $app" --last diff --git a/scripts/upgrade b/scripts/upgrade new file mode 100755 index 0000000..e1334a3 --- /dev/null +++ b/scripts/upgrade @@ -0,0 +1,244 @@ +#!/bin/bash + +#================================================= +# IMPORT GENERIC HELPERS +#================================================= + +source _common.sh +source /usr/share/yunohost/helpers + +#================================================= +# LOAD SETTINGS +#================================================= +ynh_script_progression --message="Loading installation settings..." + +admin=$(ynh_app_setting_get --app="$app" --key=admin) +public_path=$(ynh_app_setting_get --app="$app" --key=public_path) +final_path=$(ynh_app_setting_get --app="$app" --key=final_path) +log_path=$(ynh_app_setting_get --app="$app" --key=log_path) + +domain=$(ynh_app_setting_get --app="$app" --key=domain) +path_url=$(ynh_app_setting_get --app="$app" --key=path) + +port=$(ynh_app_setting_get --app="$app" --key=port) + +db_pwd=$(ynh_app_setting_get --app="$app" --key=psqlpwd) +db_name=$(ynh_sanitize_dbid --db_name="$app") +db_user=$db_name + +admin_mail=$(ynh_user_get_info "$admin" mail) +redis_db=$(ynh_app_setting_get --app="$app" --key=redis_db) + +django_debug=$(ynh_app_setting_get --app="$app" --key=django_debug) + +#================================================= +# BACKUP BEFORE UPGRADE THEN ACTIVE TRAP +#================================================= +ynh_script_progression --message="Backing up the app before upgrading (may take a while)..." --weight=40 + +# Backup the current version of the app +ynh_backup_before_upgrade +ynh_clean_setup () { + # restore it if the upgrade fails + ynh_restore_upgradebackup +} +# Exit if an error occurs during the execution of the script +ynh_abort_if_errors + +#================================================= +# STANDARD UPGRADE STEPS +#================================================= +# STOP SYSTEMD SERVICE +#================================================= +ynh_script_progression --message="Stopping systemd services..." --weight=5 + +ynh_systemd_action --service_name="$app" --action="stop" + +#================================================= +# Manage old settings +#================================================= + +# Always deactivate settings.DEBUG: +ynh_app_setting_set --app=$app --key=django_debug --value="False" + +#================================================= +# NGINX CONFIGURATION +#================================================= +ynh_script_progression --message="Upgrading nginx web server configuration..." + +# Create a dedicated nginx config +# https://github.com/YunoHost/yunohost/blob/dev/data/helpers.d/nginx +ynh_add_nginx_config "public_path" "port" + +#================================================= +# SPECIFIC UPGRADE +#================================================= +# Update dependencies +#================================================= +ynh_script_progression --message="Upgrading dependencies..." --weight=20 + +ynh_exec_warn_less ynh_install_app_dependencies "$pkg_dependencies" + +#================================================= +# CREATE DEDICATED USER +#================================================= +ynh_script_progression --message="Making sure dedicated system user exists..." + +# Create a system user +ynh_system_user_create --username="$app" --home_dir="$final_path" --use_shell + +#================================================= +# SETUP SYSTEMD +#================================================= +ynh_script_progression --message="Configuring a systemd service..." + +ynh_add_systemd_config --service="$app" --template="django-fmd.service" + +#================================================= +# UPGRADE VENV +#================================================= +ynh_script_progression --message="Upgrade project via pip..." --weight=50 + +# Always recreate everything fresh with current python version +ynh_secure_remove "${final_path}/venv" + +# Skip pip because of: https://github.com/YunoHost/issues/issues/1960 +python3 -m venv --without-pip "${final_path}/venv" + +cp ../conf/requirements.txt "$final_path/requirements.txt" +chown -R "$app:" "$final_path" + +#run source in a 'sub shell' +( + set +o nounset + source "${final_path}/venv/bin/activate" + set -o nounset + ynh_exec_as $app $final_path/venv/bin/python3 -m ensurepip + ynh_exec_as $app $final_path/venv/bin/pip3 install --upgrade wheel pip setuptools + ynh_exec_as $app $final_path/venv/bin/pip3 install --no-deps -r "$final_path/requirements.txt" +) + +#================================================= +# copy config files +# ================================================ +ynh_script_progression --message="Create project configuration files..." + +gunicorn_conf="$final_path/gunicorn.conf.py" +ynh_backup_if_checksum_is_different --file="$gunicorn_conf" +cp "../conf/gunicorn.conf.py" "$gunicorn_conf" +ynh_replace_string --match_string="__FINAL_HOME_PATH__" --replace_string="$final_path" --target_file="$gunicorn_conf" +ynh_replace_string --match_string="__LOG_FILE__" --replace_string="$log_file" --target_file="$gunicorn_conf" +ynh_replace_string --match_string="__PORT__" --replace_string="$port" --target_file="$gunicorn_conf" +ynh_store_file_checksum --file="$gunicorn_conf" + +cp ../conf/manage.py "$final_path/manage.py" +ynh_replace_string --match_string="__FINAL_HOME_PATH__" --replace_string="$final_path" --target_file="$final_path/manage.py" +ynh_store_file_checksum --file="$final_path/manage.py" +chmod +x "$final_path/manage.py" + +# save old settings file +settings="$final_path/settings.py" +ynh_backup_if_checksum_is_different --file="$settings" + +cp "../conf/settings.py" "$settings" + +ynh_replace_string --match_string="__APP__" --replace_string="$app" --target_file="$settings" +ynh_replace_string --match_string="__DB_NAME__" --replace_string="$db_name" --target_file="$settings" +ynh_replace_string --match_string="__DB_USER__" --replace_string="$db_user" --target_file="$settings" +ynh_replace_string --match_string="__DB_PWD__" --replace_string="$db_pwd" --target_file="$settings" +ynh_replace_string --match_string="__ADMIN__" --replace_string="$admin" --target_file="$settings" +ynh_replace_string --match_string="__ADMINMAIL__" --replace_string="$admin_mail" --target_file="$settings" +ynh_replace_string --match_string="__FINAL_HOME_PATH__" --replace_string="$final_path" --target_file="$settings" +ynh_replace_string --match_string="__FINAL_WWW_PATH__" --replace_string="$public_path" --target_file="$settings" +ynh_replace_string --match_string="__LOG_FILE__" --replace_string="$log_file" --target_file="$settings" +ynh_replace_string --match_string="__REDIS_DB__" --replace_string="$redis_db" --target_file="$settings" + +ynh_replace_string --match_string="__DOMAIN__" --replace_string="$domain" --target_file="$settings" +ynh_replace_string --match_string="__PATH_URL__" --replace_string="$path_url" --target_file="$settings" + +# Recalculate and store the config file checksum into the app settings +ynh_store_file_checksum --file="$settings" + +ynh_backup_if_checksum_is_different --file="$final_path/setup_user.py" +cp ../conf/setup_user.py "$final_path/setup_user.py" + +ynh_backup_if_checksum_is_different --file="$final_path/urls.py" +cp ../conf/urls.py "$final_path/urls.py" + +ynh_backup_if_checksum_is_different --file="$final_path/wsgi.py" +cp ../conf/wsgi.py "$final_path/wsgi.py" + +# Regenerate a fresh local_settings: +ynh_backup_if_checksum_is_different --file="$final_path/local_settings.py" +ynh_add_config --template="local_settings.py" --destination="$final_path/local_settings.py" + +#================================================= +# MIGRATE PYINVENTORY +#================================================= +ynh_script_progression --message="migrate/collectstatic/createadmin..." --weight=10 + +cd "$final_path" || exit + +# Just for debugging: +./manage.py diffsettings + +./manage.py migrate --no-input +./manage.py collectstatic --no-input + +# Create/update Django superuser (set unusable password, because auth done via SSOwat): +./manage.py create_superuser --username="$admin" --email="$admin_mail" + +# Check the configuration +# This may fail in some cases with errors, etc., but the app works and the user can fix issues later. +./manage.py check --deploy || true + + +#================================================= +# SETUP LOGROTATE +#================================================= +ynh_script_progression --message="Upgrading logrotate configuration..." + +# Use logrotate to manage app-specific logfile(s) +ynh_use_logrotate --non-append + +#================================================= +# INTEGRATE SERVICE IN YUNOHOST +#================================================= +ynh_script_progression --message="Integrating service in YunoHost..." + +yunohost service add $app --description="Web based management to catalog things" --log="${log_file}" + +#================================================= +# GENERIC FINALIZATION +#================================================= +# SECURE FILES AND DIRECTORIES +#================================================= + +# Set permissions to app files +chown -R "$app:" "$log_path" +chown -R "$app:www-data" "$public_path" +chown -R "$app:" "$final_path" + +chmod o-rwx "$log_path" +chmod o-rwx "$public_path" +chmod o-rwx "$final_path" + +#================================================= +# Start django-fmd via systemd +#================================================= +ynh_script_progression --message="Starting django-fmd's services..." --weight=5 + +ynh_systemd_action --service_name="$app" --action="start" + +#================================================= +# RELOAD NGINX +#================================================= +ynh_script_progression --message="Reloading nginx web server..." + +ynh_systemd_action --service_name=nginx --action=reload + +#================================================= +# END OF SCRIPT +#================================================= + +ynh_script_progression --message="Upgrade of $app completed" --last diff --git a/test_requirements.sh b/test_requirements.sh new file mode 100755 index 0000000..e7ececf --- /dev/null +++ b/test_requirements.sh @@ -0,0 +1,17 @@ +#!/bin/bash + +# Test to create the python virtual env and install all requirements. +# Note: Maybe you didn't have all OS packages installed ;) + +set -e + +final_path="./local_test" + +set -x + +mkdir -p "${final_path}/" +python3 -m venv "${final_path}/venv" +source "${final_path}/venv/bin/activate" + +$final_path/venv/bin/pip install --upgrade wheel pip +$final_path/venv/bin/pip install --no-deps -r "./conf/requirements.txt" diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_django_project.py b/tests/test_django_project.py new file mode 100644 index 0000000..4505134 --- /dev/null +++ b/tests/test_django_project.py @@ -0,0 +1,176 @@ +from pathlib import Path + +from bx_django_utils.test_utils.html_assertion import HtmlAssertionMixin +from django.conf import LazySettings, settings +from django.contrib.auth.models import User +from django.test import override_settings +from django.test.testcases import TestCase +from django.urls import NoReverseMatch +from django.urls.base import reverse +from django_yunohost_integration.test_utils import generate_basic_auth +from django_yunohost_integration.views import request_media_debug_view + +import findmydevice + + +@override_settings(DEBUG=False) +class DjangoYnhTestCase(HtmlAssertionMixin, TestCase): + def setUp(self): + super().setUp() + + # Always start a fresh session: + self.client = self.client_class() + + def test_settings(self): + assert isinstance(settings, LazySettings) + assert settings.configured is True + + assert settings.PATH_URL == 'app_path' + + def assert_path(path, end_text): + assert isinstance(path, Path) + path = str(path) + assert path.endswith(end_text) + + assert_path(settings.FINAL_HOME_PATH, '/local_test/opt_yunohost') + assert_path(settings.FINAL_WWW_PATH, '/local_test/var_www') + assert_path(settings.LOG_FILE, '/local_test/var_log_django-fmd.log') + + assert settings.ROOT_URLCONF == 'urls' + + def test_urls(self): + assert reverse('admin:index') == '/app_path/admin/' + + # The django_yunohost_integration debug view should not be available: + with self.assertRaises(NoReverseMatch): + reverse(request_media_debug_view) + + def test_auth(self): + # SecurityMiddleware should redirects all non-HTTPS requests to HTTPS: + assert settings.SECURE_SSL_REDIRECT is True + response = self.client.get('/app_path/', secure=False) + self.assertRedirects( + response, + status_code=301, # permanent redirect + expected_url='https://testserver/app_path/', + fetch_redirect_response=False, + ) + + response = self.client.get('/app_path/', secure=True) + self.assertRedirects( + response, expected_url='/app_path/group_management/', fetch_redirect_response=False + ) + + response = self.client.get('/app_path/group_management/', secure=True) + self.assertRedirects( + response, + expected_url='/app_path/admin/login/?next=/app_path/group_management/', + fetch_redirect_response=False, + ) + + response = self.client.get('/app_path/admin/', secure=True) + self.assertRedirects( + response, + expected_url='/app_path/admin/login/?next=/app_path/admin/', + fetch_redirect_response=False, + ) + + @override_settings(SECURE_SSL_REDIRECT=False) + def test_create_unknown_user(self): + assert User.objects.count() == 0 + + self.client.cookies['SSOwAuthUser'] = 'test' + + response = self.client.get( + path='/app_path/admin/', + HTTP_REMOTE_USER='test', + HTTP_AUTH_USER='test', + HTTP_AUTHORIZATION='basic dGVzdDp0ZXN0MTIz', + ) + + assert User.objects.count() == 1 + user = User.objects.first() + assert user.username == 'test' + assert user.is_active is True + assert user.is_staff is True # Set by: django_yunohost_integration + assert user.is_superuser is False + + assert response.status_code == 200 + self.assert_html_parts( + response, + parts=( + ( + 'Site administration | django-fmd' + f' v{findmydevice.__version__}' + ), + 'test', + ), + ) + + @override_settings(SECURE_SSL_REDIRECT=False) + def test_wrong_auth_user(self): + assert User.objects.count() == 0 + + self.client.cookies['SSOwAuthUser'] = 'test' + + response = self.client.get( + path='/app_path/admin/', + HTTP_REMOTE_USER='test', + HTTP_AUTH_USER='foobar', # <<< wrong user name + HTTP_AUTHORIZATION='basic dGVzdDp0ZXN0MTIz', + ) + + assert User.objects.count() == 1 + user = User.objects.first() + assert user.username == 'test' + assert user.is_active is True + assert user.is_staff is True # Set by: django_yunohost_integration + assert user.is_superuser is False + + assert response.status_code == 403 # Forbidden + + @override_settings(SECURE_SSL_REDIRECT=False) + def test_wrong_cookie(self): + assert User.objects.count() == 0 + + self.client.cookies['SSOwAuthUser'] = 'foobar' # <<< wrong user name + + response = self.client.get( + path='/app_path/admin/', + HTTP_REMOTE_USER='test', + HTTP_AUTH_USER='test', + HTTP_AUTHORIZATION='basic dGVzdDp0ZXN0MTIz', + ) + + assert User.objects.count() == 1 + user = User.objects.first() + assert user.username == 'test' + assert user.is_active is True + assert user.is_staff is True # Set by: django_yunohost_integration + assert user.is_superuser is False + + assert response.status_code == 403 # Forbidden + + @override_settings(SECURE_SSL_REDIRECT=False) + def test_wrong_authorization_user(self): + assert User.objects.count() == 0 + + self.client.cookies['SSOwAuthUser'] = 'test' + + response = self.client.get( + path='/app_path/admin/', + HTTP_REMOTE_USER='test', + HTTP_AUTH_USER='test', + HTTP_AUTHORIZATION=generate_basic_auth( + username='foobar', password='test123' + ), # <<< wrong user name + ) + + assert User.objects.count() == 1 + user = User.objects.first() + assert user.username == 'test' + assert user.is_active is True + assert user.is_staff is True # Set by: django_yunohost_integration + assert user.is_superuser is False + + assert response.status_code == 403 # Forbidden diff --git a/tests/test_project_setup.py b/tests/test_project_setup.py new file mode 100644 index 0000000..f326315 --- /dev/null +++ b/tests/test_project_setup.py @@ -0,0 +1,89 @@ +import difflib +import os +import shutil +import subprocess +from pathlib import Path + +from bx_django_utils.filename import clean_filename +from bx_py_utils.path import assert_is_dir, assert_is_file + +import findmydevice + + +PACKAGE_ROOT = Path(__file__).parent.parent + + +def assert_file_contains_string(file_path, string): + with file_path.open('r') as f: + for line in f: + if string in line: + return + raise AssertionError(f'File {file_path} does not contain {string!r} !') + + +def test_version(): + version = findmydevice.__version__ + + assert_file_contains_string( + file_path=Path(PACKAGE_ROOT, 'pyproject.toml'), string=f'version = "{version}~ynh' + ) + assert_file_contains_string( + file_path=Path(PACKAGE_ROOT, 'manifest.json'), string=f'"version": "{version}~ynh' + ) + + +def poetry_check_output(*args): + poerty_bin = shutil.which('poetry') + + output = subprocess.check_output( + (poerty_bin,) + args, + universal_newlines=True, + env=os.environ, + stderr=subprocess.STDOUT, + cwd=str(PACKAGE_ROOT), + ) + print(output) + return output + + +def test_poetry_check(): + output = poetry_check_output('check') + assert output == 'All set!\n' + + +def test_requirements_txt(): + requirements_txt = Path('conf', 'requirements.txt') + assert_is_file(requirements_txt) + + output = poetry_check_output('export', '-f', 'requirements.txt') + assert 'Warning' not in output + + current_content = requirements_txt.read_text() + + diff = '\n'.join( + difflib.unified_diff( + current_content.splitlines(), + output.splitlines(), + fromfile=str(requirements_txt), + tofile='FRESH EXPORT', + ) + ) + print(diff) + assert diff == '', f'{requirements_txt} is not up-to-date! (Hint: call: "make update")' + + +def test_screenshot_filenames(): + """ + https://forum.yunohost.org/t/yunohost-bot-cant-handle-spaces-in-screenshots/19483 + """ + screenshot_path = PACKAGE_ROOT / 'doc' / 'screenshots' + assert_is_dir(screenshot_path) + renamed = [] + for file_path in screenshot_path.iterdir(): + file_name = file_path.name + cleaned_name = clean_filename(file_name) + if cleaned_name != file_name: + new_path = file_path.with_name(cleaned_name) + file_path.rename(new_path) + renamed.append(f'{file_name!r} renamed to {cleaned_name!r}') + assert not renamed, f'Bad screenshots file names found: {", ".join(renamed)}' diff --git a/tests/test_utils.py b/tests/test_utils.py new file mode 100644 index 0000000..1055e23 --- /dev/null +++ b/tests/test_utils.py @@ -0,0 +1,8 @@ +from unittest.case import TestCase + +from django_yunohost_integration.test_utils import generate_basic_auth + + +class TestUtilsTestCase(TestCase): + def test_generate_basic_auth(self): + assert generate_basic_auth(username='test', password='test123') == 'basic dGVzdDp0ZXN0MTIz'