Coverage for /home/runner/work/viur-core/viur-core/viur/src/viur/core/render/vi/__init__.py: 0%

118 statements  

« prev     ^ index     » next       coverage.py v7.6.1, created at 2024-09-03 13:41 +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 

9# noinspection PyUnresolvedReferences 

10from viur.core.render.vi.user import UserRender as user # this import must exist! 

11from viur.core.skeleton import SkeletonInstance 

12 

13 

14class default(DefaultRender): 

15 kind = "json.vi" 

16 

17 

18__all__ = [default] 

19 

20 

21@exposed 

22def timestamp(*args, **kwargs): 

23 d = datetime.datetime.now() 

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

25 return json.dumps(d.strftime("%Y-%m-%dT%H-%M-%S")) 

26 

27 

28@exposed 

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 path = module.split(".") 

36 moduleObj = conf.main_app.vi 

37 while path: 

38 moduleObj = getattr(moduleObj, path.pop(0), None) 

39 if not isinstance(moduleObj, Module) or not moduleObj.describe(): 

40 return json.dumps(None) 

41 

42 res = {} 

43 

44 # check for tree prototype 

45 if "nodeSkelCls" in dir(moduleObj): 

46 # Try Node/Leaf 

47 for stype in ("viewSkel", "editSkel", "addSkel"): 

48 for treeType in ("node", "leaf"): 

49 if stype in dir(moduleObj): 

50 try: 

51 skel = getattr(moduleObj, stype)(treeType) 

52 except (TypeError, ValueError): 

53 continue 

54 

55 if isinstance(skel, SkeletonInstance): 

56 storeType = stype.replace("Skel", "") + ("LeafSkel" if treeType == "leaf" else "NodeSkel") 

57 res[storeType] = DefaultRender.render_structure(skel.structure()) 

58 else: 

59 # every other prototype 

60 for stype in ("viewSkel", "editSkel", "addSkel"): # Unknown skel type 

61 if stype in dir(moduleObj): 

62 try: 

63 skel = getattr(moduleObj, stype)() 

64 except (TypeError, ValueError): 

65 continue 

66 if isinstance(skel, SkeletonInstance): 

67 res[stype] = DefaultRender.render_structure(skel.structure()) 

68 

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

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

71 

72 

73@exposed 

74@skey 

75def setLanguage(lang): 

76 if lang in conf.i18n.available_languages: 

77 current.language.set(lang) 

78 

79 

80@exposed 

81def dumpConfig(): 

82 res = {} 

83 visited_objects = set() 

84 

85 def collect_modules(parent, depth: int = 0) -> None: 

86 """Recursively collects all routable modules for the vi renderer""" 

87 if depth > 10: 

88 logging.warning(f"Reached maximum recursion limit of {depth} at {parent=}") 

89 return 

90 

91 for key in dir(parent): 

92 module = getattr(parent, key, None) 

93 if not isinstance(module, Module): 

94 continue 

95 if module in visited_objects: 

96 # Some modules reference other modules as parents, this will 

97 # lead to infinite recursion. We can avoid reaching the 

98 # maximum recursion limit by remembering already seen modules. 

99 if conf.debug.trace: 

100 logging.debug(f"Already visited and added {module=}") 

101 continue 

102 visited_objects.add(module) 

103 

104 if admin_info := module.describe(): 

105 # map path --> config 

106 res[module.modulePath.removeprefix("/vi/").replace("/", ".")] = admin_info 

107 # Collect children 

108 collect_modules(module, depth=depth + 1) 

109 

110 collect_modules(conf.main_app.vi) 

111 

112 res = { 

113 "modules": res, 

114 # "configuration": dict(conf.admin.items()), # TODO: this could be the short vision, if we use underscores 

115 "configuration": { 

116 k.replace("_", "."): v for k, v in conf.admin.items(True) 

117 } 

118 } 

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

120 return json.dumps(res, cls=CustomJsonEncoder) 

121 

122 

123@exposed 

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

125 """ 

126 Returns viur-core version number 

127 """ 

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

129 

130 version = conf.version 

131 

132 # always fill up to 4 parts 

133 while len(version) < 4: 

134 version += (None,) 

135 

136 if conf.instance.is_dev_server \ 

137 or ((cuser := current.user.get()) and ("root" in cuser["access"] or "admin" in cuser["access"])): 

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

139 

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

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

142 

143 

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

145 """ 

146 General access restrictions for the vi-render. 

147 """ 

148 

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

150 return True 

151 

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

153 

154 

155@exposed 

156def index(*args, **kwargs): 

157 if args or kwargs: 

158 raise errors.NotFound() 

159 if ( 

160 not conf.instance.project_base_path.joinpath("vi", "main.html").exists() 

161 and not conf.instance.project_base_path.joinpath("admin", "main.html").exists() 

162 ): 

163 raise errors.NotFound() 

164 if conf.instance.is_dev_server or current.request.get().isSSLConnection: 

165 raise errors.Redirect("/vi/s/main.html") 

166 else: 

167 appVersion = current.request.get().request.host 

168 raise errors.Redirect(f"https://{appVersion}/vi/s/main.html") 

169 

170 

171@exposed 

172def get_settings(): 

173 """ 

174 Get public admin-tool specific settings, requires no user to be logged in. 

175 This is used by new vi-admin. 

176 """ 

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

178 

179 if conf.user.google_client_id: 

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

181 

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

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

184 

185 

186def _postProcessAppObj(obj): 

187 obj["skey"] = json_render_skey 

188 obj["timestamp"] = timestamp 

189 obj["config"] = dumpConfig 

190 obj["settings"] = get_settings 

191 obj["getStructure"] = getStructure 

192 obj["canAccess"] = canAccess 

193 obj["setLanguage"] = setLanguage 

194 obj["getVersion"] = getVersion 

195 obj["index"] = index 

196 return obj