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

82 statements  

« prev     ^ index     » next       coverage.py v7.10.6, created at 2025-09-13 11:04 +0000

1import json 

2import typing as t 

3import logging 

4from enum import Enum 

5from viur.core import db, current 

6from viur.core.render.abstract import AbstractRenderer 

7from viur.core.skeleton import SkeletonInstance, SkelList 

8from viur.core.i18n import translate 

9from viur.core.config import conf 

10from datetime import datetime 

11from deprecated.sphinx import deprecated 

12 

13 

14class CustomJsonEncoder(json.JSONEncoder): 

15 """ 

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

17 """ 

18 

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

20 

21 if isinstance(o, translate): 

22 return str(o) 

23 elif isinstance(o, datetime): 

24 return o.isoformat() 

25 elif isinstance(o, db.Key): 

26 return str(o) 

27 elif isinstance(o, Enum): 

28 return o.value 

29 elif isinstance(o, set): 

30 return tuple(o) 

31 elif isinstance(o, SkeletonInstance): 

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

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

34 

35 

36class DefaultRender(AbstractRenderer): 

37 kind = "json" 

38 

39 @staticmethod 

40 def render_structure(structure: dict): 

41 """ 

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

43 #FIXME: Remove this entire function with VIUR4 

44 """ 

45 for struct in structure.values(): 

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

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

48 for find, replace in { 

49 "boundslat": "boundsLat", 

50 "boundslng": "boundsLng", 

51 "emptyvalue": "emptyValue", 

52 "max": "maxAmount", 

53 "maxlength": "maxLength", 

54 "min": "minAmount", 

55 "preventduplicates": "preventDuplicates", 

56 "readonly": "readOnly", 

57 "valid_html": "validHtml", 

58 "valid_mime_types": "validMimeTypes", 

59 }.items(): 

60 if find in struct: 

61 struct[replace] = struct[find] 

62 

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

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

65 if substruct in struct and struct[substruct]: 

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

67 

68 # Optionally return list of tuples instead of dict 

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

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

71 

72 return structure 

73 

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

75 def renderSkelValues(self, skel: SkeletonInstance): 

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

77 return skel.dump() 

78 

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

80 structure = None 

81 errors = None 

82 

83 if isinstance(skel, SkeletonInstance): 

84 vals = skel.dump() 

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

86 errors = [{ 

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

88 "errorMessage": error.errorMessage, 

89 "fieldPath": error.fieldPath, 

90 "invalidatedFields": error.invalidatedFields, 

91 "severity": error.severity.value, 

92 } for error in skel.errors] 

93 

94 else: 

95 # VIUR4 DEPRECATION 

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

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

98 raise ValueError("Cannot handle lists here") 

99 

100 vals = skel # VIUR4 DEPRECATION!!! 

101 

102 res = { 

103 "action": actionName, 

104 "values": vals, 

105 "structure": structure, 

106 "errors": errors, 

107 "next_url": next_url, 

108 "params": params, 

109 } 

110 

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

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

113 

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

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

116 

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

118 if not isinstance(skellist, SkelList): 

119 raise ValueError("Function requires a SkelList") 

120 

121 res = { 

122 "action": action, 

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

124 "params": params, 

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

126 "structure": 

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

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

129 else None, 

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

131 } 

132 

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

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

135 

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

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

138 

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

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

141 

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

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

144 

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

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

147 

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

149 return json.dumps("OKAY") 

150 

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

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

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

154 

155 def render( 

156 self, 

157 action: str, 

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

159 *, 

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

161 **kwargs 

162 ): 

163 """ 

164 Universal rendering function. 

165 

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

167 """ 

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