WIP: setup the project

This commit is contained in:
JensDiemer 2020-12-28 12:35:36 +01:00
parent f578f144a3
commit b5f93958ff
24 changed files with 311 additions and 164 deletions

View file

@ -48,6 +48,7 @@ publish: ## Release new version to PyPi
poetry run publish poetry run publish
local-test: check-poetry ## Run local_test.py to run the project locally local-test: check-poetry ## Run local_test.py to run the project locally
poetry install
poetry run ./local_test.py poetry run ./local_test.py
local-diff-settings: ## Run "manage.py diffsettings" with local test local-diff-settings: ## Run "manage.py diffsettings" with local test

View file

@ -86,13 +86,13 @@ drwxr-xr-x 3 root root 3 Dec 8 08:36 ..
-rw-r--r-- 1 django_ynh django_ynh 171 Dec 8 08:39 secret.txt -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 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 115 Dec 8 08:39 wsgi.py
-rw-r--r-- 1 django_ynh django_ynh 4737 Dec 8 08:39 django_ynh.settings.py -rw-r--r-- 1 django_ynh django_ynh 4737 Dec 8 08:39 django_ynh_demo_settings.py
root@yunohost:~# cd /opt/yunohost/django_ynh/ root@yunohost:~# cd /opt/yunohost/django_ynh/
root@yunohost:/opt/yunohost/django_ynh# source venv/bin/activate root@yunohost:/opt/yunohost/django_ynh# source venv/bin/activate
(venv) root@yunohost:/opt/yunohost/django_ynh# ./manage.py check (venv) root@yunohost:/opt/yunohost/django_ynh# ./manage.py check
django_ynh v0.8.2 (Django v2.2.17) django_ynh v0.8.2 (Django v2.2.17)
DJANGO_SETTINGS_MODULE='django_ynh.settings' DJANGO_SETTINGS_MODULE='django_ynh_demo_settings'
PROJECT_PATH:/opt/yunohost/django_ynh/venv/lib/python3.7/site-packages PROJECT_PATH:/opt/yunohost/django_ynh/venv/lib/python3.7/site-packages
BASE_PATH:/opt/yunohost/django_ynh BASE_PATH:/opt/yunohost/django_ynh
System check identified no issues (0 silenced). System check identified no issues (0 silenced).

View file

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

View file

@ -1,18 +1,24 @@
################################################################################ """
################################################################################ **************************************************************************
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.
# Please do not modify this file, it will be reset at the next update. The parameters you add in local_settings.py will overwrite these,
# You can edit the file __FINAL_HOME_PATH__/local_settings.py and add/modify the settings you need. but you can use the options and documentation in this file to find out
# The parameters you add in local_settings.py will overwrite these, what can be done.
# but you can use the options and documentation in this file to find out what can be done. **************************************************************************
################################################################################
################################################################################
Django Settings here depends on YunoHost app settings.
"""
from pathlib import Path as __Path from pathlib import Path as __Path
from django_ynh.base_settings import * # noqa
from django_ynh.secret_key import get_or_create_secret as __get_or_create_secret
DEBUG = True # This is only the DEMO app ;) But should never be on in production!
DEBUG = False
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
@ -28,35 +34,15 @@ 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
PATH_URL = PATH_URL.strip('/') PATH_URL = PATH_URL.strip('/')
# -----------------------------------------------------------------------------
ROOT_URLCONF = 'django_ynh.urls' # /opt/yunohost/django_ynh/urls.py
# -----------------------------------------------------------------------------
# Keep ModelBackend around for per-user permissions and superuser
AUTHENTICATION_BACKENDS = (
'axes.backends.AxesBackend', # AxesBackend should be the first backend!
# Authenticate via SSO and nginx 'HTTP_REMOTE_USER' header:
'django_ynh.sso_auth.auth_backend.SSOwatUserBackend',
# Fallback to normal Django model backend:
'django.contrib.auth.backends.ModelBackend',
)
LOGIN_REDIRECT_URL = None
LOGIN_URL = '/yunohost/sso/'
LOGOUT_REDIRECT_URL = '/yunohost/sso/'
# /yunohost/sso/?action=logout
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
ADMINS = ( ROOT_URLCONF = 'django_ynh_demo_urls'
('__ADMIN__', '__ADMINMAIL__'),
) SECRET_KEY = __get_or_create_secret(FINAL_HOME_PATH / 'secret.txt') # /opt/yunohost/$app/secret.txt
ADMINS = (('__ADMIN__', '__ADMINMAIL__'),)
MANAGERS = ADMINS MANAGERS = ADMINS
@ -92,6 +78,7 @@ 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 = {
@ -108,6 +95,7 @@ CACHES = {
}, },
} }
# _____________________________________________________________________________ # _____________________________________________________________________________
# Static files (CSS, JavaScript, Images) # Static files (CSS, JavaScript, Images)
@ -122,18 +110,10 @@ else:
STATIC_ROOT = str(FINAL_WWW_PATH / 'static') STATIC_ROOT = str(FINAL_WWW_PATH / 'static')
MEDIA_ROOT = str(FINAL_WWW_PATH / 'media') MEDIA_ROOT = str(FINAL_WWW_PATH / 'media')
# _____________________________________________________________________________
# django-ckeditor
CKEDITOR_BASEPATH = STATIC_URL + 'ckeditor/ckeditor/'
# _____________________________________________________________________________
# Django-dbbackup
DBBACKUP_STORAGE_OPTIONS['location'] = str(FINAL_HOME_PATH / 'backups')
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
LOGGING = { LOGGING = {
'version': 1, 'version': 1,
'disable_existing_loggers': True, 'disable_existing_loggers': True,
@ -162,7 +142,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},
'inventory': {'handlers': ['log_file', 'mail_admins'], 'level': 'INFO', 'propagate': False}, 'django_ynh': {'handlers': ['log_file', 'mail_admins'], 'level': 'INFO', 'propagate': False},
}, },
} }

View file

@ -3,7 +3,7 @@ from django.conf.urls import static
from django.contrib import admin from django.contrib import admin
from django.urls import path from django.urls import path
from django_ynh.views.debug import request_media_debug_view from django_ynh.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

@ -5,7 +5,7 @@ import sys
def main(): def main():
os.environ['DJANGO_SETTINGS_MODULE'] = 'django_ynh.settings' os.environ['DJANGO_SETTINGS_MODULE'] = 'django_ynh_demo_settings'
from django.core.management import execute_from_command_line from django.core.management import execute_from_command_line
execute_from_command_line(sys.argv) execute_from_command_line(sys.argv)

View file

@ -4,7 +4,7 @@
import os import os
os.environ['DJANGO_SETTINGS_MODULE'] = 'django_ynh.settings' os.environ['DJANGO_SETTINGS_MODULE'] = 'django_ynh_demo_settings'
from django.core.wsgi import get_wsgi_application from django.core.wsgi import get_wsgi_application

100
django_ynh/base_settings.py Normal file
View file

@ -0,0 +1,100 @@
"""
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:
ROOT_URLCONF = None
SECRET_KEY = None
# -----------------------------------------------------------------------------
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
]
# -----------------------------------------------------------------------------
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,18 +1,34 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
"""
Can be called e.g.:
poetry run create_superuser --ds="foo.settings" --username="bar" \
--email="foo@bar.tld" --password="no-password"
or, e.g.:
python3 -m django_ynh.create_superuser --ds="foo.settings" --username="bar" \
--email="foo@bar.tld" \
--password="no-password"
"""
import argparse import argparse
import os import os
import sys import sys
def main(): def main():
os.environ['DJANGO_SETTINGS_MODULE'] = 'django_ynh.settings'
parser = argparse.ArgumentParser(description='Create or update Django super user.') parser = argparse.ArgumentParser(description='Create or update Django super user.')
parser.add_argument('--ds', help='The "DJANGO_SETTINGS_MODULE" string')
parser.add_argument('--username') parser.add_argument('--username')
parser.add_argument('--email') parser.add_argument('--email')
parser.add_argument('--password') parser.add_argument('--password')
args = parser.parse_args() args = parser.parse_args()
os.environ['DJANGO_SETTINGS_MODULE'] = args.ds
username = args.username username = args.username
email = args.email or '' email = args.email or ''
password = args.password password = args.password
@ -26,7 +42,8 @@ def main():
User = get_user_model() User = get_user_model()
user = User.objects.filter(username=username).first() user = User.objects.filter(username=username).first()
if user: if user:
print('Update existing user and set his password.', file=sys.stderr) print(f'Update existing user "{user}" and set his password.', file=sys.stderr)
print(repr(password))
user.is_active = True user.is_active = True
user.is_staff = True user.is_staff = True
user.is_superuser = True user.is_superuser = True
@ -34,7 +51,7 @@ def main():
user.email = email user.email = email
user.save() user.save()
else: else:
print('Create new super user', file=sys.stderr) print(f'Create new super user "{username}"', file=sys.stderr)
User.objects.create_superuser(username=username, email=email, password=password) User.objects.create_superuser(username=username, email=email, password=password)

23
django_ynh/secret_key.py Normal file
View file

@ -0,0 +1,23 @@
"""
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

@ -27,6 +27,7 @@ import logging
from django.contrib.auth.backends import RemoteUserBackend from django.contrib.auth.backends import RemoteUserBackend
from django_ynh.sso_auth.signals import setup_user
from django_ynh.sso_auth.user_profile import update_user_profile from django_ynh.sso_auth.user_profile import update_user_profile
@ -46,21 +47,13 @@ class SSOwatUserBackend(RemoteUserBackend):
def configure_user(self, request, user): def configure_user(self, request, user):
""" """
Configure a user after creation and return the updated user. Configure a new user after creation and return the updated user.
Setup a normal, non-superuser
""" """
logger.warning('Configure user %s', user) logger.warning('Configure user %s', user)
user.set_unusable_password() # Always login via SSO user = update_user_profile(request)
user.is_staff = True
user.is_superuser = False
user.save()
# TODO: Add user in "normal" user group: setup_user.send(sender=self.__class__, user=user)
# django_ynh_user_group = get_or_create_normal_user_group()[0]
# user.groups.set([django_ynh_user_group])
update_user_profile(request)
return user return user

View file

@ -2,15 +2,19 @@ import base64
import logging import logging
from axes.exceptions import AxesBackendPermissionDenied from axes.exceptions import AxesBackendPermissionDenied
from django.conf import settings
from django.contrib.auth.middleware import RemoteUserMiddleware from django.contrib.auth.middleware import RemoteUserMiddleware
from django_ynh.sso_auth.signals import setup_user
from django_ynh.sso_auth.user_profile import update_user_profile
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class SSOwatRemoteUserMiddleware(RemoteUserMiddleware): class SSOwatRemoteUserMiddleware(RemoteUserMiddleware):
""" """
Middleware to login a user HTTP_REMOTE_USER header. Middleware to login a user via HTTP_REMOTE_USER header.
Use Django Axes if something is wrong. Use Django Axes if something is wrong.
Update exising user informations. Update exising user informations.
""" """
@ -34,13 +38,17 @@ class SSOwatRemoteUserMiddleware(RemoteUserMiddleware):
except KeyError: except KeyError:
logger.error('SSOwAuthUser cookie missing!') logger.error('SSOwAuthUser cookie missing!')
# emits a signal indicating user login failed, which is processed by if settings.DEBUG:
# axes.signals.log_user_login_failed which logs and flags the failed request. # e.g.: local test can't set a Cookie easily
raise AxesBackendPermissionDenied('Cookie missing') logger.warning('Ignore error, because settings.DEBUG is on!')
else:
logger.info('SSOwat username from cookies: %r', username) # emits a signal indicating user login failed, which is processed by
if username != request.user.username: # axes.signals.log_user_login_failed which logs and flags the failed request.
raise AxesBackendPermissionDenied('Wrong username') raise AxesBackendPermissionDenied('Cookie missing')
else:
logger.info('SSOwat username from cookies: %r', username)
if username != request.user.username:
raise AxesBackendPermissionDenied('Wrong username')
# Compare with HTTP_AUTH_USER # Compare with HTTP_AUTH_USER
try: try:
@ -72,4 +80,5 @@ class SSOwatRemoteUserMiddleware(RemoteUserMiddleware):
if not was_authenticated: if not was_authenticated:
# First request, after login -> update user informations # First request, after login -> update user informations
logger.info('Remote used was logged in') logger.info('Remote used was logged in')
update_user_profile(request) user = update_user_profile(request)
setup_user.send(sender=self.__class__, user=user)

View file

@ -0,0 +1,12 @@
"""
"setup_user" called via:
* SSOwatUserBackend after a new user was created
* SSOwatRemoteUserMiddleware on login request
"""
import django.dispatch
setup_user = django.dispatch.Signal(providing_args=['user'])

View file

@ -1,11 +1,6 @@
import base64
import logging import logging
from axes.exceptions import AxesBackendPermissionDenied
from django.contrib.auth.backends import RemoteUserBackend as OriginRemoteUserBackend
from django.contrib.auth.middleware import RemoteUserMiddleware as OriginRemoteUserMiddleware
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from inventory.permissions import get_or_create_normal_user_group
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -16,13 +11,17 @@ def update_user_profile(request):
Update existing user information: Update existing user information:
* Email * Email
* First / Last name * First / Last name
Called via:
* SSOwatUserBackend after a new user was created
* SSOwatRemoteUserMiddleware on login request
""" """
user = request.user user = request.user
assert user.is_authenticated assert user.is_authenticated
update_fields = [] update_fields = []
if not user.password: if not user.has_usable_password():
# Empty password is not valid, so we can't save the model, because of full_clean() call # 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) logger.info('Set unusable password for user: %s', user)
user.set_unusable_password() user.set_unusable_password()
@ -59,3 +58,5 @@ def update_user_profile(request):
logger.exception('Can not update user: %s', user) logger.exception('Can not update user: %s', user)
else: else:
user.save(update_fields=update_fields) user.save(update_fields=update_fields)
return user

View file

@ -0,0 +1,7 @@
from django.test.testcases import TestCase
class YnhTestCase(TestCase):
"""
"""

View file

View file

@ -1,12 +1,21 @@
import logging
import pprint import pprint
from django.http import HttpResponse from django.contrib.auth import get_user_model
from django.http.response import HttpResponse
from django.shortcuts import redirect from django.shortcuts import redirect
logger = logging.getLogger(__name__)
def request_media_debug_view(request): def request_media_debug_view(request):
""" debug request.META """ """ debug request.META """
if not request.user.is_authenticated: 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') return redirect('admin:index')
meta = pprint.pformat(request.META) meta = pprint.pformat(request.META)

View file

@ -22,34 +22,11 @@ INSTALLED_APPS = [
'django_ynh', # <<<< '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',
'django_ynh.sso_auth.auth_middleware.SSOwatRemoteUserMiddleware', # <<<<
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
ROOT_URLCONF = 'django_ynh_tests.test_project.urls' ROOT_URLCONF = 'django_ynh_tests.test_project.urls'
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',
],
},
},
]
WSGI_APPLICATION = 'django_ynh_tests.test_project.wsgi.application' WSGI_APPLICATION = 'django_ynh_tests.test_project.wsgi.application'
@ -84,21 +61,4 @@ INTERNAL_IPS = [
'127.0.0.1', '127.0.0.1',
] ]
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

@ -0,0 +1,26 @@
import logging
from django.dispatch import receiver
from django_ynh.sso_auth.signals import setup_user
logger = logging.getLogger(__name__)
@receiver(setup_user)
def setup_user_handler(sender, **kwargs):
"""
Make user to a "staff" user, so he can use the Django admin.
This Signal is called via:
* SSOwatUserBackend after a new user was created
* SSOwatRemoteUserMiddleware on login request
"""
user = kwargs['user']
logger.info('Receive "setup_user" signal for user: "%s"', user)
if not user.is_staff:
user.is_staff = True
user.save(update_fields=['is_staff'])
logger.info('Make user %s to a staff user', user)

View file

@ -2,11 +2,13 @@
""" """
Start django_ynh in YunoHost setup locally. Start django_ynh in YunoHost setup locally.
Note:
You can only run this script, if you are in a activated django_ynh venv! Run via:
make local-test
see README for details ;) see README for details ;)
""" """
import base64
import os import os
import shlex import shlex
import subprocess import subprocess
@ -14,15 +16,12 @@ import sys
from pathlib import Path from pathlib import Path
os.environ['DJANGO_SETTINGS_MODULE'] = 'django_ynh.settings' os.environ['DJANGO_SETTINGS_MODULE'] = 'django_ynh_demo_settings'
try: try:
import inventory_project # noqa import django_ynh # noqa
except ImportError as err: except ImportError as err:
raise ImportError( raise ImportError('Couldn\'t import django_ynh. Did you ' 'forget to activate a virtual environment?') from err
'Couldn\'t import django_ynh. Did you '
'forget to activate a virtual environment?'
) from err
BASE_PATH = Path(__file__).parent.absolute() BASE_PATH = Path(__file__).parent.absolute()
@ -34,25 +33,19 @@ FINAL_WWW_PATH = TEST_PATH / 'var_www'
LOG_FILE = TEST_PATH / 'var_log_django_ynh.log' LOG_FILE = TEST_PATH / 'var_log_django_ynh.log'
MANAGE_PY_FILE = CONF_PATH / 'manage.py' MANAGE_PY_FILE = CONF_PATH / 'manage.py'
CREATE_SUPERUSER_FILE = CONF_PATH / 'create_superuser.py' SETTINGS_FILE = CONF_PATH / 'django_ynh_demo_settings.py'
SETTINGS_FILE = CONF_PATH / 'django_ynh.settings.py' URLS_FILE = CONF_PATH / 'django_ynh_demo_urls.py'
URLS_FILE = CONF_PATH / 'ynh_urls.py'
REPLACES = { REPLACES = {
'__FINAL_HOME_PATH__': str(FINAL_HOME_PATH), '__FINAL_HOME_PATH__': str(FINAL_HOME_PATH),
'__FINAL_WWW_PATH__': str(FINAL_WWW_PATH), '__FINAL_WWW_PATH__': str(FINAL_WWW_PATH),
'__LOG_FILE__': str(TEST_PATH / 'var_log_django_ynh.log'), '__LOG_FILE__': str(TEST_PATH / 'var_log_django_ynh.log'),
'__PATH_URL__': 'app_path', '__PATH_URL__': 'app_path',
'__DOMAIN__': '127.0.0.1', '__DOMAIN__': '127.0.0.1',
'django.db.backends.postgresql': 'django.db.backends.sqlite3', 'django.db.backends.postgresql': 'django.db.backends.sqlite3',
"'NAME': '__APP__',": f"'NAME': '{TEST_PATH / 'test_db.sqlite'}',", "'NAME': '__APP__',": f"'NAME': '{TEST_PATH / 'test_db.sqlite'}',",
'django_redis.cache.RedisCache': 'django.core.cache.backends.dummy.DummyCache', 'django_redis.cache.RedisCache': 'django.core.cache.backends.dummy.DummyCache',
'DEBUG = False': 'DEBUG = True',
# Just use the default logging setup from django_ynh project: # Just use the default logging setup from django_ynh project:
'LOGGING = {': 'HACKED_DEACTIVATED_LOGGING = {', 'LOGGING = {': 'HACKED_DEACTIVATED_LOGGING = {',
} }
@ -72,12 +65,7 @@ def verbose_check_call(command, verbose=True, **kwargs):
env['PYTHONUNBUFFERED'] = '1' env['PYTHONUNBUFFERED'] = '1'
popenargs = shlex.split(command) popenargs = shlex.split(command)
subprocess.check_call( subprocess.check_call(popenargs, universal_newlines=True, env=env, **kwargs)
popenargs,
universal_newlines=True,
env=env,
**kwargs
)
def call_manage_py(args): def call_manage_py(args):
@ -102,13 +90,21 @@ def copy_patch(src_file, replaces=None):
f.write(content) f.write(content)
def assert_is_dir(dir_path):
assert dir_path.is_dir, f'Directory does not exists: {dir_path}'
def assert_is_file(file_path):
assert file_path.is_file, f'File not found: {file_path}'
def main(): def main():
print('-' * 100) print('-' * 100)
assert BASE_PATH.is_dir() assert_is_dir(BASE_PATH)
assert CONF_PATH.is_dir() assert_is_dir(CONF_PATH)
assert SETTINGS_FILE.is_file() assert_is_file(SETTINGS_FILE)
assert URLS_FILE.is_file() assert_is_file(URLS_FILE)
for p in (TEST_PATH, FINAL_HOME_PATH, FINAL_WWW_PATH): for p in (TEST_PATH, FINAL_HOME_PATH, FINAL_WWW_PATH):
if p.is_dir(): if p.is_dir():
@ -122,10 +118,7 @@ def main():
# conf/manage.py -> local_test/manage.py # conf/manage.py -> local_test/manage.py
copy_patch(src_file=MANAGE_PY_FILE) copy_patch(src_file=MANAGE_PY_FILE)
# conf/create_superuser.py -> local_test/opt_yunohost/create_superuser.py # conf/django_ynh_demo_settings.py -> local_test/django_ynh_demo_settings.py
copy_patch(src_file=CREATE_SUPERUSER_FILE)
# conf/django_ynh.settings.py -> local_test/django_ynh.settings.py
copy_patch(src_file=SETTINGS_FILE, replaces=REPLACES) copy_patch(src_file=SETTINGS_FILE, replaces=REPLACES)
# conf/ynh_urls.py -> local_test/ynh_urls.py # conf/ynh_urls.py -> local_test/ynh_urls.py
@ -133,7 +126,8 @@ def main():
with Path(FINAL_HOME_PATH / 'local_settings.py').open('w') as f: with Path(FINAL_HOME_PATH / 'local_settings.py').open('w') as f:
f.write('# Only for local test run\n') f.write('# Only for local test run\n')
f.write('SERVE_FILES=True # used in src/inventory_project/urls.py\n') f.write('SERVE_FILES = True # used in src/inventory_project/urls.py\n')
f.write('AUTH_PASSWORD_VALIDATORS = [] # accept all passwords\n')
# call "local_test/manage.py" via subprocess: # call "local_test/manage.py" via subprocess:
call_manage_py('check --deploy') call_manage_py('check --deploy')
@ -141,10 +135,24 @@ def main():
call_manage_py('collectstatic --no-input') call_manage_py('collectstatic --no-input')
verbose_check_call( verbose_check_call(
command=f'{sys.executable} create_superuser.py --username="test" --password="test"', command=(
f'{sys.executable} -m django_ynh.create_superuser'
' --ds="django_ynh_demo_settings" --username="test" --password="test"'
),
cwd=FINAL_HOME_PATH, cwd=FINAL_HOME_PATH,
) )
# 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'
creds = str(base64.b64encode(b'test:test'), encoding='utf-8')
basic_auth = f'basic {creds}'
os.environ['HTTP_AUTHORIZATION'] = basic_auth
try: try:
call_manage_py('runserver --nostatic') call_manage_py('runserver --nostatic')
except KeyboardInterrupt: except KeyboardInterrupt:

View file

@ -36,6 +36,7 @@ requires = ["poetry-core>=1.0.0"]
build-backend = "poetry.core.masonry.api" build-backend = "poetry.core.masonry.api"
[tool.poetry.scripts] [tool.poetry.scripts]
create_superuser = "django_ynh.create_superuser:main"
publish = "django_ynh_tests.test_project.publish:publish" publish = "django_ynh_tests.test_project.publish:publish"
[tool.isort] [tool.isort]

View file

@ -116,10 +116,10 @@ fi
ynh_script_progression --message="Modify django_ynh's config file..." ynh_script_progression --message="Modify django_ynh's config file..."
# save old settings file # save old settings file
settings="$final_path/django_ynh.settings.py" settings="$final_path/settings.py"
ynh_backup_if_checksum_is_different --file="$settings" ynh_backup_if_checksum_is_different --file="$settings"
cp "../conf/django_ynh.settings.py" "$settings" cp "../conf/settings.py" "$settings"
ynh_replace_string --match_string="__APP__" --replace_string="$app" --target_file="$settings" ynh_replace_string --match_string="__APP__" --replace_string="$app" --target_file="$settings"
ynh_replace_string --match_string="__DB_PWD__" --replace_string="$db_pwd" --target_file="$settings" ynh_replace_string --match_string="__DB_PWD__" --replace_string="$db_pwd" --target_file="$settings"

View file

@ -137,8 +137,8 @@ chmod +x "$final_path/manage.py"
cp ../conf/wsgi.py "$final_path/wsgi.py" cp ../conf/wsgi.py "$final_path/wsgi.py"
settings="$final_path/django_ynh.settings.py" settings="$final_path/settings.py"
cp "../conf/django_ynh.settings.py" "$settings" cp "../conf/settings.py" "$settings"
ynh_replace_string --match_string="__APP__" --replace_string="$app" --target_file="$settings" ynh_replace_string --match_string="__APP__" --replace_string="$app" --target_file="$settings"
ynh_replace_string --match_string="__DB_PWD__" --replace_string="$db_pwd" --target_file="$settings" ynh_replace_string --match_string="__DB_PWD__" --replace_string="$db_pwd" --target_file="$settings"

View file

@ -127,10 +127,10 @@ ynh_backup_if_checksum_is_different --file="$final_path/wsgi.py"
cp ../conf/wsgi.py "$final_path/wsgi.py" cp ../conf/wsgi.py "$final_path/wsgi.py"
# save old settings file # save old settings file
settings="$final_path/django_ynh.settings.py" settings="$final_path/settings.py"
ynh_backup_if_checksum_is_different --file="$settings" ynh_backup_if_checksum_is_different --file="$settings"
cp "../conf/django_ynh.settings.py" "$settings" cp "../conf/settings.py" "$settings"
ynh_replace_string --match_string="__APP__" --replace_string="$app" --target_file="$settings" ynh_replace_string --match_string="__APP__" --replace_string="$app" --target_file="$settings"
ynh_replace_string --match_string="__DB_PWD__" --replace_string="$db_pwd" --target_file="$settings" ynh_replace_string --match_string="__DB_PWD__" --replace_string="$db_pwd" --target_file="$settings"