mirror of
https://github.com/YunoHost-Apps/django-for-runners_ynh.git
synced 2024-09-03 18:26:16 +02:00
commit
b6f970e090
31 changed files with 2558 additions and 499 deletions
7
.flake8
Normal file
7
.flake8
Normal file
|
@ -0,0 +1,7 @@
|
|||
#
|
||||
# Move to pyproject.toml after: https://gitlab.com/pycqa/flake8/-/issues/428
|
||||
#
|
||||
[flake8]
|
||||
exclude = .pytest_cache, .tox, dist, htmlcov, local_test
|
||||
ignore = F405
|
||||
max-line-length = 119
|
41
.github/workflows/pytest.yml
vendored
Normal file
41
.github/workflows/pytest.yml
vendored
Normal file
|
@ -0,0 +1,41 @@
|
|||
name: pytest
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 8 * * *'
|
||||
push:
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
max-parallel: 2
|
||||
matrix:
|
||||
python-version: [3.9, 3.8, 3.7]
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- name: 'Set up Python ${{ matrix.python-version }}'
|
||||
uses: actions/setup-python@v1
|
||||
with:
|
||||
python-version: '${{ matrix.python-version }}'
|
||||
|
||||
- name: 'Install package'
|
||||
run: |
|
||||
pip3 install poetry
|
||||
make install
|
||||
|
||||
- name: 'List installed packages'
|
||||
run: |
|
||||
poetry run pip freeze
|
||||
|
||||
- name: 'Run tests with Python v${{ matrix.python-version }}'
|
||||
run: |
|
||||
make pytest
|
||||
|
||||
- name: 'Upload coverage report'
|
||||
run: bash <(curl -s https://codecov.io/bash)
|
||||
|
||||
- name: 'Run linters'
|
||||
if: matrix.python-version == '3.8'
|
||||
run: |
|
||||
make lint
|
4
.gitignore
vendored
4
.gitignore
vendored
|
@ -1,8 +1,10 @@
|
|||
.*
|
||||
!.github
|
||||
!.editorconfig
|
||||
!.flake8
|
||||
!.gitignore
|
||||
__pycache__
|
||||
secret.txt
|
||||
/local_test/
|
||||
/poetry.lock
|
||||
/coverage.xml
|
||||
/htmlcov/
|
||||
|
|
29
Makefile
29
Makefile
|
@ -1,4 +1,5 @@
|
|||
SHELL := /bin/bash
|
||||
MAX_LINE_LENGTH := 119
|
||||
|
||||
all: help
|
||||
|
||||
|
@ -16,16 +17,38 @@ check-poetry:
|
|||
fi
|
||||
|
||||
install-poetry: ## install or update poetry
|
||||
pip3 install -U pip
|
||||
pip3 install -U poetry
|
||||
|
||||
install: check-poetry ## install project via poetry
|
||||
poetry install
|
||||
|
||||
update: install-poetry ## update the sources and installation
|
||||
update: install-poetry ## update the sources and installation and generate "conf/requirements.txt"
|
||||
poetry run pip install -U pip
|
||||
poetry update
|
||||
poetry export -f requirements.txt --output conf/requirements.txt
|
||||
|
||||
local-test: check-poetry ## Run local_test.py to run the project locally
|
||||
poetry run ./local_test.py
|
||||
lint: ## Run code formatters and linter
|
||||
poetry run flynt --fail-on-change --line_length=${MAX_LINE_LENGTH} .
|
||||
poetry run isort --check-only .
|
||||
poetry run flake8 .
|
||||
|
||||
fix-code-style: ## Fix code formatting
|
||||
poetry run flynt --line_length=${MAX_LINE_LENGTH} .
|
||||
poetry run black --verbose --safe --line-length=${MAX_LINE_LENGTH} --skip-string-normalization .
|
||||
poetry run isort .
|
||||
|
||||
tox-listenvs: check-poetry ## List all tox test environments
|
||||
poetry run tox --listenvs
|
||||
|
||||
tox: check-poetry ## Run pytest via tox with all environments
|
||||
poetry run tox
|
||||
|
||||
pytest: install ## Run pytest
|
||||
poetry run python3 ./run_pytest.py
|
||||
|
||||
local-test: install ## Run local_test.py to run the project locally
|
||||
poetry run python3 ./local_test.py
|
||||
|
||||
local-diff-settings: ## Run "manage.py diffsettings" with local test
|
||||
poetry run python3 local_test/opt_yunohost/manage.py diffsettings
|
||||
|
|
18
README.md
18
README.md
|
@ -1,9 +1,9 @@
|
|||
# Django-For-Runners for YunoHost
|
||||
# Django-ForRunners for YunoHost
|
||||
|
||||
[![Integration level](https://dash.yunohost.org/integration/django-for-runners.svg)](https://dash.yunohost.org/appci/app/django-for-runners) ![](https://ci-apps.yunohost.org/ci/badges/django-for-runners.status.svg) ![](https://ci-apps.yunohost.org/ci/badges/django-for-runners.maintain.svg)
|
||||
[![Install Django-For-Runners with YunoHost](https://install-app.yunohost.org/install-with-yunohost.png)](https://install-app.yunohost.org/?app=django-for-runners)
|
||||
[![Install Django-ForRunners with YunoHost](https://install-app.yunohost.org/install-with-yunohost.png)](https://install-app.yunohost.org/?app=django-for-runners)
|
||||
|
||||
> *This package allows you to install Django-For-Runners quickly and simply on a YunoHost server.
|
||||
> *This package allows you to install Django-ForRunners quickly and simply on a YunoHost server.
|
||||
If you don't have YunoHost, please consult [the guide](https://yunohost.org/#/install) to learn how to install it.*
|
||||
|
||||
Current status is pre-alpha: This app doesn't work, yet ;)
|
||||
|
@ -12,7 +12,7 @@ Pull requests welcome ;)
|
|||
|
||||
## Overview
|
||||
|
||||
Django-For-Runners is a libre web-based management to catalog things including state and location etc. using Python/Django.
|
||||
Django-ForRunners is a libre web-based management to catalog things including state and location etc. using Python/Django.
|
||||
|
||||
## Screenshots
|
||||
|
||||
|
@ -26,7 +26,7 @@ An admin user is created at installation, the login is what you provided at inst
|
|||
|
||||
## Settings and upgrades
|
||||
|
||||
Almost everything related to Django-For-Runners's configuration is handled in a `"../conf/ynh_for_runners_settings.py"` file.
|
||||
Almost everything related to Django-ForRunners's configuration is handled in a `"../conf/ynh_for_runners_settings.py"` file.
|
||||
You can edit the file `$final_path/local_settings.py` to enable or disable features.
|
||||
|
||||
# Miscellaneous
|
||||
|
@ -34,7 +34,7 @@ You can edit the file `$final_path/local_settings.py` to enable or disable featu
|
|||
|
||||
## SSO authentication
|
||||
|
||||
[SSOwat](https://github.com/YunoHost/SSOwat) is fully supported:
|
||||
[SSOwat](https://github.com/YunoHost/SSOwat) is fully supported via [django_ynh](https://github.com/YunoHost-Apps/django_ynh):
|
||||
|
||||
* First user (`$YNH_APP_ARG_ADMIN`) will be created as Django's super user
|
||||
* All new users will be created as normal users
|
||||
|
@ -45,7 +45,7 @@ You can edit the file `$final_path/local_settings.py` to enable or disable featu
|
|||
## Links
|
||||
|
||||
* Report a bug about this package: https://github.com/YunoHost-Apps/django-for-runners_ynh
|
||||
* Report a bug about Django-For-Runners itself: https://github.com/jedie/django-for-runners
|
||||
* Report a bug about Django-ForRunners itself: https://github.com/jedie/django-for-runners
|
||||
* YunoHost website: https://yunohost.org/
|
||||
|
||||
---
|
||||
|
@ -99,7 +99,7 @@ drwxr-xr-x 6 django-for-runners django-for-runners 6 Dec 8 08:37 venv
|
|||
root@yunohost:~# cd /opt/yunohost/django-for-runners/
|
||||
root@yunohost:/opt/yunohost/django-for-runners# source venv/bin/activate
|
||||
(venv) root@yunohost:/opt/yunohost/django-for-runners# ./manage.py check
|
||||
Django-For-Runners v0.12.0rc2 (Django v2.2.17)
|
||||
Django-ForRunners v0.12.0rc3 (Django v2.2.17)
|
||||
DJANGO_SETTINGS_MODULE='ynh_django-for-runners_settings'
|
||||
PROJECT_PATH:/opt/yunohost/django-for-runners/venv/lib/python3.7/site-packages
|
||||
BASE_PATH:/opt/yunohost/django-for-runners
|
||||
|
@ -114,7 +114,7 @@ root@yunohost:~# journalctl --unit=django-for-runners --follow
|
|||
|
||||
## local test
|
||||
|
||||
For quicker developing of Django-For-Runners in the context of YunoHost app,
|
||||
For quicker developing of Django-ForRunners in the context of YunoHost app,
|
||||
it's possible to run the Django developer server with the settings
|
||||
and urls made for YunoHost installation.
|
||||
|
||||
|
|
|
@ -1,46 +0,0 @@
|
|||
#!/usr/bin/env python3
|
||||
import argparse
|
||||
import os
|
||||
import sys
|
||||
|
||||
|
||||
def main():
|
||||
os.environ['DJANGO_SETTINGS_MODULE'] = 'ynh_for_runners_settings'
|
||||
|
||||
parser = argparse.ArgumentParser(
|
||||
description='Create or update Django super user.'
|
||||
)
|
||||
parser.add_argument('--username')
|
||||
parser.add_argument('--email')
|
||||
parser.add_argument('--password')
|
||||
|
||||
args = parser.parse_args()
|
||||
username = args.username
|
||||
email = args.email or ''
|
||||
password = args.password
|
||||
|
||||
import django
|
||||
django.setup()
|
||||
|
||||
from django.contrib.auth import get_user_model
|
||||
User = get_user_model()
|
||||
user = User.objects.filter(username=username).first()
|
||||
if user:
|
||||
print('Update existing user and set his password.', file=sys.stderr)
|
||||
user.is_active = True
|
||||
user.is_staff = True
|
||||
user.is_superuser = True
|
||||
user.set_password(password)
|
||||
user.email = email
|
||||
user.save()
|
||||
else:
|
||||
print('Create new super user', file=sys.stderr)
|
||||
User.objects.create_superuser(
|
||||
username=username,
|
||||
email=email,
|
||||
password=password
|
||||
)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
|
@ -1,5 +1,5 @@
|
|||
[Unit]
|
||||
Description=Django-For-Runners application server
|
||||
Description=Django-ForRunners application server
|
||||
After=redis.service postgresql.service
|
||||
|
||||
[Service]
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
"""
|
||||
import multiprocessing
|
||||
|
||||
|
||||
bind = '127.0.0.1:__PORT__'
|
||||
|
||||
# https://docs.gunicorn.org/en/latest/settings.html#workers
|
||||
|
|
|
@ -5,8 +5,9 @@ import sys
|
|||
|
||||
|
||||
def main():
|
||||
os.environ['DJANGO_SETTINGS_MODULE'] = 'ynh_for_runners_settings'
|
||||
os.environ['DJANGO_SETTINGS_MODULE'] = 'settings'
|
||||
from django.core.management import execute_from_command_line
|
||||
|
||||
execute_from_command_line(sys.argv)
|
||||
|
||||
|
||||
|
|
|
@ -12,7 +12,7 @@ location __PATH__/static/ {
|
|||
# expires 30d;
|
||||
#}
|
||||
|
||||
location / {
|
||||
location __PATH__/ {
|
||||
# https://github.com/benoitc/gunicorn/blob/master/examples/nginx.conf
|
||||
|
||||
# this is needed if you have file import via upload enabled
|
||||
|
@ -30,5 +30,5 @@ location / {
|
|||
proxy_connect_timeout 30;
|
||||
proxy_redirect off;
|
||||
|
||||
proxy_pass http://127.0.0.1:__PORT__/;
|
||||
proxy_pass http://127.0.0.1:__PORT__;
|
||||
}
|
||||
|
|
353
conf/requirements.txt
Normal file
353
conf/requirements.txt
Normal file
|
@ -0,0 +1,353 @@
|
|||
autotask==0.5.4; python_version >= "3.7" and python_full_version < "4.0.0" \
|
||||
--hash=sha256:d21578ab14adafb0d9be3490a444d261fd2bcdcbb1ee941b28d4b5e5d1a386ad
|
||||
bleach==3.2.1; python_version >= "3.7" and python_full_version < "3.0.0" or python_version >= "3.7" and python_full_version < "4.0.0" and python_full_version >= "3.5.0" \
|
||||
--hash=sha256:9f8ccbeb6183c6e6cddea37592dfb0167485c1e3b13b3363bc325aa8bda3adbd \
|
||||
--hash=sha256:52b5919b81842b1854196eaae5ca29679a2f2e378905c346d3ca8227c2c66080
|
||||
bx-py-utils==20; python_version >= "3.6" and python_full_version < "4.0.0" \
|
||||
--hash=sha256:8b7d21e89e5cc99e400ab0bd910632c2d79c7b27aa45afaaa65f4dec2274496e \
|
||||
--hash=sha256:4fc9b3a78bf82a02ee5d823f45af4623f8c3bec360434b4ae4b87d01cc0ff03a
|
||||
certifi==2020.12.5; 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:719a74fb9e33b9bd44cc7f3a8d94bc35e4049deebe19ba7d8e108280cfd59830 \
|
||||
--hash=sha256:1a4995114262bffbc2413b159f2a1a480c969de6e6eb13ee966d470af86af59c
|
||||
chardet==4.0.0; python_version >= "3.7" and python_full_version < "3.0.0" or python_version >= "3.7" and python_full_version < "4.0.0" and python_full_version >= "3.5.0" \
|
||||
--hash=sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5 \
|
||||
--hash=sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa
|
||||
click==7.1.2; python_version >= "3.7" and python_full_version < "3.0.0" or python_version >= "3.7" and python_full_version < "4.0.0" and python_full_version >= "3.5.0" \
|
||||
--hash=sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc \
|
||||
--hash=sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a
|
||||
colorama==0.4.4; python_version >= "3.7" and python_full_version < "3.0.0" and sys_platform == "win32" or python_version >= "3.7" and python_full_version < "4.0.0" and sys_platform == "win32" and python_full_version >= "3.5.0" \
|
||||
--hash=sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2 \
|
||||
--hash=sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b
|
||||
colorlog==4.7.2; python_version >= "3.7" and python_full_version < "4.0.0" \
|
||||
--hash=sha256:0a9dcdba6cab68e8a768448b418a858d73c52b37b6e8dea2568296faece393bd \
|
||||
--hash=sha256:18d05b616438a75762d7d214b9ec3b05d274466c9f3ddd92807e755840c88251
|
||||
cycler==0.10.0; python_version >= "3.7" and python_full_version < "4.0.0" \
|
||||
--hash=sha256:1d8a5ae1ff6c5cf9b93e8811e581232ad8920aeec647c37316ceac982b08cb2d \
|
||||
--hash=sha256:cd7b2d1018258d7247a71425e9f26463dfb444d411c39569972f4ce586b0c9d8
|
||||
defusedxml==0.6.0; python_version >= "3.7" and python_full_version < "3.0.0" or python_version >= "3.7" and python_full_version < "4.0.0" and python_full_version >= "3.5.0" \
|
||||
--hash=sha256:6687150770438374ab581bb7a1b327a847dd9c5749e396102de3fad4e8a3ef93 \
|
||||
--hash=sha256:f684034d135af4c6cbb949b8a4d2ed61634515257a67299e5f940fbaa34377f5
|
||||
diff-match-patch==20200713; python_version >= "3.7" and python_full_version < "4.0.0" \
|
||||
--hash=sha256:da6f5a01aa586df23dfc89f3827e1cafbb5420be9d87769eeb079ddfd9477a18 \
|
||||
--hash=sha256:8bf9d9c4e059d917b5c6312bac0c137971a32815ddbda9c682b949f2986b4d34
|
||||
django-axes==5.12.0; python_version >= "3.7" and python_version < "4.0" and python_full_version < "4.0.0" \
|
||||
--hash=sha256:c26167f7ca2003df8358eb23537dffb1d97bd9f44ccef70d5c64a7aba2349456 \
|
||||
--hash=sha256:db4e17fad2e07baa02bb210c5f819fb5137c548e3a5a4330ccc8662bb88d42c7
|
||||
django-dbbackup==3.3.0; python_version >= "3.7" and python_full_version < "4.0.0" \
|
||||
--hash=sha256:bb109735cae98b64ad084e5b461b7aca2d7b39992f10c9ed9435e3ebb6fb76c8
|
||||
django-debug-toolbar==3.2; python_version >= "3.7" and python_full_version < "4.0.0" \
|
||||
--hash=sha256:84e2607d900dbd571df0a2acf380b47c088efb787dce9805aefeb407341961d2 \
|
||||
--hash=sha256:9e5a25d0c965f7e686f6a8ba23613ca9ca30184daa26487706d4829f5cfb697a
|
||||
django-for-runners==0.12.0rc3; python_version >= "3.7" and python_full_version < "4.0.0" \
|
||||
--hash=sha256:5ff9d5b5705a4d431ac27bc863f1718f7a0768088d216c2274dba7dd0d15fb92 \
|
||||
--hash=sha256:16e7efb59fbd54c72233fc278dffd3a7f68d0601feb9b8c32929bc65c9cf2cb5
|
||||
django-import-export==2.5.0; python_version >= "3.7" and python_full_version < "4.0.0" \
|
||||
--hash=sha256:c39c003bfc803fb63ba7742562f1667603a4a8d7426261845d75ce8582d40f48 \
|
||||
--hash=sha256:cf6f3dabdd4f32dcb26e25c7ddcba7aee3168b55d380b0da79f0349afa17c011
|
||||
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-processinfo==1.0.2; python_version >= "3.7" and python_full_version < "4.0.0" \
|
||||
--hash=sha256:3cacad233a5428a5cc6292eafbfddd97948d8d1e4f47e47175a7fcdfcee90f12 \
|
||||
--hash=sha256:08aefdf7285d1eaa595e46570bcc12f2dfd9e24594d524246110614819076b6c
|
||||
django-redis==4.12.1; python_version >= "3.5" \
|
||||
--hash=sha256:306589c7021e6468b2656edc89f62b8ba67e8d5a1c8877e2688042263daa7a63 \
|
||||
--hash=sha256:1133b26b75baa3664164c3f44b9d5d133d1b8de45d94d79f38d1adc5b1d502e5
|
||||
django-tools==0.48.3; python_version >= "3.7" and python_full_version < "4.0.0" \
|
||||
--hash=sha256:08ed2ae606067f49c2c3949055227a826c8b880e5816114925eca386cf1823af \
|
||||
--hash=sha256:40444fa16b703b7c6960a800ba76aad42472c9aa70040d549a4d91dbb47a5ddb
|
||||
django-ynh==0.1.4; python_version >= "3.7" and python_full_version < "4.0.0" \
|
||||
--hash=sha256:29250e9dd615f83772a7dc50f25706ebe4e8b776f576d1ed43f6c60289ecd26b \
|
||||
--hash=sha256:35788a7b91a685a80b15f462796fedb604752c2c8957a6edfb0a777346efa66e
|
||||
django==2.2.17; python_version >= "3.7" and python_full_version < "4.0.0" and python_version < "4.0" \
|
||||
--hash=sha256:558cb27930defd9a6042133258caf797b2d1dee233959f537e3dc475cb49bd7c \
|
||||
--hash=sha256:cf5370a4d7765a9dd6d42a7b96b53c74f9446cd38209211304b210fe0404b861
|
||||
et-xmlfile==1.0.1; python_version >= "3.7" and python_full_version < "4.0.0" \
|
||||
--hash=sha256:614d9722d572f6246302c4491846d2c393c199cfa4edc9af593437691683335b
|
||||
geographiclib==1.50; python_version >= "3.7" and python_full_version < "4.0.0" \
|
||||
--hash=sha256:51cfa698e7183792bce27d8fb63ac8e83689cd8170a730bf35e1a5c5bf8849b9 \
|
||||
--hash=sha256:12bd46ee7ec25b291ea139b17aa991e7ef373e21abd053949b75c0e9ca55c632
|
||||
geopy==2.1.0; python_version >= "3.7" and python_full_version < "4.0.0" \
|
||||
--hash=sha256:4db8a2b79a2b3358a7d020ea195be639251a831a1b429c0d1b20c9f00c67c788 \
|
||||
--hash=sha256:892b219413e7955587b029949af3a1949c6fbac9d5ad17b79d850718f6a9550f
|
||||
gpxpy==1.4.2; python_version >= "3.7" and python_full_version < "4.0.0" \
|
||||
--hash=sha256:0832041899cdfdc5a607291bbef3d73042e16ffcecc3f2cb9631b699db0bb53f
|
||||
gunicorn==20.0.4; python_version >= "3.4" \
|
||||
--hash=sha256:cd4a810dd51bf497552cf3f863b575dabd73d6ad6a91075b65936b151cbf4f9c \
|
||||
--hash=sha256:1904bb2b8a43658807108d59c3f3d56c2b6121a701161de0ddf9ad140073c626
|
||||
icdiff==1.9.1; python_version >= "3.7" and python_full_version < "4.0.0" \
|
||||
--hash=sha256:66972dd03318da55280991db375d3ef6b66d948c67af96c1ebdb21587e86655e
|
||||
idna==2.10; 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:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0 \
|
||||
--hash=sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6
|
||||
jdcal==1.4.1; python_version >= "3.7" and python_full_version < "4.0.0" \
|
||||
--hash=sha256:1abf1305fce18b4e8aa248cf8fe0c56ce2032392bc64bbd61b5dff2a19ec8bba \
|
||||
--hash=sha256:472872e096eb8df219c23f2689fc336668bdb43d194094b5cc1707e1640acfc8
|
||||
kiwisolver==1.3.1; python_version >= "3.7" and python_full_version < "4.0.0" \
|
||||
--hash=sha256:fd34fbbfbc40628200730bc1febe30631347103fc8d3d4fa012c21ab9c11eca9 \
|
||||
--hash=sha256:d3155d828dec1d43283bd24d3d3e0d9c7c350cdfcc0bd06c0ad1209c1bbc36d0 \
|
||||
--hash=sha256:5a7a7dbff17e66fac9142ae2ecafb719393aaee6a3768c9de2fd425c63b53e21 \
|
||||
--hash=sha256:f8d6f8db88049a699817fd9178782867bf22283e3813064302ac59f61d95be05 \
|
||||
--hash=sha256:5f6ccd3dd0b9739edcf407514016108e2280769c73a85b9e59aa390046dbf08b \
|
||||
--hash=sha256:225e2e18f271e0ed8157d7f4518ffbf99b9450fca398d561eb5c4a87d0986dd9 \
|
||||
--hash=sha256:cf8b574c7b9aa060c62116d4181f3a1a4e821b2ec5cbfe3775809474113748d4 \
|
||||
--hash=sha256:232c9e11fd7ac3a470d65cd67e4359eee155ec57e822e5220322d7b2ac84fbf0 \
|
||||
--hash=sha256:b38694dcdac990a743aa654037ff1188c7a9801ac3ccc548d3341014bc5ca278 \
|
||||
--hash=sha256:ca3820eb7f7faf7f0aa88de0e54681bddcb46e485beb844fcecbcd1c8bd01689 \
|
||||
--hash=sha256:c8fd0f1ae9d92b42854b2979024d7597685ce4ada367172ed7c09edf2cef9cb8 \
|
||||
--hash=sha256:1e1bc12fb773a7b2ffdeb8380609f4f8064777877b2225dec3da711b421fda31 \
|
||||
--hash=sha256:72c99e39d005b793fb7d3d4e660aed6b6281b502e8c1eaf8ee8346023c8e03bc \
|
||||
--hash=sha256:8be8d84b7d4f2ba4ffff3665bcd0211318aa632395a1a41553250484a871d454 \
|
||||
--hash=sha256:31dfd2ac56edc0ff9ac295193eeaea1c0c923c0355bf948fbd99ed6018010b72 \
|
||||
--hash=sha256:563c649cfdef27d081c84e72a03b48ea9408c16657500c312575ae9d9f7bc1c3 \
|
||||
--hash=sha256:78751b33595f7f9511952e7e60ce858c6d64db2e062afb325985ddbd34b5c131 \
|
||||
--hash=sha256:a357fd4f15ee49b4a98b44ec23a34a95f1e00292a139d6015c11f55774ef10de \
|
||||
--hash=sha256:5989db3b3b34b76c09253deeaf7fbc2707616f130e166996606c284395da3f18 \
|
||||
--hash=sha256:c08e95114951dc2090c4a630c2385bef681cacf12636fb0241accdc6b303fd81 \
|
||||
--hash=sha256:44a62e24d9b01ba94ae7a4a6c3fb215dc4af1dde817e7498d901e229aaf50e4e \
|
||||
--hash=sha256:50af681a36b2a1dee1d3c169ade9fdc59207d3c31e522519181e12f1b3ba7000 \
|
||||
--hash=sha256:a53d27d0c2a0ebd07e395e56a1fbdf75ffedc4a05943daf472af163413ce9598 \
|
||||
--hash=sha256:834ee27348c4aefc20b479335fd422a2c69db55f7d9ab61721ac8cd83eb78882 \
|
||||
--hash=sha256:5c3e6455341008a054cccee8c5d24481bcfe1acdbc9add30aa95798e95c65621 \
|
||||
--hash=sha256:acef3d59d47dd85ecf909c359d0fd2c81ed33bdff70216d3956b463e12c38a54 \
|
||||
--hash=sha256:c5518d51a0735b1e6cee1fdce66359f8d2b59c3ca85dc2b0813a8aa86818a030 \
|
||||
--hash=sha256:b9edd0110a77fc321ab090aaa1cfcaba1d8499850a12848b81be2222eab648f6 \
|
||||
--hash=sha256:0cd53f403202159b44528498de18f9285b04482bab2a6fc3f5dd8dbb9352e30d \
|
||||
--hash=sha256:33449715e0101e4d34f64990352bce4095c8bf13bed1b390773fc0a7295967b3 \
|
||||
--hash=sha256:401a2e9afa8588589775fe34fc22d918ae839aaaf0c0e96441c0fdbce6d8ebe6 \
|
||||
--hash=sha256:950a199911a8d94683a6b10321f9345d5a3a8433ec58b217ace979e18f16e248
|
||||
lxml==4.6.2; python_version >= "3.7" and python_full_version < "3.0.0" or python_version >= "3.7" and python_full_version < "4.0.0" and python_full_version >= "3.5.0" \
|
||||
--hash=sha256:a9d6bc8642e2c67db33f1247a77c53476f3a166e09067c0474facb045756087f \
|
||||
--hash=sha256:791394449e98243839fa822a637177dd42a95f4883ad3dec2a0ce6ac99fb0a9d \
|
||||
--hash=sha256:68a5d77e440df94011214b7db907ec8f19e439507a70c958f750c18d88f995d2 \
|
||||
--hash=sha256:fc37870d6716b137e80d19241d0e2cff7a7643b925dfa49b4c8ebd1295eb506e \
|
||||
--hash=sha256:69a63f83e88138ab7642d8f61418cf3180a4d8cd13995df87725cb8b893e950e \
|
||||
--hash=sha256:42ebca24ba2a21065fb546f3e6bd0c58c3fe9ac298f3a320147029a4850f51a2 \
|
||||
--hash=sha256:f83d281bb2a6217cd806f4cf0ddded436790e66f393e124dfe9731f6b3fb9afe \
|
||||
--hash=sha256:535f067002b0fd1a4e5296a8f1bf88193080ff992a195e66964ef2a6cfec5388 \
|
||||
--hash=sha256:366cb750140f221523fa062d641393092813b81e15d0e25d9f7c6025f910ee80 \
|
||||
--hash=sha256:97db258793d193c7b62d4e2586c6ed98d51086e93f9a3af2b2034af01450a74b \
|
||||
--hash=sha256:648914abafe67f11be7d93c1a546068f8eff3c5fa938e1f94509e4a5d682b2d8 \
|
||||
--hash=sha256:4e751e77006da34643ab782e4a5cc21ea7b755551db202bc4d3a423b307db780 \
|
||||
--hash=sha256:681d75e1a38a69f1e64ab82fe4b1ed3fd758717bed735fb9aeaa124143f051af \
|
||||
--hash=sha256:127f76864468d6630e1b453d3ffbbd04b024c674f55cf0a30dc2595137892d37 \
|
||||
--hash=sha256:4fb85c447e288df535b17ebdebf0ec1cf3a3f1a8eba7e79169f4f37af43c6b98 \
|
||||
--hash=sha256:5be4a2e212bb6aa045e37f7d48e3e1e4b6fd259882ed5a00786f82e8c37ce77d \
|
||||
--hash=sha256:8c88b599e226994ad4db29d93bc149aa1aff3dc3a4355dd5757569ba78632bdf \
|
||||
--hash=sha256:6e4183800f16f3679076dfa8abf2db3083919d7e30764a069fb66b2b9eff9939 \
|
||||
--hash=sha256:d8d3d4713f0c28bdc6c806a278d998546e8efc3498949e3ace6e117462ac0a5e \
|
||||
--hash=sha256:8246f30ca34dc712ab07e51dc34fea883c00b7ccb0e614651e49da2c49a30711 \
|
||||
--hash=sha256:923963e989ffbceaa210ac37afc9b906acebe945d2723e9679b643513837b089 \
|
||||
--hash=sha256:1471cee35eba321827d7d53d104e7b8c593ea3ad376aa2df89533ce8e1b24a01 \
|
||||
--hash=sha256:2363c35637d2d9d6f26f60a208819e7eafc4305ce39dc1d5005eccc4593331c2 \
|
||||
--hash=sha256:f4822c0660c3754f1a41a655e37cb4dbbc9be3d35b125a37fab6f82d47674ebc \
|
||||
--hash=sha256:0448576c148c129594d890265b1a83b9cd76fd1f0a6a04620753d9a6bcfd0a4d \
|
||||
--hash=sha256:60a20bfc3bd234d54d49c388950195d23a5583d4108e1a1d47c9eef8d8c042b3 \
|
||||
--hash=sha256:2e5cc908fe43fe1aa299e58046ad66981131a66aea3129aac7770c37f590a644 \
|
||||
--hash=sha256:50c348995b47b5a4e330362cf39fc503b4a43b14a91c34c83b955e1805c8e308 \
|
||||
--hash=sha256:94d55bd03d8671686e3f012577d9caa5421a07286dd351dfef64791cf7c6c505 \
|
||||
--hash=sha256:7a7669ff50f41225ca5d6ee0a1ec8413f3a0d8aa2b109f86d540887b7ec0d72a \
|
||||
--hash=sha256:e0bfe9bb028974a481410432dbe1b182e8191d5d40382e5b8ff39cdd2e5c5931 \
|
||||
--hash=sha256:6fd8d5903c2e53f49e99359b063df27fdf7acb89a52b6a12494208bf61345a03 \
|
||||
--hash=sha256:7e9eac1e526386df7c70ef253b792a0a12dd86d833b1d329e038c7a235dfceb5 \
|
||||
--hash=sha256:7ee8af0b9f7de635c61cdd5b8534b76c52cd03536f29f51151b377f76e214a1a \
|
||||
--hash=sha256:2e6fd1b8acd005bd71e6c94f30c055594bbd0aa02ef51a22bbfa961ab63b2d75 \
|
||||
--hash=sha256:535332fe9d00c3cd455bd3dd7d4bacab86e2d564bdf7606079160fa6251caacf \
|
||||
--hash=sha256:cd11c7e8d21af997ee8079037fff88f16fda188a9776eb4b81c7e4c9c0a7d7fc
|
||||
markuppy==1.14; python_version >= "3.7" and python_full_version < "4.0.0" \
|
||||
--hash=sha256:1adee2c0a542af378fe84548ff6f6b0168f3cb7f426b46961038a2bcfaad0d5f
|
||||
matplotlib==3.3.3; python_version >= "3.7" and python_full_version < "4.0.0" \
|
||||
--hash=sha256:b2a5e1f637a92bb6f3526cc54cc8af0401112e81ce5cba6368a1b7908f9e18bc \
|
||||
--hash=sha256:c586ac1d64432f92857c3cf4478cfb0ece1ae18b740593f8a39f2f0b27c7fda5 \
|
||||
--hash=sha256:9b03722c89a43a61d4d148acfc89ec5bb54cd0fd1539df25b10eb9c5fa6c393a \
|
||||
--hash=sha256:2c2c5041608cb75c39cbd0ed05256f8a563e144234a524c59d091abbfa7a868f \
|
||||
--hash=sha256:c092fc4673260b1446b8578015321081d5db73b94533fe4bf9b69f44e948d174 \
|
||||
--hash=sha256:27c9393fada62bd0ad7c730562a0fecbd3d5aaa8d9ed80ba7d3ebb8abc4f0453 \
|
||||
--hash=sha256:b8ba2a1dbb4660cb469fe8e1febb5119506059e675180c51396e1723ff9b79d9 \
|
||||
--hash=sha256:0caa687fce6174fef9b27d45f8cc57cbc572e04e98c81db8e628b12b563d59a2 \
|
||||
--hash=sha256:b7b09c61a91b742cb5460b72efd1fe26ef83c1c704f666e0af0df156b046aada \
|
||||
--hash=sha256:6ffd2d80d76df2e5f9f0c0140b5af97e3b87dd29852dcdb103ec177d853ec06b \
|
||||
--hash=sha256:5111d6d47a0f5b8f3e10af7a79d5e7eb7e73a22825391834734274c4f312a8a0 \
|
||||
--hash=sha256:a4fe54eab2c7129add75154823e6543b10261f9b65b2abe692d68743a4999f8c \
|
||||
--hash=sha256:83e6c895d93fdf93eeff1a21ee96778ba65ef258e5d284160f7c628fee40c38f \
|
||||
--hash=sha256:b26c472847911f5a7eb49e1c888c31c77c4ddf8023c1545e0e8e0367ba74fb15 \
|
||||
--hash=sha256:09225edca87a79815822eb7d3be63a83ebd4d9d98d5aa3a15a94f4eee2435954 \
|
||||
--hash=sha256:eb6b6700ea454bb88333d98601e74928e06f9669c1ea231b4c4c666c1d7701b4 \
|
||||
--hash=sha256:2d31aff0c8184b05006ad756b9a4dc2a0805e94d28f3abc3187e881b6673b302 \
|
||||
--hash=sha256:d082f77b4ed876ae94a9373f0db96bf8768a7cca6c58fc3038f94e30ffde1880 \
|
||||
--hash=sha256:e71cdd402047e657c1662073e9361106c6981e9621ab8c249388dfc3ec1de07b \
|
||||
--hash=sha256:756ee498b9ba35460e4cbbd73f09018e906daa8537fff61da5b5bf8d5e9de5c7 \
|
||||
--hash=sha256:7ad44f2c74c50567c694ee91c6fa16d67e7c8af6f22c656b80469ad927688457 \
|
||||
--hash=sha256:3a4c3e9be63adf8e9b305aa58fb3ec40ecc61fd0f8fd3328ce55bc30e7a2aeb0 \
|
||||
--hash=sha256:746897fbd72bd462b888c74ed35d812ca76006b04f717cd44698cdfc99aca70d \
|
||||
--hash=sha256:5ed3d3342698c2b1f3651f8ea6c099b0f196d16ee00e33dc3a6fee8cb01d530a \
|
||||
--hash=sha256:b1b60c6476c4cfe9e5cf8ab0d3127476fd3d5f05de0f343a452badaad0e4bdec
|
||||
numpy==1.19.5; python_version >= "3.7" and python_full_version < "4.0.0" \
|
||||
--hash=sha256:cc6bd4fd593cb261332568485e20a0712883cf631f6f5e8e86a52caa8b2b50ff \
|
||||
--hash=sha256:aeb9ed923be74e659984e321f609b9ba54a48354bfd168d21a2b072ed1e833ea \
|
||||
--hash=sha256:8b5e972b43c8fc27d56550b4120fe6257fdc15f9301914380b27f74856299fea \
|
||||
--hash=sha256:43d4c81d5ffdff6bae58d66a3cd7f54a7acd9a0e7b18d97abb255defc09e3140 \
|
||||
--hash=sha256:a4646724fba402aa7504cd48b4b50e783296b5e10a524c7a6da62e4a8ac9698d \
|
||||
--hash=sha256:2e55195bc1c6b705bfd8ad6f288b38b11b1af32f3c8289d6c50d47f950c12e76 \
|
||||
--hash=sha256:39b70c19ec771805081578cc936bbe95336798b7edf4732ed102e7a43ec5c07a \
|
||||
--hash=sha256:dbd18bcf4889b720ba13a27ec2f2aac1981bd41203b3a3b27ba7a33f88ae4827 \
|
||||
--hash=sha256:603aa0706be710eea8884af807b1b3bc9fb2e49b9f4da439e76000f3b3c6ff0f \
|
||||
--hash=sha256:cae865b1cae1ec2663d8ea56ef6ff185bad091a5e33ebbadd98de2cfa3fa668f \
|
||||
--hash=sha256:36674959eed6957e61f11c912f71e78857a8d0604171dfd9ce9ad5cbf41c511c \
|
||||
--hash=sha256:06fab248a088e439402141ea04f0fffb203723148f6ee791e9c75b3e9e82f080 \
|
||||
--hash=sha256:6149a185cece5ee78d1d196938b2a8f9d09f5a5ebfbba66969302a778d5ddd1d \
|
||||
--hash=sha256:50a4a0ad0111cc1b71fa32dedd05fa239f7fb5a43a40663269bb5dc7877cfd28 \
|
||||
--hash=sha256:d051ec1c64b85ecc69531e1137bb9751c6830772ee5c1c426dbcfe98ef5788d7 \
|
||||
--hash=sha256:a12ff4c8ddfee61f90a1633a4c4afd3f7bcb32b11c52026c92a12e1325922d0d \
|
||||
--hash=sha256:cf2402002d3d9f91c8b01e66fbb436a4ed01c6498fffed0e4c7566da1d40ee1e \
|
||||
--hash=sha256:1ded4fce9cfaaf24e7a0ab51b7a87be9038ea1ace7f34b841fe3b6894c721d1c \
|
||||
--hash=sha256:012426a41bc9ab63bb158635aecccc7610e3eff5d31d1eb43bc099debc979d94 \
|
||||
--hash=sha256:759e4095edc3c1b3ac031f34d9459fa781777a93ccc633a472a5468587a190ff \
|
||||
--hash=sha256:a9d17f2be3b427fbb2bce61e596cf555d6f8a56c222bd2ca148baeeb5e5c783c \
|
||||
--hash=sha256:99abf4f353c3d1a0c7a5f27699482c987cf663b1eac20db59b8c7b061eabd7fc \
|
||||
--hash=sha256:384ec0463d1c2671170901994aeb6dce126de0a95ccc3976c43b0038a37329c2 \
|
||||
--hash=sha256:811daee36a58dc79cf3d8bdd4a490e4277d0e4b7d103a001a4e73ddb48e7e6aa \
|
||||
--hash=sha256:c843b3f50d1ab7361ca4f0b3639bf691569493a56808a0b0c54a051d260b7dbd \
|
||||
--hash=sha256:d6631f2e867676b13026e2846180e2c13c1e11289d67da08d71cacb2cd93d4aa \
|
||||
--hash=sha256:7fb43004bce0ca31d8f13a6eb5e943fa73371381e53f7074ed21a4cb786c32f8 \
|
||||
--hash=sha256:2ea52bd92ab9f768cc64a4c3ef8f4b2580a17af0a5436f6126b08efbd1838371 \
|
||||
--hash=sha256:400580cbd3cff6ffa6293df2278c75aef2d58d8d93d3c5614cd67981dae68ceb \
|
||||
--hash=sha256:df609c82f18c5b9f6cb97271f03315ff0dbe481a2a02e56aeb1b1a985ce38e60 \
|
||||
--hash=sha256:ab83f24d5c52d60dbc8cd0528759532736b56db58adaa7b5f1f76ad551416a1e \
|
||||
--hash=sha256:0eef32ca3132a48e43f6a0f5a82cb508f22ce5a3d6f67a8329c81c8e226d3f6e \
|
||||
--hash=sha256:a0d53e51a6cb6f0d9082decb7a4cb6dfb33055308c4c44f53103c073f649af73 \
|
||||
--hash=sha256:a76f502430dd98d7546e1ea2250a7360c065a5fdea52b2dffe8ae7180909b6f4
|
||||
odfpy==1.4.1; python_version >= "3.7" and python_full_version < "4.0.0" \
|
||||
--hash=sha256:fc3b8d1bc098eba4a0fda865a76d9d1e577c4ceec771426bcb169a82c5e9dfe0 \
|
||||
--hash=sha256:db766a6e59c5103212f3cc92ec8dd50a0f3a02790233ed0b52148b70d3c438ec
|
||||
openpyxl==3.0.6; python_version >= "3.7" and python_full_version < "4.0.0" \
|
||||
--hash=sha256:1a4b3869c2500b5c713e8e28341cdada49ecfcff1b10cd9006945f5bcefc090d \
|
||||
--hash=sha256:b229112b46e158b910a5d1b270b212c42773d39cab24e8db527f775b82afc041
|
||||
packaging==20.8; 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:24e0da08660a87484d1602c30bb4902d74816b6985b93de36926f5bc95741858 \
|
||||
--hash=sha256:78598185a7008a470d64526a8059de9aaa449238f280fc9eb6b13ba6c4109093
|
||||
pillow==8.1.0; python_version >= "3.7" and python_full_version < "4.0.0" \
|
||||
--hash=sha256:d355502dce85ade85a2511b40b4c61a128902f246504f7de29bbeec1ae27933a \
|
||||
--hash=sha256:93a473b53cc6e0b3ce6bf51b1b95b7b1e7e6084be3a07e40f79b42e83503fbf2 \
|
||||
--hash=sha256:2353834b2c49b95e1313fb34edf18fca4d57446675d05298bb694bca4b194174 \
|
||||
--hash=sha256:1d208e670abfeb41b6143537a681299ef86e92d2a3dac299d3cd6830d5c7bded \
|
||||
--hash=sha256:dd9eef866c70d2cbbea1ae58134eaffda0d4bfea403025f4db6859724b18ab3d \
|
||||
--hash=sha256:b09e10ec453de97f9a23a5aa5e30b334195e8d2ddd1ce76cc32e52ba63c8b31d \
|
||||
--hash=sha256:b02a0b9f332086657852b1f7cb380f6a42403a6d9c42a4c34a561aa4530d5234 \
|
||||
--hash=sha256:ca20739e303254287138234485579b28cb0d524401f83d5129b5ff9d606cb0a8 \
|
||||
--hash=sha256:604815c55fd92e735f9738f65dabf4edc3e79f88541c221d292faec1904a4b17 \
|
||||
--hash=sha256:cf6e33d92b1526190a1de904df21663c46a456758c0424e4f947ae9aa6088bf7 \
|
||||
--hash=sha256:47c0d93ee9c8b181f353dbead6530b26980fe4f5485aa18be8f1fd3c3cbc685e \
|
||||
--hash=sha256:96d4dc103d1a0fa6d47c6c55a47de5f5dafd5ef0114fa10c85a1fd8e0216284b \
|
||||
--hash=sha256:7916cbc94f1c6b1301ac04510d0881b9e9feb20ae34094d3615a8a7c3db0dcc0 \
|
||||
--hash=sha256:3de6b2ee4f78c6b3d89d184ade5d8fa68af0848f9b6b6da2b9ab7943ec46971a \
|
||||
--hash=sha256:cdbbe7dff4a677fb555a54f9bc0450f2a21a93c5ba2b44e09e54fcb72d2bd13d \
|
||||
--hash=sha256:f50e7a98b0453f39000619d845be8b06e611e56ee6e8186f7f60c3b1e2f0feae \
|
||||
--hash=sha256:cb192176b477d49b0a327b2a5a4979552b7a58cd42037034316b8018ac3ebb59 \
|
||||
--hash=sha256:6c5275bd82711cd3dcd0af8ce0bb99113ae8911fc2952805f1d012de7d600a4c \
|
||||
--hash=sha256:165c88bc9d8dba670110c689e3cc5c71dbe4bfb984ffa7cbebf1fac9554071d6 \
|
||||
--hash=sha256:5e2fe3bb2363b862671eba632537cd3a823847db4d98be95690b7e382f3d6378 \
|
||||
--hash=sha256:7612520e5e1a371d77e1d1ca3a3ee6227eef00d0a9cddb4ef7ecb0b7396eddf7 \
|
||||
--hash=sha256:d673c4990acd016229a5c1c4ee8a9e6d8f481b27ade5fc3d95938697fa443ce0 \
|
||||
--hash=sha256:dc577f4cfdda354db3ae37a572428a90ffdbe4e51eda7849bf442fb803f09c9b \
|
||||
--hash=sha256:22d070ca2e60c99929ef274cfced04294d2368193e935c5d6febfd8b601bf865 \
|
||||
--hash=sha256:a3d3e086474ef12ef13d42e5f9b7bbf09d39cf6bd4940f982263d6954b13f6a9 \
|
||||
--hash=sha256:731ca5aabe9085160cf68b2dbef95fc1991015bc0a3a6ea46a371ab88f3d0913 \
|
||||
--hash=sha256:bba80df38cfc17f490ec651c73bb37cd896bc2400cfba27d078c2135223c1206 \
|
||||
--hash=sha256:c3d911614b008e8a576b8e5303e3db29224b455d3d66d1b2848ba6ca83f9ece9 \
|
||||
--hash=sha256:39725acf2d2e9c17356e6835dccebe7a697db55f25a09207e38b835d5e1bc032 \
|
||||
--hash=sha256:81c3fa9a75d9f1afafdb916d5995633f319db09bd773cb56b8e39f1e98d90820 \
|
||||
--hash=sha256:b6f00ad5ebe846cc91763b1d0c6d30a8042e02b2316e27b05de04fa6ec831ec5 \
|
||||
--hash=sha256:887668e792b7edbfb1d3c9d8b5d8c859269a0f0eba4dda562adb95500f60dbba
|
||||
pprintpp==0.4.0; python_version >= "3.7" and python_full_version < "4.0.0" \
|
||||
--hash=sha256:b6b4dcdd0c0c0d75e4d7b2f21a9e933e5b2ce62b26e1a54537f9651ae5a5c01d \
|
||||
--hash=sha256:ea826108e2c7f49dc6d66c752973c3fc9749142a798d6b254e1e301cfdbc6403
|
||||
psycopg2-binary==2.8.6; (python_version >= "2.7" and python_full_version < "3.0.0") or (python_full_version >= "3.4.0") \
|
||||
--hash=sha256:11b9c0ebce097180129e422379b824ae21c8f2a6596b159c7659e2e5a00e1aa0 \
|
||||
--hash=sha256:d14b140a4439d816e3b1229a4a525df917d6ea22a0771a2a78332273fd9528a4 \
|
||||
--hash=sha256:1fabed9ea2acc4efe4671b92c669a213db744d2af8a9fc5d69a8e9bc14b7a9db \
|
||||
--hash=sha256:f5ab93a2cb2d8338b1674be43b442a7f544a0971da062a5da774ed40587f18f5 \
|
||||
--hash=sha256:b4afc542c0ac0db720cf516dd20c0846f71c248d2b3d21013aa0d4ef9c71ca25 \
|
||||
--hash=sha256:e74a55f6bad0e7d3968399deb50f61f4db1926acf4a6d83beaaa7df986f48b1c \
|
||||
--hash=sha256:0deac2af1a587ae12836aa07970f5cb91964f05a7c6cdb69d8425ff4c15d4e2c \
|
||||
--hash=sha256:ad20d2eb875aaa1ea6d0f2916949f5c08a19c74d05b16ce6ebf6d24f2c9f75d1 \
|
||||
--hash=sha256:950bc22bb56ee6ff142a2cb9ee980b571dd0912b0334aa3fe0fe3788d860bea2 \
|
||||
--hash=sha256:b8a3715b3c4e604bcc94c90a825cd7f5635417453b253499664f784fc4da0152 \
|
||||
--hash=sha256:d1b4ab59e02d9008efe10ceabd0b31e79519da6fb67f7d8e8977118832d0f449 \
|
||||
--hash=sha256:ac0c682111fbf404525dfc0f18a8b5f11be52657d4f96e9fcb75daf4f3984859 \
|
||||
--hash=sha256:7d92a09b788cbb1aec325af5fcba9fed7203897bbd9269d5691bb1e3bce29550 \
|
||||
--hash=sha256:aaa4213c862f0ef00022751161df35804127b78adf4a2755b9f991a507e425fd \
|
||||
--hash=sha256:c2507d796fca339c8fb03216364cca68d87e037c1f774977c8fc377627d01c71 \
|
||||
--hash=sha256:ee69dad2c7155756ad114c02db06002f4cded41132cc51378e57aad79cc8e4f4 \
|
||||
--hash=sha256:e82aba2188b9ba309fd8e271702bd0d0fc9148ae3150532bbb474f4590039ffb \
|
||||
--hash=sha256:d5227b229005a696cc67676e24c214740efd90b148de5733419ac9aaba3773da \
|
||||
--hash=sha256:a0eb43a07386c3f1f1ebb4dc7aafb13f67188eab896e7397aa1ee95a9c884eb2 \
|
||||
--hash=sha256:e1f57aa70d3f7cc6947fd88636a481638263ba04a742b4a37dd25c373e41491a \
|
||||
--hash=sha256:833709a5c66ca52f1d21d41865a637223b368c0ee76ea54ca5bad6f2526c7679 \
|
||||
--hash=sha256:ba28584e6bca48c59eecbf7efb1576ca214b47f05194646b081717fa628dfddf \
|
||||
--hash=sha256:6a32f3a4cb2f6e1a0b15215f448e8ce2da192fd4ff35084d80d5e39da683e79b \
|
||||
--hash=sha256:0e4dc3d5996760104746e6cfcdb519d9d2cd27c738296525d5867ea695774e67 \
|
||||
--hash=sha256:cec7e622ebc545dbb4564e483dd20e4e404da17ae07e06f3e780b2dacd5cee66 \
|
||||
--hash=sha256:ba381aec3a5dc29634f20692349d73f2d21f17653bda1decf0b52b11d694541f \
|
||||
--hash=sha256:a0c50db33c32594305b0ef9abc0cb7db13de7621d2cadf8392a1d9b3c437ef77 \
|
||||
--hash=sha256:2dac98e85565d5688e8ab7bdea5446674a83a3945a8f416ad0110018d1501b94 \
|
||||
--hash=sha256:bd1be66dde2b82f80afb9459fc618216753f67109b859a361cf7def5c7968729 \
|
||||
--hash=sha256:8cd0fb36c7412996859cb4606a35969dd01f4ea34d9812a141cd920c3b18be77 \
|
||||
--hash=sha256:89705f45ce07b2dfa806ee84439ec67c5d9a0ef20154e0e475e2b2ed392a5b83 \
|
||||
--hash=sha256:42ec1035841b389e8cc3692277a0bd81cdfe0b65d575a2c8862cec7a80e62e52 \
|
||||
--hash=sha256:7312e931b90fe14f925729cde58022f5d034241918a5c4f9797cac62f6b3a9dd \
|
||||
--hash=sha256:6422f2ff0919fd720195f64ffd8f924c1395d30f9a495f31e2392c2efafb5056 \
|
||||
--hash=sha256:15978a1fbd225583dd8cdaf37e67ccc278b5abecb4caf6b2d6b8e2b948e953f6
|
||||
pyparsing==2.4.7; 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:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b \
|
||||
--hash=sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1
|
||||
python-dateutil==2.8.1; python_version >= "3.7" and python_full_version < "3.0.0" or python_version >= "3.7" and python_full_version < "4.0.0" and python_full_version >= "3.3.0" \
|
||||
--hash=sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c \
|
||||
--hash=sha256:75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a
|
||||
python-stdnum==1.15; python_version >= "3.7" and python_full_version < "4.0.0" \
|
||||
--hash=sha256:ff0e4d2b8731711cf2a73d22964139208088b4bd1a3e3b0c4f78d6e823dd8923 \
|
||||
--hash=sha256:46c05dff2d5ff89b12cc63ab54cca1e4788e4e3087c163a1b7434a53fa926f28
|
||||
pytz==2020.5; python_version >= "3.7" and python_full_version < "4.0.0" \
|
||||
--hash=sha256:16962c5fb8db4a8f63a26646d8886e9d769b6c511543557bc84e9569fb9a9cb4 \
|
||||
--hash=sha256:180befebb1927b16f6b57101720075a984c019ac16b1b7575673bea42c6c3da5
|
||||
pyyaml==5.3.1; python_version >= "3.7" and python_full_version < "4.0.0" \
|
||||
--hash=sha256:74809a57b329d6cc0fdccee6318f44b9b8649961fa73144a98735b0aaf029f1f \
|
||||
--hash=sha256:240097ff019d7c70a4922b6869d8a86407758333f02203e0fc6ff79c5dcede76 \
|
||||
--hash=sha256:4f4b913ca1a7319b33cfb1369e91e50354d6f07a135f3b901aca02aa95940bd2 \
|
||||
--hash=sha256:cc8955cfbfc7a115fa81d85284ee61147059a753344bc51098f3ccd69b0d7e0c \
|
||||
--hash=sha256:7739fc0fa8205b3ee8808aea45e968bc90082c10aef6ea95e855e10abf4a37b2 \
|
||||
--hash=sha256:69f00dca373f240f842b2931fb2c7e14ddbacd1397d57157a9b005a6a9942648 \
|
||||
--hash=sha256:d13155f591e6fcc1ec3b30685d50bf0711574e2c0dfffd7644babf8b5102ca1a \
|
||||
--hash=sha256:73f099454b799e05e5ab51423c7bcf361c58d3206fa7b0d555426b1f4d9a3eaf \
|
||||
--hash=sha256:06a0d7ba600ce0b2d2fe2e78453a470b5a6e000a985dd4a4e54e436cc36b0e97 \
|
||||
--hash=sha256:95f71d2af0ff4227885f7a6605c37fd53d3a106fcab511b8860ecca9fcf400ee \
|
||||
--hash=sha256:b8eac752c5e14d3eca0e6dd9199cd627518cb5ec06add0de9d32baeee6fe645d
|
||||
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
|
||||
requests==2.25.1; python_version >= "3.7" and python_full_version < "3.0.0" or python_version >= "3.7" and python_full_version < "4.0.0" and python_full_version >= "3.5.0" \
|
||||
--hash=sha256:c210084e36a42ae6b9219e00e48287def368a26d03a048ddad7bfee44f75871e \
|
||||
--hash=sha256:27973dd4a904a4f13b263a19c866c13b92a39ed1c964655f025f3f8d3d75b804
|
||||
six==1.15.0; python_version >= "3.7" and python_full_version < "3.0.0" or python_version >= "3.7" and python_full_version < "4.0.0" and python_full_version >= "3.5.0" \
|
||||
--hash=sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced \
|
||||
--hash=sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259
|
||||
sqlparse==0.4.1; python_version >= "3.7" and python_full_version < "4.0.0" \
|
||||
--hash=sha256:017cde379adbd6a1f15a61873f43e8274179378e95ef3fede90b5aa64d304ed0 \
|
||||
--hash=sha256:0f91fd2e829c44362cbcfab3e9ae12e22badaa8a29ad5ff599f9ec109f0454e8
|
||||
svgwrite==1.4.1; python_version >= "3.7" and python_full_version < "4.0.0" \
|
||||
--hash=sha256:4b21652a1d9c543a6bf4f9f2a54146b214519b7540ca60cb99968ad09ef631d0 \
|
||||
--hash=sha256:e220a4bf189e7e214a55e8a11421d152b5b6fb1dd660c86a8b6b61fe8cc2ac48
|
||||
tablib==3.0.0; python_version >= "3.7" and python_full_version < "4.0.0" \
|
||||
--hash=sha256:41aa40981cddd7ec4d1fabeae7c38d271601b306386bd05b5c3bcae13e5aeb20 \
|
||||
--hash=sha256:f83cac08454f225a34a305daa20e2110d5e6335135d505f93bc66583a5f9c10d
|
||||
urllib3==1.26.2; python_version >= "3.7" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version < "4" and python_version >= "3.7" and python_full_version < "4.0.0" \
|
||||
--hash=sha256:d8ff90d979214d7b4f8ce956e80f4028fc6860e4431f731ea4a8c08f23f99473 \
|
||||
--hash=sha256:19188f96923873c92ccb987120ec4acaa12f0461fa9ce5d3d0772bc965a39e08
|
||||
webencodings==0.5.1; python_version >= "3.7" and python_full_version < "3.0.0" or python_version >= "3.7" and python_full_version < "4.0.0" and python_full_version >= "3.5.0" \
|
||||
--hash=sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78 \
|
||||
--hash=sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923
|
||||
xlrd==2.0.1; python_version >= "3.7" and python_full_version < "3.0.0" or python_version >= "3.7" and python_full_version < "4.0.0" and python_full_version >= "3.6.0" \
|
||||
--hash=sha256:6a33ee89877bd9abc1158129f6e94be74e2679636b8a205b43b85206c3f0bbdd \
|
||||
--hash=sha256:f72f148f54442c6b056bf931dbc34f986fd0c3b0b6b5a58d013c9aef274d0c88
|
||||
xlwt==1.3.0; python_version >= "3.7" and python_full_version < "4.0.0" \
|
||||
--hash=sha256:a082260524678ba48a297d922cc385f58278b8aa68741596a87de01a9c628b2e \
|
||||
--hash=sha256:c59912717a9b28f1a3c2a98fd60741014b06b043936dcecbc113eaaada156c88
|
|
@ -11,9 +11,11 @@
|
|||
|
||||
from pathlib import Path as __Path
|
||||
|
||||
from django_ynh.secret_key import get_or_create_secret as __get_or_create_secret
|
||||
from for_runners_project.settings.base import * # noqa
|
||||
|
||||
DEBUG = False
|
||||
|
||||
DEBUG = False # Don't turn DEBUG on in production!
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
|
@ -31,40 +33,41 @@ PATH_URL = PATH_URL.strip('/')
|
|||
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
ROOT_URLCONF = 'ynh_urls' # /opt/yunohost/django-for-runners/ynh_urls.py
|
||||
ROOT_URLCONF = 'urls' # /opt/yunohost/django-for-runners/ynh_urls.py
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Function that will be called to finalize a user profile:
|
||||
YNH_SETUP_USER = 'setup_user.setup_project_user'
|
||||
|
||||
SECRET_KEY = __get_or_create_secret(FINAL_HOME_PATH / 'secret.txt') # /opt/yunohost/$app/secret.txt
|
||||
|
||||
INSTALLED_APPS.append('django_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:
|
||||
'ynh_authenticate.RemoteUserBackend',
|
||||
|
||||
'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
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# https://docs.djangoproject.com/en/2.2/howto/auth-remote-user/
|
||||
# Add RemoteUserMiddleware after AuthenticationMiddleware
|
||||
|
||||
MIDDLEWARE.insert(
|
||||
MIDDLEWARE.index('django.contrib.auth.middleware.AuthenticationMiddleware') + 1,
|
||||
'ynh_authenticate.RemoteUserMiddleware',
|
||||
)
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
|
||||
ADMINS = (
|
||||
('__ADMIN__', '__ADMINMAIL__'),
|
||||
)
|
||||
ADMINS = (('__ADMIN__', '__ADMINMAIL__'),)
|
||||
|
||||
MANAGERS = ADMINS
|
||||
|
||||
|
@ -106,7 +109,7 @@ 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-For-Runners, you might
|
||||
# If redis is running on same host as Django-ForRunners, you might
|
||||
# want to use unix sockets instead:
|
||||
# 'LOCATION': 'unix:///var/run/redis/redis.sock?db=1',
|
||||
'OPTIONS': {
|
||||
|
@ -130,7 +133,6 @@ else:
|
|||
STATIC_ROOT = str(FINAL_WWW_PATH / 'static')
|
||||
MEDIA_ROOT = str(FINAL_WWW_PATH / 'media')
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
LOGGING = {
|
||||
|
@ -161,6 +163,7 @@ LOGGING = {
|
|||
'django': {'handlers': ['log_file', 'mail_admins'], 'level': 'INFO', 'propagate': False},
|
||||
'axes': {'handlers': ['log_file', 'mail_admins'], 'level': 'WARNING', 'propagate': False},
|
||||
'django_tools': {'handlers': ['log_file', 'mail_admins'], 'level': 'INFO', 'propagate': False},
|
||||
'django_ynh': {'handlers': ['log_file', 'mail_admins'], 'level': 'INFO', 'propagate': False},
|
||||
'for_runners': {'handlers': ['log_file', 'mail_admins'], 'level': 'INFO', 'propagate': False},
|
||||
},
|
||||
}
|
8
conf/setup_user.py
Normal file
8
conf/setup_user.py
Normal file
|
@ -0,0 +1,8 @@
|
|||
def setup_project_user(user):
|
||||
"""
|
||||
All users used the Django admin, so we need to set the "staff" user flag.
|
||||
Called from django_ynh.sso_auth
|
||||
"""
|
||||
user.is_staff = True
|
||||
user.save()
|
||||
return user
|
18
conf/urls.py
Normal file
18
conf/urls.py
Normal file
|
@ -0,0 +1,18 @@
|
|||
from django.conf import settings
|
||||
from django.conf.urls import static
|
||||
from django.contrib import admin
|
||||
from django.urls import path
|
||||
|
||||
|
||||
if settings.PATH_URL:
|
||||
# settings.PATH_URL is the $YNH_APP_ARG_PATH
|
||||
# Prefix all urls with "PATH_URL":
|
||||
urlpatterns = [
|
||||
path(f'{settings.PATH_URL}/', admin.site.urls),
|
||||
]
|
||||
if settings.SERVE_FILES:
|
||||
urlpatterns += static.static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
|
||||
else:
|
||||
# Installed to domain root, without a path prefix
|
||||
# Just use the default project urls.py
|
||||
from for_runners_project.urls import urlpatterns # noqa
|
|
@ -2,7 +2,11 @@
|
|||
WSGI config
|
||||
"""
|
||||
import os
|
||||
os.environ['DJANGO_SETTINGS_MODULE'] = 'ynh_for_runners_settings'
|
||||
|
||||
from django.core.wsgi import get_wsgi_application
|
||||
|
||||
os.environ['DJANGO_SETTINGS_MODULE'] = 'settings'
|
||||
|
||||
from django.core.wsgi import get_wsgi_application # noqa
|
||||
|
||||
|
||||
application = get_wsgi_application()
|
|
@ -1,185 +0,0 @@
|
|||
"""
|
||||
* remote user authentication backend
|
||||
* remote user middleware
|
||||
|
||||
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 base64
|
||||
import logging
|
||||
|
||||
from axes.exceptions import AxesBackendPermissionDenied
|
||||
from django.contrib.auth.backends import RemoteUserBackend as OriginRemoteUserBackend
|
||||
from django.contrib.auth.middleware import RemoteUserMiddleware as OriginRemoteUserMiddleware
|
||||
from django.core.exceptions import ValidationError
|
||||
from inventory.permissions import get_or_create_normal_user_group
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def update_user_profile(request):
|
||||
"""
|
||||
Update existing user information:
|
||||
* Email
|
||||
* First / Last name
|
||||
"""
|
||||
user = request.user
|
||||
assert user.is_authenticated
|
||||
|
||||
update_fields = []
|
||||
|
||||
if not user.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)
|
||||
|
||||
|
||||
class RemoteUserMiddleware(OriginRemoteUserMiddleware):
|
||||
"""
|
||||
Middleware to login a user 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
|
||||
|
||||
if not request.user.is_authenticated:
|
||||
# Not logged in -> nothing to verify here
|
||||
return
|
||||
|
||||
# Check SSOwat cookie informations:
|
||||
try:
|
||||
username = request.COOKIES['SSOwAuthUser']
|
||||
except KeyError:
|
||||
logger.error('SSOwAuthUser cookie missing!')
|
||||
|
||||
# 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 AxesBackendPermissionDenied('Cookie missing')
|
||||
|
||||
logger.info('SSOwat username from cookies: %r', username)
|
||||
if username != request.user.username:
|
||||
raise AxesBackendPermissionDenied('Wrong username')
|
||||
|
||||
# Compare with HTTP_AUTH_USER
|
||||
try:
|
||||
username = request.META['HTTP_AUTH_USER']
|
||||
except KeyError:
|
||||
logger.error('HTTP_AUTH_USER missing!')
|
||||
raise AxesBackendPermissionDenied('No HTTP_AUTH_USER')
|
||||
|
||||
if username != request.user.username:
|
||||
raise AxesBackendPermissionDenied('Wrong HTTP_AUTH_USER username')
|
||||
|
||||
# Also check 'HTTP_AUTHORIZATION', but only the username ;)
|
||||
try:
|
||||
auth = request.META['HTTP_AUTHORIZATION']
|
||||
except KeyError:
|
||||
logger.error('HTTP_AUTHORIZATION missing!')
|
||||
raise AxesBackendPermissionDenied('No HTTP_AUTHORIZATION')
|
||||
|
||||
scheme, creds = auth.split(' ', 1)
|
||||
if scheme.lower() != 'basic':
|
||||
logger.error('HTTP_AUTHORIZATION with %r not supported', scheme)
|
||||
raise AxesBackendPermissionDenied('HTTP_AUTHORIZATION scheme not supported')
|
||||
|
||||
creds = str(base64.b64decode(creds), encoding='utf-8')
|
||||
username = creds.split(':', 1)[0]
|
||||
if username != request.user.username:
|
||||
raise AxesBackendPermissionDenied('Wrong HTTP_AUTHORIZATION username')
|
||||
|
||||
if not was_authenticated:
|
||||
# First request, after login -> update user informations
|
||||
logger.info('Remote used was logged in')
|
||||
update_user_profile(request)
|
||||
|
||||
|
||||
class RemoteUserBackend(OriginRemoteUserBackend):
|
||||
"""
|
||||
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 user after creation and return the updated user.
|
||||
Setup a normal, non-superuser
|
||||
"""
|
||||
logger.warning('Configure user %s', user)
|
||||
|
||||
user.set_unusable_password() # Always login via SSO
|
||||
user.is_staff = True
|
||||
user.is_superuser = False
|
||||
user.save()
|
||||
|
||||
pyinventory_user_group = get_or_create_normal_user_group()[0]
|
||||
user.groups.set([pyinventory_user_group])
|
||||
|
||||
update_user_profile(request)
|
||||
|
||||
return user
|
||||
|
||||
def user_can_authenticate(self, user):
|
||||
logger.warning('Remote user login: %s', user)
|
||||
return True
|
|
@ -1,23 +0,0 @@
|
|||
from django.conf import settings
|
||||
from django.contrib import admin
|
||||
from django.urls import path
|
||||
|
||||
# def debug_view(request):
|
||||
# """ debug request.META """
|
||||
# if not request.user.is_authenticated:
|
||||
# from django.shortcuts import redirect
|
||||
# return redirect('admin:index')
|
||||
#
|
||||
# import pprint
|
||||
# meta = pprint.pformat(request.META)
|
||||
# html = f'<html><body>request.META: <pre>{meta}</pre></body></html>'
|
||||
# from django.http import HttpResponse
|
||||
# return HttpResponse(html)
|
||||
|
||||
|
||||
admin.autodiscover()
|
||||
|
||||
urlpatterns = [
|
||||
# path(f'{settings.PATH_URL}/debug/', debug_view),
|
||||
path(f'{settings.PATH_URL}/', admin.site.urls),
|
||||
]
|
149
local_test.py
Executable file → Normal file
149
local_test.py
Executable file → Normal file
|
@ -1,154 +1,29 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
"""
|
||||
Start Django-For-Runners in YunoHost setup locally.
|
||||
Note:
|
||||
You can only run this script, if you are in a activated Django-For-Runners venv!
|
||||
Build a "local_test" YunoHost installation and start the Django dev. server against it.
|
||||
|
||||
Run via:
|
||||
make local-test
|
||||
|
||||
see README for details ;)
|
||||
"""
|
||||
|
||||
import os
|
||||
import shlex
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
os.environ['DJANGO_SETTINGS_MODULE'] = 'ynh_for_runners_settings'
|
||||
|
||||
try:
|
||||
import for_runners_project # noqa
|
||||
from django_ynh.local_test import create_local_test
|
||||
except ImportError as err:
|
||||
raise ImportError(
|
||||
'Couldn\'t import Django-For-Runners. Did you '
|
||||
'forget to activate a virtual environment?'
|
||||
) from err
|
||||
raise ImportError('Did you forget to activate a virtual environment?') from err
|
||||
|
||||
|
||||
BASE_PATH = Path(__file__).parent.absolute()
|
||||
TEST_PATH = BASE_PATH / 'local_test'
|
||||
CONF_PATH = BASE_PATH / 'conf'
|
||||
|
||||
FINAL_HOME_PATH = TEST_PATH / 'opt_yunohost'
|
||||
FINAL_WWW_PATH = TEST_PATH / 'var_www'
|
||||
LOG_FILE = TEST_PATH / 'var_log_django-for-runners.log'
|
||||
|
||||
MANAGE_PY_FILE = CONF_PATH / 'manage.py'
|
||||
CREATE_SUPERUSER_FILE = CONF_PATH / 'create_superuser.py'
|
||||
SETTINGS_FILE = CONF_PATH / 'ynh_for_runners_settings.py'
|
||||
URLS_FILE = CONF_PATH / 'ynh_urls.py'
|
||||
|
||||
REPLACES = {
|
||||
'__FINAL_HOME_PATH__': str(FINAL_HOME_PATH),
|
||||
'__FINAL_WWW_PATH__': str(FINAL_WWW_PATH),
|
||||
'__LOG_FILE__': str(TEST_PATH / 'var_log_django-for-runners.log'),
|
||||
|
||||
'__PATH_URL__': 'app_path',
|
||||
'__DOMAIN__': '127.0.0.1',
|
||||
|
||||
'django.db.backends.postgresql': 'django.db.backends.sqlite3',
|
||||
"'NAME': '__APP__',": f"'NAME': '{TEST_PATH / 'test_db.sqlite'}',",
|
||||
|
||||
'django_redis.cache.RedisCache': 'django.core.cache.backends.dummy.DummyCache',
|
||||
|
||||
'DEBUG = False': 'DEBUG = True',
|
||||
|
||||
# Just use the default logging setup from Django-For-Runners project:
|
||||
'LOGGING = {': 'HACKED_DEACTIVATED_LOGGING = {',
|
||||
}
|
||||
|
||||
|
||||
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(args):
|
||||
verbose_check_call(
|
||||
command=f'{sys.executable} manage.py {args}',
|
||||
cwd=FINAL_HOME_PATH,
|
||||
)
|
||||
|
||||
|
||||
def copy_patch(src_file, replaces=None):
|
||||
dst_file = FINAL_HOME_PATH / src_file.name
|
||||
print(f'{src_file.relative_to(BASE_PATH)} -> {dst_file.relative_to(BASE_PATH)}')
|
||||
|
||||
with src_file.open('r') as f:
|
||||
content = f.read()
|
||||
|
||||
if replaces:
|
||||
for old, new in replaces.items():
|
||||
content = content.replace(old, new)
|
||||
|
||||
with dst_file.open('w') as f:
|
||||
f.write(content)
|
||||
BASE_PATH = Path(__file__).parent
|
||||
|
||||
|
||||
def main():
|
||||
print('-' * 100)
|
||||
|
||||
assert BASE_PATH.is_dir()
|
||||
assert CONF_PATH.is_dir()
|
||||
assert SETTINGS_FILE.is_file()
|
||||
assert URLS_FILE.is_file()
|
||||
|
||||
for p in (TEST_PATH, FINAL_HOME_PATH, FINAL_WWW_PATH):
|
||||
if p.is_dir():
|
||||
print(f'Already exists: "{p.relative_to(BASE_PATH)}", ok.')
|
||||
else:
|
||||
print(f'Create: "{p.relative_to(BASE_PATH)}"')
|
||||
p.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
LOG_FILE.touch(exist_ok=True)
|
||||
|
||||
# conf/manage.py -> local_test/manage.py
|
||||
copy_patch(src_file=MANAGE_PY_FILE)
|
||||
|
||||
# conf/create_superuser.py -> local_test/opt_yunohost/create_superuser.py
|
||||
copy_patch(src_file=CREATE_SUPERUSER_FILE)
|
||||
|
||||
# conf/ynh_for_runners_settings.py -> local_test/ynh_for_runners_settings.py
|
||||
copy_patch(src_file=SETTINGS_FILE, replaces=REPLACES)
|
||||
|
||||
# conf/ynh_urls.py -> local_test/ynh_urls.py
|
||||
copy_patch(src_file=URLS_FILE, replaces=REPLACES)
|
||||
|
||||
with Path(FINAL_HOME_PATH / 'local_settings.py').open('w') as f:
|
||||
f.write('# Only for local test run\n')
|
||||
f.write('SERVE_FILES=True # used in src/for_runners_project/urls.py\n')
|
||||
|
||||
# call "local_test/manage.py" via subprocess:
|
||||
call_manage_py('check --deploy')
|
||||
call_manage_py('migrate --no-input')
|
||||
call_manage_py('collectstatic --no-input')
|
||||
|
||||
verbose_check_call(
|
||||
command=f'{sys.executable} create_superuser.py --username="test" --password="test"',
|
||||
cwd=FINAL_HOME_PATH,
|
||||
create_local_test(
|
||||
django_settings_path=BASE_PATH / 'conf' / 'settings.py',
|
||||
destination=BASE_PATH / 'local_test',
|
||||
runserver=True,
|
||||
)
|
||||
|
||||
try:
|
||||
call_manage_py('runserver --nostatic')
|
||||
except KeyboardInterrupt:
|
||||
print('\nBye ;)')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
{
|
||||
"name": "Django-For-Runners",
|
||||
"name": "Django-ForRunners",
|
||||
"id": "django-for-runners",
|
||||
"packaging_format": 1,
|
||||
"description": {
|
||||
"en": "Store your GPX tracks of your running (or other sports activity)"
|
||||
},
|
||||
"version": "0.12.0rc2~ynh2",
|
||||
"version": "0.12.0rc3~ynh2",
|
||||
"url": "https://github.com/jedie/django-for-runners",
|
||||
"license": "GPL-3.0",
|
||||
"maintainer": {
|
||||
|
@ -26,8 +26,8 @@
|
|||
"name": "domain",
|
||||
"type": "domain",
|
||||
"ask": {
|
||||
"en": "Choose a domain for Django-For-Runners",
|
||||
"fr": "Choisissez un domaine pour Django-For-Runners"
|
||||
"en": "Choose a domain for Django-ForRunners",
|
||||
"fr": "Choisissez un domaine pour Django-ForRunners"
|
||||
},
|
||||
"example": "domain.org"
|
||||
},
|
||||
|
@ -35,8 +35,8 @@
|
|||
"name": "path",
|
||||
"type": "path",
|
||||
"ask": {
|
||||
"en": "Choose a path for Django-For-Runners",
|
||||
"fr": "Choisissez un chemin pour Django-For-Runners"
|
||||
"en": "Choose a path for Django-ForRunners",
|
||||
"fr": "Choisissez un chemin pour Django-ForRunners"
|
||||
},
|
||||
"example": "/django-for-runners",
|
||||
"default": "/django-for-runners"
|
||||
|
@ -45,8 +45,8 @@
|
|||
"name": "admin",
|
||||
"type": "user",
|
||||
"ask": {
|
||||
"en": "Choose an admin user for Django-For-Runners",
|
||||
"fr": "Choisissez l'administrateur pour Django-For-Runners"
|
||||
"en": "Choose an admin user for Django-ForRunners",
|
||||
"fr": "Choisissez l'administrateur pour Django-ForRunners"
|
||||
},
|
||||
"example": "johndoe"
|
||||
},
|
||||
|
@ -54,8 +54,8 @@
|
|||
"name": "is_public",
|
||||
"type": "boolean",
|
||||
"ask": {
|
||||
"en": "Should Django-For-Runners be public accessible?",
|
||||
"fr": "Django-For-Runners doit-il être accessible au public ?"
|
||||
"en": "Should Django-ForRunners be public accessible?",
|
||||
"fr": "Django-ForRunners doit-il être accessible au public ?"
|
||||
},
|
||||
"help": {
|
||||
"en": "Any YunoHost user and anonymous people from the web will be able to access the application",
|
||||
|
|
1680
poetry.lock
generated
Normal file
1680
poetry.lock
generated
Normal file
File diff suppressed because it is too large
Load diff
|
@ -1,16 +1,92 @@
|
|||
[tool.poetry]
|
||||
name = "django-for-runners_ynh"
|
||||
version = "0.12.0rc2~ynh2"
|
||||
version = "0.12.0rc3~ynh2"
|
||||
description = "Test django-for-runners_ynh via local_test.py"
|
||||
authors = ["JensDiemer <git@jensdiemer.de>"]
|
||||
license = "GPL"
|
||||
|
||||
[tool.poetry.dependencies]
|
||||
python = ">=3.7,<4.0.0"
|
||||
django-for-runners = "*"
|
||||
django-for-runners = "==0.12.0rc3"
|
||||
django_ynh = "*"
|
||||
gunicorn = "*"
|
||||
psycopg2-binary = "*"
|
||||
django-redis = "*"
|
||||
#django-axes = "*" # https://github.com/jazzband/django-axes
|
||||
|
||||
|
||||
[tool.poetry.dev-dependencies]
|
||||
bx_py_utils = "*"
|
||||
tox = "*"
|
||||
pytest = "*"
|
||||
pytest-cov = "*"
|
||||
pytest-django = "*"
|
||||
coveralls = "*"
|
||||
isort = "*"
|
||||
flake8 = "*"
|
||||
flynt = "*"
|
||||
black = "*"
|
||||
pyupgrade = "*"
|
||||
|
||||
[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/*"]
|
||||
multi_line_output=3
|
||||
include_trailing_comma=true
|
||||
known_first_party=["inventory"]
|
||||
no_lines_before="LOCALFOLDER"
|
||||
default_section="THIRDPARTY"
|
||||
sections=["FUTURE","STDLIB","THIRDPARTY","FIRSTPARTY","LOCALFOLDER"]
|
||||
lines_after_imports=2
|
||||
|
||||
|
||||
[tool.pytest.ini_options]
|
||||
# https://docs.pytest.org/en/latest/customize.html#pyproject-toml
|
||||
minversion = "6.0"
|
||||
norecursedirs = ".* .git __pycache__ conf coverage* dist htmlcov"
|
||||
# sometimes helpfull "addopts" arguments:
|
||||
# -vv
|
||||
# --verbose
|
||||
# --capture=no
|
||||
# --trace-config
|
||||
# --full-trace
|
||||
# -p no:warnings
|
||||
addopts = """
|
||||
--import-mode=importlib
|
||||
--reuse-db
|
||||
--nomigrations
|
||||
--cov=.
|
||||
--cov-report term-missing
|
||||
--cov-report html
|
||||
--cov-report xml
|
||||
--no-cov-on-fail
|
||||
--showlocals
|
||||
--doctest-modules
|
||||
--failed-first
|
||||
--last-failed-no-failures all
|
||||
--new-first
|
||||
"""
|
||||
|
||||
|
||||
[tool.tox]
|
||||
# https://tox.readthedocs.io/en/latest/example/basic.html#pyproject-toml-tox-legacy-ini
|
||||
legacy_tox_ini = """
|
||||
[tox]
|
||||
isolated_build = True
|
||||
envlist = py39,py38,py37
|
||||
skip_missing_interpreters = True
|
||||
|
||||
[testenv]
|
||||
passenv = *
|
||||
whitelist_externals = make
|
||||
commands =
|
||||
make pytest
|
||||
"""
|
||||
|
|
25
run_pytest.py
Normal file
25
run_pytest.py
Normal file
|
@ -0,0 +1,25 @@
|
|||
"""
|
||||
Run pytest against local test creation
|
||||
"""
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
try:
|
||||
from django_ynh.pytest_helper import run_pytest
|
||||
except ImportError as err:
|
||||
raise ImportError('Did you forget to activate a virtual environment?') from err
|
||||
|
||||
|
||||
BASE_PATH = Path(__file__).parent
|
||||
|
||||
|
||||
def main():
|
||||
run_pytest(
|
||||
django_settings_path=BASE_PATH / 'conf' / 'settings.py',
|
||||
destination=BASE_PATH / 'local_test',
|
||||
)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
|
@ -25,14 +25,7 @@ log_file="${log_path}/django-for-runners.log"
|
|||
#=================================================
|
||||
|
||||
# dependencies used by the app
|
||||
pkg_dependencies="build-essential python3-dev python3-pip python3-venv git \
|
||||
postgresql postgresql-contrib"
|
||||
|
||||
# Django-For-Runners's version for PIP and settings file
|
||||
for_runners_version="0.12.0rc2"
|
||||
|
||||
# Extra python packages:
|
||||
pypi_extras="django-redis"
|
||||
pkg_dependencies="build-essential python3-dev python3-pip python3-venv git postgresql postgresql-contrib"
|
||||
|
||||
#=================================================
|
||||
# Redis HELPERS
|
||||
|
|
|
@ -113,13 +113,13 @@ fi
|
|||
#=================================================
|
||||
# MODIFY SETTINGS
|
||||
#=================================================
|
||||
ynh_script_progression --message="Modify Django-For-Runners's config file..."
|
||||
ynh_script_progression --message="Modify project config file..."
|
||||
|
||||
# save old settings file
|
||||
settings="$final_path/ynh_for_runners_settings.py"
|
||||
settings="$final_path/settings.py"
|
||||
ynh_backup_if_checksum_is_different --file="$settings"
|
||||
|
||||
cp "../conf/ynh_for_runners_settings.py" "$settings"
|
||||
cp "../conf/settings.py" "$settings"
|
||||
|
||||
ynh_replace_string --match_string="__APP__" --replace_string="$app" --target_file="$settings"
|
||||
ynh_replace_string --match_string="__DB_PWD__" --replace_string="$db_pwd" --target_file="$settings"
|
||||
|
|
|
@ -101,9 +101,10 @@ ynh_system_user_create --username="$app" --home_dir="$final_path" --use_shell
|
|||
#=================================================
|
||||
# PIP INSTALLATION
|
||||
#=================================================
|
||||
ynh_script_progression --message="Install Django-For-Runners using PIP..." --weight=80
|
||||
ynh_script_progression --message="Install project via pip..." --weight=80
|
||||
|
||||
python3 -m venv "${final_path}/venv"
|
||||
cp ../conf/requirements.txt "$final_path/requirements.txt"
|
||||
chown -R "$app" "$final_path"
|
||||
|
||||
#run source in a 'sub shell'
|
||||
|
@ -112,18 +113,13 @@ chown -R "$app" "$final_path"
|
|||
source "${final_path}/venv/bin/activate"
|
||||
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 --upgrade setuptools wheel psycopg2-binary
|
||||
ynh_exec_as $app $final_path/venv/bin/pip install --upgrade django-for-runners=="$for_runners_version"
|
||||
ynh_exec_as $app $final_path/venv/bin/pip install --upgrade ${pypi_extras}
|
||||
ynh_exec_as $app $final_path/venv/bin/pip install -r "$final_path/requirements.txt"
|
||||
)
|
||||
|
||||
#=================================================
|
||||
# copy config files
|
||||
# ================================================
|
||||
ynh_script_progression --message="Create django-for-runners configuration file..."
|
||||
|
||||
cp ../conf/create_superuser.py "$final_path/create_superuser.py"
|
||||
chmod +x "$final_path/create_superuser.py"
|
||||
ynh_script_progression --message="Create project configuration files..."
|
||||
|
||||
gunicorn_conf="$final_path/gunicorn.conf.py"
|
||||
cp "../conf/gunicorn.conf.py" "$gunicorn_conf"
|
||||
|
@ -135,10 +131,8 @@ ynh_store_file_checksum --file="$gunicorn_conf"
|
|||
cp ../conf/manage.py "$final_path/manage.py"
|
||||
chmod +x "$final_path/manage.py"
|
||||
|
||||
cp ../conf/wsgi.py "$final_path/wsgi.py"
|
||||
|
||||
settings="$final_path/ynh_for_runners_settings.py"
|
||||
cp "../conf/ynh_for_runners_settings.py" "$settings"
|
||||
settings="$final_path/settings.py"
|
||||
cp "../conf/settings.py" "$settings"
|
||||
|
||||
ynh_replace_string --match_string="__APP__" --replace_string="$app" --target_file="$settings"
|
||||
ynh_replace_string --match_string="__DB_PWD__" --replace_string="$db_pwd" --target_file="$settings"
|
||||
|
@ -157,10 +151,11 @@ ynh_store_file_checksum --file="$settings"
|
|||
|
||||
ynh_app_setting_set --app="$app" --key=redis_db --value="$redis_db"
|
||||
|
||||
touch "$final_path/local_settings.py"
|
||||
cp ../conf/setup_user.py "$final_path/setup_user.py"
|
||||
cp ../conf/urls.py "$final_path/urls.py"
|
||||
cp ../conf/wsgi.py "$final_path/wsgi.py"
|
||||
|
||||
cp "../conf/ynh_authenticate.py" "$final_path/ynh_authenticate.py"
|
||||
cp "../conf/ynh_urls.py" "$final_path/ynh_urls.py"
|
||||
touch "$final_path/local_settings.py"
|
||||
|
||||
#=================================================
|
||||
# MIGRATE / COLLECTSTATIC / CREATEADMIN
|
||||
|
@ -178,7 +173,9 @@ ynh_script_progression --message="migrate/collectstatic/createadmin..." --weight
|
|||
|
||||
./manage.py migrate --no-input
|
||||
./manage.py collectstatic --no-input
|
||||
./create_superuser.py --username="$admin" --email="$admin_mail" --password="django-for-runners"
|
||||
|
||||
# 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.
|
||||
|
@ -233,9 +230,9 @@ then
|
|||
fi
|
||||
|
||||
#=================================================
|
||||
# Start django-for-runners via systemd
|
||||
# Start pyinventory via systemd
|
||||
#=================================================
|
||||
ynh_script_progression --message="Starting Django-For-Runners's services..." --weight=5
|
||||
ynh_script_progression --message="Starting project services..." --weight=5
|
||||
|
||||
ynh_systemd_action --service_name="$app" --action="start"
|
||||
|
||||
|
|
|
@ -83,33 +83,27 @@ ynh_script_progression --message="Configuring a systemd service..."
|
|||
ynh_add_systemd_config --service="$app" --template="for_runners.service"
|
||||
|
||||
#=================================================
|
||||
# UPGRADE PYINVENTORY
|
||||
# UPGRADE VENV
|
||||
#=================================================
|
||||
|
||||
ynh_script_progression --message="Install django-for-runners using PIP..." --weight=15
|
||||
ynh_script_progression --message="Upgrade project via pip..." --weight=80
|
||||
|
||||
python3 -m venv "${final_path}/venv"
|
||||
cp ../conf/requirements.txt "$final_path/requirements.txt"
|
||||
chown -R "$app" "$final_path"
|
||||
|
||||
#run source in a 'sub shell'
|
||||
(
|
||||
set +o nounset
|
||||
source "${final_path}/venv/bin/activate"
|
||||
set -o nounset
|
||||
ynh_exec_as $app $final_path/venv/bin/pip install --upgrade pip
|
||||
ynh_exec_as $app $final_path/venv/bin/pip install --upgrade setuptools wheel psycopg2-binary
|
||||
ynh_exec_as $app $final_path/venv/bin/pip install --upgrade django-for-runners=="$for_runners_version"
|
||||
ynh_exec_as $app $final_path/venv/bin/pip install --upgrade ${pypi_extras}
|
||||
set +o nounset
|
||||
source "${final_path}/venv/bin/activate"
|
||||
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"
|
||||
)
|
||||
|
||||
#=================================================
|
||||
# copy config files
|
||||
# ================================================
|
||||
ynh_script_progression --message="Create django-for-runners configuration file..."
|
||||
|
||||
ynh_backup_if_checksum_is_different --file="$final_path/create_superuser.py"
|
||||
cp ../conf/create_superuser.py "$final_path/create_superuser.py"
|
||||
chmod +x "$final_path/create_superuser.py"
|
||||
ynh_script_progression --message="Create project configuration files..."
|
||||
|
||||
gunicorn_conf="$final_path/gunicorn.conf.py"
|
||||
ynh_backup_if_checksum_is_different --file="$gunicorn_conf"
|
||||
|
@ -123,14 +117,11 @@ ynh_backup_if_checksum_is_different --file="$final_path/manage.py"
|
|||
cp ../conf/manage.py "$final_path/manage.py"
|
||||
chmod +x "$final_path/manage.py"
|
||||
|
||||
ynh_backup_if_checksum_is_different --file="$final_path/wsgi.py"
|
||||
cp ../conf/wsgi.py "$final_path/wsgi.py"
|
||||
|
||||
# save old settings file
|
||||
settings="$final_path/ynh_for_runners_settings.py"
|
||||
settings="$final_path/settings.py"
|
||||
ynh_backup_if_checksum_is_different --file="$settings"
|
||||
|
||||
cp "../conf/ynh_for_runners_settings.py" "$settings"
|
||||
cp "../conf/settings.py" "$settings"
|
||||
|
||||
ynh_replace_string --match_string="__APP__" --replace_string="$app" --target_file="$settings"
|
||||
ynh_replace_string --match_string="__DB_PWD__" --replace_string="$db_pwd" --target_file="$settings"
|
||||
|
@ -147,10 +138,16 @@ ynh_replace_string --match_string="__PATH_URL__" --replace_string="$path_url" --
|
|||
# Recalculate and store the config file checksum into the app settings
|
||||
ynh_store_file_checksum --file="$settings"
|
||||
|
||||
touch "$final_path/local_settings.py"
|
||||
ynh_backup_if_checksum_is_different --file="$final_path/setup_user.py"
|
||||
cp ../conf/setup_user.py "$final_path/setup_user.py"
|
||||
|
||||
cp "../conf/ynh_authenticate.py" "$final_path/ynh_authenticate.py"
|
||||
cp "../conf/ynh_urls.py" "$final_path/ynh_urls.py"
|
||||
ynh_backup_if_checksum_is_different --file="$final_path/urls.py"
|
||||
cp ../conf/urls.py "$final_path/urls.py"
|
||||
|
||||
ynh_backup_if_checksum_is_different --file="$final_path/wsgi.py"
|
||||
cp ../conf/wsgi.py "$final_path/wsgi.py"
|
||||
|
||||
touch "$final_path/local_settings.py"
|
||||
|
||||
#=================================================
|
||||
# MIGRATE PYINVENTORY
|
||||
|
@ -168,7 +165,9 @@ ynh_script_progression --message="migrate/collectstatic/createadmin..." --weight
|
|||
|
||||
./manage.py migrate --no-input
|
||||
./manage.py collectstatic --no-input
|
||||
./create_superuser.py --username="$admin" --email="$admin_mail" --password="django-for-runners"
|
||||
|
||||
# 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.
|
||||
|
@ -202,9 +201,9 @@ chown -R "$app" "$public_path"
|
|||
chown -R "$app" "$final_path"
|
||||
|
||||
#=================================================
|
||||
# Start django-for-runners via systemd
|
||||
# Start pyinventory via systemd
|
||||
#=================================================
|
||||
ynh_script_progression --message="Starting Django-For-Runners's services..." --weight=5
|
||||
ynh_script_progression --message="Starting project services..." --weight=5
|
||||
|
||||
ynh_systemd_action --service_name="$app" --action="start"
|
||||
|
||||
|
|
0
tests/__init__.py
Normal file
0
tests/__init__.py
Normal file
145
tests/test_django_project.py
Normal file
145
tests/test_django_project.py
Normal file
|
@ -0,0 +1,145 @@
|
|||
import for_runners
|
||||
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
|
||||
|
||||
|
||||
@override_settings(DEBUG=False)
|
||||
class DjangoYnhTestCase(HtmlAssertionMixin, TestCase):
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
|
||||
# Always start a fresh session:
|
||||
self.client = self.client_class()
|
||||
|
||||
def test_settings(self):
|
||||
assert settings.PATH_URL == 'app_path'
|
||||
|
||||
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-for-runners_ynh.log')
|
||||
|
||||
assert settings.ROOT_URLCONF == 'urls'
|
||||
|
||||
def test_urls(self):
|
||||
assert reverse('admin:index') == '/app_path/'
|
||||
|
||||
# The django_ynh debug view should not be avaiable:
|
||||
with self.assertRaises(NoReverseMatch):
|
||||
reverse(request_media_debug_view)
|
||||
|
||||
# TODO: https://github.com/jedie/django-for-runners/issues/25
|
||||
# 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/')
|
||||
self.assertRedirects(response, expected_url='/app_path/login/?next=/app_path/')
|
||||
|
||||
def test_create_unknown_user(self):
|
||||
assert User.objects.count() == 0
|
||||
|
||||
self.client.cookies['SSOwAuthUser'] = 'test'
|
||||
|
||||
response = self.client.get(
|
||||
path='/app_path/',
|
||||
HTTP_REMOTE_USER='test',
|
||||
HTTP_AUTH_USER='test',
|
||||
HTTP_AUTHORIZATION='basic dGVzdDp0ZXN0MTIz',
|
||||
)
|
||||
|
||||
assert User.objects.count() == 1
|
||||
user = User.objects.first()
|
||||
assert user.username == 'test'
|
||||
assert user.is_active is True
|
||||
assert user.is_staff is True # Set by: conf.django_ynh_demo_urls.setup_user_handler
|
||||
assert user.is_superuser is False
|
||||
|
||||
self.assert_html_parts(
|
||||
response,
|
||||
parts=(
|
||||
f'<title>Site administration | Django-ForRunners v{for_runners.__version__}</title>',
|
||||
'<strong>test</strong>',
|
||||
),
|
||||
)
|
||||
|
||||
def test_wrong_auth_user(self):
|
||||
assert User.objects.count() == 0
|
||||
assert AccessLog.objects.count() == 0
|
||||
|
||||
self.client.cookies['SSOwAuthUser'] = 'test'
|
||||
|
||||
response = self.client.get(
|
||||
path='/app_path/',
|
||||
HTTP_REMOTE_USER='test',
|
||||
HTTP_AUTH_USER='foobar', # <<< wrong user name
|
||||
HTTP_AUTHORIZATION='basic dGVzdDp0ZXN0MTIz',
|
||||
)
|
||||
|
||||
assert User.objects.count() == 1
|
||||
user = User.objects.first()
|
||||
assert user.username == 'test'
|
||||
assert user.is_active is True
|
||||
assert user.is_staff is True # Set by: conf.django_ynh_demo_urls.setup_user_handler
|
||||
assert user.is_superuser is False
|
||||
|
||||
assert AccessLog.objects.count() == 1
|
||||
|
||||
assert response.status_code == 403 # Forbidden
|
||||
|
||||
def test_wrong_cookie(self):
|
||||
assert User.objects.count() == 0
|
||||
assert AccessLog.objects.count() == 0
|
||||
|
||||
self.client.cookies['SSOwAuthUser'] = 'foobar' # <<< wrong user name
|
||||
|
||||
response = self.client.get(
|
||||
path='/app_path/',
|
||||
HTTP_REMOTE_USER='test',
|
||||
HTTP_AUTH_USER='test',
|
||||
HTTP_AUTHORIZATION='basic dGVzdDp0ZXN0MTIz',
|
||||
)
|
||||
|
||||
assert User.objects.count() == 1
|
||||
user = User.objects.first()
|
||||
assert user.username == 'test'
|
||||
assert user.is_active is True
|
||||
assert user.is_staff is True # Set by: conf.django_ynh_demo_urls.setup_user_handler
|
||||
assert user.is_superuser is False
|
||||
|
||||
assert AccessLog.objects.count() == 1
|
||||
|
||||
assert response.status_code == 403 # Forbidden
|
||||
|
||||
def test_wrong_authorization_user(self):
|
||||
assert User.objects.count() == 0
|
||||
|
||||
self.client.cookies['SSOwAuthUser'] = 'test'
|
||||
|
||||
response = self.client.get(
|
||||
path='/app_path/',
|
||||
HTTP_REMOTE_USER='test',
|
||||
HTTP_AUTH_USER='test',
|
||||
HTTP_AUTHORIZATION=generate_basic_auth(username='foobar', password='test123'), # <<< wrong user name
|
||||
)
|
||||
|
||||
assert User.objects.count() == 1
|
||||
user = User.objects.first()
|
||||
assert user.username == 'test'
|
||||
assert user.is_active is True
|
||||
assert user.is_staff is True # Set by: conf.django_ynh_demo_urls.setup_user_handler
|
||||
assert user.is_superuser is False
|
||||
|
||||
assert AccessLog.objects.count() == 1
|
||||
|
||||
assert response.status_code == 403 # Forbidden
|
13
tests/test_lint.py
Normal file
13
tests/test_lint.py
Normal file
|
@ -0,0 +1,13 @@
|
|||
import shutil
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
BASE_PATH = Path(__file__).parent.parent
|
||||
|
||||
|
||||
def test_lint():
|
||||
assert Path(BASE_PATH, 'Makefile').is_file()
|
||||
make_bin = shutil.which('make')
|
||||
assert make_bin is not None
|
||||
subprocess.check_call([make_bin, 'lint'], cwd=BASE_PATH)
|
41
tests/test_project_setup.py
Normal file
41
tests/test_project_setup.py
Normal file
|
@ -0,0 +1,41 @@
|
|||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
import for_runners
|
||||
|
||||
|
||||
PACKAGE_ROOT = Path(__file__).parent.parent
|
||||
|
||||
|
||||
def assert_file_contains_string(file_path, string):
|
||||
with file_path.open('r') as f:
|
||||
for line in f:
|
||||
if string in line:
|
||||
return
|
||||
raise AssertionError(f'File {file_path} does not contain {string!r} !')
|
||||
|
||||
|
||||
def test_version():
|
||||
version = for_runners.__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-for-runners = "=={version}"'
|
||||
)
|
||||
assert_file_contains_string(file_path=Path(PACKAGE_ROOT, 'manifest.json'), string=f'"version": "{version}~ynh')
|
||||
|
||||
|
||||
def test_poetry_check():
|
||||
poerty_bin = shutil.which('poetry')
|
||||
|
||||
output = subprocess.check_output(
|
||||
[poerty_bin, 'check'],
|
||||
universal_newlines=True,
|
||||
env=os.environ,
|
||||
stderr=subprocess.STDOUT,
|
||||
cwd=str(PACKAGE_ROOT),
|
||||
)
|
||||
print(output)
|
||||
assert output == 'All set!\n'
|
8
tests/test_utils.py
Normal file
8
tests/test_utils.py
Normal file
|
@ -0,0 +1,8 @@
|
|||
from unittest.case import TestCase
|
||||
|
||||
from django_ynh.test_utils import generate_basic_auth
|
||||
|
||||
|
||||
class TestUtilsTestCase(TestCase):
|
||||
def test_generate_basic_auth(self):
|
||||
assert generate_basic_auth(username='test', password='test123') == 'basic dGVzdDp0ZXN0MTIz'
|
Loading…
Reference in a new issue