From a8164a2905dd0992efd4ed31e339434e27b9a7f7 Mon Sep 17 00:00:00 2001 From: JensDiemer Date: Sun, 28 Feb 2021 10:56:42 +0100 Subject: [PATCH] WIP: rename/split from django_ynh See discussion here: https://github.com/YunoHost-Apps/django_ynh/pull/13 Corresponding to: https://github.com/jedie/django_yunnohost_integration/pull/1 --- .flake8 | 4 +- .gitignore | 7 +- Makefile | 5 +- README.md | 31 +- ...ynh.service => django_example_ynh.service} | 2 +- conf/nginx.conf | 10 + conf/requirements.txt | 35 +- conf/settings.py | 73 +- conf/setup_user.py | 5 +- conf/urls.py | 2 +- conf/wsgi.py | 2 +- django_ynh/__init__.py | 1 - django_ynh/base_settings.py | 102 -- django_ynh/local_test.py | 165 --- django_ynh/management/__init__.py | 0 django_ynh/management/commands/__init__.py | 0 .../management/commands/create_superuser.py | 50 - django_ynh/path_utils.py | 25 - django_ynh/pytest_helper.py | 38 - django_ynh/secret_key.py | 23 - django_ynh/sso_auth/__init__.py | 0 django_ynh/sso_auth/auth_backend.py | 61 - django_ynh/sso_auth/auth_middleware.py | 101 -- django_ynh/sso_auth/user_profile.py | 95 -- django_ynh/test_utils.py | 8 - django_ynh/views.py | 27 - manifest.json | 32 +- poetry.lock | 1021 +++++++++++++++++ publish.py | 31 - pyproject.toml | 36 +- scripts/_common.sh | 5 +- scripts/change_url | 2 +- scripts/install | 18 +- scripts/remove | 2 +- scripts/restore | 2 +- scripts/upgrade | 38 +- ...t_django_ynh.py => test_django_project.py} | 25 +- tests/test_lint.py | 4 +- tests/test_project_setup.py | 32 +- 39 files changed, 1214 insertions(+), 906 deletions(-) rename conf/{django_ynh.service => django_example_ynh.service} (87%) delete mode 100644 django_ynh/__init__.py delete mode 100644 django_ynh/base_settings.py delete mode 100755 django_ynh/local_test.py delete mode 100644 django_ynh/management/__init__.py delete mode 100644 django_ynh/management/commands/__init__.py delete mode 100644 django_ynh/management/commands/create_superuser.py delete mode 100644 django_ynh/path_utils.py delete mode 100644 django_ynh/pytest_helper.py delete mode 100644 django_ynh/secret_key.py delete mode 100644 django_ynh/sso_auth/__init__.py delete mode 100644 django_ynh/sso_auth/auth_backend.py delete mode 100644 django_ynh/sso_auth/auth_middleware.py delete mode 100644 django_ynh/sso_auth/user_profile.py delete mode 100644 django_ynh/test_utils.py delete mode 100644 django_ynh/views.py create mode 100644 poetry.lock delete mode 100644 publish.py rename tests/{test_django_ynh.py => test_django_project.py} (85%) diff --git a/.flake8 b/.flake8 index 242b2c6..6ad2ff9 100644 --- a/.flake8 +++ b/.flake8 @@ -2,6 +2,6 @@ # Move to pyproject.toml after: https://gitlab.com/pycqa/flake8/-/issues/428 # [flake8] -exclude = .pytest_cache, .tox, dist, htmlcov, */migrations/* -#ignore = E402 +exclude = .pytest_cache, .tox, dist, htmlcov, local_test +ignore = F405 max-line-length = 119 diff --git a/.gitignore b/.gitignore index 999db84..d4aff27 100644 --- a/.gitignore +++ b/.gitignore @@ -3,11 +3,8 @@ !.editorconfig !.flake8 !.gitignore -coverage.xml __pycache__ secret.txt -/htmlcov/ /local_test/ -/dist/ -/poetry.lock -*.log +/coverage.xml +/htmlcov/ diff --git a/Makefile b/Makefile index 476b90e..3692cc4 100644 --- a/Makefile +++ b/Makefile @@ -31,7 +31,7 @@ update: install-poetry ## update the sources and installation and generate "con lint: ## Run code formatters and linter poetry run flynt --fail-on-change --line_length=${MAX_LINE_LENGTH} . poetry run isort --check-only . - poetry run flake8 django_ynh + poetry run flake8 . fix-code-style: ## Fix code formatting poetry run flynt --line_length=${MAX_LINE_LENGTH} . @@ -47,9 +47,6 @@ tox: check-poetry ## Run pytest via tox with all environments pytest: install ## Run pytest poetry run python3 ./run_pytest.py -publish: ## Release new version to PyPi - poetry run python3 ./publish.py - local-test: install ## Run local_test.py to run the project locally poetry run python3 ./local_test.py diff --git a/README.md b/README.md index 7bb6266..6466b55 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,9 @@ -# django_ynh +# django_example_ynh +Demo [YunoHost Application](https://install-app.yunohost.org/?app=django_example_ynh) to demonstrate the integration of a Django project under YunoHost. -Glue code to package django projects as yunohost apps. - -This repository is: - -* The Python package [django-ynh](https://pypi.org/project/django-ynh/) with helpers for integrate a Django project as YunoHost package -* A example [YunoHost Application](https://install-app.yunohost.org/?app=django_ynh) that can be installed - - -[![Integration level](https://dash.yunohost.org/integration/django_ynh.svg)](https://dash.yunohost.org/appci/app/django_ynh) ![](https://ci-apps.yunohost.org/ci/badges/django_ynh.status.svg) ![](https://ci-apps.yunohost.org/ci/badges/django_ynh.maintain.svg) -[![Install django_ynh with YunoHost](https://install-app.yunohost.org/install-with-yunohost.svg)](https://install-app.yunohost.org/?app=django_ynh) +[![Integration level](https://dash.yunohost.org/integration/django_example_ynh.svg)](https://dash.yunohost.org/appci/app/django_example_ynh) ![](https://ci-apps.yunohost.org/ci/badges/django_example_ynh.status.svg) ![](https://ci-apps.yunohost.org/ci/badges/django_example_ynh.maintain.svg) +[![Install django_example_ynh with YunoHost](https://install-app.yunohost.org/install-with-yunohost.svg)](https://install-app.yunohost.org/?app=django_example_ynh) Pull requests welcome ;) @@ -92,9 +85,9 @@ and urls made for YunoHost installation. e.g.: ```bash -~$ git clone https://github.com/YunoHost-Apps/django_ynh.git +~$ git clone https://github.com/YunoHost-Apps/django_example_ynh.git ~$ cd django_ynh/ -~/django_ynh$ make +~/django_example_ynh$ make install-poetry install or update poetry install install project via poetry update update the sources and installation and generate "conf/requirements.txt" @@ -107,9 +100,9 @@ publish Release new version to PyPi local-test Run local_test.py to run the project locally local-diff-settings Run "manage.py diffsettings" with local test -~/django_ynh$ make install-poetry -~/django_ynh$ make install -~/django_ynh$ make local-test +~/django_example_ynh$ make install-poetry +~/django_example_ynh$ make install +~/django_example_ynh$ make local-test ``` Notes: @@ -123,6 +116,10 @@ Notes: * [compare v0.1.5...master](https://github.com/YunoHost-Apps/django_ynh/compare/v0.1.5...master) **dev** * tbc +* v0.2.0.alpha0 **dev** + * rename/split `django_ynh` into: + * `django_yunohost_integration` - Python package with the glue code to integrate a Django project with YunoHost + * `django_example_ynh` - Demo YunoHost App to demonstrate the integration of a Django project under YunoHost * [v0.1.5 - 19.01.2021](https://github.com/YunoHost-Apps/django_ynh/compare/v0.1.4...v0.1.5) * Make some deps `gunicorn`, `psycopg2-binary`, `django-redis`, `django-axes` optional * [v0.1.4 - 08.01.2021](https://github.com/YunoHost-Apps/django_ynh/compare/v0.1.3...v0.1.4) @@ -150,7 +147,7 @@ Notes: These projects used `django_ynh`: -* https://github.com/YunoHost-Apps/pyinventory_ynh +* https://github.com/YunoHost-Apps/django_example_ynh * https://github.com/YunoHost-Apps/django-for-runners_ynh --- diff --git a/conf/django_ynh.service b/conf/django_example_ynh.service similarity index 87% rename from conf/django_ynh.service rename to conf/django_example_ynh.service index 7b11dd0..67f504b 100644 --- a/conf/django_ynh.service +++ b/conf/django_example_ynh.service @@ -1,5 +1,5 @@ [Unit] -Description=django_ynh DEMO application server +Description=PyInventory application server After=redis.service postgresql.service [Service] diff --git a/conf/nginx.conf b/conf/nginx.conf index 7cb87c2..729b72d 100644 --- a/conf/nginx.conf +++ b/conf/nginx.conf @@ -5,9 +5,19 @@ location __PATH__/static/ { expires 30d; } +# TODO: django-sendfile2: +#location __PATH__/media/ { +# # DATA_DIR/media/ +# alias __PUBLIC_PATH__/media/; +# expires 30d; +#} + location __PATH__/ { # https://github.com/benoitc/gunicorn/blob/master/examples/nginx.conf + # this is needed if you have file import via upload enabled + client_max_body_size 100M; + proxy_http_version 1.1; proxy_set_header Host $host; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; diff --git a/conf/requirements.txt b/conf/requirements.txt index 17b51d1..b2e2c47 100644 --- a/conf/requirements.txt +++ b/conf/requirements.txt @@ -1,21 +1,24 @@ -asgiref==3.3.1; python_version >= "3.6" and python_version < "4.0" \ +asgiref==3.3.1; python_version >= "3.7" and python_full_version < "4.0.0" \ --hash=sha256:5ee950735509d04eb673bd7f7120f8fa1c9e2df495394992c73234d526907e17 \ --hash=sha256:7162a3cb30ab0609f1a4c95938fd73e8604f63bdba516a7f7d64b83ff09478f0 -django-axes==5.10.0; python_version >= "3.6" and python_version < "4.0" \ - --hash=sha256:8a62cd4cc78ef08007e8102def34be83832995eb6e3e0c814d605741b82a2796 \ - --hash=sha256:3c81ddca8a9d7fd0019cb440f711cc873c3039546f7eacb3f2ec616bf0ec1b32 -django-ipware==3.0.2; python_version >= "3.6" and python_version < "4.0" \ +django-axes==5.13.1; python_version >= "3.7" and python_version < "4.0" and python_full_version < "4.0.0" \ + --hash=sha256:8f7870dc18ace6155127073bffe7276719c71c5ad4e67b45abedc0207f64a2f6 \ + --hash=sha256:aef814f738742b01cc7730cd2bebe3e5b462b807fd6c2ed609b62437ccc71f80 +django-ipware==3.0.2; python_version >= "3.7" and python_version < "4.0" and python_full_version < "4.0.0" \ --hash=sha256:c7df8e1410a8e5d6b1fbae58728402ea59950f043c3582e033e866f0f0cf5e94 -django-redis==4.12.1; python_version >= "3.5" \ +django-redis==4.12.1; python_version >= "3.7" and python_full_version < "4.0.0" \ --hash=sha256:306589c7021e6468b2656edc89f62b8ba67e8d5a1c8877e2688042263daa7a63 \ --hash=sha256:1133b26b75baa3664164c3f44b9d5d133d1b8de45d94d79f38d1adc5b1d502e5 -django==3.1.4; python_version >= "3.6" \ - --hash=sha256:5c866205f15e7a7123f1eec6ab939d22d5bde1416635cab259684af66d8e48a2 \ - --hash=sha256:edb10b5c45e7e9c0fb1dc00b76ec7449aca258a39ffd613dbd078c51d19c9f03 -gunicorn==20.0.4; python_version >= "3.4" \ +django-yunohost-integration==0.2.0a0; python_version >= "3.7" and python_full_version < "4.0.0" \ + --hash=sha256:a45197a3c4595a496674e7e48a58f58351b6022526893264831e0d6ba463a44f \ + --hash=sha256:9acdf320537ccce47ceb1d51d2a00fafbf30938152d3b22c59a3a2ead7952227 +django==3.1.7; python_version >= "3.7" and python_full_version < "4.0.0" and python_version < "4.0" \ + --hash=sha256:baf099db36ad31f970775d0be5587cc58a6256a6771a44eb795b554d45f211b8 \ + --hash=sha256:32ce792ee9b6a0cbbec340123e229ac9f765dff8c2a4ae9247a14b2ba3a365a7 +gunicorn==20.0.4; python_version >= "3.7" and python_full_version < "4.0.0" \ --hash=sha256:cd4a810dd51bf497552cf3f863b575dabd73d6ad6a91075b65936b151cbf4f9c \ --hash=sha256:1904bb2b8a43658807108d59c3f3d56c2b6121a701161de0ddf9ad140073c626 -psycopg2-binary==2.8.6; (python_version >= "2.7" and python_full_version < "3.0.0") or (python_full_version >= "3.4.0") \ +psycopg2-binary==2.8.6; python_version >= "3.7" and python_full_version < "3.0.0" or python_version >= "3.7" and python_full_version < "4.0.0" and python_full_version >= "3.4.0" \ --hash=sha256:11b9c0ebce097180129e422379b824ae21c8f2a6596b159c7659e2e5a00e1aa0 \ --hash=sha256:d14b140a4439d816e3b1229a4a525df917d6ea22a0771a2a78332273fd9528a4 \ --hash=sha256:1fabed9ea2acc4efe4671b92c669a213db744d2af8a9fc5d69a8e9bc14b7a9db \ @@ -51,12 +54,12 @@ psycopg2-binary==2.8.6; (python_version >= "2.7" and python_full_version < "3.0. --hash=sha256:7312e931b90fe14f925729cde58022f5d034241918a5c4f9797cac62f6b3a9dd \ --hash=sha256:6422f2ff0919fd720195f64ffd8f924c1395d30f9a495f31e2392c2efafb5056 \ --hash=sha256:15978a1fbd225583dd8cdaf37e67ccc278b5abecb4caf6b2d6b8e2b948e953f6 -pytz==2020.5; python_version >= "3.6" and python_version < "4.0" \ - --hash=sha256:16962c5fb8db4a8f63a26646d8886e9d769b6c511543557bc84e9569fb9a9cb4 \ - --hash=sha256:180befebb1927b16f6b57101720075a984c019ac16b1b7575673bea42c6c3da5 -redis==3.5.3; python_version >= "3.5" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.5" \ +pytz==2021.1; python_version >= "3.7" and python_full_version < "4.0.0" \ + --hash=sha256:eb10ce3e7736052ed3623d49975ce333bcd712c7bb19a58b9e2089d4057d0798 \ + --hash=sha256:83a4a90894bf38e243cf052c8b58f381bfe9a7a483f6a9cab140bc7f702ac4da +redis==3.5.3; python_version >= "3.7" and python_full_version < "3.0.0" or python_version >= "3.7" and python_full_version < "4.0.0" and python_full_version >= "3.5.0" \ --hash=sha256:432b788c4530cfe16d8d943a09d40ca6c16149727e4afe8c2c9d5580c59d9f24 \ --hash=sha256:0e7e0cfca8660dea8b7d5cd8c4f6c5e29e11f31158c0b0ae91a397f00e5a05a2 -sqlparse==0.4.1; python_version >= "3.6" and python_version < "4.0" \ +sqlparse==0.4.1; python_version >= "3.7" and python_full_version < "4.0.0" \ --hash=sha256:017cde379adbd6a1f15a61873f43e8274179378e95ef3fede90b5aa64d304ed0 \ --hash=sha256:0f91fd2e829c44362cbcfab3e9ae12e22badaa8a29ad5ff599f9ec109f0454e8 diff --git a/conf/settings.py b/conf/settings.py index e74869d..c5bf106 100644 --- a/conf/settings.py +++ b/conf/settings.py @@ -1,24 +1,21 @@ -""" - ************************************************************************** - Please do not modify this file, it will be reset at the next update. - You can edit the file __FINAL_HOME_PATH__/local_settings.py and add/modify - the settings you need. +################################################################################ +################################################################################ - The parameters you add in local_settings.py will overwrite these, - but you can use the options and documentation in this file to find out - what can be done. - ************************************************************************** +# Please do not modify this file, it will be reset at the next update. +# You can edit the file __FINAL_HOME_PATH__/local_settings.py and add/modify the settings you need. +# The parameters you add in local_settings.py will overwrite these, +# but you can use the options and documentation in this file to find out what can be done. + +################################################################################ +################################################################################ - Django Settings here depends on YunoHost app settings. -""" from pathlib import Path as __Path -from django_ynh.base_settings import * # noqa from django_ynh.secret_key import get_or_create_secret as __get_or_create_secret +from inventory_project.settings.base import * # noqa -DEBUG = True # This is only the DEMO app ;) But should never be on in production! - +DEBUG = False # Don't turn DEBUG on in production! # ----------------------------------------------------------------------------- @@ -28,7 +25,7 @@ assert FINAL_HOME_PATH.is_dir(), f'Directory not exists: {FINAL_HOME_PATH}' FINAL_WWW_PATH = __Path('__FINAL_WWW_PATH__') # /var/www/$app assert FINAL_WWW_PATH.is_dir(), f'Directory not exists: {FINAL_WWW_PATH}' -LOG_FILE = __Path('__LOG_FILE__') # /var/log/$app/django_ynh.log +LOG_FILE = __Path('__LOG_FILE__') # /var/log/$app/django_example_ynh.log assert LOG_FILE.is_file(), f'File not exists: {LOG_FILE}' PATH_URL = '__PATH_URL__' # $YNH_APP_ARG_PATH @@ -36,11 +33,40 @@ PATH_URL = PATH_URL.strip('/') # ----------------------------------------------------------------------------- +ROOT_URLCONF = 'urls' # /opt/yunohost/django_example_ynh/ynh_urls.py + # Function that will be called to finalize a user profile: -YNH_SETUP_USER = 'setup_user.setup_demo_user' +YNH_SETUP_USER = 'setup_user.setup_project_user' SECRET_KEY = __get_or_create_secret(FINAL_HOME_PATH / 'secret.txt') # /opt/yunohost/$app/secret.txt +INSTALLED_APPS.append('django_ynh') + +MIDDLEWARE.insert( + MIDDLEWARE.index('django.contrib.auth.middleware.AuthenticationMiddleware') + 1, + # login a user via HTTP_REMOTE_USER header from SSOwat: + 'django_ynh.sso_auth.auth_middleware.SSOwatRemoteUserMiddleware', +) + +# Keep ModelBackend around for per-user permissions and superuser +AUTHENTICATION_BACKENDS = ( + 'axes.backends.AxesBackend', # AxesBackend should be the first backend! + # + # Authenticate via SSO and nginx 'HTTP_REMOTE_USER' header: + 'django_ynh.sso_auth.auth_backend.SSOwatUserBackend', + # + # Fallback to normal Django model backend: + 'django.contrib.auth.backends.ModelBackend', +) + +LOGIN_REDIRECT_URL = None +LOGIN_URL = '/yunohost/sso/' +LOGOUT_REDIRECT_URL = '/yunohost/sso/' +# /yunohost/sso/?action=logout + +# ----------------------------------------------------------------------------- + + ADMINS = (('__ADMIN__', '__ADMINMAIL__'),) MANAGERS = ADMINS @@ -77,14 +103,13 @@ DEFAULT_FROM_EMAIL = '__ADMINMAIL__' # List of URLs your site is supposed to serve ALLOWED_HOSTS = ['__DOMAIN__'] - # _____________________________________________________________________________ # Configuration for caching CACHES = { 'default': { 'BACKEND': 'django_redis.cache.RedisCache', 'LOCATION': 'redis://127.0.0.1:6379/__REDIS_DB__', - # If redis is running on same host as django_ynh, you might + # If redis is running on same host as PyInventory, you might # want to use unix sockets instead: # 'LOCATION': 'unix:///var/run/redis/redis.sock?db=1', 'OPTIONS': { @@ -94,7 +119,6 @@ CACHES = { }, } - # _____________________________________________________________________________ # Static files (CSS, JavaScript, Images) @@ -109,10 +133,18 @@ else: STATIC_ROOT = str(FINAL_WWW_PATH / 'static') MEDIA_ROOT = str(FINAL_WWW_PATH / 'media') +# _____________________________________________________________________________ +# django-ckeditor + +CKEDITOR_BASEPATH = STATIC_URL + 'ckeditor/ckeditor/' + +# _____________________________________________________________________________ +# Django-dbbackup + +DBBACKUP_STORAGE_OPTIONS['location'] = str(FINAL_HOME_PATH / 'backups') # ----------------------------------------------------------------------------- - LOGGING = { 'version': 1, 'disable_existing_loggers': True, @@ -142,6 +174,7 @@ LOGGING = { 'axes': {'handlers': ['log_file', 'mail_admins'], 'level': 'WARNING', 'propagate': False}, 'django_tools': {'handlers': ['log_file', 'mail_admins'], 'level': 'INFO', 'propagate': False}, 'django_ynh': {'handlers': ['log_file', 'mail_admins'], 'level': 'INFO', 'propagate': False}, + 'inventory': {'handlers': ['log_file', 'mail_admins'], 'level': 'INFO', 'propagate': False}, }, } diff --git a/conf/setup_user.py b/conf/setup_user.py index 983c5f3..5d820de 100644 --- a/conf/setup_user.py +++ b/conf/setup_user.py @@ -1,6 +1,7 @@ -def setup_demo_user(user): +def setup_project_user(user): """ - The django_ynh DEMO use the Django admin. So we need a "staff" user ;) + All users used the Django admin, so we need to set the "staff" user flag. + Called from django_ynh.sso_auth """ user.is_staff = True user.save() diff --git a/conf/urls.py b/conf/urls.py index 714ec0d..2f0ddcd 100644 --- a/conf/urls.py +++ b/conf/urls.py @@ -2,7 +2,7 @@ from django.conf import settings from django.contrib import admin from django.urls import path -from django_ynh.views import request_media_debug_view +from django_yunohost_integration.views import request_media_debug_view # settings.PATH_URL is the $YNH_APP_ARG_PATH diff --git a/conf/wsgi.py b/conf/wsgi.py index 81075a9..018a0cc 100644 --- a/conf/wsgi.py +++ b/conf/wsgi.py @@ -6,7 +6,7 @@ import os os.environ['DJANGO_SETTINGS_MODULE'] = 'settings' -from django.core.wsgi import get_wsgi_application +from django.core.wsgi import get_wsgi_application # noqa application = get_wsgi_application() diff --git a/django_ynh/__init__.py b/django_ynh/__init__.py deleted file mode 100644 index 66a87bb..0000000 --- a/django_ynh/__init__.py +++ /dev/null @@ -1 +0,0 @@ -__version__ = '0.1.5' diff --git a/django_ynh/base_settings.py b/django_ynh/base_settings.py deleted file mode 100644 index 32dea60..0000000 --- a/django_ynh/base_settings.py +++ /dev/null @@ -1,102 +0,0 @@ -""" - Base settings for a Django project installed in Yunohost. - All values should not depent on YunoHost app settings. -""" - - -# ----------------------------------------------------------------------------- -# settings that should be set in project settings: - -SECRET_KEY = None - -# ----------------------------------------------------------------------------- - -ROOT_URLCONF = 'urls' # .../conf/urls.py - -INSTALLED_APPS = [ - 'django.contrib.admin', - 'django.contrib.auth', - 'django.contrib.contenttypes', - 'django.contrib.sessions', - 'django.contrib.messages', - 'django.contrib.staticfiles', - 'axes', # https://github.com/jazzband/django-axes - 'django_ynh', -] - -# ----------------------------------------------------------------------------- - -MIDDLEWARE = [ - 'django.middleware.security.SecurityMiddleware', - 'django.contrib.sessions.middleware.SessionMiddleware', - 'django.middleware.common.CommonMiddleware', - 'django.middleware.csrf.CsrfViewMiddleware', - 'django.contrib.auth.middleware.AuthenticationMiddleware', - # - # login a user via HTTP_REMOTE_USER header from SSOwat: - 'django_ynh.sso_auth.auth_middleware.SSOwatRemoteUserMiddleware', - # - 'django.contrib.messages.middleware.MessageMiddleware', - 'django.middleware.clickjacking.XFrameOptionsMiddleware', - # - # AxesMiddleware should be the last middleware: - 'axes.middleware.AxesMiddleware', -] - -# ----------------------------------------------------------------------------- - -TEMPLATES = [ - { - 'BACKEND': 'django.template.backends.django.DjangoTemplates', - 'DIRS': [], - 'APP_DIRS': True, - 'OPTIONS': { - 'context_processors': [ - 'django.template.context_processors.debug', - 'django.template.context_processors.request', - 'django.contrib.auth.context_processors.auth', - 'django.contrib.messages.context_processors.messages', - ], - }, - }, -] - -# ----------------------------------------------------------------------------- - -# Keep ModelBackend around for per-user permissions and superuser -AUTHENTICATION_BACKENDS = ( - 'axes.backends.AxesBackend', # AxesBackend should be the first backend! - # - # Authenticate via SSO and nginx 'HTTP_REMOTE_USER' header: - 'django_ynh.sso_auth.auth_backend.SSOwatUserBackend', - # - # Fallback to normal Django model backend: - 'django.contrib.auth.backends.ModelBackend', -) - -LOGIN_REDIRECT_URL = None -LOGIN_URL = '/yunohost/sso/' -LOGOUT_REDIRECT_URL = '/yunohost/sso/' -# /yunohost/sso/?action=logout - -# _____________________________________________________________________________ -# Setting below, should be overwritten! - -LOGGING = { - 'version': 1, - 'disable_existing_loggers': False, - 'formatters': { - 'verbose': { - 'format': '{asctime} {levelname} {name} {module}.{funcName} {message}', - 'style': '{', - }, - }, - 'handlers': {'console': {'class': 'logging.StreamHandler', 'formatter': 'verbose'}}, - 'loggers': { - 'django': {'handlers': ['console'], 'level': 'INFO', 'propagate': False}, - 'django.auth': {'handlers': ['console'], 'level': 'DEBUG', 'propagate': False}, - 'django.security': {'handlers': ['console'], 'level': 'DEBUG', 'propagate': False}, - 'django.request': {'handlers': ['console'], 'level': 'DEBUG', 'propagate': False}, - 'django_ynh': {'handlers': ['console'], 'level': 'DEBUG', 'propagate': False}, - }, -} diff --git a/django_ynh/local_test.py b/django_ynh/local_test.py deleted file mode 100755 index 5eaf845..0000000 --- a/django_ynh/local_test.py +++ /dev/null @@ -1,165 +0,0 @@ -""" - Create a YunoHost package local test -""" -import argparse -import os -import shlex -import subprocess -import sys -from pathlib import Path - -from django_ynh.path_utils import assert_is_dir, assert_is_file -from django_ynh.test_utils import generate_basic_auth - - -def verbose_check_call(command, verbose=True, **kwargs): - """ 'verbose' version of subprocess.check_call() """ - if verbose: - print('_' * 100) - msg = f'Call: {command!r}' - verbose_kwargs = ', '.join(f'{k}={v!r}' for k, v in sorted(kwargs.items())) - if verbose_kwargs: - msg += f' (kwargs: {verbose_kwargs})' - print(f'{msg}\n', flush=True) - - env = os.environ.copy() - env['PYTHONUNBUFFERED'] = '1' - - popenargs = shlex.split(command) - subprocess.check_call(popenargs, universal_newlines=True, env=env, **kwargs) - - -def call_manage_py(final_home_path, args): - verbose_check_call( - command=f'{sys.executable} manage.py {args}', - cwd=final_home_path, - ) - - -def copy_patch(src_file, replaces, final_home_path): - dst_file = final_home_path / src_file.name - print(f'{src_file} -> {dst_file}') - - with src_file.open('r') as f: - content = f.read() - - if replaces: - for old, new in replaces.items(): - if old in content: - print(f' * Replace "{old}" -> "{new}"') - content = content.replace(old, new) - - with dst_file.open('w') as f: - f.write(content) - - -def create_local_test(django_settings_path, destination, runserver=False): - django_settings_path = django_settings_path.resolve() - assert_is_file(django_settings_path) - - django_settings_name = django_settings_path.stem - - conf_path = django_settings_path.parent - - project_name = conf_path.parent.name - - assert isinstance(destination, Path) - destination = destination.resolve() - if not destination.is_dir(): - destination.mkdir(parents=False) - - assert_is_dir(destination) - - final_home_path = destination / 'opt_yunohost' - final_www_path = destination / 'var_www' - log_file = destination / f'var_log_{project_name}.log' - - REPLACES = { - '__FINAL_HOME_PATH__': str(final_home_path), - '__FINAL_WWW_PATH__': str(final_www_path), - '__LOG_FILE__': str(log_file), - '__PATH_URL__': 'app_path', - '__DOMAIN__': '127.0.0.1', - 'django.db.backends.postgresql': 'django.db.backends.sqlite3', - "'NAME': '__APP__',": f"'NAME': '{destination / 'test_db.sqlite'}',", - 'django_redis.cache.RedisCache': 'django.core.cache.backends.dummy.DummyCache', - # Just use the default logging setup from django_ynh project: - 'LOGGING = {': 'HACKED_DEACTIVATED_LOGGING = {', - } - - for p in (final_home_path, final_www_path): - if p.is_dir(): - print(f'Already exists: "{p}", ok.') - else: - p.mkdir(parents=True, exist_ok=True) - - log_file.touch(exist_ok=True) - - for src_file in conf_path.glob('*.py'): - copy_patch(src_file=src_file, replaces=REPLACES, final_home_path=final_home_path) - - with Path(final_home_path / 'local_settings.py').open('w') as f: - f.write('# Only for local test run\n') - f.write('DEBUG = True\n') - f.write('SERVE_FILES = True # May used in urls.py\n') - f.write('AUTH_PASSWORD_VALIDATORS = [] # accept all passwords\n') - - # call "local_test/manage.py" via subprocess: - call_manage_py(final_home_path, 'check --deploy') - if runserver: - call_manage_py(final_home_path, 'migrate --no-input') - call_manage_py(final_home_path, 'collectstatic --no-input') - call_manage_py(final_home_path, 'create_superuser --username="test"') - - os.environ['DJANGO_SETTINGS_MODULE'] = django_settings_name - - # All environment variables are passed to Django's "runnserver" ;) - # "Simulate" SSOwat authentication, by set "http headers" - # Still missing is the 'SSOwAuthUser' cookie, - # but this is ignored, if settings.DEBUG=True ;) - os.environ['HTTP_AUTH_USER'] = 'test' - os.environ['HTTP_REMOTE_USER'] = 'test' - - os.environ['HTTP_AUTHORIZATION'] = generate_basic_auth(username='test', password='test123') - - try: - call_manage_py(final_home_path, 'runserver') - except KeyboardInterrupt: - print('\nBye ;)') - - return final_home_path - - -def cli(): - parser = argparse.ArgumentParser(description='Generate a YunoHost package local test') - - parser.add_argument( - '--django_settings_path', - action='store', - metavar='path', - help='Path to YunoHost package settings.py file (in "conf" directory)', - ) - parser.add_argument( - '--destination', - action='store', - metavar='path', - help='Destination directory for the local test files', - ) - parser.add_argument( - '--runserver', - action='store', - type=bool, - default=False, - help='Start Django "runserver" after local test file creation?', - ) - args = parser.parse_args() - - create_local_test( - django_settings_path=Path(args.django_settings_path), - destination=Path(args.destination), - runserver=args.runserver, - ) - - -if __name__ == '__main__': - cli() diff --git a/django_ynh/management/__init__.py b/django_ynh/management/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/django_ynh/management/commands/__init__.py b/django_ynh/management/commands/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/django_ynh/management/commands/create_superuser.py b/django_ynh/management/commands/create_superuser.py deleted file mode 100644 index 263d45f..0000000 --- a/django_ynh/management/commands/create_superuser.py +++ /dev/null @@ -1,50 +0,0 @@ -""" - Create or update Django super user with a unusable password - - A "unusable password" because it's not needed while auth via SSOwat ;) - - Can be called e.g.: - ./manage.py create_superuser --username="bar" --email="foo@bar.tld" -""" - - -import sys - -from django.contrib.auth import get_user_model -from django.core.management import BaseCommand - - -class Command(BaseCommand): - help = 'Create or update Django super user with a unusable password (auth via SSOwat)' - - def add_arguments(self, parser): - parser.add_argument( - "--username", - action="store", - required=True, - ) - parser.add_argument( - "--email", - action="store", - default=None, - ) - - def handle(self, *args, **options): - username = options['username'] - email = options['email'] - - User = get_user_model() - user = User.objects.filter(username=username).first() - if user: - self.stderr.write(f'Update existing user "{user}" and set his password.') - user.is_active = True - user.is_staff = True - user.is_superuser = True - if email: - user.email = email - else: - print(f'Create new super user "{username}"', file=sys.stderr) - user = User.objects.create_superuser(username=username, email=email, password=None) - - user.set_unusable_password() - user.save() diff --git a/django_ynh/path_utils.py b/django_ynh/path_utils.py deleted file mode 100644 index dffcfe1..0000000 --- a/django_ynh/path_utils.py +++ /dev/null @@ -1,25 +0,0 @@ -from pathlib import Path - - -def assert_is_dir(dir_path): - assert isinstance(dir_path, Path) - assert dir_path.is_dir, f'Directory does not exists: {dir_path}' - - -def assert_is_file(file_path): - assert isinstance(file_path, Path) - assert file_path.is_file, f'File not found: {file_path}' - - -def is_relative_to(p, other): - """ - Path.is_relative_to() is new in Python 3.9 - """ - p = Path(p) - other = Path(other) - try: - p.relative_to(other) - except ValueError: - return False - else: - return True diff --git a/django_ynh/pytest_helper.py b/django_ynh/pytest_helper.py deleted file mode 100644 index 83b6358..0000000 --- a/django_ynh/pytest_helper.py +++ /dev/null @@ -1,38 +0,0 @@ -import os -import sys -from pathlib import Path - -from django_ynh.local_test import create_local_test -from django_ynh.path_utils import assert_is_dir, assert_is_file - - -def run_pytest(django_settings_path, destination): - """ - 1. Generate "local test installation" - 2. Run pytest against generated sources - """ - assert_is_file(django_settings_path) - - conf_path = django_settings_path.parent - base_path = conf_path.parent - test_path = Path(base_path / 'tests') - - assert_is_dir(test_path) - - final_home_path = create_local_test( - django_settings_path=django_settings_path, - destination=destination, - runserver=False, - ) - django_settings_name = django_settings_path.stem - os.environ['DJANGO_SETTINGS_MODULE'] = django_settings_name - print(f'DJANGO_SETTINGS_MODULE={django_settings_name}') - - sys.path.insert(0, str(final_home_path)) - - import pytest - - # collect only project tests and pass existing pytest arguments: - sys.argv = [__file__, str(test_path)] + sys.argv[1:] - - raise SystemExit(pytest.console_main()) diff --git a/django_ynh/secret_key.py b/django_ynh/secret_key.py deleted file mode 100644 index eac93d2..0000000 --- a/django_ynh/secret_key.py +++ /dev/null @@ -1,23 +0,0 @@ -""" - Helper to create a random string for settings.SECRET_KEY - - SECURITY WARNING: keep the secret key used in production secret! -""" -import logging -from pathlib import Path -from secrets import token_urlsafe - - -logger = logging.getLogger(__name__) - - -def get_or_create_secret(secret_file): - assert isinstance(secret_file, Path) - assert secret_file.parent.is_dir, f'Directory does not exists: {secret_file.parent}' - - if not secret_file.is_file(): - logger.info('Generate %s', secret_file) - secret_file.open('w').write(token_urlsafe(128)) - - with secret_file.open('r') as f: - return f.read() diff --git a/django_ynh/sso_auth/__init__.py b/django_ynh/sso_auth/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/django_ynh/sso_auth/auth_backend.py b/django_ynh/sso_auth/auth_backend.py deleted file mode 100644 index 9f8e506..0000000 --- a/django_ynh/sso_auth/auth_backend.py +++ /dev/null @@ -1,61 +0,0 @@ -""" - remote user authentication backend - - Note: SSOwat/nginx add authentication headers: - - 'HTTP_AUTHORIZATION': 'Basic XXXXXXXXXXXXXXXX=' - 'HTTP_AUTH_USER': 'username' - 'HTTP_REMOTE_USER': 'username' - - Basic auth contains "{username}:{plaintext-password}" - - and we get SSOwat cookies like: - - 'HTTP_COOKIE': 'SSOwAuthUser=username; ' - 'SSOwAuthHash=593876aa66...99e69f88af1e; ' - 'SSOwAuthExpire=1609227697.998; ' - - * Login a user via HTTP_REMOTE_USER header, but check also username in: - * SSOwAuthUser - * HTTP_AUTH_USER - * HTTP_AUTHORIZATION (Basic auth) - * Create new users - * Update Email, First / Last name for existing users -""" - -import logging - -from django.contrib.auth.backends import RemoteUserBackend - -from django_ynh.sso_auth.user_profile import call_setup_user, update_user_profile - - -logger = logging.getLogger(__name__) - - -class SSOwatUserBackend(RemoteUserBackend): - """ - Authentication backend via SSO/nginx header - """ - - create_unknown_user = True - - def authenticate(self, request, remote_user): - logger.info('Remote user authenticate: %r', remote_user) - return super().authenticate(request, remote_user) - - def configure_user(self, request, user): - """ - Configure a new user after creation and return the updated user. - """ - logger.warning('Configure user %s', user) - - user = update_user_profile(request, user) - user = call_setup_user(user=user) - - return user - - def user_can_authenticate(self, user): - logger.warning('Remote user login: %s', user) - assert not user.is_anonymous - return True diff --git a/django_ynh/sso_auth/auth_middleware.py b/django_ynh/sso_auth/auth_middleware.py deleted file mode 100644 index 7b41d0a..0000000 --- a/django_ynh/sso_auth/auth_middleware.py +++ /dev/null @@ -1,101 +0,0 @@ -import base64 -import logging - -from django.conf import settings -from django.contrib import auth -from django.contrib.auth import get_user_model -from django.contrib.auth.middleware import RemoteUserMiddleware - - -try: - from axes.exceptions import AxesBackendPermissionDenied as SuspiciousOperation # log to Axes DB models -except ImportError: - from django.core.exceptions import SuspiciousOperation - -from django_ynh.sso_auth.user_profile import call_setup_user, update_user_profile - - -logger = logging.getLogger(__name__) - - -UserModel = get_user_model() - - -class SSOwatRemoteUserMiddleware(RemoteUserMiddleware): - """ - Middleware to login a user via HTTP_REMOTE_USER header. - Use Django Axes if something is wrong. - Update exising user informations. - """ - - header = 'HTTP_REMOTE_USER' - force_logout_if_no_header = True - - def process_request(self, request): - # Keep the information if the user is already logged in - was_authenticated = request.user.is_authenticated - - super().process_request(request) # login remote user - - user = request.user - - if not user.is_authenticated: - logger.debug('Not logged in -> nothing to verify here') - return - - # Check SSOwat cookie informations: - try: - username = request.COOKIES['SSOwAuthUser'] - except KeyError: - logger.error('SSOwAuthUser cookie missing!') - - if settings.DEBUG: - # e.g.: local test can't set a Cookie easily - logger.warning('Ignore error, because settings.DEBUG is on!') - else: - # emits a signal indicating user login failed, which is processed by - # axes.signals.log_user_login_failed which logs and flags the failed request. - raise SuspiciousOperation('Cookie missing') - else: - logger.info('SSOwat username from cookies: %r', username) - if username != user.username: - raise SuspiciousOperation('Wrong username') - - # Compare with HTTP_AUTH_USER - try: - username = request.META['HTTP_AUTH_USER'] - except KeyError: - logger.error('HTTP_AUTH_USER missing!') - raise SuspiciousOperation('No HTTP_AUTH_USER') - - if username != user.username: - raise SuspiciousOperation('Wrong HTTP_AUTH_USER username') - - # Also check 'HTTP_AUTHORIZATION', but only the username ;) - try: - authorization = request.META['HTTP_AUTHORIZATION'] - except KeyError: - logger.error('HTTP_AUTHORIZATION missing!') - raise SuspiciousOperation('No HTTP_AUTHORIZATION') - - scheme, creds = authorization.split(' ', 1) - if scheme.lower() != 'basic': - logger.error('HTTP_AUTHORIZATION with %r not supported', scheme) - raise SuspiciousOperation('HTTP_AUTHORIZATION scheme not supported') - - creds = str(base64.b64decode(creds), encoding='utf-8') - username = creds.split(':', 1)[0] - if username != user.username: - raise SuspiciousOperation('Wrong HTTP_AUTHORIZATION username') - - if not was_authenticated: - # First request, after login -> update user informations - logger.info('Remote user "%s" was logged in', user) - user = update_user_profile(request, user) - - user = call_setup_user(user=user) - assert isinstance(user, UserModel) - - # persist user in the session - request.user = user - auth.login(request, user) diff --git a/django_ynh/sso_auth/user_profile.py b/django_ynh/sso_auth/user_profile.py deleted file mode 100644 index d0dbc9f..0000000 --- a/django_ynh/sso_auth/user_profile.py +++ /dev/null @@ -1,95 +0,0 @@ -import logging -from functools import lru_cache - -from django.conf import settings -from django.contrib.auth import get_user_model -from django.core.exceptions import ValidationError -from django.utils.module_loading import import_string - - -logger = logging.getLogger(__name__) - - -UserModel = get_user_model() - - -@lru_cache(maxsize=None) -def get_setup_user_func(): - setup_user_func = import_string(settings.YNH_SETUP_USER) - assert callable(setup_user_func) - return setup_user_func - - -def call_setup_user(user): - """ - Hook for the YunoHost package application to setup a Django user. - Call function defined in settings.YNH_SETUP_USER - - called via: - * SSOwatUserBackend after a new user was created - * SSOwatRemoteUserMiddleware on login request - """ - old_pk = user.pk - - setup_user_func = get_setup_user_func() - logger.debug('Call "%s" for user "%s"', settings.YNH_SETUP_USER, user) - - user = setup_user_func(user=user) - - assert isinstance(user, UserModel) - assert user.pk == old_pk - - return user - - -def update_user_profile(request, user): - """ - Update existing user information: - * Email - * First / Last name - - Called via: - * SSOwatUserBackend after a new user was created - * SSOwatRemoteUserMiddleware on login request - """ - update_fields = [] - - if user.is_authenticated and not user.has_usable_password(): - # Empty password is not valid, so we can't save the model, because of full_clean() call - logger.info('Set unusable password for user: %s', user) - user.set_unusable_password() - update_fields.append('password') - - email = request.META.get('HTTP_EMAIL') - if email and user.email != email: - logger.info('Update email: %r -> %r', user.email, email) - user.email = email - update_fields.append('email') - - raw_username = request.META.get('HTTP_NAME') - if raw_username: - if ' ' in raw_username: - first_name, last_name = raw_username.split(' ', 1) - else: - first_name = '' - last_name = raw_username - - if user.first_name != first_name: - logger.info('Update first name: %r -> %r', user.first_name, first_name) - user.first_name = first_name - update_fields.append('first_name') - - if user.last_name != last_name: - logger.info('Update last name: %r -> %r', user.last_name, last_name) - user.last_name = last_name - update_fields.append('last_name') - - if update_fields: - try: - user.full_clean() - except ValidationError: - logger.exception('Can not update user: %s', user) - else: - user.save(update_fields=update_fields) - - return user diff --git a/django_ynh/test_utils.py b/django_ynh/test_utils.py deleted file mode 100644 index 0e78986..0000000 --- a/django_ynh/test_utils.py +++ /dev/null @@ -1,8 +0,0 @@ -import base64 - - -def generate_basic_auth(username, password): - basic_auth = f'{username}:{password}' - basic_auth_creds = bytes(basic_auth, encoding='utf-8') - creds = str(base64.b64encode(basic_auth_creds), encoding='utf-8') - return f'basic {creds}' diff --git a/django_ynh/views.py b/django_ynh/views.py deleted file mode 100644 index 057ddaa..0000000 --- a/django_ynh/views.py +++ /dev/null @@ -1,27 +0,0 @@ -import logging -import pprint - -from django.conf import settings -from django.contrib.auth import get_user_model -from django.http.response import HttpResponse -from django.shortcuts import redirect - - -logger = logging.getLogger(__name__) - - -def request_media_debug_view(request): - """ debug request.META """ - - assert settings.DEBUG is True, 'Only in DEBUG mode available!' - - if not request.user.is_authenticated: - logger.info('Deny debug view: User not logged in!') - UserModel = get_user_model() - logger.info('Existing users are: %s', ', '.join(f'"{user}"' for user in UserModel.objects.all())) - return redirect('admin:index') - - meta = pprint.pformat(request.META) - html = f'request.META:
{meta}
' - - return HttpResponse(html) diff --git a/manifest.json b/manifest.json index a4b85e6..9cedf0e 100644 --- a/manifest.json +++ b/manifest.json @@ -1,16 +1,16 @@ { - "name": "django_ynh", - "id": "django_ynh", + "name": "Django Example", + "id": "django_example_ynh", "packaging_format": 1, "description": { - "en": "Glue code to package django projects as yunohost apps." + "en": "Demo YunoHost Application to demonstrate the integration of a Django project under YunoHost." }, - "version": "0.1.5~ynh1", - "url": "https://github.com/jedie/django_ynh", + "version": "v0.2.0.alpha0~ynh1", + "url": "https://github.com/YunoHost-Apps/django_example_ynh", "license": "GPL-3.0", "maintainer": { "name": "Jens Diemer", - "email": "django_ynh@jensdiemer.de" + "email": "django_example_ynh@jensdiemer.de" }, "previous_maintainers": [], "requirements": { @@ -26,8 +26,8 @@ "name": "domain", "type": "domain", "ask": { - "en": "Choose a domain for django_ynh", - "fr": "Choisissez un domaine pour django_ynh" + "en": "Choose a domain for PyInventory", + "fr": "Choisissez un domaine pour PyInventory" }, "example": "domain.org" }, @@ -35,18 +35,18 @@ "name": "path", "type": "path", "ask": { - "en": "Choose a path for django_ynh", - "fr": "Choisissez un chemin pour django_ynh" + "en": "Choose a path for PyInventory", + "fr": "Choisissez un chemin pour PyInventory" }, - "example": "/django_ynh", - "default": "/django_ynh" + "example": "/django_example_ynh", + "default": "/django_example_ynh" }, { "name": "admin", "type": "user", "ask": { - "en": "Choose an admin user for django_ynh", - "fr": "Choisissez l'administrateur pour django_ynh" + "en": "Choose an admin user for PyInventory", + "fr": "Choisissez l'administrateur pour PyInventory" }, "example": "johndoe" }, @@ -54,8 +54,8 @@ "name": "is_public", "type": "boolean", "ask": { - "en": "Should django_ynh be public accessible?", - "fr": "django_ynh doit-il ĂȘtre accessible au public ?" + "en": "Should PyInventory be public accessible?", + "fr": "PyInventory doit-il ĂȘtre accessible au public ?" }, "help": { "en": "Any YunoHost user and anonymous people from the web will be able to access the application", diff --git a/poetry.lock b/poetry.lock new file mode 100644 index 0000000..f3cdb97 --- /dev/null +++ b/poetry.lock @@ -0,0 +1,1021 @@ +[[package]] +name = "appdirs" +version = "1.4.4" +description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "asgiref" +version = "3.3.1" +description = "ASGI specs, helper code, and adapters" +category = "main" +optional = false +python-versions = ">=3.5" + +[package.extras] +tests = ["pytest", "pytest-asyncio"] + +[[package]] +name = "astor" +version = "0.8.1" +description = "Read/rewrite/write Python ASTs" +category = "dev" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7" + +[[package]] +name = "atomicwrites" +version = "1.4.0" +description = "Atomic file writes." +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[[package]] +name = "attrs" +version = "20.3.0" +description = "Classes Without Boilerplate" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[package.extras] +dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface", "furo", "sphinx", "pre-commit"] +docs = ["furo", "sphinx", "zope.interface"] +tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface"] +tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six"] + +[[package]] +name = "black" +version = "20.8b1" +description = "The uncompromising code formatter." +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +appdirs = "*" +click = ">=7.1.2" +mypy-extensions = ">=0.4.3" +pathspec = ">=0.6,<1" +regex = ">=2020.1.8" +toml = ">=0.10.1" +typed-ast = ">=1.4.0" +typing-extensions = ">=3.7.4" + +[package.extras] +colorama = ["colorama (>=0.4.3)"] +d = ["aiohttp (>=3.3.2)", "aiohttp-cors"] + +[[package]] +name = "bx-py-utils" +version = "26" +description = "Various Python / Django utility functions" +category = "dev" +optional = false +python-versions = ">=3.6,<4.0.0" + +[package.dependencies] +django = "*" +python-stdnum = "*" + +[[package]] +name = "certifi" +version = "2020.12.5" +description = "Python package for providing Mozilla's CA Bundle." +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "chardet" +version = "4.0.0" +description = "Universal encoding detector for Python 2 and 3" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[[package]] +name = "click" +version = "7.1.2" +description = "Composable command line interface toolkit" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[[package]] +name = "colorama" +version = "0.4.4" +description = "Cross-platform colored terminal text." +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[[package]] +name = "coverage" +version = "5.4" +description = "Code coverage measurement for Python" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" + +[package.extras] +toml = ["toml"] + +[[package]] +name = "coveralls" +version = "3.0.0" +description = "Show coverage stats online via coveralls.io" +category = "dev" +optional = false +python-versions = ">= 3.5" + +[package.dependencies] +coverage = ">=4.1,<6.0" +docopt = ">=0.6.1" +requests = ">=1.0.0" + +[package.extras] +yaml = ["PyYAML (>=3.10)"] + +[[package]] +name = "distlib" +version = "0.3.1" +description = "Distribution utilities" +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "django" +version = "3.1.7" +description = "A high-level Python Web framework that encourages rapid development and clean, pragmatic design." +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +asgiref = ">=3.2.10,<4" +pytz = "*" +sqlparse = ">=0.2.2" + +[package.extras] +argon2 = ["argon2-cffi (>=16.1.0)"] +bcrypt = ["bcrypt"] + +[[package]] +name = "django-axes" +version = "5.13.1" +description = "Keep track of failed login attempts in Django-powered sites." +category = "main" +optional = false +python-versions = "~=3.6" + +[package.dependencies] +django = ">=2.2" +django-ipware = ">=3,<4" + +[[package]] +name = "django-ipware" +version = "3.0.2" +description = "A Django utility application that returns client's real IP address" +category = "main" +optional = false +python-versions = "*" + +[[package]] +name = "django-redis" +version = "4.12.1" +description = "Full featured redis cache backend for Django." +category = "main" +optional = false +python-versions = ">=3.5" + +[package.dependencies] +Django = ">=2.2" +redis = ">=3.0.0" + +[[package]] +name = "django-yunohost-integration" +version = "0.2.0a0" +description = "Glue code to package django projects as yunohost apps." +category = "main" +optional = false +python-versions = ">=3.7,<4.0.0" + +[package.dependencies] +django = "*" +django-axes = {version = "*", optional = true, markers = "extra == \"ynh\""} +django-redis = {version = "*", optional = true, markers = "extra == \"ynh\""} +gunicorn = {version = "*", optional = true, markers = "extra == \"ynh\""} +psycopg2-binary = {version = "*", optional = true, markers = "extra == \"ynh\""} + +[package.extras] +ynh = ["django-axes", "django-redis", "gunicorn", "psycopg2-binary"] + +[[package]] +name = "docopt" +version = "0.6.2" +description = "Pythonic argument parser, that will make you smile" +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "filelock" +version = "3.0.12" +description = "A platform independent file lock." +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "flake8" +version = "3.8.4" +description = "the modular source code checker: pep8 pyflakes and co" +category = "dev" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7" + +[package.dependencies] +importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} +mccabe = ">=0.6.0,<0.7.0" +pycodestyle = ">=2.6.0a1,<2.7.0" +pyflakes = ">=2.2.0,<2.3.0" + +[[package]] +name = "flynt" +version = "0.60" +description = "CLI tool to convert a python project's %-formatted strings to f-strings." +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +astor = "*" + +[[package]] +name = "gunicorn" +version = "20.0.4" +description = "WSGI HTTP Server for UNIX" +category = "main" +optional = false +python-versions = ">=3.4" + +[package.extras] +eventlet = ["eventlet (>=0.9.7)"] +gevent = ["gevent (>=0.13)"] +setproctitle = ["setproctitle"] +tornado = ["tornado (>=0.2)"] + +[[package]] +name = "idna" +version = "2.10" +description = "Internationalized Domain Names in Applications (IDNA)" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[[package]] +name = "importlib-metadata" +version = "3.7.0" +description = "Read metadata from Python packages" +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +typing-extensions = {version = ">=3.6.4", markers = "python_version < \"3.8\""} +zipp = ">=0.5" + +[package.extras] +docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] +testing = ["pytest (>=3.5,!=3.7.3)", "pytest-checkdocs (>=1.2.3)", "pytest-flake8", "pytest-cov", "pytest-enabler", "packaging", "pep517", "pyfakefs", "flufl.flake8", "pytest-black (>=0.3.7)", "pytest-mypy", "importlib-resources (>=1.3)"] + +[[package]] +name = "iniconfig" +version = "1.1.1" +description = "iniconfig: brain-dead simple config-ini parsing" +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "isort" +version = "5.7.0" +description = "A Python utility / library to sort Python imports." +category = "dev" +optional = false +python-versions = ">=3.6,<4.0" + +[package.extras] +pipfile_deprecated_finder = ["pipreqs", "requirementslib"] +requirements_deprecated_finder = ["pipreqs", "pip-api"] +colors = ["colorama (>=0.4.3,<0.5.0)"] + +[[package]] +name = "mccabe" +version = "0.6.1" +description = "McCabe checker, plugin for flake8" +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "mypy-extensions" +version = "0.4.3" +description = "Experimental type system extensions for programs checked with the mypy typechecker." +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "packaging" +version = "20.9" +description = "Core utilities for Python packages" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[package.dependencies] +pyparsing = ">=2.0.2" + +[[package]] +name = "pathspec" +version = "0.8.1" +description = "Utility library for gitignore style pattern matching of file paths." +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[[package]] +name = "pluggy" +version = "0.13.1" +description = "plugin and hook calling mechanisms for python" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[package.dependencies] +importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} + +[package.extras] +dev = ["pre-commit", "tox"] + +[[package]] +name = "psycopg2-binary" +version = "2.8.6" +description = "psycopg2 - Python-PostgreSQL Database Adapter" +category = "main" +optional = false +python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*" + +[[package]] +name = "py" +version = "1.10.0" +description = "library with cross-python path, ini-parsing, io, code, log facilities" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[[package]] +name = "pycodestyle" +version = "2.6.0" +description = "Python style guide checker" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[[package]] +name = "pyflakes" +version = "2.2.0" +description = "passive checker of Python programs" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[[package]] +name = "pyparsing" +version = "2.4.7" +description = "Python parsing module" +category = "dev" +optional = false +python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" + +[[package]] +name = "pytest" +version = "6.2.2" +description = "pytest: simple powerful testing with Python" +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +atomicwrites = {version = ">=1.0", markers = "sys_platform == \"win32\""} +attrs = ">=19.2.0" +colorama = {version = "*", markers = "sys_platform == \"win32\""} +importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} +iniconfig = "*" +packaging = "*" +pluggy = ">=0.12,<1.0.0a1" +py = ">=1.8.2" +toml = "*" + +[package.extras] +testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xmlschema"] + +[[package]] +name = "pytest-cov" +version = "2.11.1" +description = "Pytest plugin for measuring coverage." +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[package.dependencies] +coverage = ">=5.2.1" +pytest = ">=4.6" + +[package.extras] +testing = ["fields", "hunter", "process-tests (==2.0.2)", "six", "pytest-xdist", "virtualenv"] + +[[package]] +name = "pytest-django" +version = "4.1.0" +description = "A Django plugin for pytest." +category = "dev" +optional = false +python-versions = ">=3.5" + +[package.dependencies] +pytest = ">=5.4.0" + +[package.extras] +docs = ["sphinx", "sphinx-rtd-theme"] +testing = ["django", "django-configurations (>=2.0)"] + +[[package]] +name = "python-stdnum" +version = "1.16" +description = "Python module to handle standardized numbers and codes" +category = "dev" +optional = false +python-versions = "*" + +[package.extras] +soap = ["zeep"] +soap-alt = ["suds"] +soap-fallback = ["pysimplesoap"] + +[[package]] +name = "pytz" +version = "2021.1" +description = "World timezone definitions, modern and historical" +category = "main" +optional = false +python-versions = "*" + +[[package]] +name = "pyupgrade" +version = "2.10.0" +description = "A tool to automatically upgrade syntax for newer versions." +category = "dev" +optional = false +python-versions = ">=3.6.1" + +[package.dependencies] +tokenize-rt = ">=3.2.0" + +[[package]] +name = "redis" +version = "3.5.3" +description = "Python client for Redis key-value store" +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[package.extras] +hiredis = ["hiredis (>=0.1.3)"] + +[[package]] +name = "regex" +version = "2020.11.13" +description = "Alternative regular expression module, to replace re." +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "requests" +version = "2.25.1" +description = "Python HTTP for Humans." +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[package.dependencies] +certifi = ">=2017.4.17" +chardet = ">=3.0.2,<5" +idna = ">=2.5,<3" +urllib3 = ">=1.21.1,<1.27" + +[package.extras] +security = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)"] +socks = ["PySocks (>=1.5.6,!=1.5.7)", "win-inet-pton"] + +[[package]] +name = "six" +version = "1.15.0" +description = "Python 2 and 3 compatibility utilities" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" + +[[package]] +name = "sqlparse" +version = "0.4.1" +description = "A non-validating SQL parser." +category = "main" +optional = false +python-versions = ">=3.5" + +[[package]] +name = "tokenize-rt" +version = "4.1.0" +description = "A wrapper around the stdlib `tokenize` which roundtrips." +category = "dev" +optional = false +python-versions = ">=3.6.1" + +[[package]] +name = "toml" +version = "0.10.2" +description = "Python Library for Tom's Obvious, Minimal Language" +category = "dev" +optional = false +python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" + +[[package]] +name = "tox" +version = "3.22.0" +description = "tox is a generic virtualenv management and test command line tool" +category = "dev" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" + +[package.dependencies] +colorama = {version = ">=0.4.1", markers = "platform_system == \"Windows\""} +filelock = ">=3.0.0" +importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} +packaging = ">=14" +pluggy = ">=0.12.0" +py = ">=1.4.17" +six = ">=1.14.0" +toml = ">=0.9.4" +virtualenv = ">=16.0.0,<20.0.0 || >20.0.0,<20.0.1 || >20.0.1,<20.0.2 || >20.0.2,<20.0.3 || >20.0.3,<20.0.4 || >20.0.4,<20.0.5 || >20.0.5,<20.0.6 || >20.0.6,<20.0.7 || >20.0.7" + +[package.extras] +docs = ["pygments-github-lexers (>=0.0.5)", "sphinx (>=2.0.0)", "sphinxcontrib-autoprogram (>=0.1.5)", "towncrier (>=18.5.0)"] +testing = ["flaky (>=3.4.0)", "freezegun (>=0.3.11)", "psutil (>=5.6.1)", "pytest (>=4.0.0)", "pytest-cov (>=2.5.1)", "pytest-mock (>=1.10.0)", "pytest-randomly (>=1.0.0)", "pytest-xdist (>=1.22.2)", "pathlib2 (>=2.3.3)"] + +[[package]] +name = "typed-ast" +version = "1.4.2" +description = "a fork of Python 2 and 3 ast modules with type comment support" +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "typing-extensions" +version = "3.7.4.3" +description = "Backported and Experimental Type Hints for Python 3.5+" +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "urllib3" +version = "1.26.3" +description = "HTTP library with thread-safe connection pooling, file post, and more." +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" + +[package.extras] +brotli = ["brotlipy (>=0.6.0)"] +secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"] +socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] + +[[package]] +name = "virtualenv" +version = "20.4.2" +description = "Virtual Python Environment builder" +category = "dev" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7" + +[package.dependencies] +appdirs = ">=1.4.3,<2" +distlib = ">=0.3.1,<1" +filelock = ">=3.0.0,<4" +importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} +six = ">=1.9.0,<2" + +[package.extras] +docs = ["proselint (>=0.10.2)", "sphinx (>=3)", "sphinx-argparse (>=0.2.5)", "sphinx-rtd-theme (>=0.4.3)", "towncrier (>=19.9.0rc1)"] +testing = ["coverage (>=4)", "coverage-enable-subprocess (>=1)", "flaky (>=3)", "pytest (>=4)", "pytest-env (>=0.6.2)", "pytest-freezegun (>=0.4.1)", "pytest-mock (>=2)", "pytest-randomly (>=1)", "pytest-timeout (>=1)", "packaging (>=20.0)", "xonsh (>=0.9.16)"] + +[[package]] +name = "zipp" +version = "3.4.0" +description = "Backport of pathlib-compatible object wrapper for zip files" +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.extras] +docs = ["sphinx", "jaraco.packaging (>=3.2)", "rst.linker (>=1.9)"] +testing = ["pytest (>=3.5,!=3.7.3)", "pytest-checkdocs (>=1.2.3)", "pytest-flake8", "pytest-cov", "jaraco.test (>=3.2.0)", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy"] + +[metadata] +lock-version = "1.1" +python-versions = ">=3.7,<4.0.0" +content-hash = "d3487bcfd4579bd20ce6a8cd244b51d25e2ffe3c197b3cf5cfbb1b49b6cfbb75" + +[metadata.files] +appdirs = [ + {file = "appdirs-1.4.4-py2.py3-none-any.whl", hash = "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128"}, + {file = "appdirs-1.4.4.tar.gz", hash = "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41"}, +] +asgiref = [ + {file = "asgiref-3.3.1-py3-none-any.whl", hash = "sha256:5ee950735509d04eb673bd7f7120f8fa1c9e2df495394992c73234d526907e17"}, + {file = "asgiref-3.3.1.tar.gz", hash = "sha256:7162a3cb30ab0609f1a4c95938fd73e8604f63bdba516a7f7d64b83ff09478f0"}, +] +astor = [ + {file = "astor-0.8.1-py2.py3-none-any.whl", hash = "sha256:070a54e890cefb5b3739d19f30f5a5ec840ffc9c50ffa7d23cc9fc1a38ebbfc5"}, + {file = "astor-0.8.1.tar.gz", hash = "sha256:6a6effda93f4e1ce9f618779b2dd1d9d84f1e32812c23a29b3fff6fd7f63fa5e"}, +] +atomicwrites = [ + {file = "atomicwrites-1.4.0-py2.py3-none-any.whl", hash = "sha256:6d1784dea7c0c8d4a5172b6c620f40b6e4cbfdf96d783691f2e1302a7b88e197"}, + {file = "atomicwrites-1.4.0.tar.gz", hash = "sha256:ae70396ad1a434f9c7046fd2dd196fc04b12f9e91ffb859164193be8b6168a7a"}, +] +attrs = [ + {file = "attrs-20.3.0-py2.py3-none-any.whl", hash = "sha256:31b2eced602aa8423c2aea9c76a724617ed67cf9513173fd3a4f03e3a929c7e6"}, + {file = "attrs-20.3.0.tar.gz", hash = "sha256:832aa3cde19744e49938b91fea06d69ecb9e649c93ba974535d08ad92164f700"}, +] +black = [ + {file = "black-20.8b1.tar.gz", hash = "sha256:1c02557aa099101b9d21496f8a914e9ed2222ef70336404eeeac8edba836fbea"}, +] +bx-py-utils = [ + {file = "bx_py_utils-26-py3-none-any.whl", hash = "sha256:0afd16c3912f0f7c9b8e0b5de1a9181a7250a19e3a9eabd7444a278ca1a1f423"}, + {file = "bx_py_utils-26.tar.gz", hash = "sha256:8a50ee8a55d4b2278ed0a60f583b2d3802ed0ba894cee84133ee2ab85ab3f2ab"}, +] +certifi = [ + {file = "certifi-2020.12.5-py2.py3-none-any.whl", hash = "sha256:719a74fb9e33b9bd44cc7f3a8d94bc35e4049deebe19ba7d8e108280cfd59830"}, + {file = "certifi-2020.12.5.tar.gz", hash = "sha256:1a4995114262bffbc2413b159f2a1a480c969de6e6eb13ee966d470af86af59c"}, +] +chardet = [ + {file = "chardet-4.0.0-py2.py3-none-any.whl", hash = "sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5"}, + {file = "chardet-4.0.0.tar.gz", hash = "sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa"}, +] +click = [ + {file = "click-7.1.2-py2.py3-none-any.whl", hash = "sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc"}, + {file = "click-7.1.2.tar.gz", hash = "sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a"}, +] +colorama = [ + {file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"}, + {file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"}, +] +coverage = [ + {file = "coverage-5.4-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:6d9c88b787638a451f41f97446a1c9fd416e669b4d9717ae4615bd29de1ac135"}, + {file = "coverage-5.4-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:66a5aae8233d766a877c5ef293ec5ab9520929c2578fd2069308a98b7374ea8c"}, + {file = "coverage-5.4-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:9754a5c265f991317de2bac0c70a746efc2b695cf4d49f5d2cddeac36544fb44"}, + {file = "coverage-5.4-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:fbb17c0d0822684b7d6c09915677a32319f16ff1115df5ec05bdcaaee40b35f3"}, + {file = "coverage-5.4-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:b7f7421841f8db443855d2854e25914a79a1ff48ae92f70d0a5c2f8907ab98c9"}, + {file = "coverage-5.4-cp27-cp27m-win32.whl", hash = "sha256:4a780807e80479f281d47ee4af2eb2df3e4ccf4723484f77da0bb49d027e40a1"}, + {file = "coverage-5.4-cp27-cp27m-win_amd64.whl", hash = "sha256:87c4b38288f71acd2106f5d94f575bc2136ea2887fdb5dfe18003c881fa6b370"}, + {file = "coverage-5.4-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:c6809ebcbf6c1049002b9ac09c127ae43929042ec1f1dbd8bb1615f7cd9f70a0"}, + {file = "coverage-5.4-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:ba7ca81b6d60a9f7a0b4b4e175dcc38e8fef4992673d9d6e6879fd6de00dd9b8"}, + {file = "coverage-5.4-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:89fc12c6371bf963809abc46cced4a01ca4f99cba17be5e7d416ed7ef1245d19"}, + {file = "coverage-5.4-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:4a8eb7785bd23565b542b01fb39115a975fefb4a82f23d407503eee2c0106247"}, + {file = "coverage-5.4-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:7e40d3f8eb472c1509b12ac2a7e24158ec352fc8567b77ab02c0db053927e339"}, + {file = "coverage-5.4-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:1ccae21a076d3d5f471700f6d30eb486da1626c380b23c70ae32ab823e453337"}, + {file = "coverage-5.4-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:755c56beeacac6a24c8e1074f89f34f4373abce8b662470d3aa719ae304931f3"}, + {file = "coverage-5.4-cp35-cp35m-manylinux2010_i686.whl", hash = "sha256:322549b880b2d746a7672bf6ff9ed3f895e9c9f108b714e7360292aa5c5d7cf4"}, + {file = "coverage-5.4-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:60a3307a84ec60578accd35d7f0c71a3a971430ed7eca6567399d2b50ef37b8c"}, + {file = "coverage-5.4-cp35-cp35m-win32.whl", hash = "sha256:1375bb8b88cb050a2d4e0da901001347a44302aeadb8ceb4b6e5aa373b8ea68f"}, + {file = "coverage-5.4-cp35-cp35m-win_amd64.whl", hash = "sha256:16baa799ec09cc0dcb43a10680573269d407c159325972dd7114ee7649e56c66"}, + {file = "coverage-5.4-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:2f2cf7a42d4b7654c9a67b9d091ec24374f7c58794858bff632a2039cb15984d"}, + {file = "coverage-5.4-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:b62046592b44263fa7570f1117d372ae3f310222af1fc1407416f037fb3af21b"}, + {file = "coverage-5.4-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:812eaf4939ef2284d29653bcfee9665f11f013724f07258928f849a2306ea9f9"}, + {file = "coverage-5.4-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:859f0add98707b182b4867359e12bde806b82483fb12a9ae868a77880fc3b7af"}, + {file = "coverage-5.4-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:04b14e45d6a8e159c9767ae57ecb34563ad93440fc1b26516a89ceb5b33c1ad5"}, + {file = "coverage-5.4-cp36-cp36m-win32.whl", hash = "sha256:ebfa374067af240d079ef97b8064478f3bf71038b78b017eb6ec93ede1b6bcec"}, + {file = "coverage-5.4-cp36-cp36m-win_amd64.whl", hash = "sha256:84df004223fd0550d0ea7a37882e5c889f3c6d45535c639ce9802293b39cd5c9"}, + {file = "coverage-5.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:1b811662ecf72eb2d08872731636aee6559cae21862c36f74703be727b45df90"}, + {file = "coverage-5.4-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:6b588b5cf51dc0fd1c9e19f622457cc74b7d26fe295432e434525f1c0fae02bc"}, + {file = "coverage-5.4-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:3fe50f1cac369b02d34ad904dfe0771acc483f82a1b54c5e93632916ba847b37"}, + {file = "coverage-5.4-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:32ab83016c24c5cf3db2943286b85b0a172dae08c58d0f53875235219b676409"}, + {file = "coverage-5.4-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:68fb816a5dd901c6aff352ce49e2a0ffadacdf9b6fae282a69e7a16a02dad5fb"}, + {file = "coverage-5.4-cp37-cp37m-win32.whl", hash = "sha256:a636160680c6e526b84f85d304e2f0bb4e94f8284dd765a1911de9a40450b10a"}, + {file = "coverage-5.4-cp37-cp37m-win_amd64.whl", hash = "sha256:bb32ca14b4d04e172c541c69eec5f385f9a075b38fb22d765d8b0ce3af3a0c22"}, + {file = "coverage-5.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6c4d7165a4e8f41eca6b990c12ee7f44fef3932fac48ca32cecb3a1b2223c21f"}, + {file = "coverage-5.4-cp38-cp38-manylinux1_i686.whl", hash = "sha256:a565f48c4aae72d1d3d3f8e8fb7218f5609c964e9c6f68604608e5958b9c60c3"}, + {file = "coverage-5.4-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:fff1f3a586246110f34dc762098b5afd2de88de507559e63553d7da643053786"}, + {file = "coverage-5.4-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:a839e25f07e428a87d17d857d9935dd743130e77ff46524abb992b962eb2076c"}, + {file = "coverage-5.4-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:6625e52b6f346a283c3d563d1fd8bae8956daafc64bb5bbd2b8f8a07608e3994"}, + {file = "coverage-5.4-cp38-cp38-win32.whl", hash = "sha256:5bee3970617b3d74759b2d2df2f6a327d372f9732f9ccbf03fa591b5f7581e39"}, + {file = "coverage-5.4-cp38-cp38-win_amd64.whl", hash = "sha256:03ed2a641e412e42cc35c244508cf186015c217f0e4d496bf6d7078ebe837ae7"}, + {file = "coverage-5.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:14a9f1887591684fb59fdba8feef7123a0da2424b0652e1b58dd5b9a7bb1188c"}, + {file = "coverage-5.4-cp39-cp39-manylinux1_i686.whl", hash = "sha256:9564ac7eb1652c3701ac691ca72934dd3009997c81266807aef924012df2f4b3"}, + {file = "coverage-5.4-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:0f48fc7dc82ee14aeaedb986e175a429d24129b7eada1b7e94a864e4f0644dde"}, + {file = "coverage-5.4-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:107d327071061fd4f4a2587d14c389a27e4e5c93c7cba5f1f59987181903902f"}, + {file = "coverage-5.4-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:0cdde51bfcf6b6bd862ee9be324521ec619b20590787d1655d005c3fb175005f"}, + {file = "coverage-5.4-cp39-cp39-win32.whl", hash = "sha256:c67734cff78383a1f23ceba3b3239c7deefc62ac2b05fa6a47bcd565771e5880"}, + {file = "coverage-5.4-cp39-cp39-win_amd64.whl", hash = "sha256:c669b440ce46ae3abe9b2d44a913b5fd86bb19eb14a8701e88e3918902ecd345"}, + {file = "coverage-5.4-pp36-none-any.whl", hash = "sha256:c0ff1c1b4d13e2240821ef23c1efb1f009207cb3f56e16986f713c2b0e7cd37f"}, + {file = "coverage-5.4-pp37-none-any.whl", hash = "sha256:cd601187476c6bed26a0398353212684c427e10a903aeafa6da40c63309d438b"}, + {file = "coverage-5.4.tar.gz", hash = "sha256:6d2e262e5e8da6fa56e774fb8e2643417351427604c2b177f8e8c5f75fc928ca"}, +] +coveralls = [ + {file = "coveralls-3.0.0-py2.py3-none-any.whl", hash = "sha256:f8384968c57dee4b7133ae701ecdad88e85e30597d496dcba0d7fbb470dca41f"}, + {file = "coveralls-3.0.0.tar.gz", hash = "sha256:5399c0565ab822a70a477f7031f6c88a9dd196b3de2877b3facb43b51bd13434"}, +] +distlib = [ + {file = "distlib-0.3.1-py2.py3-none-any.whl", hash = "sha256:8c09de2c67b3e7deef7184574fc060ab8a793e7adbb183d942c389c8b13c52fb"}, + {file = "distlib-0.3.1.zip", hash = "sha256:edf6116872c863e1aa9d5bb7cb5e05a022c519a4594dc703843343a9ddd9bff1"}, +] +django = [ + {file = "Django-3.1.7-py3-none-any.whl", hash = "sha256:baf099db36ad31f970775d0be5587cc58a6256a6771a44eb795b554d45f211b8"}, + {file = "Django-3.1.7.tar.gz", hash = "sha256:32ce792ee9b6a0cbbec340123e229ac9f765dff8c2a4ae9247a14b2ba3a365a7"}, +] +django-axes = [ + {file = "django-axes-5.13.1.tar.gz", hash = "sha256:8f7870dc18ace6155127073bffe7276719c71c5ad4e67b45abedc0207f64a2f6"}, + {file = "django_axes-5.13.1-py3-none-any.whl", hash = "sha256:aef814f738742b01cc7730cd2bebe3e5b462b807fd6c2ed609b62437ccc71f80"}, +] +django-ipware = [ + {file = "django-ipware-3.0.2.tar.gz", hash = "sha256:c7df8e1410a8e5d6b1fbae58728402ea59950f043c3582e033e866f0f0cf5e94"}, +] +django-redis = [ + {file = "django-redis-4.12.1.tar.gz", hash = "sha256:306589c7021e6468b2656edc89f62b8ba67e8d5a1c8877e2688042263daa7a63"}, + {file = "django_redis-4.12.1-py3-none-any.whl", hash = "sha256:1133b26b75baa3664164c3f44b9d5d133d1b8de45d94d79f38d1adc5b1d502e5"}, +] +django-yunohost-integration = [ + {file = "django_yunohost_integration-0.2.0a0-py3-none-any.whl", hash = "sha256:a45197a3c4595a496674e7e48a58f58351b6022526893264831e0d6ba463a44f"}, + {file = "django_yunohost_integration-0.2.0a0.tar.gz", hash = "sha256:9acdf320537ccce47ceb1d51d2a00fafbf30938152d3b22c59a3a2ead7952227"}, +] +docopt = [ + {file = "docopt-0.6.2.tar.gz", hash = "sha256:49b3a825280bd66b3aa83585ef59c4a8c82f2c8a522dbe754a8bc8d08c85c491"}, +] +filelock = [ + {file = "filelock-3.0.12-py3-none-any.whl", hash = "sha256:929b7d63ec5b7d6b71b0fa5ac14e030b3f70b75747cef1b10da9b879fef15836"}, + {file = "filelock-3.0.12.tar.gz", hash = "sha256:18d82244ee114f543149c66a6e0c14e9c4f8a1044b5cdaadd0f82159d6a6ff59"}, +] +flake8 = [ + {file = "flake8-3.8.4-py2.py3-none-any.whl", hash = "sha256:749dbbd6bfd0cf1318af27bf97a14e28e5ff548ef8e5b1566ccfb25a11e7c839"}, + {file = "flake8-3.8.4.tar.gz", hash = "sha256:aadae8761ec651813c24be05c6f7b4680857ef6afaae4651a4eccaef97ce6c3b"}, +] +flynt = [ + {file = "flynt-0.60-py3-none-any.whl", hash = "sha256:2993c08ae355d797bd0b0ba798425de1e09436d7ff39c289e7f30e3f0e7715db"}, + {file = "flynt-0.60.tar.gz", hash = "sha256:e3fb84495682e8cd4bea1b0db331d96286eee2f886d82e0e2609751a68cb2fc6"}, +] +gunicorn = [ + {file = "gunicorn-20.0.4-py2.py3-none-any.whl", hash = "sha256:cd4a810dd51bf497552cf3f863b575dabd73d6ad6a91075b65936b151cbf4f9c"}, + {file = "gunicorn-20.0.4.tar.gz", hash = "sha256:1904bb2b8a43658807108d59c3f3d56c2b6121a701161de0ddf9ad140073c626"}, +] +idna = [ + {file = "idna-2.10-py2.py3-none-any.whl", hash = "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0"}, + {file = "idna-2.10.tar.gz", hash = "sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6"}, +] +importlib-metadata = [ + {file = "importlib_metadata-3.7.0-py3-none-any.whl", hash = "sha256:c6af5dbf1126cd959c4a8d8efd61d4d3c83bddb0459a17e554284a077574b614"}, + {file = "importlib_metadata-3.7.0.tar.gz", hash = "sha256:24499ffde1b80be08284100393955842be4a59c7c16bbf2738aad0e464a8e0aa"}, +] +iniconfig = [ + {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"}, + {file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"}, +] +isort = [ + {file = "isort-5.7.0-py3-none-any.whl", hash = "sha256:fff4f0c04e1825522ce6949973e83110a6e907750cd92d128b0d14aaaadbffdc"}, + {file = "isort-5.7.0.tar.gz", hash = "sha256:c729845434366216d320e936b8ad6f9d681aab72dc7cbc2d51bedc3582f3ad1e"}, +] +mccabe = [ + {file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"}, + {file = "mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"}, +] +mypy-extensions = [ + {file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"}, + {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"}, +] +packaging = [ + {file = "packaging-20.9-py2.py3-none-any.whl", hash = "sha256:67714da7f7bc052e064859c05c595155bd1ee9f69f76557e21f051443c20947a"}, + {file = "packaging-20.9.tar.gz", hash = "sha256:5b327ac1320dc863dca72f4514ecc086f31186744b84a230374cc1fd776feae5"}, +] +pathspec = [ + {file = "pathspec-0.8.1-py2.py3-none-any.whl", hash = "sha256:aa0cb481c4041bf52ffa7b0d8fa6cd3e88a2ca4879c533c9153882ee2556790d"}, + {file = "pathspec-0.8.1.tar.gz", hash = "sha256:86379d6b86d75816baba717e64b1a3a3469deb93bb76d613c9ce79edc5cb68fd"}, +] +pluggy = [ + {file = "pluggy-0.13.1-py2.py3-none-any.whl", hash = "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"}, + {file = "pluggy-0.13.1.tar.gz", hash = "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0"}, +] +psycopg2-binary = [ + {file = "psycopg2-binary-2.8.6.tar.gz", hash = "sha256:11b9c0ebce097180129e422379b824ae21c8f2a6596b159c7659e2e5a00e1aa0"}, + {file = "psycopg2_binary-2.8.6-cp27-cp27m-macosx_10_6_intel.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:d14b140a4439d816e3b1229a4a525df917d6ea22a0771a2a78332273fd9528a4"}, + {file = "psycopg2_binary-2.8.6-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:1fabed9ea2acc4efe4671b92c669a213db744d2af8a9fc5d69a8e9bc14b7a9db"}, + {file = "psycopg2_binary-2.8.6-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:f5ab93a2cb2d8338b1674be43b442a7f544a0971da062a5da774ed40587f18f5"}, + {file = "psycopg2_binary-2.8.6-cp27-cp27m-win32.whl", hash = "sha256:b4afc542c0ac0db720cf516dd20c0846f71c248d2b3d21013aa0d4ef9c71ca25"}, + {file = "psycopg2_binary-2.8.6-cp27-cp27m-win_amd64.whl", hash = "sha256:e74a55f6bad0e7d3968399deb50f61f4db1926acf4a6d83beaaa7df986f48b1c"}, + {file = "psycopg2_binary-2.8.6-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:0deac2af1a587ae12836aa07970f5cb91964f05a7c6cdb69d8425ff4c15d4e2c"}, + {file = "psycopg2_binary-2.8.6-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:ad20d2eb875aaa1ea6d0f2916949f5c08a19c74d05b16ce6ebf6d24f2c9f75d1"}, + {file = "psycopg2_binary-2.8.6-cp34-cp34m-win32.whl", hash = "sha256:950bc22bb56ee6ff142a2cb9ee980b571dd0912b0334aa3fe0fe3788d860bea2"}, + {file = "psycopg2_binary-2.8.6-cp34-cp34m-win_amd64.whl", hash = "sha256:b8a3715b3c4e604bcc94c90a825cd7f5635417453b253499664f784fc4da0152"}, + {file = "psycopg2_binary-2.8.6-cp35-cp35m-macosx_10_6_intel.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:d1b4ab59e02d9008efe10ceabd0b31e79519da6fb67f7d8e8977118832d0f449"}, + {file = "psycopg2_binary-2.8.6-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:ac0c682111fbf404525dfc0f18a8b5f11be52657d4f96e9fcb75daf4f3984859"}, + {file = "psycopg2_binary-2.8.6-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:7d92a09b788cbb1aec325af5fcba9fed7203897bbd9269d5691bb1e3bce29550"}, + {file = "psycopg2_binary-2.8.6-cp35-cp35m-win32.whl", hash = "sha256:aaa4213c862f0ef00022751161df35804127b78adf4a2755b9f991a507e425fd"}, + {file = "psycopg2_binary-2.8.6-cp35-cp35m-win_amd64.whl", hash = "sha256:c2507d796fca339c8fb03216364cca68d87e037c1f774977c8fc377627d01c71"}, + {file = "psycopg2_binary-2.8.6-cp36-cp36m-macosx_10_6_intel.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:ee69dad2c7155756ad114c02db06002f4cded41132cc51378e57aad79cc8e4f4"}, + {file = "psycopg2_binary-2.8.6-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:e82aba2188b9ba309fd8e271702bd0d0fc9148ae3150532bbb474f4590039ffb"}, + {file = "psycopg2_binary-2.8.6-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:d5227b229005a696cc67676e24c214740efd90b148de5733419ac9aaba3773da"}, + {file = "psycopg2_binary-2.8.6-cp36-cp36m-win32.whl", hash = "sha256:a0eb43a07386c3f1f1ebb4dc7aafb13f67188eab896e7397aa1ee95a9c884eb2"}, + {file = "psycopg2_binary-2.8.6-cp36-cp36m-win_amd64.whl", hash = "sha256:e1f57aa70d3f7cc6947fd88636a481638263ba04a742b4a37dd25c373e41491a"}, + {file = "psycopg2_binary-2.8.6-cp37-cp37m-macosx_10_6_intel.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:833709a5c66ca52f1d21d41865a637223b368c0ee76ea54ca5bad6f2526c7679"}, + {file = "psycopg2_binary-2.8.6-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:ba28584e6bca48c59eecbf7efb1576ca214b47f05194646b081717fa628dfddf"}, + {file = "psycopg2_binary-2.8.6-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:6a32f3a4cb2f6e1a0b15215f448e8ce2da192fd4ff35084d80d5e39da683e79b"}, + {file = "psycopg2_binary-2.8.6-cp37-cp37m-win32.whl", hash = "sha256:0e4dc3d5996760104746e6cfcdb519d9d2cd27c738296525d5867ea695774e67"}, + {file = "psycopg2_binary-2.8.6-cp37-cp37m-win_amd64.whl", hash = "sha256:cec7e622ebc545dbb4564e483dd20e4e404da17ae07e06f3e780b2dacd5cee66"}, + {file = "psycopg2_binary-2.8.6-cp38-cp38-macosx_10_9_x86_64.macosx_10_9_intel.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:ba381aec3a5dc29634f20692349d73f2d21f17653bda1decf0b52b11d694541f"}, + {file = "psycopg2_binary-2.8.6-cp38-cp38-manylinux1_i686.whl", hash = "sha256:a0c50db33c32594305b0ef9abc0cb7db13de7621d2cadf8392a1d9b3c437ef77"}, + {file = "psycopg2_binary-2.8.6-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:2dac98e85565d5688e8ab7bdea5446674a83a3945a8f416ad0110018d1501b94"}, + {file = "psycopg2_binary-2.8.6-cp38-cp38-win32.whl", hash = "sha256:bd1be66dde2b82f80afb9459fc618216753f67109b859a361cf7def5c7968729"}, + {file = "psycopg2_binary-2.8.6-cp38-cp38-win_amd64.whl", hash = "sha256:8cd0fb36c7412996859cb4606a35969dd01f4ea34d9812a141cd920c3b18be77"}, + {file = "psycopg2_binary-2.8.6-cp39-cp39-macosx_10_9_x86_64.macosx_10_9_intel.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:89705f45ce07b2dfa806ee84439ec67c5d9a0ef20154e0e475e2b2ed392a5b83"}, + {file = "psycopg2_binary-2.8.6-cp39-cp39-manylinux1_i686.whl", hash = "sha256:42ec1035841b389e8cc3692277a0bd81cdfe0b65d575a2c8862cec7a80e62e52"}, + {file = "psycopg2_binary-2.8.6-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:7312e931b90fe14f925729cde58022f5d034241918a5c4f9797cac62f6b3a9dd"}, + {file = "psycopg2_binary-2.8.6-cp39-cp39-win32.whl", hash = "sha256:6422f2ff0919fd720195f64ffd8f924c1395d30f9a495f31e2392c2efafb5056"}, + {file = "psycopg2_binary-2.8.6-cp39-cp39-win_amd64.whl", hash = "sha256:15978a1fbd225583dd8cdaf37e67ccc278b5abecb4caf6b2d6b8e2b948e953f6"}, +] +py = [ + {file = "py-1.10.0-py2.py3-none-any.whl", hash = "sha256:3b80836aa6d1feeaa108e046da6423ab8f6ceda6468545ae8d02d9d58d18818a"}, + {file = "py-1.10.0.tar.gz", hash = "sha256:21b81bda15b66ef5e1a777a21c4dcd9c20ad3efd0b3f817e7a809035269e1bd3"}, +] +pycodestyle = [ + {file = "pycodestyle-2.6.0-py2.py3-none-any.whl", hash = "sha256:2295e7b2f6b5bd100585ebcb1f616591b652db8a741695b3d8f5d28bdc934367"}, + {file = "pycodestyle-2.6.0.tar.gz", hash = "sha256:c58a7d2815e0e8d7972bf1803331fb0152f867bd89adf8a01dfd55085434192e"}, +] +pyflakes = [ + {file = "pyflakes-2.2.0-py2.py3-none-any.whl", hash = "sha256:0d94e0e05a19e57a99444b6ddcf9a6eb2e5c68d3ca1e98e90707af8152c90a92"}, + {file = "pyflakes-2.2.0.tar.gz", hash = "sha256:35b2d75ee967ea93b55750aa9edbbf72813e06a66ba54438df2cfac9e3c27fc8"}, +] +pyparsing = [ + {file = "pyparsing-2.4.7-py2.py3-none-any.whl", hash = "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"}, + {file = "pyparsing-2.4.7.tar.gz", hash = "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1"}, +] +pytest = [ + {file = "pytest-6.2.2-py3-none-any.whl", hash = "sha256:b574b57423e818210672e07ca1fa90aaf194a4f63f3ab909a2c67ebb22913839"}, + {file = "pytest-6.2.2.tar.gz", hash = "sha256:9d1edf9e7d0b84d72ea3dbcdfd22b35fb543a5e8f2a60092dd578936bf63d7f9"}, +] +pytest-cov = [ + {file = "pytest-cov-2.11.1.tar.gz", hash = "sha256:359952d9d39b9f822d9d29324483e7ba04a3a17dd7d05aa6beb7ea01e359e5f7"}, + {file = "pytest_cov-2.11.1-py2.py3-none-any.whl", hash = "sha256:bdb9fdb0b85a7cc825269a4c56b48ccaa5c7e365054b6038772c32ddcdc969da"}, +] +pytest-django = [ + {file = "pytest-django-4.1.0.tar.gz", hash = "sha256:26f02c16d36fd4c8672390deebe3413678d89f30720c16efb8b2a6bf63b9041f"}, + {file = "pytest_django-4.1.0-py3-none-any.whl", hash = "sha256:10e384e6b8912ded92db64c58be8139d9ae23fb8361e5fc139d8e4f8fc601bc2"}, +] +python-stdnum = [ + {file = "python-stdnum-1.16.tar.gz", hash = "sha256:4248d898042a801fc4eff96fbfe4bf63a43324854efe3b5534718c1c195c6f43"}, + {file = "python_stdnum-1.16-py2.py3-none-any.whl", hash = "sha256:2e2c56c548ca166b95547a8d748f4d71320a5b4896960717c8e6380e08d993a5"}, +] +pytz = [ + {file = "pytz-2021.1-py2.py3-none-any.whl", hash = "sha256:eb10ce3e7736052ed3623d49975ce333bcd712c7bb19a58b9e2089d4057d0798"}, + {file = "pytz-2021.1.tar.gz", hash = "sha256:83a4a90894bf38e243cf052c8b58f381bfe9a7a483f6a9cab140bc7f702ac4da"}, +] +pyupgrade = [ + {file = "pyupgrade-2.10.0-py2.py3-none-any.whl", hash = "sha256:b26a00db6e2d745fe5a949e1fd02c5286c3999edaf804f746c69d559c8f8b365"}, + {file = "pyupgrade-2.10.0.tar.gz", hash = "sha256:601427033f280d50b5b102fed1013b96f91244777772114aeb7e191762cd6050"}, +] +redis = [ + {file = "redis-3.5.3-py2.py3-none-any.whl", hash = "sha256:432b788c4530cfe16d8d943a09d40ca6c16149727e4afe8c2c9d5580c59d9f24"}, + {file = "redis-3.5.3.tar.gz", hash = "sha256:0e7e0cfca8660dea8b7d5cd8c4f6c5e29e11f31158c0b0ae91a397f00e5a05a2"}, +] +regex = [ + {file = "regex-2020.11.13-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:8b882a78c320478b12ff024e81dc7d43c1462aa4a3341c754ee65d857a521f85"}, + {file = "regex-2020.11.13-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:a63f1a07932c9686d2d416fb295ec2c01ab246e89b4d58e5fa468089cab44b70"}, + {file = "regex-2020.11.13-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:6e4b08c6f8daca7d8f07c8d24e4331ae7953333dbd09c648ed6ebd24db5a10ee"}, + {file = "regex-2020.11.13-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:bba349276b126947b014e50ab3316c027cac1495992f10e5682dc677b3dfa0c5"}, + {file = "regex-2020.11.13-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:56e01daca75eae420bce184edd8bb341c8eebb19dd3bce7266332258f9fb9dd7"}, + {file = "regex-2020.11.13-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:6a8ce43923c518c24a2579fda49f093f1397dad5d18346211e46f134fc624e31"}, + {file = "regex-2020.11.13-cp36-cp36m-manylinux2014_i686.whl", hash = "sha256:1ab79fcb02b930de09c76d024d279686ec5d532eb814fd0ed1e0051eb8bd2daa"}, + {file = "regex-2020.11.13-cp36-cp36m-manylinux2014_x86_64.whl", hash = "sha256:9801c4c1d9ae6a70aeb2128e5b4b68c45d4f0af0d1535500884d644fa9b768c6"}, + {file = "regex-2020.11.13-cp36-cp36m-win32.whl", hash = "sha256:49cae022fa13f09be91b2c880e58e14b6da5d10639ed45ca69b85faf039f7a4e"}, + {file = "regex-2020.11.13-cp36-cp36m-win_amd64.whl", hash = "sha256:749078d1eb89484db5f34b4012092ad14b327944ee7f1c4f74d6279a6e4d1884"}, + {file = "regex-2020.11.13-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b2f4007bff007c96a173e24dcda236e5e83bde4358a557f9ccf5e014439eae4b"}, + {file = "regex-2020.11.13-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:38c8fd190db64f513fe4e1baa59fed086ae71fa45083b6936b52d34df8f86a88"}, + {file = "regex-2020.11.13-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:5862975b45d451b6db51c2e654990c1820523a5b07100fc6903e9c86575202a0"}, + {file = "regex-2020.11.13-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:262c6825b309e6485ec2493ffc7e62a13cf13fb2a8b6d212f72bd53ad34118f1"}, + {file = "regex-2020.11.13-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:bafb01b4688833e099d79e7efd23f99172f501a15c44f21ea2118681473fdba0"}, + {file = "regex-2020.11.13-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:e32f5f3d1b1c663af7f9c4c1e72e6ffe9a78c03a31e149259f531e0fed826512"}, + {file = "regex-2020.11.13-cp37-cp37m-manylinux2014_i686.whl", hash = "sha256:3bddc701bdd1efa0d5264d2649588cbfda549b2899dc8d50417e47a82e1387ba"}, + {file = "regex-2020.11.13-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:02951b7dacb123d8ea6da44fe45ddd084aa6777d4b2454fa0da61d569c6fa538"}, + {file = "regex-2020.11.13-cp37-cp37m-win32.whl", hash = "sha256:0d08e71e70c0237883d0bef12cad5145b84c3705e9c6a588b2a9c7080e5af2a4"}, + {file = "regex-2020.11.13-cp37-cp37m-win_amd64.whl", hash = "sha256:1fa7ee9c2a0e30405e21031d07d7ba8617bc590d391adfc2b7f1e8b99f46f444"}, + {file = "regex-2020.11.13-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:baf378ba6151f6e272824b86a774326f692bc2ef4cc5ce8d5bc76e38c813a55f"}, + {file = "regex-2020.11.13-cp38-cp38-manylinux1_i686.whl", hash = "sha256:e3faaf10a0d1e8e23a9b51d1900b72e1635c2d5b0e1bea1c18022486a8e2e52d"}, + {file = "regex-2020.11.13-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:2a11a3e90bd9901d70a5b31d7dd85114755a581a5da3fc996abfefa48aee78af"}, + {file = "regex-2020.11.13-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:d1ebb090a426db66dd80df8ca85adc4abfcbad8a7c2e9a5ec7513ede522e0a8f"}, + {file = "regex-2020.11.13-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:b2b1a5ddae3677d89b686e5c625fc5547c6e492bd755b520de5332773a8af06b"}, + {file = "regex-2020.11.13-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:2c99e97d388cd0a8d30f7c514d67887d8021541b875baf09791a3baad48bb4f8"}, + {file = "regex-2020.11.13-cp38-cp38-manylinux2014_i686.whl", hash = "sha256:c084582d4215593f2f1d28b65d2a2f3aceff8342aa85afd7be23a9cad74a0de5"}, + {file = "regex-2020.11.13-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:a3d748383762e56337c39ab35c6ed4deb88df5326f97a38946ddd19028ecce6b"}, + {file = "regex-2020.11.13-cp38-cp38-win32.whl", hash = "sha256:7913bd25f4ab274ba37bc97ad0e21c31004224ccb02765ad984eef43e04acc6c"}, + {file = "regex-2020.11.13-cp38-cp38-win_amd64.whl", hash = "sha256:6c54ce4b5d61a7129bad5c5dc279e222afd00e721bf92f9ef09e4fae28755683"}, + {file = "regex-2020.11.13-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1862a9d9194fae76a7aaf0150d5f2a8ec1da89e8b55890b1786b8f88a0f619dc"}, + {file = "regex-2020.11.13-cp39-cp39-manylinux1_i686.whl", hash = "sha256:4902e6aa086cbb224241adbc2f06235927d5cdacffb2425c73e6570e8d862364"}, + {file = "regex-2020.11.13-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:7a25fcbeae08f96a754b45bdc050e1fb94b95cab046bf56b016c25e9ab127b3e"}, + {file = "regex-2020.11.13-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:d2d8ce12b7c12c87e41123997ebaf1a5767a5be3ec545f64675388970f415e2e"}, + {file = "regex-2020.11.13-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:f7d29a6fc4760300f86ae329e3b6ca28ea9c20823df123a2ea8693e967b29917"}, + {file = "regex-2020.11.13-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:717881211f46de3ab130b58ec0908267961fadc06e44f974466d1887f865bd5b"}, + {file = "regex-2020.11.13-cp39-cp39-manylinux2014_i686.whl", hash = "sha256:3128e30d83f2e70b0bed9b2a34e92707d0877e460b402faca908c6667092ada9"}, + {file = "regex-2020.11.13-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:8f6a2229e8ad946e36815f2a03386bb8353d4bde368fdf8ca5f0cb97264d3b5c"}, + {file = "regex-2020.11.13-cp39-cp39-win32.whl", hash = "sha256:f8f295db00ef5f8bae530fc39af0b40486ca6068733fb860b42115052206466f"}, + {file = "regex-2020.11.13-cp39-cp39-win_amd64.whl", hash = "sha256:a15f64ae3a027b64496a71ab1f722355e570c3fac5ba2801cafce846bf5af01d"}, + {file = "regex-2020.11.13.tar.gz", hash = "sha256:83d6b356e116ca119db8e7c6fc2983289d87b27b3fac238cfe5dca529d884562"}, +] +requests = [ + {file = "requests-2.25.1-py2.py3-none-any.whl", hash = "sha256:c210084e36a42ae6b9219e00e48287def368a26d03a048ddad7bfee44f75871e"}, + {file = "requests-2.25.1.tar.gz", hash = "sha256:27973dd4a904a4f13b263a19c866c13b92a39ed1c964655f025f3f8d3d75b804"}, +] +six = [ + {file = "six-1.15.0-py2.py3-none-any.whl", hash = "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced"}, + {file = "six-1.15.0.tar.gz", hash = "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259"}, +] +sqlparse = [ + {file = "sqlparse-0.4.1-py3-none-any.whl", hash = "sha256:017cde379adbd6a1f15a61873f43e8274179378e95ef3fede90b5aa64d304ed0"}, + {file = "sqlparse-0.4.1.tar.gz", hash = "sha256:0f91fd2e829c44362cbcfab3e9ae12e22badaa8a29ad5ff599f9ec109f0454e8"}, +] +tokenize-rt = [ + {file = "tokenize_rt-4.1.0-py2.py3-none-any.whl", hash = "sha256:b37251fa28c21e8cce2e42f7769a35fba2dd2ecafb297208f9a9a8add3ca7793"}, + {file = "tokenize_rt-4.1.0.tar.gz", hash = "sha256:ab339b5ff829eb5e198590477f9c03c84e762b3e455e74c018956e7e326cbc70"}, +] +toml = [ + {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, + {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, +] +tox = [ + {file = "tox-3.22.0-py2.py3-none-any.whl", hash = "sha256:89afa9c59c04beb55eda789c7a65feb1a70fde117f85f1bd1c27c66758456e60"}, + {file = "tox-3.22.0.tar.gz", hash = "sha256:ed1e650cf6368bcbc4a071eeeba363c480920e0ed8a9ad1793c7caaa5ad33d49"}, +] +typed-ast = [ + {file = "typed_ast-1.4.2-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:7703620125e4fb79b64aa52427ec192822e9f45d37d4b6625ab37ef403e1df70"}, + {file = "typed_ast-1.4.2-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:c9aadc4924d4b5799112837b226160428524a9a45f830e0d0f184b19e4090487"}, + {file = "typed_ast-1.4.2-cp35-cp35m-manylinux2014_aarch64.whl", hash = "sha256:9ec45db0c766f196ae629e509f059ff05fc3148f9ffd28f3cfe75d4afb485412"}, + {file = "typed_ast-1.4.2-cp35-cp35m-win32.whl", hash = "sha256:85f95aa97a35bdb2f2f7d10ec5bbdac0aeb9dafdaf88e17492da0504de2e6400"}, + {file = "typed_ast-1.4.2-cp35-cp35m-win_amd64.whl", hash = "sha256:9044ef2df88d7f33692ae3f18d3be63dec69c4fb1b5a4a9ac950f9b4ba571606"}, + {file = "typed_ast-1.4.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:c1c876fd795b36126f773db9cbb393f19808edd2637e00fd6caba0e25f2c7b64"}, + {file = "typed_ast-1.4.2-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:5dcfc2e264bd8a1db8b11a892bd1647154ce03eeba94b461effe68790d8b8e07"}, + {file = "typed_ast-1.4.2-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:8db0e856712f79c45956da0c9a40ca4246abc3485ae0d7ecc86a20f5e4c09abc"}, + {file = "typed_ast-1.4.2-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:d003156bb6a59cda9050e983441b7fa2487f7800d76bdc065566b7d728b4581a"}, + {file = "typed_ast-1.4.2-cp36-cp36m-win32.whl", hash = "sha256:4c790331247081ea7c632a76d5b2a265e6d325ecd3179d06e9cf8d46d90dd151"}, + {file = "typed_ast-1.4.2-cp36-cp36m-win_amd64.whl", hash = "sha256:d175297e9533d8d37437abc14e8a83cbc68af93cc9c1c59c2c292ec59a0697a3"}, + {file = "typed_ast-1.4.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:cf54cfa843f297991b7388c281cb3855d911137223c6b6d2dd82a47ae5125a41"}, + {file = "typed_ast-1.4.2-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:b4fcdcfa302538f70929eb7b392f536a237cbe2ed9cba88e3bf5027b39f5f77f"}, + {file = "typed_ast-1.4.2-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:987f15737aba2ab5f3928c617ccf1ce412e2e321c77ab16ca5a293e7bbffd581"}, + {file = "typed_ast-1.4.2-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:37f48d46d733d57cc70fd5f30572d11ab8ed92da6e6b28e024e4a3edfb456e37"}, + {file = "typed_ast-1.4.2-cp37-cp37m-win32.whl", hash = "sha256:36d829b31ab67d6fcb30e185ec996e1f72b892255a745d3a82138c97d21ed1cd"}, + {file = "typed_ast-1.4.2-cp37-cp37m-win_amd64.whl", hash = "sha256:8368f83e93c7156ccd40e49a783a6a6850ca25b556c0fa0240ed0f659d2fe496"}, + {file = "typed_ast-1.4.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:963c80b583b0661918718b095e02303d8078950b26cc00b5e5ea9ababe0de1fc"}, + {file = "typed_ast-1.4.2-cp38-cp38-manylinux1_i686.whl", hash = "sha256:e683e409e5c45d5c9082dc1daf13f6374300806240719f95dc783d1fc942af10"}, + {file = "typed_ast-1.4.2-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:84aa6223d71012c68d577c83f4e7db50d11d6b1399a9c779046d75e24bed74ea"}, + {file = "typed_ast-1.4.2-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:a38878a223bdd37c9709d07cd357bb79f4c760b29210e14ad0fb395294583787"}, + {file = "typed_ast-1.4.2-cp38-cp38-win32.whl", hash = "sha256:a2c927c49f2029291fbabd673d51a2180038f8cd5a5b2f290f78c4516be48be2"}, + {file = "typed_ast-1.4.2-cp38-cp38-win_amd64.whl", hash = "sha256:c0c74e5579af4b977c8b932f40a5464764b2f86681327410aa028a22d2f54937"}, + {file = "typed_ast-1.4.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:07d49388d5bf7e863f7fa2f124b1b1d89d8aa0e2f7812faff0a5658c01c59aa1"}, + {file = "typed_ast-1.4.2-cp39-cp39-manylinux1_i686.whl", hash = "sha256:240296b27397e4e37874abb1df2a608a92df85cf3e2a04d0d4d61055c8305ba6"}, + {file = "typed_ast-1.4.2-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:d746a437cdbca200622385305aedd9aef68e8a645e385cc483bdc5e488f07166"}, + {file = "typed_ast-1.4.2-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:14bf1522cdee369e8f5581238edac09150c765ec1cb33615855889cf33dcb92d"}, + {file = "typed_ast-1.4.2-cp39-cp39-win32.whl", hash = "sha256:cc7b98bf58167b7f2db91a4327da24fb93368838eb84a44c472283778fc2446b"}, + {file = "typed_ast-1.4.2-cp39-cp39-win_amd64.whl", hash = "sha256:7147e2a76c75f0f64c4319886e7639e490fee87c9d25cb1d4faef1d8cf83a440"}, + {file = "typed_ast-1.4.2.tar.gz", hash = "sha256:9fc0b3cb5d1720e7141d103cf4819aea239f7d136acf9ee4a69b047b7986175a"}, +] +typing-extensions = [ + {file = "typing_extensions-3.7.4.3-py2-none-any.whl", hash = "sha256:dafc7639cde7f1b6e1acc0f457842a83e722ccca8eef5270af2d74792619a89f"}, + {file = "typing_extensions-3.7.4.3-py3-none-any.whl", hash = "sha256:7cb407020f00f7bfc3cb3e7881628838e69d8f3fcab2f64742a5e76b2f841918"}, + {file = "typing_extensions-3.7.4.3.tar.gz", hash = "sha256:99d4073b617d30288f569d3f13d2bd7548c3a7e4c8de87db09a9d29bb3a4a60c"}, +] +urllib3 = [ + {file = "urllib3-1.26.3-py2.py3-none-any.whl", hash = "sha256:1b465e494e3e0d8939b50680403e3aedaa2bc434b7d5af64dfd3c958d7f5ae80"}, + {file = "urllib3-1.26.3.tar.gz", hash = "sha256:de3eedaad74a2683334e282005cd8d7f22f4d55fa690a2a1020a416cb0a47e73"}, +] +virtualenv = [ + {file = "virtualenv-20.4.2-py2.py3-none-any.whl", hash = "sha256:2be72df684b74df0ea47679a7df93fd0e04e72520022c57b479d8f881485dbe3"}, + {file = "virtualenv-20.4.2.tar.gz", hash = "sha256:147b43894e51dd6bba882cf9c282447f780e2251cd35172403745fc381a0a80d"}, +] +zipp = [ + {file = "zipp-3.4.0-py3-none-any.whl", hash = "sha256:102c24ef8f171fd729d46599845e95c7ab894a4cf45f5de11a44cc7444fb1108"}, + {file = "zipp-3.4.0.tar.gz", hash = "sha256:ed5eee1974372595f9e416cc7bbeeb12335201d8081ca8a0743c954d4446e5cb"}, +] diff --git a/publish.py b/publish.py deleted file mode 100644 index da362a1..0000000 --- a/publish.py +++ /dev/null @@ -1,31 +0,0 @@ -from pathlib import Path - -from poetry_publish.publish import poetry_publish -from poetry_publish.utils.subprocess_utils import verbose_check_call - -import django_ynh -from django_ynh.path_utils import assert_is_file - - -PACKAGE_ROOT = Path(django_ynh.__file__).parent.parent.parent - - -def publish(): - """ - Publish to PyPi - Call this via: - $ make publish - """ - assert_is_file(PACKAGE_ROOT / 'README.md') - - verbose_check_call('make', 'pytest') # don't publish if tests fail - verbose_check_call('make', 'fix-code-style') # don't publish if code style wrong - - poetry_publish( - package_root=PACKAGE_ROOT, - version=django_ynh.__version__, - ) - - -if __name__ == '__main__': - publish() diff --git a/pyproject.toml b/pyproject.toml index 8176ca5..7475fea 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,28 +1,15 @@ [tool.poetry] -name = "django_ynh" -version = "0.1.5" -description = "Glue code to package django projects as yunohost apps." +name = "django_example_ynh" +version = "v0.2.0.alpha0" +description = "Demo YunoHost Application to demonstrate the integration of a Django project under YunoHost." authors = ["JensDiemer "] license = "GPL" -readme = "README.md" -homepage = "https://github.com/YunoHost-Apps/django_ynh" -packages = [ - { include = "django_ynh" }, -] - [tool.poetry.dependencies] python = ">=3.7,<4.0.0" -django = "*" -# The follogin extra packages are used for install "django_ynh" as YunoHost app: -gunicorn = { version = "*", optional = true } -psycopg2-binary = { version = "*", optional = true } -django-redis = { version = "*", optional = true } -django-axes = { version = "*", optional = true } # https://github.com/jazzband/django-axes +django_yunohost_integration = {version = "*", extras = ["ynh"]} [tool.poetry.dev-dependencies] -django-axes = "*" # https://github.com/jazzband/django-axes -poetry-publish = "*" # https://github.com/jedie/poetry-publish bx_py_utils = "*" tox = "*" pytest = "*" @@ -35,23 +22,20 @@ flynt = "*" black = "*" pyupgrade = "*" -[tool.poetry.extras] -ynh = ["gunicorn", "psycopg2-binary", "django-redis", "django-axes"] # install as YunoHost app - - [build-system] requires = ["poetry-core>=1.0.0"] build-backend = "poetry.core.masonry.api" + [tool.isort] # https://pycqa.github.io/isort/docs/configuration/config_files/#pyprojecttoml-preferred-format atomic=true line_length=120 case_sensitive=false -skip_glob=["*/htmlcov/*","*/migrations/*","*/volumes/*"] +skip_glob=["*/htmlcov/*","*/migrations/*"] multi_line_output=3 include_trailing_comma=true -known_first_party=["django_ynh","django_ynh_project","django_ynh_tests"] +known_first_party=[] no_lines_before="LOCALFOLDER" default_section="THIRDPARTY" sections=["FUTURE","STDLIB","THIRDPARTY","FIRSTPARTY","LOCALFOLDER"] @@ -61,7 +45,7 @@ lines_after_imports=2 [tool.pytest.ini_options] # https://docs.pytest.org/en/latest/customize.html#pyproject-toml minversion = "6.0" -norecursedirs = ".* .git __pycache__ conf coverage* dist htmlcov volumes" +norecursedirs = ".* .git __pycache__ conf coverage* dist htmlcov" # sometimes helpfull "addopts" arguments: # -vv # --verbose @@ -96,7 +80,7 @@ skip_missing_interpreters = True [testenv] passenv = * -whitelist_externals = pytest +whitelist_externals = make commands = - pytest --workers auto --tests-per-worker 1 --pyargs django_ynh django_ynh_project + make pytest """ diff --git a/scripts/_common.sh b/scripts/_common.sh index 1c6e13d..cf62f1d 100644 --- a/scripts/_common.sh +++ b/scripts/_common.sh @@ -18,7 +18,7 @@ app=$YNH_APP_INSTANCE_NAME public_path=/var/www/$app final_path=/opt/yunohost/$app log_path=/var/log/$app -log_file="${log_path}/django_ynh.log" +log_file="${log_path}/django_example_ynh.log" #================================================= # COMMON VARIABLES @@ -27,9 +27,6 @@ log_file="${log_path}/django_ynh.log" # dependencies used by the app pkg_dependencies="build-essential python3-dev python3-pip python3-venv git postgresql postgresql-contrib" -# To install/upgrade this project via pip: -pip_install_string="django_ynh[ynh]==0.1.5" - #================================================= # Redis HELPERS #================================================= diff --git a/scripts/change_url b/scripts/change_url index 01fe801..2b32057 100644 --- a/scripts/change_url +++ b/scripts/change_url @@ -113,7 +113,7 @@ fi #================================================= # MODIFY SETTINGS #================================================= -ynh_script_progression --message="Modify django_ynh's config file..." +ynh_script_progression --message="Modify PyInventory's config file..." # save old settings file settings="$final_path/settings.py" diff --git a/scripts/install b/scripts/install index e48af2a..3085b18 100755 --- a/scripts/install +++ b/scripts/install @@ -114,13 +114,12 @@ chown -R "$app" "$final_path" set -o nounset ynh_exec_as $app $final_path/venv/bin/pip install --upgrade pip ynh_exec_as $app $final_path/venv/bin/pip install -r "$final_path/requirements.txt" - ynh_exec_as $app $final_path/venv/bin/pip install --upgrade "$pip_install_string" ) #================================================= # copy config files # ================================================ -ynh_script_progression --message="Create django_ynh configuration file..." +ynh_script_progression --message="Create project configuration files..." gunicorn_conf="$final_path/gunicorn.conf.py" cp "../conf/gunicorn.conf.py" "$gunicorn_conf" @@ -158,11 +157,10 @@ cp ../conf/wsgi.py "$final_path/wsgi.py" touch "$final_path/local_settings.py" - #================================================= -# MIGRATE / COLLECTSTATIC / CREATE SUPERUSER +# MIGRATE / COLLECTSTATIC / CREATEADMIN #================================================= -ynh_script_progression --message="migrate/collectstatic/create superuser..." --weight=10 +ynh_script_progression --message="migrate/collectstatic/createadmin..." --weight=10 ( set +o nounset @@ -176,8 +174,8 @@ ynh_script_progression --message="migrate/collectstatic/create superuser..." --w ./manage.py migrate --no-input ./manage.py collectstatic --no-input - # Create/update Django superuser (set unusable password, because auth done via SSOwat): - ./manage.py create_superuser --username="$admin" --email="$admin_mail" + # Create/update Django superuser (set unusable password, because auth done via SSOwat): + ./manage.py create_superuser --username="$admin" --email="$admin_mail" # Check the configuration # This may fail in some cases with errors, etc., but the app works and the user can fix issues later. @@ -216,7 +214,7 @@ chown -R "$app" "$final_path" ynh_script_progression --message="Configuring a systemd service..." # https://github.com/YunoHost/yunohost/blob/dev/data/helpers.d/systemd -ynh_add_systemd_config --service="$app" --template="django_ynh.service" +ynh_add_systemd_config --service="$app" --template="django_example_ynh.service" #================================================= # SETUP SSOWAT @@ -232,9 +230,9 @@ then fi #================================================= -# Start django_ynh via systemd +# Start django_example_ynh via systemd #================================================= -ynh_script_progression --message="Starting django_ynh's services..." --weight=5 +ynh_script_progression --message="Starting PyInventory's services..." --weight=5 ynh_systemd_action --service_name="$app" --action="start" diff --git a/scripts/remove b/scripts/remove index 3befc84..5e630a4 100755 --- a/scripts/remove +++ b/scripts/remove @@ -34,7 +34,7 @@ then fi #================================================= -# STOP django_ynh'S SERVICES +# STOP PYINVENTORY'S SERVICES #================================================= ynh_script_progression --message="Stopping and removing systemd services..." --weight=5 diff --git a/scripts/restore b/scripts/restore index 87dc87f..1302c03 100755 --- a/scripts/restore +++ b/scripts/restore @@ -118,7 +118,7 @@ ynh_restore_file --origin_path="/etc/logrotate.d/$app" #================================================= # GENERIC FINALIZATION #================================================= -# START django_ynh +# START PYINVENTORY #================================================= ynh_script_progression --message="Starting a systemd service..." --weight=5 diff --git a/scripts/upgrade b/scripts/upgrade index 69c0bce..5beb487 100755 --- a/scripts/upgrade +++ b/scripts/upgrade @@ -80,7 +80,7 @@ ynh_system_user_create --username="$app" --home_dir="$final_path" --use_shell #================================================= ynh_script_progression --message="Configuring a systemd service..." -ynh_add_systemd_config --service="$app" --template="django_ynh.service" +ynh_add_systemd_config --service="$app" --template="django_example_ynh.service" #================================================= # UPGRADE VENV @@ -98,7 +98,6 @@ chown -R "$app" "$final_path" set -o nounset ynh_exec_as $app $final_path/venv/bin/pip install --upgrade pip ynh_exec_as $app $final_path/venv/bin/pip install -r "$final_path/requirements.txt" - ynh_exec_as $app $final_path/venv/bin/pip install --upgrade "$pip_install_string" ) #================================================= @@ -150,30 +149,29 @@ cp ../conf/wsgi.py "$final_path/wsgi.py" touch "$final_path/local_settings.py" - #================================================= -# MIGRATE / COLLECTSTATIC / CREATE SUPERUSER +# MIGRATE PYINVENTORY #================================================= -ynh_script_progression --message="migrate/collectstatic/create superuser..." --weight=10 +ynh_script_progression --message="migrate/collectstatic/createadmin..." --weight=10 ( - set +o nounset - source "${final_path}/venv/bin/activate" - set -o nounset - cd "${final_path}" + set +o nounset + source "${final_path}/venv/bin/activate" + set -o nounset + cd "${final_path}" - # Just for debugging: - ./manage.py diffsettings + # Just for debugging: + ./manage.py diffsettings - ./manage.py migrate --no-input - ./manage.py collectstatic --no-input + ./manage.py migrate --no-input + ./manage.py collectstatic --no-input - # Create/update Django superuser (set unusable password, because auth done via SSOwat): - ./manage.py create_superuser --username="$admin" --email="$admin_mail" + # Create/update Django superuser (set unusable password, because auth done via SSOwat): + ./manage.py create_superuser --username="$admin" --email="$admin_mail" - # Check the configuration - # This may fail in some cases with errors, etc., but the app works and the user can fix issues later. - ./manage.py check --deploy || true + # 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 ) #================================================= @@ -203,9 +201,9 @@ chown -R "$app" "$public_path" chown -R "$app" "$final_path" #================================================= -# Start django_ynh via systemd +# Start django_example_ynh via systemd #================================================= -ynh_script_progression --message="Starting django_ynh's services..." --weight=5 +ynh_script_progression --message="Starting PyInventory's services..." --weight=5 ynh_systemd_action --service_name="$app" --action="start" diff --git a/tests/test_django_ynh.py b/tests/test_django_project.py similarity index 85% rename from tests/test_django_ynh.py rename to tests/test_django_project.py index aefe636..0b4f3d7 100644 --- a/tests/test_django_ynh.py +++ b/tests/test_django_project.py @@ -1,14 +1,16 @@ -from axes.models import AccessAttempt, AccessLog +from axes.models import AccessLog from bx_py_utils.test_utils.html_assertion import HtmlAssertionMixin from django.conf import settings from django.contrib.auth.models import User from django.test import override_settings from django.test.testcases import TestCase +from django.urls import NoReverseMatch from django.urls.base import reverse - from django_ynh.test_utils import generate_basic_auth from django_ynh.views import request_media_debug_view +import inventory + @override_settings(DEBUG=False) class DjangoYnhTestCase(HtmlAssertionMixin, TestCase): @@ -23,13 +25,22 @@ class DjangoYnhTestCase(HtmlAssertionMixin, TestCase): assert str(settings.FINAL_HOME_PATH).endswith('/local_test/opt_yunohost') assert str(settings.FINAL_WWW_PATH).endswith('/local_test/var_www') - assert str(settings.LOG_FILE).endswith('/local_test/var_log_django_ynh.log') + assert str(settings.LOG_FILE).endswith('/local_test/var_log_django_example_ynh.log') assert settings.ROOT_URLCONF == 'urls' def test_urls(self): assert reverse('admin:index') == '/app_path/' - assert reverse(request_media_debug_view) == '/app_path/debug/' + + # The django_ynh debug view should not be avaiable: + with self.assertRaises(NoReverseMatch): + reverse(request_media_debug_view) + + # Serve user uploads via django_tools.serve_media_app: + assert settings.MEDIA_URL == '/app_path/media/' + assert reverse('serve_media_app:serve-media', kwargs={'user_token': 'token', 'path': 'foo/bar/'}) == ( + '/app_path/media/token/foo/bar/' + ) def test_auth(self): response = self.client.get('/app_path/') @@ -55,7 +66,11 @@ class DjangoYnhTestCase(HtmlAssertionMixin, TestCase): assert user.is_superuser is False self.assert_html_parts( - response, parts=('Site administration | Django site admin', 'test') + response, + parts=( + f'Site administration | PyInventory v{inventory.__version__}', + 'test', + ), ) def test_wrong_auth_user(self): diff --git a/tests/test_lint.py b/tests/test_lint.py index 86a87c6..003dc76 100644 --- a/tests/test_lint.py +++ b/tests/test_lint.py @@ -2,10 +2,8 @@ import shutil import subprocess from pathlib import Path -import django_ynh - -BASE_PATH = Path(django_ynh.__file__).parent.parent +BASE_PATH = Path(__file__).parent.parent def test_lint(): diff --git a/tests/test_project_setup.py b/tests/test_project_setup.py index aa391b4..3efd244 100644 --- a/tests/test_project_setup.py +++ b/tests/test_project_setup.py @@ -3,10 +3,10 @@ import shutil import subprocess from pathlib import Path -import django_ynh +import django_exyunohost_integration -PACKAGE_ROOT = Path(django_ynh.__file__).parent.parent +PACKAGE_ROOT = Path(__file__).parent.parent def assert_file_contains_string(file_path, string): @@ -17,29 +17,15 @@ def assert_file_contains_string(file_path, string): raise AssertionError(f'File {file_path} does not contain {string!r} !') -def test_version(package_root=None, version=None): - if package_root is None: - package_root = PACKAGE_ROOT +def test_version(): + version = inventory.__version__ - if version is None: - version = django_ynh.__version__ - - if 'dev' not in version and 'rc' not in version: - version_string = f'v{version}' - - assert_file_contains_string(file_path=Path(package_root, 'README.md'), string=version_string) - - assert_file_contains_string(file_path=Path(package_root, 'pyproject.toml'), string=f'version = "{version}"') - assert_file_contains_string(file_path=Path(package_root, 'manifest.json'), string=f'"version": "{version}~ynh') - assert_file_contains_string( - file_path=Path(package_root, 'scripts', '_common.sh'), string=f'"django_ynh[ynh]=={version}"' - ) + assert_file_contains_string(file_path=Path(PACKAGE_ROOT, 'pyproject.toml'), string=f'version = "{version}~ynh') + assert_file_contains_string(file_path=Path(PACKAGE_ROOT, 'pyproject.toml'), string=f'django_example_ynh = "=={version}"') + assert_file_contains_string(file_path=Path(PACKAGE_ROOT, 'manifest.json'), string=f'"version": "{version}~ynh') -def test_poetry_check(package_root=None): - if package_root is None: - package_root = PACKAGE_ROOT - +def test_poetry_check(): poerty_bin = shutil.which('poetry') output = subprocess.check_output( @@ -47,7 +33,7 @@ def test_poetry_check(package_root=None): universal_newlines=True, env=os.environ, stderr=subprocess.STDOUT, - cwd=str(package_root), + cwd=str(PACKAGE_ROOT), ) print(output) assert output == 'All set!\n'