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

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 

8 

9 

10class Singleton(SkelModule): 

11 """ 

12 Singleton module prototype. 

13 

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") 

18 

19 def getKey(self) -> str: 

20 """ 

21 Returns the DB-Key for the current context. 

22 

23 This implementation provides one module-global key. 

24 It *must* return *exactly one* key at any given time in any given context. 

25 

26 :returns: Current context DB-key 

27 """ 

28 return f"{self._resolveSkelCls().kindName}-modulekey" 

29 

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. 

34 

35 The default is a Skeleton instance returned by :func:`~baseSkel`. 

36 

37 .. seealso:: :func:`addSkel`, :func:`editSkel`, :func:`~baseSkel` 

38 

39 :return: Returns a Skeleton instance for viewing the singleton entry. 

40 """ 

41 return self.baseSkel(**kwargs) 

42 

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. 

47 

48 The default is a Skeleton instance returned by :func:`~baseSkel`. 

49 

50 .. seealso:: :func:`viewSkel`, :func:`editSkel`, :func:`~baseSkel` 

51 

52 :return: Returns a Skeleton instance for editing the entry. 

53 """ 

54 return self.baseSkel(**kwargs) 

55 

56 ## External exposed functions 

57 

58 @exposed 

59 def index(self): 

60 return self.view() 

61 

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. 

68 

69 Any entity values are provided via *kwargs*. 

70 

71 The function uses the viewTemplate of the application. 

72 

73 :returns: The rendered representation of the supplied data. 

74 """ 

75 if not self.canPreview(): 

76 raise errors.Unauthorized() 

77 

78 skel = self.viewSkel(allow_client_defined=utils.string.is_prefix(self.render.kind, "json")) 

79 skel.fromClient(kwargs) 

80 

81 return self.render.view(skel) 

82 

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. 

88 

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() 

97 

98 case "edit": 

99 skel = self.editSkel() 

100 if not self.canEdit(): 

101 raise errors.Unauthorized() 

102 

103 case _: 

104 raise errors.NotImplemented(f"The action {action!r} is not implemented.") 

105 

106 return self.render.render(f"structure.{action}", skel) 

107 

108 @exposed 

109 def view(self, *args, **kwargs) -> t.Any: 

110 """ 

111 Prepares and renders the singleton entry for viewing. 

112 

113 The function performs several access control checks on the requested entity before it is rendered. 

114 

115 .. seealso:: :func:`viewSkel`, :func:`canView`, :func:`onView` 

116 

117 :returns: The rendered representation of the entity. 

118 

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() 

124 

125 skel = self.viewSkel(allow_client_defined=utils.string.is_prefix(self.render.kind, "json")) 

126 key = db.Key(skel.kindName, self.getKey()) 

127 

128 if not skel.read(key): 

129 raise errors.NotFound() 

130 

131 self.onView(skel) 

132 return self.render.view(skel) 

133 

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. 

140 

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. 

144 

145 .. seealso:: :func:`editSkel`, :func:`onEdited`, :func:`canEdit` 

146 

147 :returns: The rendered, edited object of the entry, eventually with error hints. 

148 

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() 

154 

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 

159 

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) 

167 

168 self.onEdit(skel) 

169 skel.write() 

170 self.onEdited(skel) 

171 return self.render.editSuccess(skel) 

172 

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. 

179 

180 :param create: Whether the entity should be created if it does not exist. 

181 See :meth:`Skeleton.read` for more details. 

182 

183 :returns: The read skeleton or `None`. 

184 """ 

185 skel = self.viewSkel() 

186 key = db.Key(self.viewSkel().kindName, self.getKey()) 

187 

188 if not skel.read(key, create=create): 

189 return None 

190 

191 return skel 

192 

193 def canPreview(self) -> bool: 

194 """ 

195 Access control function for preview permission. 

196 

197 Checks if the current user has the permission to preview the singletons entry. 

198 

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. 

204 

205 It should be overridden for a module-specific behavior. 

206 

207 .. seealso:: :func:`preview` 

208 

209 :returns: True, if previewing entries is allowed, False otherwise. 

210 """ 

211 if not (user := current.user.get()): 

212 return False 

213 

214 if user["access"] and "root" in user["access"]: 

215 return True 

216 

217 if user["access"] and f"{self.moduleName}-edit" in user["access"]: 

218 return True 

219 

220 return False 

221 

222 def canEdit(self) -> bool: 

223 """ 

224 Access control function for modification permission. 

225 

226 Checks if the current user has the permission to edit the singletons entry. 

227 

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. 

232 

233 It should be overridden for a module-specific behavior. 

234 

235 .. seealso:: :func:`edit` 

236 

237 :returns: True, if editing is allowed, False otherwise. 

238 """ 

239 if not (user := current.user.get()): 

240 return False 

241 

242 if user["access"] and "root" in user["access"]: 

243 return True 

244 

245 if user["access"] and f"{self.moduleName}-edit" in user["access"]: 

246 return True 

247 

248 return False 

249 

250 def canView(self) -> bool: 

251 """ 

252 Access control function for viewing permission. 

253 

254 Checks if the current user has the permission to view the singletons entry. 

255 

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. 

260 

261 It should be overridden for a module-specific behavior. 

262 

263 .. seealso:: :func:`view` 

264 

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 

274 

275 def onEdit(self, skel: SkeletonInstance): 

276 """ 

277 Hook function that is called before editing an entry. 

278 

279 It can be overridden for a module-specific behavior. 

280 

281 :param skel: The Skeleton that is going to be edited. 

282 

283 .. seealso:: :func:`edit`, :func:`onEdited` 

284 """ 

285 pass 

286 

287 def onEdited(self, skel: SkeletonInstance): 

288 """ 

289 Hook function that is called after modifying the entry. 

290 

291 It should be overridden for a module-specific behavior. 

292 The default is writing a log entry. 

293 

294 :param skel: The Skeleton that has been modified. 

295 

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})""") 

302 

303 def onView(self, skel: SkeletonInstance): 

304 """ 

305 Hook function that is called when viewing an entry. 

306 

307 It should be overridden for a module-specific behavior. 

308 The default is doing nothing. 

309 

310 :param skel: The Skeleton that is being viewed. 

311 

312 .. seealso:: :func:`view` 

313 """ 

314 pass 

315 

316 

317Singleton.admin = True 

318Singleton.vi = True