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

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 

10 

11 

12class default(DefaultRender): 

13 kind = "json.vi" 

14 

15 

16__all__ = [default] 

17 

18 

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

24 

25 

26@exposed 

27def getStructure(module): 

28 """ 

29 Returns all available skeleton structures for a given module. 

30 

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) 

39 

40 res = {} 

41 

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 

52 

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

66 

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

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

69 

70 

71@exposed 

72@skey 

73def setLanguage(lang): 

74 if lang in conf.i18n.available_languages: 

75 current.language.set(lang) 

76 

77 

78@exposed 

79def dumpConfig(): 

80 res = {} 

81 visited_objects = set() 

82 

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 

88 

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) 

101 

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) 

107 

108 collect_modules(conf.main_app.vi) 

109 

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) 

119 

120 

121@exposed 

122def getVersion(*args, **kwargs): 

123 """ 

124 Returns viur-core version number 

125 """ 

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

127 

128 version = conf.version 

129 

130 # always fill up to 4 parts 

131 while len(version) < 4: 

132 version += (None,) 

133 

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

137 

138 # Hide patch level + appendix to non-authorized users 

139 return json.dumps((version[0], version[1], None, None)) 

140 

141 

142def canAccess(*args, **kwargs) -> bool: 

143 """ 

144 General access restrictions for the vi-render. 

145 """ 

146 

147 if (cuser := current.user.get()) and any(right in cuser["access"] for right in ("root", "admin")): 

148 return True 

149 

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

151 

152 

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

167 

168 

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

176 

177 if conf.user.google_client_id: 

178 fields["admin.user.google.clientID"] = conf.user.google_client_id 

179 

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

181 return json.dumps(fields, cls=CustomJsonEncoder) 

182 

183 

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