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

87 statements  

« prev     ^ index     » next       coverage.py v7.13.4, created at 2026-03-13 14:41 +0000

1import json 

2import typing as t 

3import logging 

4from enum import Enum 

5from viur.core import db, current 

6from viur.core.bones import BaseBone 

7from viur.core.render.abstract import AbstractRenderer 

8from viur.core.skeleton import SkeletonInstance, SkelList 

9from viur.core.i18n import translate 

10from viur.core.config import conf 

11from datetime import datetime 

12from deprecated.sphinx import deprecated 

13 

14 

15# VIUR4: Remove this piece of sh.. 

16class CustomJsonEncoder(json.JSONEncoder): 

17 """ 

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

19 """ 

20 

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

22 

23 if isinstance(o, translate): 

24 return str(o) 

25 elif isinstance(o, datetime): 

26 return o.isoformat() 

27 elif isinstance(o, db.Key): 

28 return str(o) 

29 elif isinstance(o, Enum): 

30 return o.value 

31 elif isinstance(o, set): 

32 return tuple(o) 

33 elif isinstance(o, SkeletonInstance): 

34 return {bone_name: o[bone_name] for bone_name in o} 

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

36 

37 

38class DefaultRender(AbstractRenderer): 

39 kind = "json" 

40 

41 @staticmethod 

42 def render_structure(structure: dict): 

43 """ 

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

45 #FIXME: Remove this entire function with VIUR4 

46 """ 

47 for struct in structure.values(): 

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

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

50 for find, replace in { 

51 "boundslat": "boundsLat", 

52 "boundslng": "boundsLng", 

53 "emptyvalue": "emptyValue", 

54 "max": "maxAmount", 

55 "maxlength": "maxLength", 

56 "min": "minAmount", 

57 "preventduplicates": "preventDuplicates", 

58 "readonly": "readOnly", 

59 "valid_html": "validHtml", 

60 "valid_mime_types": "validMimeTypes", 

61 }.items(): 

62 if find in struct: 

63 struct[replace] = struct[find] 

64 

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

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

67 if substruct in struct and struct[substruct]: 

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

69 

70 # Optionally return list of tuples instead of dict 

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

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

73 

74 return structure 

75 

76 @deprecated(version="3.8.0", reason="Just use `skel.dump()` for this now") 

77 def renderSkelValues(self, skel: SkeletonInstance, injectDownloadURL: bool = False): 

78 logging.warning( 

79 "DefaultRender.renderSkelValues() is obsolete, just use `skel.dump()` for it now!", 

80 stacklevel=3, 

81 ) 

82 return skel.dump() 

83 

84 @deprecated(version="3.8.0", reason="Just use `skel.dump()` for this now") 

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

86 logging.warning( 

87 "DefaultRender.renderBoneValue() is obsolete, just use `bone.dump()` for it now!", 

88 stacklevel=3, 

89 ) 

90 return bone.dump(skel, key) 

91 

92 def renderEntry(self, skel: SkeletonInstance, actionName, params=None, *, next_url: t.Optional[str] = None): 

93 structure = None 

94 errors = None 

95 

96 if isinstance(skel, SkeletonInstance): 

97 vals = skel.dump() 

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

99 errors = [{ 

100 "error": error.severity.name.upper(), 

101 "errorMessage": error.errorMessage, 

102 "fieldPath": error.fieldPath, 

103 "invalidatedFields": error.invalidatedFields, 

104 "severity": error.severity.value, 

105 } for error in skel.errors] 

106 

107 else: 

108 # VIUR4 DEPRECATION 

109 logging.warning(f"Passing a {type(skel)!r} here is invalid. It should be a SkeletonInstance.") 

110 if isinstance(skel, (list, tuple)): 

111 raise ValueError("Cannot handle lists here") 

112 

113 vals = skel # VIUR4 DEPRECATION!!! 

114 

115 res = { 

116 "action": actionName, 

117 "values": vals, 

118 "structure": structure, 

119 "errors": errors, 

120 "next_url": next_url, 

121 "params": params, 

122 } 

123 

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

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

126 

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

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

129 

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

131 if not isinstance(skellist, SkelList): 

132 raise ValueError("Function requires a SkelList") 

133 

134 res = { 

135 "action": action, 

136 "cursor": skellist.getCursor() if skellist else None, 

137 "params": params, 

138 "skellist": [item.dump() for item in skellist], 

139 "structure": 

140 DefaultRender.render_structure(skellist[0].structure()) 

141 if skellist and "json.bone.structure.inlists" in conf.compatibility 

142 else None, 

143 "orders": skellist.get_orders() if skellist else None, 

144 } 

145 

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

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

148 

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

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

151 

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

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

154 

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

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

157 

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

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

160 

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

162 return json.dumps("OKAY") 

163 

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

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

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

167 

168 def render( 

169 self, 

170 action: str, 

171 skel: t.Optional[SkeletonInstance] = None, 

172 *, 

173 next_url: t.Optional[str] = None, 

174 **kwargs 

175 ): 

176 """ 

177 Universal rendering function. 

178 

179 Handles an action and a skeleton. It shall be used by any action, in future. 

180 """ 

181 return self.renderEntry(skel, action, next_url=next_url, params=kwargs)