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

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 

11 

12 

13class default(DefaultRender): 

14 kind = "json.vi" 

15 

16 

17__all__ = [default] 

18 

19 

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")) 

25 

26 

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. 

32 

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 ) 

39 

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) 

46 

47 res = {} 

48 

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 

59 

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()) 

73 

74 current.request.get().response.headers["Content-Type"] = "application/json" 

75 return json.dumps(res or None, cls=CustomJsonEncoder) 

76 

77 

78@exposed 

79@skey 

80def setLanguage(lang): 

81 if lang in conf.i18n.available_languages: 

82 current.language.set(lang) 

83 

84 

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" 

92 

93 version = conf.version 

94 

95 # always fill up to 4 parts 

96 while len(version) < 4: 

97 version += (None,) 

98 

99 return json.dumps(version[:4]) 

100 

101 

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"))) 

108 

109 

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 

116 

117 return any(fnmatch.fnmatch(current.request.get().path, pat) for pat in conf.security.admin_allowed_paths) 

118 

119 

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") 

134 

135 

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" 

143 

144 result = {} 

145 config = {k.replace("_", "."): v for k, v in conf.admin.items(True)} 

146 

147 if conf.user.google_client_id: 

148 config["admin.user.google.clientID"] = conf.user.google_client_id 

149 

150 config["admin.language"] = current.language.get() or conf.i18n.default_language 

151 config["admin.languages"] = conf.i18n.available_languages 

152 

153 if _can_access(current.user.get()): 

154 # Core Version 

155 version = conf.version 

156 

157 # always fill up to 4 parts 

158 while len(version) < 4: 

159 version += (None,) 

160 

161 result["version"] = version[:4] 

162 

163 # Modules 

164 modules = {} 

165 visited_objects = set() 

166 

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 

172 

173 for key in dir(parent): 

174 module = getattr(parent, key, None) 

175 if not isinstance(module, Module): 

176 continue 

177 

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 

185 

186 visited_objects.add(module) 

187 

188 if admin_info := module.describe(): 

189 # map path --> config 

190 modules[module.modulePath.removeprefix("/vi/").replace("/", ".")] = admin_info 

191 

192 # Collect children 

193 collect_modules(module, depth=depth + 1) 

194 

195 collect_modules(conf.main_app.vi) 

196 

197 result["modules"] = modules 

198 

199 result["configuration"] = config 

200 

201 return json.dumps(result, cls=CustomJsonEncoder) 

202 

203 

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"]) 

208 

209 

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" 

216 

217 def resolve(o): 

218 result = [] 

219 

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) 

226 

227 return result 

228 

229 return json.dumps(resolve(conf.main_resolver)) 

230 

231 

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 

240 

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 

245 

246 return obj