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
« 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
14class CustomJsonEncoder(json.JSONEncoder):
15 """
16 This custom JSON-Encoder for this json-render ensures that translations are evaluated and can be dumped.
17 """
19 def default(self, o: t.Any) -> t.Any:
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)
36class DefaultRender(AbstractRenderer):
37 kind = "json"
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]
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])
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()]
72 return structure
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()
79 def renderEntry(self, skel: SkeletonInstance, actionName, params=None, *, next_url: t.Optional[str] = None):
80 structure = None
81 errors = None
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]
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")
100 vals = skel # VIUR4 DEPRECATION!!!
102 res = {
103 "action": actionName,
104 "values": vals,
105 "structure": structure,
106 "errors": errors,
107 "next_url": next_url,
108 "params": params,
109 }
111 current.request.get().response.headers["Content-Type"] = "application/json"
112 return json.dumps(res, cls=CustomJsonEncoder)
114 def view(self, skel: SkeletonInstance, action: str = "view", params=None, **kwargs):
115 return self.renderEntry(skel, action, params)
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")
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 }
133 current.request.get().response.headers["Content-Type"] = "application/json"
134 return json.dumps(res, cls=CustomJsonEncoder)
136 def add(self, skel: SkeletonInstance, action: str = "add", params=None, **kwargs):
137 return self.renderEntry(skel, action, params)
139 def edit(self, skel: SkeletonInstance, action: str = "edit", params=None, **kwargs):
140 return self.renderEntry(skel, action, params)
142 def editSuccess(self, skel: SkeletonInstance, action: str = "editSuccess", params=None, **kwargs):
143 return self.renderEntry(skel, action, params)
145 def addSuccess(self, skel: SkeletonInstance, action: str = "addSuccess", params=None, **kwargs):
146 return self.renderEntry(skel, action, params)
148 def deleteSuccess(self, skel: SkeletonInstance, params=None, *args, **kwargs):
149 return json.dumps("OKAY")
151 def listRootNodes(self, rootNodes, *args, **kwargs):
152 current.request.get().response.headers["Content-Type"] = "application/json"
153 return json.dumps(rootNodes, cls=CustomJsonEncoder)
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.
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)