mirror of
https://github.com/YunoHost-Apps/django_example_ynh.git
synced 2024-09-03 18:26:21 +02:00
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
This commit is contained in:
parent
eb3fe62cac
commit
a8164a2905
39 changed files with 1214 additions and 906 deletions
4
.flake8
4
.flake8
|
@ -2,6 +2,6 @@
|
||||||
# Move to pyproject.toml after: https://gitlab.com/pycqa/flake8/-/issues/428
|
# Move to pyproject.toml after: https://gitlab.com/pycqa/flake8/-/issues/428
|
||||||
#
|
#
|
||||||
[flake8]
|
[flake8]
|
||||||
exclude = .pytest_cache, .tox, dist, htmlcov, */migrations/*
|
exclude = .pytest_cache, .tox, dist, htmlcov, local_test
|
||||||
#ignore = E402
|
ignore = F405
|
||||||
max-line-length = 119
|
max-line-length = 119
|
||||||
|
|
7
.gitignore
vendored
7
.gitignore
vendored
|
@ -3,11 +3,8 @@
|
||||||
!.editorconfig
|
!.editorconfig
|
||||||
!.flake8
|
!.flake8
|
||||||
!.gitignore
|
!.gitignore
|
||||||
coverage.xml
|
|
||||||
__pycache__
|
__pycache__
|
||||||
secret.txt
|
secret.txt
|
||||||
/htmlcov/
|
|
||||||
/local_test/
|
/local_test/
|
||||||
/dist/
|
/coverage.xml
|
||||||
/poetry.lock
|
/htmlcov/
|
||||||
*.log
|
|
||||||
|
|
5
Makefile
5
Makefile
|
@ -31,7 +31,7 @@ update: install-poetry ## update the sources and installation and generate "con
|
||||||
lint: ## Run code formatters and linter
|
lint: ## Run code formatters and linter
|
||||||
poetry run flynt --fail-on-change --line_length=${MAX_LINE_LENGTH} .
|
poetry run flynt --fail-on-change --line_length=${MAX_LINE_LENGTH} .
|
||||||
poetry run isort --check-only .
|
poetry run isort --check-only .
|
||||||
poetry run flake8 django_ynh
|
poetry run flake8 .
|
||||||
|
|
||||||
fix-code-style: ## Fix code formatting
|
fix-code-style: ## Fix code formatting
|
||||||
poetry run flynt --line_length=${MAX_LINE_LENGTH} .
|
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
|
pytest: install ## Run pytest
|
||||||
poetry run python3 ./run_pytest.py
|
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
|
local-test: install ## Run local_test.py to run the project locally
|
||||||
poetry run python3 ./local_test.py
|
poetry run python3 ./local_test.py
|
||||||
|
|
||||||
|
|
31
README.md
31
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.
|
[![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)
|
||||||
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)
|
|
||||||
|
|
||||||
|
|
||||||
Pull requests welcome ;)
|
Pull requests welcome ;)
|
||||||
|
@ -92,9 +85,9 @@ and urls made for YunoHost installation.
|
||||||
|
|
||||||
e.g.:
|
e.g.:
|
||||||
```bash
|
```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/
|
~$ cd django_ynh/
|
||||||
~/django_ynh$ make
|
~/django_example_ynh$ make
|
||||||
install-poetry install or update poetry
|
install-poetry install or update poetry
|
||||||
install install project via poetry
|
install install project via poetry
|
||||||
update update the sources and installation and generate "conf/requirements.txt"
|
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-test Run local_test.py to run the project locally
|
||||||
local-diff-settings Run "manage.py diffsettings" with local test
|
local-diff-settings Run "manage.py diffsettings" with local test
|
||||||
|
|
||||||
~/django_ynh$ make install-poetry
|
~/django_example_ynh$ make install-poetry
|
||||||
~/django_ynh$ make install
|
~/django_example_ynh$ make install
|
||||||
~/django_ynh$ make local-test
|
~/django_example_ynh$ make local-test
|
||||||
```
|
```
|
||||||
|
|
||||||
Notes:
|
Notes:
|
||||||
|
@ -123,6 +116,10 @@ Notes:
|
||||||
|
|
||||||
* [compare v0.1.5...master](https://github.com/YunoHost-Apps/django_ynh/compare/v0.1.5...master) **dev**
|
* [compare v0.1.5...master](https://github.com/YunoHost-Apps/django_ynh/compare/v0.1.5...master) **dev**
|
||||||
* tbc
|
* 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)
|
* [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
|
* 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)
|
* [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`:
|
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
|
* https://github.com/YunoHost-Apps/django-for-runners_ynh
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
[Unit]
|
[Unit]
|
||||||
Description=django_ynh DEMO application server
|
Description=PyInventory application server
|
||||||
After=redis.service postgresql.service
|
After=redis.service postgresql.service
|
||||||
|
|
||||||
[Service]
|
[Service]
|
|
@ -5,9 +5,19 @@ location __PATH__/static/ {
|
||||||
expires 30d;
|
expires 30d;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# TODO: django-sendfile2:
|
||||||
|
#location __PATH__/media/ {
|
||||||
|
# # DATA_DIR/media/
|
||||||
|
# alias __PUBLIC_PATH__/media/;
|
||||||
|
# expires 30d;
|
||||||
|
#}
|
||||||
|
|
||||||
location __PATH__/ {
|
location __PATH__/ {
|
||||||
# https://github.com/benoitc/gunicorn/blob/master/examples/nginx.conf
|
# 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_http_version 1.1;
|
||||||
proxy_set_header Host $host;
|
proxy_set_header Host $host;
|
||||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
|
|
@ -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:5ee950735509d04eb673bd7f7120f8fa1c9e2df495394992c73234d526907e17 \
|
||||||
--hash=sha256:7162a3cb30ab0609f1a4c95938fd73e8604f63bdba516a7f7d64b83ff09478f0
|
--hash=sha256:7162a3cb30ab0609f1a4c95938fd73e8604f63bdba516a7f7d64b83ff09478f0
|
||||||
django-axes==5.10.0; 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:8a62cd4cc78ef08007e8102def34be83832995eb6e3e0c814d605741b82a2796 \
|
--hash=sha256:8f7870dc18ace6155127073bffe7276719c71c5ad4e67b45abedc0207f64a2f6 \
|
||||||
--hash=sha256:3c81ddca8a9d7fd0019cb440f711cc873c3039546f7eacb3f2ec616bf0ec1b32
|
--hash=sha256:aef814f738742b01cc7730cd2bebe3e5b462b807fd6c2ed609b62437ccc71f80
|
||||||
django-ipware==3.0.2; python_version >= "3.6" and python_version < "4.0" \
|
django-ipware==3.0.2; python_version >= "3.7" and python_version < "4.0" and python_full_version < "4.0.0" \
|
||||||
--hash=sha256:c7df8e1410a8e5d6b1fbae58728402ea59950f043c3582e033e866f0f0cf5e94
|
--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:306589c7021e6468b2656edc89f62b8ba67e8d5a1c8877e2688042263daa7a63 \
|
||||||
--hash=sha256:1133b26b75baa3664164c3f44b9d5d133d1b8de45d94d79f38d1adc5b1d502e5
|
--hash=sha256:1133b26b75baa3664164c3f44b9d5d133d1b8de45d94d79f38d1adc5b1d502e5
|
||||||
django==3.1.4; python_version >= "3.6" \
|
django-yunohost-integration==0.2.0a0; python_version >= "3.7" and python_full_version < "4.0.0" \
|
||||||
--hash=sha256:5c866205f15e7a7123f1eec6ab939d22d5bde1416635cab259684af66d8e48a2 \
|
--hash=sha256:a45197a3c4595a496674e7e48a58f58351b6022526893264831e0d6ba463a44f \
|
||||||
--hash=sha256:edb10b5c45e7e9c0fb1dc00b76ec7449aca258a39ffd613dbd078c51d19c9f03
|
--hash=sha256:9acdf320537ccce47ceb1d51d2a00fafbf30938152d3b22c59a3a2ead7952227
|
||||||
gunicorn==20.0.4; python_version >= "3.4" \
|
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:cd4a810dd51bf497552cf3f863b575dabd73d6ad6a91075b65936b151cbf4f9c \
|
||||||
--hash=sha256:1904bb2b8a43658807108d59c3f3d56c2b6121a701161de0ddf9ad140073c626
|
--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:11b9c0ebce097180129e422379b824ae21c8f2a6596b159c7659e2e5a00e1aa0 \
|
||||||
--hash=sha256:d14b140a4439d816e3b1229a4a525df917d6ea22a0771a2a78332273fd9528a4 \
|
--hash=sha256:d14b140a4439d816e3b1229a4a525df917d6ea22a0771a2a78332273fd9528a4 \
|
||||||
--hash=sha256:1fabed9ea2acc4efe4671b92c669a213db744d2af8a9fc5d69a8e9bc14b7a9db \
|
--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:7312e931b90fe14f925729cde58022f5d034241918a5c4f9797cac62f6b3a9dd \
|
||||||
--hash=sha256:6422f2ff0919fd720195f64ffd8f924c1395d30f9a495f31e2392c2efafb5056 \
|
--hash=sha256:6422f2ff0919fd720195f64ffd8f924c1395d30f9a495f31e2392c2efafb5056 \
|
||||||
--hash=sha256:15978a1fbd225583dd8cdaf37e67ccc278b5abecb4caf6b2d6b8e2b948e953f6
|
--hash=sha256:15978a1fbd225583dd8cdaf37e67ccc278b5abecb4caf6b2d6b8e2b948e953f6
|
||||||
pytz==2020.5; python_version >= "3.6" and python_version < "4.0" \
|
pytz==2021.1; python_version >= "3.7" and python_full_version < "4.0.0" \
|
||||||
--hash=sha256:16962c5fb8db4a8f63a26646d8886e9d769b6c511543557bc84e9569fb9a9cb4 \
|
--hash=sha256:eb10ce3e7736052ed3623d49975ce333bcd712c7bb19a58b9e2089d4057d0798 \
|
||||||
--hash=sha256:180befebb1927b16f6b57101720075a984c019ac16b1b7575673bea42c6c3da5
|
--hash=sha256:83a4a90894bf38e243cf052c8b58f381bfe9a7a483f6a9cab140bc7f702ac4da
|
||||||
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" \
|
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:432b788c4530cfe16d8d943a09d40ca6c16149727e4afe8c2c9d5580c59d9f24 \
|
||||||
--hash=sha256:0e7e0cfca8660dea8b7d5cd8c4f6c5e29e11f31158c0b0ae91a397f00e5a05a2
|
--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:017cde379adbd6a1f15a61873f43e8274179378e95ef3fede90b5aa64d304ed0 \
|
||||||
--hash=sha256:0f91fd2e829c44362cbcfab3e9ae12e22badaa8a29ad5ff599f9ec109f0454e8
|
--hash=sha256:0f91fd2e829c44362cbcfab3e9ae12e22badaa8a29ad5ff599f9ec109f0454e8
|
||||||
|
|
|
@ -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,
|
# Please do not modify this file, it will be reset at the next update.
|
||||||
but you can use the options and documentation in this file to find out
|
# You can edit the file __FINAL_HOME_PATH__/local_settings.py and add/modify the settings you need.
|
||||||
what can be done.
|
# 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 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 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
|
FINAL_WWW_PATH = __Path('__FINAL_WWW_PATH__') # /var/www/$app
|
||||||
assert FINAL_WWW_PATH.is_dir(), f'Directory not exists: {FINAL_WWW_PATH}'
|
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}'
|
assert LOG_FILE.is_file(), f'File not exists: {LOG_FILE}'
|
||||||
|
|
||||||
PATH_URL = '__PATH_URL__' # $YNH_APP_ARG_PATH
|
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:
|
# 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
|
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__'),)
|
ADMINS = (('__ADMIN__', '__ADMINMAIL__'),)
|
||||||
|
|
||||||
MANAGERS = ADMINS
|
MANAGERS = ADMINS
|
||||||
|
@ -77,14 +103,13 @@ DEFAULT_FROM_EMAIL = '__ADMINMAIL__'
|
||||||
# List of URLs your site is supposed to serve
|
# List of URLs your site is supposed to serve
|
||||||
ALLOWED_HOSTS = ['__DOMAIN__']
|
ALLOWED_HOSTS = ['__DOMAIN__']
|
||||||
|
|
||||||
|
|
||||||
# _____________________________________________________________________________
|
# _____________________________________________________________________________
|
||||||
# Configuration for caching
|
# Configuration for caching
|
||||||
CACHES = {
|
CACHES = {
|
||||||
'default': {
|
'default': {
|
||||||
'BACKEND': 'django_redis.cache.RedisCache',
|
'BACKEND': 'django_redis.cache.RedisCache',
|
||||||
'LOCATION': 'redis://127.0.0.1:6379/__REDIS_DB__',
|
'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:
|
# want to use unix sockets instead:
|
||||||
# 'LOCATION': 'unix:///var/run/redis/redis.sock?db=1',
|
# 'LOCATION': 'unix:///var/run/redis/redis.sock?db=1',
|
||||||
'OPTIONS': {
|
'OPTIONS': {
|
||||||
|
@ -94,7 +119,6 @@ CACHES = {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
# _____________________________________________________________________________
|
# _____________________________________________________________________________
|
||||||
# Static files (CSS, JavaScript, Images)
|
# Static files (CSS, JavaScript, Images)
|
||||||
|
|
||||||
|
@ -109,10 +133,18 @@ else:
|
||||||
STATIC_ROOT = str(FINAL_WWW_PATH / 'static')
|
STATIC_ROOT = str(FINAL_WWW_PATH / 'static')
|
||||||
MEDIA_ROOT = str(FINAL_WWW_PATH / 'media')
|
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 = {
|
LOGGING = {
|
||||||
'version': 1,
|
'version': 1,
|
||||||
'disable_existing_loggers': True,
|
'disable_existing_loggers': True,
|
||||||
|
@ -142,6 +174,7 @@ LOGGING = {
|
||||||
'axes': {'handlers': ['log_file', 'mail_admins'], 'level': 'WARNING', 'propagate': False},
|
'axes': {'handlers': ['log_file', 'mail_admins'], 'level': 'WARNING', 'propagate': False},
|
||||||
'django_tools': {'handlers': ['log_file', 'mail_admins'], 'level': 'INFO', 'propagate': False},
|
'django_tools': {'handlers': ['log_file', 'mail_admins'], 'level': 'INFO', 'propagate': False},
|
||||||
'django_ynh': {'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},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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.is_staff = True
|
||||||
user.save()
|
user.save()
|
||||||
|
|
|
@ -2,7 +2,7 @@ from django.conf import settings
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from django.urls import path
|
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
|
# settings.PATH_URL is the $YNH_APP_ARG_PATH
|
||||||
|
|
|
@ -6,7 +6,7 @@ import os
|
||||||
|
|
||||||
os.environ['DJANGO_SETTINGS_MODULE'] = 'settings'
|
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()
|
application = get_wsgi_application()
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
__version__ = '0.1.5'
|
|
|
@ -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},
|
|
||||||
},
|
|
||||||
}
|
|
|
@ -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()
|
|
|
@ -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()
|
|
|
@ -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
|
|
|
@ -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())
|
|
|
@ -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()
|
|
|
@ -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
|
|
|
@ -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)
|
|
|
@ -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
|
|
|
@ -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}'
|
|
|
@ -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'<html><body>request.META: <pre>{meta}</pre></body></html>'
|
|
||||||
|
|
||||||
return HttpResponse(html)
|
|
|
@ -1,16 +1,16 @@
|
||||||
{
|
{
|
||||||
"name": "django_ynh",
|
"name": "Django Example",
|
||||||
"id": "django_ynh",
|
"id": "django_example_ynh",
|
||||||
"packaging_format": 1,
|
"packaging_format": 1,
|
||||||
"description": {
|
"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",
|
"version": "v0.2.0.alpha0~ynh1",
|
||||||
"url": "https://github.com/jedie/django_ynh",
|
"url": "https://github.com/YunoHost-Apps/django_example_ynh",
|
||||||
"license": "GPL-3.0",
|
"license": "GPL-3.0",
|
||||||
"maintainer": {
|
"maintainer": {
|
||||||
"name": "Jens Diemer",
|
"name": "Jens Diemer",
|
||||||
"email": "django_ynh@jensdiemer.de"
|
"email": "django_example_ynh@jensdiemer.de"
|
||||||
},
|
},
|
||||||
"previous_maintainers": [],
|
"previous_maintainers": [],
|
||||||
"requirements": {
|
"requirements": {
|
||||||
|
@ -26,8 +26,8 @@
|
||||||
"name": "domain",
|
"name": "domain",
|
||||||
"type": "domain",
|
"type": "domain",
|
||||||
"ask": {
|
"ask": {
|
||||||
"en": "Choose a domain for django_ynh",
|
"en": "Choose a domain for PyInventory",
|
||||||
"fr": "Choisissez un domaine pour django_ynh"
|
"fr": "Choisissez un domaine pour PyInventory"
|
||||||
},
|
},
|
||||||
"example": "domain.org"
|
"example": "domain.org"
|
||||||
},
|
},
|
||||||
|
@ -35,18 +35,18 @@
|
||||||
"name": "path",
|
"name": "path",
|
||||||
"type": "path",
|
"type": "path",
|
||||||
"ask": {
|
"ask": {
|
||||||
"en": "Choose a path for django_ynh",
|
"en": "Choose a path for PyInventory",
|
||||||
"fr": "Choisissez un chemin pour django_ynh"
|
"fr": "Choisissez un chemin pour PyInventory"
|
||||||
},
|
},
|
||||||
"example": "/django_ynh",
|
"example": "/django_example_ynh",
|
||||||
"default": "/django_ynh"
|
"default": "/django_example_ynh"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "admin",
|
"name": "admin",
|
||||||
"type": "user",
|
"type": "user",
|
||||||
"ask": {
|
"ask": {
|
||||||
"en": "Choose an admin user for django_ynh",
|
"en": "Choose an admin user for PyInventory",
|
||||||
"fr": "Choisissez l'administrateur pour django_ynh"
|
"fr": "Choisissez l'administrateur pour PyInventory"
|
||||||
},
|
},
|
||||||
"example": "johndoe"
|
"example": "johndoe"
|
||||||
},
|
},
|
||||||
|
@ -54,8 +54,8 @@
|
||||||
"name": "is_public",
|
"name": "is_public",
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"ask": {
|
"ask": {
|
||||||
"en": "Should django_ynh be public accessible?",
|
"en": "Should PyInventory be public accessible?",
|
||||||
"fr": "django_ynh doit-il être accessible au public ?"
|
"fr": "PyInventory doit-il être accessible au public ?"
|
||||||
},
|
},
|
||||||
"help": {
|
"help": {
|
||||||
"en": "Any YunoHost user and anonymous people from the web will be able to access the application",
|
"en": "Any YunoHost user and anonymous people from the web will be able to access the application",
|
||||||
|
|
1021
poetry.lock
generated
Normal file
1021
poetry.lock
generated
Normal file
File diff suppressed because it is too large
Load diff
31
publish.py
31
publish.py
|
@ -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()
|
|
|
@ -1,28 +1,15 @@
|
||||||
[tool.poetry]
|
[tool.poetry]
|
||||||
name = "django_ynh"
|
name = "django_example_ynh"
|
||||||
version = "0.1.5"
|
version = "v0.2.0.alpha0"
|
||||||
description = "Glue code to package django projects as yunohost apps."
|
description = "Demo YunoHost Application to demonstrate the integration of a Django project under YunoHost."
|
||||||
authors = ["JensDiemer <git@jensdiemer.de>"]
|
authors = ["JensDiemer <git@jensdiemer.de>"]
|
||||||
license = "GPL"
|
license = "GPL"
|
||||||
readme = "README.md"
|
|
||||||
homepage = "https://github.com/YunoHost-Apps/django_ynh"
|
|
||||||
packages = [
|
|
||||||
{ include = "django_ynh" },
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
[tool.poetry.dependencies]
|
[tool.poetry.dependencies]
|
||||||
python = ">=3.7,<4.0.0"
|
python = ">=3.7,<4.0.0"
|
||||||
django = "*"
|
django_yunohost_integration = {version = "*", extras = ["ynh"]}
|
||||||
# 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
|
|
||||||
|
|
||||||
[tool.poetry.dev-dependencies]
|
[tool.poetry.dev-dependencies]
|
||||||
django-axes = "*" # https://github.com/jazzband/django-axes
|
|
||||||
poetry-publish = "*" # https://github.com/jedie/poetry-publish
|
|
||||||
bx_py_utils = "*"
|
bx_py_utils = "*"
|
||||||
tox = "*"
|
tox = "*"
|
||||||
pytest = "*"
|
pytest = "*"
|
||||||
|
@ -35,23 +22,20 @@ flynt = "*"
|
||||||
black = "*"
|
black = "*"
|
||||||
pyupgrade = "*"
|
pyupgrade = "*"
|
||||||
|
|
||||||
[tool.poetry.extras]
|
|
||||||
ynh = ["gunicorn", "psycopg2-binary", "django-redis", "django-axes"] # install as YunoHost app
|
|
||||||
|
|
||||||
|
|
||||||
[build-system]
|
[build-system]
|
||||||
requires = ["poetry-core>=1.0.0"]
|
requires = ["poetry-core>=1.0.0"]
|
||||||
build-backend = "poetry.core.masonry.api"
|
build-backend = "poetry.core.masonry.api"
|
||||||
|
|
||||||
|
|
||||||
[tool.isort]
|
[tool.isort]
|
||||||
# https://pycqa.github.io/isort/docs/configuration/config_files/#pyprojecttoml-preferred-format
|
# https://pycqa.github.io/isort/docs/configuration/config_files/#pyprojecttoml-preferred-format
|
||||||
atomic=true
|
atomic=true
|
||||||
line_length=120
|
line_length=120
|
||||||
case_sensitive=false
|
case_sensitive=false
|
||||||
skip_glob=["*/htmlcov/*","*/migrations/*","*/volumes/*"]
|
skip_glob=["*/htmlcov/*","*/migrations/*"]
|
||||||
multi_line_output=3
|
multi_line_output=3
|
||||||
include_trailing_comma=true
|
include_trailing_comma=true
|
||||||
known_first_party=["django_ynh","django_ynh_project","django_ynh_tests"]
|
known_first_party=[]
|
||||||
no_lines_before="LOCALFOLDER"
|
no_lines_before="LOCALFOLDER"
|
||||||
default_section="THIRDPARTY"
|
default_section="THIRDPARTY"
|
||||||
sections=["FUTURE","STDLIB","THIRDPARTY","FIRSTPARTY","LOCALFOLDER"]
|
sections=["FUTURE","STDLIB","THIRDPARTY","FIRSTPARTY","LOCALFOLDER"]
|
||||||
|
@ -61,7 +45,7 @@ lines_after_imports=2
|
||||||
[tool.pytest.ini_options]
|
[tool.pytest.ini_options]
|
||||||
# https://docs.pytest.org/en/latest/customize.html#pyproject-toml
|
# https://docs.pytest.org/en/latest/customize.html#pyproject-toml
|
||||||
minversion = "6.0"
|
minversion = "6.0"
|
||||||
norecursedirs = ".* .git __pycache__ conf coverage* dist htmlcov volumes"
|
norecursedirs = ".* .git __pycache__ conf coverage* dist htmlcov"
|
||||||
# sometimes helpfull "addopts" arguments:
|
# sometimes helpfull "addopts" arguments:
|
||||||
# -vv
|
# -vv
|
||||||
# --verbose
|
# --verbose
|
||||||
|
@ -96,7 +80,7 @@ skip_missing_interpreters = True
|
||||||
|
|
||||||
[testenv]
|
[testenv]
|
||||||
passenv = *
|
passenv = *
|
||||||
whitelist_externals = pytest
|
whitelist_externals = make
|
||||||
commands =
|
commands =
|
||||||
pytest --workers auto --tests-per-worker 1 --pyargs django_ynh django_ynh_project
|
make pytest
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -18,7 +18,7 @@ app=$YNH_APP_INSTANCE_NAME
|
||||||
public_path=/var/www/$app
|
public_path=/var/www/$app
|
||||||
final_path=/opt/yunohost/$app
|
final_path=/opt/yunohost/$app
|
||||||
log_path=/var/log/$app
|
log_path=/var/log/$app
|
||||||
log_file="${log_path}/django_ynh.log"
|
log_file="${log_path}/django_example_ynh.log"
|
||||||
|
|
||||||
#=================================================
|
#=================================================
|
||||||
# COMMON VARIABLES
|
# COMMON VARIABLES
|
||||||
|
@ -27,9 +27,6 @@ log_file="${log_path}/django_ynh.log"
|
||||||
# dependencies used by the app
|
# dependencies used by the app
|
||||||
pkg_dependencies="build-essential python3-dev python3-pip python3-venv git postgresql postgresql-contrib"
|
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
|
# Redis HELPERS
|
||||||
#=================================================
|
#=================================================
|
||||||
|
|
|
@ -113,7 +113,7 @@ fi
|
||||||
#=================================================
|
#=================================================
|
||||||
# MODIFY SETTINGS
|
# 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
|
# save old settings file
|
||||||
settings="$final_path/settings.py"
|
settings="$final_path/settings.py"
|
||||||
|
|
|
@ -114,13 +114,12 @@ chown -R "$app" "$final_path"
|
||||||
set -o nounset
|
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 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 -r "$final_path/requirements.txt"
|
||||||
ynh_exec_as $app $final_path/venv/bin/pip install --upgrade "$pip_install_string"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
#=================================================
|
#=================================================
|
||||||
# copy config files
|
# 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"
|
gunicorn_conf="$final_path/gunicorn.conf.py"
|
||||||
cp "../conf/gunicorn.conf.py" "$gunicorn_conf"
|
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"
|
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
|
set +o nounset
|
||||||
|
@ -176,8 +174,8 @@ ynh_script_progression --message="migrate/collectstatic/create superuser..." --w
|
||||||
./manage.py migrate --no-input
|
./manage.py migrate --no-input
|
||||||
./manage.py collectstatic --no-input
|
./manage.py collectstatic --no-input
|
||||||
|
|
||||||
# Create/update Django superuser (set unusable password, because auth done via SSOwat):
|
# Create/update Django superuser (set unusable password, because auth done via SSOwat):
|
||||||
./manage.py create_superuser --username="$admin" --email="$admin_mail"
|
./manage.py create_superuser --username="$admin" --email="$admin_mail"
|
||||||
|
|
||||||
# Check the configuration
|
# Check the configuration
|
||||||
# This may fail in some cases with errors, etc., but the app works and the user can fix issues later.
|
# 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..."
|
ynh_script_progression --message="Configuring a systemd service..."
|
||||||
|
|
||||||
# https://github.com/YunoHost/yunohost/blob/dev/data/helpers.d/systemd
|
# 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
|
# SETUP SSOWAT
|
||||||
|
@ -232,9 +230,9 @@ then
|
||||||
fi
|
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"
|
ynh_systemd_action --service_name="$app" --action="start"
|
||||||
|
|
||||||
|
|
|
@ -34,7 +34,7 @@ then
|
||||||
fi
|
fi
|
||||||
|
|
||||||
#=================================================
|
#=================================================
|
||||||
# STOP django_ynh'S SERVICES
|
# STOP PYINVENTORY'S SERVICES
|
||||||
#=================================================
|
#=================================================
|
||||||
ynh_script_progression --message="Stopping and removing systemd services..." --weight=5
|
ynh_script_progression --message="Stopping and removing systemd services..." --weight=5
|
||||||
|
|
||||||
|
|
|
@ -118,7 +118,7 @@ ynh_restore_file --origin_path="/etc/logrotate.d/$app"
|
||||||
#=================================================
|
#=================================================
|
||||||
# GENERIC FINALIZATION
|
# GENERIC FINALIZATION
|
||||||
#=================================================
|
#=================================================
|
||||||
# START django_ynh
|
# START PYINVENTORY
|
||||||
#=================================================
|
#=================================================
|
||||||
ynh_script_progression --message="Starting a systemd service..." --weight=5
|
ynh_script_progression --message="Starting a systemd service..." --weight=5
|
||||||
|
|
||||||
|
|
|
@ -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_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
|
# UPGRADE VENV
|
||||||
|
@ -98,7 +98,6 @@ chown -R "$app" "$final_path"
|
||||||
set -o nounset
|
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 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 -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"
|
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
|
set +o nounset
|
||||||
source "${final_path}/venv/bin/activate"
|
source "${final_path}/venv/bin/activate"
|
||||||
set -o nounset
|
set -o nounset
|
||||||
cd "${final_path}"
|
cd "${final_path}"
|
||||||
|
|
||||||
# Just for debugging:
|
# Just for debugging:
|
||||||
./manage.py diffsettings
|
./manage.py diffsettings
|
||||||
|
|
||||||
./manage.py migrate --no-input
|
./manage.py migrate --no-input
|
||||||
./manage.py collectstatic --no-input
|
./manage.py collectstatic --no-input
|
||||||
|
|
||||||
# Create/update Django superuser (set unusable password, because auth done via SSOwat):
|
# Create/update Django superuser (set unusable password, because auth done via SSOwat):
|
||||||
./manage.py create_superuser --username="$admin" --email="$admin_mail"
|
./manage.py create_superuser --username="$admin" --email="$admin_mail"
|
||||||
|
|
||||||
# Check the configuration
|
# Check the configuration
|
||||||
# This may fail in some cases with errors, etc., but the app works and the user can fix issues later.
|
# 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
|
./manage.py check --deploy || true
|
||||||
)
|
)
|
||||||
|
|
||||||
#=================================================
|
#=================================================
|
||||||
|
@ -203,9 +201,9 @@ chown -R "$app" "$public_path"
|
||||||
chown -R "$app" "$final_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"
|
ynh_systemd_action --service_name="$app" --action="start"
|
||||||
|
|
||||||
|
|
|
@ -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 bx_py_utils.test_utils.html_assertion import HtmlAssertionMixin
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
from django.test import override_settings
|
from django.test import override_settings
|
||||||
from django.test.testcases import TestCase
|
from django.test.testcases import TestCase
|
||||||
|
from django.urls import NoReverseMatch
|
||||||
from django.urls.base import reverse
|
from django.urls.base import reverse
|
||||||
|
|
||||||
from django_ynh.test_utils import generate_basic_auth
|
from django_ynh.test_utils import generate_basic_auth
|
||||||
from django_ynh.views import request_media_debug_view
|
from django_ynh.views import request_media_debug_view
|
||||||
|
|
||||||
|
import inventory
|
||||||
|
|
||||||
|
|
||||||
@override_settings(DEBUG=False)
|
@override_settings(DEBUG=False)
|
||||||
class DjangoYnhTestCase(HtmlAssertionMixin, TestCase):
|
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_HOME_PATH).endswith('/local_test/opt_yunohost')
|
||||||
assert str(settings.FINAL_WWW_PATH).endswith('/local_test/var_www')
|
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'
|
assert settings.ROOT_URLCONF == 'urls'
|
||||||
|
|
||||||
def test_urls(self):
|
def test_urls(self):
|
||||||
assert reverse('admin:index') == '/app_path/'
|
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):
|
def test_auth(self):
|
||||||
response = self.client.get('/app_path/')
|
response = self.client.get('/app_path/')
|
||||||
|
@ -55,7 +66,11 @@ class DjangoYnhTestCase(HtmlAssertionMixin, TestCase):
|
||||||
assert user.is_superuser is False
|
assert user.is_superuser is False
|
||||||
|
|
||||||
self.assert_html_parts(
|
self.assert_html_parts(
|
||||||
response, parts=('<title>Site administration | Django site admin</title>', '<strong>test</strong>')
|
response,
|
||||||
|
parts=(
|
||||||
|
f'<title>Site administration | PyInventory v{inventory.__version__}</title>',
|
||||||
|
'<strong>test</strong>',
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_wrong_auth_user(self):
|
def test_wrong_auth_user(self):
|
|
@ -2,10 +2,8 @@ import shutil
|
||||||
import subprocess
|
import subprocess
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
import django_ynh
|
|
||||||
|
|
||||||
|
BASE_PATH = Path(__file__).parent.parent
|
||||||
BASE_PATH = Path(django_ynh.__file__).parent.parent
|
|
||||||
|
|
||||||
|
|
||||||
def test_lint():
|
def test_lint():
|
||||||
|
|
|
@ -3,10 +3,10 @@ import shutil
|
||||||
import subprocess
|
import subprocess
|
||||||
from pathlib import Path
|
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):
|
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} !')
|
raise AssertionError(f'File {file_path} does not contain {string!r} !')
|
||||||
|
|
||||||
|
|
||||||
def test_version(package_root=None, version=None):
|
def test_version():
|
||||||
if package_root is None:
|
version = inventory.__version__
|
||||||
package_root = PACKAGE_ROOT
|
|
||||||
|
|
||||||
if version is None:
|
assert_file_contains_string(file_path=Path(PACKAGE_ROOT, 'pyproject.toml'), string=f'version = "{version}~ynh')
|
||||||
version = django_ynh.__version__
|
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')
|
||||||
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}"'
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def test_poetry_check(package_root=None):
|
def test_poetry_check():
|
||||||
if package_root is None:
|
|
||||||
package_root = PACKAGE_ROOT
|
|
||||||
|
|
||||||
poerty_bin = shutil.which('poetry')
|
poerty_bin = shutil.which('poetry')
|
||||||
|
|
||||||
output = subprocess.check_output(
|
output = subprocess.check_output(
|
||||||
|
@ -47,7 +33,7 @@ def test_poetry_check(package_root=None):
|
||||||
universal_newlines=True,
|
universal_newlines=True,
|
||||||
env=os.environ,
|
env=os.environ,
|
||||||
stderr=subprocess.STDOUT,
|
stderr=subprocess.STDOUT,
|
||||||
cwd=str(package_root),
|
cwd=str(PACKAGE_ROOT),
|
||||||
)
|
)
|
||||||
print(output)
|
print(output)
|
||||||
assert output == 'All set!\n'
|
assert output == 'All set!\n'
|
||||||
|
|
Loading…
Reference in a new issue