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.6.12, created at 2025-02-27 07:59 +0000
« prev ^ index » next coverage.py v7.6.12, created at 2025-02-27 07:59 +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.editSkel().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(*args, **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(*args, **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()
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 """
123 skel = self.viewSkel()
124 if not self.canView():
125 raise errors.Unauthorized()
127 key = db.Key(self.editSkel().kindName, self.getKey())
129 if not skel.read(key):
130 raise errors.NotFound()
132 self.onView(skel)
133 return self.render.view(skel)
135 @exposed
136 @force_ssl
137 @skey(allow_empty=True)
138 def edit(self, *args, **kwargs) -> t.Any:
139 """
140 Modify the existing entry, and render the entry, eventually with error notes on incorrect data.
142 The entry is fetched by its entity key, which either is provided via *kwargs["key"]*,
143 or as the first parameter in *args*. The function performs several access control checks
144 on the singleton's entity before it is modified.
146 .. seealso:: :func:`editSkel`, :func:`onEdited`, :func:`canEdit`
148 :returns: The rendered, edited object of the entry, eventually with error hints.
150 :raises: :exc:`viur.core.errors.Unauthorized`, if the current user does not have the required permissions.
151 :raises: :exc:`viur.core.errors.PreconditionFailed`, if the *skey* could not be verified.
152 """
153 if not self.canEdit():
154 raise errors.Unauthorized()
156 key = db.Key(self.editSkel().kindName, self.getKey())
157 skel = self.editSkel()
158 if not skel.read(key): # Its not there yet; we need to set the key again
159 skel["key"] = key
161 if (
162 not kwargs # no data supplied
163 or not current.request.get().isPostRequest # failure if not using POST-method
164 or not skel.fromClient(kwargs, amend=True) # failure on reading into the bones
165 or utils.parse.bool(kwargs.get("bounce")) # review before changing
166 ):
167 return self.render.edit(skel)
169 self.onEdit(skel)
170 skel.write()
171 self.onEdited(skel)
172 return self.render.editSuccess(skel)
174 def getContents(self) -> SkeletonInstance | None:
175 """
176 Returns the entity of this singleton application as :class:`viur.core.skeleton.Skeleton` object.
178 :returns: The content as Skeleton provided by :func:`viewSkel`.
179 """
180 skel = self.viewSkel()
181 key = db.Key(self.viewSkel().kindName, self.getKey())
183 if not skel.read(key):
184 return None
186 return skel
188 def canPreview(self) -> bool:
189 """
190 Access control function for preview permission.
192 Checks if the current user has the permission to preview the singletons entry.
194 The default behavior is:
195 - If no user is logged in, previewing is generally refused.
196 - If the user has "root" access, previewing is generally allowed.
197 - If the user has the modules "edit" permission (module-edit) enabled, \
198 previewing is allowed.
200 It should be overridden for a module-specific behavior.
202 .. seealso:: :func:`preview`
204 :returns: True, if previewing entries is allowed, False otherwise.
205 """
206 if not (user := current.user.get()):
207 return False
209 if user["access"] and "root" in user["access"]:
210 return True
212 if user["access"] and f"{self.moduleName}-edit" in user["access"]:
213 return True
215 return False
217 def canEdit(self) -> bool:
218 """
219 Access control function for modification permission.
221 Checks if the current user has the permission to edit the singletons entry.
223 The default behavior is:
224 - If no user is logged in, editing is generally refused.
225 - If the user has "root" access, editing is generally allowed.
226 - If the user has the modules "edit" permission (module-edit) enabled, editing is allowed.
228 It should be overridden for a module-specific behavior.
230 .. seealso:: :func:`edit`
232 :returns: True, if editing is allowed, False otherwise.
233 """
234 if not (user := current.user.get()):
235 return False
237 if user["access"] and "root" in user["access"]:
238 return True
240 if user["access"] and f"{self.moduleName}-edit" in user["access"]:
241 return True
243 return False
245 def canView(self) -> bool:
246 """
247 Access control function for viewing permission.
249 Checks if the current user has the permission to view the singletons entry.
251 The default behavior is:
252 - If no user is logged in, viewing is generally refused.
253 - If the user has "root" access, viewing is generally allowed.
254 - If the user has the modules "view" permission (module-view) enabled, viewing is allowed.
256 It should be overridden for a module-specific behavior.
258 .. seealso:: :func:`view`
260 :returns: True, if viewing is allowed, False otherwise.
261 """
262 if not (user := current.user.get()):
263 return False
264 if user["access"] and "root" in user["access"]:
265 return True
266 if user["access"] and f"{self.moduleName}-view" in user["access"]:
267 return True
268 return False
270 def onEdit(self, skel: SkeletonInstance):
271 """
272 Hook function that is called before editing an entry.
274 It can be overridden for a module-specific behavior.
276 :param skel: The Skeleton that is going to be edited.
278 .. seealso:: :func:`edit`, :func:`onEdited`
279 """
280 pass
282 def onEdited(self, skel: SkeletonInstance):
283 """
284 Hook function that is called after modifying the entry.
286 It should be overridden for a module-specific behavior.
287 The default is writing a log entry.
289 :param skel: The Skeleton that has been modified.
291 .. seealso:: :func:`edit`, :func:`onEdit`
292 """
293 logging.info(f"""Entry changed: {skel["key"]!r}""")
294 flushCache(key=skel["key"])
295 if user := current.user.get():
296 logging.info(f"""User: {user["name"]!r} ({user["key"]!r})""")
298 def onView(self, skel: SkeletonInstance):
299 """
300 Hook function that is called when viewing an entry.
302 It should be overridden for a module-specific behavior.
303 The default is doing nothing.
305 :param skel: The Skeleton that is being viewed.
307 .. seealso:: :func:`view`
308 """
309 pass
312Singleton.admin = True
313Singleton.vi = True