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

122 statements  

« prev     ^ index     » next       coverage.py v7.6.3, created at 2024-10-16 22:16 +0000

1import json 

2from enum import Enum 

3 

4from viur.core import bones, utils, db, current 

5from viur.core.skeleton import SkeletonInstance 

6from viur.core.i18n import translate 

7from viur.core.config import conf 

8from datetime import datetime 

9import typing as t 

10 

11 

12class CustomJsonEncoder(json.JSONEncoder): 

13 """ 

14 This custom JSON-Encoder for this json-render ensures that translations are evaluated and can be dumped. 

15 """ 

16 

17 def default(self, o: t.Any) -> t.Any: 

18 if isinstance(o, translate): 

19 return str(o) 

20 elif isinstance(o, datetime): 

21 return o.isoformat() 

22 elif isinstance(o, db.Key): 

23 return db.encodeKey(o) 

24 elif isinstance(o, Enum): 

25 return o.value 

26 return json.JSONEncoder.default(self, o) 

27 

28 

29class DefaultRender(object): 

30 kind = "json" 

31 

32 def __init__(self, parent=None, *args, **kwargs): 

33 super(DefaultRender, self).__init__(*args, **kwargs) 

34 self.parent = parent 

35 

36 @staticmethod 

37 def render_structure(structure: dict): 

38 """ 

39 Performs structure rewriting according to VIUR2/3 compatibility flags. 

40 # fixme: Remove this entire function with VIUR4 

41 """ 

42 for struct in structure.values(): 

43 # Optionally replace new-key by a copy of the value under the old-key 

44 if "json.bone.structure.camelcasenames" in conf.compatibility: 

45 for find, replace in { 

46 "boundslat": "boundsLat", 

47 "boundslng": "boundsLng", 

48 "emptyvalue": "emptyValue", 

49 "max": "maxAmount", 

50 "maxlength": "maxLength", 

51 "min": "minAmount", 

52 "preventduplicates": "preventDuplicates", 

53 "readonly": "readOnly", 

54 "valid_html": "validHtml", 

55 "valid_mime_types": "validMimeTypes", 

56 }.items(): 

57 if find in struct: 

58 struct[replace] = struct[find] 

59 

60 # Call render_structure() recursively on "using" and "relskel" members. 

61 for substruct in ("using", "relskel"): 

62 if substruct in struct and struct[substruct]: 

63 struct[substruct] = DefaultRender.render_structure(struct[substruct]) 

64 

65 # Optionally return list of tuples instead of dict 

66 if "json.bone.structure.keytuples" in conf.compatibility: 

67 return [(key, struct) for key, struct in structure.items()] 

68 

69 return structure 

70 

71 def renderSingleBoneValue(self, value: t.Any, 

72 bone: bones.BaseBone, 

73 skel: SkeletonInstance, 

74 key 

75 ) -> dict | str | None: 

76 """ 

77 Renders the value of a bone. 

78 

79 It can be overridden and super-called from a custom renderer. 

80 

81 :param bone: The bone which value should be rendered. 

82 :type bone: Any bone that inherits from :class:`server.bones.base.BaseBone`. 

83 

84 :return: A dict containing the rendered attributes. 

85 """ 

86 if isinstance(bone, bones.RelationalBone): 

87 if isinstance(value, dict): 

88 return { 

89 "dest": self.renderSkelValues(value["dest"], injectDownloadURL=isinstance(bone, bones.FileBone)), 

90 "rel": (self.renderSkelValues(value["rel"], injectDownloadURL=isinstance(bone, bones.FileBone)) 

91 if value["rel"] else None), 

92 } 

93 elif isinstance(bone, bones.RecordBone): 

94 return self.renderSkelValues(value) 

95 elif isinstance(bone, bones.PasswordBone): 

96 return "" 

97 else: 

98 return value 

99 return None 

100 

101 def renderBoneValue(self, bone: bones.BaseBone, skel: SkeletonInstance, key: str) -> list | dict | None: 

102 boneVal = skel[key] 

103 if bone.languages and bone.multiple: 

104 res = {} 

105 for language in bone.languages: 

106 if boneVal and language in boneVal and boneVal[language]: 

107 res[language] = [self.renderSingleBoneValue(v, bone, skel, key) for v in boneVal[language]] 

108 else: 

109 res[language] = [] 

110 elif bone.languages: 

111 res = {} 

112 for language in bone.languages: 

113 if boneVal and language in boneVal and boneVal[language] is not None: 

114 res[language] = self.renderSingleBoneValue(boneVal[language], bone, skel, key) 

115 else: 

116 res[language] = None 

117 elif bone.multiple: 

118 res = [self.renderSingleBoneValue(v, bone, skel, key) for v in boneVal] if boneVal else None 

119 else: 

120 res = self.renderSingleBoneValue(boneVal, bone, skel, key) 

121 return res 

122 

123 def renderSkelValues(self, skel: SkeletonInstance, injectDownloadURL: bool = False) -> t.Optional[dict]: 

124 """ 

125 Prepares values of one :class:`viur.core.skeleton.Skeleton` or a list of skeletons for output. 

126 

127 :param skel: Skeleton which contents will be processed. 

128 """ 

129 if skel is None: 

130 return None 

131 elif isinstance(skel, dict): 

132 return skel 

133 

134 res = {} 

135 

136 for key, bone in skel.items(): 

137 res[key] = self.renderBoneValue(bone, skel, key) 

138 

139 if ( 

140 injectDownloadURL 

141 and (file := getattr(conf.main_app, "file", None)) 

142 and "dlkey" in skel 

143 and "name" in skel 

144 ): 

145 res["downloadUrl"] = file.create_download_url( 

146 skel["dlkey"], 

147 skel["name"], 

148 expires=conf.render_json_download_url_expiration 

149 ) 

150 return res 

151 

152 def renderEntry(self, skel: SkeletonInstance, actionName, params=None): 

153 structure = None 

154 errors = None 

155 

156 if isinstance(skel, list): 

157 vals = [self.renderSkelValues(x) for x in skel] 

158 if isinstance(skel[0], SkeletonInstance): 

159 structure = DefaultRender.render_structure(skel[0].structure()) 

160 

161 elif isinstance(skel, SkeletonInstance): 

162 vals = self.renderSkelValues(skel) 

163 structure = DefaultRender.render_structure(skel.structure()) 

164 errors = [{"severity": x.severity.value, "fieldPath": x.fieldPath, "errorMessage": x.errorMessage, 

165 "invalidatedFields": x.invalidatedFields} for x in skel.errors] 

166 

167 else: # Hopefully we can pass it directly... 

168 vals = skel 

169 

170 res = { 

171 "action": actionName, 

172 "errors": errors, 

173 "params": params, 

174 "structure": structure, 

175 "values": vals, 

176 } 

177 

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

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

180 

181 def view(self, skel: SkeletonInstance, action: str = "view", params=None, **kwargs): 

182 return self.renderEntry(skel, action, params) 

183 

184 def list(self, skellist, action: str = "list", params=None, **kwargs): 

185 # Rendering the structure in lists is flagged as deprecated 

186 structure = None 

187 cursor = None 

188 orders = None 

189 

190 if skellist: 

191 if isinstance(skellist[0], SkeletonInstance): 

192 if "json.bone.structure.inlists" in conf.compatibility: 

193 structure = DefaultRender.render_structure(skellist[0].structure()) 

194 

195 cursor = skellist.getCursor() 

196 orders = skellist.get_orders() 

197 

198 skellist = [self.renderSkelValues(skel) for skel in skellist] 

199 else: 

200 skellist = [] 

201 

202 # VIUR4 ;-) 

203 # loc = locals() 

204 # res = {k: loc[k] for k in ("action", "cursor", "params", "skellist", "structure", "orders") if loc[k]} 

205 

206 res = { 

207 "action": action, 

208 "cursor": cursor, 

209 "params": params, 

210 "skellist": skellist, 

211 "structure": structure, 

212 "orders": orders 

213 } 

214 

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

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

217 

218 def add(self, skel: SkeletonInstance, action: str = "add", params=None, **kwargs): 

219 return self.renderEntry(skel, action, params) 

220 

221 def edit(self, skel: SkeletonInstance, action: str = "edit", params=None, **kwargs): 

222 return self.renderEntry(skel, action, params) 

223 

224 def editSuccess(self, skel: SkeletonInstance, action: str = "editSuccess", params=None, **kwargs): 

225 return self.renderEntry(skel, action, params) 

226 

227 def addSuccess(self, skel: SkeletonInstance, action: str = "addSuccess", params=None, **kwargs): 

228 return self.renderEntry(skel, action, params) 

229 

230 def deleteSuccess(self, skel: SkeletonInstance, params=None, *args, **kwargs): 

231 return json.dumps("OKAY") 

232 

233 def listRootNodes(self, rootNodes, *args, **kwargs): 

234 for rn in rootNodes: 

235 rn["key"] = db.encodeKey(rn["key"]) 

236 

237 return json.dumps(rootNodes, cls=CustomJsonEncoder)