Compare commits
20 Commits
5ff906086b
...
2fcc7ffc5b
Author | SHA1 | Date |
---|---|---|
|
2fcc7ffc5b | |
|
05667787de | |
|
46ecfcd62a | |
|
76f660c160 | |
|
c137765fbe | |
|
44a17d7155 | |
|
9226fb6bea | |
|
d8f21d6c0a | |
|
ef6a2b32c6 | |
|
84fef2b49a | |
|
f1df5076ed | |
|
35a2f25e71 | |
|
e510c54bd8 | |
|
c5db932065 | |
|
1484d678ce | |
|
e82d830410 | |
|
760ee9fe36 | |
|
90999bbefb | |
|
fb50ed05be | |
|
54a789b772 |
|
@ -3,4 +3,4 @@ pipeline:
|
||||||
image: python:slim
|
image: python:slim
|
||||||
commands:
|
commands:
|
||||||
- pip install black
|
- pip install black
|
||||||
- black --check --line-length 120 --target-version=py37 .
|
- black --check --line-length 120 --target-version=py39 .
|
||||||
|
|
|
@ -17,5 +17,3 @@ matrix:
|
||||||
PYTHON:
|
PYTHON:
|
||||||
- 3.10
|
- 3.10
|
||||||
- 3.9
|
- 3.9
|
||||||
- 3.8
|
|
||||||
- 3.7
|
|
||||||
|
|
38
README.md
38
README.md
|
@ -3,27 +3,33 @@
|
||||||
|
|
||||||
This is the backend of the Flaschengeist.
|
This is the backend of the Flaschengeist.
|
||||||
|
|
||||||
# Installation
|
## Installation
|
||||||
## Main package
|
### Requirements
|
||||||
### System dependencies
|
- `mysql` or `mariadb`
|
||||||
- **python 3.7+**
|
- maybe `libmariadb` development files[1]
|
||||||
- Database (MySQL / mariadb by default)
|
- python 3.9+
|
||||||
|
- pip 21.0+
|
||||||
|
|
||||||
By default Flaschengeist uses mysql as database backend,
|
*[1] By default Flaschengeist uses mysql as database backend, if you are on Windows Flaschengeist uses `PyMySQL`, but on
|
||||||
if you are on Windows Flaschengeist uses `PyMySQL`, which does not require any other system packages.
|
Linux / Mac the faster `mysqlclient` is used, if it is not already installed installing from pypi requires the
|
||||||
|
development files for `libmariadb` to be present on your system.*
|
||||||
But on Linux / Mac / *nix the faster `mysqlclient` is used,
|
|
||||||
if it is not already installed, installing from PyPi requires the
|
|
||||||
development files for `libmariadb` to be present on your system.
|
|
||||||
|
|
||||||
### Install python files
|
### Install python files
|
||||||
pip3 install --user .
|
It is recommended to upgrade pip to the latest version before installing:
|
||||||
|
|
||||||
|
python -m pip install --upgrade pip
|
||||||
|
|
||||||
|
Default installation with *mariadb*/*mysql* support:
|
||||||
|
|
||||||
|
pip3 install --user ".[mysql]"
|
||||||
|
|
||||||
or with ldap support
|
or with ldap support
|
||||||
|
|
||||||
pip3 install --user ".[ldap]"
|
pip3 install --user ".[ldap]"
|
||||||
|
|
||||||
or if you want to also run the tests:
|
or if you want to also run the tests:
|
||||||
|
|
||||||
pip3 install --user ".[ldap,test]"
|
pip3 install --user ".[ldap,tests]"
|
||||||
|
|
||||||
You will also need a MySQL driver, by default one of this is installed:
|
You will also need a MySQL driver, by default one of this is installed:
|
||||||
- `mysqlclient` (non Windows)
|
- `mysqlclient` (non Windows)
|
||||||
|
@ -75,6 +81,12 @@ So you have to configure one of the following options to call flaschengeists CRO
|
||||||
- Cons: Uses one of the webserver threads while executing
|
- Cons: Uses one of the webserver threads while executing
|
||||||
|
|
||||||
### Run
|
### Run
|
||||||
|
Flaschengeist provides a CLI, based on the flask CLI, respectivly called `flaschengeist`.
|
||||||
|
|
||||||
|
⚠️ When using the CLI for running Flaschengeist, please note that logging will happen as configured,
|
||||||
|
with the difference of the main logger will be forced to output to `stderr` and the logging level
|
||||||
|
of the CLI will override the logging level you have configured for the main logger.
|
||||||
|
|
||||||
$ flaschengeist run
|
$ flaschengeist run
|
||||||
or with debug messages:
|
or with debug messages:
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,9 @@
|
||||||
"""Flaschengeist"""
|
"""Flaschengeist"""
|
||||||
import logging
|
import logging
|
||||||
import pkg_resources
|
from importlib.metadata import version
|
||||||
from werkzeug.local import LocalProxy
|
|
||||||
|
|
||||||
__version__ = pkg_resources.get_distribution("flaschengeist").version
|
__version__ = version("flaschengeist")
|
||||||
__pdoc__ = {}
|
__pdoc__ = {}
|
||||||
|
|
||||||
logger: logging.Logger = LocalProxy(lambda: logging.getLogger(__name__))
|
logger = logging.getLogger(__name__)
|
||||||
__pdoc__["logger"] = "Flaschengeist's logger instance (`werkzeug.local.LocalProxy`)"
|
__pdoc__["logger"] = "Flaschengeist's logger instance (`werkzeug.local.LocalProxy`)"
|
||||||
|
|
|
@ -7,12 +7,12 @@ from flask.json import JSONEncoder, jsonify
|
||||||
from sqlalchemy.exc import OperationalError
|
from sqlalchemy.exc import OperationalError
|
||||||
from werkzeug.exceptions import HTTPException
|
from werkzeug.exceptions import HTTPException
|
||||||
|
|
||||||
|
from flaschengeist import logger
|
||||||
|
from flaschengeist.utils.hook import Hook
|
||||||
|
from flaschengeist.plugins import AuthPlugin, Plugin
|
||||||
from flaschengeist.utils.plugin import get_plugins
|
from flaschengeist.utils.plugin import get_plugins
|
||||||
|
from flaschengeist.controller import roleController
|
||||||
from . import logger
|
from flaschengeist.config import config, configure_app
|
||||||
from .plugins import Plugin
|
|
||||||
from .config import config, configure_app
|
|
||||||
from .utils.hook import Hook
|
|
||||||
|
|
||||||
|
|
||||||
class CustomJSONEncoder(JSONEncoder):
|
class CustomJSONEncoder(JSONEncoder):
|
||||||
|
@ -67,14 +67,14 @@ def load_plugins(app: Flask):
|
||||||
|
|
||||||
|
|
||||||
def create_app(test_config=None, cli=False):
|
def create_app(test_config=None, cli=False):
|
||||||
app = Flask(__name__)
|
app = Flask("flaschengeist")
|
||||||
app.json_encoder = CustomJSONEncoder
|
app.json_encoder = CustomJSONEncoder
|
||||||
CORS(app)
|
CORS(app)
|
||||||
|
|
||||||
with app.app_context():
|
with app.app_context():
|
||||||
from flaschengeist.database import db, migrate
|
from flaschengeist.database import db, migrate
|
||||||
|
|
||||||
configure_app(app, test_config, cli)
|
configure_app(app, test_config)
|
||||||
db.init_app(app)
|
db.init_app(app)
|
||||||
migrate.init_app(app, db, compare_type=True)
|
migrate.init_app(app, db, compare_type=True)
|
||||||
load_plugins(app)
|
load_plugins(app)
|
||||||
|
|
|
@ -1,7 +1,14 @@
|
||||||
|
from os import environ
|
||||||
|
import sys
|
||||||
import click
|
import click
|
||||||
|
import logging
|
||||||
|
|
||||||
from flask.cli import FlaskGroup, with_appcontext
|
from flask.cli import FlaskGroup, with_appcontext
|
||||||
|
from flaschengeist import logger
|
||||||
from flaschengeist.app import create_app
|
from flaschengeist.app import create_app
|
||||||
from flaschengeist.config import configure_logger
|
|
||||||
|
LOGGING_MIN = 5 # TRACE (custom)
|
||||||
|
LOGGING_MAX = logging.ERROR
|
||||||
|
|
||||||
|
|
||||||
def get_version(ctx, param, value):
|
def get_version(ctx, param, value):
|
||||||
|
@ -23,19 +30,37 @@ def get_version(ctx, param, value):
|
||||||
ctx.exit()
|
ctx.exit()
|
||||||
|
|
||||||
|
|
||||||
|
def configure_logger(level):
|
||||||
|
"""Reconfigure main logger"""
|
||||||
|
global logger
|
||||||
|
|
||||||
|
# Handle TRACE -> meaning enable debug even for werkzeug
|
||||||
|
if level == 5:
|
||||||
|
level = 10
|
||||||
|
logging.getLogger("werkzeug").setLevel(level)
|
||||||
|
|
||||||
|
logger.setLevel(level)
|
||||||
|
environ["FG_LOGGING"] = logging.getLevelName(level)
|
||||||
|
for h in logger.handlers:
|
||||||
|
if isinstance(h, logging.StreamHandler) and h.name == "wsgi":
|
||||||
|
h.setLevel(level)
|
||||||
|
h.setStream(sys.stderr)
|
||||||
|
|
||||||
|
|
||||||
@with_appcontext
|
@with_appcontext
|
||||||
def verbosity(ctx, param, value):
|
def verbosity(ctx, param, value):
|
||||||
"""Toggle verbosity between WARNING <-> DEBUG"""
|
"""Callback: Toggle verbosity between ERROR <-> TRACE"""
|
||||||
|
|
||||||
if not value or ctx.resilient_parsing:
|
if not value or ctx.resilient_parsing:
|
||||||
return
|
return
|
||||||
configure_logger(cli=30 - max(0, min(value * 10, 20)))
|
configure_logger(LOGGING_MAX - max(LOGGING_MIN, min(value * 10, LOGGING_MAX - LOGGING_MIN)))
|
||||||
|
|
||||||
|
|
||||||
@click.group(
|
@click.group(
|
||||||
cls=FlaskGroup,
|
cls=FlaskGroup,
|
||||||
add_version_option=False,
|
add_version_option=False,
|
||||||
add_default_commands=False,
|
add_default_commands=False,
|
||||||
create_app=lambda: create_app(cli=30),
|
create_app=create_app,
|
||||||
)
|
)
|
||||||
@click.option(
|
@click.option(
|
||||||
"--version",
|
"--version",
|
||||||
|
@ -59,12 +84,15 @@ def cli():
|
||||||
|
|
||||||
|
|
||||||
def main(*args, **kwargs):
|
def main(*args, **kwargs):
|
||||||
from .plugin_cmd import plugin
|
# from .plugin_cmd import plugin
|
||||||
from .export_cmd import export
|
from .export_cmd import export
|
||||||
from .docs_cmd import docs
|
from .docs_cmd import docs
|
||||||
from .run_cmd import run
|
from .run_cmd import run
|
||||||
|
|
||||||
cli.add_command(plugin)
|
# Override logging level
|
||||||
|
environ.setdefault("FG_LOGGING", logging.getLevelName(LOGGING_MAX))
|
||||||
|
|
||||||
|
# cli.add_command(plugin)
|
||||||
cli.add_command(export)
|
cli.add_command(export)
|
||||||
cli.add_command(docs)
|
cli.add_command(docs)
|
||||||
cli.add_command(run)
|
cli.add_command(run)
|
||||||
|
|
|
@ -28,13 +28,11 @@ class PrefixMiddleware(object):
|
||||||
@click.pass_context
|
@click.pass_context
|
||||||
def run(ctx, host, port, debug):
|
def run(ctx, host, port, debug):
|
||||||
"""Run Flaschengeist using a development server."""
|
"""Run Flaschengeist using a development server."""
|
||||||
from flaschengeist.config import config, configure_logger
|
from flaschengeist.config import config
|
||||||
|
|
||||||
# re configure logger, as we are no logger in CLI mode
|
|
||||||
configure_logger()
|
|
||||||
current_app.wsgi_app = PrefixMiddleware(current_app.wsgi_app, prefix=config["FLASCHENGEIST"].get("root", ""))
|
current_app.wsgi_app = PrefixMiddleware(current_app.wsgi_app, prefix=config["FLASCHENGEIST"].get("root", ""))
|
||||||
if debug:
|
if debug:
|
||||||
environ["FLASK_DEBUG"] = "1"
|
environ["FLASK_DEBUG"] = "1"
|
||||||
environ["FLASK_ENV"] = "development"
|
environ["FLASK_ENV"] = "development"
|
||||||
|
|
||||||
ctx.invoke(run_command, host=host, port=port, debugger=debug)
|
ctx.invoke(run_command, reload=True, host=host, port=port, debugger=debug)
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
import os
|
import os
|
||||||
import toml
|
import toml
|
||||||
import logging.config
|
|
||||||
import collections.abc
|
import collections.abc
|
||||||
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
from logging.config import dictConfig
|
||||||
from werkzeug.middleware.proxy_fix import ProxyFix
|
from werkzeug.middleware.proxy_fix import ProxyFix
|
||||||
from flaschengeist import logger
|
|
||||||
|
|
||||||
|
from flaschengeist import logger
|
||||||
|
|
||||||
# Default config:
|
# Default config:
|
||||||
config = {"DATABASE": {"engine": "mysql", "port": 3306}}
|
config = {"DATABASE": {"engine": "mysql", "port": 3306}}
|
||||||
|
@ -41,36 +41,41 @@ def read_configuration(test_config):
|
||||||
update_dict(config, test_config)
|
update_dict(config, test_config)
|
||||||
|
|
||||||
|
|
||||||
def configure_logger(cli=False):
|
def configure_logger():
|
||||||
global config
|
"""Configure the logger
|
||||||
|
|
||||||
|
force_console: Force a console handler
|
||||||
|
"""
|
||||||
|
|
||||||
|
def set_level(level):
|
||||||
|
# TRACE means even with werkzeug's request traces
|
||||||
|
if isinstance(level, str) and level.lower() == "trace":
|
||||||
|
level = "DEBUG"
|
||||||
|
logger_config["loggers"]["werkzeug"] = {"level": level}
|
||||||
|
logger_config["loggers"]["flaschengeist"] = {"level": level}
|
||||||
|
logger_config["handlers"]["wsgi"]["level"] = level
|
||||||
|
|
||||||
# Read default config
|
# Read default config
|
||||||
logger_config = toml.load(Path(__file__).parent / "logging.toml")
|
logger_config = toml.load(Path(__file__).parent / "logging.toml")
|
||||||
|
|
||||||
if "LOGGING" in config:
|
if "LOGGING" in config:
|
||||||
# Override with user config
|
# Override with user config
|
||||||
update_dict(logger_config, config.get("LOGGING"))
|
update_dict(logger_config, config.get("LOGGING"))
|
||||||
# Check for shortcuts
|
# Check for shortcuts
|
||||||
if "level" in config["LOGGING"] or isinstance(cli, int):
|
if "level" in config["LOGGING"]:
|
||||||
level = cli if cli and isinstance(cli, int) else config["LOGGING"]["level"]
|
set_level(config["LOGGING"]["level"])
|
||||||
logger_config["loggers"]["flaschengeist"] = {"level": level}
|
|
||||||
logger_config["handlers"]["console"]["level"] = level
|
# Override logging, used e.g. by CLI
|
||||||
logger_config["handlers"]["file"]["level"] = level
|
if "FG_LOGGING" in os.environ:
|
||||||
if cli is True or not config["LOGGING"].get("console", True):
|
set_level(os.environ.get("FG_LOGGING", "CRITICAL"))
|
||||||
logger_config["handlers"]["console"]["level"] = "CRITICAL"
|
|
||||||
if not cli and isinstance(config["LOGGING"].get("file", False), str):
|
dictConfig(logger_config)
|
||||||
logger_config["root"]["handlers"].append("file")
|
|
||||||
logger_config["handlers"]["file"]["filename"] = config["LOGGING"]["file"]
|
|
||||||
Path(config["LOGGING"]["file"]).parent.mkdir(parents=True, exist_ok=True)
|
|
||||||
else:
|
|
||||||
del logger_config["handlers"]["file"]
|
|
||||||
logging.config.dictConfig(logger_config)
|
|
||||||
|
|
||||||
|
|
||||||
def configure_app(app, test_config=None, cli=False):
|
def configure_app(app, test_config=None):
|
||||||
global config
|
global config
|
||||||
read_configuration(test_config)
|
read_configuration(test_config)
|
||||||
|
|
||||||
configure_logger(cli)
|
configure_logger()
|
||||||
|
|
||||||
# Always enable this builtin plugins!
|
# Always enable this builtin plugins!
|
||||||
update_dict(
|
update_dict(
|
||||||
|
@ -84,10 +89,10 @@ def configure_app(app, test_config=None, cli=False):
|
||||||
)
|
)
|
||||||
|
|
||||||
if "secret_key" not in config["FLASCHENGEIST"]:
|
if "secret_key" not in config["FLASCHENGEIST"]:
|
||||||
logger.warning("No secret key was configured, please configure one for production systems!")
|
logger.critical("No secret key was configured, please configure one for production systems!")
|
||||||
app.config["SECRET_KEY"] = "0a657b97ef546da90b2db91862ad4e29"
|
raise RuntimeError("No secret key was configured")
|
||||||
else:
|
|
||||||
app.config["SECRET_KEY"] = config["FLASCHENGEIST"]["secret_key"]
|
app.config["SECRET_KEY"] = config["FLASCHENGEIST"]["secret_key"]
|
||||||
|
|
||||||
if test_config is not None:
|
if test_config is not None:
|
||||||
config["DATABASE"]["engine"] = "sqlite"
|
config["DATABASE"]["engine"] = "sqlite"
|
||||||
|
|
|
@ -0,0 +1,76 @@
|
||||||
|
"""Controller for Plugin logic
|
||||||
|
|
||||||
|
Used by plugins for setting and notification functionality.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import sqlalchemy
|
||||||
|
from ..database import db
|
||||||
|
from ..models.setting import _PluginSetting
|
||||||
|
from ..models.notification import Notification
|
||||||
|
|
||||||
|
|
||||||
|
def get_setting(plugin_id: str, name: str, **kwargs):
|
||||||
|
"""Get plugin setting from database
|
||||||
|
|
||||||
|
Args:
|
||||||
|
plugin_id: ID of the plugin
|
||||||
|
name: string identifying the setting
|
||||||
|
default: Default value
|
||||||
|
Returns:
|
||||||
|
Value stored in database (native python)
|
||||||
|
Raises:
|
||||||
|
`KeyError` if no such setting exists in the database
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
setting = (
|
||||||
|
_PluginSetting.query.filter(_PluginSetting.plugin == plugin_id).filter(_PluginSetting.name == name).one()
|
||||||
|
)
|
||||||
|
return setting.value
|
||||||
|
except sqlalchemy.orm.exc.NoResultFound:
|
||||||
|
if "default" in kwargs:
|
||||||
|
return kwargs["default"]
|
||||||
|
else:
|
||||||
|
raise KeyError
|
||||||
|
|
||||||
|
|
||||||
|
def set_setting(plugin_id: str, name: str, value):
|
||||||
|
"""Save setting in database
|
||||||
|
|
||||||
|
Args:
|
||||||
|
plugin_id: ID of the plugin
|
||||||
|
name: String identifying the setting
|
||||||
|
value: Value to be stored
|
||||||
|
"""
|
||||||
|
setting = (
|
||||||
|
_PluginSetting.query.filter(_PluginSetting.plugin == plugin_id)
|
||||||
|
.filter(_PluginSetting.name == name)
|
||||||
|
.one_or_none()
|
||||||
|
)
|
||||||
|
if setting is not None:
|
||||||
|
if value is None:
|
||||||
|
db.session.delete(setting)
|
||||||
|
else:
|
||||||
|
setting.value = value
|
||||||
|
else:
|
||||||
|
db.session.add(_PluginSetting(plugin=plugin_id, name=name, value=value))
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
|
||||||
|
def notify(plugin_id: str, user, text: str, data=None):
|
||||||
|
"""Create a new notification for an user
|
||||||
|
|
||||||
|
Args:
|
||||||
|
plugin_id: ID of the plugin
|
||||||
|
user: `flaschengeist.models.user.User` to notify
|
||||||
|
text: Visibile notification text
|
||||||
|
data: Optional data passed to the notificaton
|
||||||
|
Returns:
|
||||||
|
ID of the created `flaschengeist.models.notification.Notification`
|
||||||
|
|
||||||
|
Hint: use the data for frontend actions.
|
||||||
|
"""
|
||||||
|
if not user.deleted:
|
||||||
|
n = Notification(text=text, data=data, plugin=plugin_id, user_=user)
|
||||||
|
db.session.add(n)
|
||||||
|
db.session.commit()
|
||||||
|
return n.id
|
|
@ -12,23 +12,6 @@ root = "/api"
|
||||||
secret_key = "V3ryS3cr3t"
|
secret_key = "V3ryS3cr3t"
|
||||||
# Domain used by frontend
|
# Domain used by frontend
|
||||||
|
|
||||||
[scheduler]
|
|
||||||
# Possible values are: "passive_web" (default), "active_web" and "system"
|
|
||||||
# See documentation
|
|
||||||
# cron = "passive_web"
|
|
||||||
|
|
||||||
[LOGGING]
|
|
||||||
# You can override all settings from the logging.toml here
|
|
||||||
# E.g. override the formatters etc
|
|
||||||
#
|
|
||||||
# Logging level, possible: DEBUG INFO WARNING ERROR
|
|
||||||
level = "DEBUG"
|
|
||||||
# Logging to a file is simple, just add the path
|
|
||||||
# file = "/tmp/flaschengeist-debug.log"
|
|
||||||
file = false
|
|
||||||
# Uncomment to disable console logging
|
|
||||||
# console = false
|
|
||||||
|
|
||||||
[DATABASE]
|
[DATABASE]
|
||||||
# engine = "mysql" (default)
|
# engine = "mysql" (default)
|
||||||
host = "localhost"
|
host = "localhost"
|
||||||
|
@ -36,6 +19,22 @@ user = "flaschengeist"
|
||||||
password = "flaschengeist"
|
password = "flaschengeist"
|
||||||
database = "flaschengeist"
|
database = "flaschengeist"
|
||||||
|
|
||||||
|
[LOGGING]
|
||||||
|
# You can override all settings from the logging.toml here
|
||||||
|
# Default: Logging to WSGI stream (commonly stderr)
|
||||||
|
|
||||||
|
# Logging level, possible: TRACE DEBUG INFO WARNING ERROR CRITICAL
|
||||||
|
# On TRACE level additionally every request will get logged
|
||||||
|
level = "DEBUG"
|
||||||
|
|
||||||
|
# If you want the logger to log to a file, you could use:
|
||||||
|
#[LOGGING.handlers.file]
|
||||||
|
# class = "logging.handlers.WatchedFileHandler"
|
||||||
|
# level = "WARNING"
|
||||||
|
# formatter = "extended"
|
||||||
|
# encoding = "utf8"
|
||||||
|
# filename = "flaschengeist.log"
|
||||||
|
|
||||||
[FILES]
|
[FILES]
|
||||||
# Path for file / image uploads
|
# Path for file / image uploads
|
||||||
data_path = "./data"
|
data_path = "./data"
|
||||||
|
@ -49,6 +48,11 @@ allowed_mimetypes = [
|
||||||
"image/webp"
|
"image/webp"
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[scheduler]
|
||||||
|
# Possible values are: "passive_web" (default), "active_web" and "system"
|
||||||
|
# See documentation
|
||||||
|
# cron = "passive_web"
|
||||||
|
|
||||||
[auth_ldap]
|
[auth_ldap]
|
||||||
# Full documentation https://flaschengeist.dev/Flaschengeist/flaschengeist/wiki/plugins_auth_ldap
|
# Full documentation https://flaschengeist.dev/Flaschengeist/flaschengeist/wiki/plugins_auth_ldap
|
||||||
# host = "localhost"
|
# host = "localhost"
|
||||||
|
|
|
@ -6,22 +6,16 @@ disable_existing_loggers = false
|
||||||
|
|
||||||
[formatters]
|
[formatters]
|
||||||
[formatters.simple]
|
[formatters.simple]
|
||||||
format = "%(asctime)s - %(levelname)s - %(message)s"
|
format = "[%(asctime)s] %(levelname)s - %(message)s"
|
||||||
[formatters.extended]
|
[formatters.extended]
|
||||||
format = "%(asctime)s — %(filename)s - %(funcName)s - %(lineno)d - %(threadName)s - %(name)s — %(levelname)s — %(message)s"
|
format = "[%(asctime)s] %(levelname)s %(filename)s - %(funcName)s - %(lineno)d - %(threadName)s - %(name)s — %(message)s"
|
||||||
|
|
||||||
[handlers]
|
[handlers]
|
||||||
[handlers.console]
|
[handlers.wsgi]
|
||||||
|
stream = "ext://flask.logging.wsgi_errors_stream"
|
||||||
class = "logging.StreamHandler"
|
class = "logging.StreamHandler"
|
||||||
level = "DEBUG"
|
|
||||||
formatter = "simple"
|
formatter = "simple"
|
||||||
stream = "ext://sys.stderr"
|
level = "DEBUG"
|
||||||
[handlers.file]
|
|
||||||
class = "logging.handlers.WatchedFileHandler"
|
|
||||||
level = "WARNING"
|
|
||||||
formatter = "extended"
|
|
||||||
encoding = "utf8"
|
|
||||||
filename = "flaschengeist.log"
|
|
||||||
|
|
||||||
[loggers]
|
[loggers]
|
||||||
[loggers.werkzeug]
|
[loggers.werkzeug]
|
||||||
|
@ -29,4 +23,4 @@ disable_existing_loggers = false
|
||||||
|
|
||||||
[root]
|
[root]
|
||||||
level = "WARNING"
|
level = "WARNING"
|
||||||
handlers = ["console"]
|
handlers = ["wsgi"]
|
|
@ -24,7 +24,6 @@ For more information, please refer to
|
||||||
- `flaschengeist.utils.hook.HookAfter`
|
- `flaschengeist.utils.hook.HookAfter`
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import sqlalchemy
|
|
||||||
from werkzeug.exceptions import MethodNotAllowed, NotFound
|
from werkzeug.exceptions import MethodNotAllowed, NotFound
|
||||||
from flaschengeist.utils.hook import HookBefore, HookAfter
|
from flaschengeist.utils.hook import HookBefore, HookAfter
|
||||||
|
|
||||||
|
@ -167,20 +166,9 @@ class Plugin:
|
||||||
Raises:
|
Raises:
|
||||||
`KeyError` if no such setting exists in the database
|
`KeyError` if no such setting exists in the database
|
||||||
"""
|
"""
|
||||||
from flaschengeist.models.setting import _PluginSetting
|
from ..controller import pluginController
|
||||||
|
|
||||||
try:
|
return pluginController.get_setting(self.id)
|
||||||
setting = (
|
|
||||||
_PluginSetting.query.filter(_PluginSetting.plugin == self.name)
|
|
||||||
.filter(_PluginSetting.name == name)
|
|
||||||
.one()
|
|
||||||
)
|
|
||||||
return setting.value
|
|
||||||
except sqlalchemy.orm.exc.NoResultFound:
|
|
||||||
if "default" in kwargs:
|
|
||||||
return kwargs["default"]
|
|
||||||
else:
|
|
||||||
raise KeyError
|
|
||||||
|
|
||||||
def set_setting(self, name: str, value):
|
def set_setting(self, name: str, value):
|
||||||
"""Save setting in database
|
"""Save setting in database
|
||||||
|
@ -189,22 +177,9 @@ class Plugin:
|
||||||
name: String identifying the setting
|
name: String identifying the setting
|
||||||
value: Value to be stored
|
value: Value to be stored
|
||||||
"""
|
"""
|
||||||
from flaschengeist.models.setting import _PluginSetting
|
from ..controller import pluginController
|
||||||
from flaschengeist.database import db
|
|
||||||
|
|
||||||
setting = (
|
return pluginController.set_setting(self.id, name, value)
|
||||||
_PluginSetting.query.filter(_PluginSetting.plugin == self.name)
|
|
||||||
.filter(_PluginSetting.name == name)
|
|
||||||
.one_or_none()
|
|
||||||
)
|
|
||||||
if setting is not None:
|
|
||||||
if value is None:
|
|
||||||
db.session.delete(setting)
|
|
||||||
else:
|
|
||||||
setting.value = value
|
|
||||||
else:
|
|
||||||
db.session.add(_PluginSetting(plugin=self.name, name=name, value=value))
|
|
||||||
db.session.commit()
|
|
||||||
|
|
||||||
def notify(self, user, text: str, data=None):
|
def notify(self, user, text: str, data=None):
|
||||||
"""Create a new notification for an user
|
"""Create a new notification for an user
|
||||||
|
@ -218,14 +193,9 @@ class Plugin:
|
||||||
|
|
||||||
Hint: use the data for frontend actions.
|
Hint: use the data for frontend actions.
|
||||||
"""
|
"""
|
||||||
from flaschengeist.models.notification import Notification
|
from ..controller import pluginController
|
||||||
from flaschengeist.database import db
|
|
||||||
|
|
||||||
if not user.deleted:
|
return pluginController.notify(self.id, user, text, data)
|
||||||
n = Notification(text=text, data=data, plugin=self.id, user_=user)
|
|
||||||
db.session.add(n)
|
|
||||||
db.session.commit()
|
|
||||||
return n.id
|
|
||||||
|
|
||||||
def serialize(self):
|
def serialize(self):
|
||||||
"""Serialize a plugin into a dict
|
"""Serialize a plugin into a dict
|
||||||
|
@ -331,7 +301,9 @@ class AuthPlugin(Plugin):
|
||||||
MethodNotAllowed: If not supported by Backend
|
MethodNotAllowed: If not supported by Backend
|
||||||
Any valid HTTP exception
|
Any valid HTTP exception
|
||||||
"""
|
"""
|
||||||
from flaschengeist.controller import imageController
|
# By default save the image to the avatar,
|
||||||
|
# deleting would happen by unsetting it
|
||||||
|
from ..controller import imageController
|
||||||
|
|
||||||
user.avatar_ = imageController.upload_image(file)
|
user.avatar_ = imageController.upload_image(file)
|
||||||
|
|
||||||
|
|
|
@ -105,7 +105,7 @@ def frontend(userid, current_session):
|
||||||
raise Forbidden
|
raise Forbidden
|
||||||
|
|
||||||
if request.method == "POST":
|
if request.method == "POST":
|
||||||
if request.content_length > 1024 ** 2:
|
if request.content_length > 1024**2:
|
||||||
raise BadRequest
|
raise BadRequest
|
||||||
current_session.user_.set_attribute("frontend", request.get_json())
|
current_session.user_.set_attribute("frontend", request.get_json())
|
||||||
return no_content()
|
return no_content()
|
||||||
|
|
Loading…
Reference in New Issue