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

91 statements  

« prev     ^ index     » next       coverage.py v7.16.1, created at 2026-09-25 15:02 +0000

1import json 

2import typing as t 

3import logging 

4import warnings 

5from decimal import Decimal 

6from enum import Enum 

7from viur.core import db, current 

8from viur.core.bones import BaseBone 

9from viur.core.render.abstract import AbstractRenderer 

10from viur.core.skeleton import SkeletonInstance, SkelList 

11from viur.core.i18n import translate 

12from viur.core.config import conf 

13from datetime import datetime 

14from deprecated.sphinx import deprecated 

15 

16 

17# VIUR4: Remove this piece of sh.. 

18class CustomJsonEncoder(json.JSONEncoder): 

19 """ 

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

21 """ 

22 

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

24 

25 if isinstance(o, Decimal): 

26 return str(o) 

27 elif isinstance(o, translate): 27 ↛ 28line 27 didn't jump to line 28 because the condition on line 27 was never true

28 return str(o) 

29 elif isinstance(o, datetime): 

30 return o.isoformat() 

31 elif isinstance(o, db.Key): 31 ↛ 32line 31 didn't jump to line 32 because the condition on line 31 was never true

32 return str(o) 

33 elif isinstance(o, Enum): 

34 return o.value 

35 elif isinstance(o, set): 35 ↛ 37line 35 didn't jump to line 37 because the condition on line 35 was always true

36 return tuple(o) 

37 elif isinstance(o, SkeletonInstance): 

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

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

40 

41 

42class DefaultRender(AbstractRenderer): 

43 kind = "json" 

44 

45 @staticmethod 

46 def render_structure(structure: dict): 

47 """ 

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

49 #FIXME: Remove this entire function with VIUR4 

50 """ 

51 for struct in structure.values(): 

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

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

54 for find, replace in { 

55 "boundslat": "boundsLat", 

56 "boundslng": "boundsLng", 

57 "emptyvalue": "emptyValue", 

58 "max": "maxAmount", 

59 "maxlength": "maxLength", 

60 "min": "minAmount", 

61 "preventduplicates": "preventDuplicates", 

62 "readonly": "readOnly", 

63 "valid_html": "validHtml", 

64 "valid_mime_types": "validMimeTypes", 

65 }.items(): 

66 if find in struct: 

67 struct[replace] = struct[find] 

68 

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

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

71 if substruct in struct and struct[substruct]: 

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

73 

74 # Optionally return list of tuples instead of dict 

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

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

77 

78 return structure 

79 

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

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

82 logging.warning( 

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

84 stacklevel=3, 

85 ) 

86 return skel.dump() 

87 

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

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

90 logging.warning( 

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

92 stacklevel=3, 

93 ) 

94 return bone.dump(skel, key) 

95 

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

97 structure = None 

98 errors = None 

99 

100 if isinstance(skel, SkeletonInstance): 

101 vals = skel.dump() 

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

103 errors = [{ 

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

105 "errorMessage": error.errorMessage, 

106 "fieldPath": error.fieldPath, 

107 "invalidatedFields": error.invalidatedFields, 

108 "severity": error.severity.value, 

109 } for error in skel.errors] 

110 

111 else: 

112 # VIUR4 DEPRECATION 

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

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

115 raise ValueError("Cannot handle lists here") 

116 

117 vals = skel # VIUR4 DEPRECATION!!! 

118 

119 res = { 

120 "action": actionName, 

121 "values": vals, 

122 "structure": structure, 

123 "errors": errors, 

124 "next_url": next_url, 

125 "params": params, 

126 } 

127 

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

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

130 

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

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

133 

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

135 if not isinstance(skellist, SkelList): 

136 raise ValueError("Function requires a SkelList") 

137 

138 res = { 

139 "action": action, 

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

141 "params": params, 

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

143 "structure": 

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

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

146 else None, 

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

148 } 

149 

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

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

152 

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

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

155 

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

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

158 

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

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

161 

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

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

164 

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

166 return json.dumps("OKAY") 

167 

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

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

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

171 

172 def render( 

173 self, 

174 action: str, 

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

176 *, 

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

178 **kwargs 

179 ): 

180 """ 

181 Universal rendering function. 

182 

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

184 """ 

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