From 882a811389f7d26c9082f0fc3161a30edf1066a4 Mon Sep 17 00:00:00 2001 From: JensDiemer Date: Mon, 15 Aug 2022 16:56:45 +0200 Subject: [PATCH 1/3] update project setup inspired from: https://github.com/YunoHost-Apps/django_example_ynh --- conf/gunicorn.conf.py | 2 +- conf/manage.py | 2 +- conf/nginx.conf | 10 +--- conf/requirements.txt | 18 +++--- conf/settings.py | 93 ++++++++++++++++++++++--------- conf/systemd.service | 17 ++++++ conf/urls.py | 8 --- local_test.py | 6 ++ poetry.lock | 94 +++++++++++++++---------------- pyproject.toml | 4 +- run_pytest.py | 25 --------- scripts/_common.sh | 21 +++++-- scripts/change_url | 35 +++++------- scripts/install | 97 ++++++++++++++++---------------- scripts/restore | 14 +++-- scripts/upgrade | 105 +++++++++++++++-------------------- tests/conftest.py | 43 ++++++++++++++ tests/test_django_project.py | 15 ++++- tests/test_project_setup.py | 2 +- 19 files changed, 340 insertions(+), 271 deletions(-) create mode 100644 conf/systemd.service delete mode 100644 run_pytest.py create mode 100644 tests/conftest.py diff --git a/conf/gunicorn.conf.py b/conf/gunicorn.conf.py index bcab967..f49a397 100644 --- a/conf/gunicorn.conf.py +++ b/conf/gunicorn.conf.py @@ -17,4 +17,4 @@ accesslog = '__LOG_FILE__' errorlog = '__LOG_FILE__' # https://docs.gunicorn.org/en/latest/settings.html#pidfile -pidfile = '__FINAL_HOME_PATH__/gunicorn.pid' +pidfile = '__FINALPATH__/gunicorn.pid' diff --git a/conf/manage.py b/conf/manage.py index 9962cbd..ec26808 100755 --- a/conf/manage.py +++ b/conf/manage.py @@ -1,4 +1,4 @@ -#!__FINAL_HOME_PATH__/venv/bin/python +#!__FINALPATH__/venv/bin/python import os import sys diff --git a/conf/nginx.conf b/conf/nginx.conf index 729b72d..867d5ef 100644 --- a/conf/nginx.conf +++ b/conf/nginx.conf @@ -1,17 +1,11 @@ location __PATH__/static/ { - # Django static files + # Service static files by nginx + # e.g.: /var/www/$app/static alias __PUBLIC_PATH__/static/; expires 30d; } -# TODO: django-sendfile2: -#location __PATH__/media/ { -# # DATA_DIR/media/ -# alias __PUBLIC_PATH__/media/; -# expires 30d; -#} - location __PATH__/ { # https://github.com/benoitc/gunicorn/blob/master/examples/nginx.conf diff --git a/conf/requirements.txt b/conf/requirements.txt index 8a4f615..9d02d77 100644 --- a/conf/requirements.txt +++ b/conf/requirements.txt @@ -10,9 +10,9 @@ bleach==5.0.1; python_version >= "3.7" and python_full_version < "4.0.0" \ bx-django-utils==26; python_version >= "3.7" and python_full_version < "4.0.0" \ --hash=sha256:c04776c4c6b275ddcadae79fd1578e6ae9ba92d8cf752a9ee4f2b70a22f4236e \ --hash=sha256:19349d0a8fa165d87b4fd544efb61418f7d6c41cfabaa46d0153bb6d844262a1 -bx-py-utils==67; python_version >= "3.7" and python_full_version < "4.0.0" \ - --hash=sha256:4810dcab69146ffd5c31d45710c47fa9ac63f074b26326bc2f17eb7eb7f5e09e \ - --hash=sha256:981877273d1c480f8d6f1de78b1a034c97b5e420df75e66799b320ff9133a047 +bx-py-utils==68; python_version >= "3.7" and python_full_version < "4.0.0" \ + --hash=sha256:00c3fbc9b5f48626a0a58141aaa90104349bac9723941c64f528f8742b0961db \ + --hash=sha256:fe5808c379db7165a51cbf5e4d947ef783d78bdfb5c47665e85e9cfe0b520ff9 certifi==2022.6.15; python_version >= "3.7" and python_version < "4" and python_full_version < "4.0.0" \ --hash=sha256:fe86415d55e84719d75f8b69414f6438ac3547d2078ab91b67e779ef69378412 \ --hash=sha256:84c85a9078b11105f04f3036a9482ae10e4621616db313fe045dd24743a0820d @@ -46,9 +46,9 @@ django-redis==5.2.0; python_version >= "3.7" and python_full_version < "4.0.0" \ django-tools==0.51.0; python_version >= "3.7" and python_full_version < "4.0.0" \ --hash=sha256:ebe69065ae5504e66667125858a7edd1cf6627e2b96cb03aeaab9e0ee3b8b98b \ --hash=sha256:4fe3768d4d863e7f3fa8bd7a3f014c4320ab320776218641fe760202c08f2d5a -django-yunohost-integration==0.2.4; python_version >= "3.7" and python_full_version < "4.0.0" \ - --hash=sha256:f5cdb5480025e90de0221d2b1c6282f517fd0c903702563cccb771cb3e1d9417 \ - --hash=sha256:a4b3617a3b39225d6162fa88827e9fe8b65388e26a0bbc23ea665c62aa8cb044 +django-yunohost-integration==0.3.0; python_version >= "3.7" and python_full_version < "4.0.0" \ + --hash=sha256:f7f8e9d55c50231bb32d3809b240d6ac718aadac1a1f471284baa1bfa6e98dff \ + --hash=sha256:d0e9cc91d241075613cb54b5da0879ad9b8ad3d22b84130cd7a331025650406e django==3.2.15; python_version >= "3.7" and python_full_version < "4.0.0" \ --hash=sha256:115baf5049d5cf4163e43492cdc7139c306ed6d451e7d3571fe9612903903713 \ --hash=sha256:f71934b1a822f14a86c9ac9634053689279cd04ae69cb6ade4a59471b886582b @@ -118,9 +118,9 @@ pyparsing==3.0.9; python_version >= "3.7" and python_full_version < "4.0.0" and python-stdnum==1.17; python_version >= "3.7" and python_full_version < "4.0.0" \ --hash=sha256:374e2b5e13912ccdbf50b0b23fca2c3e0531174805c32d74e145f37756328340 \ --hash=sha256:a46e6cf9652807314d369b654b255c86a59f93d18be2834f3d567ed1a346c547 -pytz==2022.1; python_version >= "3.7" and python_full_version < "4.0.0" \ - --hash=sha256:e68985985296d9a66a881eb3193b0906246245294a881e7c8afe623866ac6a5c \ - --hash=sha256:1e760e2fe6a8163bc0b3d9a19c4f84342afa0a2affebfaa84b01b978a02ecaa7 +pytz==2022.2.1; python_version >= "3.7" and python_full_version < "4.0.0" \ + --hash=sha256:220f481bdafa09c3955dfbdddb7b57780e9a94f5127e35456a48589b9e0c0197 \ + --hash=sha256:cea221417204f2d1a2aa03ddae3e867921971d0d76f14d87abb4414415bbdcf5 redis==4.3.4; python_version >= "3.7" and python_full_version < "4.0.0" \ --hash=sha256:a52d5694c9eb4292770084fa8c863f79367ca19884b329ab574d5cb2036b3e54 \ --hash=sha256:ddf27071df4adf3821c4f2ca59d67525c3a82e5f268bed97b813cb4fabf87880 diff --git a/conf/settings.py b/conf/settings.py index 27be13e..1850f03 100644 --- a/conf/settings.py +++ b/conf/settings.py @@ -2,7 +2,7 @@ ################################################################################ # 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. +# You can edit the file __FINALPATH__/local_settings.py and add/modify the settings you need. # The parameters you add in local_settings.py will overwrite these, # but you can use the options and documentation in this file to find out what can be done. @@ -11,45 +11,78 @@ from pathlib import Path as __Path +from django.core.validators import EmailValidator as __EmailValidator from django_yunohost_integration.base_settings import * # noqa from django_yunohost_integration.secret_key import get_or_create_secret as __get_or_create_secret from findmydevice_project.settings.base import * # noqa -# ----------------------------------------------------------------------------- +FINALPATH = __Path('__FINALPATH__') # /opt/yunohost/$app +assert FINALPATH.is_dir(), f'Directory not exists: {FINALPATH}' -FINAL_HOME_PATH = __Path('__FINAL_HOME_PATH__') # /opt/yunohost/$app -assert FINAL_HOME_PATH.is_dir(), f'Directory not exists: {FINAL_HOME_PATH}' +PUBLIC_PATH = __Path('__PUBLIC_PATH__') # /var/www/$app +assert PUBLIC_PATH.is_dir(), f'Directory not exists: {PUBLIC_PATH}' -FINAL_WWW_PATH = __Path('__FINAL_WWW_PATH__') # /var/www/$app -assert FINAL_WWW_PATH.is_dir(), f'Directory not exists: {FINAL_WWW_PATH}' - -LOG_FILE = __Path('__LOG_FILE__') # /var/log/$app/django-fmd.log +LOG_FILE = __Path('__LOG_FILE__') # /var/log/$app/django_example_ynh.log assert LOG_FILE.is_file(), f'File not exists: {LOG_FILE}' PATH_URL = '__PATH_URL__' # $YNH_APP_ARG_PATH PATH_URL = PATH_URL.strip('/') -# ----------------------------------------------------------------------------- +ROOT_URLCONF = 'urls' # /opt/yunohost/django-fmd/urls.py -ROOT_URLCONF = 'urls' # /opt/yunohost/django-fmd/ynh_urls.py +# ----------------------------------------------------------------------------- +# config_panel.toml settings: + +DEBUG_ENABLED = '__DEBUG_ENABLED__' +LOG_LEVEL = '__LOG_LEVEL__' +ADMIN_EMAIL = '__ADMIN_EMAIL__' +DEFAULT_FROM_EMAIL = '__DEFAULT_FROM_EMAIL__' + +# ----------------------------------------------------------------------------- +# Use/convert/validate config_panel.toml settings: + +DEBUG = bool(int(DEBUG_ENABLED)) +assert LOG_LEVEL in ( + 'DEBUG', + 'INFO', + 'WARNING', + 'ERROR', + 'CRITICAL', +), f'Invalid LOG_LEVEL: {LOG_LEVEL!r}' +__EmailValidator( + message='ADMIN_EMAIL %(value)r from config panel is not valid!', +)(ADMIN_EMAIL) +__EmailValidator( + message='DEFAULT_FROM_EMAIL %(value)r from config panel is not valid!', +)(DEFAULT_FROM_EMAIL) + +# ----------------------------------------------------------------------------- # 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 +SECRET_KEY = __get_or_create_secret(FINALPATH / 'secret.txt') # /opt/yunohost/$app/secret.txt -INSTALLED_APPS.append('django_yunohost_integration') +INSTALLED_APPS += [ + 'axes', # https://github.com/jazzband/django-axes + 'django_yunohost_integration', +] MIDDLEWARE.insert( MIDDLEWARE.index('django.contrib.auth.middleware.AuthenticationMiddleware') + 1, # login a user via HTTP_REMOTE_USER header from SSOwat: 'django_yunohost_integration.sso_auth.auth_middleware.SSOwatRemoteUserMiddleware', ) +# AxesMiddleware should be the last middleware: +MIDDLEWARE.append('axes.middleware.AxesMiddleware') + # 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_yunohost_integration.sso_auth.auth_backend.SSOwatUserBackend', # @@ -62,10 +95,11 @@ LOGIN_URL = '/yunohost/sso/' LOGOUT_REDIRECT_URL = '/yunohost/sso/' # /yunohost/sso/?action=logout + # ----------------------------------------------------------------------------- -ADMINS = (('__ADMIN__', '__ADMINMAIL__'),) +ADMINS = (('__ADMIN__', ADMIN_EMAIL),) MANAGERS = ADMINS @@ -94,10 +128,6 @@ EMAIL_SUBJECT_PREFIX = f'[{SITE_TITLE}] ' # E-mail address that error messages come from. SERVER_EMAIL = 'noreply@__DOMAIN__' -# Default email address to use for various automated correspondence from -# the site managers. Used for registration emails. -DEFAULT_FROM_EMAIL = '__ADMINMAIL__' - # List of URLs your site is supposed to serve ALLOWED_HOSTS = ['__DOMAIN__'] @@ -128,8 +158,9 @@ else: STATIC_URL = '/static/' MEDIA_URL = '/media/' -STATIC_ROOT = str(FINAL_WWW_PATH / 'static') -MEDIA_ROOT = str(FINAL_WWW_PATH / 'media') +STATIC_ROOT = str(PUBLIC_PATH / 'static') +MEDIA_ROOT = str(PUBLIC_PATH / 'media') + # ----------------------------------------------------------------------------- @@ -157,12 +188,24 @@ LOGGING = { }, }, 'loggers': { - '': {'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}, - 'django_tools': {'handlers': ['log_file', 'mail_admins'], 'level': 'INFO', 'propagate': False}, - 'django_yunohost_integration': {'handlers': ['log_file', 'mail_admins'], 'level': 'INFO', 'propagate': False}, - 'findmydevice': {'handlers': ['log_file', 'mail_admins'], 'level': 'INFO', 'propagate': False}, + '': {'handlers': ['log_file', 'mail_admins'], 'level': LOG_LEVEL, 'propagate': False}, + 'django': {'handlers': ['log_file', 'mail_admins'], 'level': LOG_LEVEL, 'propagate': False}, + 'axes': {'handlers': ['log_file', 'mail_admins'], 'level': LOG_LEVEL, 'propagate': False}, + 'django_tools': { + 'handlers': ['log_file', 'mail_admins'], + 'level': LOG_LEVEL, + 'propagate': False, + }, + 'django_yunohost_integration': { + 'handlers': ['log_file', 'mail_admins'], + 'level': LOG_LEVEL, + 'propagate': False, + }, + 'findmydevice': { + 'handlers': ['log_file', 'mail_admins'], + 'level': LOG_LEVEL, + 'propagate': False, + }, }, } diff --git a/conf/systemd.service b/conf/systemd.service new file mode 100644 index 0000000..6b6cc92 --- /dev/null +++ b/conf/systemd.service @@ -0,0 +1,17 @@ +[Unit] +Description=__APP__ server +After=redis.service postgresql.service + +[Service] +User=__APP__ +Group=__APP__ +WorkingDirectory=__FINALPATH__/ + +ExecStart=__FINALPATH__/venv/bin/gunicorn --config __FINALPATH__/gunicorn.conf.py wsgi + +StandardOutput=syslog +StandardError=syslog +SyslogIdentifier=__APP__-server + +[Install] +WantedBy=multi-user.target diff --git a/conf/urls.py b/conf/urls.py index 6a6f129..0fe56ef 100644 --- a/conf/urls.py +++ b/conf/urls.py @@ -1,27 +1,19 @@ from django.conf import settings -from django.conf.urls import static from django.urls import include, path from django.views.static import serve import findmydevice -# from django_yunohost_integration.views import request_media_debug_view - - if settings.PATH_URL: # settings.PATH_URL is the $YNH_APP_ARG_PATH # Prefix all urls with "PATH_URL": urlpatterns = [ - # path(f'{settings.PATH_URL}/debug/', request_media_debug_view), - path(f'{settings.PATH_URL}/', include('findmydevice_project.urls')), # # TODO: Serve from nginx server ;) path(f'{settings.PATH_URL}/', serve, {'document_root': findmydevice.WEB_PATH}) ] - if settings.SERVE_FILES: - urlpatterns += static.static(settings.STATIC_URL, document_root=settings.STATIC_ROOT) else: # Installed to domain root, without a path prefix # Just use the default project urls.py diff --git a/local_test.py b/local_test.py index e2664e5..4b3d6de 100644 --- a/local_test.py +++ b/local_test.py @@ -22,6 +22,12 @@ def main(): django_settings_path=BASE_PATH / 'conf' / 'settings.py', destination=BASE_PATH / 'local_test', runserver=True, + extra_replacements={ + '__DEBUG_ENABLED__': '1', + '__LOG_LEVEL__': 'DEBUG', + '__ADMIN_EMAIL__': 'foo-bar@test.tld', + '__DEFAULT_FROM_EMAIL__': 'django_app@test.tld', + }, ) diff --git a/poetry.lock b/poetry.lock index 8c8d696..379b807 100644 --- a/poetry.lock +++ b/poetry.lock @@ -10,7 +10,7 @@ python-versions = ">=3.7" typing-extensions = {version = "*", markers = "python_version < \"3.8\""} [package.extras] -tests = ["pytest", "pytest-asyncio", "mypy (>=0.800)"] +tests = ["mypy (>=0.800)", "pytest-asyncio", "pytest"] [[package]] name = "astor" @@ -48,10 +48,10 @@ optional = false python-versions = ">=3.5" [package.extras] -dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "mypy (>=0.900,!=0.940)", "pytest-mypy-plugins", "zope.interface", "furo", "sphinx", "sphinx-notfound-page", "pre-commit", "cloudpickle"] -docs = ["furo", "sphinx", "zope.interface", "sphinx-notfound-page"] -tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "mypy (>=0.900,!=0.940)", "pytest-mypy-plugins", "zope.interface", "cloudpickle"] -tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "mypy (>=0.900,!=0.940)", "pytest-mypy-plugins", "cloudpickle"] +tests_no_zope = ["cloudpickle", "pytest-mypy-plugins", "mypy (>=0.900,!=0.940)", "pytest (>=4.3.0)", "pympler", "hypothesis", "coverage[toml] (>=5.0.2)"] +tests = ["cloudpickle", "zope.interface", "pytest-mypy-plugins", "mypy (>=0.900,!=0.940)", "pytest (>=4.3.0)", "pympler", "hypothesis", "coverage[toml] (>=5.0.2)"] +docs = ["sphinx-notfound-page", "zope.interface", "sphinx", "furo"] +dev = ["cloudpickle", "pre-commit", "sphinx-notfound-page", "sphinx", "furo", "zope.interface", "pytest-mypy-plugins", "mypy (>=0.900,!=0.940)", "pytest (>=4.3.0)", "pympler", "hypothesis", "coverage[toml] (>=5.0.2)"] [[package]] name = "beautifulsoup4" @@ -65,8 +65,8 @@ python-versions = ">=3.6.0" soupsieve = ">1.2" [package.extras] -html5lib = ["html5lib"] lxml = ["lxml"] +html5lib = ["html5lib"] [[package]] name = "black" @@ -86,10 +86,10 @@ typed-ast = {version = ">=1.4.2", markers = "python_version < \"3.8\" and implem typing-extensions = {version = ">=3.10.0.0", markers = "python_version < \"3.10\""} [package.extras] -colorama = ["colorama (>=0.4.3)"] -d = ["aiohttp (>=3.7.4)"] -jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] uvloop = ["uvloop (>=0.15.2)"] +jupyter = ["tokenize-rt (>=3.2.0)", "ipython (>=7.8.0)"] +d = ["aiohttp (>=3.7.4)"] +colorama = ["colorama (>=0.4.3)"] [[package]] name = "bleach" @@ -104,8 +104,8 @@ six = ">=1.9.0" webencodings = "*" [package.extras] +dev = ["mypy (==0.961)", "black (==22.3.0)", "wheel (==0.37.1)", "twine (==4.0.1)", "tox (==3.25.0)", "Sphinx (==4.3.2)", "pytest (==7.1.2)", "pip-tools (==6.6.2)", "hashin (==0.17.0)", "flake8 (==4.0.1)", "build (==0.8.0)"] css = ["tinycss2 (>=1.1.0,<1.2)"] -dev = ["build (==0.8.0)", "flake8 (==4.0.1)", "hashin (==0.17.0)", "pip-tools (==6.6.2)", "pytest (==7.1.2)", "Sphinx (==4.3.2)", "tox (==3.25.0)", "twine (==4.0.1)", "wheel (==0.37.1)", "black (==22.3.0)", "mypy (==0.961)"] [[package]] name = "bx-django-utils" @@ -122,7 +122,7 @@ python-stdnum = "*" [[package]] name = "bx-py-utils" -version = "67" +version = "68" description = "Various Python utility functions" category = "main" optional = false @@ -179,7 +179,7 @@ python-versions = ">=3.6" colorama = {version = "*", markers = "sys_platform == \"win32\""} [package.extras] -development = ["black", "flake8", "mypy", "pytest", "types-colorama"] +development = ["types-colorama", "pytest", "mypy", "flake8", "black"] [[package]] name = "coverage" @@ -242,7 +242,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" wrapt = ">=1.10,<2" [package.extras] -dev = ["tox", "bump2version (<1)", "sphinx (<2)", "importlib-metadata (<3)", "importlib-resources (<4)", "configparser (<5)", "sphinxcontrib-websupport (<2)", "zipp (<2)", "PyTest (<5)", "PyTest-Cov (<2.6)", "pytest", "pytest-cov"] +dev = ["pytest-cov", "pytest", "PyTest-Cov (<2.6)", "PyTest (<5)", "zipp (<2)", "sphinxcontrib-websupport (<2)", "configparser (<5)", "importlib-resources (<4)", "importlib-metadata (<3)", "sphinx (<2)", "bump2version (<1)", "tox"] [[package]] name = "distlib" @@ -266,8 +266,8 @@ pytz = "*" sqlparse = ">=0.2.2" [package.extras] -argon2 = ["argon2-cffi (>=19.1.0)"] bcrypt = ["bcrypt"] +argon2 = ["argon2-cffi (>=19.1.0)"] [[package]] name = "django-axes" @@ -351,7 +351,7 @@ pprintpp = "*" [[package]] name = "django-yunohost-integration" -version = "0.2.4" +version = "0.3.0" description = "Glue code to package django projects as yunohost apps." category = "main" optional = false @@ -384,8 +384,8 @@ optional = false python-versions = ">=3.7" [package.extras] -docs = ["furo (>=2022.6.21)", "sphinx (>=5.1.1)", "sphinx-autodoc-typehints (>=1.19.1)"] -testing = ["covdefaults (>=2.2)", "coverage (>=6.4.2)", "pytest (>=7.1.2)", "pytest-cov (>=3)", "pytest-timeout (>=2.1)"] +testing = ["pytest-timeout (>=2.1)", "pytest-cov (>=3)", "pytest (>=7.1.2)", "coverage (>=6.4.2)", "covdefaults (>=2.2)"] +docs = ["sphinx-autodoc-typehints (>=1.19.1)", "sphinx (>=5.1.1)", "furo (>=2022.6.21)"] [[package]] name = "flake8" @@ -456,8 +456,8 @@ typing-extensions = {version = ">=3.6.4", markers = "python_version < \"3.8\""} zipp = ">=0.5" [package.extras] -docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] -testing = ["pytest (>=4.6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "packaging", "pep517", "pyfakefs", "flufl.flake8", "pytest-black (>=0.3.7)", "pytest-mypy", "importlib-resources (>=1.3)"] +testing = ["importlib-resources (>=1.3)", "pytest-mypy", "pytest-black (>=0.3.7)", "flufl.flake8", "pyfakefs", "pep517", "packaging", "pytest-enabler (>=1.0.1)", "pytest-cov", "pytest-flake8", "pytest-checkdocs (>=2.4)", "pytest (>=4.6)"] +docs = ["rst.linker (>=1.9)", "jaraco.packaging (>=8.2)", "sphinx"] [[package]] name = "iniconfig" @@ -476,10 +476,10 @@ optional = false python-versions = ">=3.6.1,<4.0" [package.extras] -pipfile_deprecated_finder = ["pipreqs", "requirementslib"] -requirements_deprecated_finder = ["pipreqs", "pip-api"] -colors = ["colorama (>=0.4.3,<0.5.0)"] plugins = ["setuptools"] +colors = ["colorama (>=0.4.3,<0.5.0)"] +requirements_deprecated_finder = ["pip-api", "pipreqs"] +pipfile_deprecated_finder = ["requirementslib", "pipreqs"] [[package]] name = "mccabe" @@ -525,8 +525,8 @@ optional = false python-versions = ">=3.7" [package.extras] -docs = ["furo (>=2021.7.5b38)", "proselint (>=0.10.2)", "sphinx-autodoc-typehints (>=1.12)", "sphinx (>=4)"] -test = ["appdirs (==1.4.4)", "pytest-cov (>=2.7)", "pytest-mock (>=3.6)", "pytest (>=6)"] +test = ["pytest (>=6)", "pytest-mock (>=3.6)", "pytest-cov (>=2.7)", "appdirs (==1.4.4)"] +docs = ["sphinx (>=4)", "sphinx-autodoc-typehints (>=1.12)", "proselint (>=0.10.2)", "furo (>=2021.7.5b38)"] [[package]] name = "pluggy" @@ -600,7 +600,7 @@ optional = false python-versions = ">=3.6.8" [package.extras] -diagrams = ["railroad-diagrams", "jinja2"] +diagrams = ["jinja2", "railroad-diagrams"] [[package]] name = "pytest" @@ -622,7 +622,7 @@ py = ">=1.8.2" tomli = ">=1.0.0" [package.extras] -testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "xmlschema"] +testing = ["xmlschema", "requests", "pygments (>=2.7.2)", "nose", "mock", "hypothesis (>=3.56)", "argcomplete"] [[package]] name = "pytest-cov" @@ -666,8 +666,8 @@ python-versions = ">=3.5" pytest = ">=5.4.0" [package.extras] -docs = ["sphinx", "sphinx-rtd-theme"] -testing = ["django", "django-configurations (>=2.0)"] +testing = ["django-configurations (>=2.0)", "django"] +docs = ["sphinx-rtd-theme", "sphinx"] [[package]] name = "python-stdnum" @@ -678,13 +678,13 @@ optional = false python-versions = "*" [package.extras] -soap = ["zeep"] -soap-alt = ["suds"] soap-fallback = ["pysimplesoap"] +soap-alt = ["suds"] +soap = ["zeep"] [[package]] name = "pytz" -version = "2022.1" +version = "2022.2.1" description = "World timezone definitions, modern and historical" category = "main" optional = false @@ -717,8 +717,8 @@ packaging = ">=20.4" typing-extensions = {version = "*", markers = "python_version < \"3.8\""} [package.extras] +ocsp = ["requests (>=2.26.0)", "pyopenssl (==20.0.1)", "cryptography (>=36.0.1)"] hiredis = ["hiredis (>=1.0.0)"] -ocsp = ["cryptography (>=36.0.1)", "pyopenssl (==20.0.1)", "requests (>=2.26.0)"] [[package]] name = "requests" @@ -735,8 +735,8 @@ idna = ">=2.5,<4" urllib3 = ">=1.21.1,<1.27" [package.extras] -socks = ["PySocks (>=1.5.6,!=1.5.7)"] use_chardet_on_py3 = ["chardet (>=3.0.2,<6)"] +socks = ["PySocks (>=1.5.6,!=1.5.7)"] [[package]] name = "six" @@ -806,8 +806,8 @@ toml = ">=0.9.4" virtualenv = ">=16.0.0,<20.0.0 || >20.0.0,<20.0.1 || >20.0.1,<20.0.2 || >20.0.2,<20.0.3 || >20.0.3,<20.0.4 || >20.0.4,<20.0.5 || >20.0.5,<20.0.6 || >20.0.6,<20.0.7 || >20.0.7" [package.extras] -docs = ["pygments-github-lexers (>=0.0.5)", "sphinx (>=2.0.0)", "sphinxcontrib-autoprogram (>=0.1.5)", "towncrier (>=18.5.0)"] -testing = ["flaky (>=3.4.0)", "freezegun (>=0.3.11)", "pytest (>=4.0.0)", "pytest-cov (>=2.5.1)", "pytest-mock (>=1.10.0)", "pytest-randomly (>=1.0.0)", "psutil (>=5.6.1)", "pathlib2 (>=2.3.3)"] +testing = ["pathlib2 (>=2.3.3)", "psutil (>=5.6.1)", "pytest-randomly (>=1.0.0)", "pytest-mock (>=1.10.0)", "pytest-cov (>=2.5.1)", "pytest (>=4.0.0)", "freezegun (>=0.3.11)", "flaky (>=3.4.0)"] +docs = ["towncrier (>=18.5.0)", "sphinxcontrib-autoprogram (>=0.1.5)", "sphinx (>=2.0.0)", "pygments-github-lexers (>=0.0.5)"] [[package]] name = "typed-ast" @@ -834,9 +834,9 @@ optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, <4" [package.extras] -brotli = ["brotlicffi (>=0.8.0)", "brotli (>=1.0.9)", "brotlipy (>=0.6.0)"] -secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"] socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] +secure = ["ipaddress", "certifi", "idna (>=2.0.0)", "cryptography (>=1.3.4)", "pyOpenSSL (>=0.14)"] +brotli = ["brotlipy (>=0.6.0)", "brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] [[package]] name = "virtualenv" @@ -853,8 +853,8 @@ importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} platformdirs = ">=2,<3" [package.extras] -docs = ["proselint (>=0.10.2)", "sphinx (>=3)", "sphinx-argparse (>=0.2.5)", "sphinx-rtd-theme (>=0.4.3)", "towncrier (>=21.3)"] -testing = ["coverage (>=4)", "coverage-enable-subprocess (>=1)", "flaky (>=3)", "packaging (>=20.0)", "pytest (>=4)", "pytest-env (>=0.6.2)", "pytest-freezegun (>=0.4.1)", "pytest-mock (>=2)", "pytest-randomly (>=1)", "pytest-timeout (>=1)"] +testing = ["pytest-timeout (>=1)", "pytest-randomly (>=1)", "pytest-mock (>=2)", "pytest-freezegun (>=0.4.1)", "pytest-env (>=0.6.2)", "pytest (>=4)", "packaging (>=20.0)", "flaky (>=3)", "coverage-enable-subprocess (>=1)", "coverage (>=4)"] +docs = ["towncrier (>=21.3)", "sphinx-rtd-theme (>=0.4.3)", "sphinx-argparse (>=0.2.5)", "sphinx (>=3)", "proselint (>=0.10.2)"] [[package]] name = "webencodings" @@ -881,8 +881,8 @@ optional = false python-versions = ">=3.7" [package.extras] -docs = ["sphinx", "jaraco.packaging (>=9)", "rst.linker (>=1.9)", "jaraco.tidelift (>=1.4)"] -testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.3)", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy (>=0.9.1)"] +testing = ["pytest-mypy (>=0.9.1)", "pytest-black (>=0.3.7)", "func-timeout", "jaraco.itertools", "pytest-enabler (>=1.3)", "pytest-cov", "pytest-flake8", "pytest-checkdocs (>=2.4)", "pytest (>=6)"] +docs = ["jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "jaraco.packaging (>=9)", "sphinx"] [metadata] lock-version = "1.1" @@ -947,8 +947,8 @@ bx-django-utils = [ {file = "bx_django_utils-26.tar.gz", hash = "sha256:19349d0a8fa165d87b4fd544efb61418f7d6c41cfabaa46d0153bb6d844262a1"}, ] bx-py-utils = [ - {file = "bx_py_utils-67-py3-none-any.whl", hash = "sha256:4810dcab69146ffd5c31d45710c47fa9ac63f074b26326bc2f17eb7eb7f5e09e"}, - {file = "bx_py_utils-67.tar.gz", hash = "sha256:981877273d1c480f8d6f1de78b1a034c97b5e420df75e66799b320ff9133a047"}, + {file = "bx_py_utils-68-py3-none-any.whl", hash = "sha256:00c3fbc9b5f48626a0a58141aaa90104349bac9723941c64f528f8742b0961db"}, + {file = "bx_py_utils-68.tar.gz", hash = "sha256:fe5808c379db7165a51cbf5e4d947ef783d78bdfb5c47665e85e9cfe0b520ff9"}, ] certifi = [ {file = "certifi-2022.6.15-py3-none-any.whl", hash = "sha256:fe86415d55e84719d75f8b69414f6438ac3547d2078ab91b67e779ef69378412"}, @@ -1058,8 +1058,8 @@ django-tools = [ {file = "django_tools-0.51.0-py3-none-any.whl", hash = "sha256:4fe3768d4d863e7f3fa8bd7a3f014c4320ab320776218641fe760202c08f2d5a"}, ] django-yunohost-integration = [ - {file = "django_yunohost_integration-0.2.4-py3-none-any.whl", hash = "sha256:f5cdb5480025e90de0221d2b1c6282f517fd0c903702563cccb771cb3e1d9417"}, - {file = "django_yunohost_integration-0.2.4.tar.gz", hash = "sha256:a4b3617a3b39225d6162fa88827e9fe8b65388e26a0bbc23ea665c62aa8cb044"}, + {file = "django_yunohost_integration-0.3.0-py3-none-any.whl", hash = "sha256:f7f8e9d55c50231bb32d3809b240d6ac718aadac1a1f471284baa1bfa6e98dff"}, + {file = "django_yunohost_integration-0.3.0.tar.gz", hash = "sha256:d0e9cc91d241075613cb54b5da0879ad9b8ad3d22b84130cd7a331025650406e"}, ] docopt = [ {file = "docopt-0.6.2.tar.gz", hash = "sha256:49b3a825280bd66b3aa83585ef59c4a8c82f2c8a522dbe754a8bc8d08c85c491"}, @@ -1209,8 +1209,8 @@ python-stdnum = [ {file = "python_stdnum-1.17-py2.py3-none-any.whl", hash = "sha256:a46e6cf9652807314d369b654b255c86a59f93d18be2834f3d567ed1a346c547"}, ] pytz = [ - {file = "pytz-2022.1-py2.py3-none-any.whl", hash = "sha256:e68985985296d9a66a881eb3193b0906246245294a881e7c8afe623866ac6a5c"}, - {file = "pytz-2022.1.tar.gz", hash = "sha256:1e760e2fe6a8163bc0b3d9a19c4f84342afa0a2affebfaa84b01b978a02ecaa7"}, + {file = "pytz-2022.2.1-py2.py3-none-any.whl", hash = "sha256:220f481bdafa09c3955dfbdddb7b57780e9a94f5127e35456a48589b9e0c0197"}, + {file = "pytz-2022.2.1.tar.gz", hash = "sha256:cea221417204f2d1a2aa03ddae3e867921971d0d76f14d87abb4414415bbdcf5"}, ] pyupgrade = [ {file = "pyupgrade-2.37.3-py2.py3-none-any.whl", hash = "sha256:9746efd064dbf53d7f86d6f88a1d48120f58dbfc378f517768634740ea2225e2"}, diff --git a/pyproject.toml b/pyproject.toml index 0ae222c..c504cc2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -68,7 +68,7 @@ lines_after_imports=2 [tool.pytest.ini_options] # https://docs.pytest.org/en/latest/customize.html#pyproject-toml minversion = "6.0" -norecursedirs = ".* .git __pycache__ conf coverage* dist htmlcov" +norecursedirs = ".* .git __pycache__ conf local_test coverage* dist htmlcov" # sometimes helpfull "addopts" arguments: # -vv # --verbose @@ -103,7 +103,7 @@ skip_missing_interpreters = True [testenv] passenv = * -whitelist_externals = make +whitelist_externals = pytest commands = pytest findmydevice findmydevice_project """ diff --git a/run_pytest.py b/run_pytest.py deleted file mode 100644 index 086b5ef..0000000 --- a/run_pytest.py +++ /dev/null @@ -1,25 +0,0 @@ -""" - Run pytest against local test creation -""" - -from pathlib import Path - - -try: - from django_yunohost_integration.pytest_helper import run_pytest -except ImportError as err: - raise ImportError('Did you forget to activate a virtual environment?') from err - - -BASE_PATH = Path(__file__).parent - - -def main(): - run_pytest( - django_settings_path=BASE_PATH / 'conf' / 'settings.py', - destination=BASE_PATH / 'local_test', - ) - - -if __name__ == '__main__': - main() diff --git a/scripts/_common.sh b/scripts/_common.sh index e348462..3359f29 100644 --- a/scripts/_common.sh +++ b/scripts/_common.sh @@ -13,6 +13,22 @@ app=$YNH_APP_INSTANCE_NAME # Currently not used: django-fmd has no public pages, yet! is_public=$YNH_APP_ARG_IS_PUBLIC +#================================================= +# ARGUMENTS FROM CONFIG PANEL +#================================================= + +# 'debug_enabled' -> '__DEBUG_ENABLED__' -> settings.DEBUG +debug_enabled="0" + +# 'log_level' -> '__LOG_LEVEL__' -> settings.LOG_LEVEL +log_level="WARNING" + +# 'admin_email' -> '__ADMIN_EMAIL__' add in settings.ADMINS +admin_email="${admin}@${domain}" + +# 'default_from_email' -> '__DEFAULT_FROM_EMAIL__' -> settings.DEFAULT_FROM_EMAIL +default_from_email="${app}@${domain}" + #================================================= # SET CONSTANTS #================================================= @@ -20,10 +36,7 @@ is_public=$YNH_APP_ARG_IS_PUBLIC public_path=/var/www/$app final_path=/opt/yunohost/$app log_path=/var/log/$app -log_file="${log_path}/django-fmd.log" - -# Default: settings.DEBUG=False -django_debug="False" +log_file="${log_path}/${app}.log" #================================================= # COMMON VARIABLES diff --git a/scripts/change_url b/scripts/change_url index eaad24b..5bb62fb 100644 --- a/scripts/change_url +++ b/scripts/change_url @@ -30,10 +30,21 @@ final_path=$(ynh_app_setting_get --app="$app" --key=final_path) log_path=$(ynh_app_setting_get --app="$app" --key=log_path) port=$(ynh_app_setting_get --app="$app" --key=port) + db_pwd=$(ynh_app_setting_get --app="$app" --key=psqlpwd) -admin_mail=$(ynh_user_get_info "$admin" mail) +db_name=$(ynh_sanitize_dbid --db_name="$app") +db_user=$db_name + redis_db=$(ynh_app_setting_get --app="$app" --key=redis_db) +#------------------------------------------------- +# config_panel.toml settings: + +debug_enabled=$(ynh_app_setting_get --app="$app" --key=debug_enabled) +log_level=$(ynh_app_setting_get --app="$app" --key=log_level) +admin_email=$(ynh_app_setting_get --app="$app" --key=admin_email) +default_from_email=$(ynh_app_setting_get --app="$app" --key=default_from_email) + #================================================= # BACKUP BEFORE UPGRADE THEN ACTIVE TRAP #================================================= @@ -114,27 +125,7 @@ fi #================================================= ynh_script_progression --message="Modify django-fmd's config file..." -# save old settings file -settings="$final_path/settings.py" -ynh_backup_if_checksum_is_different --file="$settings" - -cp "../conf/settings.py" "$settings" - -ynh_replace_string --match_string="__APP__" --replace_string="$app" --target_file="$settings" -ynh_replace_string --match_string="__DB_PWD__" --replace_string="$db_pwd" --target_file="$settings" -ynh_replace_string --match_string="__ADMIN__" --replace_string="$admin" --target_file="$settings" -ynh_replace_string --match_string="__ADMINMAIL__" --replace_string="$admin_mail" --target_file="$settings" -ynh_replace_string --match_string="__FINAL_HOME_PATH__" --replace_string="$final_path" --target_file="$settings" -ynh_replace_string --match_string="__FINAL_WWW_PATH__" --replace_string="$public_path" --target_file="$settings" -ynh_replace_string --match_string="__LOG_FILE__" --replace_string="$log_file" --target_file="$settings" -ynh_replace_string --match_string="__REDIS_DB__" --replace_string="$redis_db" --target_file="$settings" - -# Difference to install/upgrade scripts: Use $new_domain and $new_path here: -ynh_replace_string --match_string="__DOMAIN__" --replace_string="$new_domain" --target_file="$settings" -ynh_replace_string --match_string="__PATH_URL__" --replace_string="$new_path" --target_file="$settings" - -# Recalculate and store the config file checksum into the app settings -ynh_store_file_checksum --file="$settings" +ynh_add_config --template="settings.py" --destination="$final_path/settings.py" #================================================= # GENERIC FINALISATION diff --git a/scripts/install b/scripts/install index 029e772..fc24acc 100755 --- a/scripts/install +++ b/scripts/install @@ -50,15 +50,22 @@ ynh_app_setting_set --app="$app" --key=path --value="$path_url" # Find a free port port=$(ynh_find_port --port=8000) # Set port as application setting -# https://github.com/YunoHost/yunohost/blob/dev/data/helpers.d/setting +# https://yunohost.org/en/contribute/packaging_apps/helpers +# https://github.com/YunoHost/yunohost/blob/dev/helpers/setting ynh_app_setting_set --app="$app" --key=port --value="$port" db_pwd=$(ynh_app_setting_get --app="$app" --key=psqlpwd) -admin_mail=$(ynh_user_get_info --username="$admin" --key=mail) -redis_db=$(ynh_redis_get_free_db) -# Always deactivate settings.DEBUG: -ynh_app_setting_set --app=$app --key=django_debug --value="False" +redis_db=$(ynh_redis_get_free_db) +ynh_app_setting_set --app="$app" --key=redis_db --value="$redis_db" + +#------------------------------------------------- +# config_panel.toml settings: + +ynh_app_setting_set --app="$app" --key=debug_enabled --value="$debug_enabled" +ynh_app_setting_set --app="$app" --key=log_level --value="$log_level" +ynh_app_setting_set --app="$app" --key=admin_email --value="$admin_email" +ynh_app_setting_set --app="$app" --key=default_from_email --value="$default_from_email" #================================================= # STANDARD MODIFICATIONS @@ -89,7 +96,8 @@ ynh_psql_setup_db --db_user="$db_user" --db_name="$db_name" ynh_script_progression --message="Configuring nginx web server..." # Create a dedicated nginx config -# https://github.com/YunoHost/yunohost/blob/dev/data/helpers.d/nginx +# https://yunohost.org/en/contribute/packaging_apps/helpers +# https://github.com/YunoHost/yunohost/blob/dev/helpers/nginx ynh_add_nginx_config "public_path" "port" #================================================= @@ -101,9 +109,9 @@ ynh_script_progression --message="Configuring system user..." ynh_system_user_create --username="$app" --home_dir="$final_path" --use_shell #================================================= -# PIP INSTALLATION +# PYTHON VIRTUALENV #================================================= -ynh_script_progression --message="Install project via pip..." --weight=50 +ynh_script_progression --message="Create Python virtualenv..." --weight=5 # Always recreate everything fresh with current python version ynh_secure_remove "${final_path}/venv" @@ -114,6 +122,11 @@ python3 -m venv --without-pip "${final_path}/venv" cp ../conf/requirements.txt "$final_path/requirements.txt" chown -R "$app:" "$final_path" +#================================================= +# PIP INSTALLATION +#================================================= +ynh_script_progression --message="Install project via pip..." --weight=45 + #run source in a 'sub shell' ( set +o nounset @@ -129,45 +142,17 @@ chown -R "$app:" "$final_path" # ================================================ ynh_script_progression --message="Create project configuration files..." -gunicorn_conf="$final_path/gunicorn.conf.py" -cp "../conf/gunicorn.conf.py" "$gunicorn_conf" -ynh_replace_string --match_string="__FINAL_HOME_PATH__" --replace_string="$final_path" --target_file="$gunicorn_conf" -ynh_replace_string --match_string="__LOG_FILE__" --replace_string="$log_file" --target_file="$gunicorn_conf" -ynh_replace_string --match_string="__PORT__" --replace_string="$port" --target_file="$gunicorn_conf" -ynh_store_file_checksum --file="$gunicorn_conf" +ynh_add_config --template="gunicorn.conf.py" --destination="$final_path/gunicorn.conf.py" -cp ../conf/manage.py "$final_path/manage.py" -ynh_replace_string --match_string="__FINAL_HOME_PATH__" --replace_string="$final_path" --target_file="$final_path/manage.py" -ynh_store_file_checksum --file="$final_path/manage.py" +ynh_add_config --template="manage.py" --destination="$final_path/manage.py" chmod +x "$final_path/manage.py" -settings="$final_path/settings.py" -cp "../conf/settings.py" "$settings" +ynh_add_config --template="settings.py" --destination="$final_path/settings.py" +ynh_add_config --template="setup_user.py" --destination="$final_path/setup_user.py" +ynh_add_config --template="urls.py" --destination="$final_path/urls.py" +ynh_add_config --template="wsgi.py" --destination="$final_path/wsgi.py" -ynh_replace_string --match_string="__APP__" --replace_string="$app" --target_file="$settings" -ynh_replace_string --match_string="__DB_NAME__" --replace_string="$db_name" --target_file="$settings" -ynh_replace_string --match_string="__DB_USER__" --replace_string="$db_user" --target_file="$settings" -ynh_replace_string --match_string="__DB_PWD__" --replace_string="$db_pwd" --target_file="$settings" -ynh_replace_string --match_string="__ADMIN__" --replace_string="$admin" --target_file="$settings" -ynh_replace_string --match_string="__ADMINMAIL__" --replace_string="$admin_mail" --target_file="$settings" -ynh_replace_string --match_string="__FINAL_HOME_PATH__" --replace_string="$final_path" --target_file="$settings" -ynh_replace_string --match_string="__FINAL_WWW_PATH__" --replace_string="$public_path" --target_file="$settings" -ynh_replace_string --match_string="__LOG_FILE__" --replace_string="$log_file" --target_file="$settings" -ynh_replace_string --match_string="__REDIS_DB__" --replace_string="$redis_db" --target_file="$settings" - -ynh_replace_string --match_string="__DOMAIN__" --replace_string="$domain" --target_file="$settings" -ynh_replace_string --match_string="__PATH_URL__" --replace_string="$path_url" --target_file="$settings" - -# Calculate and store the config file checksum into the app settings -ynh_store_file_checksum --file="$settings" - -ynh_app_setting_set --app="$app" --key=redis_db --value="$redis_db" - -cp ../conf/setup_user.py "$final_path/setup_user.py" -cp ../conf/urls.py "$final_path/urls.py" -cp ../conf/wsgi.py "$final_path/wsgi.py" - -ynh_add_config --template="local_settings.py" --destination="$final_path/local_settings.py" +touch "$final_path/local_settings.py" #================================================= # MIGRATE / COLLECTSTATIC / CREATEADMIN @@ -183,7 +168,7 @@ cd "$final_path" || exit ./manage.py collectstatic --no-input # Create/update Django superuser (set unusable password, because auth done via SSOwat): -./manage.py create_superuser --username="$admin" --email="$admin_mail" +./manage.py create_superuser --username="$admin" --email="$(ynh_user_get_info "$admin" mail)" # Check the configuration # This may fail in some cases with errors, etc., but the app works and the user can fix issues later. @@ -225,13 +210,29 @@ chmod o-rwx "$final_path" #================================================= ynh_script_progression --message="Configuring a systemd service..." -# https://github.com/YunoHost/yunohost/blob/dev/data/helpers.d/systemd -ynh_add_systemd_config --service="$app" --template="django-fmd.service" +# https://yunohost.org/en/contribute/packaging_apps/helpers +# https://github.com/YunoHost/yunohost/blob/dev/helpers/systemd +ynh_add_systemd_config --service="$app" --template="systemd.service" #================================================= -# Start django-fmd via systemd +# SETUP SSOWAT #================================================= -ynh_script_progression --message="Starting django-fmd's services..." --weight=5 +ynh_script_progression --message="Configuring SSOwat..." + +# Currently there are no public pages: +# +## Make app public if necessary or protect it +#if [ $is_public -eq 1 ] +#then +# # Everyone can access the app. +# # The "main" permission is automatically created before the install script. +# ynh_permission_update --permission "main" --add "visitors" +#fi + +#================================================= +# Start the app server via systemd +#================================================= +ynh_script_progression --message="Starting django_example_ynh's services..." --weight=5 ynh_systemd_action --service_name="$app" --action="start" diff --git a/scripts/restore b/scripts/restore index 43fa5f8..0401327 100755 --- a/scripts/restore +++ b/scripts/restore @@ -20,8 +20,8 @@ ynh_abort_if_errors #================================================= ynh_script_progression --message="Loading settings..." -public_path=$(ynh_app_setting_get --app="$app" --key=public_path) final_path=$(ynh_app_setting_get --app="$app" --key=final_path) +public_path=$(ynh_app_setting_get --app="$app" --key=public_path) db_name=$(ynh_app_setting_get --app="$app" --key=db_name) db_user=$db_name db_pwd=$(ynh_app_setting_get --app="$app" --key=psqlpwd) @@ -34,8 +34,6 @@ path_url=$(ynh_app_setting_get --app="$app" --key=path) #================================================= ynh_script_progression --message="Validating restoration parameters..." -ynh_webpath_available --domain=$domain --path_url=$path_url \ - || ynh_die --message="Path not available: ${domain}${path_url}" test ! -d $final_path \ || ynh_die --message="There is already a directory: $final_path " @@ -52,8 +50,8 @@ ynh_restore_file --origin_path="/etc/nginx/conf.d/$domain.d/$app.conf" #================================================= ynh_script_progression --message="Restoring the app main directory..." -ynh_restore_file --origin_path="$public_path" ynh_restore_file --origin_path="$final_path" +ynh_restore_file --origin_path="$public_path" #================================================= # RECREATE THE DEDICATED USER @@ -81,10 +79,10 @@ ynh_script_progression --message="Reinstalling dependencies..." --weight=20 ynh_exec_warn_less ynh_install_app_dependencies "$pkg_dependencies" #================================================= -# REINSTALL PYTHON VIRTUALENV +# PYTHON VIRTUALENV # Maybe the backup contains a other Python version #================================================= -ynh_script_progression --message="Upgrade Python virtualenv..." --weight=50 +ynh_script_progression --message="Recreate Python virtualenv..." --weight=5 # Always recreate everything fresh with current python version ynh_secure_remove "${final_path}/venv" @@ -93,6 +91,10 @@ ynh_secure_remove "${final_path}/venv" python3 -m venv --without-pip "${final_path}/venv" chown -R "$app:" "$final_path" +#================================================= +# PIP INSTALLATION +#================================================= +ynh_script_progression --message="Install project via pip..." --weight=45 #run source in a 'sub shell' ( set +o nounset diff --git a/scripts/upgrade b/scripts/upgrade index e1334a3..d211280 100755 --- a/scripts/upgrade +++ b/scripts/upgrade @@ -26,10 +26,34 @@ db_pwd=$(ynh_app_setting_get --app="$app" --key=psqlpwd) db_name=$(ynh_sanitize_dbid --db_name="$app") db_user=$db_name -admin_mail=$(ynh_user_get_info "$admin" mail) redis_db=$(ynh_app_setting_get --app="$app" --key=redis_db) -django_debug=$(ynh_app_setting_get --app="$app" --key=django_debug) +#------------------------------------------------- +# config_panel.toml settings: + +debug_enabled=$(ynh_app_setting_get --app="$app" --key=debug_enabled) +if [ -z "$debug_enabled" ]; then + debug_enabled="0" + ynh_app_setting_set --app="$app" --key=debug_enabled --value="$debug_enabled" +fi + +log_level=$(ynh_app_setting_get --app="$app" --key=log_level) +if [ -z "$log_level" ]; then + log_level="WARNING" + ynh_app_setting_set --app="$app" --key=log_level --value="$log_level" +fi + +admin_email=$(ynh_app_setting_get --app="$app" --key=admin_email) +if [ -z "$admin_email" ]; then + admin_email="${admin}@${domain}" + ynh_app_setting_set --app="$app" --key=admin_email --value="$admin_email" +fi + +default_from_email=$(ynh_app_setting_get --app="$app" --key=default_from_email) +if [ -z "$default_from_email" ]; then + default_from_email="${app}@${domain}" + ynh_app_setting_set --app="$app" --key=default_from_email --value="$default_from_email" +fi #================================================= # BACKUP BEFORE UPGRADE THEN ACTIVE TRAP @@ -54,20 +78,14 @@ ynh_script_progression --message="Stopping systemd services..." --weight=5 ynh_systemd_action --service_name="$app" --action="stop" -#================================================= -# Manage old settings -#================================================= - -# Always deactivate settings.DEBUG: -ynh_app_setting_set --app=$app --key=django_debug --value="False" - #================================================= # NGINX CONFIGURATION #================================================= ynh_script_progression --message="Upgrading nginx web server configuration..." # Create a dedicated nginx config -# https://github.com/YunoHost/yunohost/blob/dev/data/helpers.d/nginx +# https://yunohost.org/en/contribute/packaging_apps/helpers +# https://github.com/YunoHost/yunohost/blob/dev/helpers/nginx ynh_add_nginx_config "public_path" "port" #================================================= @@ -92,12 +110,12 @@ ynh_system_user_create --username="$app" --home_dir="$final_path" --use_shell #================================================= ynh_script_progression --message="Configuring a systemd service..." -ynh_add_systemd_config --service="$app" --template="django-fmd.service" +ynh_add_systemd_config --service="$app" --template="systemd.service" #================================================= -# UPGRADE VENV +# PYTHON VIRTUALENV #================================================= -ynh_script_progression --message="Upgrade project via pip..." --weight=50 +ynh_script_progression --message="Recreate Python virtualenv..." --weight=5 # Always recreate everything fresh with current python version ynh_secure_remove "${final_path}/venv" @@ -108,6 +126,10 @@ python3 -m venv --without-pip "${final_path}/venv" cp ../conf/requirements.txt "$final_path/requirements.txt" chown -R "$app:" "$final_path" +#================================================= +# PIP INSTALLATION +#================================================= +ynh_script_progression --message="Install project via pip..." --weight=45 #run source in a 'sub shell' ( set +o nounset @@ -123,54 +145,15 @@ chown -R "$app:" "$final_path" # ================================================ ynh_script_progression --message="Create project configuration files..." -gunicorn_conf="$final_path/gunicorn.conf.py" -ynh_backup_if_checksum_is_different --file="$gunicorn_conf" -cp "../conf/gunicorn.conf.py" "$gunicorn_conf" -ynh_replace_string --match_string="__FINAL_HOME_PATH__" --replace_string="$final_path" --target_file="$gunicorn_conf" -ynh_replace_string --match_string="__LOG_FILE__" --replace_string="$log_file" --target_file="$gunicorn_conf" -ynh_replace_string --match_string="__PORT__" --replace_string="$port" --target_file="$gunicorn_conf" -ynh_store_file_checksum --file="$gunicorn_conf" +ynh_add_config --template="gunicorn.conf.py" --destination="$final_path/gunicorn.conf.py" -cp ../conf/manage.py "$final_path/manage.py" -ynh_replace_string --match_string="__FINAL_HOME_PATH__" --replace_string="$final_path" --target_file="$final_path/manage.py" -ynh_store_file_checksum --file="$final_path/manage.py" +ynh_add_config --template="manage.py" --destination="$final_path/manage.py" chmod +x "$final_path/manage.py" -# save old settings file -settings="$final_path/settings.py" -ynh_backup_if_checksum_is_different --file="$settings" - -cp "../conf/settings.py" "$settings" - -ynh_replace_string --match_string="__APP__" --replace_string="$app" --target_file="$settings" -ynh_replace_string --match_string="__DB_NAME__" --replace_string="$db_name" --target_file="$settings" -ynh_replace_string --match_string="__DB_USER__" --replace_string="$db_user" --target_file="$settings" -ynh_replace_string --match_string="__DB_PWD__" --replace_string="$db_pwd" --target_file="$settings" -ynh_replace_string --match_string="__ADMIN__" --replace_string="$admin" --target_file="$settings" -ynh_replace_string --match_string="__ADMINMAIL__" --replace_string="$admin_mail" --target_file="$settings" -ynh_replace_string --match_string="__FINAL_HOME_PATH__" --replace_string="$final_path" --target_file="$settings" -ynh_replace_string --match_string="__FINAL_WWW_PATH__" --replace_string="$public_path" --target_file="$settings" -ynh_replace_string --match_string="__LOG_FILE__" --replace_string="$log_file" --target_file="$settings" -ynh_replace_string --match_string="__REDIS_DB__" --replace_string="$redis_db" --target_file="$settings" - -ynh_replace_string --match_string="__DOMAIN__" --replace_string="$domain" --target_file="$settings" -ynh_replace_string --match_string="__PATH_URL__" --replace_string="$path_url" --target_file="$settings" - -# Recalculate and store the config file checksum into the app settings -ynh_store_file_checksum --file="$settings" - -ynh_backup_if_checksum_is_different --file="$final_path/setup_user.py" -cp ../conf/setup_user.py "$final_path/setup_user.py" - -ynh_backup_if_checksum_is_different --file="$final_path/urls.py" -cp ../conf/urls.py "$final_path/urls.py" - -ynh_backup_if_checksum_is_different --file="$final_path/wsgi.py" -cp ../conf/wsgi.py "$final_path/wsgi.py" - -# Regenerate a fresh local_settings: -ynh_backup_if_checksum_is_different --file="$final_path/local_settings.py" -ynh_add_config --template="local_settings.py" --destination="$final_path/local_settings.py" +ynh_add_config --template="settings.py" --destination="$final_path/settings.py" +ynh_add_config --template="setup_user.py" --destination="$final_path/setup_user.py" +ynh_add_config --template="urls.py" --destination="$final_path/urls.py" +ynh_add_config --template="wsgi.py" --destination="$final_path/wsgi.py" #================================================= # MIGRATE PYINVENTORY @@ -186,7 +169,7 @@ cd "$final_path" || exit ./manage.py collectstatic --no-input # Create/update Django superuser (set unusable password, because auth done via SSOwat): -./manage.py create_superuser --username="$admin" --email="$admin_mail" +./manage.py create_superuser --username="$admin" --email="$(ynh_user_get_info "$admin" mail)" # Check the configuration # This may fail in some cases with errors, etc., but the app works and the user can fix issues later. @@ -224,9 +207,9 @@ chmod o-rwx "$public_path" chmod o-rwx "$final_path" #================================================= -# Start django-fmd via systemd +# Start the app server via systemd #================================================= -ynh_script_progression --message="Starting django-fmd's services..." --weight=5 +ynh_script_progression --message="Starting django_example_ynh's services..." --weight=5 ynh_systemd_action --service_name="$app" --action="start" diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 0000000..75f20f0 --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,43 @@ +""" + Special pytest init: + + - Build a "local_test" YunoHost installation + - init Django with this local test installation + + So the pytests will run against this local test installation +""" +import os +import sys +from pathlib import Path + +import django +from django_yunohost_integration.local_test import create_local_test + + +BASE_PATH = Path(__file__).parent.parent + +os.environ['DJANGO_SETTINGS_MODULE'] = 'settings' + + +def pytest_configure(): + print('Compile YunoHost files...') + final_path = create_local_test( + django_settings_path=BASE_PATH / 'conf' / 'settings.py', + destination=BASE_PATH / 'local_test', + runserver=False, + extra_replacements={ + '__DEBUG_ENABLED__': '0', + '__LOG_LEVEL__': 'INFO', + '__ADMIN_EMAIL__': 'foo-bar@test.tld', + '__DEFAULT_FROM_EMAIL__': 'django_app@test.tld', + }, + ) + print('Local test files created here:') + print(f'"{final_path}"') + + os.chdir(final_path) + final_home_str = str(final_path) + if final_home_str not in sys.path: + sys.path.insert(0, final_home_str) + + django.setup() diff --git a/tests/test_django_project.py b/tests/test_django_project.py index b8d4a2a..8908220 100644 --- a/tests/test_django_project.py +++ b/tests/test_django_project.py @@ -29,6 +29,9 @@ class DjangoYnhTestCase(HtmlAssertionMixin, TestCase): assert isinstance(settings, LazySettings) assert settings.configured is True + assert 'django_yunohost_integration' in settings.INSTALLED_APPS + assert 'findmydevice.apps.FindMyDeviceConfig' in settings.INSTALLED_APPS + assert settings.PATH_URL == 'app_path' def assert_path(path, end_text): @@ -36,11 +39,17 @@ class DjangoYnhTestCase(HtmlAssertionMixin, TestCase): path = str(path) assert path.endswith(end_text) - assert_path(settings.FINAL_HOME_PATH, '/local_test/opt_yunohost') - assert_path(settings.FINAL_WWW_PATH, '/local_test/var_www') + assert_path(settings.FINALPATH, '/local_test/opt_yunohost') + assert_path(settings.PUBLIC_PATH, '/local_test/var_www') assert_path(settings.LOG_FILE, '/local_test/var_log_django-fmd.log') - assert settings.ROOT_URLCONF == 'urls' + assert settings.ROOT_URLCONF == 'urls' # ./conf/urls.py + + middlewares = [entry.rsplit('.', 1)[-1] for entry in settings.MIDDLEWARE] + assert 'SSOwatRemoteUserMiddleware' in middlewares + + auth_backends = [entry.rsplit('.', 1)[-1] for entry in settings.AUTHENTICATION_BACKENDS] + assert 'SSOwatUserBackend' in auth_backends def test_urls(self): assert reverse('admin:index') == '/app_path/admin/' diff --git a/tests/test_project_setup.py b/tests/test_project_setup.py index d1a3d5c..9ea54fd 100644 --- a/tests/test_project_setup.py +++ b/tests/test_project_setup.py @@ -53,7 +53,7 @@ def test_poetry_check(): def test_requirements_txt(): - requirements_txt = Path('conf', 'requirements.txt') + requirements_txt = PACKAGE_ROOT / 'conf' / 'requirements.txt' assert_is_file(requirements_txt) output = poetry_check_output('export', '-f', 'requirements.txt') From 90282a4a22d1b4d5e74b4b93dde626b5559d4701 Mon Sep 17 00:00:00 2001 From: JensDiemer Date: Tue, 16 Aug 2022 08:59:39 +0200 Subject: [PATCH 2/3] small changes --- config_panel.toml | 38 +++++++++++++++++++++++++++----------- manifest.json | 2 +- 2 files changed, 28 insertions(+), 12 deletions(-) diff --git a/config_panel.toml b/config_panel.toml index 565701f..0c26992 100644 --- a/config_panel.toml +++ b/config_panel.toml @@ -3,18 +3,34 @@ version = "1.0" [main] -name = "Settings" +services = ["__APP__"] - [main.findmydevice_config] - name = "django-fmd configuration" + [main.config] + name = "Configuration Options" - # Trigger a service reload-or-restart after the user change a question in this panel - services = ["__APP__"] + [main.config.default_from_email] + ask = "from email" + type = "string" + help = "Default email address to use for various automated emails." + bind = "default_from_email:__FINALPATH__/settings.py" - [main.findmydevice_config.django_debug] - ask = "debug mode" - help = "Important: Never activate this settings.DEBUG on production!" + [main.config.admin_email] + ask = "ADMIN email" + type = "string" + help = "EMail address for error emails." + bind = "admin_email:__FINALPATH__/settings.py" + + [main.config.debug_enabled] + ask = "DEBUG mode" type = "boolean" - yes = "True" - no = "False" - bind = "DJANGO_DEBUG:__FINALPATH__/local_settings.py" + yes = "1" + no = "0" + help = "Should be never enabled in production!" + bind = "debug_enabled:__FINALPATH__/settings.py" + + [main.config.log_level] + type = "string" + ask = "Log Level" + choices = ["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"] + default = "WARNING" + bind = "log_level:__FINALPATH__/settings.py" diff --git a/manifest.json b/manifest.json index 2d66dc0..ce723bf 100644 --- a/manifest.json +++ b/manifest.json @@ -23,7 +23,7 @@ }, "multi_instance": false, "services": [ - "nginx" + "nginx", "postgresql", "redis" ], "arguments": { "install" : [ From a0ac85bad25f365b024bf1074a34ca5bfa409ac4 Mon Sep 17 00:00:00 2001 From: JensDiemer Date: Tue, 16 Aug 2022 09:03:20 +0200 Subject: [PATCH 3/3] update Makefile --- Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 650fd20..82e6ab4 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,5 @@ SHELL := /bin/bash -MAX_LINE_LENGTH := 119 +MAX_LINE_LENGTH := 100 all: help @@ -45,7 +45,7 @@ tox: check-poetry ## Run pytest via tox with all environments poetry run tox pytest: install ## Run pytest - poetry run python3 ./run_pytest.py + poetry run pytest local-test: install ## Run local_test.py to run the project locally poetry run python3 ./local_test.py