Coverage for /home/runner/work/viur-core/viur-core/viur/src/viur/core/render/vi/__init__.py: 18%
143 statements
« prev ^ index » next coverage.py v7.16.1, created at 2026-09-25 15:02 +0000
« prev ^ index » next coverage.py v7.16.1, created at 2026-09-25 15:02 +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
10from deprecated.sphinx import deprecated
13class default(DefaultRender):
14 kind = "json.vi"
17__all__ = [default]
20@exposed
21def timestamp(*args, **kwargs):
22 d = datetime.datetime.now()
23 current.request.get().response.headers["Content-Type"] = "application/json"
24 return json.dumps(d.strftime("%Y-%m-%dT%H-%M-%S"))
27@exposed
28@deprecated(version="3.9.0", reason="Use '/vi/module/structure' as new endpoint")
29def getStructure(module):
30 """
31 Returns all available skeleton structures for a given module.
33 To access the structure of a nested module, separate the path with dots (.).
34 """
35 logging.warning(
36 f"DEPRECATED!!! Use '/vi/{module}/structure' or '/json/{module}/structure' for this, "
37 "or update your admin version!"
38 )
40 path = module.split(".")
41 moduleObj = conf.main_app.vi
42 while path:
43 moduleObj = getattr(moduleObj, path.pop(0), None)
44 if not isinstance(moduleObj, Module) or not moduleObj.describe():
45 return json.dumps(None)
47 res = {}
49 # check for tree prototype
50 if "nodeSkelCls" in dir(moduleObj):
51 # Try Node/Leaf
52 for stype in ("viewSkel", "editSkel", "addSkel"):
53 for treeType in ("node", "leaf"):
54 if stype in dir(moduleObj):
55 try:
56 skel = getattr(moduleObj, stype)(treeType)
57 except (TypeError, ValueError):
58 continue
60 if isinstance(skel, SkeletonInstance):
61 storeType = stype.replace("Skel", "") + ("LeafSkel" if treeType == "leaf" else "NodeSkel")
62 res[storeType] = DefaultRender.render_structure(skel.structure())
63 else:
64 # every other prototype
65 for stype in ("viewSkel", "editSkel", "addSkel"): # Unknown skel type
66 if stype in dir(moduleObj):
67 try:
68 skel = getattr(moduleObj, stype)()
69 except (TypeError, ValueError):
70 continue
71 if isinstance(skel, SkeletonInstance):
72 res[stype] = DefaultRender.render_structure(skel.structure())
74 current.request.get().response.headers["Content-Type"] = "application/json"
75 return json.dumps(res or None, cls=CustomJsonEncoder)
78@exposed
79@skey
80def setLanguage(lang):
81 if lang in conf.i18n.available_languages:
82 current.language.set(lang)
85@exposed
86@deprecated(version="3.9.0", reason="Use '/vi/config' as new endpoint, see 'version' property.")
87def getVersion(*args, **kwargs):
88 """
89 Returns viur-core version number
90 """
91 current.request.get().response.headers["Content-Type"] = "application/json"
93 version = conf.version
95 # always fill up to 4 parts
96 while len(version) < 4:
97 version += (None,)
99 return json.dumps(version[:4])
102def _can_access(user_skel: SkeletonInstance) -> bool:
103 """
104 Internal check for vi-render if a given user is allowed to use this render.
105 The function is used in several places for better integration.
106 """
107 return bool(user_skel and user_skel["access"] and any(flag in user_skel["access"] for flag in ("admin", "root")))
110def canAccess(*args, **kwargs) -> bool:
111 """
112 General access restrictions for the vi-render.
113 """
114 if _can_access(current.user.get()):
115 return True
117 return any(fnmatch.fnmatch(current.request.get().path, pat) for pat in conf.security.admin_allowed_paths)
120@exposed
121def index(*args, **kwargs):
122 if args or kwargs:
123 raise errors.NotFound()
124 if (
125 not conf.instance.project_base_path.joinpath("vi", "main.html").exists()
126 and not conf.instance.project_base_path.joinpath("admin", "main.html").exists()
127 ):
128 raise errors.NotFound()
129 if conf.instance.is_dev_server or current.request.get().isSSLConnection:
130 raise errors.Redirect("/vi/s/main.html")
131 else:
132 appVersion = current.request.get().request.host
133 raise errors.Redirect(f"https://{appVersion}/vi/s/main.html")
136@exposed
137def config():
138 """
139 Get public admin-tool specific settings, requires no user to be logged in.
140 This is used by new vi-admin.
141 """
142 current.request.get().response.headers["Content-Type"] = "application/json"
144 result = {}
145 config = {k.replace("_", "."): v for k, v in conf.admin.items(True)}
147 if conf.user.google_client_id:
148 config["admin.user.google.clientID"] = conf.user.google_client_id
150 config["admin.language"] = current.language.get() or conf.i18n.default_language
151 config["admin.languages"] = conf.i18n.available_languages
153 if _can_access(current.user.get()):
154 # Core Version
155 version = conf.version
157 # always fill up to 4 parts
158 while len(version) < 4:
159 version += (None,)
161 result["version"] = version[:4]
163 # Modules
164 modules = {}
165 visited_objects = set()
167 def collect_modules(parent, depth: int = 0) -> None:
168 """Recursively collects all routable modules for the vi renderer"""
169 if depth > 10:
170 logging.warning(f"Reached maximum recursion limit of {depth} at {parent=}")
171 return
173 for key in dir(parent):
174 module = getattr(parent, key, None)
175 if not isinstance(module, Module):
176 continue
178 if module in visited_objects:
179 # Some modules reference other modules as parents, this will
180 # lead to infinite recursion. We can avoid reaching the
181 # maximum recursion limit by remembering already seen modules.
182 if conf.debug.trace:
183 logging.debug(f"Already visited and added {module=}")
184 continue
186 visited_objects.add(module)
188 if admin_info := module.describe():
189 # map path --> config
190 modules[module.modulePath.removeprefix("/vi/").replace("/", ".")] = admin_info
192 # Collect children
193 collect_modules(module, depth=depth + 1)
195 collect_modules(conf.main_app.vi)
197 result["modules"] = modules
199 result["configuration"] = config
201 return json.dumps(result, cls=CustomJsonEncoder)
204@exposed
205@deprecated(version="3.9.0", reason="Use '/vi/config' as new endpoint, see 'configuration' property.")
206def settings():
207 return json.dumps(json.loads(config())["configuration"])
210@exposed
211def routes():
212 """
213 Endpoint to retrieve an array of all available routes on this ViUR instance.
214 """
215 current.request.get().response.headers["Content-Type"] = "application/json"
217 def resolve(o):
218 result = []
220 for key, value in o.items():
221 if isinstance(value, dict):
222 for subresult in resolve(value):
223 result.append(f"{key}/{subresult}")
224 else:
225 result.append(key)
227 return result
229 return json.dumps(resolve(conf.main_resolver))
232def _postProcessAppObj(obj):
233 obj["canAccess"] = canAccess
234 obj["config"] = config
235 obj["index"] = index
236 obj["routes"] = routes
237 obj["setLanguage"] = setLanguage
238 obj["skey"] = json_render_skey
239 obj["timestamp"] = timestamp
241 # DEPRECATED:
242 obj["getStructure"] = getStructure
243 obj["getVersion"] = getVersion # FIXME: Deprecated; vi-admin 4.x backward compatiblity
244 obj["settings"] = settings # FIXME: Deprecated; vi-admin 4.x backward compatiblity
246 return obj