1
0
Fork 0
mirror of https://github.com/YunoHost-Apps/django-fmd_ynh.git synced 2024-09-03 18:26:27 +02:00

Update project setup via manageprojects

This commit is contained in:
Jens Diemer 2024-06-17 21:12:19 +02:00
parent 21c23df690
commit 851f09fa0a
34 changed files with 2370 additions and 283 deletions

View file

@ -1,4 +1,4 @@
# see http://editorconfig.org # see https://editorconfig.org
root = true root = true
[*] [*]
@ -10,11 +10,11 @@ trim_trailing_whitespace = true
insert_final_newline = true insert_final_newline = true
[*.py] [*.py]
max_line_length = 100 max_line_length = 119
[{Makefile,**.mk}] [{Makefile,**.mk}]
indent_style = tab indent_style = tab
insert_final_newline = false insert_final_newline = false
[*.yml] [{*.yaml,*.yml}]
indent_style = tab 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] [flake8]
exclude = .pytest_cache, .tox, dist, htmlcov, local_test exclude = .*, dist, htmlcov, local_test
ignore = F405 ignore = F405
max-line-length = 119 max-line-length = 119

View file

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

13
.gitignore vendored
View file

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

View file

@ -1,7 +1,7 @@
GNU GENERAL PUBLIC LICENSE GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007 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 Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed. 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. GNU General Public License for more details.
You should have received a copy of the GNU General Public License 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. 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, 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. 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 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 The GNU General Public License does not permit incorporating your program
into proprietary programs. If your program is a subroutine library, you into proprietary programs. If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with 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 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 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,4 +1,3 @@
#!/usr/bin/env python3
""" """
Configuration for Gunicorn Configuration for Gunicorn
""" """
@ -14,8 +13,8 @@ workers = multiprocessing.cpu_count() * 2 + 1
loglevel = 'info' loglevel = 'info'
# https://docs.gunicorn.org/en/latest/settings.html#logging # https://docs.gunicorn.org/en/latest/settings.html#logging
accesslog = '/var/log/__APP__/__APP__.log' accesslog = '__LOG_FILE__'
errorlog = '/var/log/__APP__/__APP__.log' errorlog = '__LOG_FILE__'
# https://docs.gunicorn.org/en/latest/settings.html#pidfile # https://docs.gunicorn.org/en/latest/settings.html#pidfile
pidfile = '__INSTALL_DIR__/gunicorn.pid' pidfile = '__DATA_DIR__/gunicorn.pid' # /home/yunohost.app/$app/gunicorn.pid

View file

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

View file

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

View file

@ -1,43 +1,46 @@
#!/usr/bin/env python3
################################################################################ ################################################################################
################################################################################ ################################################################################
# Please do not modify this file, it will be reset at the next update. # 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, # 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. # but you can use the options and documentation in this file to find out what can be done.
################################################################################ ################################################################################
################################################################################ ################################################################################
from pathlib import Path from pathlib import Path as __Path
from django_yunohost_integration.base_settings import * # noqa:F401,F403 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 django_yunohost_integration.secret_key import get_or_create_secret as __get_or_create_secret
from findmydevice_project.settings.base import * # noqa:F401,F403
# https://gitlab.com/jedie/django-find-my-device
from django-fmd.settings.prod import * # noqa:F401,F403 isort:skip
from django_yunohost_integration.base_settings import LOGGING # noqa:F401 isort:skip from django_yunohost_integration.base_settings import LOGGING # noqa:F401 isort:skip
INSTALL_DIR = Path('__INSTALL_DIR__') DATA_DIR_PATH = __Path('__DATA_DIR__') # /home/yunohost.app/$app/
assert INSTALL_DIR.is_dir(), f'Directory not exists: {INSTALL_DIR}' assert DATA_DIR_PATH.is_dir(), f'Directory not exists: {DATA_DIR_PATH}'
PUBLIC_PATH = Path('__INSTALL_DIR__/public') INSTALL_DIR_PATH = __Path('__INSTALL_DIR__') # /var/www/$app/
assert PUBLIC_PATH.is_dir(), f'Directory not exists: {PUBLIC_PATH}' assert INSTALL_DIR_PATH.is_dir(), f'Directory not exists: {INSTALL_DIR_PATH}'
LOG_FILE = Path('/var/log/__APP__/__APP__.log') LOG_FILE_PATH = __Path('__LOG_FILE__') # /var/log/$app/django_fmd_ynh.log
assert LOG_FILE.is_file(), f'File not exists: {LOG_FILE}' assert LOG_FILE_PATH.is_file(), f'File not exists: {LOG_FILE_PATH}'
PATH = '__PATH__' # $YNH_APP_ARG_PATH PATH_URL = '__PATH__'
PATH = PATH.strip('/') PATH_URL = PATH_URL.strip('/')
YNH_CURRENT_HOST = '__YNH_CURRENT_HOST__' # YunoHost main domain from: /etc/yunohost/current_host
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
# config_panel.toml settings: # config_panel.toml settings:
DEBUG_ENABLED = '__DEBUG_ENABLED__' DEBUG_ENABLED = '__DEBUG_ENABLED__'
DEBUG = bool(int(DEBUG_ENABLED)) DEBUG = DEBUG_ENABLED == '1'
LOG_LEVEL = '__LOG_LEVEL__' LOG_LEVEL = '__LOG_LEVEL__'
ADMIN_EMAIL = '__ADMIN_EMAIL__' ADMIN_EMAIL = '__ADMIN_EMAIL__'
@ -49,20 +52,26 @@ DEFAULT_FROM_EMAIL = '__DEFAULT_FROM_EMAIL__'
# Function that will be called to finalize a user profile: # Function that will be called to finalize a user profile:
YNH_SETUP_USER = 'setup_user.setup_project_user' YNH_SETUP_USER = 'setup_user.setup_project_user'
SECRET_KEY = __get_or_create_secret(INSTALL_DIR / 'secret.txt')
INSTALLED_APPS += [ if 'axes' not in INSTALLED_APPS:
'axes', # https://github.com/jazzband/django-axes INSTALLED_APPS.append('axes') # https://github.com/jazzband/django-axes
'django_yunohost_integration',
] 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.insert(
MIDDLEWARE.index('django.contrib.auth.middleware.AuthenticationMiddleware') + 1, MIDDLEWARE.index('django.contrib.auth.middleware.AuthenticationMiddleware') + 1,
# login a user via HTTP_REMOTE_USER header from SSOwat: # login a user via HTTP_REMOTE_USER header from SSOwat:
'django_yunohost_integration.sso_auth.auth_middleware.SSOwatRemoteUserMiddleware', 'django_yunohost_integration.sso_auth.auth_middleware.SSOwatRemoteUserMiddleware',
) )
# AxesMiddleware should be the last middleware: if 'axes.middleware.AxesMiddleware' not in MIDDLEWARE:
MIDDLEWARE.append('axes.middleware.AxesMiddleware') # AxesMiddleware should be the last middleware:
MIDDLEWARE.append('axes.middleware.AxesMiddleware')
# Keep ModelBackend around for per-user permissions and superuser # Keep ModelBackend around for per-user permissions and superuser
@ -140,28 +149,30 @@ CACHES = {
# _____________________________________________________________________________ # _____________________________________________________________________________
# Static files (CSS, JavaScript, Images) # Static files (CSS, JavaScript, Images)
if PATH: if PATH_URL:
STATIC_URL = f'/{PATH}/static/' STATIC_URL = f'/{PATH_URL}/static/'
MEDIA_URL = f'/{PATH}/media/' MEDIA_URL = f'/{PATH_URL}/media/'
else: else:
# Installed to domain root, without a path prefix? # Installed to domain root, without a path prefix?
STATIC_URL = '/static/' STATIC_URL = '/static/'
MEDIA_URL = '/media/' MEDIA_URL = '/media/'
STATIC_ROOT = str(PUBLIC_PATH / 'static') STATIC_ROOT = str(INSTALL_DIR_PATH / 'static')
MEDIA_ROOT = str(PUBLIC_PATH / 'media') MEDIA_ROOT = str(INSTALL_DIR_PATH / 'media')
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
# Set log file to e.g.: /var/log/$app/$app.log # 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']['findmydevice'] = { LOGGING['loggers']['findmydevice'] = {
'handlers': ['syslog', 'log_file', 'mail_admins'], 'handlers': ['syslog', 'log_file', 'mail_admins'],
'level': 'INFO',
'propagate': False, 'propagate': False,
} }
for __logger_name in LOGGING['loggers'].keys():
LOGGING['loggers'][__logger_name]['level'] = 'DEBUG' if DEBUG else LOG_LEVEL
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------

View file

@ -1,5 +1,3 @@
#!/usr/bin/env python3
def setup_project_user(user): def setup_project_user(user):
""" """
All users used the Django admin, so we need to set the "staff" user flag. All users used the Django admin, so we need to set the "staff" user flag.

View file

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

View file

@ -1,23 +1,20 @@
#!/usr/bin/env python3
"""
urls.py
~~~~~~~
"""
from django.conf import settings from django.conf import settings
from django.urls import include, path from django.urls import include, path
from django.views.static import serve from django.views.static import serve
from django.views.generic import RedirectView
import findmydevice import findmydevice
if settings.PATH_URL:
if settings.PATH: # settings.PATH_URL is __PATH__
# settings.PATH is the $YNH_APP_ARG_PATH # Prefix all urls with "PATH_URL":
# Prefix all urls with "PATH":
urlpatterns = [ urlpatterns = [
path(f'{settings.PATH}/', include('findmydevice_project.urls')),
# #
# TODO: Serve from nginx server ;) # TODO: Serve from nginx server ;)
path(f'{settings.PATH}/<path:path>', serve, {'document_root': findmydevice.WEB_PATH}) path(f'{settings.PATH}/<path:path>', serve, {'document_root': findmydevice.WEB_PATH}),
#
path('', RedirectView.as_view(url=f'{settings.PATH_URL}/')),
path(f'{settings.PATH_URL}/', include('findmydevice_project.urls')),
] ]
else: else:
# Installed to domain root, without a path prefix # 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 # https://github.com/YunoHost/example_ynh/blob/master/config_panel.toml.example
version = "1.0" version = "1.0"
@ -14,13 +15,16 @@ services = ["__APP__"]
ask = "from email" ask = "from email"
type = "email" type = "email"
help = "Default email address to use for various automated emails." help = "Default email address to use for various automated emails."
bind = "default_from_email:__INSTALL_DIR__/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] [main.config.admin_email]
ask = "ADMIN email" ask = "ADMIN email"
type = "email" type = "email"
help = "EMail address for error emails." help = "EMail address for error emails."
bind = "admin_email:__INSTALL_DIR__/settings.py" bind = "admin_email:/home/yunohost.app/__APP__/settings.py"
[main.config.debug_enabled] [main.config.debug_enabled]
ask = "DEBUG mode" ask = "DEBUG mode"
@ -28,11 +32,11 @@ services = ["__APP__"]
yes = "1" yes = "1"
no = "0" no = "0"
help = "Should be never enabled in production!" help = "Should be never enabled in production!"
bind = "debug_enabled:__INSTALL_DIR__/settings.py" bind = "debug_enabled:/home/yunohost.app/__APP__/settings.py"
[main.config.log_level] [main.config.log_level]
type = "string" type = "string"
ask = "Log Level" ask = "Log Level"
choices = ["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"] choices = ["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"]
default = "WARNING" default = "WARNING"
bind = "log_level:__INSTALL_DIR__/settings.py" bind = "log_level:/home/yunohost.app/__APP__/settings.py"

116
dev-cli.py Normal 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, 10), 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'python{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 / 'django_fmd_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('Create virtual env here:', VENV_PATH.absolute())
builder = venv.EnvBuilder(symlinks=True, upgrade=True, with_pip=True)
builder.create(env_dir=VENV_PATH)
# Update pip
verbose_check_call(PYTHON_PATH, '-m', 'pip', 'install', '-U', 'pip')
if not PIP_SYNC_PATH.is_file():
# Install pip-tools
verbose_check_call(PYTHON_PATH, '-m', 'pip', 'install', '-U', 'pip-tools')
if not PROJECT_SHELL_SCRIPT.is_file() or not venv_up2date():
# 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)

View file

@ -0,0 +1,7 @@
"""
django_fmd_ynh
YunoHost app package for https://gitlab.com/jedie/django-find-my-device
"""
__version__ = '0.4.0+ynh1'
__author__ = 'Jens Diemer <git@jensdiemer.de>'

View file

294
django_fmd_ynh/cli/dev.py Normal file
View file

@ -0,0 +1,294 @@
"""
CLI for development
"""
import logging
import shlex
import sys
from pathlib import Path
import django_fmd_ynh
import rich_click as click
from cli_base.cli_tools import code_style
from cli_base.cli_tools.dev_tools import 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 django.core.management.commands.test import Command as DjangoTestCommand
from django_fmd_ynh import constants
from django_fmd_ynh.constants import PACKAGE_ROOT
from django_fmd_ynh.tests import setup_ynh_tests
from django_yunohost_integration.local_test import create_local_test
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
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=PACKAGE_ROOT, verbose=verbosity > 0, exit_on_error=True)
@cli.command()
def install():
"""
Run pip-sync and install 'django_fmd_ynh' via pip as editable.
"""
verbose_check_call('pip-sync', PACKAGE_ROOT / 'requirements.dev.txt')
verbose_check_call('pip', 'install', '--no-deps', '-e', '.')
@cli.command()
def safety():
"""
Run safety check against current requirements files
"""
verbose_check_call(
'safety',
'check',
'-r',
'requirements.dev.txt',
'--ignore',
'67599', # Ignore CVE-2018-20225: We do not use the `--extra-index-url` option
)
@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,
)
verbose_check_call(bin_path / 'safety', 'check', '-r', 'requirements.dev.txt')
# 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=django_fmd_ynh,
package_path=PACKAGE_ROOT,
distribution_name='django_fmd_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=PACKAGE_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=PACKAGE_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=PACKAGE_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 "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=PACKAGE_ROOT / 'conf' / 'settings.py',
destination=PACKAGE_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 = PACKAGE_ROOT / 'local_test'
create_local_test(
django_settings_path=PACKAGE_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(django_fmd_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,
}
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()

View file

@ -0,0 +1,11 @@
from pathlib import Path
import django_fmd_ynh
from bx_py_utils.path import assert_is_file
PACKAGE_ROOT = Path(django_fmd_ynh.__file__).parent.parent
assert_is_file(PACKAGE_ROOT / 'pyproject.toml')
CLI_EPILOG = 'Project Homepage: https://github.com/YunoHost-Apps/django-fmd_ynh'

View file

@ -0,0 +1,63 @@
import os
import sys
import unittest.util
from pathlib import Path
import django
import django_fmd_ynh
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_fmd_ynh.constants import PACKAGE_ROOT
from django_yunohost_integration.local_test import CreateResults, create_local_test
from rich import print # noqa
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:
os.environ['DJANGO_SETTINGS_MODULE'] = 'settings'
print('Compile YunoHost files...')
result: CreateResults = create_local_test(
django_settings_path=PACKAGE_ROOT / 'conf' / 'settings.py',
destination=PACKAGE_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(django_fmd_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_example.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 id="site-name"><a href="/app_path/admin/">Django administration</a></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 django_fmd_ynh
class DocTests(BaseDocTests):
def test_doctests(self):
self.run_doctests(
modules=(django_fmd_ynh,),
)

View file

@ -0,0 +1,95 @@
from django_fmd_ynh.cli.dev import PACKAGE_ROOT
try:
import tomllib # New in Python 3.11
except ImportError:
import tomli as 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_example import __version__ as upstream_version
from django_fmd_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 = PACKAGE_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 = PACKAGE_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=PACKAGE_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,36 @@
from pathlib import Path
from bx_py_utils.auto_doc import assert_readme_block
from django_fmd_ynh.cli.dev import PACKAGE_ROOT, cli
from django_fmd_ynh.constants import CLI_EPILOG
from manageprojects.test_utils.click_cli_utils import invoke_click
from manageprojects.tests.base import BaseTestCase
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=PACKAGE_ROOT / 'doc' / 'ADMIN.md',
)

View file

@ -1,67 +1,112 @@
#:schema https://raw.githubusercontent.com/YunoHost/apps/master/schemas/manifest.v2.schema.json # https://yunohost.org/en/packaging_manifest
packaging_format = 2 packaging_format = 2
id = "django-fmd" id = "django-fmd"
name = "django-fmd" name = "django-fmd"
description.en = "Web based FritzBox management using Python/Django." description.en = "YunoHost app package for https://gitlab.com/jedie/django-find-my-device"
version = "0.3.2~ynh2" version = "0.4.0~ynh1"
maintainers = ["Jens Diemer"] maintainers = ["Jens Diemer"]
[upstream] [upstream]
license = "GPL-3.0" # https://yunohost.org/en/packaging_manifest#upstream-section
website = "https://gitlab.com/jedie/django-find-my-device" license = "GPL-3.0-or-later"
code = "https://gitlab.com/jedie/django-find-my-device" # website = "https://github.com/YunoHost-Apps/django-fmd_ynh" # If the app has no proper website, just remove the 'website' key entirely
admindoc = "https://github.com/YunoHost-Apps/django-fmd_ynh"
userdoc = "https://gitlab.com/jedie/django-find-my-device"
code = "https://github.com/YunoHost-Apps/django-fmd_ynh"
[integration] [integration]
# https://yunohost.org/en/packaging_manifest#integration-section
yunohost = ">= 11.2.12" yunohost = ">= 11.2.12"
architectures = "all" architectures = "all"
multi_instance = false multi_instance = false
ldap = true ldap = true
sso = true sso = true
disk = "50M" disk = "50M" # **estimate** minimum disk requirement. e.g. 20M, 400M, 1G, ...
ram.build = "50M" ram.build = "50M" # **estimate** minimum ram requirement. e.g. 50M, 400M, 1G, ...
ram.runtime = "50M" ram.runtime = "50M" # **estimate** minimum ram requirement. e.g. 50M, 400M, 1G, ...
[install] [install]
# https://yunohost.org/en/packaging_manifest#install-questions
[install.domain] [install.domain]
# this is a generic question - ask strings are automatically handled by Yunohost's core
type = "domain" type = "domain"
[install.path] [install.path]
# this is a generic question - ask strings are automatically handled by Yunohost's core
# setting $path and template variable __PATH__
type = "path" type = "path"
default = "/fmd" default = "/fmd"
[install.admin]
# this is a generic question - ask strings are automatically handled by Yunohost's core
type = "user"
default = "admin"
[install.init_main_permission] [install.init_main_permission]
type = "group" 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]
[resources.system_user] [resources.system_user]
# This will provision/deprovision a unix system user
[resources.install_dir] [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] [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 = "/" main.url = "/"
[resources.ports] [resources.ports]
main.default = 8000 # 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] [resources.apt]
packages = [ # https://yunohost.org/en/packaging_apps_resources#apt
"build-essential", # This will automatically install/uninstall the following apt packages
"python3-dev", packages = "build-essential, python3-dev, python3-pip, python3-venv, git, libpq-dev, postgresql, postgresql-contrib"
"python3-pip",
"python3-venv",
"git",
"libpq-dev",
"postgresql",
"postgresql-contrib"
]
[resources.database] [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" type = "postgresql"

View file

@ -118,6 +118,9 @@ initial_revision = "2281f4b"
initial_date = 2023-04-02T17:40:58+02:00 initial_date = 2023-04-02T17:40:58+02:00
cookiecutter_template = "https://github.com/jedie/cookiecutter_templates/" cookiecutter_template = "https://github.com/jedie/cookiecutter_templates/"
cookiecutter_directory = "yunohost_django_package" cookiecutter_directory = "yunohost_django_package"
applied_migrations = [
"1f3a70e", # 2024-05-21T21:22:39+02:00
]
[manageprojects.cookiecutter_context.cookiecutter] [manageprojects.cookiecutter_context.cookiecutter]
project_name = "django-fmd" project_name = "django-fmd"

971
requirements.dev.txt Normal file
View file

@ -0,0 +1,971 @@
#
# This file is autogenerated by pip-compile with Python 3.11
# by the following command:
#
# ./dev-cli.py update
#
arrow==1.3.0 \
--hash=sha256:c728b120ebc00eb84e01882a6f5e7927a53960aa990ce7dd2b10f39005a67f80 \
--hash=sha256:d4540617648cb5f895730f1ad8c82a65f2dad0166f57b75f3ca54759c4d67a85
# via cookiecutter
asgiref==3.7.2 \
--hash=sha256:89b2ef2247e3b562a16eef663bc0e2e703ec6468e2fa8a5cd61cd449786d4f6e \
--hash=sha256:9e0ce3aa93a819ba5b45120216b23878cf6e8525eb3848653452b4192b92afed
# via django
astor==0.8.1 \
--hash=sha256:070a54e890cefb5b3739d19f30f5a5ec840ffc9c50ffa7d23cc9fc1a38ebbfc5 \
--hash=sha256:6a6effda93f4e1ce9f618779b2dd1d9d84f1e32812c23a29b3fff6fd7f63fa5e
# via flynt
async-timeout==4.0.3 \
--hash=sha256:4640d96be84d82d02ed59ea2b7105a0f7b33abe8703703cd0ab0bf87c427522f \
--hash=sha256:7405140ff1230c310e51dc27b3145b9092d659ce68ff733fb0cefe3ee42be028
# via django-example-ynh (pyproject.toml)
attrs==23.1.0 \
--hash=sha256:1f28b4522cdc2fb4256ac1a020c78acf9cba2c6b461ccd2c126f3aa8e8335d04 \
--hash=sha256:6279836d581513a26f1bf235f9acd333bc9115683f14f7e8fae46c98fc50e015
# via flake8-bugbear
autoflake==2.2.1 \
--hash=sha256:265cde0a43c1f44ecfb4f30d95b0437796759d07be7706a2f70e4719234c0f79 \
--hash=sha256:62b7b6449a692c3c9b0c916919bbc21648da7281e8506bcf8d3f8280e431ebc1
# via manageprojects
autopep8==2.0.4 \
--hash=sha256:067959ca4a07b24dbd5345efa8325f5f58da4298dab0dde0443d5ed765de80cb \
--hash=sha256:2913064abd97b3419d1cc83ea71f042cb821f87e45b9c88cad5ad3c4ea87fe0c
# via
# django-example-ynh (pyproject.toml)
# manageprojects
beautifulsoup4==4.12.2 \
--hash=sha256:492bbc69dca35d12daac71c4db1bfff0c876c00ef4a2ffacce226d4638eb72da \
--hash=sha256:bd2520ca0d9d7d12694a53d44ac482d181b4ec1888909b035a3dbf40d0f57d4a
# via django-example-ynh (pyproject.toml)
binaryornot==0.4.4 \
--hash=sha256:359501dfc9d40632edc9fac890e19542db1a287bbcfa58175b66658392018061 \
--hash=sha256:b8b71173c917bddcd2c16070412e369c3ed7f0528926f70cac18a6c97fd563e4
# via cookiecutter
black==23.11.0 \
--hash=sha256:250d7e60f323fcfc8ea6c800d5eba12f7967400eb6c2d21ae85ad31c204fb1f4 \
--hash=sha256:2a9acad1451632021ee0d146c8765782a0c3846e0e0ea46659d7c4f89d9b212b \
--hash=sha256:412f56bab20ac85927f3a959230331de5614aecda1ede14b373083f62ec24e6f \
--hash=sha256:421f3e44aa67138ab1b9bfbc22ee3780b22fa5b291e4db8ab7eee95200726b07 \
--hash=sha256:45aa1d4675964946e53ab81aeec7a37613c1cb71647b5394779e6efb79d6d187 \
--hash=sha256:4c44b7211a3a0570cc097e81135faa5f261264f4dfaa22bd5ee2875a4e773bd6 \
--hash=sha256:4c68855825ff432d197229846f971bc4d6666ce90492e5b02013bcaca4d9ab05 \
--hash=sha256:5133f5507007ba08d8b7b263c7aa0f931af5ba88a29beacc4b2dc23fcefe9c06 \
--hash=sha256:54caaa703227c6e0c87b76326d0862184729a69b73d3b7305b6288e1d830067e \
--hash=sha256:58e5f4d08a205b11800332920e285bd25e1a75c54953e05502052738fe16b3b5 \
--hash=sha256:698c1e0d5c43354ec5d6f4d914d0d553a9ada56c85415700b81dc90125aac244 \
--hash=sha256:6c1cac07e64433f646a9a838cdc00c9768b3c362805afc3fce341af0e6a9ae9f \
--hash=sha256:760415ccc20f9e8747084169110ef75d545f3b0932ee21368f63ac0fee86b221 \
--hash=sha256:7f622b6822f02bfaf2a5cd31fdb7cd86fcf33dab6ced5185c35f5db98260b055 \
--hash=sha256:cf57719e581cfd48c4efe28543fea3d139c6b6f1238b3f0102a9c73992cbb479 \
--hash=sha256:d136ef5b418c81660ad847efe0e55c58c8208b77a57a28a503a5f345ccf01394 \
--hash=sha256:dbea0bb8575c6b6303cc65017b46351dc5953eea5c0a59d7b7e3a2d2f433a911 \
--hash=sha256:fc7f6a44d52747e65a02558e1d807c82df1d66ffa80a601862040a43ec2e3142
# via darker
bleach==6.1.0 \
--hash=sha256:0a31f1837963c41d46bbf1331b8778e1308ea0791db03cc4e7357b97cf42a8fe \
--hash=sha256:3225f354cfc436b9789c66c4ee030194bee0568fbf9cbdad3bc8b5c26c5f12b6
# via django-tools
build==1.0.3 \
--hash=sha256:538aab1b64f9828977f84bc63ae570b060a8ed1be419e7870b8b4fc5e6ea553b \
--hash=sha256:589bf99a67df7c9cf07ec0ac0e5e2ea5d4b37ac63301c4986d1acb126aa83f8f
# via pip-tools
bx-django-utils==70 \
--hash=sha256:41c8bd54c0eab07ce914ec7bf3eca115324fba577b158a89d827b4312a4e6a98 \
--hash=sha256:dfc2db1ef019c51ea446687dff1602f75984f3bc410426d3c3ff34d2fab58f1a
# via
# django-example
# django-example-ynh (pyproject.toml)
bx-py-utils==89 \
--hash=sha256:179649df6b2541d241e23e2cf1ac314586717d8e60f57326fe5c9f110f3d602e \
--hash=sha256:68215036c448fd04baf989cd51a9523a9edb8239a7520c9177fb874d64914878
# via
# bx-django-utils
# cli-base-utilities
# django-tools
cachetools==5.3.2 \
--hash=sha256:086ee420196f7b2ab9ca2db2520aca326318b68fe5ba8bc4d49cca91add450f2 \
--hash=sha256:861f35a13a451f94e301ce2bec7cac63e881232ccce7ed67fab9b5df4d3beaa1
# via tox
certifi==2023.11.17 \
--hash=sha256:9b469f3a900bf28dc19b8cfbf8019bf47f7fdd1a65a1d4ffb98fc14166beb4d1 \
--hash=sha256:e036ab49d5b79556f99cfc2d9320b34cfbe5be05c5871b51de9329f0603b0474
# via requests
cffi==1.16.0 \
--hash=sha256:0c9ef6ff37e974b73c25eecc13952c55bceed9112be2d9d938ded8e856138bcc \
--hash=sha256:131fd094d1065b19540c3d72594260f118b231090295d8c34e19a7bbcf2e860a \
--hash=sha256:1b8ebc27c014c59692bb2664c7d13ce7a6e9a629be20e54e7271fa696ff2b417 \
--hash=sha256:2c56b361916f390cd758a57f2e16233eb4f64bcbeee88a4881ea90fca14dc6ab \
--hash=sha256:2d92b25dbf6cae33f65005baf472d2c245c050b1ce709cc4588cdcdd5495b520 \
--hash=sha256:31d13b0f99e0836b7ff893d37af07366ebc90b678b6664c955b54561fc36ef36 \
--hash=sha256:32c68ef735dbe5857c810328cb2481e24722a59a2003018885514d4c09af9743 \
--hash=sha256:3686dffb02459559c74dd3d81748269ffb0eb027c39a6fc99502de37d501faa8 \
--hash=sha256:582215a0e9adbe0e379761260553ba11c58943e4bbe9c36430c4ca6ac74b15ed \
--hash=sha256:5b50bf3f55561dac5438f8e70bfcdfd74543fd60df5fa5f62d94e5867deca684 \
--hash=sha256:5bf44d66cdf9e893637896c7faa22298baebcd18d1ddb6d2626a6e39793a1d56 \
--hash=sha256:6602bc8dc6f3a9e02b6c22c4fc1e47aa50f8f8e6d3f78a5e16ac33ef5fefa324 \
--hash=sha256:673739cb539f8cdaa07d92d02efa93c9ccf87e345b9a0b556e3ecc666718468d \
--hash=sha256:68678abf380b42ce21a5f2abde8efee05c114c2fdb2e9eef2efdb0257fba1235 \
--hash=sha256:68e7c44931cc171c54ccb702482e9fc723192e88d25a0e133edd7aff8fcd1f6e \
--hash=sha256:6b3d6606d369fc1da4fd8c357d026317fbb9c9b75d36dc16e90e84c26854b088 \
--hash=sha256:748dcd1e3d3d7cd5443ef03ce8685043294ad6bd7c02a38d1bd367cfd968e000 \
--hash=sha256:7651c50c8c5ef7bdb41108b7b8c5a83013bfaa8a935590c5d74627c047a583c7 \
--hash=sha256:7b78010e7b97fef4bee1e896df8a4bbb6712b7f05b7ef630f9d1da00f6444d2e \
--hash=sha256:7e61e3e4fa664a8588aa25c883eab612a188c725755afff6289454d6362b9673 \
--hash=sha256:80876338e19c951fdfed6198e70bc88f1c9758b94578d5a7c4c91a87af3cf31c \
--hash=sha256:8895613bcc094d4a1b2dbe179d88d7fb4a15cee43c052e8885783fac397d91fe \
--hash=sha256:88e2b3c14bdb32e440be531ade29d3c50a1a59cd4e51b1dd8b0865c54ea5d2e2 \
--hash=sha256:8f8e709127c6c77446a8c0a8c8bf3c8ee706a06cd44b1e827c3e6a2ee6b8c098 \
--hash=sha256:9cb4a35b3642fc5c005a6755a5d17c6c8b6bcb6981baf81cea8bfbc8903e8ba8 \
--hash=sha256:9f90389693731ff1f659e55c7d1640e2ec43ff725cc61b04b2f9c6d8d017df6a \
--hash=sha256:a09582f178759ee8128d9270cd1344154fd473bb77d94ce0aeb2a93ebf0feaf0 \
--hash=sha256:a6a14b17d7e17fa0d207ac08642c8820f84f25ce17a442fd15e27ea18d67c59b \
--hash=sha256:a72e8961a86d19bdb45851d8f1f08b041ea37d2bd8d4fd19903bc3083d80c896 \
--hash=sha256:abd808f9c129ba2beda4cfc53bde801e5bcf9d6e0f22f095e45327c038bfe68e \
--hash=sha256:ac0f5edd2360eea2f1daa9e26a41db02dd4b0451b48f7c318e217ee092a213e9 \
--hash=sha256:b29ebffcf550f9da55bec9e02ad430c992a87e5f512cd63388abb76f1036d8d2 \
--hash=sha256:b2ca4e77f9f47c55c194982e10f058db063937845bb2b7a86c84a6cfe0aefa8b \
--hash=sha256:b7be2d771cdba2942e13215c4e340bfd76398e9227ad10402a8767ab1865d2e6 \
--hash=sha256:b84834d0cf97e7d27dd5b7f3aca7b6e9263c56308ab9dc8aae9784abb774d404 \
--hash=sha256:b86851a328eedc692acf81fb05444bdf1891747c25af7529e39ddafaf68a4f3f \
--hash=sha256:bcb3ef43e58665bbda2fb198698fcae6776483e0c4a631aa5647806c25e02cc0 \
--hash=sha256:c0f31130ebc2d37cdd8e44605fb5fa7ad59049298b3f745c74fa74c62fbfcfc4 \
--hash=sha256:c6a164aa47843fb1b01e941d385aab7215563bb8816d80ff3a363a9f8448a8dc \
--hash=sha256:d8a9d3ebe49f084ad71f9269834ceccbf398253c9fac910c4fd7053ff1386936 \
--hash=sha256:db8e577c19c0fda0beb7e0d4e09e0ba74b1e4c092e0e40bfa12fe05b6f6d75ba \
--hash=sha256:dc9b18bf40cc75f66f40a7379f6a9513244fe33c0e8aa72e2d56b0196a7ef872 \
--hash=sha256:e09f3ff613345df5e8c3667da1d918f9149bd623cd9070c983c013792a9a62eb \
--hash=sha256:e4108df7fe9b707191e55f33efbcb2d81928e10cea45527879a4749cbe472614 \
--hash=sha256:e6024675e67af929088fda399b2094574609396b1decb609c55fa58b028a32a1 \
--hash=sha256:e70f54f1796669ef691ca07d046cd81a29cb4deb1e5f942003f401c0c4a2695d \
--hash=sha256:e715596e683d2ce000574bae5d07bd522c781a822866c20495e52520564f0969 \
--hash=sha256:e760191dd42581e023a68b758769e2da259b5d52e3103c6060ddc02c9edb8d7b \
--hash=sha256:ed86a35631f7bfbb28e108dd96773b9d5a6ce4811cf6ea468bb6a359b256b1e4 \
--hash=sha256:ee07e47c12890ef248766a6e55bd38ebfb2bb8edd4142d56db91b21ea68b7627 \
--hash=sha256:fa3a0128b152627161ce47201262d3140edb5a5c3da88d73a1b790a959126956 \
--hash=sha256:fcc8eb6d5902bb1cf6dc4f187ee3ea80a1eba0a89aba40a5cb20a5087d961357
# via cryptography
chardet==5.2.0 \
--hash=sha256:1b3b6ff479a8c414bc3fa2c0852995695c4a026dcd6d0633b2dd092ca39c1cf7 \
--hash=sha256:e1cf59446890a00105fe7b7912492ea04b6e6f06d4b742b2c788469e34c82970
# via
# binaryornot
# tox
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.6.0 \
--hash=sha256:c3e0efafca519f7f79a1aff67d099987d849f1c3d795c4e605b6b9be49537712 \
--hash=sha256:e403c6e584508aa69cc093b7a325127f6ef9835624e4fde7f104e941314645a9
# via
# django-example-ynh (pyproject.toml)
# manageprojects
click==8.1.7 \
--hash=sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28 \
--hash=sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de
# via
# black
# cli-base-utilities
# cookiecutter
# manageprojects
# pip-tools
# rich-click
# safety
codespell==2.2.6 \
--hash=sha256:9ee9a3e5df0990604013ac2a9f22fa8e57669c827124a2e961fe8a1da4cacc07 \
--hash=sha256:a8c65d8eb3faa03deabab6b3bbe798bea72e1799c7e9e955d57eca4096abcff9
# via
# django-example-ynh (pyproject.toml)
# manageprojects
colorama==0.4.6 \
--hash=sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44 \
--hash=sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6
# via tox
colorlog==6.8.0 \
--hash=sha256:4ed23b05a1154294ac99f511fabe8c1d6d4364ec1f7fc989c7fb515ccc29d375 \
--hash=sha256:fbb6fdf9d5685f2517f388fb29bb27d54e8654dd31f58bc2a3b217e967a95ca6
# via django-yunohost-integration
cookiecutter==2.5.0 \
--hash=sha256:8aa2f12ed11bc05628651e9dc4353a10571dd9908aaaaeec959a2b9ea465a5d2 \
--hash=sha256:e61e9034748e3f41b8bd2c11f00d030784b48711c4d5c42363c50989a65331ec
# via manageprojects
coverage==7.3.2 \
--hash=sha256:0cbf38419fb1a347aaf63481c00f0bdc86889d9fbf3f25109cf96c26b403fda1 \
--hash=sha256:12d15ab5833a997716d76f2ac1e4b4d536814fc213c85ca72756c19e5a6b3d63 \
--hash=sha256:149de1d2401ae4655c436a3dced6dd153f4c3309f599c3d4bd97ab172eaf02d9 \
--hash=sha256:1981f785239e4e39e6444c63a98da3a1db8e971cb9ceb50a945ba6296b43f312 \
--hash=sha256:2443cbda35df0d35dcfb9bf8f3c02c57c1d6111169e3c85fc1fcc05e0c9f39a3 \
--hash=sha256:289fe43bf45a575e3ab10b26d7b6f2ddb9ee2dba447499f5401cfb5ecb8196bb \
--hash=sha256:2f11cc3c967a09d3695d2a6f03fb3e6236622b93be7a4b5dc09166a861be6d25 \
--hash=sha256:307adb8bd3abe389a471e649038a71b4eb13bfd6b7dd9a129fa856f5c695cf92 \
--hash=sha256:310b3bb9c91ea66d59c53fa4989f57d2436e08f18fb2f421a1b0b6b8cc7fffda \
--hash=sha256:315a989e861031334d7bee1f9113c8770472db2ac484e5b8c3173428360a9148 \
--hash=sha256:3a4006916aa6fee7cd38db3bfc95aa9c54ebb4ffbfc47c677c8bba949ceba0a6 \
--hash=sha256:3c7bba973ebee5e56fe9251300c00f1579652587a9f4a5ed8404b15a0471f216 \
--hash=sha256:4175e10cc8dda0265653e8714b3174430b07c1dca8957f4966cbd6c2b1b8065a \
--hash=sha256:43668cabd5ca8258f5954f27a3aaf78757e6acf13c17604d89648ecc0cc66640 \
--hash=sha256:4cbae1051ab791debecc4a5dcc4a1ff45fc27b91b9aee165c8a27514dd160836 \
--hash=sha256:5c913b556a116b8d5f6ef834038ba983834d887d82187c8f73dec21049abd65c \
--hash=sha256:5f7363d3b6a1119ef05015959ca24a9afc0ea8a02c687fe7e2d557705375c01f \
--hash=sha256:630b13e3036e13c7adc480ca42fa7afc2a5d938081d28e20903cf7fd687872e2 \
--hash=sha256:72c0cfa5250f483181e677ebc97133ea1ab3eb68645e494775deb6a7f6f83901 \
--hash=sha256:7dbc3ed60e8659bc59b6b304b43ff9c3ed858da2839c78b804973f613d3e92ed \
--hash=sha256:88ed2c30a49ea81ea3b7f172e0269c182a44c236eb394718f976239892c0a27a \
--hash=sha256:89a937174104339e3a3ffcf9f446c00e3a806c28b1841c63edb2b369310fd074 \
--hash=sha256:9028a3871280110d6e1aa2df1afd5ef003bab5fb1ef421d6dc748ae1c8ef2ebc \
--hash=sha256:99b89d9f76070237975b315b3d5f4d6956ae354a4c92ac2388a5695516e47c84 \
--hash=sha256:9f805d62aec8eb92bab5b61c0f07329275b6f41c97d80e847b03eb894f38d083 \
--hash=sha256:a889ae02f43aa45032afe364c8ae84ad3c54828c2faa44f3bfcafecb5c96b02f \
--hash=sha256:aa72dbaf2c2068404b9870d93436e6d23addd8bbe9295f49cbca83f6e278179c \
--hash=sha256:ac8c802fa29843a72d32ec56d0ca792ad15a302b28ca6203389afe21f8fa062c \
--hash=sha256:ae97af89f0fbf373400970c0a21eef5aa941ffeed90aee43650b81f7d7f47637 \
--hash=sha256:af3d828d2c1cbae52d34bdbb22fcd94d1ce715d95f1a012354a75e5913f1bda2 \
--hash=sha256:b4275802d16882cf9c8b3d057a0839acb07ee9379fa2749eca54efbce1535b82 \
--hash=sha256:b4767da59464bb593c07afceaddea61b154136300881844768037fd5e859353f \
--hash=sha256:b631c92dfe601adf8f5ebc7fc13ced6bb6e9609b19d9a8cd59fa47c4186ad1ce \
--hash=sha256:be32ad29341b0170e795ca590e1c07e81fc061cb5b10c74ce7203491484404ef \
--hash=sha256:beaa5c1b4777f03fc63dfd2a6bd820f73f036bfb10e925fce067b00a340d0f3f \
--hash=sha256:c0ba320de3fb8c6ec16e0be17ee1d3d69adcda99406c43c0409cb5c41788a611 \
--hash=sha256:c9eacf273e885b02a0273bb3a2170f30e2d53a6d53b72dbe02d6701b5296101c \
--hash=sha256:cb536f0dcd14149425996821a168f6e269d7dcd2c273a8bff8201e79f5104e76 \
--hash=sha256:d1bc430677773397f64a5c88cb522ea43175ff16f8bfcc89d467d974cb2274f9 \
--hash=sha256:d1c88ec1a7ff4ebca0219f5b1ef863451d828cccf889c173e1253aa84b1e07ce \
--hash=sha256:d3d9df4051c4a7d13036524b66ecf7a7537d14c18a384043f30a303b146164e9 \
--hash=sha256:d51ac2a26f71da1b57f2dc81d0e108b6ab177e7d30e774db90675467c847bbdf \
--hash=sha256:d872145f3a3231a5f20fd48500274d7df222e291d90baa2026cc5152b7ce86bf \
--hash=sha256:d8f17966e861ff97305e0801134e69db33b143bbfb36436efb9cfff6ec7b2fd9 \
--hash=sha256:dbc1b46b92186cc8074fee9d9fbb97a9dd06c6cbbef391c2f59d80eabdf0faa6 \
--hash=sha256:e10c39c0452bf6e694511c901426d6b5ac005acc0f78ff265dbe36bf81f808a2 \
--hash=sha256:e267e9e2b574a176ddb983399dec325a80dbe161f1a32715c780b5d14b5f583a \
--hash=sha256:f47d39359e2c3779c5331fc740cf4bce6d9d680a7b4b4ead97056a0ae07cb49a \
--hash=sha256:f6e9589bd04d0461a417562649522575d8752904d35c12907d8c9dfeba588faf \
--hash=sha256:f94b734214ea6a36fe16e96a70d941af80ff3bfd716c141300d95ebc85339738 \
--hash=sha256:fa28e909776dc69efb6ed975a63691bc8172b64ff357e663a1bb06ff3c9b589a \
--hash=sha256:fe494faa90ce6381770746077243231e0b83ff3f17069d748f645617cefe19d4
# via django-example-ynh (pyproject.toml)
cryptography==41.0.7 \
--hash=sha256:079b85658ea2f59c4f43b70f8119a52414cdb7be34da5d019a77bf96d473b960 \
--hash=sha256:09616eeaef406f99046553b8a40fbf8b1e70795a91885ba4c96a70793de5504a \
--hash=sha256:13f93ce9bea8016c253b34afc6bd6a75993e5c40672ed5405a9c832f0d4a00bc \
--hash=sha256:37a138589b12069efb424220bf78eac59ca68b95696fc622b6ccc1c0a197204a \
--hash=sha256:3c78451b78313fa81607fa1b3f1ae0a5ddd8014c38a02d9db0616133987b9cdf \
--hash=sha256:43f2552a2378b44869fe8827aa19e69512e3245a219104438692385b0ee119d1 \
--hash=sha256:48a0476626da912a44cc078f9893f292f0b3e4c739caf289268168d8f4702a39 \
--hash=sha256:49f0805fc0b2ac8d4882dd52f4a3b935b210935d500b6b805f321addc8177406 \
--hash=sha256:5429ec739a29df2e29e15d082f1d9ad683701f0ec7709ca479b3ff2708dae65a \
--hash=sha256:5a1b41bc97f1ad230a41657d9155113c7521953869ae57ac39ac7f1bb471469a \
--hash=sha256:68a2dec79deebc5d26d617bfdf6e8aab065a4f34934b22d3b5010df3ba36612c \
--hash=sha256:7a698cb1dac82c35fcf8fe3417a3aaba97de16a01ac914b89a0889d364d2f6be \
--hash=sha256:841df4caa01008bad253bce2a6f7b47f86dc9f08df4b433c404def869f590a15 \
--hash=sha256:90452ba79b8788fa380dfb587cca692976ef4e757b194b093d845e8d99f612f2 \
--hash=sha256:928258ba5d6f8ae644e764d0f996d61a8777559f72dfeb2eea7e2fe0ad6e782d \
--hash=sha256:af03b32695b24d85a75d40e1ba39ffe7db7ffcb099fe507b39fd41a565f1b157 \
--hash=sha256:b640981bf64a3e978a56167594a0e97db71c89a479da8e175d8bb5be5178c003 \
--hash=sha256:c5ca78485a255e03c32b513f8c2bc39fedb7f5c5f8535545bdc223a03b24f248 \
--hash=sha256:c7f3201ec47d5207841402594f1d7950879ef890c0c495052fa62f58283fde1a \
--hash=sha256:d5ec85080cce7b0513cfd233914eb8b7bbd0633f1d1703aa28d1dd5a72f678ec \
--hash=sha256:d6c391c021ab1f7a82da5d8d0b3cee2f4b2c455ec86c8aebbc84837a631ff309 \
--hash=sha256:e3114da6d7f95d2dee7d3f4eec16dacff819740bbab931aff8648cb13c5ff5e7 \
--hash=sha256:f983596065a18a2183e7f79ab3fd4c475205b839e02cbc0efbbf9666c4b3083d
# via secretstorage
darker[color,flynt,isort]==1.7.2 \
--hash=sha256:ec5b7c382d9537611c164f3ecca2e1b8a7923bc5a02bf22f6e7f6c8bcbdf593a \
--hash=sha256:ec9d130ab2a0f7fa49ab68a08fd231a5bec66147ecbbf94c92a1f33d97b5ef6f
# via
# django-example-ynh (pyproject.toml)
# manageprojects
distlib==0.3.7 \
--hash=sha256:2e24928bc811348f0feb63014e97aaae3037f2cf48712d51ae61df7fd6075057 \
--hash=sha256:9dafe54b34a028eafd95039d5e5d4851a13734540f1331060d31c9916e7147a8
# via virtualenv
django==4.2.7 \
--hash=sha256:8e0f1c2c2786b5c0e39fe1afce24c926040fad47c8ea8ad30aaf1188df29fc41 \
--hash=sha256:e1d37c51ad26186de355cbcec16613ebdabfa9689bbade9c538835205a8abbe9
# via
# bx-django-utils
# django-axes
# django-redis
# django-tools
# django-yunohost-integration
django-axes==6.1.1 \
--hash=sha256:29c48ff5f09046afd5e9a16e96d3bbb79f6c11c59f0a7bbd732559e60d0aa9fa \
--hash=sha256:cd1bc4f7becc8e9243eb4090dffa258d7d7125ca0ce3153b6ffc920bccbf2c3f
# via django-yunohost-integration
django-example==0.2.0 \
--hash=sha256:2bcaeed97868e8be5c4d7d6a745b054f6983c931d5cefb763e6ff25807f15793 \
--hash=sha256:469beaa9e4f3e5d0ee98f043ee533f0f01fafef128391ac9f4ca9d9f35290da0
# via django-example-ynh (pyproject.toml)
django-redis==5.4.0 \
--hash=sha256:6a02abaa34b0fea8bf9b707d2c363ab6adc7409950b2db93602e6cb292818c42 \
--hash=sha256:ebc88df7da810732e2af9987f7f426c96204bf89319df4c6da6ca9a2942edd5b
# via django-yunohost-integration
django-tools==0.54.0 \
--hash=sha256:5040a91282be9d1c9d379b0c65da50bcb3691bff03cee54fd4123ace238c3a43 \
--hash=sha256:a7b7bfa5b9c5a81966454d17dffb2403cee25a806c858ee0486a08798227598f
# via django-yunohost-integration
django-yunohost-integration[ynh]==0.6.0 \
--hash=sha256:9596ab56b66edf1b56eccaceb4b5807df237e752128e79568cd13b075267f26f \
--hash=sha256:dfb72b94ae30e02948dd60ca76d56da4ca6a74ea04f357b8d61b94807fca0493
# via
# django-example-ynh (pyproject.toml)
# django-yunohost-integration
docutils==0.20.1 \
--hash=sha256:96f387a2c5562db4476f09f13bbab2192e764cac08ebbf3a34a95d9b1e4a59d6 \
--hash=sha256:f08a4e276c3a1583a86dce3e34aba3fe04d02bba2dd51ed16106244e8a923e3b
# via readme-renderer
dparse==0.6.3 \
--hash=sha256:0d8fe18714056ca632d98b24fbfc4e9791d4e47065285ab486182288813a5318 \
--hash=sha256:27bb8b4bcaefec3997697ba3f6e06b2447200ba273c0b085c3d012a04571b528
# via safety
editorconfig==0.12.3 \
--hash=sha256:57f8ce78afcba15c8b18d46b5170848c88d56fd38f05c2ec60dbbfcb8996e89e \
--hash=sha256:6b0851425aa875b08b16789ee0eeadbd4ab59666e9ebe728e526314c4a2e52c1
# via
# django-example-ynh (pyproject.toml)
# manageprojects
filelock==3.13.1 \
--hash=sha256:521f5f56c50f8426f5e03ad3b281b490a87ef15bc6c526f168290f0c7148d44e \
--hash=sha256:57dbda9b35157b05fb3e58ee91448612eb674172fab98ee235ccb0b5bee19a1c
# via
# tox
# virtualenv
flake8==6.1.0 \
--hash=sha256:d5b3857f07c030bdb5bf41c7f53799571d75c4491748a3adcd47de929e34cd23 \
--hash=sha256:ffdfce58ea94c6580c77888a86506937f9a1a227dfcd15f245d694ae20a6b6e5
# via
# django-example-ynh (pyproject.toml)
# flake8-bugbear
# manageprojects
flake8-bugbear==23.12.2 \
--hash=sha256:32b2903e22331ae04885dae25756a32a8c666c85142e933f43512a70f342052a \
--hash=sha256:83324bad4d90fee4bf64dd69c61aff94debf8073fbd807c8b6a36eec7a2f0719
# via
# django-example-ynh (pyproject.toml)
# manageprojects
flynt==0.77 \
--hash=sha256:2863ac8ec19d6ec8d29e760546e6ced644baf6dff3c7cdc77e03abbd29b80f14 \
--hash=sha256:2bd1b37043ad88a3f3c3c34a76fc0b64d24e5f03d36ea6b48cb69cc642bff17e
# via darker
gunicorn==21.2.0 \
--hash=sha256:3213aa5e8c24949e792bcacfc176fef362e7aac80b76c56f6b5122bf350722f0 \
--hash=sha256:88ec8bff1d634f98e61b9f65bc4bf3cd918a90806c6f5c48bc5603849ec81033
# via django-yunohost-integration
icdiff==2.0.7 \
--hash=sha256:f05d1b3623223dd1c70f7848da7d699de3d9a2550b902a8234d9026292fb5762 \
--hash=sha256:f79a318891adbf59a45e3a7694f5e1f18c5407065264637072ac8363b759866f
# via django-tools
idna==3.6 \
--hash=sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca \
--hash=sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f
# via requests
importlib-metadata==6.9.0 \
--hash=sha256:1c8dc6839ddc9771412596926f24cb5a553bbd40624ee2c7e55e531542bed3b8 \
--hash=sha256:e8acb523c335a91822674e149b46c0399ec4d328c4d1f6e49c273da5ff0201b9
# via
# keyring
# twine
isort==5.12.0 \
--hash=sha256:8bef7dde241278824a6d83f44a544709b065191b95b6e50894bdc722fcba0504 \
--hash=sha256:f84c2818376e66cf843d497486ea8fed8700b340f308f076c6fb1229dff318b6
# via darker
jaraco-classes==3.3.0 \
--hash=sha256:10afa92b6743f25c0cf5f37c6bb6e18e2c5bb84a16527ccfc0040ea377e7aaeb \
--hash=sha256:c063dd08e89217cee02c8d5e5ec560f2c8ce6cdc2fcdc2e68f7b2e5547ed3621
# via keyring
jeepney==0.8.0 \
--hash=sha256:5efe48d255973902f6badc3ce55e2aa6c5c3b3bc642059ef3a91247bcfcc5806 \
--hash=sha256:c0a454ad016ca575060802ee4d590dd912e35c122fa04e70306de3d076cce755
# via
# keyring
# secretstorage
jinja2==3.1.2 \
--hash=sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852 \
--hash=sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61
# via cookiecutter
keyring==24.3.0 \
--hash=sha256:4446d35d636e6a10b8bce7caa66913dd9eca5fd222ca03a3d42c38608ac30836 \
--hash=sha256:e730ecffd309658a08ee82535a3b5ec4b4c8669a9be11efb66249d8e0aeb9a25
# via twine
manageprojects==0.16.0 \
--hash=sha256:97de7b291926b417c991762ed6b2d9945d25875db132113ca1dc56295e7acedd \
--hash=sha256:e2f2d308cdf62beea4b70997694dc034007c8189bcbc3da1c9089faeb5b34a7b
# via django-example-ynh (pyproject.toml)
markdown-it-py==3.0.0 \
--hash=sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1 \
--hash=sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb
# via rich
markupsafe==2.1.3 \
--hash=sha256:05fb21170423db021895e1ea1e1f3ab3adb85d1c2333cbc2310f2a26bc77272e \
--hash=sha256:0a4e4a1aff6c7ac4cd55792abf96c915634c2b97e3cc1c7129578aa68ebd754e \
--hash=sha256:10bbfe99883db80bdbaff2dcf681dfc6533a614f700da1287707e8a5d78a8431 \
--hash=sha256:134da1eca9ec0ae528110ccc9e48041e0828d79f24121a1a146161103c76e686 \
--hash=sha256:14ff806850827afd6b07a5f32bd917fb7f45b046ba40c57abdb636674a8b559c \
--hash=sha256:1577735524cdad32f9f694208aa75e422adba74f1baee7551620e43a3141f559 \
--hash=sha256:1b40069d487e7edb2676d3fbdb2b0829ffa2cd63a2ec26c4938b2d34391b4ecc \
--hash=sha256:1b8dd8c3fd14349433c79fa8abeb573a55fc0fdd769133baac1f5e07abf54aeb \
--hash=sha256:1f67c7038d560d92149c060157d623c542173016c4babc0c1913cca0564b9939 \
--hash=sha256:282c2cb35b5b673bbcadb33a585408104df04f14b2d9b01d4c345a3b92861c2c \
--hash=sha256:2c1b19b3aaacc6e57b7e25710ff571c24d6c3613a45e905b1fde04d691b98ee0 \
--hash=sha256:2ef12179d3a291be237280175b542c07a36e7f60718296278d8593d21ca937d4 \
--hash=sha256:338ae27d6b8745585f87218a3f23f1512dbf52c26c28e322dbe54bcede54ccb9 \
--hash=sha256:3c0fae6c3be832a0a0473ac912810b2877c8cb9d76ca48de1ed31e1c68386575 \
--hash=sha256:3fd4abcb888d15a94f32b75d8fd18ee162ca0c064f35b11134be77050296d6ba \
--hash=sha256:42de32b22b6b804f42c5d98be4f7e5e977ecdd9ee9b660fda1a3edf03b11792d \
--hash=sha256:47d4f1c5f80fc62fdd7777d0d40a2e9dda0a05883ab11374334f6c4de38adffd \
--hash=sha256:504b320cd4b7eff6f968eddf81127112db685e81f7e36e75f9f84f0df46041c3 \
--hash=sha256:525808b8019e36eb524b8c68acdd63a37e75714eac50e988180b169d64480a00 \
--hash=sha256:56d9f2ecac662ca1611d183feb03a3fa4406469dafe241673d521dd5ae92a155 \
--hash=sha256:5bbe06f8eeafd38e5d0a4894ffec89378b6c6a625ff57e3028921f8ff59318ac \
--hash=sha256:65c1a9bcdadc6c28eecee2c119465aebff8f7a584dd719facdd9e825ec61ab52 \
--hash=sha256:68e78619a61ecf91e76aa3e6e8e33fc4894a2bebe93410754bd28fce0a8a4f9f \
--hash=sha256:69c0f17e9f5a7afdf2cc9fb2d1ce6aabdb3bafb7f38017c0b77862bcec2bbad8 \
--hash=sha256:6b2b56950d93e41f33b4223ead100ea0fe11f8e6ee5f641eb753ce4b77a7042b \
--hash=sha256:715d3562f79d540f251b99ebd6d8baa547118974341db04f5ad06d5ea3eb8007 \
--hash=sha256:787003c0ddb00500e49a10f2844fac87aa6ce977b90b0feaaf9de23c22508b24 \
--hash=sha256:7ef3cb2ebbf91e330e3bb937efada0edd9003683db6b57bb108c4001f37a02ea \
--hash=sha256:8023faf4e01efadfa183e863fefde0046de576c6f14659e8782065bcece22198 \
--hash=sha256:8758846a7e80910096950b67071243da3e5a20ed2546e6392603c096778d48e0 \
--hash=sha256:8afafd99945ead6e075b973fefa56379c5b5c53fd8937dad92c662da5d8fd5ee \
--hash=sha256:8c41976a29d078bb235fea9b2ecd3da465df42a562910f9022f1a03107bd02be \
--hash=sha256:8e254ae696c88d98da6555f5ace2279cf7cd5b3f52be2b5cf97feafe883b58d2 \
--hash=sha256:8f9293864fe09b8149f0cc42ce56e3f0e54de883a9de90cd427f191c346eb2e1 \
--hash=sha256:9402b03f1a1b4dc4c19845e5c749e3ab82d5078d16a2a4c2cd2df62d57bb0707 \
--hash=sha256:962f82a3086483f5e5f64dbad880d31038b698494799b097bc59c2edf392fce6 \
--hash=sha256:9aad3c1755095ce347e26488214ef77e0485a3c34a50c5a5e2471dff60b9dd9c \
--hash=sha256:9dcdfd0eaf283af041973bff14a2e143b8bd64e069f4c383416ecd79a81aab58 \
--hash=sha256:aa57bd9cf8ae831a362185ee444e15a93ecb2e344c8e52e4d721ea3ab6ef1823 \
--hash=sha256:aa7bd130efab1c280bed0f45501b7c8795f9fdbeb02e965371bbef3523627779 \
--hash=sha256:ab4a0df41e7c16a1392727727e7998a467472d0ad65f3ad5e6e765015df08636 \
--hash=sha256:ad9e82fb8f09ade1c3e1b996a6337afac2b8b9e365f926f5a61aacc71adc5b3c \
--hash=sha256:af598ed32d6ae86f1b747b82783958b1a4ab8f617b06fe68795c7f026abbdcad \
--hash=sha256:b076b6226fb84157e3f7c971a47ff3a679d837cf338547532ab866c57930dbee \
--hash=sha256:b7ff0f54cb4ff66dd38bebd335a38e2c22c41a8ee45aa608efc890ac3e3931bc \
--hash=sha256:bfce63a9e7834b12b87c64d6b155fdd9b3b96191b6bd334bf37db7ff1fe457f2 \
--hash=sha256:c011a4149cfbcf9f03994ec2edffcb8b1dc2d2aede7ca243746df97a5d41ce48 \
--hash=sha256:c9c804664ebe8f83a211cace637506669e7890fec1b4195b505c214e50dd4eb7 \
--hash=sha256:ca379055a47383d02a5400cb0d110cef0a776fc644cda797db0c5696cfd7e18e \
--hash=sha256:cb0932dc158471523c9637e807d9bfb93e06a95cbf010f1a38b98623b929ef2b \
--hash=sha256:cd0f502fe016460680cd20aaa5a76d241d6f35a1c3350c474bac1273803893fa \
--hash=sha256:ceb01949af7121f9fc39f7d27f91be8546f3fb112c608bc4029aef0bab86a2a5 \
--hash=sha256:d080e0a5eb2529460b30190fcfcc4199bd7f827663f858a226a81bc27beaa97e \
--hash=sha256:dd15ff04ffd7e05ffcb7fe79f1b98041b8ea30ae9234aed2a9168b5797c3effb \
--hash=sha256:df0be2b576a7abbf737b1575f048c23fb1d769f267ec4358296f31c2479db8f9 \
--hash=sha256:e09031c87a1e51556fdcb46e5bd4f59dfb743061cf93c4d6831bf894f125eb57 \
--hash=sha256:e4dd52d80b8c83fdce44e12478ad2e85c64ea965e75d66dbeafb0a3e77308fcc \
--hash=sha256:f698de3fd0c4e6972b92290a45bd9b1536bffe8c6759c62471efaa8acb4c37bc \
--hash=sha256:fec21693218efe39aa7f8599346e90c705afa52c5b31ae019b2e57e8f6542bb2 \
--hash=sha256:ffcc3f7c66b5f5b7931a5aa68fc9cecc51e685ef90282f4a82f0f5e9b704ad11
# via jinja2
mccabe==0.7.0 \
--hash=sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325 \
--hash=sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e
# via flake8
mdurl==0.1.2 \
--hash=sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8 \
--hash=sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba
# via markdown-it-py
more-itertools==10.1.0 \
--hash=sha256:626c369fa0eb37bac0291bce8259b332fd59ac792fa5497b59837309cd5b114a \
--hash=sha256:64e0735fcfdc6f3464ea133afe8ea4483b1c5fe3a3d69852e6503b43a0b222e6
# via jaraco-classes
mypy==1.7.1 \
--hash=sha256:12cce78e329838d70a204293e7b29af9faa3ab14899aec397798a4b41be7f340 \
--hash=sha256:1484b8fa2c10adf4474f016e09d7a159602f3239075c7bf9f1627f5acf40ad49 \
--hash=sha256:204e0d6de5fd2317394a4eff62065614c4892d5a4d1a7ee55b765d7a3d9e3f82 \
--hash=sha256:2643d145af5292ee956aa0a83c2ce1038a3bdb26e033dadeb2f7066fb0c9abce \
--hash=sha256:2c6e4464ed5f01dc44dc9821caf67b60a4e5c3b04278286a85c067010653a0eb \
--hash=sha256:2f7f6985d05a4e3ce8255396df363046c28bea790e40617654e91ed580ca7c51 \
--hash=sha256:31902408f4bf54108bbfb2e35369877c01c95adc6192958684473658c322c8a5 \
--hash=sha256:40716d1f821b89838589e5b3106ebbc23636ffdef5abc31f7cd0266db936067e \
--hash=sha256:4b901927f16224d0d143b925ce9a4e6b3a758010673eeded9b748f250cf4e8f7 \
--hash=sha256:4fc3d14ee80cd22367caaaf6e014494415bf440980a3045bf5045b525680ac33 \
--hash=sha256:5cf3f0c5ac72139797953bd50bc6c95ac13075e62dbfcc923571180bebb662e9 \
--hash=sha256:6dbdec441c60699288adf051f51a5d512b0d818526d1dcfff5a41f8cd8b4aaf1 \
--hash=sha256:72cf32ce7dd3562373f78bd751f73c96cfb441de147cc2448a92c1a308bd0ca6 \
--hash=sha256:75aa828610b67462ffe3057d4d8a4112105ed211596b750b53cbfe182f44777a \
--hash=sha256:75c4d2a6effd015786c87774e04331b6da863fc3fc4e8adfc3b40aa55ab516fe \
--hash=sha256:78e25b2fd6cbb55ddfb8058417df193f0129cad5f4ee75d1502248e588d9e0d7 \
--hash=sha256:84860e06ba363d9c0eeabd45ac0fde4b903ad7aa4f93cd8b648385a888e23200 \
--hash=sha256:8c5091ebd294f7628eb25ea554852a52058ac81472c921150e3a61cdd68f75a7 \
--hash=sha256:944bdc21ebd620eafefc090cdf83158393ec2b1391578359776c00de00e8907a \
--hash=sha256:9c7ac372232c928fff0645d85f273a726970c014749b924ce5710d7d89763a28 \
--hash=sha256:d9b338c19fa2412f76e17525c1b4f2c687a55b156320acb588df79f2e6fa9fea \
--hash=sha256:ee5d62d28b854eb61889cde4e1dbc10fbaa5560cb39780c3995f6737f7e82120 \
--hash=sha256:f2c2521a8e4d6d769e3234350ba7b65ff5d527137cdcde13ff4d99114b0c8e7d \
--hash=sha256:f6efc9bd72258f89a3816e3a98c09d36f079c223aa345c659622f056b760ab42 \
--hash=sha256:f7c5d642db47376a0cc130f0de6d055056e010debdaf0707cd2b0fc7e7ef30ea \
--hash=sha256:fcb6d9afb1b6208b4c712af0dafdc650f518836065df0d4fb1d800f5d6773db2 \
--hash=sha256:fcd2572dd4519e8a6642b733cd3a8cfc1ef94bafd0c1ceed9c94fe736cb65b6a
# via
# django-example-ynh (pyproject.toml)
# manageprojects
mypy-extensions==1.0.0 \
--hash=sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d \
--hash=sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782
# via
# black
# mypy
nh3==0.2.14 \
--hash=sha256:116c9515937f94f0057ef50ebcbcc10600860065953ba56f14473ff706371873 \
--hash=sha256:18415df36db9b001f71a42a3a5395db79cf23d556996090d293764436e98e8ad \
--hash=sha256:203cac86e313cf6486704d0ec620a992c8bc164c86d3a4fd3d761dd552d839b5 \
--hash=sha256:2b0be5c792bd43d0abef8ca39dd8acb3c0611052ce466d0401d51ea0d9aa7525 \
--hash=sha256:377aaf6a9e7c63962f367158d808c6a1344e2b4f83d071c43fbd631b75c4f0b2 \
--hash=sha256:525846c56c2bcd376f5eaee76063ebf33cf1e620c1498b2a40107f60cfc6054e \
--hash=sha256:5529a3bf99402c34056576d80ae5547123f1078da76aa99e8ed79e44fa67282d \
--hash=sha256:7771d43222b639a4cd9e341f870cee336b9d886de1ad9bec8dddab22fe1de450 \
--hash=sha256:88c753efbcdfc2644a5012938c6b9753f1c64a5723a67f0301ca43e7b85dcf0e \
--hash=sha256:93a943cfd3e33bd03f77b97baa11990148687877b74193bf777956b67054dcc6 \
--hash=sha256:9be2f68fb9a40d8440cbf34cbf40758aa7f6093160bfc7fb018cce8e424f0c3a \
--hash=sha256:a0c509894fd4dccdff557068e5074999ae3b75f4c5a2d6fb5415e782e25679c4 \
--hash=sha256:ac8056e937f264995a82bf0053ca898a1cb1c9efc7cd68fa07fe0060734df7e4 \
--hash=sha256:aed56a86daa43966dd790ba86d4b810b219f75b4bb737461b6886ce2bde38fd6 \
--hash=sha256:e8986f1dd3221d1e741fda0a12eaa4a273f1d80a35e31a1ffe579e7c621d069e \
--hash=sha256:f99212a81c62b5f22f9e7c3e347aa00491114a5647e1f13bbebd79c3e5f08d75
# via readme-renderer
packaging==23.2 \
--hash=sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5 \
--hash=sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7
# via
# black
# build
# django-yunohost-integration
# dparse
# gunicorn
# pyproject-api
# safety
# tox
pathspec==0.11.2 \
--hash=sha256:1d6ed233af05e679efb96b1851550ea95bbb64b7c490b0f5aa52996c11e92a20 \
--hash=sha256:e0d8d0ac2f12da61956eb2306b69f9469b42f4deb0f3cb6ed47b9cce9996ced3
# via black
pip-tools==7.3.0 \
--hash=sha256:8717693288720a8c6ebd07149c93ab0be1fced0b5191df9e9decd3263e20d85e \
--hash=sha256:8e9c99127fe024c025b46a0b2d15c7bd47f18f33226cf7330d35493663fc1d1d
# via django-example-ynh (pyproject.toml)
pkginfo==1.9.6 \
--hash=sha256:4b7a555a6d5a22169fcc9cf7bfd78d296b0361adad412a346c1226849af5e546 \
--hash=sha256:8fd5896e8718a4372f0ea9cc9d96f6417c9b986e23a4d116dda26b62cc29d046
# via twine
platformdirs==4.0.0 \
--hash=sha256:118c954d7e949b35437270383a3f2531e99dd93cf7ce4dc8340d3356d30f173b \
--hash=sha256:cb633b2bcf10c51af60beb0ab06d2f1d69064b43abf4c185ca6b28865f3f9731
# via
# black
# tox
# virtualenv
pluggy==1.3.0 \
--hash=sha256:cf61ae8f126ac6f7c451172cf30e3e43d3ca77615509771b3a984a0730651e12 \
--hash=sha256:d89c696a773f8bd377d18e5ecda92b7a3793cbe66c87060a6fb58c7b6e1061f7
# via tox
pprintpp==0.4.0 \
--hash=sha256:b6b4dcdd0c0c0d75e4d7b2f21a9e933e5b2ce62b26e1a54537f9651ae5a5c01d \
--hash=sha256:ea826108e2c7f49dc6d66c752973c3fc9749142a798d6b254e1e301cfdbc6403
# via django-tools
psycopg2==2.9.9 \
--hash=sha256:121081ea2e76729acfb0673ff33755e8703d45e926e416cb59bae3a86c6a4981 \
--hash=sha256:38a8dcc6856f569068b47de286b472b7c473ac7977243593a288ebce0dc89516 \
--hash=sha256:426f9f29bde126913a20a96ff8ce7d73fd8a216cfb323b1f04da402d452853c3 \
--hash=sha256:5e0d98cade4f0e0304d7d6f25bbfbc5bd186e07b38eac65379309c4ca3193efa \
--hash=sha256:7e2dacf8b009a1c1e843b5213a87f7c544b2b042476ed7755be813eaf4e8347a \
--hash=sha256:a7653d00b732afb6fc597e29c50ad28087dcb4fbfb28e86092277a559ae4e693 \
--hash=sha256:ade01303ccf7ae12c356a5e10911c9e1c51136003a9a1d92f7aa9d010fb98372 \
--hash=sha256:bac58c024c9922c23550af2a581998624d6e02350f4ae9c5f0bc642c633a2d5e \
--hash=sha256:c92811b2d4c9b6ea0285942b2e7cac98a59e166d59c588fe5cfe1eda58e72d59 \
--hash=sha256:d1454bde93fb1e224166811694d600e746430c006fbb031ea06ecc2ea41bf156 \
--hash=sha256:d735786acc7dd25815e89cc4ad529a43af779db2e25aa7c626de864127e5a024 \
--hash=sha256:de80739447af31525feddeb8effd640782cf5998e1a4e9192ebdf829717e3913 \
--hash=sha256:ff432630e510709564c01dafdbe996cb552e0b9f3f065eb89bdce5bd31fabf4c
# via django-yunohost-integration
pycodestyle==2.11.1 \
--hash=sha256:41ba0e7afc9752dfb53ced5489e89f8186be00e599e712660695b7a75ff2663f \
--hash=sha256:44fe31000b2d866f2e41841b18528a505fbd7fef9017b04eff4e2648a0fadc67
# via
# autopep8
# flake8
pycparser==2.21 \
--hash=sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9 \
--hash=sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206
# via cffi
pyflakes==3.1.0 \
--hash=sha256:4132f6d49cb4dae6819e5379898f2b8cce3c5f23994194c24b77d5da2e36f774 \
--hash=sha256:a0aae034c444db0071aa077972ba4768d40c830d9539fd45bf4cd3f8f6992efc
# via
# autoflake
# django-example-ynh (pyproject.toml)
# flake8
# manageprojects
pygments==2.17.2 \
--hash=sha256:b27c2826c47d0f3219f29554824c30c5e8945175d888647acd804ddd04af846c \
--hash=sha256:da46cec9fd2de5be3a8a784f434e4c4ab670b4ff54d605c4c2717e9d49c4c367
# via
# darker
# readme-renderer
# rich
pyproject-api==1.6.1 \
--hash=sha256:1817dc018adc0d1ff9ca1ed8c60e1623d5aaca40814b953af14a9cf9a5cae538 \
--hash=sha256:4c0116d60476b0786c88692cf4e325a9814965e2469c5998b830bba16b183675
# via tox
pyproject-hooks==1.0.0 \
--hash=sha256:283c11acd6b928d2f6a7c73fa0d01cb2bdc5f07c57a2eeb6e83d5e56b97976f8 \
--hash=sha256:f271b298b97f5955d53fb12b72c1fb1948c22c1a6b70b315c54cedaca0264ef5
# via build
python-dateutil==2.8.2 \
--hash=sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86 \
--hash=sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9
# via arrow
python-slugify==8.0.1 \
--hash=sha256:70ca6ea68fe63ecc8fa4fcf00ae651fc8a5d02d93dcd12ae6d4fc7ca46c4d395 \
--hash=sha256:ce0d46ddb668b3be82f4ed5e503dbc33dd815d83e2eb6824211310d3fb172a27
# via cookiecutter
python-stdnum==1.19 \
--hash=sha256:133ec82f56390ea74c190569e98f2fb14b869808b1d54785708f22d0fead8b3f \
--hash=sha256:1b5b401ad3f45b798b0317313b781a433f5d7a5ff2c9feb8054664f76f78644e
# via bx-django-utils
pyupgrade==3.15.0 \
--hash=sha256:8dc8ebfaed43566e2c65994162795017c7db11f531558a74bc8aa077907bc305 \
--hash=sha256:a7fde381060d7c224f55aef7a30fae5ac93bbc428367d27e70a603bc2acd4f00
# via
# django-example-ynh (pyproject.toml)
# manageprojects
pyyaml==6.0.1 \
--hash=sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5 \
--hash=sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc \
--hash=sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df \
--hash=sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741 \
--hash=sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206 \
--hash=sha256:18aeb1bf9a78867dc38b259769503436b7c72f7a1f1f4c93ff9a17de54319b27 \
--hash=sha256:1d4c7e777c441b20e32f52bd377e0c409713e8bb1386e1099c2415f26e479595 \
--hash=sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62 \
--hash=sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98 \
--hash=sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696 \
--hash=sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290 \
--hash=sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9 \
--hash=sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d \
--hash=sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6 \
--hash=sha256:4fb147e7a67ef577a588a0e2c17b6db51dda102c71de36f8549b6816a96e1867 \
--hash=sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47 \
--hash=sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486 \
--hash=sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6 \
--hash=sha256:596106435fa6ad000c2991a98fa58eeb8656ef2325d7e158344fb33864ed87e3 \
--hash=sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007 \
--hash=sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938 \
--hash=sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0 \
--hash=sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c \
--hash=sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735 \
--hash=sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d \
--hash=sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28 \
--hash=sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4 \
--hash=sha256:9046c58c4395dff28dd494285c82ba00b546adfc7ef001486fbf0324bc174fba \
--hash=sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8 \
--hash=sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5 \
--hash=sha256:afd7e57eddb1a54f0f1a974bc4391af8bcce0b444685d936840f125cf046d5bd \
--hash=sha256:b1275ad35a5d18c62a7220633c913e1b42d44b46ee12554e5fd39c70a243d6a3 \
--hash=sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0 \
--hash=sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515 \
--hash=sha256:baa90d3f661d43131ca170712d903e6295d1f7a0f595074f151c0aed377c9b9c \
--hash=sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c \
--hash=sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924 \
--hash=sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34 \
--hash=sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43 \
--hash=sha256:c8098ddcc2a85b61647b2590f825f3db38891662cfc2fc776415143f599bb859 \
--hash=sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673 \
--hash=sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54 \
--hash=sha256:d858aa552c999bc8a8d57426ed01e40bef403cd8ccdd0fc5f6f04a00414cac2a \
--hash=sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b \
--hash=sha256:f003ed9ad21d6a4713f0a9b5a7a0a79e08dd0f221aff4525a2be4c346ee60aab \
--hash=sha256:f22ac1c3cac4dbc50079e965eba2c1058622631e526bd9afd45fedd49ba781fa \
--hash=sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c \
--hash=sha256:fca0e3a251908a499833aa292323f32437106001d436eca0e6e7833256674585 \
--hash=sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d \
--hash=sha256:fd66fc5d0da6d9815ba2cebeb4205f95818ff4b79c3ebe268e75d961704af52f
# via
# cookiecutter
# django-yunohost-integration
readme-renderer==42.0 \
--hash=sha256:13d039515c1f24de668e2c93f2e877b9dbe6c6c32328b90a40a49d8b2b85f36d \
--hash=sha256:2d55489f83be4992fe4454939d1a051c33edbab778e82761d060c9fc6b308cd1
# via twine
redis==5.0.1 \
--hash=sha256:0dab495cd5753069d3bc650a0dde8a8f9edde16fc5691b689a566eda58100d0f \
--hash=sha256:ed4802971884ae19d640775ba3b03aa2e7bd5e8fb8dfaed2decce4d0fc48391f
# via django-redis
requests==2.31.0 \
--hash=sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f \
--hash=sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1
# via
# cookiecutter
# requests-toolbelt
# safety
# twine
requests-toolbelt==1.0.0 \
--hash=sha256:7681a0a3d047012b5bdc0ee37d7f8f07ebe76ab08caeccfc3921ce23c88d5bc6 \
--hash=sha256:cccfdd665f0a24fcf4726e690f65639d272bb0637b9b92dfd91a5568ccf6bd06
# via twine
rfc3986==2.0.0 \
--hash=sha256:50b1502b60e289cb37883f3dfd34532b8873c7de9f49bb546641ce9cbd256ebd \
--hash=sha256:97aacf9dbd4bfd829baad6e6309fa6573aaf1be3f6fa735c8ab05e46cecb261c
# via twine
rich==13.7.0 \
--hash=sha256:5cb5123b5cf9ee70584244246816e9114227e0b98ad9176eede6ad54bf5403fa \
--hash=sha256:6da14c108c4866ee9520bbffa71f6fe3962e193b7da68720583850cd4548e235
# via
# cli-base-utilities
# cookiecutter
# manageprojects
# rich-click
# twine
rich-click==1.7.2 \
--hash=sha256:22f93439a3d65f4a04e07cd584f4d01d132d96899766af92ed287618156abbe2 \
--hash=sha256:a42bcdcb8696c4ca7a3b1a39e1aba3d2cb64ad00690b4c022fdcb2cbccebc3fc
# via
# cli-base-utilities
# manageprojects
ruamel-yaml==0.18.5 \
--hash=sha256:61917e3a35a569c1133a8f772e1226961bf5a1198bea7e23f06a0841dea1ab0e \
--hash=sha256:a013ac02f99a69cdd6277d9664689eb1acba07069f912823177c5eced21a6ada
# via safety
ruamel-yaml-clib==0.2.8 \
--hash=sha256:024cfe1fc7c7f4e1aff4a81e718109e13409767e4f871443cbff3dba3578203d \
--hash=sha256:03d1162b6d1df1caa3a4bd27aa51ce17c9afc2046c31b0ad60a0a96ec22f8001 \
--hash=sha256:07238db9cbdf8fc1e9de2489a4f68474e70dffcb32232db7c08fa61ca0c7c462 \
--hash=sha256:09b055c05697b38ecacb7ac50bdab2240bfca1a0c4872b0fd309bb07dc9aa3a9 \
--hash=sha256:1707814f0d9791df063f8c19bb51b0d1278b8e9a2353abbb676c2f685dee6afe \
--hash=sha256:1758ce7d8e1a29d23de54a16ae867abd370f01b5a69e1a3ba75223eaa3ca1a1b \
--hash=sha256:184565012b60405d93838167f425713180b949e9d8dd0bbc7b49f074407c5a8b \
--hash=sha256:1b617618914cb00bf5c34d4357c37aa15183fa229b24767259657746c9077615 \
--hash=sha256:1dc67314e7e1086c9fdf2680b7b6c2be1c0d8e3a8279f2e993ca2a7545fecf62 \
--hash=sha256:25ac8c08322002b06fa1d49d1646181f0b2c72f5cbc15a85e80b4c30a544bb15 \
--hash=sha256:25c515e350e5b739842fc3228d662413ef28f295791af5e5110b543cf0b57d9b \
--hash=sha256:305889baa4043a09e5b76f8e2a51d4ffba44259f6b4c72dec8ca56207d9c6fe1 \
--hash=sha256:3213ece08ea033eb159ac52ae052a4899b56ecc124bb80020d9bbceeb50258e9 \
--hash=sha256:3f215c5daf6a9d7bbed4a0a4f760f3113b10e82ff4c5c44bec20a68c8014f675 \
--hash=sha256:46d378daaac94f454b3a0e3d8d78cafd78a026b1d71443f4966c696b48a6d899 \
--hash=sha256:4ecbf9c3e19f9562c7fdd462e8d18dd902a47ca046a2e64dba80699f0b6c09b7 \
--hash=sha256:53a300ed9cea38cf5a2a9b069058137c2ca1ce658a874b79baceb8f892f915a7 \
--hash=sha256:56f4252222c067b4ce51ae12cbac231bce32aee1d33fbfc9d17e5b8d6966c312 \
--hash=sha256:5c365d91c88390c8d0a8545df0b5857172824b1c604e867161e6b3d59a827eaa \
--hash=sha256:700e4ebb569e59e16a976857c8798aee258dceac7c7d6b50cab63e080058df91 \
--hash=sha256:75e1ed13e1f9de23c5607fe6bd1aeaae21e523b32d83bb33918245361e9cc51b \
--hash=sha256:77159f5d5b5c14f7c34073862a6b7d34944075d9f93e681638f6d753606c6ce6 \
--hash=sha256:7f67a1ee819dc4562d444bbafb135832b0b909f81cc90f7aa00260968c9ca1b3 \
--hash=sha256:840f0c7f194986a63d2c2465ca63af8ccbbc90ab1c6001b1978f05119b5e7334 \
--hash=sha256:84b554931e932c46f94ab306913ad7e11bba988104c5cff26d90d03f68258cd5 \
--hash=sha256:87ea5ff66d8064301a154b3933ae406b0863402a799b16e4a1d24d9fbbcbe0d3 \
--hash=sha256:955eae71ac26c1ab35924203fda6220f84dce57d6d7884f189743e2abe3a9fbe \
--hash=sha256:a1a45e0bb052edf6a1d3a93baef85319733a888363938e1fc9924cb00c8df24c \
--hash=sha256:a5aa27bad2bb83670b71683aae140a1f52b0857a2deff56ad3f6c13a017a26ed \
--hash=sha256:a6a9ffd280b71ad062eae53ac1659ad86a17f59a0fdc7699fd9be40525153337 \
--hash=sha256:a75879bacf2c987c003368cf14bed0ffe99e8e85acfa6c0bfffc21a090f16880 \
--hash=sha256:aa2267c6a303eb483de8d02db2871afb5c5fc15618d894300b88958f729ad74f \
--hash=sha256:aab7fd643f71d7946f2ee58cc88c9b7bfc97debd71dcc93e03e2d174628e7e2d \
--hash=sha256:b16420e621d26fdfa949a8b4b47ade8810c56002f5389970db4ddda51dbff248 \
--hash=sha256:b42169467c42b692c19cf539c38d4602069d8c1505e97b86387fcf7afb766e1d \
--hash=sha256:bba64af9fa9cebe325a62fa398760f5c7206b215201b0ec825005f1b18b9bccf \
--hash=sha256:beb2e0404003de9a4cab9753a8805a8fe9320ee6673136ed7f04255fe60bb512 \
--hash=sha256:bef08cd86169d9eafb3ccb0a39edb11d8e25f3dae2b28f5c52fd997521133069 \
--hash=sha256:c2a72e9109ea74e511e29032f3b670835f8a59bbdc9ce692c5b4ed91ccf1eedb \
--hash=sha256:c58ecd827313af6864893e7af0a3bb85fd529f862b6adbefe14643947cfe2942 \
--hash=sha256:c69212f63169ec1cfc9bb44723bf2917cbbd8f6191a00ef3410f5a7fe300722d \
--hash=sha256:cabddb8d8ead485e255fe80429f833172b4cadf99274db39abc080e068cbcc31 \
--hash=sha256:d176b57452ab5b7028ac47e7b3cf644bcfdc8cacfecf7e71759f7f51a59e5c92 \
--hash=sha256:da09ad1c359a728e112d60116f626cc9f29730ff3e0e7db72b9a2dbc2e4beed5 \
--hash=sha256:e2b4c44b60eadec492926a7270abb100ef9f72798e18743939bdbf037aab8c28 \
--hash=sha256:e79e5db08739731b0ce4850bed599235d601701d5694c36570a99a0c5ca41a9d \
--hash=sha256:ebc06178e8821efc9692ea7544aa5644217358490145629914d8020042c24aa1 \
--hash=sha256:edaef1c1200c4b4cb914583150dcaa3bc30e592e907c01117c08b13a07255ec2 \
--hash=sha256:f481f16baec5290e45aebdc2a5168ebc6d35189ae6fea7a58787613a25f6e875 \
--hash=sha256:fff3573c2db359f091e1589c3d7c5fc2f86f5bdb6f24252c2d8e539d4e45f412
# via ruamel-yaml
safety==2.3.4 \
--hash=sha256:6224dcd9b20986a2b2c5e7acfdfba6bca42bb11b2783b24ed04f32317e5167ea \
--hash=sha256:b9e74e794e82f54d11f4091c5d820c4d2d81de9f953bf0b4f33ac8bc402ae72c
# via django-example-ynh (pyproject.toml)
secretstorage==3.3.3 \
--hash=sha256:2403533ef369eca6d2ba81718576c5e0f564d5cca1b58f73a8b23e7d4eeebd77 \
--hash=sha256:f356e6628222568e3af06f2eba8df495efa13b3b63081dafd4f7d9a7b7bc9f99
# via keyring
six==1.16.0 \
--hash=sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926 \
--hash=sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254
# via
# bleach
# python-dateutil
soupsieve==2.5 \
--hash=sha256:5663d5a7b3bfaeee0bc4372e7fc48f9cff4940b3eec54a6451cc5299f1097690 \
--hash=sha256:eaa337ff55a1579b6549dc679565eac1e3d000563bcb1c8ab0d0fefbc0c2cdc7
# via beautifulsoup4
sqlparse==0.4.4 \
--hash=sha256:5430a4fe2ac7d0f93e66f1efc6e1338a41884b7ddf2a350cedd20ccc4d9d28f3 \
--hash=sha256:d446183e84b8349fa3061f0fe7f06ca94ba65b426946ffebe6e3e8295332420c
# via django
tblib==3.0.0 \
--hash=sha256:80a6c77e59b55e83911e1e607c649836a69c103963c5f28a46cbeef44acf8129 \
--hash=sha256:93622790a0a29e04f0346458face1e144dc4d32f493714c6c3dff82a4adb77e6
# via django-example-ynh (pyproject.toml)
text-unidecode==1.3 \
--hash=sha256:1311f10e8b895935241623731c2ba64f4c455287888b18189350b67134a822e8 \
--hash=sha256:bad6603bb14d279193107714b288be206cac565dfa49aa5b105294dd5c4aab93
# via python-slugify
tokenize-rt==5.2.0 \
--hash=sha256:9fe80f8a5c1edad2d3ede0f37481cc0cc1538a2f442c9c2f9e4feacd2792d054 \
--hash=sha256:b79d41a65cfec71285433511b50271b05da3584a1da144a0752e9c621a285289
# via pyupgrade
toml==0.10.2 \
--hash=sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b \
--hash=sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f
# via darker
tomli==2.0.1 \
--hash=sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc \
--hash=sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f
# via
# django-example-ynh (pyproject.toml)
# flynt
tomlkit==0.12.3 \
--hash=sha256:75baf5012d06501f07bee5bf8e801b9f343e7aac5a92581f20f80ce632e6b5a4 \
--hash=sha256:b0a645a9156dc7cb5d3a1f0d4bab66db287fcb8e0430bdd4664a095ea16414ba
# via
# cli-base-utilities
# manageprojects
tox==4.11.4 \
--hash=sha256:2adb83d68f27116812b69aa36676a8d6a52249cb0d173649de0e7d0c2e3e7229 \
--hash=sha256:73a7240778fabf305aeb05ab8ea26e575e042ab5a18d71d0ed13e343a51d6ce1
# via django-example-ynh (pyproject.toml)
twine==4.0.2 \
--hash=sha256:929bc3c280033347a00f847236564d1c52a3e61b1ac2516c97c48f3ceab756d8 \
--hash=sha256:9e102ef5fdd5a20661eb88fad46338806c3bd32cf1db729603fe3697b1bc83c8
# via django-example-ynh (pyproject.toml)
types-python-dateutil==2.8.19.14 \
--hash=sha256:1f4f10ac98bb8b16ade9dbee3518d9ace017821d94b057a425b069f834737f4b \
--hash=sha256:f977b8de27787639986b4e28963263fd0e5158942b3ecef91b9335c130cb1ce9
# via arrow
typing-extensions==4.8.0 \
--hash=sha256:8f92fc8806f9a6b641eaa5318da32b44d401efaac0f6678c9bc448ba3605faa0 \
--hash=sha256:df8e4339e9cb77357558cbdbceca33c303714cf861d1eef15e1070055ae8b7ef
# via
# mypy
# rich-click
urllib3==2.1.0 \
--hash=sha256:55901e917a5896a349ff771be919f8bd99aff50b79fe58fec595eb37bbc56bb3 \
--hash=sha256:df7aa8afb0148fa78488e7899b2c59b5f4ffcfa82e6c54ccb9dd37c1d7b52d54
# via
# requests
# twine
virtualenv==20.25.0 \
--hash=sha256:4238949c5ffe6876362d9c0180fc6c3a824a7b12b80604eeb8085f2ed7460de3 \
--hash=sha256:bf51c0d9c7dd63ea8e44086fa1e4fb1093a31e963b86959257378aef020e1f1b
# via tox
webencodings==0.5.1 \
--hash=sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78 \
--hash=sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923
# via bleach
wheel==0.42.0 \
--hash=sha256:177f9c9b0d45c47873b619f5b650346d632cdc35fb5e4d25058e09c9e581433d \
--hash=sha256:c45be39f7882c9d34243236f2d63cbd58039e360f85d0913425fbd7ceea617a8
# via pip-tools
zipp==3.17.0 \
--hash=sha256:0e923e726174922dce09c53c59ad483ff7bbb8e572e00c7f7c46b88556409f31 \
--hash=sha256:84e64a1c28cf7e91ed2078bb8cc8c259cb19b76942096c8d7b84947690cabaf0
# via importlib-metadata
# The following packages are considered to be unsafe in a requirements file:
pip==23.3.1 \
--hash=sha256:1fcaa041308d01f14575f6d0d2ea4b75a3e2871fe4f9c694976f908768e14174 \
--hash=sha256:55eb67bb6171d37447e82213be585b75fe2b12b359e993773aca4de9247a052b
# via pip-tools
setuptools==69.0.2 \
--hash=sha256:1e8fdff6797d3865f37397be788a4e3cba233608e9b509382a2777d25ebde7f2 \
--hash=sha256:735896e78a4742605974de002ac60562d286fa8051a7e2299445e8e8fbb01aa6
# via
# django-axes
# pip-tools
# safety

View file

@ -1,29 +1,129 @@
#!/bin/bash #!/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}
#================================================= #=================================================
# PERSONAL HELPERS # ARGUMENTS FROM CONFIG PANEL
#================================================= #=================================================
_install_fmd_venv() { # 'debug_enabled' -> '__DEBUG_ENABLED__' -> settings.DEBUG
ynh_exec_as "$app" python3 -m venv --upgrade "$install_dir/venv" debug_enabled="0" # "1" or "0" string
venvpython="$install_dir/venv/bin/python3" # 'log_level' -> '__LOG_LEVEL__' -> settings.LOG_LEVEL
log_level="WARNING"
ynh_add_config --template="requirements.txt" --destination="$install_dir/requirements.txt" # 'admin_email' -> '__ADMIN_EMAIL__' add in settings.ADMINS
admin_email="${admin}@${domain}"
ynh_exec_as "$app" "$venvpython" -m ensurepip # 'default_from_email' -> '__DEFAULT_FROM_EMAIL__' -> settings.DEFAULT_FROM_EMAIL
ynh_exec_as "$app" "$venvpython" -m pip install --upgrade wheel pip setuptools default_from_email="${app}@${domain}"
ynh_exec_as "$app" "$venvpython" -m pip install --no-deps -r "$install_dir/requirements.txt"
#=================================================
# 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"
#=================================================
# HELPERS
#=================================================
myynh_setup_python_venv() {
# Always recreate everything fresh with current python version
ynh_secure_remove "$data_dir/venv"
# Skip pip because of: https://github.com/YunoHost/issues/issues/1960
python3 -m venv --without-pip "$data_dir/venv"
chown -c -R "$app:" "$data_dir"
# 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/python3 -m ensurepip
ynh_exec_as $app $data_dir/venv/bin/pip3 install --upgrade wheel pip setuptools
ynh_exec_as $app $data_dir/venv/bin/pip3 install --no-deps -r "$data_dir/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"
)
}
myynh_fix_file_permissions() {
(
set -x
# /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"
)
} }
#================================================= #=================================================
# EXPERIMENTAL HELPERS # Redis HELPERS
#================================================= #=================================================
#================================================= # get the first available redis database
# FUTURE OFFICIAL HELPERS #
#================================================= # usage: ynh_redis_get_free_db
# | returns: the database number to use
ynh_redis_get_free_db() {
local result max db
result=$(redis-cli INFO keyspace)
# get the num
max=$(cat /etc/redis/redis.conf | grep ^databases | grep -Eow "[0-9]+")
db=0
# default Debian setting is 15 databases
for i in $(seq 0 "$max")
do
if ! echo "$result" | grep -q "db$i"
then
db=$i
break 1
fi
db=-1
done
test "$db" -eq -1 && ynh_die "No available Redis databases..."
echo "$db"
}
# Create a master password and set up global settings
# Please always call this script in install and restore scripts
#
# usage: ynh_redis_remove_db database
# | arg: database - the database to erase
ynh_redis_remove_db() {
local db=$1
redis-cli -n "$db" flushall
}

View file

@ -18,27 +18,38 @@ ynh_print_info --message="Declaring files to be backed up..."
# BACKUP THE APP MAIN DIR # BACKUP THE APP MAIN DIR
#================================================= #=================================================
# /var/www/$app/
ynh_backup --src_path="$install_dir" ynh_backup --src_path="$install_dir"
# /home/yunohost.app/$app/
ynh_backup --src_path="$data_dir"
#================================================= #=================================================
# SYSTEM CONFIGURATION # BACKUP THE NGINX CONFIGURATION
#================================================= #=================================================
ynh_backup --src_path="/etc/nginx/conf.d/$domain.d/$app.conf" ynh_backup --src_path="/etc/nginx/conf.d/$domain.d/$app.conf"
ynh_backup --src_path="/etc/systemd/system/$app.service"
ynh_backup --src_path="/etc/logrotate.d/$app"
ynh_backup --src_path="/var/log/$app/"
#================================================= #=================================================
# BACKUP THE PostgreSQL DATABASE # BACKUP THE PostgreSQL DATABASE
#================================================= #=================================================
ynh_print_info --message="Backing up the PostgreSQL database..."
ynh_psql_dump_db --database="$db_name" > db.sql 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 # END OF SCRIPT
#================================================= #=================================================

View file

@ -16,34 +16,31 @@ source /usr/share/yunohost/helpers
#================================================= #=================================================
ynh_script_progression --message="Stopping systemd service '$app'..." 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"
#=================================================
# STANDARD MODIFICATIONS
#================================================= #=================================================
# MODIFY URL IN NGINX CONF # MODIFY URL IN NGINX CONF
#================================================= #=================================================
ynh_script_progression --message="Updating NGINX web server configuration..." ynh_script_progression --message="Updating nginx web server configuration..."
ynh_change_url_nginx_config ynh_change_url_nginx_config
#================================================= #=================================================
# SPECIFIC MODIFICATIONS # UPDATE DJANGO SETTINGS
#================================================= #=================================================
# MODIFY SETTINGS ynh_script_progression --message="Update $app settings file..." --weight=1
#=================================================
ynh_script_progression --message="Modify $app config file..."
ynh_add_config --template="settings.py" --destination="$install_dir/settings.py" path=$new_path
domain=$new_domain
ynh_add_config --template="settings.py" --destination="$data_dir/settings.py"
#=================================================
# GENERIC FINALISATION
#================================================= #=================================================
# START SYSTEMD SERVICE # START SYSTEMD SERVICE
#================================================= #=================================================
ynh_script_progression --message="Starting systemd service '$app'..." --weight=5 ynh_script_progression --message="Starting systemd service '$app'..." --weight=5
ynh_systemd_action --service_name="$app" --action="start" ynh_systemd_action --service_name=$app --action="start" --log_path="$log_file"
#================================================= #=================================================
# END OF SCRIPT # END OF SCRIPT

View file

@ -7,110 +7,147 @@
source _common.sh source _common.sh
source /usr/share/yunohost/helpers source /usr/share/yunohost/helpers
# 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
# ...
mkdir -p "$install_dir/public/media" "$install_dir/public/static" #
mkdir -p "$install_dir" # $app is the app id (i.e. 'example' for first install,
# or 'example__2', '__3', ... for multi-instance installs)
#
mkdir -p "/var/log/$app" #=================================================
touch "/var/log/$app/$app.log" # 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) 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"
# 'debug_enabled' -> '__DEBUG_ENABLED__' -> settings.DEBUG # App settings:
debug_enabled="0" 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"
# 'log_level' -> '__LOG_LEVEL__' -> settings.LOG_LEVEL 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"
# '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}"
ynh_app_setting_set --app="$app" --key=debug_enabled --value="$debug_enabled"
ynh_app_setting_set --app="$app" --key=log_level --value="$log_level"
ynh_app_setting_set --app="$app" --key=admin_email --value="$admin_email"
ynh_app_setting_set --app="$app" --key=default_from_email --value="$default_from_email"
#================================================= #=================================================
# INSTALLATION # CHECK IF THE APP CAN BE INSTALLED WITH THESE ARGS
#================================================= #=================================================
ynh_script_progression --message="Installing project via pip..." --weight=45 ynh_script_progression --message="Validating installation parameters..."
_install_fmd_venv mkdir -p "$install_dir/media" "$install_dir/static"
mkdir -p "$install_dir/public/media" "$install_dir/public/static" #=================================================
# SETUP LOG FILE
#=================================================
ynh_script_progression --message="Setup logging..."
chmod o-rwx "$install_dir" myynh_setup_log_file
chown -R "$app:www-data" "$install_dir"
mkdir -p "/var/log/$app" # Use logrotate to manage application logfile(s)
touch "/var/log/$app/$app.log" ynh_use_logrotate --logfile="$log_file" --specific_user=$app
chmod o-rwx "/var/log/$app" #=================================================
chown -R "$app:$app" "/var/log/$app" # PYTHON VIRTUALENV
#=================================================
ynh_script_progression --message="Create and setup Python virtualenv..." --weight=45
cp ../conf/requirements.txt "$data_dir/requirements.txt"
myynh_setup_python_venv
#================================================= #=================================================
# copy config files # copy config files
# ================================================ # ================================================
ynh_script_progression --message="Create $app configuration files..." ynh_script_progression --message="Create $app configuration files..."
ynh_add_config --template="gunicorn.conf.py" --destination="$install_dir/gunicorn.conf.py" ynh_add_config --template="gunicorn.conf.py" --destination="$data_dir/gunicorn.conf.py"
ynh_add_config --template="manage.py" --destination="$install_dir/manage.py" ynh_add_config --template="manage.py" --destination="$data_dir/manage.py"
chmod +x "$install_dir/manage.py" chmod -c +x "$data_dir/manage.py"
ynh_add_config --template="settings.py" --destination="$install_dir/settings.py" ynh_add_config --template="settings.py" --destination="$data_dir/settings.py"
ynh_add_config --template="setup_user.py" --destination="$install_dir/setup_user.py" ynh_add_config --template="setup_user.py" --destination="$data_dir/setup_user.py"
ynh_add_config --template="urls.py" --destination="$install_dir/urls.py" ynh_add_config --template="urls.py" --destination="$data_dir/urls.py"
ynh_add_config --template="wsgi.py" --destination="$install_dir/wsgi.py" ynh_add_config --template="wsgi.py" --destination="$data_dir/wsgi.py"
touch "$install_dir/local_settings.py" touch "$data_dir/local_settings.py"
#================================================= #=================================================
# MIGRATE / COLLECTSTATIC / CREATEADMIN # MIGRATE / COLLECTSTATIC / CREATEADMIN
#================================================= #=================================================
ynh_script_progression --message="migrate/collectstatic/createadmin..." --weight=10 ynh_script_progression --message="migrate/collectstatic/createadmin..." --weight=10
pushd "$install_dir" cd "$data_dir" || exit
# Just for debugging:
ynh_exec_as "$app" "$venvpython" ./manage.py diffsettings
ynh_exec_as "$app" "$venvpython" ./manage.py migrate --no-input # Just for debugging:
ynh_exec_as "$app" "$venvpython" ./manage.py collectstatic --no-input ./manage.py diffsettings
# Create/update Django superuser (set unusable password, because auth done via SSOwat): ./manage.py migrate --no-input
ynh_exec_as "$app" "$venvpython" ./manage.py create_superuser --username="$admin" --email="$(ynh_user_get_info "$admin" mail)" ./manage.py collectstatic --no-input
# Check the configuration # Create/update Django superuser (set unusable password, because auth done via SSOwat):
# This may fail in some cases with errors, etc., but the app works and the user can fix issues later. ./manage.py create_superuser --username="$admin" --email="$(ynh_user_get_info "$admin" mail)"
ynh_exec_as "$app" "$venvpython" ./manage.py check --deploy || true
popd # 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
#================================================= #=================================================
# SYSTEM CONFIGURATION # INTEGRATE SERVICE IN YUNOHOST
#================================================= #=================================================
ynh_script_progression --message="Adding system configurations related to $app..." --weight=1 ynh_script_progression --message="Integrating service in YunoHost..."
# Create a dedicated nginx config yunohost service add $app
ynh_add_nginx_config
# Create a dedicated systemd config #=================================================
ynh_add_systemd_config --service="$app" --template="systemd.service" # GENERIC FINALIZATION
yunohost service add "$app" --description="$app service" --log="/var/log/$app/$app.log" #=================================================
# SECURE FILES AND DIRECTORIES
#=================================================
ynh_script_progression --message="Set file permissions..."
myynh_fix_file_permissions
# Use logrotate to manage app-specific logfile(s) #=================================================
ynh_use_logrotate # 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 # Start the app server via systemd
#================================================= #=================================================
ynh_script_progression --message="Starting systemd service '$app'..." --weight=5 ynh_script_progression --message="Starting systemd service '$app'..." --weight=5
ynh_systemd_action --service_name="$app" --action="start" 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 # END OF SCRIPT

View file

@ -10,22 +10,57 @@ source _common.sh
source /usr/share/yunohost/helpers 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 a service from the admin panel, added by `yunohost service add` # Remove a service from the admin panel, added by `yunohost service add`
if yunohost service status "$app" >/dev/null 2>&1; then if yunohost service status $app >/dev/null 2>&1
yunohost service remove "$app" then
ynh_script_progression --message="Removing $app service integration..."
yunohost service remove $app
fi fi
ynh_remove_logrotate #=================================================
# STOP PYINVENTORY'S SERVICES
#=================================================
ynh_script_progression --message="Stopping and removing systemd service '$app'..." --weight=5
ynh_remove_systemd_config --service="$app" 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 ynh_remove_nginx_config
ynh_redis_remove_db "$redis_db" #=================================================
# REMOVE LOGROTATE CONFIGURATION
#=================================================
ynh_script_progression --message="Removing logrotate configuration..."
# Remove the app-specific logrotate config
ynh_remove_logrotate
#================================================= #=================================================
# END OF SCRIPT # END OF SCRIPT

View file

@ -10,56 +10,86 @@ source ../settings/scripts/_common.sh
source /usr/share/yunohost/helpers source /usr/share/yunohost/helpers
#================================================= #=================================================
# RESTORE THE APP MAIN DIR # STANDARD RESTORATION STEPS
#================================================= #=================================================
ynh_script_progression --message="Restoring the app main directory..." --weight=1 # RESTORE THE NGINX CONFIGURATION
ynh_restore_file --origin_path="$install_dir"
chmod o-rwx "$install_dir"
chown -R "$app:www-data" "$install_dir"
#================================================= #=================================================
# RESTORE THE POSTGRES DATABASE ynh_script_progression --message="Restoring the NGINX web server configuration..." --weight=1
#=================================================
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
#=================================================
ynh_script_progression --message="Update Python virtualenv..." --weight=5
python3 -m venv --upgrade --without-pip "$install_dir/venv"
#=================================================
# RESTORE SYSTEM CONFIGURATIONS
#=================================================
ynh_script_progression --message="Restoring system configurations related to $app..." --weight=1
ynh_restore_file --origin_path="/etc/nginx/conf.d/$domain.d/$app.conf" ynh_restore_file --origin_path="/etc/nginx/conf.d/$domain.d/$app.conf"
#=================================================
# 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" ynh_restore_file --origin_path="/etc/systemd/system/$app.service"
systemctl enable "$app.service" --quiet systemctl enable $app.service --quiet
yunohost service add "$app" --description="$app service" --log="/var/log/$app/$app.log"
#=================================================
# INTEGRATE SERVICE IN YUNOHOST
#=================================================
ynh_script_progression --message="Integrating service in YunoHost..."
yunohost service add $app
#=================================================
# RESTORE THE LOGROTATE CONFIGURATION
#=================================================
ynh_script_progression --message="Setup logging..."
myynh_setup_log_file
ynh_restore_file --origin_path="/etc/logrotate.d/$app" ynh_restore_file --origin_path="/etc/logrotate.d/$app"
ynh_restore_file --origin_path="/var/log/$app/"
chmod o-rwx "/var/log/$app"
chown -R "$app:" "/var/log/$app"
#================================================= #=================================================
# GENERIC FINALIZATION # GENERIC FINALIZATION
#================================================= #=================================================
# RELOAD NGINX AND PHP-FPM OR THE APP SERVICE # SECURE FILES AND DIRECTORIES
#================================================= #=================================================
ynh_script_progression --message="Reloading NGINX web server and $app's service..." --weight=1 ynh_script_progression --message="Set file permissions..."
myynh_fix_file_permissions
ynh_systemd_action --service_name="$app" --action="start" --log_path="/var/log/$app/$app.log" #=================================================
# GENERIC FINALIZATION
#=================================================
# START PYINVENTORY
#=================================================
ynh_script_progression --message="Starting systemd service '$app'..." --weight=5
ynh_systemd_action --service_name=nginx --action=reload ynh_systemd_action --service_name=$app --action="start" --log_path="$log_file"
#=================================================
# RELOAD NGINX
#=================================================
ynh_script_progression --message="Reloading nginx web server..."
ynh_systemd_action --service_name="nginx" --action="reload"
#================================================= #=================================================
# END OF SCRIPT # END OF SCRIPT

View file

@ -7,31 +7,27 @@
source _common.sh source _common.sh
source /usr/share/yunohost/helpers source /usr/share/yunohost/helpers
#================================================= #-------------------------------------------------
# STANDARD UPGRADE STEPS # config_panel.toml settings:
#=================================================
# ENSURE DOWNWARD COMPATIBILITY
#=================================================
ynh_script_progression --message="Ensuring downward compatibility..." --weight=1
if [ -z "${debug_enabled:-}" ]; then if [ -z "$debug_enabled" ]; then
debug_enabled="0" debug_enabled="0"
ynh_app_setting_set --app="$app" --key=debug_enabled --value="$debug_enabled" ynh_app_setting_set --app=$app --key=debug_enabled --value="$debug_enabled"
fi fi
if [ -z "${log_level:-}" ]; then if [ -z "$log_level" ]; then
log_level="WARNING" log_level="WARNING"
ynh_app_setting_set --app="$app" --key=log_level --value="$log_level" ynh_app_setting_set --app=$app --key=log_level --value="$log_level"
fi fi
if [ -z "${admin_email:-}" ]; then if [ -z "$admin_email" ]; then
admin_email="${admin}@${domain}" admin_email="${admin}@${domain}"
ynh_app_setting_set --app="$app" --key=admin_email --value="$admin_email" ynh_app_setting_set --app=$app --key=admin_email --value="$admin_email"
fi fi
if [ -z "${default_from_email:-}" ]; then if [ -z "$default_from_email" ]; then
default_from_email="${app}@${domain}" default_from_email="${app}@${domain}"
ynh_app_setting_set --app="$app" --key=default_from_email --value="$default_from_email" ynh_app_setting_set --app=$app --key=default_from_email --value="$default_from_email"
fi fi
#================================================= #=================================================
@ -41,76 +37,82 @@ fi
#================================================= #=================================================
ynh_script_progression --message="Stopping systemd service '$app'..." --weight=5 ynh_script_progression --message="Stopping systemd service '$app'..." --weight=5
ynh_systemd_action --service_name="$app" --action="stop" --log_path="/var/log/$app/$app.log" ynh_systemd_action --service_name=$app --action="stop" --log_path="$log_file"
#================================================= #=================================================
# PIP INSTALLATION # SETUP SYSTEMD
#================================================= #=================================================
ynh_script_progression --message="Installing project via pip..." --weight=45 ynh_script_progression --message="Configuring systemd service '$app'..." --weight=5
# Always recreate everything fresh with current python version ynh_add_systemd_config --service=$app --template="systemd.service"
ynh_secure_remove "$install_dir/venv"
_install_fmd_venv #=================================================
# PYTHON VIRTUALENV
chmod o-rwx "$install_dir" #=================================================
chown -R "$app:www-data" "$install_dir" ynh_script_progression --message="Create and setup Python virtualenv..." --weight=45
cp ../conf/requirements.txt "$data_dir/requirements.txt"
myynh_setup_python_venv
#================================================= #=================================================
# copy config files # copy config files
# ================================================ # ================================================
ynh_script_progression --message="Create $app configuration files..." ynh_script_progression --message="Create project configuration files..."
ynh_add_config --template="gunicorn.conf.py" --destination="$install_dir/gunicorn.conf.py" ynh_add_config --template="gunicorn.conf.py" --destination="$data_dir/gunicorn.conf.py"
ynh_add_config --template="manage.py" --destination="$install_dir/manage.py" ynh_add_config --template="manage.py" --destination="$data_dir/manage.py"
chmod +x "$install_dir/manage.py" chmod -c +x "$data_dir/manage.py"
ynh_add_config --template="settings.py" --destination="$install_dir/settings.py" ynh_add_config --template="settings.py" --destination="$data_dir/settings.py"
ynh_add_config --template="setup_user.py" --destination="$install_dir/setup_user.py" ynh_add_config --template="setup_user.py" --destination="$data_dir/setup_user.py"
ynh_add_config --template="urls.py" --destination="$install_dir/urls.py" ynh_add_config --template="urls.py" --destination="$data_dir/urls.py"
ynh_add_config --template="wsgi.py" --destination="$install_dir/wsgi.py" ynh_add_config --template="wsgi.py" --destination="$data_dir/wsgi.py"
#================================================= #=================================================
# MIGRATE PYINVENTORY # MIGRATE PYINVENTORY
#================================================= #=================================================
ynh_script_progression --message="migrate/collectstatic/createadmin..." --weight=10 ynh_script_progression --message="migrate/collectstatic/createadmin..." --weight=10
pushd "$install_dir" cd "$data_dir" || exit
# Just for debugging:
ynh_exec_as "$app" "$venvpython" ./manage.py diffsettings
ynh_exec_as "$app" "$venvpython" ./manage.py migrate --no-input # Just for debugging:
ynh_exec_as "$app" "$venvpython" ./manage.py collectstatic --no-input ./manage.py diffsettings
# Create/update Django superuser (set unusable password, because auth done via SSOwat): ./manage.py migrate --no-input
ynh_exec_as "$app" "$venvpython" ./manage.py create_superuser --username="$admin" --email="$(ynh_user_get_info "$admin" mail)" ./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
# 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" "$venvpython" ./manage.py check --deploy || true
popd
#================================================= #=================================================
# REAPPLY SYSTEM CONFIGURATIONS # SETUP LOGROTATE
#================================================= #=================================================
ynh_script_progression --message="Upgrading system configurations related to $app..." --weight=1 ynh_script_progression --message="Upgrading logrotate configuration..."
ynh_add_nginx_config # Use logrotate to manage app-specific logfile(s)
ynh_use_logrotate --logfile="$log_file" --specific_user=$app --non-append
ynh_add_systemd_config --service="$app" --template="systemd.service" #=================================================
yunohost service add "$app" --description="$app service" --log="/var/log/$app/$app.log" # GENERIC FINALIZATION
#=================================================
ynh_use_logrotate --non-append # SECURE FILES AND DIRECTORIES
chmod o-rwx "/var/log/$app" #=================================================
chown -R "$app:" "/var/log/$app" ynh_script_progression --message="Set file permissions..."
myynh_fix_file_permissions
#================================================= #=================================================
# Start the app server via systemd # Start the app server via systemd
#================================================= #=================================================
ynh_script_progression --message="Starting systemd service '$app'..." --weight=5 ynh_script_progression --message="Starting systemd service '$app'..." --weight=5
ynh_systemd_action --service_name="$app" --action="start" yunohost service add $app
ynh_systemd_action --service_name=$app --action="start" --log_path="$log_file"
#================================================= #=================================================
# END OF SCRIPT # END OF SCRIPT

View file

@ -7,3 +7,27 @@ test_format = 1.0
# ------------ # ------------
# Tests to run # Tests to run
# ------------ # ------------
# 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"