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
« prev ^ index » next coverage.py v7.13.1, created at 2026-03-03 09:17 +0000
1from __future__ import annotations
3from abc import ABC, abstractmethod
4from dataclasses import asdict, dataclass
5from typing import TYPE_CHECKING, ClassVar
7from django.db.models import QuerySet
8from django.utils.functional import cached_property
9from django.utils.translation import gettext
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
16if TYPE_CHECKING:
17 from ..submission_status import SubmissionStatusData
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 """
26 user: User
28 def __init__(self, user: User) -> None:
29 self.user = user
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
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
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
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()
83 def can_create_submission(self) -> bool:
84 """
85 Wether the user role has rights to create a new submission.
86 """
87 return False
89 def can_access_submission(self, submission: Submission) -> bool:
90 """
91 Wether the user role can access the given submission.
92 """
93 return False
95 def can_edit_submission(self, submission: Submission) -> bool:
96 """
97 Whether the user role can edit the given submission.
98 """
99 return True
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
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
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
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
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
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
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
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
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
157 def can_edit_review(self, review: Review) -> bool:
158 """
159 Whether the user role can edit the given review.
160 """
161 return False
163 def can_submit_review(self, review: Review) -> bool:
164 """
165 Whether the user role can submit the given review.
166 """
167 return False
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
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
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
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
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
199 def can_impersonate(self) -> bool:
200 """
201 Whether the user role can impersonate other users.
202 """
203 return False
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
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
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
223 def can_access_journal_sections(self) -> bool:
224 """
225 Whether the user can access the submission journal_sections views.
226 """
227 return False
229 def can_edit_journal_sections(self) -> bool:
230 """
231 Whether the user can edit the submission journal_sections.
232 """
233 return False
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
241 def can_access_last_activity(self) -> bool:
242 """
243 Whether the user role can view the last activity.
244 """
245 return True
247 def can_access_shortcut_actions(self) -> bool:
248 return False
251@dataclass
252class RoleSummary:
253 code: str
254 name: str
255 icon_class: str
256 submission_list_title: str
258 def serialize(self) -> dict:
259 return asdict(self)
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 """
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
286 def __init__(self, user: User) -> None:
287 self.user = user
288 self.rights = self.get_rights()
290 @property
291 @abstractmethod
292 def active(self) -> bool:
293 """
294 Wether the role is active for the user.
295 """
296 return False
298 @classmethod
299 def code(cls) -> str:
300 """
301 Returns the role's code.
302 """
303 return cls._CODE
305 @classmethod
306 def name(cls) -> str:
307 """
308 Returns the role's display name.
309 """
310 return cls._NAME
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
319 @classmethod
320 def submissions_list_title(cls) -> str:
321 return cls._SUBMISSION_LIST_TITLE
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 )
332 @abstractmethod
333 def get_rights(self) -> RoleRights:
334 """
335 Returns the rights object associated to the role.
336 """
337 pass
339 def accept(self, visitor, submission, *args, **kwargs):
340 return visitor.visit(self, submission, *args, **kwargs)