Coverage for /home/runner/work/viur-core/viur-core/viur/src/viur/core/prototypes/singleton.py: 0%
108 statements
« prev ^ index » next coverage.py v7.9.1, created at 2025-06-26 11:31 +0000
« prev ^ index » next coverage.py v7.9.1, created at 2025-06-26 11:31 +0000
1import logging
2import typing as t
3from viur.core import db, current, utils, errors
4from viur.core.decorators import *
5from viur.core.cache import flushCache
6from viur.core.skeleton import SkeletonInstance
7from .skelmodule import SkelModule
10class Singleton(SkelModule):
11 """
12 Singleton module prototype.
14 It is used to store one single data entity, and needs to be sub-classed for individual modules.
15 """
16 handler = "singleton"
17 accessRights = ("edit", "view", "manage")
19 def getKey(self) -> str:
20 """
21 Returns the DB-Key for the current context.
23 This implementation provides one module-global key.
24 It *must* return *exactly one* key at any given time in any given context.
26 :returns: Current context DB-key
27 """
28 return f"{self._resolveSkelCls().kindName}-modulekey"
30 def viewSkel(self, *args, **kwargs) -> SkeletonInstance:
31 """
32 Retrieve a new instance of a :class:`viur.core.skeleton.Skeleton` that is used by the application
33 for viewing the existing entry.
35 The default is a Skeleton instance returned by :func:`~baseSkel`.
37 .. seealso:: :func:`addSkel`, :func:`editSkel`, :func:`~baseSkel`
39 :return: Returns a Skeleton instance for viewing the singleton entry.
40 """
41 return self.baseSkel(**kwargs)
43 def editSkel(self, *args, **kwargs) -> SkeletonInstance:
44 """
45 Retrieve a new instance of a :class:`viur.core.skeleton.Skeleton` that is used by the application
46 for editing the existing entry.
48 The default is a Skeleton instance returned by :func:`~baseSkel`.
50 .. seealso:: :func:`viewSkel`, :func:`editSkel`, :func:`~baseSkel`
52 :return: Returns a Skeleton instance for editing the entry.
53 """
54 return self.baseSkel(**kwargs)
56 ## External exposed functions
58 @exposed
59 def index(self):
60 return self.view()
62 @exposed
63 @skey
64 def preview(self, *args, **kwargs) -> t.Any:
65 """
66 Renders data for the entry, without reading it from the database.
67 This function allows to preview the entry without writing it to the database.
69 Any entity values are provided via *kwargs*.
71 The function uses the viewTemplate of the application.
73 :returns: The rendered representation of the supplied data.
74 """
75 if not self.canPreview():
76 raise errors.Unauthorized()
78 skel = self.viewSkel(allow_client_defined=utils.string.is_prefix(self.render.kind, "json"))
79 skel.fromClient(kwargs)
81 return self.render.view(skel)
83 @exposed
84 def structure(self, action: t.Optional[str] = "view") -> t.Any:
85 """
86 :returns: Returns the structure of our skeleton as used in list/view. Values are the defaultValues set
87 in each bone.
89 :raises: :exc:`viur.core.errors.Unauthorized`, if the current user does not have the required permissions.
90 """
91 # FIXME: In ViUR > 3.7 this could also become dynamic (ActionSkel paradigm).
92 match action:
93 case "view":
94 skel = self.viewSkel()
95 if not self.canView():
96 raise errors.Unauthorized()
98 case "edit":
99 skel = self.editSkel()
100 if not self.canEdit():
101 raise errors.Unauthorized()
103 case _:
104 raise errors.NotImplemented(f"The action {action!r} is not implemented.")
106 return self.render.render(f"structure.{action}", skel)
108 @exposed
109 def view(self, *args, **kwargs) -> t.Any:
110 """
111 Prepares and renders the singleton entry for viewing.
113 The function performs several access control checks on the requested entity before it is rendered.
115 .. seealso:: :func:`viewSkel`, :func:`canView`, :func:`onView`
117 :returns: The rendered representation of the entity.
119 :raises: :exc:`viur.core.errors.NotFound`, if there is no singleton entry existing, yet.
120 :raises: :exc:`viur.core.errors.Unauthorized`, if the current user does not have the required permissions.
121 """
122 if not self.canView():
123 raise errors.Unauthorized()
125 skel = self.viewSkel(allow_client_defined=utils.string.is_prefix(self.render.kind, "json"))
126 key = db.Key(skel.kindName, self.getKey())
128 if not skel.read(key):
129 raise errors.NotFound()
131 self.onView(skel)
132 return self.render.view(skel)
134 @exposed
135 @force_ssl
136 @skey(allow_empty=True)
137 def edit(self, *args, **kwargs) -> t.Any:
138 """
139 Modify the existing entry, and render the entry, eventually with error notes on incorrect data.
141 The entry is fetched by its entity key, which either is provided via *kwargs["key"]*,
142 or as the first parameter in *args*. The function performs several access control checks
143 on the singleton's entity before it is modified.
145 .. seealso:: :func:`editSkel`, :func:`onEdited`, :func:`canEdit`
147 :returns: The rendered, edited object of the entry, eventually with error hints.
149 :raises: :exc:`viur.core.errors.Unauthorized`, if the current user does not have the required permissions.
150 :raises: :exc:`viur.core.errors.PreconditionFailed`, if the *skey* could not be verified.
151 """
152 if not self.canEdit():
153 raise errors.Unauthorized()
155 skel = self.editSkel()
156 key = db.Key(skel.kindName, self.getKey())
157 if not skel.read(key): # Its not there yet; we need to set the key again
158 skel["key"] = key
160 if (
161 not kwargs # no data supplied
162 or not current.request.get().isPostRequest # failure if not using POST-method
163 or not skel.fromClient(kwargs, amend=True) # failure on reading into the bones
164 or utils.parse.bool(kwargs.get("bounce")) # review before changing
165 ):
166 return self.render.edit(skel)
168 self.onEdit(skel)
169 skel.write()
170 self.onEdited(skel)
171 return self.render.editSuccess(skel)
173 def getContents(
174 self,
175 create: bool | dict | t.Callable[[SkeletonInstance], None] = False,
176 ) -> SkeletonInstance | None:
177 """
178 Return the entity of this singleton application as :class:`SkeletonInstance` object.
180 :param create: Whether the entity should be created if it does not exist.
181 See :meth:`Skeleton.read` for more details.
183 :returns: The read skeleton or `None`.
184 """
185 skel = self.viewSkel()
186 key = db.Key(self.viewSkel().kindName, self.getKey())
188 if not skel.read(key, create=create):
189 return None
191 return skel
193 def canPreview(self) -> bool:
194 """
195 Access control function for preview permission.
197 Checks if the current user has the permission to preview the singletons entry.
199 The default behavior is:
200 - If no user is logged in, previewing is generally refused.
201 - If the user has "root" access, previewing is generally allowed.
202 - If the user has the modules "edit" permission (module-edit) enabled, \
203 previewing is allowed.
205 It should be overridden for a module-specific behavior.
207 .. seealso:: :func:`preview`
209 :returns: True, if previewing entries is allowed, False otherwise.
210 """
211 if not (user := current.user.get()):
212 return False
214 if user["access"] and "root" in user["access"]:
215 return True
217 if user["access"] and f"{self.moduleName}-edit" in user["access"]:
218 return True
220 return False
222 def canEdit(self) -> bool:
223 """
224 Access control function for modification permission.
226 Checks if the current user has the permission to edit the singletons entry.
228 The default behavior is:
229 - If no user is logged in, editing is generally refused.
230 - If the user has "root" access, editing is generally allowed.
231 - If the user has the modules "edit" permission (module-edit) enabled, editing is allowed.
233 It should be overridden for a module-specific behavior.
235 .. seealso:: :func:`edit`
237 :returns: True, if editing is allowed, False otherwise.
238 """
239 if not (user := current.user.get()):
240 return False
242 if user["access"] and "root" in user["access"]:
243 return True
245 if user["access"] and f"{self.moduleName}-edit" in user["access"]:
246 return True
248 return False
250 def canView(self) -> bool:
251 """
252 Access control function for viewing permission.
254 Checks if the current user has the permission to view the singletons entry.
256 The default behavior is:
257 - If no user is logged in, viewing is generally refused.
258 - If the user has "root" access, viewing is generally allowed.
259 - If the user has the modules "view" permission (module-view) enabled, viewing is allowed.
261 It should be overridden for a module-specific behavior.
263 .. seealso:: :func:`view`
265 :returns: True, if viewing is allowed, False otherwise.
266 """
267 if not (user := current.user.get()):
268 return False
269 if user["access"] and "root" in user["access"]:
270 return True
271 if user["access"] and f"{self.moduleName}-view" in user["access"]:
272 return True
273 return False
275 def onEdit(self, skel: SkeletonInstance):
276 """
277 Hook function that is called before editing an entry.
279 It can be overridden for a module-specific behavior.
281 :param skel: The Skeleton that is going to be edited.
283 .. seealso:: :func:`edit`, :func:`onEdited`
284 """
285 pass
287 def onEdited(self, skel: SkeletonInstance):
288 """
289 Hook function that is called after modifying the entry.
291 It should be overridden for a module-specific behavior.
292 The default is writing a log entry.
294 :param skel: The Skeleton that has been modified.
296 .. seealso:: :func:`edit`, :func:`onEdit`
297 """
298 logging.info(f"""Entry changed: {skel["key"]!r}""")
299 flushCache(key=skel["key"])
300 if user := current.user.get():
301 logging.info(f"""User: {user["name"]!r} ({user["key"]!r})""")
303 def onView(self, skel: SkeletonInstance):
304 """
305 Hook function that is called when viewing an entry.
307 It should be overridden for a module-specific behavior.
308 The default is doing nothing.
310 :param skel: The Skeleton that is being viewed.
312 .. seealso:: :func:`view`
313 """
314 pass
317Singleton.admin = True
318Singleton.vi = True