Coverage for src / mesh / model / roles / base_role.py: 78%

122 statements  

« prev     ^ index     » next       coverage.py v7.13.1, created at 2026-03-03 09:17 +0000

1from __future__ import annotations 

2 

3from abc import ABC, abstractmethod 

4from dataclasses import asdict, dataclass 

5from typing import TYPE_CHECKING, ClassVar 

6 

7from django.db.models import QuerySet 

8from django.utils.functional import cached_property 

9from django.utils.translation import gettext 

10 

11from mesh.models.editorial_models import EditorialDecision 

12from mesh.models.review_models import Review, ReviewAdditionalFile 

13from mesh.models.submission_models import Submission, SubmissionVersion 

14from mesh.models.user_models import User 

15 

16if TYPE_CHECKING: 

17 from ..submission_status import SubmissionStatusData 

18 

19 

20class RoleRights(ABC): 

21 """ 

22 Base interface for rights management. 

23 Contains the actual data used to figure out the rights over a given object/entity. 

24 """ 

25 

26 user: User 

27 

28 def __init__(self, user: User) -> None: 

29 self.user = user 

30 

31 @cached_property 

32 @abstractmethod 

33 def submissions(self) -> QuerySet[Submission]: 

34 """ 

35 Returns the queryset of submissions the user has access to. 

36 """ 

37 pass 

38 

39 def get_current_open_review(self, submission: Submission) -> Review | None: 

40 """ 

41 Returns the current open review for the given submission, if any. 

42 Current review = Round not closed + review not submitted. 

43 """ 

44 return None 

45 

46 @abstractmethod 

47 def get_submission_status(self, submission: Submission) -> SubmissionStatusData: 

48 """ 

49 Returns the submission status according to the user role + an optional string 

50 describing the submission status. 

51 Ex: (WAITING, "X reports missing") for an editor+ 

52 (WAITING, "Under review") for the author 

53 (TODO, "Reports due for {{date}}") for a reviewer 

54 """ 

55 pass 

56 

57 # def get_submission_list_config(self) -> list[SubmissionListConfig]: 

58 # """ 

59 # Returns the config to display the submissions for the user role. 

60 # """ 

61 # return [ 

62 # SubmissionListConfig( 

63 # key=SubmissionStatus.TODO, title=_("Requires action"), html_classes="todo" 

64 # ), 

65 # SubmissionListConfig( 

66 # key=SubmissionStatus.WAITING, 

67 # title=_("Waiting for other's input"), 

68 # html_classes="waiting", 

69 # ), 

70 # SubmissionListConfig( 

71 # key=SubmissionStatus.ARCHIVED, 

72 # title=_("Closed / Archived"), 

73 # html_classes="archived", 

74 # ), 

75 # ] 

76 # 

77 # def get_archived_submission_list_config(self) -> list[SubmissionListConfig]: 

78 # """ 

79 # Returns the config to display only the Archived submissions. 

80 # """ 

81 # return self.get_archived_submission_list_config() 

82 

83 def can_create_submission(self) -> bool: 

84 """ 

85 Wether the user role has rights to create a new submission. 

86 """ 

87 return False 

88 

89 def can_access_submission(self, submission: Submission) -> bool: 

90 """ 

91 Wether the user role can access the given submission. 

92 """ 

93 return False 

94 

95 def can_edit_submission(self, submission: Submission) -> bool: 

96 """ 

97 Whether the user role can edit the given submission. 

98 """ 

99 return True 

100 

101 def can_submit_submission(self, submission: Submission) -> bool: 

102 """ 

103 Whether the user role can submit the given submission. 

104 It doesn't check whether the submission has the required fields to be submitted. 

105 """ 

106 return self.can_edit_submission(submission) and submission.is_draft 

107 

108 def can_create_version(self, submission: Submission) -> bool: 

109 """ 

110 Whether the user role can submit a new version for the given submission 

111 """ 

112 return False 

113 

114 def can_edit_version(self, version: SubmissionVersion) -> bool: 

115 """ 

116 Whether the user role can edit the given submission version. 

117 """ 

118 return False 

119 

120 def can_access_version(self, version: SubmissionVersion) -> bool: 

121 """ 

122 Whether the user role can view the data of the given submission version. 

123 """ 

124 return False 

125 

126 def can_start_review_process(self, submission: Submission) -> bool: 

127 """ 

128 Whether the user role can send the submission into the review process. 

129 """ 

130 return False 

131 

132 def can_create_editorial_decision(self, submission: Submission) -> bool: 

133 """ 

134 Whether the user role can create an editorial decision for the given submission. 

135 """ 

136 return False 

137 

138 def can_edit_editorial_decision(self, decision: EditorialDecision) -> bool: 

139 """ 

140 Whether the user role can edit the given editorial decision. 

141 """ 

142 return False 

143 

144 def can_access_reviews(self, version: SubmissionVersion) -> bool: 

145 """ 

146 Whether the user role can view the reviews section of the given submission 

147 version. 

148 """ 

149 return False 

150 

151 def can_access_review(self, review: Review) -> bool: 

152 """ 

153 Whether the user role can view the data of the given review. 

154 """ 

155 return False 

156 

157 def can_edit_review(self, review: Review) -> bool: 

158 """ 

159 Whether the user role can edit the given review. 

160 """ 

161 return False 

162 

163 def can_submit_review(self, review: Review) -> bool: 

164 """ 

165 Whether the user role can submit the given review. 

166 """ 

167 return False 

168 

169 def can_access_review_author(self, review: Review) -> bool: 

170 """ 

171 Whether the user role can view the review's author name 

172 """ 

173 return False 

174 

175 def can_access_review_file(self, file: ReviewAdditionalFile) -> bool: 

176 """ 

177 Whether the user role can access the given review's file. 

178 """ 

179 return False 

180 

181 def can_access_review_details(self, review: Review) -> bool: 

182 """ 

183 Whether the user role can access the review details. 

184 """ 

185 return False 

186 

187 def can_invite_reviewer(self, version: SubmissionVersion) -> bool: 

188 """ 

189 Whether the user role can invite reviewer for the given submission version. 

190 """ 

191 return False 

192 

193 def can_access_submission_author(self, submission: Submission) -> bool: 

194 """ 

195 Wether the user role can access the author name of the given submission. 

196 """ 

197 return False 

198 

199 def can_impersonate(self) -> bool: 

200 """ 

201 Whether the user role can impersonate other users. 

202 """ 

203 return False 

204 

205 def can_access_submission_log(self, submission: Submission) -> bool: 

206 """ 

207 Whether the user role can view the submission log. 

208 """ 

209 return False 

210 

211 def can_assign_editor(self, submission: Submission) -> bool: 

212 """ 

213 Whether the user role can assign an editor to the given submission. 

214 """ 

215 return False 

216 

217 def can_filter_submissions(self) -> bool: 

218 """ 

219 Whether the user role can use filters on the submission list dashboard. 

220 """ 

221 return False 

222 

223 def can_access_journal_sections(self) -> bool: 

224 """ 

225 Whether the user can access the submission journal_sections views. 

226 """ 

227 return False 

228 

229 def can_edit_journal_sections(self) -> bool: 

230 """ 

231 Whether the user can edit the submission journal_sections. 

232 """ 

233 return False 

234 

235 def can_edit_review_file_right(self, review: Review) -> bool: 

236 """ 

237 Whether the user can edit the review file access right. 

238 """ 

239 return False 

240 

241 def can_access_last_activity(self) -> bool: 

242 """ 

243 Whether the user role can view the last activity. 

244 """ 

245 return True 

246 

247 def can_access_shortcut_actions(self) -> bool: 

248 return False 

249 

250 

251@dataclass 

252class RoleSummary: 

253 code: str 

254 name: str 

255 icon_class: str 

256 submission_list_title: str 

257 

258 def serialize(self) -> dict: 

259 return asdict(self) 

260 

261 

262class Role(ABC): 

263 """ 

264 Base interface for a role object. 

265 TODO: Is the split Role & RoleRights logically relevant ? Wouldn't it be simpler 

266 to merge them in a single object ? 

267 -----> Yes it is. It's the basis that will enable doing something like OJS with 

268 both role and permission level: any number of app-dependent role can be created but 

269 the available permission levels are the implemented RoleRights. 

270 https://docs.pkp.sfu.ca/learning-ojs/en/users-and-roles#permissions-and-roles 

271 This will require quite some work to enable freely creating role with our 

272 role handling system. 

273 """ 

274 

275 # Role code - Stored in user table keep track of the user current role. 

276 _CODE: ClassVar[str] 

277 # Role name 

278 _NAME: ClassVar[str] 

279 # Font-awesome 6 icon class (ex: "fa-user") used to represent the role. 

280 _ICON_CLASS: ClassVar[str] 

281 # Title for the "mesh:submission_list" view 

282 _SUBMISSION_LIST_TITLE: ClassVar[str] = gettext("My submissions") 

283 user: User 

284 rights: RoleRights 

285 

286 def __init__(self, user: User) -> None: 

287 self.user = user 

288 self.rights = self.get_rights() 

289 

290 @property 

291 @abstractmethod 

292 def active(self) -> bool: 

293 """ 

294 Wether the role is active for the user. 

295 """ 

296 return False 

297 

298 @classmethod 

299 def code(cls) -> str: 

300 """ 

301 Returns the role's code. 

302 """ 

303 return cls._CODE 

304 

305 @classmethod 

306 def name(cls) -> str: 

307 """ 

308 Returns the role's display name. 

309 """ 

310 return cls._NAME 

311 

312 @classmethod 

313 def icon_class(cls) -> str: 

314 """ 

315 Returns the role's icon HTML tag (it uses font awesome 6). 

316 """ 

317 return cls._ICON_CLASS 

318 

319 @classmethod 

320 def submissions_list_title(cls) -> str: 

321 return cls._SUBMISSION_LIST_TITLE 

322 

323 @classmethod 

324 def summary(cls) -> RoleSummary: 

325 return RoleSummary( 

326 code=cls.code(), 

327 name=cls.name(), 

328 icon_class=cls.icon_class(), 

329 submission_list_title=cls.submissions_list_title(), 

330 ) 

331 

332 @abstractmethod 

333 def get_rights(self) -> RoleRights: 

334 """ 

335 Returns the rights object associated to the role. 

336 """ 

337 pass 

338 

339 def accept(self, visitor, submission, *args, **kwargs): 

340 return visitor.visit(self, submission, *args, **kwargs)