Coverage for /home/runner/work/viur-core/viur-core/viur/src/viur/core/render/vi/__init__.py: 0%
117 statements
« prev ^ index » next coverage.py v7.10.6, created at 2025-09-13 11:04 +0000
« prev ^ index » next coverage.py v7.10.6, created at 2025-09-13 11:04 +0000
1import datetime
2import fnmatch
3import json
4import logging
5from viur.core import Module, conf, current, errors
6from viur.core.decorators import *
7from viur.core.render.json import skey as json_render_skey
8from viur.core.render.json.default import CustomJsonEncoder, DefaultRender
9from viur.core.skeleton import SkeletonInstance
12class default(DefaultRender):
13 kind = "json.vi"
16__all__ = [default]
19@exposed
20def timestamp(*args, **kwargs):
21 d = datetime.datetime.now()
22 current.request.get().response.headers["Content-Type"] = "application/json"
23 return json.dumps(d.strftime("%Y-%m-%dT%H-%M-%S"))
26@exposed
27def getStructure(module):
28 """
29 Returns all available skeleton structures for a given module.
31 To access the structure of a nested module, separate the path with dots (.).
32 """
33 path = module.split(".")
34 moduleObj = conf.main_app.vi
35 while path:
36 moduleObj = getattr(moduleObj, path.pop(0), None)
37 if not isinstance(moduleObj, Module) or not moduleObj.describe():
38 return json.dumps(None)
40 res = {}
42 # check for tree prototype
43 if "nodeSkelCls" in dir(moduleObj):
44 # Try Node/Leaf
45 for stype in ("viewSkel", "editSkel", "addSkel"):
46 for treeType in ("node", "leaf"):
47 if stype in dir(moduleObj):
48 try:
49 skel = getattr(moduleObj, stype)(treeType)
50 except (TypeError, ValueError):
51 continue
53 if isinstance(skel, SkeletonInstance):
54 storeType = stype.replace("Skel", "") + ("LeafSkel" if treeType == "leaf" else "NodeSkel")
55 res[storeType] = DefaultRender.render_structure(skel.structure())
56 else:
57 # every other prototype
58 for stype in ("viewSkel", "editSkel", "addSkel"): # Unknown skel type
59 if stype in dir(moduleObj):
60 try:
61 skel = getattr(moduleObj, stype)()
62 except (TypeError, ValueError):
63 continue
64 if isinstance(skel, SkeletonInstance):
65 res[stype] = DefaultRender.render_structure(skel.structure())
67 current.request.get().response.headers["Content-Type"] = "application/json"
68 return json.dumps(res or None, cls=CustomJsonEncoder)
71@exposed
72@skey
73def setLanguage(lang):
74 if lang in conf.i18n.available_languages:
75 current.language.set(lang)
78@exposed
79def dumpConfig():
80 res = {}
81 visited_objects = set()
83 def collect_modules(parent, depth: int = 0) -> None:
84 """Recursively collects all routable modules for the vi renderer"""
85 if depth > 10:
86 logging.warning(f"Reached maximum recursion limit of {depth} at {parent=}")
87 return
89 for key in dir(parent):
90 module = getattr(parent, key, None)
91 if not isinstance(module, Module):
92 continue
93 if module in visited_objects:
94 # Some modules reference other modules as parents, this will
95 # lead to infinite recursion. We can avoid reaching the
96 # maximum recursion limit by remembering already seen modules.
97 if conf.debug.trace:
98 logging.debug(f"Already visited and added {module=}")
99 continue
100 visited_objects.add(module)
102 if admin_info := module.describe():
103 # map path --> config
104 res[module.modulePath.removeprefix("/vi/").replace("/", ".")] = admin_info
105 # Collect children
106 collect_modules(module, depth=depth + 1)
108 collect_modules(conf.main_app.vi)
110 res = {
111 "modules": res,
112 # "configuration": dict(conf.admin.items()), # TODO: this could be the short vision, if we use underscores
113 "configuration": {
114 k.replace("_", "."): v for k, v in conf.admin.items(True)
115 }
116 }
117 current.request.get().response.headers["Content-Type"] = "application/json"
118 return json.dumps(res, cls=CustomJsonEncoder)
121@exposed
122def getVersion(*args, **kwargs):
123 """
124 Returns viur-core version number
125 """
126 current.request.get().response.headers["Content-Type"] = "application/json"
128 version = conf.version
130 # always fill up to 4 parts
131 while len(version) < 4:
132 version += (None,)
134 if conf.instance.is_dev_server \
135 or ((cuser := current.user.get()) and ("root" in cuser["access"] or "admin" in cuser["access"])):
136 return json.dumps(version[:4])
138 # Hide patch level + appendix to non-authorized users
139 return json.dumps((version[0], version[1], None, None))
142def canAccess(*args, **kwargs) -> bool:
143 """
144 General access restrictions for the vi-render.
145 """
147 if (cuser := current.user.get()) and any(right in cuser["access"] for right in ("root", "admin")):
148 return True
150 return any(fnmatch.fnmatch(current.request.get().path, pat) for pat in conf.security.admin_allowed_paths)
153@exposed
154def index(*args, **kwargs):
155 if args or kwargs:
156 raise errors.NotFound()
157 if (
158 not conf.instance.project_base_path.joinpath("vi", "main.html").exists()
159 and not conf.instance.project_base_path.joinpath("admin", "main.html").exists()
160 ):
161 raise errors.NotFound()
162 if conf.instance.is_dev_server or current.request.get().isSSLConnection:
163 raise errors.Redirect("/vi/s/main.html")
164 else:
165 appVersion = current.request.get().request.host
166 raise errors.Redirect(f"https://{appVersion}/vi/s/main.html")
169@exposed
170def get_settings():
171 """
172 Get public admin-tool specific settings, requires no user to be logged in.
173 This is used by new vi-admin.
174 """
175 fields = {k.replace("_", "."): v for k, v in conf.admin.items(True)}
177 if conf.user.google_client_id:
178 fields["admin.user.google.clientID"] = conf.user.google_client_id
180 current.request.get().response.headers["Content-Type"] = "application/json"
181 return json.dumps(fields, cls=CustomJsonEncoder)
184def _postProcessAppObj(obj):
185 obj["skey"] = json_render_skey
186 obj["timestamp"] = timestamp
187 obj["config"] = dumpConfig
188 obj["settings"] = get_settings
189 obj["getStructure"] = getStructure
190 obj["canAccess"] = canAccess
191 obj["setLanguage"] = setLanguage
192 obj["getVersion"] = getVersion
193 obj["index"] = index
194 return obj