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
« prev ^ index » next coverage.py v7.6.3, created at 2024-10-16 22:16 +0000
1import json
2from enum import Enum
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
12class CustomJsonEncoder(json.JSONEncoder):
13 """
14 This custom JSON-Encoder for this json-render ensures that translations are evaluated and can be dumped.
15 """
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)
29class DefaultRender(object):
30 kind = "json"
32 def __init__(self, parent=None, *args, **kwargs):
33 super(DefaultRender, self).__init__(*args, **kwargs)
34 self.parent = parent
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]
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])
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()]
69 return structure
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.
79 It can be overridden and super-called from a custom renderer.
81 :param bone: The bone which value should be rendered.
82 :type bone: Any bone that inherits from :class:`server.bones.base.BaseBone`.
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
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
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.
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
134 res = {}
136 for key, bone in skel.items():
137 res[key] = self.renderBoneValue(bone, skel, key)
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
152 def renderEntry(self, skel: SkeletonInstance, actionName, params=None):
153 structure = None
154 errors = None
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())
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]
167 else: # Hopefully we can pass it directly...
168 vals = skel
170 res = {
171 "action": actionName,
172 "errors": errors,
173 "params": params,
174 "structure": structure,
175 "values": vals,
176 }
178 current.request.get().response.headers["Content-Type"] = "application/json"
179 return json.dumps(res, cls=CustomJsonEncoder)
181 def view(self, skel: SkeletonInstance, action: str = "view", params=None, **kwargs):
182 return self.renderEntry(skel, action, params)
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
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())
195 cursor = skellist.getCursor()
196 orders = skellist.get_orders()
198 skellist = [self.renderSkelValues(skel) for skel in skellist]
199 else:
200 skellist = []
202 # VIUR4 ;-)
203 # loc = locals()
204 # res = {k: loc[k] for k in ("action", "cursor", "params", "skellist", "structure", "orders") if loc[k]}
206 res = {
207 "action": action,
208 "cursor": cursor,
209 "params": params,
210 "skellist": skellist,
211 "structure": structure,
212 "orders": orders
213 }
215 current.request.get().response.headers["Content-Type"] = "application/json"
216 return json.dumps(res, cls=CustomJsonEncoder)
218 def add(self, skel: SkeletonInstance, action: str = "add", params=None, **kwargs):
219 return self.renderEntry(skel, action, params)
221 def edit(self, skel: SkeletonInstance, action: str = "edit", params=None, **kwargs):
222 return self.renderEntry(skel, action, params)
224 def editSuccess(self, skel: SkeletonInstance, action: str = "editSuccess", params=None, **kwargs):
225 return self.renderEntry(skel, action, params)
227 def addSuccess(self, skel: SkeletonInstance, action: str = "addSuccess", params=None, **kwargs):
228 return self.renderEntry(skel, action, params)
230 def deleteSuccess(self, skel: SkeletonInstance, params=None, *args, **kwargs):
231 return json.dumps("OKAY")
233 def listRootNodes(self, rootNodes, *args, **kwargs):
234 for rn in rootNodes:
235 rn["key"] = db.encodeKey(rn["key"])
237 return json.dumps(rootNodes, cls=CustomJsonEncoder)