Merge pull request #1 from YunoHost-Apps/testing

WIP: rename/split from django_ynh
This commit is contained in:
Jens Diemer 2021-10-10 16:11:29 +02:00 committed by GitHub
commit e58d2c9535
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
42 changed files with 1290 additions and 1044 deletions

View file

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

7
.gitignore vendored
View file

@ -3,11 +3,8 @@
!.editorconfig
!.flake8
!.gitignore
coverage.xml
__pycache__
secret.txt
/htmlcov/
/local_test/
/dist/
/poetry.lock
*.log
/coverage.xml
/htmlcov/

View file

@ -31,7 +31,7 @@ update: install-poetry ## update the sources and installation and generate "con
lint: ## Run code formatters and linter
poetry run flynt --fail-on-change --line_length=${MAX_LINE_LENGTH} .
poetry run isort --check-only .
poetry run flake8 django_ynh
poetry run flake8 .
fix-code-style: ## Fix code formatting
poetry run flynt --line_length=${MAX_LINE_LENGTH} .
@ -47,9 +47,6 @@ tox: check-poetry ## Run pytest via tox with all environments
pytest: install ## Run pytest
poetry run python3 ./run_pytest.py
publish: ## Release new version to PyPi
poetry run python3 ./publish.py
local-test: install ## Run local_test.py to run the project locally
poetry run python3 ./local_test.py

189
README.md
View file

@ -1,100 +1,25 @@
# django_ynh
# django_example_ynh
Demo [YunoHost Application](https://install-app.yunohost.org/?app=django_example_ynh) to demonstrate the integration of a Django project under YunoHost.
Glue code to package django projects as yunohost apps.
This repository is:
* The Python package [django-ynh](https://pypi.org/project/django-ynh/) with helpers for integrate a Django project as YunoHost package
* A example [YunoHost Application](https://install-app.yunohost.org/?app=django_ynh) that can be installed
[![Integration level](https://dash.yunohost.org/integration/django_ynh.svg)](https://dash.yunohost.org/appci/app/django_ynh) ![](https://ci-apps.yunohost.org/ci/badges/django_ynh.status.svg) ![](https://ci-apps.yunohost.org/ci/badges/django_ynh.maintain.svg)
[![Install django_ynh with YunoHost](https://install-app.yunohost.org/install-with-yunohost.svg)](https://install-app.yunohost.org/?app=django_ynh)
[![Integration level](https://dash.yunohost.org/integration/django_example_ynh.svg)](https://dash.yunohost.org/appci/app/django_example_ynh) ![](https://ci-apps.yunohost.org/ci/badges/django_example_ynh.status.svg) ![](https://ci-apps.yunohost.org/ci/badges/django_example_ynh.maintain.svg)
[![Install django_example_ynh with YunoHost](https://install-app.yunohost.org/install-with-yunohost.svg)](https://install-app.yunohost.org/?app=django_example_ynh)
Pull requests welcome ;)
## Features
* SSOwat integration (see below)
* Helper to create first super user for `scripts/install`
* Run Django development server with a local generated YunoHost package installation (called `local_test`)
* Run `pytest` against `local_test` "installation"
### SSO authentication
[SSOwat](https://github.com/YunoHost/SSOwat) is fully supported:
* First user (`$YNH_APP_ARG_ADMIN`) will be created as Django's super user
* All new users will be created as normal users
* Login via SSO is fully supported
* User Email, First / Last name will be updated from SSO data
### usage
To create/update the first user in `install`/`upgrade`, e.g.:
```bash
./manage.py create_superuser --username="$admin" --email="$admin_mail"
```
This Create/update Django superuser and set a unusable password.
A password is not needed, because auth done via SSOwat ;)
Main parts in `settings.py`:
```python
from django_ynh.secret_key import get_or_create_secret as __get_or_create_secret
# 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 = [
#...
'django_ynh',
#...
]
MIDDLEWARE = [
#... after AuthenticationMiddleware ...
#
# 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/'
```
## local test
For quicker developing of django_ynh in the context of YunoHost app,
For quicker developing of django_example_ynh in the context of YunoHost app,
it's possible to run the Django developer server with the settings
and urls made for YunoHost installation.
e.g.:
```bash
~$ git clone https://github.com/YunoHost-Apps/django_ynh.git
~$ cd django_ynh/
~/django_ynh$ make
~$ git clone https://github.com/YunoHost-Apps/django_example_ynh.git
~$ cd django_example_ynh/
~/django_example_ynh$ make
install-poetry install or update poetry
install install project via poetry
update update the sources and installation and generate "conf/requirements.txt"
@ -107,9 +32,9 @@ publish Release new version to PyPi
local-test Run local_test.py to run the project locally
local-diff-settings Run "manage.py diffsettings" with local test
~/django_ynh$ make install-poetry
~/django_ynh$ make install
~/django_ynh$ make local-test
~/django_example_ynh$ make install-poetry
~/django_example_ynh$ make install
~/django_example_ynh$ make local-test
```
Notes:
@ -121,36 +46,40 @@ Notes:
## history
* [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_example_ynh/compare/v0.2.0...master) **dev**
* tbc
* [v0.1.5 - 19.01.2021](https://github.com/YunoHost-Apps/django_ynh/compare/v0.1.4...v0.1.5)
* [v0.2.0 - 15.09.2021](https://github.com/YunoHost-Apps/django_example_ynh/compare/v0.1.5...v0.2.0)
* rename/split `django_example_ynh` into:
* [django_yunohost_integration](https://github.com/jedie/django_yunohost_integration) - Python package with the glue code to integrate a Django project with YunoHost
* [django_example_ynh](https://github.com/YunoHost-Apps/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_example_ynh/compare/v0.1.4...v0.1.5)
* Make some deps `gunicorn`, `psycopg2-binary`, `django-redis`, `django-axes` optional
* [v0.1.4 - 08.01.2021](https://github.com/YunoHost-Apps/django_ynh/compare/v0.1.3...v0.1.4)
* Bugfix [CSRF verification failed on POST requests #7](https://github.com/YunoHost-Apps/django_ynh/issues/7)
* [v0.1.3 - 08.01.2021](https://github.com/YunoHost-Apps/django_ynh/compare/v0.1.2...v0.1.3)
* [v0.1.4 - 08.01.2021](https://github.com/YunoHost-Apps/django_example_ynh/compare/v0.1.3...v0.1.4)
* Bugfix [CSRF verification failed on POST requests #7](https://github.com/YunoHost-Apps/django_example_ynh/issues/7)
* [v0.1.3 - 08.01.2021](https://github.com/YunoHost-Apps/django_example_ynh/compare/v0.1.2...v0.1.3)
* set "DEBUG = True" in local_test (so static files are served and auth works)
* Bugfixes and cleanups
* [v0.1.2 - 29.12.2020](https://github.com/YunoHost-Apps/django_ynh/compare/v0.1.1...v0.1.2)
* [v0.1.2 - 29.12.2020](https://github.com/YunoHost-Apps/django_example_ynh/compare/v0.1.1...v0.1.2)
* Bugfixes
* [v0.1.1 - 29.12.2020](https://github.com/YunoHost-Apps/django_ynh/compare/v0.1.0...v0.1.1)
* Refactor "create_superuser" to a manage command, useable via "django_ynh" in `INSTALLED_APPS`
* [v0.1.1 - 29.12.2020](https://github.com/YunoHost-Apps/django_example_ynh/compare/v0.1.0...v0.1.1)
* Refactor "create_superuser" to a manage command, useable via "django_example_ynh" in `INSTALLED_APPS`
* Generate "conf/requirements.txt" and use this file for install
* rename own settings and urls (in `/conf/`)
* [v0.1.0 - 28.12.2020](https://github.com/YunoHost-Apps/django_ynh/compare/f578f14...v0.1.0)
* [v0.1.0 - 28.12.2020](https://github.com/YunoHost-Apps/django_example_ynh/compare/f578f14...v0.1.0)
* first working state
* [23.12.2020](https://github.com/YunoHost-Apps/django_ynh/commit/f578f144a3a6d11d7044597c37d550d29c247773)
* [23.12.2020](https://github.com/YunoHost-Apps/django_example_ynh/commit/f578f144a3a6d11d7044597c37d550d29c247773)
* init the project
## Links
* Report a bug about this package: https://github.com/YunoHost-Apps/django_ynh
* Report a bug about this package: https://github.com/YunoHost-Apps/django_example_ynh
* YunoHost website: https://yunohost.org/
* PyPi package: https://pypi.org/project/django-ynh/
These projects used `django_ynh`:
These projects used `django_example_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
---
@ -159,73 +88,73 @@ These projects used `django_ynh`:
## package installation / debugging
Please send your pull request to https://github.com/YunoHost-Apps/django_ynh
Please send your pull request to https://github.com/YunoHost-Apps/django_example_ynh
Try 'main' branch, e.g.:
```bash
sudo yunohost app install https://github.com/YunoHost-Apps/django_ynh/tree/master --debug
sudo yunohost app install https://github.com/YunoHost-Apps/django_example_ynh/tree/master --debug
or
sudo yunohost app upgrade django_ynh -u https://github.com/YunoHost-Apps/django_ynh/tree/master --debug
sudo yunohost app upgrade django_example_ynh -u https://github.com/YunoHost-Apps/django_example_ynh/tree/master --debug
```
Try 'testing' branch, e.g.:
```bash
sudo yunohost app install https://github.com/YunoHost-Apps/django_ynh/tree/testing --debug
sudo yunohost app install https://github.com/YunoHost-Apps/django_example_ynh/tree/testing --debug
or
sudo yunohost app upgrade django_ynh -u https://github.com/YunoHost-Apps/django_ynh/tree/testing --debug
sudo yunohost app upgrade django_example_ynh -u https://github.com/YunoHost-Apps/django_example_ynh/tree/testing --debug
```
To remove call e.g.:
```bash
sudo yunohost app remove django_ynh
sudo yunohost app remove django_example_ynh
```
Backup / remove / restore cycle, e.g.:
```bash
yunohost backup create --apps django_ynh
yunohost backup create --apps django_example_ynh
yunohost backup list
archives:
- django_ynh-pre-upgrade1
- django_example_ynh-pre-upgrade1
- 20201223-163434
yunohost app remove django_ynh
yunohost backup restore 20201223-163434 --apps django_ynh
yunohost app remove django_example_ynh
yunohost backup restore 20201223-163434 --apps django_example_ynh
```
Debug installation, e.g.:
```bash
root@yunohost:~# ls -la /var/www/django_ynh/
root@yunohost:~# ls -la /var/www/django_example_ynh/
total 18
drwxr-xr-x 4 root root 4 Dec 8 08:36 .
drwxr-xr-x 6 root root 6 Dec 8 08:36 ..
drwxr-xr-x 2 root root 2 Dec 8 08:36 media
drwxr-xr-x 7 root root 8 Dec 8 08:40 static
root@yunohost:~# ls -la /opt/yunohost/django_ynh/
root@yunohost:~# ls -la /opt/yunohost/django_example_ynh/
total 58
drwxr-xr-x 5 django_ynh django_ynh 11 Dec 8 08:39 .
drwxr-xr-x 5 django_example_ynh django_example_ynh 11 Dec 8 08:39 .
drwxr-xr-x 3 root root 3 Dec 8 08:36 ..
-rw-r--r-- 1 django_ynh django_ynh 460 Dec 8 08:39 gunicorn.conf.py
-rw-r--r-- 1 django_ynh django_ynh 0 Dec 8 08:39 local_settings.py
-rwxr-xr-x 1 django_ynh django_ynh 274 Dec 8 08:39 manage.py
-rw-r--r-- 1 django_ynh django_ynh 171 Dec 8 08:39 secret.txt
drwxr-xr-x 6 django_ynh django_ynh 6 Dec 8 08:37 venv
-rw-r--r-- 1 django_ynh django_ynh 115 Dec 8 08:39 wsgi.py
-rw-r--r-- 1 django_ynh django_ynh 4737 Dec 8 08:39 django_ynh_demo_settings.py
-rw-r--r-- 1 django_example_ynh django_example_ynh 460 Dec 8 08:39 gunicorn.conf.py
-rw-r--r-- 1 django_example_ynh django_example_ynh 0 Dec 8 08:39 local_settings.py
-rwxr-xr-x 1 django_example_ynh django_example_ynh 274 Dec 8 08:39 manage.py
-rw-r--r-- 1 django_example_ynh django_example_ynh 171 Dec 8 08:39 secret.txt
drwxr-xr-x 6 django_example_ynh django_example_ynh 6 Dec 8 08:37 venv
-rw-r--r-- 1 django_example_ynh django_example_ynh 115 Dec 8 08:39 wsgi.py
-rw-r--r-- 1 django_example_ynh django_example_ynh 4737 Dec 8 08:39 django_example_ynh_demo_settings.py
root@yunohost:~# cd /opt/yunohost/django_ynh/
root@yunohost:/opt/yunohost/django_ynh# source venv/bin/activate
(venv) root@yunohost:/opt/yunohost/django_ynh# ./manage.py check
django_ynh v0.8.2 (Django v2.2.17)
DJANGO_SETTINGS_MODULE='django_ynh_demo_settings'
PROJECT_PATH:/opt/yunohost/django_ynh/venv/lib/python3.7/site-packages
BASE_PATH:/opt/yunohost/django_ynh
root@yunohost:~# cd /opt/yunohost/django_example_ynh/
root@yunohost:/opt/yunohost/django_example_ynh# source venv/bin/activate
(venv) root@yunohost:/opt/yunohost/django_example_ynh# ./manage.py check
django_example_ynh v0.8.2 (Django v2.2.17)
DJANGO_SETTINGS_MODULE='django_example_ynh_demo_settings'
PROJECT_PATH:/opt/yunohost/django_example_ynh/venv/lib/python3.7/site-packages
BASE_PATH:/opt/yunohost/django_example_ynh
System check identified no issues (0 silenced).
root@yunohost:~# tail -f /var/log/django_ynh/django_ynh.log
root@yunohost:~# cat /etc/systemd/system/django_ynh.service
root@yunohost:~# tail -f /var/log/django_example_ynh/django_example_ynh.log
root@yunohost:~# cat /etc/systemd/system/django_example_ynh.service
root@yunohost:~# systemctl reload-or-restart django_ynh
root@yunohost:~# journalctl --unit=django_ynh --follow
root@yunohost:~# systemctl reload-or-restart django_example_ynh
root@yunohost:~# journalctl --unit=django_example_ynh --follow
```

View file

@ -1,5 +1,5 @@
[Unit]
Description=django_ynh DEMO application server
Description=__APP__ server
After=redis.service postgresql.service
[Service]

View file

@ -8,6 +8,9 @@ location __PATH__/static/ {
location __PATH__/ {
# https://github.com/benoitc/gunicorn/blob/master/examples/nginx.conf
# this is needed if you have file import via upload enabled
client_max_body_size 100M;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

View file

@ -1,21 +1,24 @@
asgiref==3.3.1; python_version >= "3.6" and python_version < "4.0" \
asgiref==3.3.1; python_version >= "3.7" and python_full_version < "4.0.0" \
--hash=sha256:5ee950735509d04eb673bd7f7120f8fa1c9e2df495394992c73234d526907e17 \
--hash=sha256:7162a3cb30ab0609f1a4c95938fd73e8604f63bdba516a7f7d64b83ff09478f0
django-axes==5.10.0; python_version >= "3.6" and python_version < "4.0" \
--hash=sha256:8a62cd4cc78ef08007e8102def34be83832995eb6e3e0c814d605741b82a2796 \
--hash=sha256:3c81ddca8a9d7fd0019cb440f711cc873c3039546f7eacb3f2ec616bf0ec1b32
django-ipware==3.0.2; python_version >= "3.6" and python_version < "4.0" \
django-axes==5.13.1; python_version >= "3.7" and python_version < "4.0" and python_full_version < "4.0.0" \
--hash=sha256:8f7870dc18ace6155127073bffe7276719c71c5ad4e67b45abedc0207f64a2f6 \
--hash=sha256:aef814f738742b01cc7730cd2bebe3e5b462b807fd6c2ed609b62437ccc71f80
django-ipware==3.0.2; python_version >= "3.7" and python_version < "4.0" and python_full_version < "4.0.0" \
--hash=sha256:c7df8e1410a8e5d6b1fbae58728402ea59950f043c3582e033e866f0f0cf5e94
django-redis==4.12.1; python_version >= "3.5" \
django-redis==4.12.1; python_version >= "3.7" and python_full_version < "4.0.0" \
--hash=sha256:306589c7021e6468b2656edc89f62b8ba67e8d5a1c8877e2688042263daa7a63 \
--hash=sha256:1133b26b75baa3664164c3f44b9d5d133d1b8de45d94d79f38d1adc5b1d502e5
django==3.1.4; python_version >= "3.6" \
--hash=sha256:5c866205f15e7a7123f1eec6ab939d22d5bde1416635cab259684af66d8e48a2 \
--hash=sha256:edb10b5c45e7e9c0fb1dc00b76ec7449aca258a39ffd613dbd078c51d19c9f03
gunicorn==20.0.4; python_version >= "3.4" \
django-yunohost-integration==0.2.0a0; python_version >= "3.7" and python_full_version < "4.0.0" \
--hash=sha256:a45197a3c4595a496674e7e48a58f58351b6022526893264831e0d6ba463a44f \
--hash=sha256:9acdf320537ccce47ceb1d51d2a00fafbf30938152d3b22c59a3a2ead7952227
django==3.1.7; python_version >= "3.7" and python_full_version < "4.0.0" and python_version < "4.0" \
--hash=sha256:baf099db36ad31f970775d0be5587cc58a6256a6771a44eb795b554d45f211b8 \
--hash=sha256:32ce792ee9b6a0cbbec340123e229ac9f765dff8c2a4ae9247a14b2ba3a365a7
gunicorn==20.0.4; python_version >= "3.7" and python_full_version < "4.0.0" \
--hash=sha256:cd4a810dd51bf497552cf3f863b575dabd73d6ad6a91075b65936b151cbf4f9c \
--hash=sha256:1904bb2b8a43658807108d59c3f3d56c2b6121a701161de0ddf9ad140073c626
psycopg2-binary==2.8.6; (python_version >= "2.7" and python_full_version < "3.0.0") or (python_full_version >= "3.4.0") \
psycopg2-binary==2.8.6; python_version >= "3.7" and python_full_version < "3.0.0" or python_version >= "3.7" and python_full_version < "4.0.0" and python_full_version >= "3.4.0" \
--hash=sha256:11b9c0ebce097180129e422379b824ae21c8f2a6596b159c7659e2e5a00e1aa0 \
--hash=sha256:d14b140a4439d816e3b1229a4a525df917d6ea22a0771a2a78332273fd9528a4 \
--hash=sha256:1fabed9ea2acc4efe4671b92c669a213db744d2af8a9fc5d69a8e9bc14b7a9db \
@ -51,12 +54,12 @@ psycopg2-binary==2.8.6; (python_version >= "2.7" and python_full_version < "3.0.
--hash=sha256:7312e931b90fe14f925729cde58022f5d034241918a5c4f9797cac62f6b3a9dd \
--hash=sha256:6422f2ff0919fd720195f64ffd8f924c1395d30f9a495f31e2392c2efafb5056 \
--hash=sha256:15978a1fbd225583dd8cdaf37e67ccc278b5abecb4caf6b2d6b8e2b948e953f6
pytz==2020.5; python_version >= "3.6" and python_version < "4.0" \
--hash=sha256:16962c5fb8db4a8f63a26646d8886e9d769b6c511543557bc84e9569fb9a9cb4 \
--hash=sha256:180befebb1927b16f6b57101720075a984c019ac16b1b7575673bea42c6c3da5
redis==3.5.3; python_version >= "3.5" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.5" \
pytz==2021.1; python_version >= "3.7" and python_full_version < "4.0.0" \
--hash=sha256:eb10ce3e7736052ed3623d49975ce333bcd712c7bb19a58b9e2089d4057d0798 \
--hash=sha256:83a4a90894bf38e243cf052c8b58f381bfe9a7a483f6a9cab140bc7f702ac4da
redis==3.5.3; python_version >= "3.7" and python_full_version < "3.0.0" or python_version >= "3.7" and python_full_version < "4.0.0" and python_full_version >= "3.5.0" \
--hash=sha256:432b788c4530cfe16d8d943a09d40ca6c16149727e4afe8c2c9d5580c59d9f24 \
--hash=sha256:0e7e0cfca8660dea8b7d5cd8c4f6c5e29e11f31158c0b0ae91a397f00e5a05a2
sqlparse==0.4.1; python_version >= "3.6" and python_version < "4.0" \
sqlparse==0.4.1; python_version >= "3.7" and python_full_version < "4.0.0" \
--hash=sha256:017cde379adbd6a1f15a61873f43e8274179378e95ef3fede90b5aa64d304ed0 \
--hash=sha256:0f91fd2e829c44362cbcfab3e9ae12e22badaa8a29ad5ff599f9ec109f0454e8

View file

@ -1,24 +1,21 @@
"""
**************************************************************************
Please do not modify this file, it will be reset at the next update.
You can edit the file __FINAL_HOME_PATH__/local_settings.py and add/modify
the settings you need.
################################################################################
################################################################################
The parameters you add in local_settings.py will overwrite these,
but you can use the options and documentation in this file to find out
what can be done.
**************************************************************************
# Please do not modify this file, it will be reset at the next update.
# You can edit the file __FINAL_HOME_PATH__/local_settings.py and add/modify the settings you need.
# The parameters you add in local_settings.py will overwrite these,
# but you can use the options and documentation in this file to find out what can be done.
################################################################################
################################################################################
Django Settings here depends on YunoHost app settings.
"""
from pathlib import Path as __Path
from django_ynh.base_settings import * # noqa
from django_ynh.secret_key import get_or_create_secret as __get_or_create_secret
from django_yunohost_integration.secret_key import get_or_create_secret as __get_or_create_secret
from django_yunohost_integration.base_settings import * # noqa
DEBUG = True # This is only the DEMO app ;) But should never be on in production!
DEBUG = False # Don't turn DEBUG on in production!
# -----------------------------------------------------------------------------
@ -28,7 +25,7 @@ assert FINAL_HOME_PATH.is_dir(), f'Directory not exists: {FINAL_HOME_PATH}'
FINAL_WWW_PATH = __Path('__FINAL_WWW_PATH__') # /var/www/$app
assert FINAL_WWW_PATH.is_dir(), f'Directory not exists: {FINAL_WWW_PATH}'
LOG_FILE = __Path('__LOG_FILE__') # /var/log/$app/django_ynh.log
LOG_FILE = __Path('__LOG_FILE__') # /var/log/$app/django_example_ynh.log
assert LOG_FILE.is_file(), f'File not exists: {LOG_FILE}'
PATH_URL = '__PATH_URL__' # $YNH_APP_ARG_PATH
@ -37,10 +34,15 @@ PATH_URL = PATH_URL.strip('/')
# -----------------------------------------------------------------------------
# Function that will be called to finalize a user profile:
YNH_SETUP_USER = 'setup_user.setup_demo_user'
YNH_SETUP_USER = 'setup_user.setup_project_user'
SECRET_KEY = __get_or_create_secret(FINAL_HOME_PATH / 'secret.txt') # /opt/yunohost/$app/secret.txt
# INSTALLED_APPS.append('<insert-your-app-here>')
# -----------------------------------------------------------------------------
ADMINS = (('__ADMIN__', '__ADMINMAIL__'),)
MANAGERS = ADMINS
@ -77,14 +79,13 @@ DEFAULT_FROM_EMAIL = '__ADMINMAIL__'
# List of URLs your site is supposed to serve
ALLOWED_HOSTS = ['__DOMAIN__']
# _____________________________________________________________________________
# Configuration for caching
CACHES = {
'default': {
'BACKEND': 'django_redis.cache.RedisCache',
'LOCATION': 'redis://127.0.0.1:6379/__REDIS_DB__',
# If redis is running on same host as django_ynh, you might
# If redis is running on same host as PyInventory, you might
# want to use unix sockets instead:
# 'LOCATION': 'unix:///var/run/redis/redis.sock?db=1',
'OPTIONS': {
@ -94,7 +95,6 @@ CACHES = {
},
}
# _____________________________________________________________________________
# Static files (CSS, JavaScript, Images)
@ -112,7 +112,6 @@ MEDIA_ROOT = str(FINAL_WWW_PATH / 'media')
# -----------------------------------------------------------------------------
LOGGING = {
'version': 1,
'disable_existing_loggers': True,
@ -141,7 +140,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},
'django_yunohost_integration': {'handlers': ['log_file', 'mail_admins'], 'level': 'INFO', 'propagate': False},
},
}

View file

@ -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_yunohost_integration.sso_auth
"""
user.is_staff = True
user.save()

View file

@ -2,7 +2,7 @@ from django.conf import settings
from django.contrib import admin
from django.urls import path
from django_ynh.views import request_media_debug_view
from django_yunohost_integration.views import request_media_debug_view
# settings.PATH_URL is the $YNH_APP_ARG_PATH

View file

@ -6,7 +6,7 @@ import os
os.environ['DJANGO_SETTINGS_MODULE'] = 'settings'
from django.core.wsgi import get_wsgi_application
from django.core.wsgi import get_wsgi_application # noqa
application = get_wsgi_application()

View file

@ -1 +0,0 @@
__version__ = '0.1.5'

View file

@ -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},
},
}

View file

@ -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()

View file

@ -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()

View file

@ -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

View file

@ -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())

View file

@ -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()

View file

@ -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

View file

@ -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)

View file

@ -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

View file

@ -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}'

View file

@ -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)

View file

@ -10,7 +10,7 @@ from pathlib import Path
try:
from django_ynh.local_test import create_local_test
from django_yunohost_integration.local_test import create_local_test
except ImportError as err:
raise ImportError('Did you forget to activate a virtual environment?') from err

View file

@ -1,16 +1,16 @@
{
"name": "django_ynh",
"id": "django_ynh",
"name": "Django Example",
"id": "django_example_ynh",
"packaging_format": 1,
"description": {
"en": "Glue code to package django projects as yunohost apps."
"en": "Demo YunoHost Application to demonstrate the integration of a Django project under YunoHost."
},
"version": "0.1.5~ynh1",
"url": "https://github.com/jedie/django_ynh",
"version": "v0.2.0.alpha0~ynh1",
"url": "https://github.com/YunoHost-Apps/django_example_ynh",
"license": "GPL-3.0",
"maintainer": {
"name": "Jens Diemer",
"email": "django_ynh@jensdiemer.de"
"email": "django_example_ynh@jensdiemer.de"
},
"previous_maintainers": [],
"requirements": {
@ -26,8 +26,8 @@
"name": "domain",
"type": "domain",
"ask": {
"en": "Choose a domain for django_ynh",
"fr": "Choisissez un domaine pour django_ynh"
"en": "Choose a domain for PyInventory",
"fr": "Choisissez un domaine pour PyInventory"
},
"example": "domain.org"
},
@ -35,18 +35,18 @@
"name": "path",
"type": "path",
"ask": {
"en": "Choose a path for django_ynh",
"fr": "Choisissez un chemin pour django_ynh"
"en": "Choose a path for PyInventory",
"fr": "Choisissez un chemin pour PyInventory"
},
"example": "/django_ynh",
"default": "/django_ynh"
"example": "/django_example_ynh",
"default": "/django_example_ynh"
},
{
"name": "admin",
"type": "user",
"ask": {
"en": "Choose an admin user for django_ynh",
"fr": "Choisissez l'administrateur pour django_ynh"
"en": "Choose an admin user for PyInventory",
"fr": "Choisissez l'administrateur pour PyInventory"
},
"example": "johndoe"
},
@ -54,8 +54,8 @@
"name": "is_public",
"type": "boolean",
"ask": {
"en": "Should django_ynh be public accessible?",
"fr": "django_ynh doit-il être accessible au public ?"
"en": "Should PyInventory be public accessible?",
"fr": "PyInventory doit-il être accessible au public ?"
},
"help": {
"en": "Any YunoHost user and anonymous people from the web will be able to access the application",

1068
poetry.lock generated Normal file

File diff suppressed because it is too large Load diff

View file

@ -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()

View file

@ -1,29 +1,18 @@
[tool.poetry]
name = "django_ynh"
version = "0.1.5"
description = "Glue code to package django projects as yunohost apps."
name = "django_example_ynh"
version = "v0.2.0"
description = "Demo YunoHost Application to demonstrate the integration of a Django project under YunoHost."
authors = ["JensDiemer <git@jensdiemer.de>"]
license = "GPL"
readme = "README.md"
homepage = "https://github.com/YunoHost-Apps/django_ynh"
packages = [
{ include = "django_ynh" },
]
homepage = "https://github.com/YunoHost-Apps/django_example_ynh"
[tool.poetry.dependencies]
python = ">=3.7,<4.0.0"
django = "*"
# The follogin extra packages are used for install "django_ynh" as YunoHost app:
gunicorn = { version = "*", optional = true }
psycopg2-binary = { version = "*", optional = true }
django-redis = { version = "*", optional = true }
django-axes = { version = "*", optional = true } # https://github.com/jazzband/django-axes
django_yunohost_integration = {version = "*", extras = ["ynh"]}
[tool.poetry.dev-dependencies]
django-axes = "*" # https://github.com/jazzband/django-axes
poetry-publish = "*" # https://github.com/jedie/poetry-publish
bx_py_utils = "*"
bx_py_utils = "*" # https://github.com/boxine/bx_py_utils
bx_django_utils = "*" # https://github.com/boxine/bx_django_utils
tox = "*"
pytest = "*"
pytest-cov = "*"
@ -35,23 +24,20 @@ flynt = "*"
black = "*"
pyupgrade = "*"
[tool.poetry.extras]
ynh = ["gunicorn", "psycopg2-binary", "django-redis", "django-axes"] # install as YunoHost app
[build-system]
requires = ["poetry-core>=1.0.0"]
build-backend = "poetry.core.masonry.api"
[tool.isort]
# https://pycqa.github.io/isort/docs/configuration/config_files/#pyprojecttoml-preferred-format
atomic=true
line_length=120
case_sensitive=false
skip_glob=["*/htmlcov/*","*/migrations/*","*/volumes/*"]
skip_glob=["*/htmlcov/*","*/migrations/*"]
multi_line_output=3
include_trailing_comma=true
known_first_party=["django_ynh","django_ynh_project","django_ynh_tests"]
known_first_party=[]
no_lines_before="LOCALFOLDER"
default_section="THIRDPARTY"
sections=["FUTURE","STDLIB","THIRDPARTY","FIRSTPARTY","LOCALFOLDER"]
@ -61,7 +47,7 @@ lines_after_imports=2
[tool.pytest.ini_options]
# https://docs.pytest.org/en/latest/customize.html#pyproject-toml
minversion = "6.0"
norecursedirs = ".* .git __pycache__ conf coverage* dist htmlcov volumes"
norecursedirs = ".* .git __pycache__ conf coverage* dist htmlcov"
# sometimes helpfull "addopts" arguments:
# -vv
# --verbose
@ -96,7 +82,7 @@ skip_missing_interpreters = True
[testenv]
passenv = *
whitelist_externals = pytest
whitelist_externals = make
commands =
pytest --workers auto --tests-per-worker 1 --pyargs django_ynh django_ynh_project
make pytest
"""

View file

@ -6,7 +6,7 @@ from pathlib import Path
try:
from django_ynh.pytest_helper import run_pytest
from django_yunohost_integration.pytest_helper import run_pytest
except ImportError as err:
raise ImportError('Did you forget to activate a virtual environment?') from err

View file

@ -18,17 +18,14 @@ app=$YNH_APP_INSTANCE_NAME
public_path=/var/www/$app
final_path=/opt/yunohost/$app
log_path=/var/log/$app
log_file="${log_path}/django_ynh.log"
log_file="${log_path}/django_example_ynh.log"
#=================================================
# COMMON VARIABLES
#=================================================
# dependencies used by the app
pkg_dependencies="build-essential python3-dev python3-pip python3-venv git postgresql postgresql-contrib"
# To install/upgrade this project via pip:
pip_install_string="django_ynh[ynh]==0.1.5"
pkg_dependencies="build-essential python3-dev python3-pip python3-venv git libpq-dev postgresql postgresql-contrib"
#=================================================
# Redis HELPERS

View file

@ -113,7 +113,7 @@ fi
#=================================================
# MODIFY SETTINGS
#=================================================
ynh_script_progression --message="Modify django_ynh's config file..."
ynh_script_progression --message="Modify PyInventory's config file..."
# save old settings file
settings="$final_path/settings.py"

View file

@ -105,7 +105,7 @@ 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"
chown -R "$app:" "$final_path"
#run source in a 'sub shell'
(
@ -114,13 +114,12 @@ chown -R "$app" "$final_path"
set -o nounset
ynh_exec_as $app $final_path/venv/bin/pip install --upgrade pip
ynh_exec_as $app $final_path/venv/bin/pip install -r "$final_path/requirements.txt"
ynh_exec_as $app $final_path/venv/bin/pip install --upgrade "$pip_install_string"
)
#=================================================
# copy config files
# ================================================
ynh_script_progression --message="Create django_ynh configuration file..."
ynh_script_progression --message="Create project configuration files..."
gunicorn_conf="$final_path/gunicorn.conf.py"
cp "../conf/gunicorn.conf.py" "$gunicorn_conf"
@ -158,11 +157,10 @@ cp ../conf/wsgi.py "$final_path/wsgi.py"
touch "$final_path/local_settings.py"
#=================================================
# MIGRATE / COLLECTSTATIC / CREATE SUPERUSER
# MIGRATE / COLLECTSTATIC / CREATEADMIN
#=================================================
ynh_script_progression --message="migrate/collectstatic/create superuser..." --weight=10
ynh_script_progression --message="migrate/collectstatic/createadmin..." --weight=10
(
set +o nounset
@ -176,8 +174,8 @@ ynh_script_progression --message="migrate/collectstatic/create superuser..." --w
./manage.py migrate --no-input
./manage.py collectstatic --no-input
# Create/update Django superuser (set unusable password, because auth done via SSOwat):
./manage.py create_superuser --username="$admin" --email="$admin_mail"
# Create/update Django superuser (set unusable password, because auth done via SSOwat):
./manage.py create_superuser --username="$admin" --email="$admin_mail"
# Check the configuration
# This may fail in some cases with errors, etc., but the app works and the user can fix issues later.
@ -206,9 +204,9 @@ yunohost service add $app --description="Web based management to catalog things"
#=================================================
# Set permissions to app files
chown -R "$app" "$log_path"
chown -R "$app" "$public_path"
chown -R "$app" "$final_path"
chown -R "$app:" "$log_path"
chown -R "$app:" "$public_path"
chown -R "$app:" "$final_path"
#=================================================
# SETUP SYSTEMD
@ -216,7 +214,7 @@ chown -R "$app" "$final_path"
ynh_script_progression --message="Configuring a systemd service..."
# https://github.com/YunoHost/yunohost/blob/dev/data/helpers.d/systemd
ynh_add_systemd_config --service="$app" --template="django_ynh.service"
ynh_add_systemd_config --service="$app" --template="django_example_ynh.service"
#=================================================
# SETUP SSOWAT
@ -232,9 +230,9 @@ then
fi
#=================================================
# Start django_ynh via systemd
# Start django_example_ynh via systemd
#=================================================
ynh_script_progression --message="Starting django_ynh's services..." --weight=5
ynh_script_progression --message="Starting PyInventory's services..." --weight=5
ynh_systemd_action --service_name="$app" --action="start"

View file

@ -34,7 +34,7 @@ then
fi
#=================================================
# STOP django_ynh'S SERVICES
# STOP PYINVENTORY'S SERVICES
#=================================================
ynh_script_progression --message="Stopping and removing systemd services..." --weight=5

View file

@ -70,8 +70,8 @@ ynh_system_user_create --username=$app --home_dir="$final_path" --use_shell
#=================================================
# Restore permissions on app files
chown -R "$app" "$public_path"
chown -R "$app" "$final_path"
chown -R "$app:" "$public_path"
chown -R "$app:" "$final_path"
#=================================================
# SPECIFIC RESTORATION
@ -112,13 +112,13 @@ yunohost service add $app --description="Web based management to catalog things"
mkdir -p "$log_path"
touch "${log_file}"
chown -R "$app" "$log_path"
chown -R "$app:" "$log_path"
ynh_restore_file --origin_path="/etc/logrotate.d/$app"
#=================================================
# GENERIC FINALIZATION
#=================================================
# START django_ynh
# START PYINVENTORY
#=================================================
ynh_script_progression --message="Starting a systemd service..." --weight=5

View file

@ -80,7 +80,7 @@ ynh_system_user_create --username="$app" --home_dir="$final_path" --use_shell
#=================================================
ynh_script_progression --message="Configuring a systemd service..."
ynh_add_systemd_config --service="$app" --template="django_ynh.service"
ynh_add_systemd_config --service="$app" --template="django_example_ynh.service"
#=================================================
# UPGRADE VENV
@ -89,7 +89,7 @@ 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"
chown -R "$app:" "$final_path"
#run source in a 'sub shell'
(
@ -98,7 +98,6 @@ chown -R "$app" "$final_path"
set -o nounset
ynh_exec_as $app $final_path/venv/bin/pip install --upgrade pip
ynh_exec_as $app $final_path/venv/bin/pip install -r "$final_path/requirements.txt"
ynh_exec_as $app $final_path/venv/bin/pip install --upgrade "$pip_install_string"
)
#=================================================
@ -150,30 +149,29 @@ cp ../conf/wsgi.py "$final_path/wsgi.py"
touch "$final_path/local_settings.py"
#=================================================
# MIGRATE / COLLECTSTATIC / CREATE SUPERUSER
# MIGRATE PYINVENTORY
#=================================================
ynh_script_progression --message="migrate/collectstatic/create superuser..." --weight=10
ynh_script_progression --message="migrate/collectstatic/createadmin..." --weight=10
(
set +o nounset
source "${final_path}/venv/bin/activate"
set -o nounset
cd "${final_path}"
set +o nounset
source "${final_path}/venv/bin/activate"
set -o nounset
cd "${final_path}"
# Just for debugging:
./manage.py diffsettings
# Just for debugging:
./manage.py diffsettings
./manage.py migrate --no-input
./manage.py collectstatic --no-input
./manage.py migrate --no-input
./manage.py collectstatic --no-input
# Create/update Django superuser (set unusable password, because auth done via SSOwat):
./manage.py create_superuser --username="$admin" --email="$admin_mail"
# Create/update Django superuser (set unusable password, because auth done via SSOwat):
./manage.py create_superuser --username="$admin" --email="$admin_mail"
# Check the configuration
# This may fail in some cases with errors, etc., but the app works and the user can fix issues later.
./manage.py check --deploy || true
# Check the configuration
# This may fail in some cases with errors, etc., but the app works and the user can fix issues later.
./manage.py check --deploy || true
)
#=================================================
@ -198,14 +196,14 @@ yunohost service add $app --description="Web based management to catalog things"
#=================================================
# Set permissions to app files
chown -R "$app" "$log_path"
chown -R "$app" "$public_path"
chown -R "$app" "$final_path"
chown -R "$app:" "$log_path"
chown -R "$app:" "$public_path"
chown -R "$app:" "$final_path"
#=================================================
# Start django_ynh via systemd
# Start django_example_ynh via systemd
#=================================================
ynh_script_progression --message="Starting django_ynh's services..." --weight=5
ynh_script_progression --message="Starting PyInventory's services..." --weight=5
ynh_systemd_action --service_name="$app" --action="start"

View file

@ -1,13 +1,13 @@
from axes.models import AccessAttempt, AccessLog
from bx_py_utils.test_utils.html_assertion import HtmlAssertionMixin
from axes.models import AccessLog
from bx_django_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
from django_yunohost_integration.test_utils import generate_basic_auth
from django_yunohost_integration.views import request_media_debug_view
@override_settings(DEBUG=False)
@ -23,13 +23,22 @@ class DjangoYnhTestCase(HtmlAssertionMixin, TestCase):
assert str(settings.FINAL_HOME_PATH).endswith('/local_test/opt_yunohost')
assert str(settings.FINAL_WWW_PATH).endswith('/local_test/var_www')
assert str(settings.LOG_FILE).endswith('/local_test/var_log_django_ynh.log')
assert str(settings.LOG_FILE).endswith('/local_test/var_log_django_example_ynh.log')
assert settings.ROOT_URLCONF == 'urls'
def test_urls(self):
assert reverse('admin:index') == '/app_path/'
assert reverse(request_media_debug_view) == '/app_path/debug/'
# The django_yunohost_integration debug view should not be avaiable:
with self.assertRaises(NoReverseMatch):
reverse(request_media_debug_view)
# Serve user uploads via django_tools.serve_media_app:
assert settings.MEDIA_URL == '/app_path/media/'
assert reverse('serve_media_app:serve-media', kwargs={'user_token': 'token', 'path': 'foo/bar/'}) == (
'/app_path/media/token/foo/bar/'
)
def test_auth(self):
response = self.client.get('/app_path/')
@ -51,11 +60,15 @@ class DjangoYnhTestCase(HtmlAssertionMixin, TestCase):
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_staff is True # Set by: conf.setup_user.setup_project_user
assert user.is_superuser is False
self.assert_html_parts(
response, parts=('<title>Site administration | Django site admin</title>', '<strong>test</strong>')
response,
parts=(
f'<title>Site administration</title>',
'<strong>test</strong>',
),
)
def test_wrong_auth_user(self):
@ -75,7 +88,7 @@ class DjangoYnhTestCase(HtmlAssertionMixin, TestCase):
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_staff is True # Set by: conf.setup_user.setup_project_user
assert user.is_superuser is False
assert AccessLog.objects.count() == 1
@ -99,7 +112,7 @@ class DjangoYnhTestCase(HtmlAssertionMixin, TestCase):
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_staff is True # Set by: conf.setup_user.setup_project_user
assert user.is_superuser is False
assert AccessLog.objects.count() == 1
@ -122,7 +135,7 @@ class DjangoYnhTestCase(HtmlAssertionMixin, TestCase):
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_staff is True # Set by: conf.setup_user.setup_project_user
assert user.is_superuser is False
assert AccessLog.objects.count() == 1

View file

@ -2,10 +2,8 @@ import shutil
import subprocess
from pathlib import Path
import django_ynh
BASE_PATH = Path(django_ynh.__file__).parent.parent
BASE_PATH = Path(__file__).parent.parent
def test_lint():

View file

@ -3,10 +3,10 @@ import shutil
import subprocess
from pathlib import Path
import django_ynh
import django_yunohost_integration
PACKAGE_ROOT = Path(django_ynh.__file__).parent.parent
PACKAGE_ROOT = Path(__file__).parent.parent
def assert_file_contains_string(file_path, string):
@ -17,29 +17,15 @@ def assert_file_contains_string(file_path, string):
raise AssertionError(f'File {file_path} does not contain {string!r} !')
def test_version(package_root=None, version=None):
if package_root is None:
package_root = PACKAGE_ROOT
def test_version():
version = django_yunohost_integration.__version__
if version is None:
version = django_ynh.__version__
if 'dev' not in version and 'rc' not in version:
version_string = f'v{version}'
assert_file_contains_string(file_path=Path(package_root, 'README.md'), string=version_string)
assert_file_contains_string(file_path=Path(package_root, 'pyproject.toml'), string=f'version = "{version}"')
assert_file_contains_string(file_path=Path(package_root, 'manifest.json'), string=f'"version": "{version}~ynh')
assert_file_contains_string(
file_path=Path(package_root, 'scripts', '_common.sh'), string=f'"django_ynh[ynh]=={version}"'
)
assert_file_contains_string(file_path=Path(PACKAGE_ROOT, 'pyproject.toml'), string=f'version = "{version}~ynh')
assert_file_contains_string(file_path=Path(PACKAGE_ROOT, 'pyproject.toml'), string=f'django_yunohost_integration = "=={version}"')
assert_file_contains_string(file_path=Path(PACKAGE_ROOT, 'manifest.json'), string=f'"version": "{version}~ynh')
def test_poetry_check(package_root=None):
if package_root is None:
package_root = PACKAGE_ROOT
def test_poetry_check():
poerty_bin = shutil.which('poetry')
output = subprocess.check_output(
@ -47,7 +33,7 @@ def test_poetry_check(package_root=None):
universal_newlines=True,
env=os.environ,
stderr=subprocess.STDOUT,
cwd=str(package_root),
cwd=str(PACKAGE_ROOT),
)
print(output)
assert output == 'All set!\n'

View file

@ -1,6 +1,6 @@
from unittest.case import TestCase
from django_ynh.test_utils import generate_basic_auth
from django_yunohost_integration.test_utils import generate_basic_auth
class TestUtilsTestCase(TestCase):