mirror of
https://github.com/YunoHost/yunohost.git
synced 2024-09-03 20:06:10 +02:00
configpanel: add config panel models
This commit is contained in:
parent
bec34b92b0
commit
564a66de2f
1 changed files with 190 additions and 2 deletions
|
@ -21,8 +21,10 @@ import os
|
|||
import re
|
||||
import urllib.parse
|
||||
from collections import OrderedDict
|
||||
from typing import Union
|
||||
from logging import getLogger
|
||||
from typing import TYPE_CHECKING, Any, Literal, Sequence, Type, Union
|
||||
|
||||
from pydantic import BaseModel, Extra, validator
|
||||
|
||||
from moulinette import Moulinette, m18n
|
||||
from moulinette.interfaces.cli import colorize
|
||||
|
@ -30,20 +32,206 @@ from moulinette.utils.filesystem import mkdir, read_toml, read_yaml, write_to_ya
|
|||
from yunohost.utils.error import YunohostError, YunohostValidationError
|
||||
from yunohost.utils.form import (
|
||||
OPTIONS,
|
||||
BaseChoicesOption,
|
||||
BaseInputOption,
|
||||
BaseOption,
|
||||
BaseReadonlyOption,
|
||||
FileOption,
|
||||
OptionsModel,
|
||||
OptionType,
|
||||
Translation,
|
||||
ask_questions_and_parse_answers,
|
||||
evaluate_simple_js_expression,
|
||||
)
|
||||
from yunohost.utils.i18n import _value_for_locale
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from pydantic.fields import ModelField
|
||||
|
||||
logger = getLogger("yunohost.configpanel")
|
||||
|
||||
|
||||
# ╭───────────────────────────────────────────────────────╮
|
||||
# │ ╭╮╮╭─╮┌─╮┌─╴╷ ╭─╴ │
|
||||
# │ ││││ ││ │├─╴│ ╰─╮ │
|
||||
# │ ╵╵╵╰─╯└─╯╰─╴╰─╴╶─╯ │
|
||||
# ╰───────────────────────────────────────────────────────╯
|
||||
|
||||
CONFIG_PANEL_VERSION_SUPPORTED = 1.0
|
||||
|
||||
|
||||
class ContainerModel(BaseModel):
|
||||
id: str
|
||||
name: Union[Translation, None] = None
|
||||
services: list[str] = []
|
||||
help: Union[Translation, None] = None
|
||||
|
||||
def translate(self, i18n_key: Union[str, None] = None):
|
||||
"""
|
||||
Translate `ask` and `name` attributes of panels and section.
|
||||
This is in place mutation.
|
||||
"""
|
||||
|
||||
for key in ("help", "name"):
|
||||
value = getattr(self, key)
|
||||
if value:
|
||||
setattr(self, key, _value_for_locale(value))
|
||||
elif key == "help" and m18n.key_exists(f"{i18n_key}_{self.id}_help"):
|
||||
setattr(self, key, m18n.n(f"{i18n_key}_{self.id}_help"))
|
||||
|
||||
|
||||
class SectionModel(ContainerModel, OptionsModel):
|
||||
visible: Union[bool, str] = True
|
||||
optional: bool = True
|
||||
|
||||
# Don't forget to pass arguments to super init
|
||||
def __init__(
|
||||
self,
|
||||
id: str,
|
||||
name: Union[Translation, None] = None,
|
||||
services: list[str] = [],
|
||||
help: Union[Translation, None] = None,
|
||||
visible: Union[bool, str] = True,
|
||||
**kwargs,
|
||||
) -> None:
|
||||
options = self.options_dict_to_list(kwargs, defaults={"optional": True})
|
||||
|
||||
ContainerModel.__init__(
|
||||
self,
|
||||
id=id,
|
||||
name=name,
|
||||
services=services,
|
||||
help=help,
|
||||
visible=visible,
|
||||
options=options,
|
||||
)
|
||||
|
||||
@property
|
||||
def is_action_section(self):
|
||||
return any([option.type is OptionType.button for option in self.options])
|
||||
|
||||
def is_visible(self, context: dict[str, Any]):
|
||||
if isinstance(self.visible, bool):
|
||||
return self.visible
|
||||
|
||||
return evaluate_simple_js_expression(self.visible, context=context)
|
||||
|
||||
def translate(self, i18n_key: Union[str, None] = None):
|
||||
"""
|
||||
Call to `Container`'s `translate` for self translation
|
||||
+ Call to `OptionsContainer`'s `translate_options` for options translation
|
||||
"""
|
||||
super().translate(i18n_key)
|
||||
self.translate_options(i18n_key)
|
||||
|
||||
|
||||
class PanelModel(ContainerModel):
|
||||
# FIXME what to do with `actions?
|
||||
actions: dict[str, Translation] = {"apply": {"en": "Apply"}}
|
||||
sections: list[SectionModel]
|
||||
|
||||
class Config:
|
||||
extra = Extra.allow
|
||||
|
||||
# Don't forget to pass arguments to super init
|
||||
def __init__(
|
||||
self,
|
||||
id: str,
|
||||
name: Union[Translation, None] = None,
|
||||
services: list[str] = [],
|
||||
help: Union[Translation, None] = None,
|
||||
**kwargs,
|
||||
) -> None:
|
||||
sections = [data | {"id": name} for name, data in kwargs.items()]
|
||||
super().__init__(
|
||||
id=id, name=name, services=services, help=help, sections=sections
|
||||
)
|
||||
|
||||
def translate(self, i18n_key: Union[str, None] = None):
|
||||
"""
|
||||
Recursivly mutate translatable attributes to their translation
|
||||
"""
|
||||
super().translate(i18n_key)
|
||||
|
||||
for section in self.sections:
|
||||
section.translate(i18n_key)
|
||||
|
||||
|
||||
class ConfigPanelModel(BaseModel):
|
||||
version: float = CONFIG_PANEL_VERSION_SUPPORTED
|
||||
i18n: Union[str, None] = None
|
||||
panels: list[PanelModel]
|
||||
|
||||
class Config:
|
||||
arbitrary_types_allowed = True
|
||||
extra = Extra.allow
|
||||
|
||||
# Don't forget to pass arguments to super init
|
||||
def __init__(
|
||||
self,
|
||||
version: float,
|
||||
i18n: Union[str, None] = None,
|
||||
**kwargs,
|
||||
) -> None:
|
||||
panels = [data | {"id": name} for name, data in kwargs.items()]
|
||||
super().__init__(version=version, i18n=i18n, panels=panels)
|
||||
|
||||
@property
|
||||
def sections(self):
|
||||
"""Convinient prop to iter on all sections"""
|
||||
for panel in self.panels:
|
||||
for section in panel.sections:
|
||||
yield section
|
||||
|
||||
@property
|
||||
def options(self):
|
||||
"""Convinient prop to iter on all options"""
|
||||
for section in self.sections:
|
||||
for option in section.options:
|
||||
yield option
|
||||
|
||||
|
||||
def iter_children(
|
||||
self,
|
||||
trigger: list[Literal["panel", "section", "option", "action"]] = ["option"],
|
||||
):
|
||||
for panel in self.panels:
|
||||
if "panel" in trigger:
|
||||
yield (panel, None, None)
|
||||
for section in panel.sections:
|
||||
if "section" in trigger:
|
||||
yield (panel, section, None)
|
||||
if "action" in trigger:
|
||||
for option in section.options:
|
||||
if option.type is OptionType.button:
|
||||
yield (panel, section, option)
|
||||
if "option" in trigger:
|
||||
for option in section.options:
|
||||
yield (panel, section, option)
|
||||
|
||||
def translate(self):
|
||||
"""
|
||||
Recursivly mutate translatable attributes to their translation
|
||||
"""
|
||||
for panel in self.panels:
|
||||
panel.translate(self.i18n)
|
||||
|
||||
@validator("version", always=True)
|
||||
def check_version(cls, value, field: "ModelField"):
|
||||
if value < CONFIG_PANEL_VERSION_SUPPORTED:
|
||||
raise ValueError(
|
||||
f"Config panels version '{value}' are no longer supported."
|
||||
)
|
||||
|
||||
return value
|
||||
|
||||
|
||||
# ╭───────────────────────────────────────────────────────╮
|
||||
# │ ╭─╴╭─╮╭╮╷┌─╴╶┬╴╭─╮ ╶┬╴╭╮╮┌─╮╷ │
|
||||
# │ │ │ ││││├─╴ │ │╶╮ │ │││├─╯│ │
|
||||
# │ ╰─╴╰─╯╵╰╯╵ ╶┴╴╰─╯ ╶┴╴╵╵╵╵ ╰─╴ │
|
||||
# ╰───────────────────────────────────────────────────────╯
|
||||
|
||||
|
||||
class ConfigPanel:
|
||||
entity_type = "config"
|
||||
save_path_tpl: Union[str, None] = None
|
||||
|
|
Loading…
Add table
Reference in a new issue