Coverage for / home / runner / work / viur-core / viur-core / viur / src / viur / core / render / json / default.py: 0%
87 statements
« prev ^ index » next coverage.py v7.13.4, created at 2026-03-13 14:41 +0000
« prev ^ index » next coverage.py v7.13.4, created at 2026-03-13 14:41 +0000
1import json
2import typing as t
3import logging
4from enum import Enum
5from viur.core import db, current
6from viur.core.bones import BaseBone
7from viur.core.render.abstract import AbstractRenderer
8from viur.core.skeleton import SkeletonInstance, SkelList
9from viur.core.i18n import translate
10from viur.core.config import conf
11from datetime import datetime
12from deprecated.sphinx import deprecated
15# VIUR4: Remove this piece of sh..
16class CustomJsonEncoder(json.JSONEncoder):
17 """
18 This custom JSON-Encoder for this json-render ensures that translations are evaluated and can be dumped.
19 """
21 def default(self, o: t.Any) -> t.Any:
23 if isinstance(o, translate):
24 return str(o)
25 elif isinstance(o, datetime):
26 return o.isoformat()
27 elif isinstance(o, db.Key):
28 return str(o)
29 elif isinstance(o, Enum):
30 return o.value
31 elif isinstance(o, set):
32 return tuple(o)
33 elif isinstance(o, SkeletonInstance):
34 return {bone_name: o[bone_name] for bone_name in o}
35 return json.JSONEncoder.default(self, o)
38class DefaultRender(AbstractRenderer):
39 kind = "json"
41 @staticmethod
42 def render_structure(structure: dict):
43 """
44 Performs structure rewriting according to VIUR2/3 compatibility flags.
45 #FIXME: Remove this entire function with VIUR4
46 """
47 for struct in structure.values():
48 # Optionally replace new-key by a copy of the value under the old-key
49 if "json.bone.structure.camelcasenames" in conf.compatibility:
50 for find, replace in {
51 "boundslat": "boundsLat",
52 "boundslng": "boundsLng",
53 "emptyvalue": "emptyValue",
54 "max": "maxAmount",
55 "maxlength": "maxLength",
56 "min": "minAmount",
57 "preventduplicates": "preventDuplicates",
58 "readonly": "readOnly",
59 "valid_html": "validHtml",
60 "valid_mime_types": "validMimeTypes",
61 }.items():
62 if find in struct:
63 struct[replace] = struct[find]
65 # Call render_structure() recursively on "using" and "relskel" members.
66 for substruct in ("using", "relskel"):
67 if substruct in struct and struct[substruct]:
68 struct[substruct] = DefaultRender.render_structure(struct[substruct])
70 # Optionally return list of tuples instead of dict
71 if "json.bone.structure.keytuples" in conf.compatibility:
72 return [(key, struct) for key, struct in structure.items()]
74 return structure
76 @deprecated(version="3.8.0", reason="Just use `skel.dump()` for this now")
77 def renderSkelValues(self, skel: SkeletonInstance, injectDownloadURL: bool = False):
78 logging.warning(
79 "DefaultRender.renderSkelValues() is obsolete, just use `skel.dump()` for it now!",
80 stacklevel=3,
81 )
82 return skel.dump()
84 @deprecated(version="3.8.0", reason="Just use `skel.dump()` for this now")
85 def renderBoneValue(self, bone: BaseBone, skel: SkeletonInstance, key: str) -> list | dict | None:
86 logging.warning(
87 "DefaultRender.renderBoneValue() is obsolete, just use `bone.dump()` for it now!",
88 stacklevel=3,
89 )
90 return bone.dump(skel, key)
92 def renderEntry(self, skel: SkeletonInstance, actionName, params=None, *, next_url: t.Optional[str] = None):
93 structure = None
94 errors = None
96 if isinstance(skel, SkeletonInstance):
97 vals = skel.dump()
98 structure = DefaultRender.render_structure(skel.structure())
99 errors = [{
100 "error": error.severity.name.upper(),
101 "errorMessage": error.errorMessage,
102 "fieldPath": error.fieldPath,
103 "invalidatedFields": error.invalidatedFields,
104 "severity": error.severity.value,
105 } for error in skel.errors]
107 else:
108 # VIUR4 DEPRECATION
109 logging.warning(f"Passing a {type(skel)!r} here is invalid. It should be a SkeletonInstance.")
110 if isinstance(skel, (list, tuple)):
111 raise ValueError("Cannot handle lists here")
113 vals = skel # VIUR4 DEPRECATION!!!
115 res = {
116 "action": actionName,
117 "values": vals,
118 "structure": structure,
119 "errors": errors,
120 "next_url": next_url,
121 "params": params,
122 }
124 current.request.get().response.headers["Content-Type"] = "application/json"
125 return json.dumps(res, cls=CustomJsonEncoder)
127 def view(self, skel: SkeletonInstance, action: str = "view", params=None, **kwargs):
128 return self.renderEntry(skel, action, params)
130 def list(self, skellist: SkelList, action: str = "list", params=None, **kwargs):
131 if not isinstance(skellist, SkelList):
132 raise ValueError("Function requires a SkelList")
134 res = {
135 "action": action,
136 "cursor": skellist.getCursor() if skellist else None,
137 "params": params,
138 "skellist": [item.dump() for item in skellist],
139 "structure":
140 DefaultRender.render_structure(skellist[0].structure())
141 if skellist and "json.bone.structure.inlists" in conf.compatibility
142 else None,
143 "orders": skellist.get_orders() if skellist else None,
144 }
146 current.request.get().response.headers["Content-Type"] = "application/json"
147 return json.dumps(res, cls=CustomJsonEncoder)
149 def add(self, skel: SkeletonInstance, action: str = "add", params=None, **kwargs):
150 return self.renderEntry(skel, action, params)
152 def edit(self, skel: SkeletonInstance, action: str = "edit", params=None, **kwargs):
153 return self.renderEntry(skel, action, params)
155 def editSuccess(self, skel: SkeletonInstance, action: str = "editSuccess", params=None, **kwargs):
156 return self.renderEntry(skel, action, params)
158 def addSuccess(self, skel: SkeletonInstance, action: str = "addSuccess", params=None, **kwargs):
159 return self.renderEntry(skel, action, params)
161 def deleteSuccess(self, skel: SkeletonInstance, params=None, *args, **kwargs):
162 return json.dumps("OKAY")
164 def listRootNodes(self, rootNodes, *args, **kwargs):
165 current.request.get().response.headers["Content-Type"] = "application/json"
166 return json.dumps(rootNodes, cls=CustomJsonEncoder)
168 def render(
169 self,
170 action: str,
171 skel: t.Optional[SkeletonInstance] = None,
172 *,
173 next_url: t.Optional[str] = None,
174 **kwargs
175 ):
176 """
177 Universal rendering function.
179 Handles an action and a skeleton. It shall be used by any action, in future.
180 """
181 return self.renderEntry(skel, action, next_url=next_url, params=kwargs)