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 # 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
View file

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

View file

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

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. [![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 ;)
## 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 ## 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 it's possible to run the Django developer server with the settings
and urls made for YunoHost installation. 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_example_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 +32,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:
@ -121,36 +46,40 @@ Notes:
## history ## 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 * 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 * 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_example_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) * 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_ynh/compare/v0.1.2...v0.1.3) * [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) * set "DEBUG = True" in local_test (so static files are served and auth works)
* Bugfixes and cleanups * 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 * Bugfixes
* [v0.1.1 - 29.12.2020](https://github.com/YunoHost-Apps/django_ynh/compare/v0.1.0...v0.1.1) * [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_ynh" in `INSTALLED_APPS` * 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 * Generate "conf/requirements.txt" and use this file for install
* rename own settings and urls (in `/conf/`) * 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 * 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 * init the project
## Links ## 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/ * YunoHost website: https://yunohost.org/
* PyPi package: https://pypi.org/project/django-ynh/ * 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 * https://github.com/YunoHost-Apps/django-for-runners_ynh
--- ---
@ -159,73 +88,73 @@ These projects used `django_ynh`:
## package installation / debugging ## 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.: Try 'main' branch, e.g.:
```bash ```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 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.: Try 'testing' branch, e.g.:
```bash ```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 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.: To remove call e.g.:
```bash ```bash
sudo yunohost app remove django_ynh sudo yunohost app remove django_example_ynh
``` ```
Backup / remove / restore cycle, e.g.: Backup / remove / restore cycle, e.g.:
```bash ```bash
yunohost backup create --apps django_ynh yunohost backup create --apps django_example_ynh
yunohost backup list yunohost backup list
archives: archives:
- django_ynh-pre-upgrade1 - django_example_ynh-pre-upgrade1
- 20201223-163434 - 20201223-163434
yunohost app remove django_ynh yunohost app remove django_example_ynh
yunohost backup restore 20201223-163434 --apps django_ynh yunohost backup restore 20201223-163434 --apps django_example_ynh
``` ```
Debug installation, e.g.: Debug installation, e.g.:
```bash ```bash
root@yunohost:~# ls -la /var/www/django_ynh/ root@yunohost:~# ls -la /var/www/django_example_ynh/
total 18 total 18
drwxr-xr-x 4 root root 4 Dec 8 08:36 . 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 6 root root 6 Dec 8 08:36 ..
drwxr-xr-x 2 root root 2 Dec 8 08:36 media drwxr-xr-x 2 root root 2 Dec 8 08:36 media
drwxr-xr-x 7 root root 8 Dec 8 08:40 static 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 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 .. 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_example_ynh django_example_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 -rw-r--r-- 1 django_example_ynh django_example_ynh 0 Dec 8 08:39 local_settings.py
-rwxr-xr-x 1 django_ynh django_ynh 274 Dec 8 08:39 manage.py -rwxr-xr-x 1 django_example_ynh django_example_ynh 274 Dec 8 08:39 manage.py
-rw-r--r-- 1 django_ynh django_ynh 171 Dec 8 08:39 secret.txt -rw-r--r-- 1 django_example_ynh django_example_ynh 171 Dec 8 08:39 secret.txt
drwxr-xr-x 6 django_ynh django_ynh 6 Dec 8 08:37 venv drwxr-xr-x 6 django_example_ynh django_example_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_example_ynh django_example_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 4737 Dec 8 08:39 django_example_ynh_demo_settings.py
root@yunohost:~# cd /opt/yunohost/django_ynh/ root@yunohost:~# cd /opt/yunohost/django_example_ynh/
root@yunohost:/opt/yunohost/django_ynh# source venv/bin/activate root@yunohost:/opt/yunohost/django_example_ynh# source venv/bin/activate
(venv) root@yunohost:/opt/yunohost/django_ynh# ./manage.py check (venv) root@yunohost:/opt/yunohost/django_example_ynh# ./manage.py check
django_ynh v0.8.2 (Django v2.2.17) django_example_ynh v0.8.2 (Django v2.2.17)
DJANGO_SETTINGS_MODULE='django_ynh_demo_settings' DJANGO_SETTINGS_MODULE='django_example_ynh_demo_settings'
PROJECT_PATH:/opt/yunohost/django_ynh/venv/lib/python3.7/site-packages PROJECT_PATH:/opt/yunohost/django_example_ynh/venv/lib/python3.7/site-packages
BASE_PATH:/opt/yunohost/django_ynh BASE_PATH:/opt/yunohost/django_example_ynh
System check identified no issues (0 silenced). System check identified no issues (0 silenced).
root@yunohost:~# tail -f /var/log/django_ynh/django_ynh.log root@yunohost:~# tail -f /var/log/django_example_ynh/django_example_ynh.log
root@yunohost:~# cat /etc/systemd/system/django_ynh.service root@yunohost:~# cat /etc/systemd/system/django_example_ynh.service
root@yunohost:~# systemctl reload-or-restart django_ynh root@yunohost:~# systemctl reload-or-restart django_example_ynh
root@yunohost:~# journalctl --unit=django_ynh --follow root@yunohost:~# journalctl --unit=django_example_ynh --follow
``` ```

View file

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

View file

@ -8,6 +8,9 @@ location __PATH__/static/ {
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;

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

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, # 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_yunohost_integration.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 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 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
@ -37,10 +34,15 @@ PATH_URL = PATH_URL.strip('/')
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
# 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('<insert-your-app-here>')
# -----------------------------------------------------------------------------
ADMINS = (('__ADMIN__', '__ADMINMAIL__'),) ADMINS = (('__ADMIN__', '__ADMINMAIL__'),)
MANAGERS = ADMINS MANAGERS = ADMINS
@ -77,14 +79,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 +95,6 @@ CACHES = {
}, },
} }
# _____________________________________________________________________________ # _____________________________________________________________________________
# Static files (CSS, JavaScript, Images) # Static files (CSS, JavaScript, Images)
@ -112,7 +112,6 @@ MEDIA_ROOT = str(FINAL_WWW_PATH / 'media')
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
LOGGING = { LOGGING = {
'version': 1, 'version': 1,
'disable_existing_loggers': True, 'disable_existing_loggers': True,
@ -141,7 +140,7 @@ LOGGING = {
'django': {'handlers': ['log_file', 'mail_admins'], 'level': 'INFO', 'propagate': False}, 'django': {'handlers': ['log_file', 'mail_admins'], 'level': 'INFO', 'propagate': False},
'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_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.is_staff = True
user.save() user.save()

View file

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

View file

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

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: try:
from django_ynh.local_test import create_local_test from django_yunohost_integration.local_test import create_local_test
except ImportError as err: except ImportError as err:
raise ImportError('Did you forget to activate a virtual environment?') from err raise ImportError('Did you forget to activate a virtual environment?') from err

View file

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

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] [tool.poetry]
name = "django_ynh" name = "django_example_ynh"
version = "0.1.5" version = "v0.2.0"
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_example_ynh"
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 bx_py_utils = "*" # https://github.com/boxine/bx_py_utils
poetry-publish = "*" # https://github.com/jedie/poetry-publish bx_django_utils = "*" # https://github.com/boxine/bx_django_utils
bx_py_utils = "*"
tox = "*" tox = "*"
pytest = "*" pytest = "*"
pytest-cov = "*" pytest-cov = "*"
@ -35,23 +24,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 +47,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 +82,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
""" """

View file

@ -6,7 +6,7 @@ from pathlib import Path
try: try:
from django_ynh.pytest_helper import run_pytest from django_yunohost_integration.pytest_helper import run_pytest
except ImportError as err: except ImportError as err:
raise ImportError('Did you forget to activate a virtual environment?') from 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 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
#================================================= #=================================================
# 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 libpq-dev postgresql postgresql-contrib"
# To install/upgrade this project via pip:
pip_install_string="django_ynh[ynh]==0.1.5"
#================================================= #=================================================
# Redis HELPERS # Redis HELPERS

View file

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

View file

@ -105,7 +105,7 @@ ynh_script_progression --message="Install project via pip..." --weight=80
python3 -m venv "${final_path}/venv" python3 -m venv "${final_path}/venv"
cp ../conf/requirements.txt "$final_path/requirements.txt" cp ../conf/requirements.txt "$final_path/requirements.txt"
chown -R "$app" "$final_path" chown -R "$app:" "$final_path"
#run source in a 'sub shell' #run source in a 'sub shell'
( (
@ -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
@ -206,9 +204,9 @@ yunohost service add $app --description="Web based management to catalog things"
#================================================= #=================================================
# Set permissions to app files # Set permissions to app files
chown -R "$app" "$log_path" chown -R "$app:" "$log_path"
chown -R "$app" "$public_path" chown -R "$app:" "$public_path"
chown -R "$app" "$final_path" chown -R "$app:" "$final_path"
#================================================= #=================================================
# SETUP SYSTEMD # SETUP SYSTEMD
@ -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"

View file

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

View file

@ -70,8 +70,8 @@ ynh_system_user_create --username=$app --home_dir="$final_path" --use_shell
#================================================= #=================================================
# Restore permissions on app files # Restore permissions on app files
chown -R "$app" "$public_path" chown -R "$app:" "$public_path"
chown -R "$app" "$final_path" chown -R "$app:" "$final_path"
#================================================= #=================================================
# SPECIFIC RESTORATION # SPECIFIC RESTORATION
@ -112,13 +112,13 @@ yunohost service add $app --description="Web based management to catalog things"
mkdir -p "$log_path" mkdir -p "$log_path"
touch "${log_file}" touch "${log_file}"
chown -R "$app" "$log_path" chown -R "$app:" "$log_path"
ynh_restore_file --origin_path="/etc/logrotate.d/$app" 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

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_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
@ -89,7 +89,7 @@ ynh_script_progression --message="Upgrade project via pip..." --weight=80
python3 -m venv "${final_path}/venv" python3 -m venv "${final_path}/venv"
cp ../conf/requirements.txt "$final_path/requirements.txt" cp ../conf/requirements.txt "$final_path/requirements.txt"
chown -R "$app" "$final_path" chown -R "$app:" "$final_path"
#run source in a 'sub shell' #run source in a 'sub shell'
( (
@ -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,11 +149,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 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
@ -198,14 +196,14 @@ yunohost service add $app --description="Web based management to catalog things"
#================================================= #=================================================
# Set permissions to app files # Set permissions to app files
chown -R "$app" "$log_path" chown -R "$app:" "$log_path"
chown -R "$app" "$public_path" 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"

View file

@ -1,13 +1,13 @@
from axes.models import AccessAttempt, AccessLog from axes.models import AccessLog
from bx_py_utils.test_utils.html_assertion import HtmlAssertionMixin from bx_django_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_yunohost_integration.test_utils import generate_basic_auth
from django_ynh.test_utils import generate_basic_auth from django_yunohost_integration.views import request_media_debug_view
from django_ynh.views import request_media_debug_view
@override_settings(DEBUG=False) @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_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_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): def test_auth(self):
response = self.client.get('/app_path/') response = self.client.get('/app_path/')
@ -51,11 +60,15 @@ class DjangoYnhTestCase(HtmlAssertionMixin, TestCase):
user = User.objects.first() user = User.objects.first()
assert user.username == 'test' assert user.username == 'test'
assert user.is_active is True 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 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</title>',
'<strong>test</strong>',
),
) )
def test_wrong_auth_user(self): def test_wrong_auth_user(self):
@ -75,7 +88,7 @@ class DjangoYnhTestCase(HtmlAssertionMixin, TestCase):
user = User.objects.first() user = User.objects.first()
assert user.username == 'test' assert user.username == 'test'
assert user.is_active is True 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 user.is_superuser is False
assert AccessLog.objects.count() == 1 assert AccessLog.objects.count() == 1
@ -99,7 +112,7 @@ class DjangoYnhTestCase(HtmlAssertionMixin, TestCase):
user = User.objects.first() user = User.objects.first()
assert user.username == 'test' assert user.username == 'test'
assert user.is_active is True 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 user.is_superuser is False
assert AccessLog.objects.count() == 1 assert AccessLog.objects.count() == 1
@ -122,7 +135,7 @@ class DjangoYnhTestCase(HtmlAssertionMixin, TestCase):
user = User.objects.first() user = User.objects.first()
assert user.username == 'test' assert user.username == 'test'
assert user.is_active is True 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 user.is_superuser is False
assert AccessLog.objects.count() == 1 assert AccessLog.objects.count() == 1

View file

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

View file

@ -3,10 +3,10 @@ import shutil
import subprocess import subprocess
from pathlib import Path 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): 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 = django_yunohost_integration.__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_yunohost_integration = "=={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'

View file

@ -1,6 +1,6 @@
from unittest.case import TestCase 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): class TestUtilsTestCase(TestCase):