Coverage for /home/runner/work/viur-core/viur-core/viur/src/viur/core/render/json/default.py: 0%
125 statements
« prev ^ index » next coverage.py v7.6.12, created at 2025-02-27 07:59 +0000
« prev ^ index » next coverage.py v7.6.12, created at 2025-02-27 07:59 +0000
1import json
2import typing as t
3from enum import Enum
5from viur.core import bones, db, current
6from viur.core.render.abstract import AbstractRenderer
7from viur.core.skeleton import SkeletonInstance
8from viur.core.i18n import translate
9from viur.core.config import conf
10from datetime import datetime
13class CustomJsonEncoder(json.JSONEncoder):
14 """
15 This custom JSON-Encoder for this json-render ensures that translations are evaluated and can be dumped.
16 """
18 def default(self, o: t.Any) -> t.Any:
20 if isinstance(o, translate):
21 return str(o)
22 elif isinstance(o, datetime):
23 return o.isoformat()
24 elif isinstance(o, db.Key):
25 return str(o)
26 elif isinstance(o, Enum):
27 return o.value
28 elif isinstance(o, set):
29 return tuple(o)
30 elif isinstance(o, SkeletonInstance):
31 return {bone_name: o[bone_name] for bone_name in o}
32 return json.JSONEncoder.default(self, o)
35class DefaultRender(AbstractRenderer):
36 kind = "json"
38 @staticmethod
39 def render_structure(structure: dict):
40 """
41 Performs structure rewriting according to VIUR2/3 compatibility flags.
42 # fixme: Remove this entire function with VIUR4
43 """
44 for struct in structure.values():
45 # Optionally replace new-key by a copy of the value under the old-key
46 if "json.bone.structure.camelcasenames" in conf.compatibility:
47 for find, replace in {
48 "boundslat": "boundsLat",
49 "boundslng": "boundsLng",
50 "emptyvalue": "emptyValue",
51 "max": "maxAmount",
52 "maxlength": "maxLength",
53 "min": "minAmount",
54 "preventduplicates": "preventDuplicates",
55 "readonly": "readOnly",
56 "valid_html": "validHtml",
57 "valid_mime_types": "validMimeTypes",
58 }.items():
59 if find in struct:
60 struct[replace] = struct[find]
62 # Call render_structure() recursively on "using" and "relskel" members.
63 for substruct in ("using", "relskel"):
64 if substruct in struct and struct[substruct]:
65 struct[substruct] = DefaultRender.render_structure(struct[substruct])
67 # Optionally return list of tuples instead of dict
68 if "json.bone.structure.keytuples" in conf.compatibility:
69 return [(key, struct) for key, struct in structure.items()]
71 return structure
73 def renderSingleBoneValue(self, value: t.Any,
74 bone: bones.BaseBone,
75 skel: SkeletonInstance,
76 key
77 ) -> dict | str | None:
78 """
79 Renders the value of a bone.
81 It can be overridden and super-called from a custom renderer.
83 :param bone: The bone which value should be rendered.
84 :type bone: Any bone that inherits from :class:`server.bones.base.BaseBone`.
86 :return: A dict containing the rendered attributes.
87 """
88 if isinstance(bone, bones.RelationalBone):
89 if isinstance(value, dict):
90 return {
91 "dest": self.renderSkelValues(value["dest"], injectDownloadURL=isinstance(bone, bones.FileBone)),
92 "rel": (self.renderSkelValues(value["rel"], injectDownloadURL=isinstance(bone, bones.FileBone))
93 if value["rel"] else None),
94 }
95 elif isinstance(bone, bones.RecordBone):
96 return self.renderSkelValues(value)
97 elif isinstance(bone, bones.PasswordBone):
98 return ""
99 else:
100 return value
101 return None
103 def renderBoneValue(self, bone: bones.BaseBone, skel: SkeletonInstance, key: str) -> list | dict | None:
104 boneVal = skel[key]
105 if bone.languages and bone.multiple:
106 res = {}
107 for language in bone.languages:
108 if boneVal and language in boneVal and boneVal[language]:
109 res[language] = [self.renderSingleBoneValue(v, bone, skel, key) for v in boneVal[language]]
110 else:
111 res[language] = []
112 elif bone.languages:
113 res = {}
114 for language in bone.languages:
115 if boneVal and language in boneVal and boneVal[language] is not None:
116 res[language] = self.renderSingleBoneValue(boneVal[language], bone, skel, key)
117 else:
118 res[language] = None
119 elif bone.multiple:
120 res = [self.renderSingleBoneValue(v, bone, skel, key) for v in boneVal] if boneVal else None
121 else:
122 res = self.renderSingleBoneValue(boneVal, bone, skel, key)
123 return res
125 def renderSkelValues(self, skel: SkeletonInstance, injectDownloadURL: bool = False) -> t.Optional[dict]:
126 """
127 Prepares values of one :class:`viur.core.skeleton.Skeleton` or a list of skeletons for output.
129 :param skel: Skeleton which contents will be processed.
130 """
131 if skel is None:
132 return None
133 elif isinstance(skel, dict):
134 return skel
136 res = {}
138 for key, bone in skel.items():
139 res[key] = self.renderBoneValue(bone, skel, key)
141 if (
142 injectDownloadURL
143 and (file := getattr(conf.main_app, "file", None))
144 and "dlkey" in skel
145 and "name" in skel
146 ):
147 res["downloadUrl"] = file.create_download_url(
148 skel["dlkey"],
149 skel["name"],
150 expires=conf.render_json_download_url_expiration
151 )
152 return res
154 def renderEntry(self, skel: SkeletonInstance, actionName, params=None):
155 structure = None
156 errors = None
158 if isinstance(skel, list):
159 vals = [self.renderSkelValues(x) for x in skel]
160 if isinstance(skel[0], SkeletonInstance):
161 structure = DefaultRender.render_structure(skel[0].structure())
163 elif isinstance(skel, SkeletonInstance):
164 vals = self.renderSkelValues(skel)
165 structure = DefaultRender.render_structure(skel.structure())
166 errors = [{"severity": x.severity.value, "fieldPath": x.fieldPath, "errorMessage": x.errorMessage,
167 "invalidatedFields": x.invalidatedFields} for x in skel.errors]
169 else: # Hopefully we can pass it directly...
170 vals = skel
172 res = {
173 "action": actionName,
174 "errors": errors,
175 "params": params,
176 "structure": structure,
177 "values": vals,
178 }
180 current.request.get().response.headers["Content-Type"] = "application/json"
181 return json.dumps(res, cls=CustomJsonEncoder)
183 def view(self, skel: SkeletonInstance, action: str = "view", params=None, **kwargs):
184 return self.renderEntry(skel, action, params)
186 def list(self, skellist, action: str = "list", params=None, **kwargs):
187 # Rendering the structure in lists is flagged as deprecated
188 structure = None
189 cursor = None
190 orders = None
192 if skellist:
193 if isinstance(skellist[0], SkeletonInstance):
194 if "json.bone.structure.inlists" in conf.compatibility:
195 structure = DefaultRender.render_structure(skellist[0].structure())
197 cursor = skellist.getCursor()
198 orders = skellist.get_orders()
200 skellist = [self.renderSkelValues(skel) for skel in skellist]
201 else:
202 skellist = []
204 # VIUR4 ;-)
205 # loc = locals()
206 # res = {k: loc[k] for k in ("action", "cursor", "params", "skellist", "structure", "orders") if loc[k]}
208 res = {
209 "action": action,
210 "cursor": cursor,
211 "params": params,
212 "skellist": skellist,
213 "structure": structure,
214 "orders": orders
215 }
217 current.request.get().response.headers["Content-Type"] = "application/json"
218 return json.dumps(res, cls=CustomJsonEncoder)
220 def add(self, skel: SkeletonInstance, action: str = "add", params=None, **kwargs):
221 return self.renderEntry(skel, action, params)
223 def edit(self, skel: SkeletonInstance, action: str = "edit", params=None, **kwargs):
224 return self.renderEntry(skel, action, params)
226 def editSuccess(self, skel: SkeletonInstance, action: str = "editSuccess", params=None, **kwargs):
227 return self.renderEntry(skel, action, params)
229 def addSuccess(self, skel: SkeletonInstance, action: str = "addSuccess", params=None, **kwargs):
230 return self.renderEntry(skel, action, params)
232 def deleteSuccess(self, skel: SkeletonInstance, params=None, *args, **kwargs):
233 return json.dumps("OKAY")
235 def listRootNodes(self, rootNodes, *args, **kwargs):
236 current.request.get().response.headers["Content-Type"] = "application/json"
237 return json.dumps(rootNodes, cls=CustomJsonEncoder)
239 def render(self, action: str, skel: t.Optional[SkeletonInstance] = None, **kwargs):
240 """
241 Universal rendering function.
243 Handles an action and a skeleton. It shall be used by any action, in future.
244 """
245 return self.renderEntry(skel, action, params=kwargs)