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

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.editSkel().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(*args, **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(*args, **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() 

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 

123 skel = self.viewSkel() 

124 if not self.canView(): 

125 raise errors.Unauthorized() 

126 

127 key = db.Key(self.editSkel().kindName, self.getKey()) 

128 

129 if not skel.read(key): 

130 raise errors.NotFound() 

131 

132 self.onView(skel) 

133 return self.render.view(skel) 

134 

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. 

141 

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. 

145 

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

147 

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

149 

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

155 

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 

160 

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) 

168 

169 self.onEdit(skel) 

170 skel.write() 

171 self.onEdited(skel) 

172 return self.render.editSuccess(skel) 

173 

174 def getContents(self) -> SkeletonInstance | None: 

175 """ 

176 Returns the entity of this singleton application as :class:`viur.core.skeleton.Skeleton` object. 

177 

178 :returns: The content as Skeleton provided by :func:`viewSkel`. 

179 """ 

180 skel = self.viewSkel() 

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

182 

183 if not skel.read(key): 

184 return None 

185 

186 return skel 

187 

188 def canPreview(self) -> bool: 

189 """ 

190 Access control function for preview permission. 

191 

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

193 

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. 

199 

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

201 

202 .. seealso:: :func:`preview` 

203 

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

205 """ 

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

207 return False 

208 

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

210 return True 

211 

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

213 return True 

214 

215 return False 

216 

217 def canEdit(self) -> bool: 

218 """ 

219 Access control function for modification permission. 

220 

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

222 

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. 

227 

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

229 

230 .. seealso:: :func:`edit` 

231 

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

233 """ 

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

235 return False 

236 

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

238 return True 

239 

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

241 return True 

242 

243 return False 

244 

245 def canView(self) -> bool: 

246 """ 

247 Access control function for viewing permission. 

248 

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

250 

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. 

255 

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

257 

258 .. seealso:: :func:`view` 

259 

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 

269 

270 def onEdit(self, skel: SkeletonInstance): 

271 """ 

272 Hook function that is called before editing an entry. 

273 

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

275 

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

277 

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

279 """ 

280 pass 

281 

282 def onEdited(self, skel: SkeletonInstance): 

283 """ 

284 Hook function that is called after modifying the entry. 

285 

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

287 The default is writing a log entry. 

288 

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

290 

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

297 

298 def onView(self, skel: SkeletonInstance): 

299 """ 

300 Hook function that is called when viewing an entry. 

301 

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

303 The default is doing nothing. 

304 

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

306 

307 .. seealso:: :func:`view` 

308 """ 

309 pass 

310 

311 

312Singleton.admin = True 

313Singleton.vi = True