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.7, created at 2025-09-29 09:00 +0000
« prev ^ index » next coverage.py v7.10.7, created at 2025-09-29 09:00 +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
14# VIUR4: Remove this piece of sh..
15class CustomJsonEncoder(json.JSONEncoder):
16 """
17 This custom JSON-Encoder for this json-render ensures that translations are evaluated and can be dumped.
18 """
20 def default(self, o: t.Any) -> t.Any:
22 if isinstance(o, translate):
23 return str(o)
24 elif isinstance(o, datetime):
25 return o.isoformat()
26 elif isinstance(o, db.Key):
27 return str(o)
28 elif isinstance(o, Enum):
29 return o.value
30 elif isinstance(o, set):
31 return tuple(o)
32 elif isinstance(o, SkeletonInstance):
33 return {bone_name: o[bone_name] for bone_name in o}
34 return json.JSONEncoder.default(self, o)
37class DefaultRender(AbstractRenderer):
38 kind = "json"
40 @staticmethod
41 def render_structure(structure: dict):
42 """
43 Performs structure rewriting according to VIUR2/3 compatibility flags.
44 #FIXME: Remove this entire function with VIUR4
45 """
46 for struct in structure.values():
47 # Optionally replace new-key by a copy of the value under the old-key
48 if "json.bone.structure.camelcasenames" in conf.compatibility:
49 for find, replace in {
50 "boundslat": "boundsLat",
51 "boundslng": "boundsLng",
52 "emptyvalue": "emptyValue",
53 "max": "maxAmount",
54 "maxlength": "maxLength",
55 "min": "minAmount",
56 "preventduplicates": "preventDuplicates",
57 "readonly": "readOnly",
58 "valid_html": "validHtml",
59 "valid_mime_types": "validMimeTypes",
60 }.items():
61 if find in struct:
62 struct[replace] = struct[find]
64 # Call render_structure() recursively on "using" and "relskel" members.
65 for substruct in ("using", "relskel"):
66 if substruct in struct and struct[substruct]:
67 struct[substruct] = DefaultRender.render_structure(struct[substruct])
69 # Optionally return list of tuples instead of dict
70 if "json.bone.structure.keytuples" in conf.compatibility:
71 return [(key, struct) for key, struct in structure.items()]
73 return structure
75 @deprecated(version="3.8.0", reason="Just use `skel.dump()` for this now")
76 def renderSkelValues(self, skel: SkeletonInstance):
77 logging.warning(
78 "DefaultRender.renderSkelValues() is obsolete, just use `skel.dump()` for it now!",
79 stacklevel=3,
80 )
81 return skel.dump()
83 def renderEntry(self, skel: SkeletonInstance, actionName, params=None, *, next_url: t.Optional[str] = None):
84 structure = None
85 errors = None
87 if isinstance(skel, SkeletonInstance):
88 vals = skel.dump()
89 structure = DefaultRender.render_structure(skel.structure())
90 errors = [{
91 "error": error.severity.name.upper(),
92 "errorMessage": error.errorMessage,
93 "fieldPath": error.fieldPath,
94 "invalidatedFields": error.invalidatedFields,
95 "severity": error.severity.value,
96 } for error in skel.errors]
98 else:
99 # VIUR4 DEPRECATION
100 logging.warning(f"Passing a {type(skel)!r} here is invalid. It should be a SkeletonInstance.")
101 if isinstance(skel, (list, tuple)):
102 raise ValueError("Cannot handle lists here")
104 vals = skel # VIUR4 DEPRECATION!!!
106 res = {
107 "action": actionName,
108 "values": vals,
109 "structure": structure,
110 "errors": errors,
111 "next_url": next_url,
112 "params": params,
113 }
115 current.request.get().response.headers["Content-Type"] = "application/json"
116 return json.dumps(res, cls=CustomJsonEncoder)
118 def view(self, skel: SkeletonInstance, action: str = "view", params=None, **kwargs):
119 return self.renderEntry(skel, action, params)
121 def list(self, skellist: SkelList, action: str = "list", params=None, **kwargs):
122 if not isinstance(skellist, SkelList):
123 raise ValueError("Function requires a SkelList")
125 res = {
126 "action": action,
127 "cursor": skellist.getCursor() if skellist else None,
128 "params": params,
129 "skellist": [item.dump() for item in skellist],
130 "structure":
131 DefaultRender.render_structure(skellist[0].structure())
132 if skellist and "json.bone.structure.inlists" in conf.compatibility
133 else None,
134 "orders": skellist.get_orders() if skellist else None,
135 }
137 current.request.get().response.headers["Content-Type"] = "application/json"
138 return json.dumps(res, cls=CustomJsonEncoder)
140 def add(self, skel: SkeletonInstance, action: str = "add", params=None, **kwargs):
141 return self.renderEntry(skel, action, params)
143 def edit(self, skel: SkeletonInstance, action: str = "edit", params=None, **kwargs):
144 return self.renderEntry(skel, action, params)
146 def editSuccess(self, skel: SkeletonInstance, action: str = "editSuccess", params=None, **kwargs):
147 return self.renderEntry(skel, action, params)
149 def addSuccess(self, skel: SkeletonInstance, action: str = "addSuccess", params=None, **kwargs):
150 return self.renderEntry(skel, action, params)
152 def deleteSuccess(self, skel: SkeletonInstance, params=None, *args, **kwargs):
153 return json.dumps("OKAY")
155 def listRootNodes(self, rootNodes, *args, **kwargs):
156 current.request.get().response.headers["Content-Type"] = "application/json"
157 return json.dumps(rootNodes, cls=CustomJsonEncoder)
159 def render(
160 self,
161 action: str,
162 skel: t.Optional[SkeletonInstance] = None,
163 *,
164 next_url: t.Optional[str] = None,
165 **kwargs
166 ):
167 """
168 Universal rendering function.
170 Handles an action and a skeleton. It shall be used by any action, in future.
171 """
172 return self.renderEntry(skel, action, next_url=next_url, params=kwargs)