Coverage for src / mesh / model / roles / author.py: 84%
76 statements
« prev ^ index » next coverage.py v7.13.1, created at 2026-03-10 09:11 +0000
« prev ^ index » next coverage.py v7.13.1, created at 2026-03-10 09:11 +0000
1from typing import ClassVar
3from django.db.models import QuerySet
5# from django.urls import reverse_lazy
6from django.utils.functional import cached_property
7from django.utils.translation import gettext as _
9from mesh.app_settings import BlindMode, app_settings
10from mesh.models.review_models import Review, ReviewAdditionalFile
11from mesh.models.submission_models import Submission, SubmissionState, SubmissionVersion
12from mesh.models.user_models import User
14from ..submission_status import SubmissionStatus, SubmissionStatusData
16# from ..views.components.button import Button
17from .base_role import Role, RoleRights
20class AuthorRights(RoleRights):
21 """
22 Rights implementation for author role.
23 """
25 @cached_property
26 def submissions(self) -> QuerySet[Submission]:
27 return Submission.objects.get_submissions().filter(created_by=self.user)
29 def can_create_submission(self) -> bool:
30 return True
32 def can_access_submission(self, submission: Submission) -> bool:
33 return submission in self.submissions
35 def can_manage_submission(self, submission: Submission) -> bool:
36 return False
38 def can_edit_submission(self, submission: Submission) -> bool:
39 result = self.can_access_submission(submission)
40 result = (
41 result
42 and submission.is_draft
43 or submission.state == SubmissionState.REVISION_REQUESTED.value
44 )
45 return result
47 def can_create_version(self, submission: Submission) -> bool:
48 if not self.can_access_submission(submission):
49 return False
50 current_version = submission.current_version
51 # The author must have entered at least 1 submission author to proceed
52 # with the submission version.
53 if submission.is_draft and current_version is None:
54 return submission.authors.exists()
55 return (
56 submission.state == SubmissionState.REVISION_REQUESTED.value
57 and current_version is not None
58 and current_version.submitted
59 )
61 def can_access_version(self, version: SubmissionVersion) -> bool:
62 return self.can_access_submission(version.submission)
64 def can_edit_version(self, version: SubmissionVersion) -> bool:
65 return not version.submitted and self.can_access_submission(version.submission)
67 def can_access_submission_author(self, submission: Submission) -> bool:
68 return self.can_access_submission(submission)
70 def can_access_reviews(self, version: SubmissionVersion) -> bool:
71 """
72 An author can access reviews when the review process is closed.
73 """
74 return not version.review_open and self.can_access_submission(version.submission)
76 def can_access_review(self, review: Review) -> bool:
77 """
78 An author can access review with at least one accessible file.
79 """
80 return (
81 self.can_access_reviews(review.version)
82 and review.submitted
83 and review.recommendation is not None
84 and any(self.can_access_review_file(f) for f in review.additional_files.all())
85 )
87 def can_access_review_author(self, review: Review) -> bool:
88 return (
89 app_settings.BLIND_MODE == BlindMode.NO_BLIND
90 and review.version.submission in self.submissions
91 )
93 def can_access_review_file(self, file: ReviewAdditionalFile) -> bool:
94 return file.author_access
96 def get_submission_status(self, submission: Submission) -> SubmissionStatusData:
97 """
98 For an author, the submission status is one of the following:
99 - "Submission is still in draft", True - when STATUS_OPENED
100 - "A new version must be submitted", True - when STATUS_REVISIONS_REQUESTED
101 - "Submission accepted", False - when STATUS_ACCEPTED
102 - "Submission declined", False - when STATUS_DECLINED
103 - "Waiting for editorial actions" - when STATUS_SUBMITTED or STATUS_REVISIONS_SUBMITTED
104 - "Under review", False - For other state
105 """
106 # Should not happen ?
107 if not self.can_access_submission(submission):
108 return SubmissionStatusData(
109 submission=submission, status=SubmissionStatus.ERROR, description=""
110 )
112 if submission.state in [
113 SubmissionState.SUBMITTED.value,
114 SubmissionState.REVISION_SUBMITTED.value,
115 ]:
116 return SubmissionStatusData(
117 submission=submission,
118 status=SubmissionStatus.WAITING,
119 description=_("Under review"),
120 )
121 elif submission.state == SubmissionState.OPENED.value:
122 status_data = SubmissionStatusData(
123 submission=submission,
124 status=SubmissionStatus.TODO,
125 description=_("Complete your submission"),
126 )
127 return status_data
129 elif submission.state == SubmissionState.REVISION_REQUESTED.value:
130 status_data = SubmissionStatusData(
131 submission=submission,
132 status=SubmissionStatus.TODO,
133 description=_("Submit a new version"),
134 )
135 # if submission.state == SubmissionState.OPENED.value and self.can_submit_submission(
136 # submission
137 # ):
138 # status_data.shortcut_actions.append(
139 # Button(
140 # id=f"submission-submit-{submission.pk}",
141 # title=_("Resume submission"),
142 # icon_class="fa-list-check",
143 # attrs={
144 # "href": [
145 # reverse_lazy(
146 # "mesh:submission_resume", kwargs={"pk": submission.pk}
147 # )
148 # ],
149 # "class": ["as-button"],
150 # },
151 # )
152 # )
153 # elif (
154 # submission.state == SubmissionState.REVISION_REQUESTED.value
155 # and self.can_create_version(submission)
156 # ):
157 # status_data.shortcut_actions.append(
158 # Button(
159 # id=f"submission-submit-{submission.pk}",
160 # title=_("Submit revisions"),
161 # icon_class="fa-plus",
162 # attrs={
163 # "href": [
164 # reverse_lazy(
165 # "mesh:submission_version_create",
166 # kwargs={"submission_pk": submission.pk},
167 # )
168 # ],
169 # "class": ["as-button"],
170 # },
171 # )
172 # )
173 # elif (
174 # submission.state == SubmissionState.REVISION_REQUESTED.value
175 # and submission.current_version
176 # and not submission.current_version.submitted
177 # ):
178 # status_data.shortcut_actions.append(
179 # Button(
180 # id=f"submission-submit-{submission.pk}",
181 # title=_("Resume revisions"),
182 # icon_class="fa-list-check",
183 # attrs={
184 # "href": [
185 # reverse_lazy(
186 # "mesh:submission_version_update",
187 # kwargs={"pk": submission.current_version.pk},
188 # )
189 # ],
190 # "class": ["as-button"],
191 # },
192 # )
193 # )
194 return status_data
196 elif submission.state == SubmissionState.ON_REVIEW.value:
197 return SubmissionStatusData(
198 submission=submission,
199 status=SubmissionStatus.WAITING,
200 description=_("Submission is under review"),
201 )
202 elif submission.state in [
203 SubmissionState.ACCEPTED.value,
204 SubmissionState.REJECTED.value,
205 ]:
206 return SubmissionStatusData(
207 submission=submission,
208 status=SubmissionStatus.DONE,
209 description=_("Submission") + " " + submission.get_state_display(),
210 )
212 return SubmissionStatusData(
213 submission=submission, status=SubmissionStatus.ERROR, description=""
214 )
216 def can_access_last_activity(self) -> bool:
217 return False
220class Author(Role):
221 """
222 Author role.
223 This is the base role of mesh app, always active.
224 """
226 _CODE: ClassVar[str] = "author"
227 _NAME: ClassVar[str] = _("Author")
228 _ICON_CLASS: ClassVar[str] = "fa-pen-nib"
229 _SUBMISSION_LIST_TITLE: ClassVar[str] = _("My submissions")
230 rights: AuthorRights
232 def __init__(self, user: User) -> None:
233 super().__init__(user)
235 @property
236 def active(self) -> bool:
237 return True
239 def get_rights(self) -> AuthorRights:
240 return AuthorRights(self.user)