Coverage for /home/runner/work/viur-core/viur-core/viur/src/viur/core/prototypes/singleton.py: 0%

97 statements  

« prev     ^ index     » next       coverage.py v7.6.3, created at 2024-10-16 22:16 +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 @skey 

60 def preview(self, *args, **kwargs) -> t.Any: 

61 """ 

62 Renders data for the entry, without reading it from the database. 

63 This function allows to preview the entry without writing it to the database. 

64 

65 Any entity values are provided via *kwargs*. 

66 

67 The function uses the viewTemplate of the application. 

68 

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

70 """ 

71 if not self.canPreview(): 

72 raise errors.Unauthorized() 

73 

74 skel = self.viewSkel() 

75 skel.fromClient(kwargs) 

76 

77 return self.render.view(skel) 

78 

79 @exposed 

80 def structure(self, *args, **kwargs) -> t.Any: 

81 """ 

82 :returns: Returns the structure of our skeleton as used in list/view. Values are the defaultValues set 

83 in each bone. 

84 

85 :raises: :exc:`viur.core.errors.Unauthorized`, if the current user does not have the required permissions. 

86 """ 

87 skel = self.viewSkel() 

88 if not self.canView(): 

89 raise errors.Unauthorized() 

90 return self.render.view(skel) 

91 

92 @exposed 

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

94 """ 

95 Prepares and renders the singleton entry for viewing. 

96 

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

98 

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

100 

101 :returns: The rendered representation of the entity. 

102 

103 :raises: :exc:`viur.core.errors.NotFound`, if there is no singleton entry existing, yet. 

104 :raises: :exc:`viur.core.errors.Unauthorized`, if the current user does not have the required permissions. 

105 """ 

106 

107 skel = self.viewSkel() 

108 if not self.canView(): 

109 raise errors.Unauthorized() 

110 

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

112 

113 if not skel.fromDB(key): 

114 raise errors.NotFound() 

115 

116 self.onView(skel) 

117 return self.render.view(skel) 

118 

119 @exposed 

120 @force_ssl 

121 @skey(allow_empty=True) 

122 def edit(self, *args, **kwargs) -> t.Any: 

123 """ 

124 Modify the existing entry, and render the entry, eventually with error notes on incorrect data. 

125 

126 The entry is fetched by its entity key, which either is provided via *kwargs["key"]*, 

127 or as the first parameter in *args*. The function performs several access control checks 

128 on the singleton's entity before it is modified. 

129 

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

131 

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

133 

134 :raises: :exc:`viur.core.errors.Unauthorized`, if the current user does not have the required permissions. 

135 :raises: :exc:`viur.core.errors.PreconditionFailed`, if the *skey* could not be verified. 

136 """ 

137 if not self.canEdit(): 

138 raise errors.Unauthorized() 

139 

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

141 skel = self.editSkel() 

142 if not skel.fromDB(key): # Its not there yet; we need to set the key again 

143 skel["key"] = key 

144 

145 if ( 

146 not kwargs # no data supplied 

147 or not current.request.get().isPostRequest # failure if not using POST-method 

148 or not skel.fromClient(kwargs, amend=True) # failure on reading into the bones 

149 or utils.parse.bool(kwargs.get("bounce")) # review before changing 

150 ): 

151 return self.render.edit(skel) 

152 

153 self.onEdit(skel) 

154 skel.toDB() 

155 self.onEdited(skel) 

156 return self.render.editSuccess(skel) 

157 

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

159 """ 

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

161 

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

163 """ 

164 skel = self.viewSkel() 

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

166 

167 if not skel.fromDB(key): 

168 return None 

169 

170 return skel 

171 

172 def canPreview(self) -> bool: 

173 """ 

174 Access control function for preview permission. 

175 

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

177 

178 The default behavior is: 

179 - If no user is logged in, previewing is generally refused. 

180 - If the user has "root" access, previewing is generally allowed. 

181 - If the user has the modules "edit" permission (module-edit) enabled, \ 

182 previewing is allowed. 

183 

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

185 

186 .. seealso:: :func:`preview` 

187 

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

189 """ 

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

191 return False 

192 

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

194 return True 

195 

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

197 return True 

198 

199 return False 

200 

201 def canEdit(self) -> bool: 

202 """ 

203 Access control function for modification permission. 

204 

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

206 

207 The default behavior is: 

208 - If no user is logged in, editing is generally refused. 

209 - If the user has "root" access, editing is generally allowed. 

210 - If the user has the modules "edit" permission (module-edit) enabled, editing is allowed. 

211 

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

213 

214 .. seealso:: :func:`edit` 

215 

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

217 """ 

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

219 return False 

220 

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

222 return True 

223 

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

225 return True 

226 

227 return False 

228 

229 def canView(self) -> bool: 

230 """ 

231 Access control function for viewing permission. 

232 

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

234 

235 The default behavior is: 

236 - If no user is logged in, viewing is generally refused. 

237 - If the user has "root" access, viewing is generally allowed. 

238 - If the user has the modules "view" permission (module-view) enabled, viewing is allowed. 

239 

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

241 

242 .. seealso:: :func:`view` 

243 

244 :returns: True, if viewing is allowed, False otherwise. 

245 """ 

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

247 return False 

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

249 return True 

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

251 return True 

252 return False 

253 

254 def onEdit(self, skel: SkeletonInstance): 

255 """ 

256 Hook function that is called before editing an entry. 

257 

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

259 

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

261 

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

263 """ 

264 pass 

265 

266 def onEdited(self, skel: SkeletonInstance): 

267 """ 

268 Hook function that is called after modifying the entry. 

269 

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

271 The default is writing a log entry. 

272 

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

274 

275 .. seealso:: :func:`edit`, :func:`onEdit` 

276 """ 

277 logging.info(f"""Entry changed: {skel["key"]!r}""") 

278 flushCache(key=skel["key"]) 

279 if user := current.user.get(): 

280 logging.info(f"""User: {user["name"]!r} ({user["key"]!r})""") 

281 

282 def onView(self, skel: SkeletonInstance): 

283 """ 

284 Hook function that is called when viewing an entry. 

285 

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

287 The default is doing nothing. 

288 

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

290 

291 .. seealso:: :func:`view` 

292 """ 

293 pass 

294 

295 

296Singleton.admin = True 

297Singleton.vi = True