Coverage for /home/runner/work/viur-core/viur-core/viur/src/viur/core/modules/moduleconf.py: 0%
80 statements
« prev ^ index » next coverage.py v7.6.3, created at 2024-10-16 22:16 +0000
« prev ^ index » next coverage.py v7.6.3, created at 2024-10-16 22:16 +0000
1import logging
2from viur.core import Module, conf, db, current, i18n, tasks, skeleton
3from viur.core.bones import StringBone, TextBone, SelectBone, TreeLeafBone
4from viur.core.bones.text import _defaultTags
5from viur.core.prototypes import List
8MODULECONF_KINDNAME = "viur-module-conf"
11class ModuleConfScriptSkel(skeleton.RelSkel):
13 name = StringBone(
14 descr="Label",
15 required=True,
16 params={
17 "tooltip": "Label for the action button displayed."
18 },
19 )
21 icon = StringBone(
22 descr="Icon",
23 params={
24 "tooltip": "Shoelace-conforming icon identifier."
25 },
26 )
28 capable = SelectBone(
29 descr="Arguments",
30 required=True,
31 defaultValue="none",
32 values={
33 "none": "none: No arguments, always executable",
34 "single": "single: Run script with single entry key as argument",
35 "multiple": "multiple: Run script with list of entity keys as argument",
36 },
37 params={
38 "tooltip":
39 "Describes the behavior in the admin, "
40 "if and how selected entries from the module are being processed."
41 },
42 )
44 access = SelectBone(
45 descr="Required access rights",
46 values=lambda: {
47 right: i18n.translate(f"server.modules.user.accessright.{right}", defaultText=right)
48 for right in sorted(conf.user.access_rights)
49 },
50 multiple=True,
51 params={
52 "tooltip":
53 "To whom the button should be displayed in the admin. "
54 "In addition, the admin checks whether all rights of the script are also fulfilled.",
55 },
56 )
59class ModuleConfSkel(skeleton.Skeleton):
60 kindName = MODULECONF_KINDNAME
62 _valid_tags = ['b', 'a', 'i', 'u', 'span', 'div', 'p', 'ol', 'ul', 'li', 'abbr', 'sub', 'sup', 'h1', 'h2', 'h3',
63 'h4', 'h5', 'h6', 'br', 'hr', 'strong', 'blockquote', 'em']
64 _valid_html = _defaultTags.copy()
65 _valid_html["validTags"] = _valid_tags
67 name = StringBone(
68 descr=i18n.translate("modulename"),
69 readOnly=True,
70 )
72 help_text = TextBone(
73 descr=i18n.translate("module helptext"),
74 validHtml=_valid_html,
75 )
77 help_text_add = TextBone(
78 descr=i18n.translate("add helptext"),
79 validHtml=_valid_html,
80 )
82 help_text_edit = TextBone(
83 descr=i18n.translate("edit helptext"),
84 validHtml=_valid_html,
85 )
87 scripts = TreeLeafBone(
88 descr=i18n.translate("scriptor scripts"),
89 module="script",
90 kind="viur-script-leaf",
91 using=ModuleConfScriptSkel,
92 refKeys=[
93 "key",
94 "name",
95 "access",
96 ],
97 multiple=True,
98 )
101class ModuleConf(List):
102 """
103 This module is for ViUR internal purposes only.
104 It lists all other modules to be able to provide them with help texts.
105 """
106 MODULES = set() # will be filled by read_all_modules
107 kindName = MODULECONF_KINDNAME
108 accessRights = ["edit"]
109 default_order = None # disable default ordering for ModuleConf
111 def adminInfo(self):
112 return conf.moduleconf_admin_info or {}
114 def canAdd(self):
115 return False
117 def canDelete(self, skel):
118 return False
120 def canEdit(self, skel):
121 if super().canEdit(skel):
122 return True
124 # Check for "manage"-flag on current user
125 return (cuser := current.user.get()) and f"""{skel["name"]}-manage""" in cuser["access"]
127 def listFilter(self, query):
128 original_query = query
130 # when super-call does not satisfy...
131 if not (query := super().listFilter(query)):
132 if cuser := current.user.get():
133 # ... then, list modules the user is allowed to use!
134 user_modules = set(right.split("-", 1)[0] for right in cuser["access"] if "-" in right)
136 query = original_query
137 query.filter("name IN", tuple(user_modules))
139 return query
141 @classmethod
142 def get_by_module_name(cls, module_name: str) -> None | skeleton.SkeletonInstance:
143 db_key = db.Key(MODULECONF_KINDNAME, module_name)
144 skel = conf.main_app.vi._moduleconf.viewSkel()
145 if not skel.fromDB(db_key):
146 logging.error(f"module({module_name}) not found")
147 return None
149 return skel
151 @tasks.StartupTask
152 @staticmethod
153 def read_all_modules():
154 db_module_names = (m["name"] for m in db.Query(MODULECONF_KINDNAME).run(999))
155 visited_modules = set()
157 def collect_modules(parent, depth: int = 0, prefix: str = "") -> None:
158 """Recursively collects all routable modules for the vi renderer"""
159 if depth > 10:
160 logging.warning(f"Reached maximum recursion limit of {depth} at {parent=}")
161 return
163 for module_name in dir(parent):
164 module = getattr(parent, module_name, None)
165 if not isinstance(module, Module):
166 continue
167 if module in visited_modules:
168 # Some modules reference other modules as parents, this will
169 # lead to infinite recursion. We can avoid reaching the
170 # maximum recursion limit by remembering already seen modules.
171 if conf.debug.trace:
172 logging.debug(f"Already visited and added {module=}")
173 continue
174 module_name = f"{prefix}{module_name}"
175 visited_modules.add(module)
176 ModuleConf.MODULES.add(module_name)
177 if module_name not in db_module_names:
178 skel = conf.main_app.vi._moduleconf.addSkel()
179 skel["key"] = db.Key(MODULECONF_KINDNAME, module_name)
180 skel["name"] = module_name
181 skel.toDB()
183 # Collect children
184 collect_modules(module, depth=depth + 1, prefix=f"{module_name}.")
186 collect_modules(conf.main_app.vi)
187 # TODO: Remove entries from MODULECONF_KINDNAME which are in db_module_names but not in ModuleConf.MODULES
190ModuleConf.json = True