Merge pull request #63 from jr-k/develop

Release v1.15
This commit is contained in:
JRK 2024-05-18 20:43:56 +02:00 committed by GitHub
commit 260366fb01
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
28 changed files with 392 additions and 321 deletions

View File

@ -6,7 +6,6 @@ out/
data/uploads/* data/uploads/*
!data/uploads/sample.jpg !data/uploads/sample.jpg
data/db/* data/db/*
!data/db/slideshow.json.dist
/plugins/user/* /plugins/user/*
!/plugins/user/.gitkeep !/plugins/user/.gitkeep
*.lock *.lock

1
.gitignore vendored
View File

@ -6,7 +6,6 @@ out/
data/uploads/* data/uploads/*
!data/uploads/sample.jpg !data/uploads/sample.jpg
data/db/* data/db/*
!data/db/slideshow.json.dist
/plugins/user/* /plugins/user/*
!/plugins/user/.gitkeep !/plugins/user/.gitkeep
*.lock *.lock

View File

@ -1,9 +1,11 @@
FROM python:3.9.17-alpine3.17 FROM python:3.9.17-alpine3.17
RUN apk add --no-cache --virtual .build-deps gcc musl-dev sqlite-dev
WORKDIR /app WORKDIR /app
COPY . . COPY . .
RUN pip install -r requirements.txt RUN pip install -r requirements.txt && apk del .build-deps gcc musl-dev sqlite-dev
ENTRYPOINT ["python", "/app/obscreen.py"] ENTRYPOINT ["python", "/app/obscreen.py"]

View File

@ -15,7 +15,7 @@ Use a RaspberryPi (Lite OS) to show a full-screen slideshow (Kiosk-mode)
- Clear GUI - Clear GUI
- Fleet view to manage many devices easily - Fleet view to manage many devices easily
- Very few dependencies - Very few dependencies
- JSON database files - SQLite database
- Plugin system - Plugin system
- No stupid pricing plan - No stupid pricing plan
- No cloud - No cloud

View File

@ -1,37 +0,0 @@
{
"version": 2,
"keys": [
"duration",
"enabled",
"location",
"name",
"position",
"type",
"cron_schedule",
"cron_schedule_end",
"created_by",
"updated_by",
"created_at",
"updated_at"
],
"data": {
"0": {
"location": "data/uploads/sample.jpg",
"duration": 10,
"type": "picture",
"enabled": true,
"name": "Picture Sample",
"position": 0,
"cron_schedule": null
},
"1": {
"location": "https://unix.org",
"duration": 20,
"type": "url",
"enabled": true,
"name": "URL Sample",
"position": 1,
"cron_schedule": null
}
}
}

View File

@ -9,7 +9,7 @@ jQuery(document).ready(function ($) {
if (confirm(l.js_sysinfo_restart_confirmation)) { if (confirm(l.js_sysinfo_restart_confirmation)) {
$('body').html(l.js_sysinfo_restart_loading).css({margin:200}); $('body').html(l.js_sysinfo_restart_loading).css({margin:200});
$.ajax({ $.ajax({
url: '/sysinfo/restart', url: '/sysinfo/restart?secret_key='+secret_key,
headers: {'Content-Type': 'application/json'}, headers: {'Content-Type': 'application/json'},
data: '', data: '',
method: 'POST', method: 'POST',

View File

@ -53,10 +53,10 @@ cd ~ && git clone https://github.com/jr-k/obscreen.git && cd obscreen
# Install application dependencies # Install application dependencies
python3 -m venv venv python3 -m venv venv
source ./venv/bin/activate source ./venv/bin/activate
pip install -r requirements.txt
# Add some sample data # 🚨For MacOS users, requirements installation may cause an error but it's ok if only for pysqlite3 package
cp data/db/slideshow.json.dist data/db/slideshow.json # you'll need to install brew and execute command `brew install sqlite3`
pip install -r requirements.txt
# Customize server default values # Customize server default values
cp .env.dist .env cp .env.dist .env

View File

@ -72,9 +72,6 @@ python3 -m venv venv
source ./venv/bin/activate source ./venv/bin/activate
pip install -r requirements.txt pip install -r requirements.txt
# Add some sample data
cp data/db/slideshow.json.dist data/db/slideshow.json
# Customize server default values # Customize server default values
cp .env.dist .env cp .env.dist .env
``` ```

View File

@ -1,6 +1,6 @@
flask==2.3.3 flask==2.3.3
pysondb-v2==2.1.0
python-dotenv python-dotenv
cron-descriptor cron-descriptor
waitress waitress
flask-login flask-login
pysqlite3

View File

@ -18,8 +18,9 @@ class Application:
self._model_store = ModelStore() self._model_store = ModelStore()
self._template_renderer = TemplateRenderer(project_dir=project_dir, model_store=self._model_store, render_hook=self.render_hook) self._template_renderer = TemplateRenderer(project_dir=project_dir, model_store=self._model_store, render_hook=self.render_hook)
self._web_server = WebServer(project_dir=project_dir, model_store=self._model_store, template_renderer=self._template_renderer) self._web_server = WebServer(project_dir=project_dir, model_store=self._model_store, template_renderer=self._template_renderer)
self._plugin_store = PluginStore(project_dir=project_dir, model_store=self._model_store, template_renderer=self._template_renderer, web_server=self._web_server)
logging.info("[Obscreen] Starting...")
self._plugin_store = PluginStore(project_dir=project_dir, model_store=self._model_store, template_renderer=self._template_renderer, web_server=self._web_server)
signal.signal(signal.SIGINT, self.signal_handler) signal.signal(signal.SIGINT, self.signal_handler)
def start(self) -> None: def start(self) -> None:
@ -27,6 +28,7 @@ class Application:
def signal_handler(self, signal, frame) -> None: def signal_handler(self, signal, frame) -> None:
logging.info("Shutting down...") logging.info("Shutting down...")
self._model_store.database().close()
self._stop_event.set() self._stop_event.set()
sys.exit(0) sys.exit(0)

View File

@ -24,6 +24,9 @@ class AuthController(ObController):
if current_user.is_authenticated: if current_user.is_authenticated:
return redirect(url_for('slideshow_slide_list')) return redirect(url_for('slideshow_slide_list'))
if not self._model_store.variable().map().get('auth_enabled').as_bool():
return redirect(url_for('slideshow_slide_list'))
if len(request.form): if len(request.form):
user = self._model_store.user().get_one_by_username(request.form['username'], enabled=True) user = self._model_store.user().get_one_by_username(request.form['username'], enabled=True)
if user: if user:
@ -42,6 +45,13 @@ class AuthController(ObController):
def logout(self): def logout(self):
logout_user() logout_user()
if request.args.get('restart'):
return redirect(url_for(
'sysinfo_restart',
secret_key=self._model_store.config().map().get('secret_key')
))
return redirect(url_for('login')) return redirect(url_for('login'))
def auth_user_list(self): def auth_user_list(self):

View File

@ -24,7 +24,7 @@ class SettingsController(ObController):
forward = self._post_update(request.form['id']) forward = self._post_update(request.form['id'])
return forward if forward is not None else redirect(url_for('settings_variable_list')) return forward if forward is not None else redirect(url_for('settings_variable_list'))
def _post_update(self, id: str): def _post_update(self, id: int):
variable = self._model_store.variable().get(id) variable = self._model_store.variable().get(id)
if variable.refresh_player: if variable.refresh_player:
@ -39,7 +39,10 @@ class SettingsController(ObController):
if variable.name == 'auth_enabled': if variable.name == 'auth_enabled':
self.reload_web_server() self.reload_web_server()
if variable.as_bool(): if variable.as_bool():
return redirect(url_for('logout')) return redirect(url_for(
'logout',
restart=1
))
if variable.name == 'lang': if variable.name == 'lang':
self._model_store.lang().set_lang(variable.value) self._model_store.lang().set_lang(variable.value)

View File

@ -2,21 +2,23 @@ import os
import sys import sys
import platform import platform
import subprocess import subprocess
import threading
import time
from flask import Flask, render_template, jsonify from flask import Flask, render_template, jsonify, request, url_for, redirect
from src.manager.VariableManager import VariableManager from src.manager.VariableManager import VariableManager
from src.manager.ConfigManager import ConfigManager from src.manager.ConfigManager import ConfigManager
from src.service.ModelStore import ModelStore from src.service.ModelStore import ModelStore
from src.interface.ObController import ObController from src.interface.ObController import ObController
from src.utils import get_ip_address from src.utils import get_ip_address, am_i_in_docker
class SysinfoController(ObController): class SysinfoController(ObController):
def register(self): def register(self):
self._app.add_url_rule('/sysinfo', 'sysinfo_attribute_list', self._auth(self.sysinfo), methods=['GET']) self._app.add_url_rule('/sysinfo', 'sysinfo_attribute_list', self._auth(self.sysinfo), methods=['GET'])
self._app.add_url_rule('/sysinfo/restart', 'sysinfo_restart', self._auth(self.sysinfo_restart), methods=['POST']) self._app.add_url_rule('/sysinfo/restart', 'sysinfo_restart', self.sysinfo_restart, methods=['GET', 'POST'])
self._app.add_url_rule('/sysinfo/restart/needed', 'sysinfo_restart_needed', self._auth(self.sysinfo_restart_needed), methods=['GET']) self._app.add_url_rule('/sysinfo/restart/needed', 'sysinfo_restart_needed', self._auth(self.sysinfo_restart_needed), methods=['GET'])
def sysinfo(self): def sysinfo(self):
@ -29,20 +31,13 @@ class SysinfoController(ObController):
) )
def sysinfo_restart(self): def sysinfo_restart(self):
if platform.system().lower() == 'darwin': secret = self._model_store.config().map().get('secret_key')
if self._model_store.config().map().get('debug'): challenge = request.args.get('secret_key')
python = sys.executable thread = threading.Thread(target=self.restart, args=(secret, challenge))
os.execl(python, python, *sys.argv) thread.daemon = True
else: thread.start()
try:
subprocess.run(["sudo", "systemctl", "restart", 'obscreen'], check=True, timeout=10, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
pass
except subprocess.TimeoutExpired:
pass
except subprocess.CalledProcessError:
pass
return jsonify({'status': 'ok'}) return redirect(url_for('manage'))
def sysinfo_restart_needed(self): def sysinfo_restart_needed(self):
var_last_slide_update = self._model_store.variable().get_one_by_name('last_slide_update') var_last_slide_update = self._model_store.variable().get_one_by_name('last_slide_update')
@ -53,3 +48,24 @@ class SysinfoController(ObController):
return jsonify({'status': True}) return jsonify({'status': True})
def restart(self, secret: str, challenge: str) -> None:
time.sleep(1)
if secret != challenge:
return jsonify({'status': 'error'})
if platform.system().lower() == 'darwin':
if self._model_store.config().map().get('debug'):
python = sys.executable
os.execl(python, python, *sys.argv)
elif am_i_in_docker:
python = sys.executable
os.execl(python, python, *sys.argv)
else:
try:
subprocess.run(["sudo", "systemctl", "restart", 'obscreen-manager'], check=True, timeout=10, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
pass
except subprocess.TimeoutExpired:
pass
except subprocess.CalledProcessError:
pass

View File

@ -1,33 +1,144 @@
import os
import json import json
import sys import sqlite3
import logging
from pysondb import PysonDB
from typing import Optional
from sqlite3 import Cursor
from src.utils import wrap_if, is_wrapped_by
from typing import Optional, Dict
class DatabaseManager: class DatabaseManager:
DB_DIR = 'data/db' DB_FILE: str = "data/db/obscreen.db"
def __init__(self): def __init__(self):
pass self._conn = None
self._enabled = True
self.init()
def open(self, table_name: str, table_model: list) -> PysonDB: def init(self):
db_file = "{}/{}.json".format(self.DB_DIR, table_name) logging.info('Using DB engine {}'.format(self.__class__.__name__))
db = PysonDB(db_file) self._open()
db = self._update_model(db_file, table_model)
return db def _open(self, flush: bool = False) -> None:
if flush and os.path.isfile(self.DB_FILE):
os.unlink(self.DB_FILE)
self._conn = sqlite3.connect(self.DB_FILE, check_same_thread=False)
self._conn.row_factory = sqlite3.Row
def open(self, table_name: str, table_model: list):
self.execute_write_query('''CREATE TABLE IF NOT EXISTS {} (
id INTEGER PRIMARY KEY AUTOINCREMENT,
{}
)'''.format(table_name, ", ".join(table_model)))
return self
def close(self) -> None:
self._conn.close()
def __enter__(self):
return self
def __exit__(self, exc_type, exc_val, exc_tb):
self.close()
def get_connection(self):
return self._conn
def execute_write_query(self, query, params=()) -> None:
logging.debug(query)
cur = None
sanitized_params = []
for param in params:
if isinstance(param, bool):
sanitized_params.append(int(param))
elif isinstance(param, dict) or isinstance(param, list):
sanitized_params.append(json.dumps(param))
else:
sanitized_params.append(param)
@staticmethod
def _update_model(db_file: str, table_model: list) -> Optional[PysonDB]:
try: try:
with open(db_file, 'r') as file: with self._conn:
db_model = file.read() cur = self._conn.cursor()
db_model = json.loads(db_model) cur.execute(query, tuple(sanitized_params))
db_model['keys'] = table_model except sqlite3.Error as e:
with open(db_file, 'w') as file: logging.error("SQL query execution error while writing '{}': {}".format(query, e))
file.write(json.dumps(db_model, indent=4)) self._conn.rollback()
return PysonDB(db_file) finally:
except FileNotFoundError: if cur is not None:
logging.error("Database file {} not found".format(db_file)) cur.close()
return None
def execute_read_query(self, query, params=()) -> list:
logging.debug(query)
cur = None
try:
with self._conn:
cur = self._conn.cursor()
cur.execute(query, params)
rows = cur.fetchall()
result = [dict(row) for row in rows]
except sqlite3.Error as e:
logging.error("SQL query execution error while reading '{}': {}".format(query, e))
result = []
finally:
if cur is not None:
cur.close()
return result
def get_all(self, table_name: str, sort: Optional[str] = None) -> list:
return self.execute_read_query(
query="select * from {} {}".format(table_name, "ORDER BY {} ASC".format(sort) if sort else "")
)
def get_by_query(self, table_name: str, query: str = "1=1", sort: Optional[str] = None) -> list:
return self.execute_read_query(
query="select * from {} where {} {}".format(
table_name,
query,
"ORDER BY {} ASC".format(sort) if sort else ""
)
)
def get_one_by_query(self, table_name: str, query: str = "1=1", sort: Optional[str] = None) -> list:
query = "select * from {} where {} {}".format(table_name, query, "ORDER BY {} ASC".format(sort) if sort else "")
lines = self.execute_read_query(query=query)
count = len(lines)
if count > 1:
raise Error("More than one line returned by query '{}'".format(query))
return lines[0] if count == 1 else None
def update_by_query(self, table_name: str, query: str = "1=1", values: dict = {}) -> list:
return self.execute_write_query(
query="UPDATE {} SET {} where {}".format(
table_name,
" , ".join(["{} = ?".format(k, v) for k, v in values.items()]),
query
),
params=tuple(v for v in values.values())
)
def update_by_id(self, table_name: str, id: int, values: dict = {}) -> list:
return self.update_by_query(table_name, "id = {}".format(id), values)
def get_by_id(self, table_name: str, id: int) -> Optional[Dict]:
return self.get_one_by_query(table_name, "id = {}".format(id))
def add(self, table_name: str, values: dict) -> None:
self.execute_write_query(
query="INSERT INTO {} ({}) VALUES ({})".format(
table_name,
", ".join(["{}".format(key) for key in values.keys()]),
", ".join(["?" for _ in values.keys()]),
),
params=tuple(v for v in values.values())
)
def delete_by_id(self, table_name: str, id: int) -> None:
self.execute_write_query("DELETE FROM {} WHERE id = ?".format(table_name), params=(id,))

View File

@ -1,4 +1,3 @@
from pysondb.errors import IdDoesNotExistError
from typing import Dict, Optional, List, Tuple, Union from typing import Dict, Optional, List, Tuple, Union
from src.model.entity.Screen import Screen from src.model.entity.Screen import Screen
@ -12,71 +11,59 @@ class ScreenManager(ModelManager):
TABLE_NAME = "fleet" TABLE_NAME = "fleet"
TABLE_MODEL = [ TABLE_MODEL = [
"name", "name CHAR(255)",
"enabled", "enabled INTEGER",
"position", "position INTEGER",
"host", "host CHAR(255)",
"port" "port INTEGER"
] ]
def __init__(self, lang_manager: LangManager, database_manager: DatabaseManager, user_manager: UserManager): def __init__(self, lang_manager: LangManager, database_manager: DatabaseManager, user_manager: UserManager):
super().__init__(lang_manager, database_manager, user_manager) super().__init__(lang_manager, database_manager, user_manager)
self._db = database_manager.open(self.TABLE_NAME, self.TABLE_MODEL) self._db = database_manager.open(self.TABLE_NAME, self.TABLE_MODEL)
def hydrate_object(self, raw_screen: dict, id: Optional[str] = None) -> Screen: def hydrate_object(self, raw_screen: dict, id: Optional[int] = None) -> Screen:
if id: if id:
raw_screen['id'] = id raw_screen['id'] = id
return Screen(**raw_screen) return Screen(**raw_screen)
def hydrate_dict(self, raw_screens: dict) -> List[Screen]:
return [self.hydrate_object(raw_screen, raw_id) for raw_id, raw_screen in raw_screens.items()]
def hydrate_list(self, raw_screens: list) -> List[Screen]: def hydrate_list(self, raw_screens: list) -> List[Screen]:
return [self.hydrate_object(raw_screen) for raw_screen in raw_screens] return [self.hydrate_object(raw_screen) for raw_screen in raw_screens]
def get(self, id: str) -> Optional[Screen]: def get(self, id: int) -> Optional[Screen]:
try: object = self._db.get_by_id(self.TABLE_NAME, id)
return self.hydrate_object(self._db.get_by_id(id), id) return self.hydrate_object(object, id) if object else None
except IdDoesNotExistError:
return None
def get_by(self, query) -> List[Screen]: def get_by(self, query, sort: Optional[str] = None) -> List[Screen]:
return self.hydrate_dict(self._db.get_by_query(query=query)) return self.hydrate_list(self._db.get_by_query(self.TABLE_NAME, query=query, sort=sort))
def get_one_by(self, query) -> Optional[Screen]: def get_one_by(self, query) -> Optional[Screen]:
screens = self.hydrate_dict(self._db.get_by_query(query=query)) object = self._db.get_one_by_query(self.TABLE_NAME, query=query)
if len(screens) == 1:
return screens[0] if not object:
elif len(screens) > 1: return None
raise Error("More than one result for query")
return None return self.hydrate_object(object)
def get_all(self, sort: bool = False) -> List[Screen]: def get_all(self, sort: bool = False) -> List[Screen]:
raw_screens = self._db.get_all() return self.hydrate_list(self._db.get_all(self.TABLE_NAME, "position" if sort else None))
if isinstance(raw_screens, dict):
if sort:
return sorted(self.hydrate_dict(raw_screens), key=lambda x: x.position)
return self.hydrate_dict(raw_screens)
return self.hydrate_list(sorted(raw_screens, key=lambda x: x['position']) if sort else raw_screens)
def get_enabled_screens(self) -> List[Screen]: def get_enabled_screens(self) -> List[Screen]:
return [screen for screen in self.get_all(sort=True) if screen.enabled] return self.get_by(query="enabled = 1", sort="position")
def get_disabled_screens(self) -> List[Screen]: def get_disabled_screens(self) -> List[Screen]:
return [screen for screen in self.get_all(sort=True) if not screen.enabled] return self.get_by(query="enabled = 0", sort="position")
def update_enabled(self, id: str, enabled: bool) -> None: def update_enabled(self, id: int, enabled: bool) -> None:
self._db.update_by_id(id, {"enabled": enabled, "position": 999}) self._db.update_by_id(self.TABLE_NAME, id, {"enabled": enabled, "position": 999})
def update_positions(self, positions: list) -> None: def update_positions(self, positions: list) -> None:
for screen_id, screen_position in positions.items(): for screen_id, screen_position in positions.items():
self._db.update_by_id(screen_id, {"position": screen_position}) self._db.update_by_id(self.TABLE_NAME, screen_id, {"position": screen_position})
def update_form(self, id: str, name: str, host: str, port: int) -> None: def update_form(self, id: int, name: str, host: str, port: int) -> None:
self._db.update_by_id(id, {"name": name, "host": host, "port": port}) self._db.update_by_id(self.TABLE_NAME, id, {"name": name, "host": host, "port": port})
def add_form(self, screen: Union[Screen, Dict]) -> None: def add_form(self, screen: Union[Screen, Dict]) -> None:
form = screen form = screen
@ -85,10 +72,10 @@ class ScreenManager(ModelManager):
form = screen.to_dict() form = screen.to_dict()
del form['id'] del form['id']
self._db.add(form) self._db.add(self.TABLE_NAME, form)
def delete(self, id: str) -> None: def delete(self, id: int) -> None:
self._db.delete_by_id(id) self._db.delete_by_id(self.TABLE_NAME, id)
def to_dict(self, screens: List[Screen]) -> List[Dict]: def to_dict(self, screens: List[Screen]) -> List[Dict]:
return [screen.to_dict() for screen in screens] return [screen.to_dict() for screen in screens]

View File

@ -1,7 +1,6 @@
import os import os
from typing import Dict, Optional, List, Tuple, Union from typing import Dict, Optional, List, Tuple, Union
from pysondb.errors import IdDoesNotExistError
from src.model.entity.Slide import Slide from src.model.entity.Slide import Slide
from src.model.enum.SlideType import SlideType from src.model.enum.SlideType import SlideType
@ -16,80 +15,68 @@ class SlideManager(ModelManager):
TABLE_NAME = "slideshow" TABLE_NAME = "slideshow"
TABLE_MODEL = [ TABLE_MODEL = [
"name", "name CHAR(255)",
"type", "type CHAR(30)",
"enabled", "enabled INTEGER",
"duration", "duration INTEGER",
"position", "position INTEGER",
"location", "location TEXT",
"cron_schedule", "cron_schedule CHAR(255)",
"cron_schedule_end", "cron_schedule_end CHAR(255)",
"created_by", "created_by CHAR(255)",
"updated_by", "updated_by CHAR(255)",
"created_at", "created_at INTEGER",
"updated_at" "updated_at INTEGER"
] ]
def __init__(self, lang_manager: LangManager, database_manager: DatabaseManager, user_manager: UserManager): def __init__(self, lang_manager: LangManager, database_manager: DatabaseManager, user_manager: UserManager):
super().__init__(lang_manager, database_manager, user_manager) super().__init__(lang_manager, database_manager, user_manager)
self._db = database_manager.open(self.TABLE_NAME, self.TABLE_MODEL) self._db = database_manager.open(self.TABLE_NAME, self.TABLE_MODEL)
def hydrate_object(self, raw_slide: dict, id: str = None) -> Slide: def hydrate_object(self, raw_slide: dict, id: int = None) -> Slide:
if id: if id:
raw_slide['id'] = id raw_slide['id'] = id
[raw_slide, user_tracker_edits] = self.user_manager.initialize_user_trackers(raw_slide) [raw_slide, user_tracker_edits] = self.user_manager.initialize_user_trackers(raw_slide)
if len(user_tracker_edits) > 0: if len(user_tracker_edits) > 0:
self._db.update_by_id(raw_slide['id'], user_tracker_edits) self._db.update_by_id(self.TABLE_NAME, raw_slide['id'], user_tracker_edits)
return Slide(**raw_slide) return Slide(**raw_slide)
def hydrate_dict(self, raw_slides: dict) -> List[Slide]:
return [self.hydrate_object(raw_slide, raw_id) for raw_id, raw_slide in raw_slides.items()]
def hydrate_list(self, raw_slides: list) -> List[Slide]: def hydrate_list(self, raw_slides: list) -> List[Slide]:
return [self.hydrate_object(raw_slide) for raw_slide in raw_slides] return [self.hydrate_object(raw_slide) for raw_slide in raw_slides]
def get(self, id: str) -> Optional[Slide]: def get(self, id: int) -> Optional[Slide]:
try: object = self._db.get_by_id(self.TABLE_NAME, id)
return self.hydrate_object(self._db.get_by_id(id), id) return self.hydrate_object(object, id) if object else None
except IdDoesNotExistError:
return None
def get_by(self, query) -> List[Slide]: def get_by(self, query, sort: Optional[str] = None) -> List[Slide]:
return self.hydrate_dict(self._db.get_by_query(query=query)) return self.hydrate_list(self._db.get_by_query(self.TABLE_NAME, query=query, sort=sort))
def get_one_by(self, query) -> Optional[Slide]: def get_one_by(self, query) -> Optional[Slide]:
slides = self.hydrate_dict(self._db.get_by_query(query=query)) object = self._db.get_one_by_query(self.TABLE_NAME, query=query)
if len(slides) == 1:
return slides[0] if not object:
elif len(slides) > 1: return None
raise Error("More than one result for query")
return None return self.hydrate_object(object)
def get_all(self, sort: bool = False) -> List[Slide]: def get_all(self, sort: bool = False) -> List[Slide]:
raw_slides = self._db.get_all() return self.hydrate_list(self._db.get_all(self.TABLE_NAME, sort="position" if sort else None))
if isinstance(raw_slides, dict): def forget_user(self, user_id: int):
if sort: slides = self.get_by("created_by = '{}' or updated_by = '{}'".format(user_id, user_id))
return sorted(self.hydrate_dict(raw_slides), key=lambda x: x.position)
return self.hydrate_dict(raw_slides)
return self.hydrate_list(sorted(raw_slides, key=lambda x: x['position']) if sort else raw_slides)
def forget_user(self, user_id: str):
slides = self.hydrate_dict(self._db.get_by_query(query=lambda s: s['created_by'] == user_id or s['updated_by'] == user_id))
edits_slides = self.user_manager.forget_user(slides, user_id) edits_slides = self.user_manager.forget_user(slides, user_id)
for slide_id, edits in edits_slides.items(): for slide_id, edits in edits_slides.items():
self._db.update_by_id(slide_id, edits) self._db.update_by_id(self.TABLE_NAME, slide_id, edits)
def get_enabled_slides(self) -> List[Slide]: def get_enabled_slides(self) -> List[Slide]:
return [slide for slide in self.get_all(sort=True) if slide.enabled] return self.get_by(query="enabled = 1", sort="position")
def get_disabled_slides(self) -> List[Slide]: def get_disabled_slides(self) -> List[Slide]:
return [slide for slide in self.get_all(sort=True) if not slide.enabled] return self.get_by(query="enabled = 0", sort="position")
def pre_add(self, slide: Dict) -> Dict: def pre_add(self, slide: Dict) -> Dict:
self.user_manager.track_user_on_create(slide) self.user_manager.track_user_on_create(slide)
@ -115,15 +102,15 @@ class SlideManager(ModelManager):
def post_delete(self, slide_id: str) -> str: def post_delete(self, slide_id: str) -> str:
return slide_id return slide_id
def update_enabled(self, id: str, enabled: bool) -> None: def update_enabled(self, id: int, enabled: bool) -> None:
self._db.update_by_id(id, self.pre_update({"enabled": enabled, "position": 999})) self._db.update_by_id(self.TABLE_NAME, id, self.pre_update({"enabled": enabled, "position": 999}))
self.post_update(id) self.post_update(id)
def update_positions(self, positions: list) -> None: def update_positions(self, positions: list) -> None:
for slide_id, slide_position in positions.items(): for slide_id, slide_position in positions.items():
self._db.update_by_id(slide_id, {"position": slide_position}) self._db.update_by_id(self.TABLE_NAME, slide_id, {"position": slide_position})
def update_form(self, id: str, name: str, duration: int, cron_schedule: Optional[str] = '', cron_schedule_end: Optional[str] = '', location: Optional[str] = None) -> None: def update_form(self, id: int, name: str, duration: int, cron_schedule: Optional[str] = '', cron_schedule_end: Optional[str] = '', location: Optional[str] = None) -> None:
slide = self.get(id) slide = self.get(id)
if not slide: if not slide:
@ -142,7 +129,7 @@ class SlideManager(ModelManager):
if slide.type == SlideType.YOUTUBE: if slide.type == SlideType.YOUTUBE:
form['location'] = get_yt_video_id(form['location']) form['location'] = get_yt_video_id(form['location'])
self._db.update_by_id(id, self.pre_update(form)) self._db.update_by_id(self.TABLE_NAME, id, self.pre_update(form))
self.post_update(id) self.post_update(id)
def add_form(self, slide: Union[Slide, Dict]) -> None: def add_form(self, slide: Union[Slide, Dict]) -> None:
@ -155,10 +142,10 @@ class SlideManager(ModelManager):
if form['type'] == SlideType.YOUTUBE.value: if form['type'] == SlideType.YOUTUBE.value:
form['location'] = get_yt_video_id(form['location']) form['location'] = get_yt_video_id(form['location'])
self._db.add(self.pre_add(form)) self._db.add(self.TABLE_NAME, self.pre_add(form))
self.post_add(slide.id) self.post_add(slide.id)
def delete(self, id: str) -> None: def delete(self, id: int) -> None:
slide = self.get(id) slide = self.get(id)
if slide: if slide:
@ -169,7 +156,7 @@ class SlideManager(ModelManager):
pass pass
self.pre_delete(id) self.pre_delete(id)
self._db.delete_by_id(id) self._db.delete_by_id(self.TABLE_NAME, id)
self.post_delete(id) self.post_delete(id)
def to_dict(self, slides: List[Slide]) -> List[Dict]: def to_dict(self, slides: List[Slide]) -> List[Dict]:

View File

@ -1,6 +1,5 @@
import hashlib import hashlib
import time import time
from pysondb.errors import IdDoesNotExistError
from typing import Dict, Optional, List, Tuple, Union from typing import Dict, Optional, List, Tuple, Union
from flask_login import current_user from flask_login import current_user
@ -14,9 +13,9 @@ class UserManager:
TABLE_NAME = "user" TABLE_NAME = "user"
TABLE_MODEL = [ TABLE_MODEL = [
"username", "username CHAR(255)",
"password", "password CHAR(255)",
"enabled" "enabled INTEGER"
] ]
def __init__(self, lang_manager: LangManager, database_manager: DatabaseManager, on_user_delete): def __init__(self, lang_manager: LangManager, database_manager: DatabaseManager, on_user_delete):
@ -44,37 +43,32 @@ class UserManager:
return user_map return user_map
def hydrate_object(self, raw_user: dict, id: Optional[str] = None) -> User: def hydrate_object(self, raw_user: dict, id: Optional[int] = None) -> User:
if id: if id:
raw_user['id'] = id raw_user['id'] = id
return User(**raw_user) return User(**raw_user)
def hydrate_dict(self, raw_users: dict) -> List[User]:
return [self.hydrate_object(raw_user, raw_id) for raw_id, raw_user in raw_users.items()]
def hydrate_list(self, raw_users: list) -> List[User]: def hydrate_list(self, raw_users: list) -> List[User]:
return [self.hydrate_object(raw_user) for raw_user in raw_users] return [self.hydrate_object(raw_user) for raw_user in raw_users]
def get(self, id: str) -> Optional[User]: def get(self, id: int) -> Optional[User]:
try: object = self._db.get_by_id(self.TABLE_NAME, id)
return self.hydrate_object(self._db.get_by_id(id), id) return self.hydrate_object(object, id) if object else None
except IdDoesNotExistError:
return None
def get_by(self, query) -> List[User]: def get_by(self, query, sort: Optional[str] = None) -> List[User]:
return self.hydrate_dict(self._db.get_by_query(query=query)) return self.hydrate_list(self._db.get_by_query(self.TABLE_NAME, query=query, sort=sort))
def get_one_by(self, query) -> Optional[User]: def get_one_by(self, query) -> Optional[User]:
users = self.hydrate_dict(self._db.get_by_query(query=query)) object = self._db.get_one_by_query(self.TABLE_NAME, query=query)
if len(users) == 1:
return users[0] if not object:
elif len(users) > 1: return None
raise Error("More than one result for query")
return None return self.hydrate_object(object)
def get_one_by_username(self, username: str, enabled: bool = None) -> Optional[User]: def get_one_by_username(self, username: str, enabled: bool = None) -> Optional[User]:
return self.get_one_by(query=lambda v: v['username'] == username and (enabled is None or v['enabled'] == enabled)) return self.get_one_by("username = '{}' and (enabled is null or enabled = {})".format(username, int(enabled)))
def count_all_enabled(self): def count_all_enabled(self):
return len(self.get_enabled_users()) return len(self.get_enabled_users())
@ -85,12 +79,16 @@ class UserManager:
def track_user_updated(self, id_or_entity: Optional[str]) -> User: def track_user_updated(self, id_or_entity: Optional[str]) -> User:
return self.track_user_action(id_or_entity, 'updated_by') return self.track_user_action(id_or_entity, 'updated_by')
def track_user_action(self, id_or_entity: Optional[str], attribute: Optional[str] = 'created_by') -> User: def track_user_action(self, id_or_entity: Optional[int], attribute: Optional[str] = 'created_by') -> User:
if not isinstance(id_or_entity, str): if not isinstance(id_or_entity, int):
id_or_entity = getattr(id_or_entity, attribute) id_or_entity = getattr(id_or_entity, attribute)
id_or_entity = str(id_or_entity)
user_map = self.map() try:
id_or_entity = int(id_or_entity)
except ValueError:
return User(username=id_or_entity, enabled=False)
user_map = self.prepare_map()
if id_or_entity in user_map: if id_or_entity in user_map:
return user_map[id_or_entity] return user_map[id_or_entity]
@ -101,20 +99,13 @@ class UserManager:
return User(username=self._lang_manager.translate('anonymous'), enabled=False) return User(username=self._lang_manager.translate('anonymous'), enabled=False)
def get_all(self, sort: bool = False) -> List[User]: def get_all(self, sort: bool = False) -> List[User]:
raw_users = self._db.get_all() return self.hydrate_list(self._db.get_all(self.TABLE_NAME, "username" if sort else None))
if isinstance(raw_users, dict):
if sort:
return sorted(self.hydrate_dict(raw_users), key=lambda x: x.username)
return self.hydrate_dict(raw_users)
return self.hydrate_list(sorted(raw_users, key=lambda x: x['username']) if sort else raw_users)
def get_enabled_users(self) -> List[User]: def get_enabled_users(self) -> List[User]:
return [user for user in self.get_all(sort=True) if user.enabled] return self.get_by(query="enabled = 1", sort="username")
def get_disabled_users(self) -> List[User]: def get_disabled_users(self) -> List[User]:
return [user for user in self.get_all(sort=True) if not user.enabled] return self.get_by(query="enabled = 0", sort="username")
def pre_add(self, user: Dict) -> Dict: def pre_add(self, user: Dict) -> Dict:
return user return user
@ -122,33 +113,33 @@ class UserManager:
def pre_update(self, user: Dict) -> Dict: def pre_update(self, user: Dict) -> Dict:
return user return user
def pre_delete(self, user_id: str) -> str: def pre_delete(self, user_id: int) -> int:
self._on_user_delete(user_id) self._on_user_delete(user_id)
return user_id return user_id
def post_add(self, user_id: str) -> str: def post_add(self, user_id: int) -> int:
self.reload() self.reload()
return user_id return user_id
def post_update(self, user_id: str) -> str: def post_update(self, user_id: int) -> int:
self.reload() self.reload()
return user_id return user_id
def post_delete(self, user_id: str) -> str: def post_delete(self, user_id: int) -> int:
self.reload() self.reload()
return user_id return user_id
def update_enabled(self, id: str, enabled: bool) -> None: def update_enabled(self, id: int, enabled: bool) -> None:
self._db.update_by_id(id, self.pre_update({"enabled": enabled})) self._db.update_by_id(self.TABLE_NAME, id, self.pre_update({"enabled": enabled}))
self.post_update(id) self.post_update(id)
def update_form(self, id: str, username: str, password: Optional[str]) -> None: def update_form(self, id: int, username: str, password: Optional[str]) -> None:
form = {"username": username} form = {"username": username}
if password is not None and password: if password is not None and password:
form['password'] = self.encode_password(password) form['password'] = self.encode_password(password)
self._db.update_by_id(id, self.pre_update(form)) self._db.update_by_id(self.TABLE_NAME, id, self.pre_update(form))
self.post_update(id) self.post_update(id)
def add_form(self, user: Union[User, Dict]) -> None: def add_form(self, user: Union[User, Dict]) -> None:
@ -160,12 +151,12 @@ class UserManager:
form['password'] = self.encode_password(form['password']) form['password'] = self.encode_password(form['password'])
self._db.add(self.pre_add(form)) self._db.add(self.TABLE_NAME, self.pre_add(form))
self.post_add(user.id) self.post_add(user.id)
def delete(self, id: str) -> None: def delete(self, id: int) -> None:
self.pre_delete(id) self.pre_delete(id)
self._db.delete_by_id(id) self._db.delete_by_id(self.TABLE_NAME, id)
self.post_delete(id) self.post_delete(id)
def to_dict(self, users: List[User]) -> List[Dict]: def to_dict(self, users: List[User]) -> List[Dict]:
@ -215,18 +206,18 @@ class UserManager:
return None return None
def forget_user(self, objects: List, user_id: str) -> Dict: def forget_user(self, objects: List, user_id: int) -> Dict:
user_map = self.map() user_map = self.prepare_map()
user_id = str(user_id) user_id = int(user_id)
edits = {} edits = {}
for object in objects: for object in objects:
edits = {object.id: {}} edits[object.id] = {}
if str(object.created_by) == user_id and user_id in user_map: if int(object.created_by) == user_id and user_id in user_map:
edits[object.id]['created_by'] = user_map[user_id].username edits[object.id]['created_by'] = user_map[user_id].username
if str(object.updated_by) == user_id and user_id in user_map: if int(object.updated_by) == user_id and user_id in user_map:
edits[object.id]['updated_by'] = user_map[user_id].username edits[object.id]['updated_by'] = user_map[user_id].username
return edits return edits

View File

@ -1,6 +1,6 @@
import json
import time import time
from typing import Dict, Optional, List, Tuple, Union from typing import Dict, Optional, List, Tuple, Union
from pysondb.errors import IdDoesNotExistError
from src.manager.DatabaseManager import DatabaseManager from src.manager.DatabaseManager import DatabaseManager
from src.manager.LangManager import LangManager from src.manager.LangManager import LangManager
@ -24,17 +24,17 @@ class VariableManager(ModelManager):
TABLE_NAME = "settings" TABLE_NAME = "settings"
TABLE_MODEL = [ TABLE_MODEL = [
"description", "description TEXT",
"description_edition", "description_edition TEXT",
"editable", "editable INTEGER",
"name", "name CHAR(255)",
"section", "section CHAR(255)",
"plugin", "plugin CHAR(255)",
"selectables", "selectables TEXT",
"type", "type CHAR(255)",
"unit", "unit CHAR(255)",
"refresh_player", "refresh_player INTEGER",
"value" "value TEXT"
] ]
def __init__(self, lang_manager: LangManager, database_manager: DatabaseManager, user_manager: UserManager): def __init__(self, lang_manager: LangManager, database_manager: DatabaseManager, user_manager: UserManager):
@ -75,25 +75,25 @@ class VariableManager(ModelManager):
same_selectables_label = get_keys(default_var, 'selectables', 'label') == get_keys(variable, 'selectables', 'label') same_selectables_label = get_keys(default_var, 'selectables', 'label') == get_keys(variable, 'selectables', 'label')
if variable.description != default_var['description']: if variable.description != default_var['description']:
self._db.update_by_id(variable.id, {"description": default_var['description']}) self._db.update_by_id(self.TABLE_NAME, variable.id, {"description": default_var['description']})
if variable.description_edition != default_var['description_edition']: if variable.description_edition != default_var['description_edition']:
self._db.update_by_id(variable.id, {"description_edition": default_var['description_edition']}) self._db.update_by_id(self.TABLE_NAME, variable.id, {"description_edition": default_var['description_edition']})
if variable.unit != default_var['unit']: if variable.unit != default_var['unit']:
self._db.update_by_id(variable.id, {"unit": default_var['unit']}) self._db.update_by_id(self.TABLE_NAME, variable.id, {"unit": default_var['unit']})
if variable.section != default_var['section']: if variable.section != default_var['section']:
self._db.update_by_id(variable.id, {"section": default_var['section']}) self._db.update_by_id(self.TABLE_NAME, variable.id, {"section": default_var['section']})
if variable.refresh_player != default_var['refresh_player']: if variable.refresh_player != default_var['refresh_player']:
self._db.update_by_id(variable.id, {"refresh_player": default_var['refresh_player']}) self._db.update_by_id(self.TABLE_NAME, variable.id, {"refresh_player": default_var['refresh_player']})
if not same_selectables_keys or not same_selectables_label: if not same_selectables_keys or not same_selectables_label:
self._db.update_by_id(variable.id, {"selectables": default_var['selectables']}) self._db.update_by_id(self.TABLE_NAME, variable.id, {"selectables": default_var['selectables']})
if variable.name == 'last_restart': if variable.name == 'last_restart':
self._db.update_by_id(variable.id, {"value": time.time()}) self._db.update_by_id(self.TABLE_NAME, variable.id, {"value": time.time()})
return variable return variable
@ -144,75 +144,60 @@ class VariableManager(ModelManager):
return var_map return var_map
def hydrate_object(self, raw_variable: dict, id: Optional[str] = None) -> Variable: def hydrate_object(self, raw_variable: dict, id: Optional[int] = None) -> Variable:
if id: if id:
raw_variable['id'] = id raw_variable['id'] = id
if 'selectables' in raw_variable and raw_variable['selectables']: if 'selectables' in raw_variable and raw_variable['selectables']:
raw_variable['selectables'] = [Selectable(**selectable) for selectable in raw_variable['selectables']] raw_variable['selectables'] = [Selectable(**selectable) for selectable in json.loads(raw_variable['selectables'])]
return Variable(**raw_variable) return Variable(**raw_variable)
def hydrate_dict(self, raw_variables: dict) -> List[Variable]:
return [self.hydrate_object(raw_variable, raw_id) for raw_id, raw_variable in raw_variables.items()]
def hydrate_list(self, raw_variables: list) -> List[Variable]: def hydrate_list(self, raw_variables: list) -> List[Variable]:
return [self.hydrate_object(raw_variable) for raw_variable in raw_variables] return [self.hydrate_object(raw_variable) for raw_variable in raw_variables]
def get(self, id: str) -> Optional[Variable]: def get(self, id: int) -> Optional[Variable]:
try: object = self._db.get_by_id(self.TABLE_NAME, id)
return self.hydrate_object(self._db.get_by_id(id), id) return self.hydrate_object(object, id) if object else None
except IdDoesNotExistError:
return None
def get_by(self, query) -> List[Variable]: def get_by(self, query, sort: Optional[str] = None) -> List[Variable]:
return self.hydrate_dict(self._db.get_by_query(query=query)) return self.hydrate_list(self._db.get_by_query(self.TABLE_NAME, query=query, sort=sort))
def get_by_prefix(self, prefix: str) -> List[Variable]: def get_by_prefix(self, prefix: str) -> List[Variable]:
return self.hydrate_dict(self._db.get_by_query(query=lambda v: v['name'].startswith(prefix))) return self.get_by(query="name like '{}%'".format(prefix))
def get_by_plugin(self, plugin: str) -> List[Variable]: def get_by_plugin(self, plugin: str) -> List[Variable]:
return self.hydrate_dict(self._db.get_by_query(query=lambda v: v['plugin'] == plugin)) return self.get_by(query="plugin = '{}'".format(plugin))
def get_one_by_name(self, name: str) -> Optional[Variable]: def get_one_by_name(self, name: str) -> Optional[Variable]:
return self.get_one_by(query=lambda v: v['name'] == name) return self.get_one_by("name = '{}'".format(name))
def get_one_by(self, query) -> Optional[Variable]: def get_one_by(self, query) -> Optional[Variable]:
object = self._db.get_by_query(query=query) object = self._db.get_one_by_query(self.TABLE_NAME, query=query)
variables = self.hydrate_dict(object)
if len(variables) == 1: if not object:
return variables[0] return None
elif len(variables) > 1:
raise Error("More than one result for query") return self.hydrate_object(object)
return None
def get_all(self) -> List[Variable]: def get_all(self) -> List[Variable]:
raw_variables = self._db.get_all() return self.hydrate_list(self._db.get_all(self.TABLE_NAME))
if isinstance(raw_variables, dict):
return self.hydrate_dict(raw_variables)
return self.hydrate_list(raw_variables)
def get_editable_variables(self, plugin: bool = True, sort: Optional[str] = None) -> List[Variable]: def get_editable_variables(self, plugin: bool = True, sort: Optional[str] = None) -> List[Variable]:
query = lambda v: (not plugin and not isinstance(v['plugin'], str)) or (plugin and isinstance(v['plugin'], str)) query = "plugin is null and editable = 1" if not plugin else "plugin is not null and length(plugin) > 0 and editable = 1"
variables = [variable for variable in self.get_by(query=query) if variable.editable] return self.get_by(query=query, sort=sort)
if sort is not None and sort:
return sorted(variables, key=lambda x: getattr(x, sort))
return variables
def get_readonly_variables(self) -> List[Variable]: def get_readonly_variables(self) -> List[Variable]:
return [variable for variable in self.get_all() if not variable.editable] return self.get_by(query="editable = 0", sort="name")
def update_form(self, id: str, value: Union[int, bool, str]) -> None: def update_form(self, id: int, value: Union[int, bool, str]) -> None:
var_dict = self._db.update_by_id(id, {"value": value}) self._db.update_by_id(self.TABLE_NAME, id, {"value": value})
var = self.hydrate_object(var_dict, id) var = self.get_one_by("id = {}".format(id))
self._var_map[var.name] = var self._var_map[var.name] = var
def update_by_name(self, name: str, value) -> Optional[Variable]: def update_by_name(self, name: str, value) -> Optional[Variable]:
[var_id] = self._db.update_by_query(query=lambda v: v['name'] == name, new_data={"value": value}) self._db.update_by_query(self.TABLE_NAME, query="name = '{}'".format(name), values={"value": value})
var_dict = self._db.get_by_id(var_id) var = self.get_one_by_name(name)
var = self.hydrate_object(var_dict, id)
self._var_map[name] = var self._var_map[name] = var
def add_form(self, variable: Union[Variable, Dict]) -> None: def add_form(self, variable: Union[Variable, Dict]) -> None:
@ -222,10 +207,10 @@ class VariableManager(ModelManager):
form = variable.to_dict() form = variable.to_dict()
del form['id'] del form['id']
self._db.add(form) self._db.add(self.TABLE_NAME, form)
def delete(self, id: str) -> None: def delete(self, id: int) -> None:
self._db.delete_by_id(id) self._db.delete_by_id(self.TABLE_NAME, id)
def to_dict(self, variables: List[Variable]) -> List[Dict]: def to_dict(self, variables: List[Variable]) -> List[Dict]:
return [variable.to_dict() for variable in variables] return [variable.to_dict() for variable in variables]

View File

@ -5,7 +5,7 @@ from typing import Optional, Union
class Screen: class Screen:
def __init__(self, host: str = '', port: int = 5000, enabled: bool = False, name: str = 'Untitled', position: int = 999, id: Optional[str] = None): def __init__(self, host: str = '', port: int = 5000, enabled: bool = False, name: str = 'Untitled', position: int = 999, id: Optional[int] = None):
self._id = id if id else None self._id = id if id else None
self._host = host self._host = host
self._port = port self._port = port
@ -14,7 +14,7 @@ class Screen:
self._position = position self._position = position
@property @property
def id(self) -> Union[int, str]: def id(self) -> Optional[int]:
return self._id return self._id
@property @property
@ -35,11 +35,11 @@ class Screen:
@property @property
def enabled(self) -> bool: def enabled(self) -> bool:
return self._enabled return bool(self._enabled)
@enabled.setter @enabled.setter
def enabled(self, value: bool): def enabled(self, value: bool):
self._enabled = value self._enabled = bool(value)
@property @property
def name(self) -> str: def name(self) -> str:

View File

@ -8,7 +8,7 @@ from src.utils import str_to_enum
class Slide: class Slide:
def __init__(self, location: str = '', duration: int = 3, type: Union[SlideType, str] = SlideType.URL, enabled: bool = False, name: str = 'Untitled', position: int = 999, id: Optional[str] = None, cron_schedule: Optional[str] = None, cron_schedule_end: Optional[str] = None, created_by: Optional[str] = None, updated_by: Optional[str] = None, created_at: Optional[int] = None, updated_at: Optional[int] = None): def __init__(self, location: str = '', duration: int = 3, type: Union[SlideType, str] = SlideType.URL, enabled: bool = False, name: str = 'Untitled', position: int = 999, id: Optional[int] = None, cron_schedule: Optional[str] = None, cron_schedule_end: Optional[str] = None, created_by: Optional[str] = None, updated_by: Optional[str] = None, created_at: Optional[int] = None, updated_at: Optional[int] = None):
self._id = id if id else None self._id = id if id else None
self._location = location self._location = location
self._duration = duration self._duration = duration
@ -24,7 +24,7 @@ class Slide:
self._updated_at = int(updated_at if updated_at else time.time()) self._updated_at = int(updated_at if updated_at else time.time())
@property @property
def id(self) -> Optional[str]: def id(self) -> Optional[int]:
return self._id return self._id
@property @property
@ -101,11 +101,11 @@ class Slide:
@property @property
def enabled(self) -> bool: def enabled(self) -> bool:
return self._enabled return bool(self._enabled)
@enabled.setter @enabled.setter
def enabled(self, value: bool): def enabled(self, value: bool):
self._enabled = value self._enabled = bool(value)
@property @property
def name(self) -> str: def name(self) -> str:

View File

@ -5,14 +5,14 @@ from typing import Optional, Union
class User: class User:
def __init__(self, username: str = '', password: str = '', enabled: bool = True, id: Optional[str] = None): def __init__(self, username: str = '', password: str = '', enabled: bool = True, id: Optional[int] = None):
self._id = id if id else None self._id = id if id else None
self._username = username self._username = username
self._password = password self._password = password
self._enabled = enabled self._enabled = enabled
@property @property
def id(self) -> Union[int, str]: def id(self) -> Optional[int]:
return self._id return self._id
@property @property
@ -33,11 +33,11 @@ class User:
@property @property
def enabled(self) -> bool: def enabled(self) -> bool:
return self._enabled return bool(self._enabled)
@enabled.setter @enabled.setter
def enabled(self, value: bool): def enabled(self, value: bool):
self._enabled = value self._enabled = bool(value)
def __str__(self) -> str: def __str__(self) -> str:
return f"User(" \ return f"User(" \

View File

@ -11,7 +11,7 @@ from src.utils import str_to_enum
class Variable: class Variable:
def __init__(self, name: str = '', section: str = '', description: str = '', description_edition: str = '', type: Union[VariableType, str] = VariableType.STRING, def __init__(self, name: str = '', section: str = '', description: str = '', description_edition: str = '', type: Union[VariableType, str] = VariableType.STRING,
value: Union[int, bool, str] = '', editable: bool = True, id: Optional[str] = None, value: Union[int, bool, str] = '', editable: bool = True, id: Optional[int] = None,
plugin: Optional[str] = None, selectables: Optional[List[Selectable]] = None, unit: Optional[VariableUnit] = None, plugin: Optional[str] = None, selectables: Optional[List[Selectable]] = None, unit: Optional[VariableUnit] = None,
refresh_player: bool = False): refresh_player: bool = False):
self._id = id if id else None self._id = id if id else None
@ -32,7 +32,7 @@ class Variable:
self._unit = None self._unit = None
@property @property
def id(self) -> Union[int, str]: def id(self) -> Optional[int]:
return self._id return self._id
@property @property
@ -96,19 +96,19 @@ class Variable:
@property @property
def editable(self) -> bool: def editable(self) -> bool:
return self._editable return bool(self._editable)
@editable.setter @editable.setter
def editable(self, value: bool): def editable(self, value: bool):
self._editable = value self._editable = bool(value)
@property @property
def refresh_player(self) -> bool: def refresh_player(self) -> bool:
return self._refresh_player return bool(self._refresh_player)
@refresh_player.setter @refresh_player.setter
def refresh_player(self, value: bool): def refresh_player(self, value: bool):
self._refresh_player = value self._refresh_player = bool(value)
@property @property
def value(self) -> Union[int, bool, str]: def value(self) -> Union[int, bool, str]:
@ -171,10 +171,10 @@ class Variable:
return str(self._value) return str(self._value)
def as_int(self) -> int: def as_int(self) -> int:
return int(self._value) return int(float(self._value))
def as_ctime(self) -> int: def as_ctime(self) -> int:
return time.ctime(self._value) return time.ctime(int(float(self._value)))
def display(self) -> Union[int, bool, str]: def display(self) -> Union[int, bool, str]:
value = self.eval() value = self.eval()

View File

@ -53,5 +53,5 @@ class ModelStore:
def user(self) -> UserManager: def user(self) -> UserManager:
return self._user_manager return self._user_manager
def on_user_delete(self, user_id: str): def on_user_delete(self, user_id: int) -> None:
self._slide_manager.forget_user(user_id) self._slide_manager.forget_user(user_id)

View File

@ -156,6 +156,9 @@ class PluginStore:
def is_plugin_enabled(self, plugin: ObPlugin) -> bool: def is_plugin_enabled(self, plugin: ObPlugin) -> bool:
var = self._model_store.variable().get_one_by_name(plugin.get_plugin_variable_name(self.DEFAULT_PLUGIN_ENABLED_VARIABLE)) var = self._model_store.variable().get_one_by_name(plugin.get_plugin_variable_name(self.DEFAULT_PLUGIN_ENABLED_VARIABLE))
if var.as_bool:
logging.info("[Plugin] {} enabled".format(plugin.use_title()))
return var.as_bool() if var else False return var.as_bool() if var else False

View File

@ -25,6 +25,7 @@ class TemplateRenderer:
def get_view_globals(self) -> dict: def get_view_globals(self) -> dict:
globals = dict( globals = dict(
STATIC_PREFIX="/{}/{}/".format(WebDirConstant.FOLDER_STATIC, WebDirConstant.FOLDER_STATIC_WEB_ASSETS), STATIC_PREFIX="/{}/{}/".format(WebDirConstant.FOLDER_STATIC, WebDirConstant.FOLDER_STATIC_WEB_ASSETS),
SECRET_KEY=self._model_store.config().map().get('secret_key'),
FLEET_ENABLED=self._model_store.variable().map().get('fleet_enabled').as_bool(), FLEET_ENABLED=self._model_store.variable().map().get('fleet_enabled').as_bool(),
AUTH_ENABLED=self._model_store.variable().map().get('auth_enabled').as_bool(), AUTH_ENABLED=self._model_store.variable().map().get('auth_enabled').as_bool(),
track_created=self._model_store.user().track_user_created, track_created=self._model_store.user().track_user_created,

View File

@ -13,6 +13,20 @@ from cron_descriptor.Exception import FormatException, WrongArgumentException, M
CAMEL_CASE_TO_SNAKE_CASE_PATTERN = re.compile(r'(?<!^)(?=[A-Z])') CAMEL_CASE_TO_SNAKE_CASE_PATTERN = re.compile(r'(?<!^)(?=[A-Z])')
def is_wrapped_by(s: str, head: str = '', tail: str = '') -> bool:
return s[0] == head and s[-1] == tail if len(s) > 0 else None
def wrap_if(s: str, condition: bool = True, quote_type: str = "'") -> str:
if not condition or is_wrapped_by(s, quote_type, quote_type):
return s
return "{}{}{}".format(
quote_type,
s,
quote_type
)
def am_i_in_docker(): def am_i_in_docker():
docker_env = os.path.exists('/.dockerenv') docker_env = os.path.exists('/.dockerenv')
docker_cgroup = False docker_cgroup = False

View File

@ -1 +1 @@
1.14 1.15

View File

@ -113,6 +113,7 @@
{% endblock %} {% endblock %}
</div> </div>
<script> <script>
var secret_key = '{{ SECRET_KEY }}';
var l = { var l = {
'js_slideshow_slide_delete_confirmation': '{{ l.slideshow_slide_delete_confirmation }}', 'js_slideshow_slide_delete_confirmation': '{{ l.slideshow_slide_delete_confirmation }}',
'js_fleet_screen_delete_confirmation': '{{ l.js_fleet_screen_delete_confirmation }}', 'js_fleet_screen_delete_confirmation': '{{ l.js_fleet_screen_delete_confirmation }}',