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
« 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
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 """
23 def default(self, o: t.Any) -> t.Any:
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)
42class DefaultRender(AbstractRenderer):
43 kind = "json"
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]
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])
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()]
78 return structure
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()
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)
96 def renderEntry(self, skel: SkeletonInstance, actionName, params=None, *, next_url: t.Optional[str] = None):
97 structure = None
98 errors = None
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]
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")
117 vals = skel # VIUR4 DEPRECATION!!!
119 res = {
120 "action": actionName,
121 "values": vals,
122 "structure": structure,
123 "errors": errors,
124 "next_url": next_url,
125 "params": params,
126 }
128 current.request.get().response.headers["Content-Type"] = "application/json"
129 return json.dumps(res, cls=CustomJsonEncoder)
131 def view(self, skel: SkeletonInstance, action: str = "view", params=None, **kwargs):
132 return self.renderEntry(skel, action, params)
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")
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 }
150 current.request.get().response.headers["Content-Type"] = "application/json"
151 return json.dumps(res, cls=CustomJsonEncoder)
153 def add(self, skel: SkeletonInstance, action: str = "add", params=None, **kwargs):
154 return self.renderEntry(skel, action, params)
156 def edit(self, skel: SkeletonInstance, action: str = "edit", params=None, **kwargs):
157 return self.renderEntry(skel, action, params)
159 def editSuccess(self, skel: SkeletonInstance, action: str = "editSuccess", params=None, **kwargs):
160 return self.renderEntry(skel, action, params)
162 def addSuccess(self, skel: SkeletonInstance, action: str = "addSuccess", params=None, **kwargs):
163 return self.renderEntry(skel, action, params)
165 def deleteSuccess(self, skel: SkeletonInstance, params=None, *args, **kwargs):
166 return json.dumps("OKAY")
168 def listRootNodes(self, rootNodes, *args, **kwargs):
169 current.request.get().response.headers["Content-Type"] = "application/json"
170 return json.dumps(rootNodes, cls=CustomJsonEncoder)
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.
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)