Apply manageprojects updates

This commit is contained in:
Jens Diemer 2024-08-27 20:51:05 +02:00
parent 869c14c61a
commit 4a4e876d0f
50 changed files with 3453 additions and 2290 deletions

View file

@ -1,4 +1,4 @@
# see http://editorconfig.org
# see https://editorconfig.org
root = true
[*]
@ -10,11 +10,11 @@ trim_trailing_whitespace = true
insert_final_newline = true
[*.py]
max_line_length = 100
max_line_length = 119
[{Makefile,**.mk}]
indent_style = tab
insert_final_newline = false
[*.yml]
indent_style = tab
[{*.yaml,*.yml}]
indent_size = 2

View file

@ -1,7 +1,7 @@
#
# Move to pyproject.toml after: https://gitlab.com/pycqa/flake8/-/issues/428
# Move to pyproject.toml after: https://github.com/PyCQA/flake8/issues/234
#
[flake8]
exclude = .pytest_cache, .tox, dist, htmlcov, local_test
exclude = .*, dist, htmlcov, local_test
ignore = F405
max-line-length = 119

View file

@ -1,6 +1,8 @@
name: YunoHost apps package linter
on:
# Allow to manually trigger the workflow
workflow_dispatch:
push:
branches:
- main
@ -12,12 +14,25 @@ jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.11'
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install toml
- name: 'Clone YunoHost apps package linter'
run: |
git clone --depth=1 https://github.com/YunoHost/package_linter ~/package_linter
- name: 'Install requirements'
run: pip3 install toml
- name: 'Run linter'
run: |
~/package_linter/package_linter.py .

View file

@ -1,56 +0,0 @@
name: pytest
on:
push:
branches:
- master
pull_request:
schedule:
- cron: '0 8 * * *'
jobs:
test:
runs-on: ubuntu-latest
strategy:
max-parallel: 2
matrix:
python-version: ["3.10", "3.9", "3.8", "3.7"]
steps:
- uses: actions/checkout@v2
with:
fetch-depth: 0
- name: 'fetch master'
run: |
git fetch origin master
- name: 'Set up Python ${{ matrix.python-version }}'
uses: actions/setup-python@v2
with:
python-version: '${{ matrix.python-version }}'
- uses: actions/cache@v2
with:
path: ~/.cache/
key: dot-cache-files
- name: 'Install package'
run: |
pip3 install poetry
make install
- name: 'List installed packages'
run: |
poetry run pip freeze
- name: 'Run tests with Python v${{ matrix.python-version }}'
run: |
make pytest
- name: 'Run Safety check'
run: |
make safety
- name: 'Upload coverage report'
uses: codecov/codecov-action@v2
with:
fail_ci_if_error: false
verbose: true

66
.github/workflows/tests.yml vendored Normal file
View file

@ -0,0 +1,66 @@
name: tests
on:
push:
branches:
- main
pull_request:
schedule:
- cron: '0 8 * * *'
jobs:
test:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
python-version: ['3.12', '3.11']
steps:
- name: Checkout
run: |
echo $GITHUB_REF $GITHUB_SHA
git clone https://github.com/$GITHUB_REPOSITORY.git .
git fetch origin $GITHUB_SHA:temporary-ci-branch
git checkout $GITHUB_SHA || (git fetch && git checkout $GITHUB_SHA)
- name: 'fetch master'
run: |
git fetch origin master
- name: 'Set up Python ${{ matrix.python-version }}'
uses: actions/setup-python@v5
# https://github.com/marketplace/actions/setup-python
with:
python-version: '${{ matrix.python-version }}'
cache: 'pip' # caching pip dependencies
cache-dependency-path: '**/requirements.*.txt'
- name: 'Bootstrap dev venv'
# The first CLI call will create the .venv
run: |
./dev-cli.py version
- name: 'dev CLI help'
run: |
./dev-cli.py --help
- name: 'Run pip-audit'
run: |
./dev-cli.py pip-audit
- name: 'Run tests with Python v${{ matrix.python-version }}'
env:
PYTHONUNBUFFERED: 1
PYTHONWARNINGS: always
run: |
.venv/bin/coverage erase
./dev-cli.py coverage
- name: 'Upload coverage report'
uses: codecov/codecov-action@v4
# https://github.com/marketplace/actions/codecov
with:
fail_ci_if_error: false
verbose: true

13
.gitignore vendored
View file

@ -1,11 +1,18 @@
.*
*.egg-info
__pycache__
/dist/
/build/
/coverage.*
/htmlcov/
*.orig
!.github
!.editorconfig
!.flake8
!.gitignore
!.gitkeep
!/doc/screenshots/.gitkeep
__pycache__
secret.txt
/local_test/
/coverage.xml
/htmlcov/

View file

@ -1,7 +1,7 @@
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
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 <http://www.gnu.org/licenses/>.
along with this program. If not, see <https://www.gnu.org/licenses/>.
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
<http://www.gnu.org/licenses/>.
<https://www.gnu.org/licenses/>.
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
<http://www.gnu.org/philosophy/why-not-lgpl.html>.
<https://www.gnu.org/philosophy/why-not-lgpl.html>.

View file

@ -1,58 +0,0 @@
SHELL := /bin/bash
MAX_LINE_LENGTH := 100
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
curl -sSL https://install.python-poetry.org | python3 -
install: check-poetry ## install project via poetry
poetry install
update: check-poetry ## update the sources and installation and generate "conf/requirements.txt"
poetry self update
poetry update -v
poetry export -f requirements.txt --output conf/requirements.txt
lint: ## Run code formatters and linter
poetry run isort --check-only .
poetry run flake8 .
fix-code-style: ## Fix code formatting
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 pytest
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
safety: ## Run https://github.com/pyupio/safety
poetry run safety check --full-report
##############################################################################
.PHONY: help check-poetry install-poetry install update local-test

View file

@ -11,7 +11,7 @@ It shall NOT be edited by hand.
*[Read this README in other languages.](./ALL_README.md)*
> *This package allows you to install django-fritzconnection quickly and simply on a YunoHost server.*
> *This package allows you to install django-fritzconnection 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

View file

@ -11,7 +11,7 @@ No se debe editar a mano.
*[Leer este README en otros idiomas.](./ALL_README.md)*
> *Este paquete le permite instalardjango-fritzconnection rapidamente y simplement en un servidor YunoHost.*
> *Este paquete le permite instalardjango-fritzconnection rapidamente y simplement en un servidor YunoHost.*
> *Si no tiene YunoHost, visita [the guide](https://yunohost.org/install) para aprender como instalarla.*
## Descripción general

View file

@ -11,7 +11,7 @@ EZ editatu eskuz.
*[Irakurri README hau beste hizkuntzatan.](./ALL_README.md)*
> *Pakete honek django-fritzconnection YunoHost zerbitzari batean azkar eta zailtasunik gabe instalatzea ahalbidetzen dizu.*
> *Pakete honek django-fritzconnection YunoHost zerbitzari batean azkar eta zailtasunik gabe instalatzea ahalbidetzen dizu.*
> *YunoHost ez baduzu, kontsultatu [gida](https://yunohost.org/install) nola instalatu ikasteko.*
## Aurreikuspena

View file

@ -11,7 +11,7 @@ Il NE doit PAS être modifié à la main.
*[Lire le README dans d'autres langues.](./ALL_README.md)*
> *Ce package vous permet dinstaller django-fritzconnection rapidement et simplement sur un serveur YunoHost.*
> *Ce package vous permet dinstaller django-fritzconnection rapidement et simplement sur un serveur YunoHost.*
> *Si vous navez pas YunoHost, consultez [ce guide](https://yunohost.org/install) pour savoir comment linstaller et en profiter.*
## Vue densemble

View file

@ -11,7 +11,7 @@ NON debe editarse manualmente.
*[Le este README en outros idiomas.](./ALL_README.md)*
> *Este paquete permíteche instalar django-fritzconnection de xeito rápido e doado nun servidor YunoHost.*
> *Este paquete permíteche instalar django-fritzconnection de xeito rápido e doado nun servidor YunoHost.*
> *Se non usas YunoHost, le a [documentación](https://yunohost.org/install) para saber como instalalo.*
## Vista xeral

View file

@ -11,7 +11,7 @@ NON DEVE essere modificato manualmente.
*[Leggi questo README in altre lingue.](./ALL_README.md)*
> *Questo pacchetto ti permette di installare django-fritzconnection su un server YunoHost in modo semplice e veloce.*
> *Questo pacchetto ti permette di installare django-fritzconnection su un server YunoHost in modo semplice e veloce.*
> *Se non hai YunoHost, consulta [la guida](https://yunohost.org/install) per imparare a installarlo.*
## Panoramica

View file

@ -11,7 +11,7 @@
*[阅读此 README 的其它语言版本。](./ALL_README.md)*
> *通过此软件包,您可以在 YunoHost 服务器上快速、简单地安装 django-fritzconnection。*
> *通过此软件包,您可以在 YunoHost 服务器上快速、简单地安装 django-fritzconnection。*
> *如果您还没有 YunoHost请参阅[指南](https://yunohost.org/install)了解如何安装它。*
## 概况

33
check_process Normal file
View file

@ -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=0
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&

View file

@ -13,8 +13,8 @@ workers = multiprocessing.cpu_count() * 2 + 1
loglevel = 'info'
# https://docs.gunicorn.org/en/latest/settings.html#logging
accesslog = '/var/log/__APP__/__APP__.log'
errorlog = '/var/log/__APP__/__APP__.log'
accesslog = '__LOG_FILE__'
errorlog = '__LOG_FILE__'
# https://docs.gunicorn.org/en/latest/settings.html#pidfile
pidfile = '__INSTALL_DIR__/app/gunicorn.pid'
pidfile = '__DATA_DIR__/gunicorn.pid' # /home/yunohost.app/$app/gunicorn.pid

View file

@ -1,4 +1,4 @@
#!__INSTALL_DIR__/venv/bin/python
#!__DATA_DIR__/venv/bin/python
import os
import sys

View file

@ -1,8 +1,8 @@
location __PATH__/static/ {
# Service static files by nginx
# e.g.: /var/www/$app/static
alias __INSTALL_DIR__/public/static/;
# e.g.: /var/www/$app/static/
alias __INSTALL_DIR__/static/;
expires 30d;
}

View file

@ -1,201 +1,425 @@
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==35 ; python_version >= "3.7" and python_full_version < "4.0.0" \
--hash=sha256:341b27ad0b72a903acf2f28def0fe371def811c1b2305da9806124869a698fc8 \
--hash=sha256:5151806d349a9dafc8dba9636239422022bab211b5b02afa52fce1f58ec2e6ab
bx-py-utils==69 ; python_version >= "3.7" and python_full_version < "4.0.0" \
--hash=sha256:728fd575c4d5048e114b502a97d19679f9abcda90889a6896534c48348320460 \
--hash=sha256:b25419e020c9c5ea16938a45cf5120086a5ac29648be78a8eb98ae202515fee1
certifi==2022.9.24 ; python_version >= "3.7" and python_version < "4" \
--hash=sha256:0d9c601124e5a6ba9712dbc60d9c53c21e34f5f641fe83002317394311bdce14 \
--hash=sha256:90c1a32f1d68f940488354e36370f6cca89f0f106db09518524c88d6ed83f382
charset-normalizer==2.1.1 ; python_version >= "3.7" and python_version < "4" \
--hash=sha256:5a3d016c7c547f69d6f81fb0db9449ce888b418b5b9952cc5e6e66843e9dd845 \
--hash=sha256:83e9a75d1911279afd89352c68b45348559d1fc0506b054b346651b5e7fee29f
colorama==0.4.5 ; python_version >= "3.7" and python_full_version < "4.0.0" and sys_platform == "win32" \
--hash=sha256:854bf444933e37f5824ae7bfc1e98d5bce2ebe4160d46b5edf346a89358e99da \
--hash=sha256:e6c6b4334fc50988a639d9b98aa429a0b57da6e17b9a44f0451f930b6967b7a4
colorlog==6.7.0 ; python_version >= "3.7" and python_full_version < "4.0.0" \
--hash=sha256:0d33ca236784a1ba3ff9c532d4964126d8a2c44f1f0cb1d2b0728196f512f662 \
--hash=sha256:bd94bd21c1e13fac7bd3153f4bc3a7dc0eb0974b8bc2fdf1a989e474f6e582e5
deprecated==1.2.13 ; python_version >= "3.7" and python_full_version < "4.0.0" \
--hash=sha256:43ac5335da90c31c24ba028af536a91d41d53f9e6901ddb021bcc572ce44e38d \
--hash=sha256:64756e3e14c8c5eea9795d93c524551432a0be75629f8f29e67ab8caf076c76d
diff-match-patch==20200713 ; python_version >= "3.7" and python_full_version < "4.0.0" \
--hash=sha256:8bf9d9c4e059d917b5c6312bac0c137971a32815ddbda9c682b949f2986b4d34 \
--hash=sha256:da6f5a01aa586df23dfc89f3827e1cafbb5420be9d87769eeb079ddfd9477a18
django-admin-sortable2==1.0.4 ; python_version >= "3.7" and python_full_version < "4.0.0" \
--hash=sha256:e22956889533b48a35a7f02859ae3a939753fa9a7d7d532cefc2835b41bdcebb \
--hash=sha256:f96044003176c6684c5f969792ca833a505d654fa0f7b24232a0a610e4332a53
django-axes==5.39.0 ; python_version >= "3.7" and python_full_version < "4.0.0" \
--hash=sha256:8f039f8e98f050f13f654efca599d8a04d0b57d330c590cf89ec2bf731c9a7fb \
--hash=sha256:97702552f7939c81db5bba2ef855ae43f20df92fa261cb79fd4c8633ba3b3955
django-debug-toolbar==3.7.0 ; python_version >= "3.7" and python_full_version < "4.0.0" \
--hash=sha256:1e3acad24e3d351ba45c6fa2072e4164820307332a776b16c9f06d1f89503465 \
--hash=sha256:80de23066b624d3970fd296cf02d61988e5d56c31aa0dc4a428970b46e2883a8
django-fritzconnection==0.2.0 ; python_version >= "3.7" and python_full_version < "4.0.0" \
--hash=sha256:4dbc96661da17cfa0f57ee6e6cc0956574d47479aa688eedf136475bf96f870e \
--hash=sha256:5573ef7497fbd339e54c6067d9d7e223d820785d581cd5e6593af46c828a6425
django-ipware==4.0.2 ; python_version >= "3.7" and python_full_version < "4.0.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:1d037dc02b11ad7aa11f655d26dac3fb1af32630f61ef4428860a2e29ff92026 \
--hash=sha256:8a99e5582c79f894168f5865c52bd921213253b7fd64d16733ae4591564465de
django-reversion-compare==0.15.0 ; python_version >= "3.7" and python_full_version < "4.0.0" \
--hash=sha256:d6f37b106aec287ae17a076bb7db1184ab02ab1898f0e8693f2779fbdaf71697 \
--hash=sha256:ed0264a2852d9d867023f1874948b8234dad9c2d2fa22ea18cfd5f28f304d7a0
django-reversion==5.0.3 ; python_version >= "3.7" and python_full_version < "4.0.0" \
--hash=sha256:acb600f8482147312a27bd58e63766aa0383310181e485e6eaa2f42d26502c9b \
--hash=sha256:c5955e09c4f290a8a3c5048b3f77d5ba75eae325c0fb1e571b0cd98df6a0017b
django-tagulous==1.3.3 ; python_version >= "3.7" and python_full_version < "4.0.0" \
--hash=sha256:ad3bb85f4cce83a47e4c0257143229cb92a294defa02fe661823b0442b35d478 \
--hash=sha256:d445590ae1b5cb9b8c5a425f97bf5f01148a33419c19edeb721ebd9fdd6792fe
django-tools==0.54.0 ; python_version >= "3.7" and python_full_version < "4.0.0" \
--hash=sha256:5040a91282be9d1c9d379b0c65da50bcb3691bff03cee54fd4123ace238c3a43 \
--hash=sha256:a7b7bfa5b9c5a81966454d17dffb2403cee25a806c858ee0486a08798227598f
django-yunohost-integration[ynh]==0.4.1 ; python_version >= "3.7" and python_full_version < "4.0.0" \
--hash=sha256:3769859db283a6b4d17468aeb1decab2f79d4b3e128b341342948e7bb3121e8a \
--hash=sha256:e097cd209f3e09cbe325eadea36e3eb64c051690297c38dd89a1cd64bc35d92e
django==3.2.16 ; python_version >= "3.7" and python_full_version < "4.0.0" \
--hash=sha256:18ba8efa36b69cfcd4b670d0fa187c6fe7506596f0ababe580e16909bcdec121 \
--hash=sha256:3adc285124244724a394fa9b9839cc8cd116faf7d159554c43ecdaa8cdf0b94d
fritzconnection==1.10.3 ; python_version >= "3.7" and python_full_version < "4.0.0" \
--hash=sha256:0b52772ff4e844edc510371ec6b9b0d2b56846c887fcbc7031e63d2d1f1e4bc0
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
idna==3.4 ; python_version >= "3.7" and python_version < "4" \
--hash=sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4 \
--hash=sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2
importlib-metadata==4.2.0 ; python_version >= "3.7" 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:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb \
--hash=sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522
pprintpp==0.4.0 ; python_version >= "3.7" and python_full_version < "4.0.0" \
#
# This file is autogenerated by pip-compile with Python 3.12
# by the following command:
#
# ./dev-cli.py update
#
asgiref==3.8.1 \
--hash=sha256:3e1e3ecc849832fe52ccf2cb6686b7a55f82bb1d6aee72a58826471390335e47 \
--hash=sha256:c343bd80a0bec947a9860adb4c432ffa7db769836c64238fc34bdc3fec84d590
# via
# django
# django-axes
async-timeout==4.0.3 \
--hash=sha256:4640d96be84d82d02ed59ea2b7105a0f7b33abe8703703cd0ab0bf87c427522f \
--hash=sha256:7405140ff1230c310e51dc27b3145b9092d659ce68ff733fb0cefe3ee42be028
# via cli-base-utilities
bleach==6.1.0 \
--hash=sha256:0a31f1837963c41d46bbf1331b8778e1308ea0791db03cc4e7357b97cf42a8fe \
--hash=sha256:3225f354cfc436b9789c66c4ee030194bee0568fbf9cbdad3bc8b5c26c5f12b6
# via django-tools
bx-django-utils==79 \
--hash=sha256:cb66087d4e9396281acf5a4394b749cff3062b66082d5726f6a8a342fdd35d0e \
--hash=sha256:d50b10ace24b0b363574542faecf04a81029e2fec6d6e6525fe063ed06238e04
# via
# django-fritzconnection
# django-tools
bx-py-utils==98 \
--hash=sha256:30fb49b0f0b21c9fbd544895480a1aa9da63f410572e4b46afbca792e64cec98 \
--hash=sha256:ac8b193ff117420b77b43c043e50b195d6aa9ee7e72e22265ca699730b61f71f
# via
# bx-django-utils
# cli-base-utilities
# django-fritzconnection
# django-tools
certifi==2024.7.4 \
--hash=sha256:5a1e7645bc0ec61a09e26c36f6106dd4cf40c6db3a1fb6352b0244e7fb057c7b \
--hash=sha256:c198e21b1289c2ab85ee4e67bb4b4ef3ead0892059901a8d5b622f24a1101e90
# via requests
charset-normalizer==3.3.2 \
--hash=sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027 \
--hash=sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087 \
--hash=sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786 \
--hash=sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8 \
--hash=sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09 \
--hash=sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185 \
--hash=sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574 \
--hash=sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e \
--hash=sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519 \
--hash=sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898 \
--hash=sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269 \
--hash=sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3 \
--hash=sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f \
--hash=sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6 \
--hash=sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8 \
--hash=sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a \
--hash=sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73 \
--hash=sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc \
--hash=sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714 \
--hash=sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2 \
--hash=sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc \
--hash=sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce \
--hash=sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d \
--hash=sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e \
--hash=sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6 \
--hash=sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269 \
--hash=sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96 \
--hash=sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d \
--hash=sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a \
--hash=sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4 \
--hash=sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77 \
--hash=sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d \
--hash=sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0 \
--hash=sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed \
--hash=sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068 \
--hash=sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac \
--hash=sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25 \
--hash=sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8 \
--hash=sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab \
--hash=sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26 \
--hash=sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2 \
--hash=sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db \
--hash=sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f \
--hash=sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5 \
--hash=sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99 \
--hash=sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c \
--hash=sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d \
--hash=sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811 \
--hash=sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa \
--hash=sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a \
--hash=sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03 \
--hash=sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b \
--hash=sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04 \
--hash=sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c \
--hash=sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001 \
--hash=sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458 \
--hash=sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389 \
--hash=sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99 \
--hash=sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985 \
--hash=sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537 \
--hash=sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238 \
--hash=sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f \
--hash=sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d \
--hash=sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796 \
--hash=sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a \
--hash=sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143 \
--hash=sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8 \
--hash=sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c \
--hash=sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5 \
--hash=sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5 \
--hash=sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711 \
--hash=sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4 \
--hash=sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6 \
--hash=sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c \
--hash=sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7 \
--hash=sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4 \
--hash=sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b \
--hash=sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae \
--hash=sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12 \
--hash=sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c \
--hash=sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae \
--hash=sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8 \
--hash=sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887 \
--hash=sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b \
--hash=sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4 \
--hash=sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f \
--hash=sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5 \
--hash=sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33 \
--hash=sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519 \
--hash=sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561
# via requests
cli-base-utilities==0.10.3 \
--hash=sha256:5c54cdd5e5122abf6a86aa6926b4b2dbc6632c113885692c8d95f531bb2b181b \
--hash=sha256:95b1119d9f4bb335974faf655e853f58bdd04af0bac99d22005b6fb79b9e4b14
# via djfritz_ynh (pyproject.toml)
click==8.1.7 \
--hash=sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28 \
--hash=sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de
# via
# cli-base-utilities
# rich-click
colorlog==6.8.2 \
--hash=sha256:3e3e079a41feb5a1b64f978b5ea4f46040a94f11f0e8bbb8261e3dbbeca64d44 \
--hash=sha256:4dcbb62368e2800cb3c5abd348da7e53f6c362dda502ec27c560b2e58a66bd33
# via
# django-fritzconnection
# django-tools
# django-yunohost-integration
diff-match-patch==20230430 \
--hash=sha256:953019cdb9c9d2c9e47b5b12bcff3cf4746fc4598eb406076fa1fc27e6a1f15c \
--hash=sha256:dce43505fb7b1b317de7195579388df0746d90db07015ed47a85e5e44930ef93
# via django-reversion-compare
django==5.1 \
--hash=sha256:848a5980e8efb76eea70872fb0e4bc5e371619c70fffbe48e3e1b50b2c09455d \
--hash=sha256:d3b811bf5371a26def053d7ee42a9df1267ef7622323fe70a601936725aa4557
# via
# bx-django-utils
# django-admin-sortable2
# django-axes
# django-debug-toolbar
# django-fritzconnection
# django-redis
# django-reversion
# django-reversion-compare
# django-tagulous
# django-tools
# django-yunohost-integration
django-admin-sortable2==2.2.2 \
--hash=sha256:efb28eed633e3a008c6938a887096a9887a213628a39458dc748d654f8f12d5c \
--hash=sha256:fc6b62ac1e5f4c95001742d568a2c1ce371bafe9d79d809fc55779fba955e498
# via django-fritzconnection
django-axes==6.5.1 \
--hash=sha256:7435068cc8523bfa3f34faa62bb3a772b76d00925c3ff54aef43e4316e74bf05 \
--hash=sha256:d57f0fc95d581a602c642b3fe5bc31488b9401bd7441f3bec1fef0e599028499
# via
# django-fritzconnection
# django-yunohost-integration
django-debug-toolbar==4.4.6 \
--hash=sha256:36e421cb908c2f0675e07f9f41e3d1d8618dc386392ec82d23bcfcd5d29c7044 \
--hash=sha256:3beb671c9ec44ffb817fad2780667f172bd1c067dbcabad6268ce39a81335f45
# via django-fritzconnection
django-fritzconnection==0.3.0 \
--hash=sha256:4bd0ad97106b12e9e5b9013406de91948e6d2fdf92b048abef999387288872a0 \
--hash=sha256:608c387c42c505515c0b20d5b84ee5e4c8aa8773a35efd9905e14adeabd292f7
# via djfritz_ynh (pyproject.toml)
django-redis==5.4.0 \
--hash=sha256:6a02abaa34b0fea8bf9b707d2c363ab6adc7409950b2db93602e6cb292818c42 \
--hash=sha256:ebc88df7da810732e2af9987f7f426c96204bf89319df4c6da6ca9a2942edd5b
# via django-yunohost-integration
django-reversion==5.1.0 \
--hash=sha256:084d4f117d9e2b4e8dfdfaad83ebb34410a03eed6071c96089e6811fdea82ad3 \
--hash=sha256:3309821e5b6fceedcce6b6975f1a9c7fab6ae7c7d0e1276a90e345946fa0dcb8
# via django-reversion-compare
django-reversion-compare==0.17.0 \
--hash=sha256:02d13ed7994d24d505671d84a005c6f99d13ed92531fa457c63245b518f835ff \
--hash=sha256:bfb036f32943965d01132809f94b853531cf6c9e3607b8894149c82fe68eddaa
# via django-fritzconnection
django-tagulous==2.0.0 \
--hash=sha256:476149f91fd0286898bf02154f0024834dd92b9d88edb6b69d5bb55d68b0b11b \
--hash=sha256:f994f04400db9742d2129eb11002bb9e41db3d76937e9ec4ffc4fe77b9beab31
# via django-fritzconnection
django-tools==0.56.2 \
--hash=sha256:29c25be814d74cd9f554d7d45bc205f5570e5feaa4232cbd09cc913c46b20c07 \
--hash=sha256:88a192f2873f0411b99ee1aba04f2779133284cd18a5c78976e8e4605ba5d7f7
# via django-yunohost-integration
django-yunohost-integration[ynh]==0.8.1 \
--hash=sha256:236bc1e427162182ca5efbf773bbad642b20741e7bf42308134887f4b7c27ced \
--hash=sha256:ced40614ba57edb0d80482e6f4f63c614115184695d2f2aaa1aa02b2485b0529
# via djfritz_ynh (pyproject.toml)
fritzconnection==1.14.0 \
--hash=sha256:95dacaf9bed6b52d809169006c0994bc55cc893dbe0a014bcf9535ae9d00982f \
--hash=sha256:f0cbc1977f21a04630773244eb1ca985af15c345aef4bbd5757b8fb124e7de1d
# via django-fritzconnection
gunicorn==23.0.0 \
--hash=sha256:ec400d38950de4dfd418cff8328b2c8faed0edb0d517d3394e457c317908ca4d \
--hash=sha256:f014447a0101dc57e294f6c18ca6b40227a4c90e9bdb586042628030cba004ec
# via
# django-fritzconnection
# django-tools
# django-yunohost-integration
icdiff==2.0.7 \
--hash=sha256:f05d1b3623223dd1c70f7848da7d699de3d9a2550b902a8234d9026292fb5762 \
--hash=sha256:f79a318891adbf59a45e3a7694f5e1f18c5407065264637072ac8363b759866f
# via django-tools
idna==3.8 \
--hash=sha256:050b4e5baadcd44d760cedbd2b8e639f2ff89bbc7a5730fcc662954303377aac \
--hash=sha256:d838c2c0ed6fced7693d5e8ab8e734d5f8fda53a039c0164afb0b82e771e3603
# via requests
markdown-it-py==3.0.0 \
--hash=sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1 \
--hash=sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb
# via rich
mdurl==0.1.2 \
--hash=sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8 \
--hash=sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba
# via markdown-it-py
packaging==24.1 \
--hash=sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002 \
--hash=sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124
# via
# cli-base-utilities
# django-yunohost-integration
# gunicorn
pprintpp==0.4.0 \
--hash=sha256:b6b4dcdd0c0c0d75e4d7b2f21a9e933e5b2ce62b26e1a54537f9651ae5a5c01d \
--hash=sha256:ea826108e2c7f49dc6d66c752973c3fc9749142a798d6b254e1e301cfdbc6403
psycopg2==2.9.3 ; python_version >= "3.7" and python_full_version < "4.0.0" \
--hash=sha256:06f32425949bd5fe8f625c49f17ebb9784e1e4fe928b7cce72edc36fb68e4c0c \
--hash=sha256:0762c27d018edbcb2d34d51596e4346c983bd27c330218c56c4dc25ef7e819bf \
--hash=sha256:083707a696e5e1c330af2508d8fab36f9700b26621ccbcb538abe22e15485362 \
--hash=sha256:34b33e0162cfcaad151f249c2649fd1030010c16f4bbc40a604c1cb77173dcf7 \
--hash=sha256:4295093a6ae3434d33ec6baab4ca5512a5082cc43c0505293087b8a46d108461 \
--hash=sha256:8cf3878353cc04b053822896bc4922b194792df9df2f1ad8da01fb3043602126 \
--hash=sha256:8e841d1bf3434da985cc5ef13e6f75c8981ced601fd70cc6bf33351b91562981 \
--hash=sha256:9572e08b50aed176ef6d66f15a21d823bb6f6d23152d35e8451d7d2d18fdac56 \
--hash=sha256:a81e3866f99382dfe8c15a151f1ca5fde5815fde879348fe5a9884a7c092a305 \
--hash=sha256:cb10d44e6694d763fa1078a26f7f6137d69f555a78ec85dc2ef716c37447e4b2 \
--hash=sha256:d3ca6421b942f60c008f81a3541e8faf6865a28d5a9b48544b0ee4f40cac7fca
pyparsing==3.0.9 ; python_version >= "3.7" and python_full_version < "4.0.0" \
--hash=sha256:2b020ecf7d21b687f219b71ecad3631f644a47f01403fa1d1036b0c6416d70fb \
--hash=sha256:5026bae9a10eeaefb61dab2f09052b9f4307d44aee4eda64b309723d8d206bbc
python-stdnum==1.17 ; python_version >= "3.7" and python_full_version < "4.0.0" \
--hash=sha256:374e2b5e13912ccdbf50b0b23fca2c3e0531174805c32d74e145f37756328340 \
--hash=sha256:a46e6cf9652807314d369b654b255c86a59f93d18be2834f3d567ed1a346c547
pytz==2022.4 ; python_version >= "3.7" and python_full_version < "4.0.0" \
--hash=sha256:2c0784747071402c6e99f0bafdb7da0fa22645f06554c7ae06bf6358897e9c91 \
--hash=sha256:48ce799d83b6f8aab2020e369b627446696619e79645419610b9facd909b3174
redis==4.3.4 ; python_version >= "3.7" and python_full_version < "4.0.0" \
--hash=sha256:a52d5694c9eb4292770084fa8c863f79367ca19884b329ab574d5cb2036b3e54 \
--hash=sha256:ddf27071df4adf3821c4f2ca59d67525c3a82e5f268bed97b813cb4fabf87880
requests==2.28.1 ; python_version >= "3.7" and python_version < "4" \
--hash=sha256:7c5599b102feddaa661c826c56ab4fee28bfd17f5abca1ebbe3e7f19d7c97983 \
--hash=sha256:8fefa2a1a1365bf5520aac41836fbee479da67864514bdb821f31ce07ce65349
setuptools==65.4.1 ; python_version >= "3.7" and python_full_version < "4.0.0" \
--hash=sha256:1b6bdc6161661409c5f21508763dc63ab20a9ac2f8ba20029aaaa7fdb9118012 \
--hash=sha256:3050e338e5871e70c72983072fe34f6032ae1cdeeeb67338199c2f74e083a80e
six==1.16.0 ; python_version >= "3.7" and python_full_version < "4.0.0" \
# via django-tools
psycopg[binary]==3.2.1 \
--hash=sha256:dc8da6dc8729dacacda3cc2f17d2c9397a70a66cf0d2b69c91065d60d5f00cb7 \
--hash=sha256:ece385fb413a37db332f97c49208b36cf030ff02b199d7635ed2fbd378724175
# via django-yunohost-integration
psycopg-binary==3.2.1 \
--hash=sha256:059cbd4e6da2337e17707178fe49464ed01de867dc86c677b30751755ec1dc51 \
--hash=sha256:06a7aae34edfe179ddc04da005e083ff6c6b0020000399a2cbf0a7121a8a22ea \
--hash=sha256:0879b5d76b7d48678d31278242aaf951bc2d69ca4e4d7cef117e4bbf7bfefda9 \
--hash=sha256:0ab58213cc976a1666f66bc1cb2e602315cd753b7981a8e17237ac2a185bd4a1 \
--hash=sha256:0b018631e5c80ce9bc210b71ea885932f9cca6db131e4df505653d7e3873a938 \
--hash=sha256:101472468d59c74bb8565fab603e032803fd533d16be4b2d13da1bab8deb32a3 \
--hash=sha256:1d353e028b8f848b9784450fc2abf149d53a738d451eab3ee4c85703438128b9 \
--hash=sha256:1d6833f607f3fc7b22226a9e121235d3b84c0eda1d3caab174673ef698f63788 \
--hash=sha256:21927f41c4d722ae8eb30d62a6ce732c398eac230509af5ba1749a337f8a63e2 \
--hash=sha256:28ada5f610468c57d8a4a055a8ea915d0085a43d794266c4f3b9d02f4288f4db \
--hash=sha256:2e8213bf50af073b1aa8dc3cff123bfeedac86332a16c1b7274910bc88a847c7 \
--hash=sha256:302b86f92c0d76e99fe1b5c22c492ae519ce8b98b88d37ef74fda4c9e24c6b46 \
--hash=sha256:334046a937bb086c36e2c6889fe327f9f29bfc085d678f70fac0b0618949f674 \
--hash=sha256:33e6669091d09f8ba36e10ce678a6d9916e110446236a9b92346464a3565635e \
--hash=sha256:3c838806eeb99af39f934b7999e35f947a8e577997cc892c12b5053a97a9057f \
--hash=sha256:40bb515d042f6a345714ec0403df68ccf13f73b05e567837d80c886c7c9d3805 \
--hash=sha256:413977d18412ff83486eeb5875eb00b185a9391c57febac45b8993bf9c0ff489 \
--hash=sha256:415c3b72ea32119163255c6504085f374e47ae7345f14bc3f0ef1f6e0976a879 \
--hash=sha256:42781ba94e8842ee98bca5a7d0c44cc9d067500fedca2d6a90fa3609b6d16b42 \
--hash=sha256:463d55345f73ff391df8177a185ad57b552915ad33f5cc2b31b930500c068b22 \
--hash=sha256:4a42b8f9ab39affcd5249b45cac763ac3cf12df962b67e23fd15a2ee2932afe5 \
--hash=sha256:4c84fcac8a3a3479ac14673095cc4e1fdba2935499f72c436785ac679bec0d1a \
--hash=sha256:592b27d6c46a40f9eeaaeea7c1fef6f3c60b02c634365eb649b2d880669f149f \
--hash=sha256:62b1b7b07e00ee490afb39c0a47d8282a9c2822c7cfed9553a04b0058adf7e7f \
--hash=sha256:6418712ba63cebb0c88c050b3997185b0ef54173b36568522d5634ac06153040 \
--hash=sha256:6f9e13600647087df5928875559f0eb8f496f53e6278b7da9511b4b3d0aff960 \
--hash=sha256:7066d3dca196ed0dc6172f9777b2d62e4f138705886be656cccff2d555234d60 \
--hash=sha256:73f9c9b984be9c322b5ec1515b12df1ee5896029f5e72d46160eb6517438659c \
--hash=sha256:74d623261655a169bc84a9669890975c229f2fa6e19a7f2d10a77675dcf1a707 \
--hash=sha256:788ffc43d7517c13e624c83e0e553b7b8823c9655e18296566d36a829bfb373f \
--hash=sha256:78c2007caf3c90f08685c5378e3ceb142bafd5636be7495f7d86ec8a977eaeef \
--hash=sha256:7a84b5eb194a258116154b2a4ff2962ea60ea52de089508db23a51d3d6b1c7d1 \
--hash=sha256:7ce965caf618061817f66c0906f0452aef966c293ae0933d4fa5a16ea6eaf5bb \
--hash=sha256:84837e99353d16c6980603b362d0f03302d4b06c71672a6651f38df8a482923d \
--hash=sha256:8f28ff0cb9f1defdc4a6f8c958bf6787274247e7dfeca811f6e2f56602695fb1 \
--hash=sha256:921f0c7f39590763d64a619de84d1b142587acc70fd11cbb5ba8fa39786f3073 \
--hash=sha256:950fd666ec9e9fe6a8eeb2b5a8f17301790e518953730ad44d715b59ffdbc67f \
--hash=sha256:9a997efbaadb5e1a294fb5760e2f5643d7b8e4e3fe6cb6f09e6d605fd28e0291 \
--hash=sha256:aa3931f308ab4a479d0ee22dc04bea867a6365cac0172e5ddcba359da043854b \
--hash=sha256:af0469c00f24c4bec18c3d2ede124bf62688d88d1b8a5f3c3edc2f61046fe0d7 \
--hash=sha256:b0104a72a17aa84b3b7dcab6c84826c595355bf54bb6ea6d284dcb06d99c6801 \
--hash=sha256:b09e8a576a2ac69d695032ee76f31e03b30781828b5dd6d18c6a009e5a3d1c35 \
--hash=sha256:b140182830c76c74d17eba27df3755a46442ce8d4fb299e7f1cf2f74a87c877b \
--hash=sha256:b1f087bd84bdcac78bf9f024ebdbfacd07fc0a23ec8191448a50679e2ac4a19e \
--hash=sha256:c1d2b6438fb83376f43ebb798bf0ad5e57bc56c03c9c29c85bc15405c8c0ac5a \
--hash=sha256:cad2de17804c4cfee8640ae2b279d616bb9e4734ac3c17c13db5e40982bd710d \
--hash=sha256:cc304a46be1e291031148d9d95c12451ffe783ff0cc72f18e2cc7ec43cdb8c68 \
--hash=sha256:dc314a47d44fe1a8069b075a64abffad347a3a1d8652fed1bab5d3baea37acb2 \
--hash=sha256:f092114f10f81fb6bae544a0ec027eb720e2d9c74a4fcdaa9dd3899873136935 \
--hash=sha256:f34e369891f77d0738e5d25727c307d06d5344948771e5379ea29c76c6d84555 \
--hash=sha256:f8a509aeaac364fa965454e80cd110fe6d48ba2c80f56c9b8563423f0b5c3cfd \
--hash=sha256:f8afb07114ea9b924a4a0305ceb15354ccf0ef3c0e14d54b8dbeb03e50182dd7 \
--hash=sha256:f99e59f8a5f4dcd9cbdec445f3d8ac950a492fc0e211032384d6992ed3c17eb7
# via psycopg
pygments==2.18.0 \
--hash=sha256:786ff802f32e91311bff3889f6e9a86e81505fe99f2735bb6d60ae0c5004f199 \
--hash=sha256:b8e6aca0523f3ab76fee51799c488e38782ac06eafcf95e7ba832985c8e7b13a
# via rich
python-dateutil==2.9.0.post0 \
--hash=sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3 \
--hash=sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427
# via cli-base-utilities
python-stdnum==1.20 \
--hash=sha256:111008e10391d54fb2afad2a10df70d5cb0c6c0a7ec82fec6f022cb8712961d3 \
--hash=sha256:ad2a2cf2eb025de408210235f36b4ae31252de3186240ccaa8126e117cb82690
# via bx-django-utils
pyyaml==6.0.2 \
--hash=sha256:01179a4a8559ab5de078078f37e5c1a30d76bb88519906844fd7bdea1b7729ff \
--hash=sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48 \
--hash=sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086 \
--hash=sha256:0b69e4ce7a131fe56b7e4d770c67429700908fc0752af059838b1cfb41960e4e \
--hash=sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133 \
--hash=sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5 \
--hash=sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484 \
--hash=sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee \
--hash=sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5 \
--hash=sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68 \
--hash=sha256:24471b829b3bf607e04e88d79542a9d48bb037c2267d7927a874e6c205ca7e9a \
--hash=sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf \
--hash=sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99 \
--hash=sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8 \
--hash=sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85 \
--hash=sha256:3b1fdb9dc17f5a7677423d508ab4f243a726dea51fa5e70992e59a7411c89d19 \
--hash=sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc \
--hash=sha256:43fa96a3ca0d6b1812e01ced1044a003533c47f6ee8aca31724f78e93ccc089a \
--hash=sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1 \
--hash=sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317 \
--hash=sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c \
--hash=sha256:6395c297d42274772abc367baaa79683958044e5d3835486c16da75d2a694631 \
--hash=sha256:688ba32a1cffef67fd2e9398a2efebaea461578b0923624778664cc1c914db5d \
--hash=sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652 \
--hash=sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5 \
--hash=sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e \
--hash=sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b \
--hash=sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8 \
--hash=sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476 \
--hash=sha256:82d09873e40955485746739bcb8b4586983670466c23382c19cffecbf1fd8706 \
--hash=sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563 \
--hash=sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237 \
--hash=sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b \
--hash=sha256:9056c1ecd25795207ad294bcf39f2db3d845767be0ea6e6a34d856f006006083 \
--hash=sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180 \
--hash=sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425 \
--hash=sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e \
--hash=sha256:a8786accb172bd8afb8be14490a16625cbc387036876ab6ba70912730faf8e1f \
--hash=sha256:a9f8c2e67970f13b16084e04f134610fd1d374bf477b17ec1599185cf611d725 \
--hash=sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183 \
--hash=sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab \
--hash=sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774 \
--hash=sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725 \
--hash=sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e \
--hash=sha256:d7fded462629cfa4b685c5416b949ebad6cec74af5e2d42905d41e257e0869f5 \
--hash=sha256:d84a1718ee396f54f3a086ea0a66d8e552b2ab2017ef8b420e92edbc841c352d \
--hash=sha256:d8e03406cac8513435335dbab54c0d385e4a49e4945d2909a581c83647ca0290 \
--hash=sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44 \
--hash=sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed \
--hash=sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4 \
--hash=sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba \
--hash=sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12 \
--hash=sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4
# via django-yunohost-integration
redis==5.0.8 \
--hash=sha256:0c5b10d387568dfe0698c6fad6615750c24170e548ca2deac10c649d463e9870 \
--hash=sha256:56134ee08ea909106090934adc36f65c9bcbbaecea5b21ba704ba6fb561f8eb4
# via django-redis
requests==2.32.3 \
--hash=sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760 \
--hash=sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6
# via fritzconnection
rich==13.8.0 \
--hash=sha256:2e85306a063b9492dffc86278197a60cbece75bcb766022f3436f567cae11bdc \
--hash=sha256:a5ac1f1cd448ade0d59cc3356f7db7a7ccda2c8cbae9c7a90c28ff463d3e91f4
# via
# cli-base-utilities
# django-reversion-compare
# rich-click
rich-click==1.8.3 \
--hash=sha256:636d9c040d31c5eee242201b5bf4f2d358bfae4db14bb22ec1cafa717cfd02cd \
--hash=sha256:6d75bdfa7aa9ed2c467789a0688bc6da23fbe3a143e19aa6ad3f8bac113d2ab3
# via cli-base-utilities
six==1.16.0 \
--hash=sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926 \
--hash=sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254
sqlparse==0.4.3 ; python_version >= "3.7" and python_full_version < "4.0.0" \
--hash=sha256:0323c0ec29cd52bceabc1b4d9d579e311f3e4961b98d174201d5622a23b85e34 \
--hash=sha256:69ca804846bb114d2ec380e4360a8a340db83f0ccf3afceeb1404df028f57268
typing-extensions==4.3.0 ; python_version >= "3.7" and python_version < "3.8" \
--hash=sha256:25642c956049920a5aa49edcdd6ab1e06d7e5d467fc00e0506c44ac86fbfca02 \
--hash=sha256:e6d2677a32f47fc7eb2795db1dd15c1f34eff616bcaf2cfb5e997f854fa1c4a6
urllib3==1.26.12 ; python_version >= "3.7" and python_version < "4" \
--hash=sha256:3fa96cf423e6987997fc326ae8df396db2a8b7c667747d47ddd8ecba91f4a74e \
--hash=sha256:b930dd878d5a8afb066a637fbb35144fe7901e3b209d1cd4f524bd0e9deee997
webencodings==0.5.1 ; python_version >= "3.7" and python_full_version < "4.0.0" \
# via
# bleach
# python-dateutil
sqlparse==0.5.1 \
--hash=sha256:773dcbf9a5ab44a090f3441e2180efe2560220203dc2f8c0b0fa141e18b505e4 \
--hash=sha256:bb6b4df465655ef332548e24f08e205afc81b9ab86cb1c45657a7ff173a3a00e
# via
# django
# django-debug-toolbar
tomli==2.0.1 \
--hash=sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc \
--hash=sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f
# via cli-base-utilities
tomlkit==0.13.2 \
--hash=sha256:7a974427f6e119197f670fbbbeae7bef749a6c14e793db934baefc1b5f03efde \
--hash=sha256:fff5fe59a87295b278abd31bec92c15d9bc4a06885ab12bcea52c71119392e79
# via cli-base-utilities
typing-extensions==4.12.2 \
--hash=sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d \
--hash=sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8
# via
# psycopg
# rich-click
urllib3==2.2.2 \
--hash=sha256:a448b2f64d686155468037e1ace9f2d2199776e17f0a46610480d311f73e3472 \
--hash=sha256:dd505485549a7a552833da5e6063639d0d177c04f23bc3864e41e5dc5f612168
# via requests
webencodings==0.5.1 \
--hash=sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78 \
--hash=sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923
wrapt==1.14.1 ; python_version >= "3.7" and python_full_version < "4.0.0" \
--hash=sha256:00b6d4ea20a906c0ca56d84f93065b398ab74b927a7a3dbd470f6fc503f95dc3 \
--hash=sha256:01c205616a89d09827986bc4e859bcabd64f5a0662a7fe95e0d359424e0e071b \
--hash=sha256:02b41b633c6261feff8ddd8d11c711df6842aba629fdd3da10249a53211a72c4 \
--hash=sha256:07f7a7d0f388028b2df1d916e94bbb40624c59b48ecc6cbc232546706fac74c2 \
--hash=sha256:11871514607b15cfeb87c547a49bca19fde402f32e2b1c24a632506c0a756656 \
--hash=sha256:1b376b3f4896e7930f1f772ac4b064ac12598d1c38d04907e696cc4d794b43d3 \
--hash=sha256:21ac0156c4b089b330b7666db40feee30a5d52634cc4560e1905d6529a3897ff \
--hash=sha256:257fd78c513e0fb5cdbe058c27a0624c9884e735bbd131935fd49e9fe719d310 \
--hash=sha256:2b39d38039a1fdad98c87279b48bc5dce2c0ca0d73483b12cb72aa9609278e8a \
--hash=sha256:2cf71233a0ed05ccdabe209c606fe0bac7379fdcf687f39b944420d2a09fdb57 \
--hash=sha256:2fe803deacd09a233e4762a1adcea5db5d31e6be577a43352936179d14d90069 \
--hash=sha256:3232822c7d98d23895ccc443bbdf57c7412c5a65996c30442ebe6ed3df335383 \
--hash=sha256:34aa51c45f28ba7f12accd624225e2b1e5a3a45206aa191f6f9aac931d9d56fe \
--hash=sha256:36f582d0c6bc99d5f39cd3ac2a9062e57f3cf606ade29a0a0d6b323462f4dd87 \
--hash=sha256:380a85cf89e0e69b7cfbe2ea9f765f004ff419f34194018a6827ac0e3edfed4d \
--hash=sha256:40e7bc81c9e2b2734ea4bc1aceb8a8f0ceaac7c5299bc5d69e37c44d9081d43b \
--hash=sha256:43ca3bbbe97af00f49efb06e352eae40434ca9d915906f77def219b88e85d907 \
--hash=sha256:4fcc4649dc762cddacd193e6b55bc02edca674067f5f98166d7713b193932b7f \
--hash=sha256:5a0f54ce2c092aaf439813735584b9537cad479575a09892b8352fea5e988dc0 \
--hash=sha256:5a9a0d155deafd9448baff28c08e150d9b24ff010e899311ddd63c45c2445e28 \
--hash=sha256:5b02d65b9ccf0ef6c34cba6cf5bf2aab1bb2f49c6090bafeecc9cd81ad4ea1c1 \
--hash=sha256:60db23fa423575eeb65ea430cee741acb7c26a1365d103f7b0f6ec412b893853 \
--hash=sha256:642c2e7a804fcf18c222e1060df25fc210b9c58db7c91416fb055897fc27e8cc \
--hash=sha256:6a9a25751acb379b466ff6be78a315e2b439d4c94c1e99cb7266d40a537995d3 \
--hash=sha256:6b1a564e6cb69922c7fe3a678b9f9a3c54e72b469875aa8018f18b4d1dd1adf3 \
--hash=sha256:6d323e1554b3d22cfc03cd3243b5bb815a51f5249fdcbb86fda4bf62bab9e164 \
--hash=sha256:6e743de5e9c3d1b7185870f480587b75b1cb604832e380d64f9504a0535912d1 \
--hash=sha256:709fe01086a55cf79d20f741f39325018f4df051ef39fe921b1ebe780a66184c \
--hash=sha256:7b7c050ae976e286906dd3f26009e117eb000fb2cf3533398c5ad9ccc86867b1 \
--hash=sha256:7d2872609603cb35ca513d7404a94d6d608fc13211563571117046c9d2bcc3d7 \
--hash=sha256:7ef58fb89674095bfc57c4069e95d7a31cfdc0939e2a579882ac7d55aadfd2a1 \
--hash=sha256:80bb5c256f1415f747011dc3604b59bc1f91c6e7150bd7db03b19170ee06b320 \
--hash=sha256:81b19725065dcb43df02b37e03278c011a09e49757287dca60c5aecdd5a0b8ed \
--hash=sha256:833b58d5d0b7e5b9832869f039203389ac7cbf01765639c7309fd50ef619e0b1 \
--hash=sha256:88bd7b6bd70a5b6803c1abf6bca012f7ed963e58c68d76ee20b9d751c74a3248 \
--hash=sha256:8ad85f7f4e20964db4daadcab70b47ab05c7c1cf2a7c1e51087bfaa83831854c \
--hash=sha256:8c0ce1e99116d5ab21355d8ebe53d9460366704ea38ae4d9f6933188f327b456 \
--hash=sha256:8d649d616e5c6a678b26d15ece345354f7c2286acd6db868e65fcc5ff7c24a77 \
--hash=sha256:903500616422a40a98a5a3c4ff4ed9d0066f3b4c951fa286018ecdf0750194ef \
--hash=sha256:9736af4641846491aedb3c3f56b9bc5568d92b0692303b5a305301a95dfd38b1 \
--hash=sha256:988635d122aaf2bdcef9e795435662bcd65b02f4f4c1ae37fbee7401c440b3a7 \
--hash=sha256:9cca3c2cdadb362116235fdbd411735de4328c61425b0aa9f872fd76d02c4e86 \
--hash=sha256:9e0fd32e0148dd5dea6af5fee42beb949098564cc23211a88d799e434255a1f4 \
--hash=sha256:9f3e6f9e05148ff90002b884fbc2a86bd303ae847e472f44ecc06c2cd2fcdb2d \
--hash=sha256:a85d2b46be66a71bedde836d9e41859879cc54a2a04fad1191eb50c2066f6e9d \
--hash=sha256:a9a52172be0b5aae932bef82a79ec0a0ce87288c7d132946d645eba03f0ad8a8 \
--hash=sha256:aa31fdcc33fef9eb2552cbcbfee7773d5a6792c137b359e82879c101e98584c5 \
--hash=sha256:b014c23646a467558be7da3d6b9fa409b2c567d2110599b7cf9a0c5992b3b471 \
--hash=sha256:b21bb4c09ffabfa0e85e3a6b623e19b80e7acd709b9f91452b8297ace2a8ab00 \
--hash=sha256:b5901a312f4d14c59918c221323068fad0540e34324925c8475263841dbdfe68 \
--hash=sha256:b9b7a708dd92306328117d8c4b62e2194d00c365f18eff11a9b53c6f923b01e3 \
--hash=sha256:d1967f46ea8f2db647c786e78d8cc7e4313dbd1b0aca360592d8027b8508e24d \
--hash=sha256:d52a25136894c63de15a35bc0bdc5adb4b0e173b9c0d07a2be9d3ca64a332735 \
--hash=sha256:d77c85fedff92cf788face9bfa3ebaa364448ebb1d765302e9af11bf449ca36d \
--hash=sha256:d79d7d5dc8a32b7093e81e97dad755127ff77bcc899e845f41bf71747af0c569 \
--hash=sha256:dbcda74c67263139358f4d188ae5faae95c30929281bc6866d00573783c422b7 \
--hash=sha256:ddaea91abf8b0d13443f6dac52e89051a5063c7d014710dcb4d4abb2ff811a59 \
--hash=sha256:dee0ce50c6a2dd9056c20db781e9c1cfd33e77d2d569f5d1d9321c641bb903d5 \
--hash=sha256:dee60e1de1898bde3b238f18340eec6148986da0455d8ba7848d50470a7a32fb \
--hash=sha256:e2f83e18fe2f4c9e7db597e988f72712c0c3676d337d8b101f6758107c42425b \
--hash=sha256:e3fb1677c720409d5f671e39bac6c9e0e422584e5f518bfd50aa4cbbea02433f \
--hash=sha256:ee2b1b1769f6707a8a445162ea16dddf74285c3964f605877a20e38545c3c462 \
--hash=sha256:ee6acae74a2b91865910eef5e7de37dc6895ad96fa23603d1d27ea69df545015 \
--hash=sha256:ef3f72c9666bba2bab70d2a8b79f2c6d2c1a42a7f7e2b0ec83bb2f9e383950af
zipp==3.8.1 ; python_version >= "3.7" and python_version < "3.8" \
--hash=sha256:05b45f1ee8f807d0cc928485ca40a07cb491cf092ff587c0df9cb1fd154848d2 \
--hash=sha256:47c40d7fe183a6f21403a199b3e4192cca5774656965b0a4988ad2f8feb5f009
# via bleach

View file

@ -2,7 +2,7 @@
################################################################################
# Please do not modify this file, it will be reset at the next update.
# You can edit the file __INSTALL_DIR__/local_settings.py and add/modify the settings you need.
# You can edit the file __DATA_DIR__/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.
@ -14,29 +14,33 @@ from pathlib import Path as __Path
from django_yunohost_integration.base_settings import * # noqa:F401,F403
from django_yunohost_integration.secret_key import get_or_create_secret as __get_or_create_secret
from djfritz_project.settings.base import * # noqa:F401,F403
# https://github.com/jedie/django-fritzconnection
from djfritz_project.settings.prod import * # noqa:F401,F403 isort:skip
from django_yunohost_integration.base_settings import LOGGING # noqa:F401 isort:skip
INSTALL_DIR = __Path('__INSTALL_DIR__/app') # /var/www/$app/app
assert INSTALL_DIR.is_dir(), f'Directory not exists: {INSTALL_DIR}'
DATA_DIR_PATH = __Path('__DATA_DIR__') # /home/yunohost.app/$app/
assert DATA_DIR_PATH.is_dir(), f'Directory not exists: {DATA_DIR_PATH}'
PUBLIC_PATH = __Path('__INSTALL_DIR__/public') # /var/www/$app/public
assert PUBLIC_PATH.is_dir(), f'Directory not exists: {PUBLIC_PATH}'
INSTALL_DIR_PATH = __Path('__INSTALL_DIR__') # /var/www/$app/
assert INSTALL_DIR_PATH.is_dir(), f'Directory not exists: {INSTALL_DIR_PATH}'
LOG_FILE = __Path('/var/log/__APP__/__APP__.log') # /var/log/$app/$app.log
assert LOG_FILE.is_file(), f'File not exists: {LOG_FILE}'
LOG_FILE_PATH = __Path('__LOG_FILE__') # /var/log/$app/djfritz_ynh.log
assert LOG_FILE_PATH.is_file(), f'File not exists: {LOG_FILE_PATH}'
PATH = '__PATH__' # $YNH_APP_ARG_PATH
PATH = PATH.strip('/')
PATH_URL = '__PATH__'
PATH_URL = PATH_URL.strip('/')
YNH_CURRENT_HOST = '__YNH_CURRENT_HOST__' # YunoHost main domain from: /etc/yunohost/current_host
# -----------------------------------------------------------------------------
# config_panel.toml settings:
DEBUG_ENABLED = '__DEBUG_ENABLED__'
DEBUG = bool(int(DEBUG_ENABLED))
DEBUG = DEBUG_ENABLED == '1'
LOG_LEVEL = '__LOG_LEVEL__'
ADMIN_EMAIL = '__ADMIN_EMAIL__'
@ -48,20 +52,26 @@ DEFAULT_FROM_EMAIL = '__DEFAULT_FROM_EMAIL__'
# Function that will be called to finalize a user profile:
YNH_SETUP_USER = 'setup_user.setup_project_user'
SECRET_KEY = __get_or_create_secret(INSTALL_DIR / 'secret.txt') # /opt/yunohost/$app/secret.txt
INSTALLED_APPS += [
'axes', # https://github.com/jazzband/django-axes
'django_yunohost_integration', # https://github.com/YunoHost-Apps/django_yunohost_integration
]
if 'axes' not in INSTALLED_APPS:
INSTALLED_APPS.append('axes') # https://github.com/jazzband/django-axes
INSTALLED_APPS.append('django_yunohost_integration.apps.YunohostIntegrationConfig')
SECRET_KEY = __get_or_create_secret(
DATA_DIR_PATH / 'secret.txt'
) # /home/yunohost.app/$app/secret.txt
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',
)
# AxesMiddleware should be the last middleware:
MIDDLEWARE.append('axes.middleware.AxesMiddleware')
if 'axes.middleware.AxesMiddleware' not in MIDDLEWARE:
# AxesMiddleware should be the last middleware:
MIDDLEWARE.append('axes.middleware.AxesMiddleware')
# Keep ModelBackend around for per-user permissions and superuser
@ -126,6 +136,9 @@ 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-fritzconnection, you might
# want to use unix sockets instead:
# 'LOCATION': 'unix:///var/run/redis/redis.sock?db=1',
'OPTIONS': {
'CLIENT_CLASS': 'django_redis.client.DefaultClient',
},
@ -136,29 +149,30 @@ CACHES = {
# _____________________________________________________________________________
# Static files (CSS, JavaScript, Images)
if PATH:
STATIC_URL = f'/{PATH}/static/'
MEDIA_URL = f'/{PATH}/media/'
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(PUBLIC_PATH / 'static')
MEDIA_ROOT = str(PUBLIC_PATH / 'media')
STATIC_ROOT = str(INSTALL_DIR_PATH / 'static')
MEDIA_ROOT = str(INSTALL_DIR_PATH / 'media')
# -----------------------------------------------------------------------------
# Set log file to e.g.: /var/log/$app/$app.log
LOGGING['handlers']['log_file']['filename'] = str(LOG_FILE)
LOGGING['handlers']['log_file']['filename'] = str(LOG_FILE_PATH)
# Example how to add logging to own app:
LOGGING['loggers']['djfritz'] = {
'handlers': ['syslog', 'log_file', 'mail_admins'],
'level': 'INFO',
'propagate': False,
}
for __logger_name in LOGGING['loggers'].keys():
LOGGING['loggers'][__logger_name]['level'] = 'DEBUG' if DEBUG else LOG_LEVEL
# -----------------------------------------------------------------------------

View file

@ -5,9 +5,9 @@ After=redis.service postgresql.service
[Service]
User=__APP__
Group=__APP__
WorkingDirectory=__INSTALL_DIR__/app
WorkingDirectory=__DATA_DIR__/
ExecStart=__INSTALL_DIR__/venv/bin/gunicorn --config __INSTALL_DIR__/app/gunicorn.conf.py wsgi
ExecStart=__DATA_DIR__/venv/bin/gunicorn --config __DATA_DIR__/gunicorn.conf.py wsgi
StandardOutput=syslog
StandardError=syslog

View file

@ -6,13 +6,15 @@
from django.conf import settings
from django.urls import include, path
from django.views.generic import RedirectView
if settings.PATH:
# settings.PATH is the $YNH_APP_ARG_PATH
# Prefix all urls with "PATH":
if settings.PATH_URL:
# settings.PATH_URL is __PATH__
# Prefix all urls with "PATH_URL":
urlpatterns = [
path(f'{settings.PATH}/', include('djfritz_project.urls')),
path('', RedirectView.as_view(url=f'{settings.PATH_URL}/')),
path(f'{settings.PATH_URL}/', include('djfritz_project.urls')),
]
else:
# Installed to domain root, without a path prefix

View file

@ -1,3 +1,4 @@
# https://yunohost.org/en/packaging_config_panels
# https://github.com/YunoHost/example_ynh/blob/master/config_panel.toml.example
version = "1.0"
@ -14,25 +15,28 @@ services = ["__APP__"]
ask = "from email"
type = "email"
help = "Default email address to use for various automated emails."
bind = "default_from_email:__INSTALL_DIR__/app/settings.py"
#
# We can't use "__DATA_DIR__" in bind value, because of this bug:
# https://github.com/YunoHost/issues/issues/2283
bind = "default_from_email:/home/yunohost.app/__APP__/settings.py"
[main.config.admin_email]
ask = "ADMIN email"
type = "email"
help = "EMail address for error emails."
bind = "admin_email:__INSTALL_DIR__/app/settings.py"
bind = "admin_email:/home/yunohost.app/__APP__/settings.py"
[main.config.debug_enabled]
ask = "DEBUG mode"
type = "boolean"
yes = "1"
no = "0"
help = "Should be never enabled in production!"
bind = "debug_enabled:__INSTALL_DIR__/app/settings.py"
help = "Enable DEBUG mode? (Should be never enabled in production!)"
bind = "debug_enabled:/home/yunohost.app/__APP__/settings.py"
[main.config.log_level]
type = "string"
ask = "Log Level"
choices = ["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"]
default = "WARNING"
bind = "log_level:__INSTALL_DIR__/app/settings.py"
bind = "log_level:/home/yunohost.app/__APP__/settings.py"

116
dev-cli.py Executable file
View file

@ -0,0 +1,116 @@
#!/usr/bin/env python3
"""
bootstrap CLI
~~~~~~~~~~~~~
Just call this file, and the magic happens ;)
"""
import hashlib
import shlex
import subprocess
import sys
import venv
from pathlib import Path
def print_no_pip_error():
print('Error: Pip not available!')
print('Hint: "apt-get install python3-venv"\n')
try:
from ensurepip import version
except ModuleNotFoundError as err:
print(err)
print('-' * 100)
print_no_pip_error()
raise
else:
if not version():
print_no_pip_error()
sys.exit(-1)
assert sys.version_info >= (3, 11), f'Python version {sys.version_info} is too old!'
if sys.platform == 'win32': # wtf
# Files under Windows, e.g.: .../.venv/Scripts/python.exe
BIN_NAME = 'Scripts'
FILE_EXT = '.exe'
else:
# Files under Linux/Mac and all other than Windows, e.g.: .../.venv/bin/python
BIN_NAME = 'bin'
FILE_EXT = ''
BASE_PATH = Path(__file__).parent
VENV_PATH = BASE_PATH / '.venv'
BIN_PATH = VENV_PATH / BIN_NAME
PYTHON_PATH = BIN_PATH / f'python3{FILE_EXT}'
PIP_PATH = BIN_PATH / f'pip{FILE_EXT}'
PIP_SYNC_PATH = BIN_PATH / f'pip-sync{FILE_EXT}'
DEP_LOCK_PATH = BASE_PATH / 'requirements.dev.txt'
DEP_HASH_PATH = VENV_PATH / '.dep_hash'
# script file defined in pyproject.toml as [console_scripts]
# (Under Windows: ".exe" not added!)
PROJECT_SHELL_SCRIPT = BIN_PATH / 'djfritz_ynh_dev'
def get_dep_hash():
"""Get SHA512 hash from lock file content."""
return hashlib.sha512(DEP_LOCK_PATH.read_bytes()).hexdigest()
def store_dep_hash():
"""Generate .venv/.dep_hash"""
DEP_HASH_PATH.write_text(get_dep_hash())
def venv_up2date():
"""Is existing .venv is up-to-date?"""
if DEP_HASH_PATH.is_file():
return DEP_HASH_PATH.read_text() == get_dep_hash()
return False
def verbose_check_call(*popen_args):
print(f'\n+ {shlex.join(str(arg) for arg in popen_args)}\n')
return subprocess.check_call(popen_args)
def main(argv):
assert DEP_LOCK_PATH.is_file(), f'File not found: "{DEP_LOCK_PATH}" !'
# Create virtual env in ".venv/":
if not PYTHON_PATH.is_file():
print(f'Create virtual env here: {VENV_PATH.absolute()}')
builder = venv.EnvBuilder(symlinks=True, upgrade=True, with_pip=True)
builder.create(env_dir=VENV_PATH)
if not PROJECT_SHELL_SCRIPT.is_file() or not venv_up2date():
# Update pip
verbose_check_call(PYTHON_PATH, '-m', 'pip', 'install', '-U', 'pip')
# Install pip-tools
verbose_check_call(PYTHON_PATH, '-m', 'pip', 'install', '-U', 'pip-tools')
# install requirements via "pip-sync"
verbose_check_call(PIP_SYNC_PATH, str(DEP_LOCK_PATH))
# install project
verbose_check_call(PIP_PATH, 'install', '--no-deps', '-e', '.')
store_dep_hash()
# Call our entry point CLI:
try:
verbose_check_call(PROJECT_SHELL_SCRIPT, *argv[1:])
except subprocess.CalledProcessError as err:
sys.exit(err.returncode)
if __name__ == '__main__':
main(sys.argv)

7
djfritz_ynh/__init__.py Normal file
View file

@ -0,0 +1,7 @@
"""
djfritz_ynh
Web based FritzBox management using Python/Django.
"""
__version__ = '0.3.0+ynh4'
__author__ = 'Jens Diemer <git@jensdiemer.de>'

View file

301
djfritz_ynh/cli/dev.py Normal file
View file

@ -0,0 +1,301 @@
"""
CLI for development
"""
import logging
import shlex
import sys
from pathlib import Path
import rich_click as click
from cli_base.cli_tools import code_style
from cli_base.cli_tools.dev_tools import run_coverage, run_tox
from cli_base.cli_tools.subprocess_utils import verbose_check_call
from cli_base.cli_tools.test_utils.snapshot import UpdateTestSnapshotFiles
from cli_base.cli_tools.verbosity import OPTION_KWARGS_VERBOSE
from cli_base.cli_tools.version_info import print_version
from cli_base.run_pip_audit import run_pip_audit
from django.core.management.commands.test import Command as DjangoTestCommand
from django_yunohost_integration.local_test import create_local_test
from django_yunohost_integration.path_utils import get_project_root
from manageprojects.utilities.publish import publish_package
from rich import print
from rich.console import Console
from rich.traceback import install as rich_traceback_install
from rich_click import RichGroup
import djfritz_ynh
from djfritz_ynh import constants
from djfritz_ynh.tests import setup_ynh_tests
logger = logging.getLogger(__name__)
OPTION_ARGS_DEFAULT_TRUE = dict(is_flag=True, show_default=True, default=True)
OPTION_ARGS_DEFAULT_FALSE = dict(is_flag=True, show_default=True, default=False)
ARGUMENT_EXISTING_DIR = dict(
type=click.Path(exists=True, file_okay=False, dir_okay=True, readable=True, path_type=Path)
)
ARGUMENT_NOT_EXISTING_DIR = dict(
type=click.Path(
exists=False,
file_okay=False,
dir_okay=True,
readable=False,
writable=True,
path_type=Path,
)
)
ARGUMENT_EXISTING_FILE = dict(
type=click.Path(exists=True, file_okay=True, dir_okay=False, readable=True, path_type=Path)
)
class ClickGroup(RichGroup): # FIXME: How to set the "info_name" easier?
def make_context(self, info_name, *args, **kwargs):
info_name = './dev-cli.py'
return super().make_context(info_name, *args, **kwargs)
@click.group(
cls=ClickGroup,
epilog=constants.CLI_EPILOG,
)
def cli():
pass
@cli.command()
@click.option('-v', '--verbosity', **OPTION_KWARGS_VERBOSE)
def mypy(verbosity: int):
"""Run Mypy (configured in pyproject.toml)"""
verbose_check_call('mypy', '.', cwd=get_project_root(), verbose=verbosity > 0, exit_on_error=True)
@cli.command()
def install():
"""
Run pip-sync and install 'djfritz_ynh' via pip as editable.
"""
verbose_check_call('pip-sync', get_project_root() / 'requirements.dev.txt')
verbose_check_call('pip', 'install', '--no-deps', '-e', '.')
@cli.command()
@click.option('-v', '--verbosity', **OPTION_KWARGS_VERBOSE)
def pip_audit(verbosity: int):
"""
Run pip-audit check against current requirements files
"""
run_pip_audit(base_path=get_project_root(), verbosity=verbosity)
@cli.command()
def update():
"""
Update "requirements*.txt" dependencies files
"""
bin_path = Path(sys.executable).parent
verbose_check_call(bin_path / 'pip', 'install', '-U', 'pip')
verbose_check_call(bin_path / 'pip', 'install', '-U', 'pip-tools')
extra_env = dict(
CUSTOM_COMPILE_COMMAND='./dev-cli.py update',
)
pip_compile_base = [
bin_path / 'pip-compile',
'--verbose',
'--allow-unsafe', # https://pip-tools.readthedocs.io/en/latest/#deprecations
'--resolver=backtracking', # https://pip-tools.readthedocs.io/en/latest/#deprecations
'--upgrade',
'--generate-hashes',
]
# Only "prod" dependencies:
verbose_check_call(
*pip_compile_base,
'pyproject.toml',
'--output-file',
'conf/requirements.txt',
extra_env=extra_env,
)
# dependencies + "dev"-optional-dependencies:
verbose_check_call(
*pip_compile_base,
'pyproject.toml',
'--extra=dev',
'--output-file',
'requirements.dev.txt',
extra_env=extra_env,
)
run_pip_audit(base_path=get_project_root())
# Install new dependencies in current .venv:
verbose_check_call(bin_path / 'pip-sync', 'requirements.dev.txt')
@cli.command()
def publish():
"""
Build and upload this project to PyPi
"""
try:
_run_django_test_cli(argv=sys.argv, exit_after_run=True) # Don't publish a broken state
except SystemExit as err:
assert err.code == 0, f'Exit code is not 0: {err.code}'
publish_package(
module=djfritz_ynh,
package_path=get_project_root(),
distribution_name='djfritz_ynh',
)
@cli.command()
@click.option('--color/--no-color', **OPTION_ARGS_DEFAULT_TRUE)
@click.option('-v', '--verbosity', **OPTION_KWARGS_VERBOSE)
def fix_code_style(color: bool, verbosity: int):
"""
Fix code style of all your_cool_package source code files via darker
"""
code_style.fix(package_root=get_project_root(), darker_color=color, darker_verbose=verbosity > 0)
@cli.command()
@click.option('--color/--no-color', **OPTION_ARGS_DEFAULT_TRUE)
@click.option('-v', '--verbosity', **OPTION_KWARGS_VERBOSE)
def check_code_style(color: bool, verbosity: int):
"""
Check code style by calling darker + flake8
"""
code_style.check(package_root=get_project_root(), darker_color=color, darker_verbose=verbosity > 0)
@cli.command()
def update_test_snapshot_files():
"""
Update all test snapshot files (by remove and recreate all snapshot files)
"""
with UpdateTestSnapshotFiles(root_path=get_project_root(), verbose=True):
# Just recreate them by running tests:
_run_django_test_cli(argv=sys.argv, exit_after_run=False)
def _run_django_test_cli(argv, exit_after_run=True):
"""
Call the origin Django test manage command CLI and pass all args to it.
"""
setup_ynh_tests()
print('\nStart Django unittests with:')
for default_arg in ('shuffle', 'buffer'):
if default_arg not in argv and f'--no-{default_arg}' not in argv:
argv.append(f'--{default_arg}')
print(shlex.join(argv))
print()
test_command = DjangoTestCommand()
test_command.run_from_argv(argv)
if exit_after_run:
sys.exit(0)
@cli.command() # Dummy command
def test():
"""
Compile YunoHost files and run Django unittests
"""
_run_django_test_cli(argv=sys.argv, exit_after_run=True)
@cli.command() # Dummy command
def coverage():
"""
Run tests and show coverage report.
"""
run_coverage()
@cli.command() # Dummy "tox" command
def tox():
"""
Run tox
"""
run_tox()
@cli.command()
def version():
"""Print version and exit"""
# Pseudo command, because the version always printed on every CLI call ;)
sys.exit(0)
@cli.command()
def local_test():
"""
Build a "local_test" YunoHost installation and start the Django dev. server against it.
"""
create_local_test(
django_settings_path=get_project_root() / 'conf' / 'settings.py',
destination=get_project_root() / 'local_test',
runserver=True,
extra_replacements={
'__DEBUG_ENABLED__': '1',
},
)
@cli.command()
def diffsettings():
"""
Run "diffsettings" manage command against a "local_test" YunoHost installation.
"""
destination = get_project_root() / 'local_test'
create_local_test(
django_settings_path=get_project_root() / 'conf' / 'settings.py',
destination=destination,
runserver=False,
extra_replacements={
'__DEBUG_ENABLED__': '1',
},
)
app_path = destination / 'opt_yunohost'
verbose_check_call(
sys.executable,
app_path / 'manage.py',
'diffsettings',
cwd=app_path,
)
def main():
print_version(djfritz_ynh)
if len(sys.argv) >= 2:
# Check if we can just pass a command call to origin CLI:
command = sys.argv[1]
command_map = {
'test': _run_django_test_cli,
'tox': run_tox,
'coverage': run_coverage,
}
if real_func := command_map.get(command):
real_func(argv=sys.argv, exit_after_run=True)
console = Console()
rich_traceback_install(
width=console.size.width, # full terminal width
show_locals=True,
suppress=[click],
max_frames=2,
)
print('Execute Click CLI')
cli()

3
djfritz_ynh/constants.py Normal file
View file

@ -0,0 +1,3 @@
CLI_EPILOG = 'Project Homepage: https://github.com/YunoHost-Apps/django-fritzconnection_ynh'

View file

@ -0,0 +1,70 @@
import os
import sys
import unittest.util
from pathlib import Path
import django
from bx_py_utils.test_utils.deny_requests import deny_any_real_request
from cli_base.cli_tools.verbosity import MAX_LOG_LEVEL, setup_logging
from django_yunohost_integration.local_test import CreateResults, create_local_test
from django_yunohost_integration.path_utils import get_project_root
from rich import print # noqa
from typeguard import install_import_hook
# Check type annotations via typeguard in all tests:
install_import_hook(packages=('djfritz_ynh',))
def pre_configure_tests() -> None:
print(f'Configure unittests via "load_tests Protocol" from {Path(__file__).relative_to(Path.cwd())}')
# Hacky way to display more "assert"-Context in failing tests:
_MIN_MAX_DIFF = unittest.util._MAX_LENGTH - unittest.util._MIN_DIFF_LEN
unittest.util._MAX_LENGTH = int(os.environ.get('UNITTEST_MAX_LENGTH', 300))
unittest.util._MIN_DIFF_LEN = unittest.util._MAX_LENGTH - _MIN_MAX_DIFF
# Deny any request via docket/urllib3 because tests they should mock all requests:
deny_any_real_request()
# Display DEBUG logs in tests:
setup_logging(verbosity=MAX_LOG_LEVEL)
def setup_ynh_tests() -> None:
# Import after "install_import_hook" to check type annotations:
import djfritz_ynh
os.environ['DJANGO_SETTINGS_MODULE'] = 'settings'
print('Compile YunoHost files...')
result: CreateResults = create_local_test(
django_settings_path=get_project_root() / 'conf' / 'settings.py',
destination=get_project_root() / 'local_test',
runserver=False,
extra_replacements={
'__DEBUG_ENABLED__': '0', # "1" or "0" string
'__LOG_LEVEL__': 'INFO',
'__ADMIN_EMAIL__': 'foo-bar@test.tld',
'__DEFAULT_FROM_EMAIL__': 'django_app@test.tld',
},
)
print('Local test files created:')
print(result)
data_dir = str(result.data_dir_path)
if data_dir not in sys.path:
sys.path.insert(0, data_dir)
django.setup()
os.chdir(Path(djfritz_ynh.__file__).parent)
def load_tests(loader, tests, pattern):
"""
Use unittest "load_tests Protocol" as a hook to setup test environment before running tests.
https://docs.python.org/3/library/unittest.html#load-tests-protocol
"""
pre_configure_tests()
return loader.discover(start_dir=Path(__file__).parent, pattern=pattern)

View file

@ -0,0 +1,169 @@
from unittest.mock import patch
from axes.models import AccessLog
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.template.defaulttags import CsrfTokenNode
from django.test import override_settings
from django.test.testcases import TestCase
from django.urls.base import reverse
from django_yunohost_integration.test_utils import generate_basic_auth
@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'
assert str(settings.DATA_DIR_PATH).endswith('/local_test/opt_yunohost'), f'{settings.DATA_DIR_PATH=}'
assert str(settings.INSTALL_DIR_PATH).endswith('/local_test/var_www'), f'{settings.INSTALL_DIR_PATH=}'
assert str(settings.LOG_FILE_PATH).endswith(
'/local_test/var_log_django-fritzconnection.log'
), f'{settings.LOG_FILE_PATH=}'
assert settings.ROOT_URLCONF == 'urls'
def test_config_panel_settings(self):
# config_panel.toml settings, set via tests.conftest.pytest_configure():
assert settings.DEBUG_ENABLED == '0' and settings.DEBUG is False
assert settings.LOG_LEVEL == 'INFO'
assert settings.ADMIN_EMAIL == 'foo-bar@test.tld'
assert settings.DEFAULT_FROM_EMAIL == 'django_app@test.tld'
def test_auth(self):
assert settings.PATH_URL == 'app_path'
self.assertEqual(reverse('admin:index'), '/app_path/admin/')
# SecurityMiddleware should redirects all non-HTTPS requests to HTTPS:
assert settings.SECURE_SSL_REDIRECT is True
response = self.client.get('/app_path/admin/', secure=False)
self.assertRedirects(
response,
status_code=301, # permanent redirect
expected_url='https://testserver/app_path/admin/',
fetch_redirect_response=False,
)
response = self.client.get('/app_path/admin/', secure=True)
self.assertRedirects(
response,
expected_url='/app_path/admin/login/?next=%2Fapp_path%2Fadmin%2F',
fetch_redirect_response=False,
)
def test_create_unknown_user(self):
assert User.objects.count() == 0
self.client.cookies['SSOwAuthUser'] = 'test'
with patch.object(CsrfTokenNode, 'render', return_value='MockedCsrfTokenNode'):
response = self.client.get(
path='/app_path/admin/',
HTTP_REMOTE_USER='test',
HTTP_AUTH_USER='test',
HTTP_AUTHORIZATION='basic dGVzdDp0ZXN0MTIz',
secure=True,
)
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: conf.setup_user.setup_project_user
assert user.is_superuser is False
self.assert_html_parts(
response,
parts=(
'<h1>Site administration</h1>',
'<strong>test</strong>',
),
)
# TODO: assert_html_response_snapshot(response, query_selector='#container', validate=False)
def test_wrong_auth_user(self):
assert User.objects.count() == 0
assert AccessLog.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',
secure=True,
)
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: conf.setup_user.setup_project_user
assert user.is_superuser is False
assert AccessLog.objects.count() == 1
assert response.status_code == 403 # Forbidden
def test_wrong_cookie(self):
assert User.objects.count() == 0
assert AccessLog.objects.count() == 0
self.client.cookies['SSOwAuthUser'] = 'foobar' # <<< wrong user name
response = self.client.get(
path='/app_path/',
HTTP_REMOTE_USER='test',
HTTP_AUTH_USER='test',
HTTP_AUTHORIZATION='basic dGVzdDp0ZXN0MTIz',
secure=True,
)
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: conf.setup_user.setup_project_user
assert user.is_superuser is False
assert AccessLog.objects.count() == 1
assert response.status_code == 403 # Forbidden
def test_wrong_authorization_user(self):
assert User.objects.count() == 0
self.client.cookies['SSOwAuthUser'] = 'test'
response = self.client.get(
path='/app_path/',
HTTP_REMOTE_USER='test',
HTTP_AUTH_USER='test',
HTTP_AUTHORIZATION=generate_basic_auth(
username='foobar', # <<< wrong user name
password='test123',
),
secure=True,
)
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: conf.setup_user.setup_project_user
assert user.is_superuser is False
assert AccessLog.objects.count() == 1
assert response.status_code == 403 # Forbidden

View file

@ -0,0 +1,10 @@
from bx_py_utils.test_utils.unittest_utils import BaseDocTests
import djfritz_ynh
class DocTests(BaseDocTests):
def test_doctests(self):
self.run_doctests(
modules=(djfritz_ynh,),
)

View file

@ -0,0 +1,89 @@
import tomllib
from bx_django_utils.filename import clean_filename
from bx_py_utils.path import assert_is_dir, assert_is_file
from django.test.testcases import TestCase
from django_tools.unittest_utils.project_setup import check_editor_config
from django_yunohost_integration.path_utils import get_project_root
from djfritz import __version__ as upstream_version
from djfritz_ynh import __version__ as ynh_pkg_version
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} !')
class ProjectSetupTestCase(TestCase):
@classmethod
def setUpClass(cls):
super().setUpClass()
manifest_path = get_project_root() / 'manifest.toml'
assert_is_file(manifest_path)
cls.manifest_cfg = tomllib.loads(manifest_path.read_text(encoding='UTF-8'))
def test_version(self):
assert ynh_pkg_version.startswith(
upstream_version
), f'{ynh_pkg_version=} does not start with {upstream_version=}'
self.assertIn('+ynh', ynh_pkg_version)
# pyproject.toml needs a PEP 440 conform version and used "+ynh"
# the YunoHost syntax is: "~ynh", just "convert this:
manifest_version = ynh_pkg_version.replace('+', '~')
self.assertEqual(self.manifest_cfg['version'], manifest_version)
def test_screenshot_filenames(self):
"""
https://forum.yunohost.org/t/yunohost-bot-cant-handle-spaces-in-screenshots/19483
"""
screenshot_path = get_project_root() / 'doc' / 'screenshots'
assert_is_dir(screenshot_path)
renamed = []
for file_path in screenshot_path.iterdir():
file_name = file_path.name
if file_name.startswith('.'):
continue
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)}'
def test_check_editor_config(self):
check_editor_config(package_root=get_project_root())
def test_manifest_toml(self):
self.assertEqual(self.manifest_cfg['packaging_format'], 2)
self.assertEqual(
set(self.manifest_cfg['install'].keys()),
{
'admin',
'admin_email',
'debug_enabled',
'default_from_email',
'domain',
'init_main_permission',
'log_level',
'path',
},
)
self.assertEqual(
set(self.manifest_cfg['resources'].keys()),
{
'apt',
'data_dir',
'database',
'install_dir',
'permissions',
'ports',
'system_user',
},
)

View file

@ -0,0 +1,38 @@
from pathlib import Path
from bx_py_utils.auto_doc import assert_readme_block
from django_yunohost_integration.path_utils import get_project_root
from manageprojects.test_utils.click_cli_utils import invoke_click
from manageprojects.tests.base import BaseTestCase
from djfritz_ynh.cli.dev import cli
from djfritz_ynh.constants import CLI_EPILOG
def assert_cli_help_in_readme(text_block: str, marker: str, readme_path: Path):
text_block = text_block.replace(CLI_EPILOG, '')
text_block = f'```\n{text_block.strip()}\n```'
assert_readme_block(
readme_path=readme_path,
text_block=text_block,
start_marker_line=f'[comment]: <> (✂✂✂ auto generated {marker} start ✂✂✂)',
end_marker_line=f'[comment]: <> (✂✂✂ auto generated {marker} end ✂✂✂)',
)
class ReadmeTestCase(BaseTestCase):
def test_main_help(self):
stdout = invoke_click(cli, '--help')
self.assert_in_content(
got=stdout,
parts=(
'Usage: ./dev-cli.py [OPTIONS] COMMAND [ARGS]...',
' local-test ',
CLI_EPILOG,
),
)
assert_cli_help_in_readme(
text_block=stdout,
marker='help',
readme_path=get_project_root() / 'doc' / 'ADMIN.md',
)

View file

@ -1,130 +1,60 @@
## Settings and upgrades
Almost everything related to django-fritzconnection's configuration is handled in a `"../conf/settings.py"` file.
You can edit the file `/var/www/django-fritzconnection/app/local_settings.py` to enable or disable features.
You can edit the file `/home/yunohost.app/django-fritzconnection/local_settings.py` to enable or disable features.
Test sending emails:
Test sending emails, e.g.:
```bash
ssh admin@yourdomain.tld
root@yunohost:~# cd /var/www/django-fritzconnection/
root@yunohost:/var/www/django-fritzconnection# source venv/bin/activate
(venv) root@yunohost:/var/www/django-fritzconnection# ./app/manage.py sendtestemail --admins
root@yunohost:~# /home/yunohost.app/django-fritzconnection/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:
How to debug a django YunoHost app, take a look into:
```bash
(venv) root@yunohost:/var/www/django-fritzconnection# ./app/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'),)" >> /var/www/django-fritzconnection/app/local_settings.py
```
To check the effective settings, run this:
```bash
(venv) root@yunohost:/var/www/django-fritzconnection# ./app/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-fritzconnection
```
Backup / remove / restore cycle, e.g.:
```bash
yunohost backup create --apps django-fritzconnection
yunohost backup list
archives:
- django-fritzconnection-pre-upgrade1
- 20201223-163434
yunohost app remove django-fritzconnection
yunohost backup restore 20201223-163434 --apps django-fritzconnection
```
Debug installation, e.g.:
```bash
root@yunohost:~# ls -la /var/www/django-fritzconnection/
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 /var/www/django-fritzconnection/
total 58
drwxr-xr-x 5 django-fritzconnection django-fritzconnection 11 Dec 8 08:39 .
drwxr-xr-x 3 root root 3 Dec 8 08:36 ..
-rw-r--r-- 1 django-fritzconnection django-fritzconnection 460 Dec 8 08:39 gunicorn.conf.py
-rw-r--r-- 1 django-fritzconnection django-fritzconnection 0 Dec 8 08:39 local_settings.py
-rwxr-xr-x 1 django-fritzconnection django-fritzconnection 274 Dec 8 08:39 manage.py
-rw-r--r-- 1 django-fritzconnection django-fritzconnection 171 Dec 8 08:39 secret.txt
drwxr-xr-x 6 django-fritzconnection django-fritzconnection 6 Dec 8 08:37 venv
-rw-r--r-- 1 django-fritzconnection django-fritzconnection 115 Dec 8 08:39 wsgi.py
-rw-r--r-- 1 django-fritzconnection django-fritzconnection 4737 Dec 8 08:39 settings.py
root@yunohost:~# cd /var/www/django-fritzconnection/
root@yunohost:/var/www/django-fritzconnection# source venv/bin/activate
(venv) root@yunohost:/var/www/django-fritzconnection# ./manage.py check
django-fritzconnection v0.8.2 (Django v2.2.17)
DJANGO_SETTINGS_MODULE='settings'
PROJECT_PATH:/var/www/django-fritzconnection/venv/lib/python3.7/site-packages
BASE_PATH:/var/www/django-fritzconnection
System check identified no issues (0 silenced).
root@yunohost:~# tail -f /var/log/django-fritzconnection/django-fritzconnection.log
root@yunohost:~# cat /etc/systemd/system/django-fritzconnection.service
root@yunohost:~# systemctl reload-or-restart django-fritzconnection
root@yunohost:~# journalctl --unit=django-fritzconnection --follow
```
* https://github.com/YunoHost-Apps/django-fritzconnection_ynh#developer-info
## local test
For quicker developing of django-fritzconnection in the context of YunoHost app,
For quicker developing of djfritz_ynh 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-fritzconnection_ynh.git
~$ cd django-fritzconnection_ynh/
~/django-fritzconnection_ynh$ make
install-poetry install or update poetry
install install django-fritzconnection via poetry
update update the sources and installation
local-test Run local_test.py to run django-fritzconnection_ynh locally
~/django-fritzconnection_ynh$ make install-poetry
~/django-fritzconnection_ynh$ make install
~/django-fritzconnection_ynh$ make local-test
~$ git clone https://github.com/YunoHost-Apps/django-fritzconnection.git
~$ cd djfritz_ynh/
~/django-fritzconnection$ ./dev-cli.py --help
```
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/`
The output will looks like:
[comment]: <> (✂✂✂ auto generated help start ✂✂✂)
```
Usage: ./dev-cli.py [OPTIONS] COMMAND [ARGS]...
╭─ Options ────────────────────────────────────────────────────────────────────────────────────────╮
│ --help Show this message and exit. │
╰──────────────────────────────────────────────────────────────────────────────────────────────────╯
╭─ Commands ───────────────────────────────────────────────────────────────────────────────────────╮
│ check-code-style Check code style by calling darker + flake8 │
│ coverage Run tests and show coverage report. │
│ diffsettings Run "diffsettings" manage command against a "local_test" YunoHost │
│ installation. │
│ fix-code-style Fix code style of all your_cool_package source code files via darker │
│ install Run pip-sync and install 'djfritz_ynh' via pip as editable. │
│ local-test Build a "local_test" YunoHost installation and start the Django dev. │
│ server against it. │
│ mypy Run Mypy (configured in pyproject.toml) │
│ pip-audit Run pip-audit check against current requirements files │
│ publish Build and upload this project to PyPi │
│ test Compile YunoHost files and run Django unittests │
│ tox Run tox │
│ update Update "requirements*.txt" dependencies files │
│ update-test-snapshot-files Update all test snapshot files (by remove and recreate all snapshot │
│ files) │
│ version Print version and exit │
╰──────────────────────────────────────────────────────────────────────────────────────────────────╯
```
[comment]: <> (✂✂✂ auto generated help end ✂✂✂)

View file

@ -1,13 +1,10 @@
Web based FritzBox management using Python/Django and the great [fritzconnection](https://github.com/kbr/fritzconnection) library.
[![tests](https://github.com/YunoHost-Apps/django-fritzconnection_ynh/actions/workflows/tests.yml/badge.svg?branch=main)](https://github.com/YunoHost-Apps/django-fritzconnection_ynh/actions/workflows/tests.yml)
[![codecov](https://codecov.io/github/jedie/djfritz_ynh/branch/main/graph/badge.svg)](https://app.codecov.io/github/jedie/djfritz_ynh)
[![djfritz_ynh @ PyPi](https://img.shields.io/pypi/v/djfritz_ynh?label=djfritz_ynh%20%40%20PyPi)](https://pypi.org/project/djfritz_ynh/)
[![Python Versions](https://img.shields.io/pypi/pyversions/djfritz_ynh)](https://github.com/YunoHost-Apps/django-fritzconnection_ynh/blob/main/pyproject.toml)
[![License GPL-3.0-or-later](https://img.shields.io/pypi/l/djfritz_ynh)](https://github.com/YunoHost-Apps/django-fritzconnection_ynh/blob/main/LICENSE)
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-fritzconnection_ynh/actions/workflows/pytest.yml/badge.svg?branch=master)](https://github.com/YunoHost-Apps/django-fritzconnection_ynh/actions/workflows/pytest.yml) [![YunoHost apps package linter](https://github.com/YunoHost-Apps/django-fritzconnection_ynh/actions/workflows/package_linter.yml/badge.svg)](https://github.com/YunoHost-Apps/django-fritzconnection_ynh/actions/workflows/package_linter.yml) [![Coverage Status on codecov.io](https://codecov.io/gh/YunoHost-Apps/django-fritzconnection_ynh/branch/master/graph/badge.svg)](https://codecov.io/gh/YunoHost-Apps/django-fritzconnection_ynh)
![django-fritzconnection @ PyPi](https://img.shields.io/pypi/v/django-fritzconnection?label=django-fritzconnection%20%40%20PyPi)
![Python Versions](https://img.shields.io/pypi/pyversions/django-fritzconnection)
![License GPL V3+](https://img.shields.io/pypi/l/django-fritzconnection)
Pull requests welcome ;)

25
local_settings_source.py Normal file
View file

@ -0,0 +1,25 @@
# This file will be copied to the "local test" files, to overwrite Django settings
import os
print('Load local settings file:', __file__)
ENV_TYPE = os.environ.get('ENV_TYPE', None)
print(f'ENV_TYPE: {ENV_TYPE!r}')
if ENV_TYPE == 'local':
print(f'Activate settings overwrite by {__file__}')
SECURE_SSL_REDIRECT = False # Don't redirect http to https
SERVE_FILES = True # May used in urls.py
AUTH_PASSWORD_VALIDATORS = [] # accept all passwords
ALLOWED_HOSTS = ['127.0.0.1', 'localhost'] # For local dev. server
CACHES = { # Setup a working cache, without Redis ;)
'default': {
'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
'LOCATION': 'unique-snowflake',
},
}
elif ENV_TYPE == 'test':
SILENCED_SYSTEM_CHECKS = ['security.W018'] # tests runs with DEBUG=True
ALLOWED_HOSTS = [] # For unittests (Django's setup_test_environment() will add 'testserver')

11
manage_local_test.py Normal file
View file

@ -0,0 +1,11 @@
#!.venv/bin/python3
"""
Call the "manage.py" from the local test environment.
"""
from django_yunohost_integration.local_test import run_local_test_manage
if __name__ == '__main__':
run_local_test_manage()

View file

@ -1,66 +1,112 @@
#:schema https://raw.githubusercontent.com/YunoHost/apps/master/schemas/manifest.v2.schema.json
# https://yunohost.org/en/packaging_manifest
packaging_format = 2
id = "django-fritzconnection"
name = "django-fritzconnection"
description.en = "Web based FritzBox management using Python/Django."
version = "0.2.0~ynh3"
version = "0.3.0~ynh4"
maintainers = ["Jens Diemer"]
[upstream]
license = "GPL-3.0"
code = "https://github.com/jedie/django-fritzconnection"
# https://yunohost.org/en/packaging_manifest#upstream-section
license = "GPL-3.0-or-later"
# website = "https://github.com/YunoHost-Apps/django-fritzconnection_ynh" # If the app has no proper website, just remove the 'website' key entirely
admindoc = "https://github.com/YunoHost-Apps/django-fritzconnection_ynh"
userdoc = "https://github.com/jedie/django-fritzconnection"
code = "https://github.com/YunoHost-Apps/django-fritzconnection_ynh"
[integration]
yunohost = ">= 11.2.12"
# https://yunohost.org/en/packaging_manifest#integration-section
yunohost = ">=11.2"
architectures = "all"
multi_instance = true
ldap = true
sso = true
disk = "100M"
ram.build = "500M"
ram.runtime = "100M"
disk = "50M" # **estimate** minimum disk requirement. e.g. 20M, 400M, 1G, ...
ram.build = "50M" # **estimate** minimum ram requirement. e.g. 50M, 400M, 1G, ...
ram.runtime = "50M" # **estimate** minimum ram requirement. e.g. 50M, 400M, 1G, ...
[install]
# https://yunohost.org/en/packaging_manifest#install-questions
[install.domain]
# this is a generic question - ask strings are automatically handled by Yunohost's core
type = "domain"
[install.path]
# this is a generic question - ask strings are automatically handled by Yunohost's core
# setting $path and template variable __PATH__
type = "path"
default = "/django-fritzconnection"
default = "/djfritz_ynh"
[install.admin]
# this is a generic question - ask strings are automatically handled by Yunohost's core
type = "user"
default = "admin"
[install.init_main_permission]
type = "group"
default = "visitors"
default = "admins"
[install.default_from_email] # __DEFAULT_FROM_EMAIL__
ask.en = "Default email address to use for various automated emails."
type = "email"
example = "admin@example.com"
[install.admin_email] # __ADMIN_EMAIL__
ask.en = "EMail address for error emails."
type = "email"
example = "admin@example.com"
[install.debug_enabled] # __DEBUG_ENABLED__ will be set to "0" or "1" string
ask.en = "Should be never enabled in production!"
type = "boolean"
[install.log_level] # __LOG_LEVEL__
ask.en = "Logging level"
type = "select"
choices = ["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"]
default = "WARNING"
[install.admin]
type = "user"
[resources]
[resources.system_user]
# This will provision/deprovision a unix system user
[resources.install_dir]
# https://yunohost.org/en/packaging_apps_resources#install-dir
# This will create/remove the install dir as /var/www/$app/
# and store the corresponding setting $install_dir and template variable __INSTALL_DIR__
[resources.data_dir]
# https://yunohost.org/en/packaging_apps_resources#data-dir
# This will create/remove the data dir as /home/yunohost.app/$app/
# and store the corresponding setting $data_dir and template variable __DATA_DIR__
[resources.permissions]
# https://yunohost.org/en/packaging_apps_resources#permissions
# This will configure SSOwat permission for $domain/$path/
# The initial allowed group of user is configured via the init_main_permission question (public=visitors, private=all_users)
main.url = "/"
[resources.ports]
# https://yunohost.org/en/packaging_apps_resources#ports
# This will pick a random port for reverse-proxying and store it as the $port setting
[resources.apt]
packages = [
"build-essential",
"python3-dev",
"python3-pip",
"python3-venv",
"git",
"libpq-dev",
"postgresql",
"postgresql-contrib",
"redis-server",
]
# https://yunohost.org/en/packaging_apps_resources#apt
# This will automatically install/uninstall the following apt packages
packages = "build-essential, python3-dev, python3-pip, python3-venv, git, libffi-dev, libpq-dev, postgresql, postgresql-contrib, redis-server, checkinstall, pkg-config, libssl-dev, openssl"
[resources.database]
# https://yunohost.org/en/packaging_apps_resources#database
# This will automatically provision/deprovison a Postgres DB
# and store the corresponding credentials in settings $db_user, $db_name, $db_pwd
type = "postgresql"

1462
poetry.lock generated

File diff suppressed because it is too large Load diff

View file

@ -1,49 +1,94 @@
[tool.poetry]
name = "django-fritzconnection_ynh"
version = "0.2.0+ynh2"
description = "Test django-fritzconnection_ynh via local_test.py"
authors = ["JensDiemer <git@jensdiemer.de>"]
license = "GPL"
homepage = "https://github.com/YunoHost-Apps/django-fritzconnection_ynh"
[project]
name = "djfritz_ynh"
dynamic = ["version"]
description = "Web based FritzBox management using Python/Django."
license = {text = "GPL-3.0-or-later"}
readme = "README.md"
authors = [
{name = 'Jens Diemer', email = 'git@jensdiemer.de'}
]
requires-python = ">=3.11"
dependencies = [
"django-fritzconnection", # https://github.com/jedie/django-fritzconnection
#
# extras "ynh" will install: gunicorn, psycopg2, django-redis and django-axes
# see: https://github.com/YunoHost-Apps/django_yunohost_integration/blob/main/pyproject.toml
"django_yunohost_integration[ynh]", # https://github.com/YunoHost-Apps/django_yunohost_integration
#
"cli-base-utilities", # https://github.com/jedie/cli-base-utilities
]
[project.optional-dependencies]
dev = [
"bx_django_utils", # https://github.com/boxine/bx_django_utils
"beautifulsoup4", # https://pypi.org/project/beautifulsoup4/
"manageprojects", # https://github.com/jedie/manageprojects
"pip-tools", # https://github.com/jazzband/pip-tools/
"tblib", # https://github.com/ionelmc/python-tblib
"tox", # https://github.com/tox-dev/tox
"coverage", # https://github.com/nedbat/coveragepy
"autopep8", # https://github.com/hhatto/autopep8
"pyupgrade", # https://github.com/asottile/pyupgrade
"flake8", # https://github.com/pycqa/flake8
"flake8-bugbear", # https://github.com/PyCQA/flake8-bugbear
"pyflakes", # https://github.com/PyCQA/pyflakes
"codespell", # https://github.com/codespell-project/codespell
"EditorConfig", # https://github.com/editorconfig/editorconfig-core-py
"pip-audit", # https://github.com/pypa/pip-audit
"mypy", # https://github.com/python/mypy
"twine", # https://github.com/pypa/twine
"typeguard", # https://github.com/agronholm/typeguard/
[tool.poetry.urls]
"Bug Tracker" = "https://github.com/YunoHost-Apps/django-fritzconnection_ynh/issues"
# https://github.com/akaihola/darker
# https://github.com/ikamensh/flynt
# https://github.com/pycqa/isort
# https://github.com/pygments/pygments
"darker[flynt, isort, color]",
[tool.poetry.dependencies]
python = ">=3.7,<4.0.0" # TODO: Update to >=3.8 after YunoHost updates to Bullseye
django-fritzconnection = ">=0.2.0"
# Work-a-round for: https://github.com/jazzband/pip-tools/issues/1866
# see also: https://github.com/jazzband/pip-tools/issues/994#issuecomment-1321226661
# backports.tarfile is needed for python <3.12
'backports.tarfile', # via jaraco-context -> keyring -> twine
]
# extras "ynh" will install: gunicorn, psycopg2, django-redis and django-axes
# see: https://github.com/YunoHost-Apps/django_yunohost_integration/blob/main/pyproject.toml
django_yunohost_integration = {version = ">=0.4.1", extras = ["ynh"]} # https://github.com/YunoHost-Apps/django_yunohost_integration
[project.urls]
Documentation = "https://github.com/YunoHost-Apps/django-fritzconnection_ynh"
Source = "https://github.com/YunoHost-Apps/django-fritzconnection_ynh"
[tool.poetry.dev-dependencies]
bx_py_utils = "*" # https://github.com/boxine/bx_py_utils
bx_django_utils = "*" # https://github.com/boxine/bx_django_utils
tox = "*"
pytest = "*"
pytest-cov = "*"
pytest-django = "*"
pytest-darker = "*" # https://github.com/akaihola/pytest-darker
coveralls = "*"
isort = "*"
flake8 = "*"
EditorConfig = "*" # https://github.com/editorconfig/editorconfig-core-py
safety = "*" # https://github.com/pyupio/safety
requests = "*" # https://github.com/psf/requests
packaging = "*" # https://github.com/pypa/packagi
[project.scripts]
djfritz_ynh_app = "djfritz_ynh.__main__:main"
djfritz_ynh_dev = "djfritz_ynh.cli.dev:main"
[ynh-integration]
local_settings_source= "local_settings_source.py"
[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"
requires = ["setuptools>=61.0", "setuptools_scm>=7.1"]
build-backend = "setuptools.build_meta"
[tool.setuptools.packages.find]
where = ["."]
include = ["djfritz_ynh*"]
[tool.setuptools.dynamic]
version = {attr = "djfritz_ynh.__version__"}
[tool.cli_base.pip_audit]
# https://github.com/jedie/cli-base-utilities/blob/main/docs/pip_audit.md
requirements=["requirements.dev.txt"]
strict=true
require_hashes=true
ignore-vuln=[]
[tool.darker]
src = ['.']
# YunoHost apps still use "master" istead of "main", isn't it?
revision = "origin/master..."
line_length = 100
verbose = true
line_length = 119
color = true
skip_string_normalization = true
diff = false
check = false
@ -59,54 +104,85 @@ log_level = "INFO"
# https://pycqa.github.io/isort/docs/configuration/config_files/#pyprojecttoml-preferred-format
atomic=true
profile='black'
skip_glob=["*/htmlcov/*","*/migrations/*","*/local_test/*"]
line_length=100
known_first_party=["djfritz","djfritz_project","djfritz_tests"]
skip_glob=[".*", "*/htmlcov/*","*/migrations/*","*/local_test/*"]
known_first_party=['djfritz', 'djfritz_ynh']
line_length=119
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 local_test 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
--doctest-modules
--failed-first
--new-first
"""
[tool.coverage.run]
omit = [".*"]
branch = true
parallel = true
concurrency = ["multiprocessing"]
source = ['.']
command_line = './dev-cli.py test'
disable_warnings = ["couldnt-parse"]
[tool.coverage.report]
omit = ['.*', '*/tests/*']
skip_empty = true
fail_under = 10
show_missing = true
exclude_lines = [
'if self.debug:',
'pragma: no cover',
'raise NotImplementedError',
'if __name__ == .__main__.:',
]
[tool.tox]
# https://tox.readthedocs.io/en/latest/example/basic.html#pyproject-toml-tox-legacy-ini
[tool.tox] # https://tox.wiki/en/latest/config.html#pyproject-toml
legacy_tox_ini = """
[tox]
isolated_build = True
envlist = py{37,38,39,310}
envlist = py{312,311}
skip_missing_interpreters = True
[testenv]
passenv = *
whitelist_externals = make
skip_install = true
commands_pre =
pip install -U pip-tools
pip-sync requirements.dev.txt
commands =
make pytest
{envpython} -m coverage run --context='{envname}'
{envpython} -m coverage combine --append
{envpython} -m coverage xml
{envpython} -m coverage report
"""
[tool.mypy]
warn_unused_configs = true
ignore_missing_imports = true
allow_redefinition = true # https://github.com/python/mypy/issues/7165
show_error_codes = true
plugins = []
exclude = ['.venv', 'tests']
[manageprojects] # https://github.com/jedie/manageprojects
initial_revision = "2281f4b"
initial_date = 2023-04-02T17:40:58+02:00
cookiecutter_template = "https://github.com/jedie/cookiecutter_templates/"
cookiecutter_directory = "yunohost_django_package"
applied_migrations = [
]
[manageprojects.cookiecutter_context.cookiecutter]
project_name = "django-fritzconnection"
full_name = "Jens Diemer"
github_username = "jedie"
author_email = "git@jensdiemer.de"
upstream_pkg_name = "djfritz"
upstream_url = "https://github.com/jedie/django-fritzconnection"
upstream_pkg_app_name = "djfritz"
upstream_pkg_project_name = "djfritz_project"
ynh_app_pkg_name = "djfritz_ynh"
ynh_app_url = "https://github.com/YunoHost-Apps/django-fritzconnection_ynh"
bug_tracker_url = "https://github.com/jedie/django-fritzconnection/issues"
upstream_version = "0.3.0"
ynh_version = "+ynh3"
package_description = "Web based FritzBox management using Python/Django."
license = "GPL-3.0-or-later"
_template = "https://github.com/jedie/cookiecutter_templates/"

1194
requirements.dev.txt Normal file

File diff suppressed because it is too large Load diff

View file

@ -1,36 +1,182 @@
#!/bin/bash
#=================================================
# COMMON VARIABLES
# RETRIEVE ARGUMENTS FROM THE MANIFEST
#=================================================
# Transfer the main SSO domain to the App:
ynh_current_host=$(cat /etc/yunohost/current_host)
__YNH_CURRENT_HOST__=${ynh_current_host}
#=================================================
# ARGUMENTS FROM CONFIG PANEL
#=================================================
# 'debug_enabled' -> '__DEBUG_ENABLED__' -> settings.DEBUG
debug_enabled="0" # "1" or "0" string
# 'log_level' -> '__LOG_LEVEL__' -> settings.LOG_LEVEL
log_level="WARNING"
# 'admin_email' -> '__ADMIN_EMAIL__' add in settings.ADMINS
admin_email="${admin}@${domain}"
# 'default_from_email' -> '__DEFAULT_FROM_EMAIL__' -> settings.DEFAULT_FROM_EMAIL
default_from_email="${app}@${domain}"
#=================================================
# SET CONSTANTS
#=================================================
# e.g.: point pip cache to: /home/yunohost.app/$app/.cache/
XDG_CACHE_HOME="$data_dir/.cache/"
log_path=/var/log/$app
log_file="${log_path}/${app}.log"
#=================================================
# PERSONAL HELPERS
# HELPERS
#=================================================
_venv_install() {
ynh_exec_as "$app" python3 -m venv --upgrade "$install_dir/venv"
venvpy="$install_dir/venv/bin/python3"
ynh_exec_as "$app" "$venvpy" -m pip install --upgrade --no-cache-dir pip
#==================================================================================
# myynh_install_python() Borrowed from:
# https://github.com/YunoHost-Apps/homeassistant_ynh/blob/master/scripts/_common.sh
# Until we get a newer Python in YunoHost, see:
# https://forum.yunohost.org/t/use-newer-python-than-3-9/22568
#==================================================================================
py_required_major=3.11
py_required_version=$(curl -Ls https://www.python.org/ftp/python/ \
| grep '>'$py_required_major | cut -d '/' -f 2 \
| cut -d '>' -f 2 | sort -rV | head -n 1) #3.11.8
ynh_exec_as "$app" "$venvpy" -m pip install setuptools wheel pyyaml
myynh_install_python() {
# Declare an array to define the options of this helper.
local legacy_args=u
local -A args_array=( [p]=python= )
local python
# Manage arguments with getopts
ynh_handle_getopts_args "$@"
# Check python version from APT
local py_apt_version=$(python3 --version | cut -d ' ' -f 2)
# Usefull variables
local python_major=${python%.*}
# Check existing built version of python in /usr/local/bin
if [ -e "/usr/local/bin/python$python_major" ]
then
local py_built_version=$(/usr/local/bin/python$python_major --version \
| cut -d ' ' -f 2)
else
local py_built_version=0
fi
# Compare version
if $(dpkg --compare-versions $py_apt_version ge $python)
then
# APT >= Required
ynh_print_info --message="Using provided python3..."
py_app_version="python3"
else
# Either python already built or to build
if $(dpkg --compare-versions $py_built_version ge $python)
then
# Built >= Required
py_app_version="/usr/local/bin/python${py_built_version%.*}"
ynh_print_info --message="Using already used python3 built version: $py_app_version"
else
# APT < Minimal & Actual < Minimal => Build & install Python into /usr/local/bin
ynh_print_info --message="Building $python (may take a while)..."
# Store current direcotry
local MY_DIR=$(pwd)
# Create a temp direcotry
tmpdir="$(mktemp --directory)"
cd "$tmpdir"
# Download
wget --output-document="Python-$python.tar.xz" \
"https://www.python.org/ftp/python/$python/Python-$python.tar.xz" 2>&1
# Extract
tar xf "Python-$python.tar.xz"
# Install
cd "Python-$python"
./configure --enable-optimizations
ynh_exec_warn_less make -j4
ynh_exec_warn_less make altinstall
# Go back to working directory
cd "$MY_DIR"
# Clean
ynh_secure_remove "$tmpdir"
# Set version
py_app_version="/usr/local/bin/python$python_major"
fi
fi
# Save python version in settings
ynh_app_setting_set --app=$app --key=python --value="$python"
# Print some version information:
ynh_print_info --message="Python version: $($py_app_version -VV)"
ynh_print_info --message="Pip version: $($py_app_version -m pip -V)"
}
#==================================================================================
#==================================================================================
myynh_setup_python_venv() {
# Install Python if needed:
myynh_install_python --python="$py_required_version"
# Create a virtualenv with python installed by myynh_install_python():
# Skip pip because of: https://github.com/YunoHost/issues/issues/1960
ynh_exec_as $app $py_app_version -m venv --clear --upgrade-deps "$data_dir/venv"
# Print some version information:
ynh_print_info --message="venv Python version: $($data_dir/venv/bin/python3 -VV)"
ynh_print_info --message="venv Pip version: $($data_dir/venv/bin/python3 -m pip -V)"
# run source in a 'sub shell'
(
set +o nounset
source "$data_dir/venv/bin/activate"
set -o nounset
set -x
ynh_exec_as $app $data_dir/venv/bin/pip3 install --upgrade pip wheel setuptools
ynh_exec_as $app $data_dir/venv/bin/pip3 install --no-deps -r "$data_dir/requirements.txt"
)
}
_build_app() {
ynh_exec_as "$app" "$venvpy" -m ensurepip
ynh_exec_as "$app" "$venvpy" -m pip install --upgrade wheel pip setuptools
ynh_exec_as "$app" "$venvpy" -m pip install --no-deps -r "$install_dir/app/requirements.txt"
myynh_setup_log_file() {
(
set -x
mkdir -p "$(dirname "$log_file")"
touch "$log_file"
chown -c -R $app:$app "$log_path"
chmod -c o-rwx "$log_path"
)
}
#=================================================
# EXPERIMENTAL HELPERS
#=================================================
myynh_fix_file_permissions() {
(
set -x
#=================================================
# FUTURE OFFICIAL HELPERS
#=================================================
# /var/www/$app/
chown -c -R "$app:www-data" "$install_dir"
chmod -c o-rwx "$install_dir"
# /home/yunohost.app/$app/
chown -c -R "$app:" "$data_dir"
chmod -c o-rwx "$data_dir"
)
}

View file

@ -1,5 +1,7 @@
#!/bin/bash
#=================================================
# GENERIC START
#=================================================
# IMPORT GENERIC HELPERS
#=================================================
@ -16,33 +18,38 @@ ynh_print_info --message="Declaring files to be backed up..."
# BACKUP THE APP MAIN DIR
#=================================================
# /var/www/$app/
ynh_backup --src_path="$install_dir"
# /home/yunohost.app/$app/
ynh_backup --src_path="$data_dir"
#=================================================
# SYSTEM CONFIGURATION
# BACKUP THE NGINX CONFIGURATION
#=================================================
# Backup the nginx configuration
ynh_backup --src_path="/etc/nginx/conf.d/$domain.d/$app.conf"
# Backup the systemd service unit
ynh_backup --src_path="/etc/systemd/system/$app.service"
# Backup the logrotate configuration
ynh_backup --src_path="/etc/logrotate.d/$app"
#=================================================
# BACKUP VARIOUS FILES
#=================================================
ynh_backup --src_path="/var/log/$app/"
#=================================================
# 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
#=================================================

View file

@ -1,5 +1,7 @@
#!/bin/bash
#=================================================
# GENERIC STARTING
#=================================================
# IMPORT GENERIC HELPERS
#=================================================
@ -7,33 +9,38 @@
source _common.sh
source /usr/share/yunohost/helpers
#=================================================
# STANDARD MODIFICATIONS
#=================================================
# STOP SYSTEMD SERVICE
#=================================================
ynh_script_progression --message="Stopping systemd service '$app'..."
ynh_systemd_action --service_name="$app" --action="stop"
ynh_systemd_action --service_name=$app --action="stop" --log_path="$log_file"
#=================================================
# MODIFY URL IN NGINX CONF
#=================================================
ynh_script_progression --message="Updating NGINX web server configuration..." --weight=1
ynh_script_progression --message="Updating nginx web server configuration..."
ynh_change_url_nginx_config
#=================================================
# SPECIFIC MODIFICATIONS
# UPDATE DJANGO SETTINGS
#=================================================
ynh_script_progression --message="Modify $app config file..."
ynh_script_progression --message="Update $app settings file..." --weight=1
ynh_add_config --template="settings.py" --destination="$install_dir/app/settings.py"
path=$new_path
domain=$new_domain
ynh_add_config --template="settings.py" --destination="$data_dir/settings.py"
#=================================================
# START SYSTEMD SERVICE
#=================================================
ynh_script_progression --message="Starting $app's systemd service..." --weight=1
ynh_script_progression --message="Starting systemd service '$app'..." --weight=5
ynh_systemd_action --service_name="$app" --action="start" --log_path="/var/log/$app/$app.log"
ynh_systemd_action --service_name=$app --action="start" --log_path="$log_file"
#=================================================
# END OF SCRIPT

View file

@ -7,99 +7,147 @@
source _common.sh
source /usr/share/yunohost/helpers
#=================================================
# INITIALIZE AND STORE SETTINGS
#=================================================
# Install parameters are automatically saved as settings
#
# Settings are automatically loaded as bash variables
# in every app script context, therefore typically these will exist:
# - $domain
# - $path
# - $language
# ... etc
#
# Resources defined in the manifest are provisioned prior to this script
# and corresponding settings are also available, such as:
# - $install_dir
# - $port
# - $db_name
# ...
admin_email="$(ynh_user_get_info "$admin" mail)"
ynh_app_setting_set --app="$app" --key=admin_email --value="$admin_email"
#
# $app is the app id (i.e. 'example' for first install,
# or 'example__2', '__3', ... for multi-instance installs)
#
#=================================================
# SETTINGS
#=================================================
ynh_script_progression --message="Storing installation settings..."
# Logging:
log_file="/var/log/$app/$app.log"
ynh_app_setting_set --app=$app --key=log_file --value="$log_file"
# Redis:
redis_db=$(ynh_redis_get_free_db)
ynh_app_setting_set --app="$app" --key=redis_db --value="$redis_db"
ynh_app_setting_set --app=$app --key=redis_db --value="$redis_db"
#-------------------------------------------------
# config_panel.toml settings:
debug_enabled="0"
ynh_app_setting_set --app="$app" --key=debug_enabled --value="$debug_enabled"
log_level="WARNING"
ynh_app_setting_set --app="$app" --key=log_level --value="$log_level"
default_from_email="${app}@${domain}"
ynh_app_setting_set --app="$app" --key=default_from_email --value="$default_from_email"
# App settings:
ynh_app_setting_set --app=$app --key=default_from_email --value="$default_from_email"
ynh_app_setting_set --app=$app --key=admin_email --value="$admin_email"
ynh_app_setting_set --app=$app --key=debug_enabled --value="$debug_enabled"
ynh_app_setting_set --app=$app --key=log_level --value="$log_level"
#=================================================
# DOWNLOAD, CHECK AND UNPACK SOURCE
# CHECK IF THE APP CAN BE INSTALLED WITH THESE ARGS
#=================================================
ynh_script_progression --message="Setting up source files..." --weight=1
ynh_script_progression --message="Validating installation parameters..."
mkdir -p "$install_dir/"{app,public/media,public/static}
mkdir -p "$install_dir/media" "$install_dir/static"
ynh_add_config --template="requirements.txt" --destination="$install_dir/app/requirements.txt"
#=================================================
# SETUP LOG FILE
#=================================================
ynh_script_progression --message="Setup logging..."
ynh_add_config --template="gunicorn.conf.py" --destination="$install_dir/app/gunicorn.conf.py"
myynh_setup_log_file
ynh_add_config --template="manage.py" --destination="$install_dir/app/manage.py"
chmod +x "$install_dir/app/manage.py"
ynh_add_config --template="settings.py" --destination="$install_dir/app/settings.py"
ynh_add_config --template="setup_user.py" --destination="$install_dir/app/setup_user.py"
ynh_add_config --template="urls.py" --destination="$install_dir/app/urls.py"
ynh_add_config --template="wsgi.py" --destination="$install_dir/app/wsgi.py"
touch "$install_dir/app/local_settings.py"
chown -R "$app:www-data" "$install_dir"
mkdir -p "/var/log/$app"
touch "/var/log/$app/$app.log"
chown -R "$app:$app" "/var/log/$app"
# Use logrotate to manage application logfile(s)
ynh_use_logrotate --logfile="$log_file" --specific_user=$app
#=================================================
# PYTHON VIRTUALENV
#=================================================
ynh_script_progression --message="Installing $app..." --weight=10
_venv_install
_build_app
pushd "$install_dir/app"
# Just for debugging:
ynh_exec_as "$app" "$venvpy" ./manage.py diffsettings
ynh_exec_as "$app" "$venvpy" ./manage.py migrate --no-input
ynh_exec_as "$app" "$venvpy" ./manage.py collectstatic --no-input
# Create/update Django superuser (set unusable password, because auth done via SSOwat):
ynh_exec_as "$app" "$venvpy" ./manage.py create_superuser --username="$admin" --email="$(ynh_user_get_info "$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.
ynh_exec_as "$app" "$venvpy" ./manage.py check --deploy || true
popd
ynh_script_progression --message="Create and setup Python virtualenv..." --weight=45
cp ../conf/requirements.txt "$data_dir/requirements.txt"
myynh_setup_python_venv
#=================================================
# SYSTEM CONFIGURATION
#=================================================
ynh_script_progression --message="Adding system configurations related to $app..." --weight=1
# copy config files
# ================================================
ynh_script_progression --message="Create $app configuration files..."
# Create a dedicated NGINX config using the conf/nginx.conf template
ynh_add_nginx_config
ynh_add_config --template="gunicorn.conf.py" --destination="$data_dir/gunicorn.conf.py"
# Create a dedicated systemd config
ynh_add_systemd_config
yunohost service add "$app" --description="Django-fritzconnection server" --log="/var/log/$app/$app.log"
ynh_add_config --template="manage.py" --destination="$data_dir/manage.py"
chmod -c +x "$data_dir/manage.py"
# Use logrotate to manage app-specific logfile(s)
ynh_use_logrotate "$log_file"
ynh_add_config --template="settings.py" --destination="$data_dir/settings.py"
ynh_add_config --template="setup_user.py" --destination="$data_dir/setup_user.py"
ynh_add_config --template="urls.py" --destination="$data_dir/urls.py"
ynh_add_config --template="wsgi.py" --destination="$data_dir/wsgi.py"
touch "$data_dir/local_settings.py"
#=================================================
# START SYSTEMD SERVICE
# MIGRATE / COLLECTSTATIC / CREATEADMIN
#=================================================
ynh_script_progression --message="Starting $app's systemd service..." --weight=1
ynh_script_progression --message="migrate/collectstatic/createadmin..." --weight=10
# Start a systemd service
ynh_systemd_action --service_name="$app" --action="start" --log_path="/var/log/$app/$app.log"
cd "$data_dir" || 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="$(ynh_user_get_info "$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
#=================================================
# INTEGRATE SERVICE IN YUNOHOST
#=================================================
ynh_script_progression --message="Integrating service in YunoHost..."
yunohost service add --description $app $app
#=================================================
# GENERIC FINALIZATION
#=================================================
# SECURE FILES AND DIRECTORIES
#=================================================
ynh_script_progression --message="Set file permissions..."
myynh_fix_file_permissions
#=================================================
# SETUP SYSTEMD
#=================================================
ynh_script_progression --message="Configuring systemd service '$app'..." --weight=5
# https://yunohost.org/en/packaging_apps_helpers#ynh-add-systemd-config
# https://github.com/YunoHost/yunohost/blob/dev/helpers/systemd
ynh_add_systemd_config --service=$app --template="systemd.service"
#=================================================
# Start the app server via systemd
#=================================================
ynh_script_progression --message="Starting systemd service '$app'..." --weight=5
ynh_systemd_action --service_name=$app --action="start" --log_path="$log_file"
#=================================================
# NGINX CONFIGURATION
#=================================================
ynh_script_progression --message="Configuring nginx web server..."
# Create a dedicated nginx config
# https://yunohost.org/en/contribute/packaging_apps/helpers
# https://github.com/YunoHost/yunohost/blob/dev/helpers/nginx
ynh_add_nginx_config "public_path" "port"
#=================================================
# END OF SCRIPT

View file

@ -1,5 +1,7 @@
#!/bin/bash
#=================================================
# GENERIC START
#=================================================
# IMPORT GENERIC HELPERS
#=================================================
@ -8,19 +10,56 @@ source _common.sh
source /usr/share/yunohost/helpers
#=================================================
# REMOVE SYSTEM CONFIGURATIONS
# STANDARD REMOVE
#=================================================
# REMOVE SERVICE FROM ADMIN PANEL
#=================================================
ynh_script_progression --message="Removing system configurations related to $app..." --weight=1
# Remove the service from the list of services known by YunoHost (added from `yunohost service add`)
if ynh_exec_warn_less yunohost service status "$app" >/dev/null; then
yunohost service remove "$app"
# 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
ynh_remove_systemd_config
#=================================================
# STOP PYINVENTORY'S SERVICES
#=================================================
ynh_script_progression --message="Stopping and removing systemd service '$app'..." --weight=5
ynh_remove_systemd_config --service=$app
##=================================================
## REMOVE REDIS DB
##=================================================
ynh_redis_remove_db
#=================================================
# REMOVE APP MAIN DIR
#=================================================
ynh_script_progression --message="Removing app main directory..."
# /var/www/$app/
ynh_secure_remove --file="$install_dir"
# /home/yunohost.app/$app/
ynh_secure_remove --file="$data_dir"
#=================================================
# 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
#=================================================

View file

@ -1,5 +1,7 @@
j#!/bin/bash
#!/bin/bash
#=================================================
# GENERIC START
#=================================================
# IMPORT GENERIC HELPERS
#=================================================
@ -8,61 +10,86 @@ source ../settings/scripts/_common.sh
source /usr/share/yunohost/helpers
#=================================================
# RESTORE THE APP MAIN DIR
# STANDARD RESTORATION STEPS
#=================================================
ynh_script_progression --message="Restoring the app main directory..."
ynh_restore_file --origin_path="$install_dir"
chmod -R o-rwx "$install_dir"
chown -R "$app:www-data" "$install_dir"
# RESTORE THE NGINX CONFIGURATION
#=================================================
# RESTORE THE POSTGRESQL DATABASE
#=================================================
ynh_script_progression --message="Restoring the PostgreSQL database..." --weight=1
ynh_psql_connect_as --user="$db_user" --password="$db_pwd" --database="$db_name" < ./db.sql
#=================================================
# PYTHON VIRTUALENV
# Maybe the backup contains another Python version
#=================================================
ynh_script_progression --message="Updating Python virtualenv and rebuilding $app..." --weight=5
# Always recreate everything fresh with current python version
ynh_secure_remove "$install_dir/venv"
_venv_install
chown -R "$app:www-data" "$install_dir"
_build_app
#=================================================
# RESTORE SYSTEM CONFIGURATIONS
#=================================================
ynh_script_progression --message="Restoring system configurations related to $app..." --weight=1
ynh_script_progression --message="Restoring the NGINX web server configuration..." --weight=1
ynh_restore_file --origin_path="/etc/nginx/conf.d/$domain.d/$app.conf"
ynh_restore_file --origin_path="/etc/systemd/system/$app.service"
systemctl enable "$app.service" --quiet
yunohost service add "$app" --description="Django-fritzconnection server" --log="/var/log/$app/$app.log"
#=================================================
# RESTORE THE APP MAIN DIR
#=================================================
ynh_script_progression --message="Restoring $app main directory..."
ynh_restore_file --origin_path="$install_dir"
ynh_restore_file --origin_path="$data_dir"
ynh_script_progression --message="Set file permissions..."
myynh_fix_file_permissions
#=================================================
# PYTHON VIRTUALENV
# Maybe the backup contains a other Python version
#=================================================
ynh_script_progression --message="Create and setup Python virtualenv..." --weight=45
myynh_setup_python_venv
#=================================================
# RESTORE THE PostgreSQL DATABASE
#=================================================
ynh_script_progression --message="Restoring the PostgreSQL database..." --weight=5
ynh_psql_connect_as --user=$db_user --password=$db_pwd --database=$db_name < ./db.sql
#=================================================
# RESTORE SYSTEMD
#=================================================
ynh_script_progression --message="Restoring the systemd $app 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 --description $app $app
#=================================================
# RESTORE THE LOGROTATE CONFIGURATION
#=================================================
ynh_script_progression --message="Setup logging..."
myynh_setup_log_file
ynh_restore_file --origin_path="/etc/logrotate.d/$app"
#=================================================
# RESTORE VARIOUS FILES
# GENERIC FINALIZATION
#=================================================
ynh_restore_file --origin_path="/var/log/$app/"
# SECURE FILES AND DIRECTORIES
#=================================================
ynh_script_progression --message="Set file permissions..."
myynh_fix_file_permissions
#=================================================
# RELOAD NGINX AND PHP-FPM OR THE APP SERVICE
# GENERIC FINALIZATION
#=================================================
ynh_script_progression --message="Reloading NGINX web server and $app's service..." --weight=1
# START PYINVENTORY
#=================================================
ynh_script_progression --message="Starting systemd service '$app'..." --weight=5
ynh_systemd_action --service_name="$app" --action="start" --log_path="/var/log/$app/$app.log"
ynh_systemd_action --service_name=$app --action="start" --log_path="$log_file"
ynh_systemd_action --service_name=nginx --action=reload
#=================================================
# RELOAD NGINX
#=================================================
ynh_script_progression --message="Reloading nginx web server..."
ynh_systemd_action --service_name="nginx" --action="reload"
#=================================================
# END OF SCRIPT

View file

@ -7,114 +7,112 @@
source _common.sh
source /usr/share/yunohost/helpers
#-------------------------------------------------
# config_panel.toml settings:
if [ -z "$debug_enabled" ]; then
debug_enabled="0"
ynh_app_setting_set --app=$app --key=debug_enabled --value="$debug_enabled"
fi
if [ -z "$log_level" ]; then
log_level="WARNING"
ynh_app_setting_set --app=$app --key=log_level --value="$log_level"
fi
if [ -z "$admin_email" ]; then
admin_email="${admin}@${domain}"
ynh_app_setting_set --app=$app --key=admin_email --value="$admin_email"
fi
if [ -z "$default_from_email" ]; then
default_from_email="${app}@${domain}"
ynh_app_setting_set --app=$app --key=default_from_email --value="$default_from_email"
fi
#=================================================
# STANDARD UPGRADE STEPS
#=================================================
# STOP SYSTEMD SERVICE
#=================================================
ynh_script_progression --message="Stopping $app's systemd service..." --weight=1
ynh_script_progression --message="Stopping systemd service '$app'..." --weight=5
ynh_systemd_action --service_name=$app --action="stop" --log_path="$log_file"
ynh_systemd_action --service_name="$app" --action="stop" --log_path="/var/log/$app/$app.log"
#=================================================
# ENSURE DOWNWARD COMPATIBILITY
# SETUP SYSTEMD
#=================================================
ynh_script_progression --message="Ensuring downward compatibility..." --weight=1
ynh_script_progression --message="Configuring systemd service '$app'..." --weight=5
if [ -z "${debug_enabled:-}" ]; then
debug_enabled="0"
ynh_app_setting_set --app="$app" --key=debug_enabled --value="$debug_enabled"
fi
if [ -z "${log_level:-}" ]; then
log_level="WARNING"
ynh_app_setting_set --app="$app" --key=log_level --value="$log_level"
fi
if [ -z "${admin_email:-}" ]; then
admin_email="${admin}@${domain}"
fi
if [ -z "${default_from_email:-}" ]; then
default_from_email="${app}@${domain}"
ynh_app_setting_set --app="$app" --key=default_from_email --value="$default_from_email"
fi
if [ -d "/opt/yunohost/$app" ]; then
if [ -d "$install_dir/app" ]; then
ynh_secure_remove --file="/opt/yunohost/$app"
else
mv "/opt/yunohost/$app" "$install_dir/app"
fi
fi
#=================================================
# DOWNLOAD, CHECK AND UNPACK SOURCE
#=================================================
ynh_script_progression --message="Upgrading source files..." --weight=1
mkdir -p "$install_dir/"{app,public/media,public/static}
ynh_add_config --template="requirements.txt" --destination="$install_dir/app/requirements.txt"
ynh_add_config --template="gunicorn.conf.py" --destination="$install_dir/app/gunicorn.conf.py"
ynh_add_config --template="manage.py" --destination="$install_dir/app/manage.py"
chmod +x "$install_dir/app/manage.py"
ynh_add_config --template="settings.py" --destination="$install_dir/app/settings.py"
ynh_add_config --template="setup_user.py" --destination="$install_dir/app/setup_user.py"
ynh_add_config --template="urls.py" --destination="$install_dir/app/urls.py"
ynh_add_config --template="wsgi.py" --destination="$install_dir/app/wsgi.py"
chown -R "$app:www-data" "$install_dir"
chown -R "$app:www-data" "$install_dir"
mkdir -p "/var/log/$app"
touch "/var/log/$app/$app.log"
chown -R "$app:$app" "/var/log/$app"
ynh_add_systemd_config --service=$app --template="systemd.service"
#=================================================
# PYTHON VIRTUALENV
#=================================================
ynh_script_progression --message="Upgrading $app..." --weight=10
_venv_install
_build_app
pushd "$install_dir/app"
# Just for debugging:
ynh_exec_as "$app" "$venvpy" ./manage.py diffsettings
ynh_exec_as "$app" "$venvpy" ./manage.py migrate --no-input
ynh_exec_as "$app" "$venvpy" ./manage.py collectstatic --no-input
# Create/update Django superuser (set unusable password, because auth done via SSOwat):
ynh_exec_as "$app" "$venvpy" ./manage.py create_superuser --username="$admin" --email="$(ynh_user_get_info "$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.
ynh_exec_as "$app" "$venvpy" ./manage.py check --deploy || true
popd
ynh_script_progression --message="Create and setup Python virtualenv..." --weight=45
cp ../conf/requirements.txt "$data_dir/requirements.txt"
myynh_setup_python_venv
#=================================================
# REAPPLY SYSTEM CONFIGURATIONS
#=================================================
ynh_script_progression --message="Upgrading system configurations related to $app..." --weight=1
# copy config files
# ================================================
ynh_script_progression --message="Create project configuration files..."
ynh_add_nginx_config
ynh_add_config --template="gunicorn.conf.py" --destination="$data_dir/gunicorn.conf.py"
ynh_add_systemd_config
yunohost service add "$app" --description="Django-fritzconnection server" --log="/var/log/$app/$app.log"
ynh_add_config --template="manage.py" --destination="$data_dir/manage.py"
chmod -c +x "$data_dir/manage.py"
ynh_use_logrotate --non-append
ynh_add_config --template="settings.py" --destination="$data_dir/settings.py"
ynh_add_config --template="setup_user.py" --destination="$data_dir/setup_user.py"
ynh_add_config --template="urls.py" --destination="$data_dir/urls.py"
ynh_add_config --template="wsgi.py" --destination="$data_dir/wsgi.py"
#=================================================
# START SYSTEMD SERVICE
# MIGRATE PYINVENTORY
#=================================================
ynh_script_progression --message="Starting $app's systemd service..." --weight=1
ynh_script_progression --message="migrate/collectstatic/createadmin..." --weight=10
ynh_systemd_action --service_name="$app" --action="start" --log_path="/var/log/$app/$app.log"
cd "$data_dir" || 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="$(ynh_user_get_info "$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 --logfile="$log_file" --specific_user=$app --non-append
#=================================================
# GENERIC FINALIZATION
#=================================================
# SECURE FILES AND DIRECTORIES
#=================================================
ynh_script_progression --message="Set file permissions..."
myynh_fix_file_permissions
#=================================================
# Start the app server via systemd
#=================================================
ynh_script_progression --message="Starting systemd service '$app'..." --weight=5
yunohost service add --description $app $app
ynh_systemd_action --service_name=$app --action="start" --log_path="$log_file"
#=================================================
# END OF SCRIPT

View file

@ -1,5 +1,3 @@
#:schema https://raw.githubusercontent.com/YunoHost/apps/master/schemas/tests.v1.schema.json
test_format = 1.0
[default]
@ -8,4 +6,26 @@ test_format = 1.0
# Tests to run
# ------------
test_upgrade_from.4b0799f31dfbde3884f88aed8ebb5a42c4dc4a69.name = "Packaging v1"
# NB: the tests to run are automatically deduced by the CI script according to the
# content of the app's manifest. The declarations below allow to customize which
# tests are ran, possibly add special test suite to test special args, or
# declare which commits to test upgrade from.
#
# You can also decide (though this is discouraged!) to ban/ignore some tests,
# The test IDs to be used in only/exclude statements are:
# install.root, install.subdir, install.nourl, install.multi, backup_restore, upgrade, upgrade.someCommitId change_url
#exclude = ["install.private", "install.multi"]
# -------------------------------
# Default args to use for install
# -------------------------------
# By default, the CI will automagically fill the 'standard' args
# such as domain, path, admin, is_public and password with relevant values
# and also install args with a "default" provided in the manifest..
# It should only make sense to declare custom args here for args with no default values
args.default_from_email = "default_from_email@example.tld"
args.admin_email = "admin_email@example.tld"