Coverage for src / mesh / models / roles / author.py: 95%
74 statements
« prev ^ index » next coverage.py v7.13.1, created at 2026-08-11 09:47 +0000
« prev ^ index » next coverage.py v7.13.1, created at 2026-08-11 09:47 +0000
1from typing import ClassVar
3# from django.urls import reverse_lazy
4from django.db.models import Case, CharField, Value, When
5from django.utils.translation import gettext as _
6from opentelemetry import trace
8from mesh.app_settings import BlindMode, app_settings
9from mesh.models.orm.review_models import Review
10from mesh.models.orm.submission_models import Submission, SubmissionState, SubmissionVersion
12# from ..views.components.button import Button
13from mesh.models.roles.base_role import Role
14from mesh.models.submission_status import SubmissionStatus, SubmissionStatusData
16tracer = trace.get_tracer(__name__)
19class Author(Role):
20 """
21 Author role.
22 This is the base role of mesh app, always active.
23 """
25 _CODE: ClassVar[str] = "author"
26 _NAME: ClassVar[str] = _("Author")
27 _ICON_CLASS: ClassVar[str] = "fa-pen-nib"
28 _SUBMISSION_LIST_TITLE: ClassVar[str] = _("My submissions")
30 @tracer.start_as_current_span("Author.__init__")
31 def __init__(self, user) -> None:
32 super().__init__(user)
33 self.reviews_queryset = Review.objects.filter(
34 version__review_open=False,
35 submitted=True,
36 accepted=True,
37 recommendation__isnull=False,
38 additional_files__author_access=True,
39 )
41 if app_settings.BLIND_MODE != BlindMode.NO_BLIND:
42 self.reviewer_censored_annotate = Value("****, ****")
44 self.versions_queryset = SubmissionVersion.objects.all()
45 self.submissions_queryset = Submission.objects.filter(created_by=self.user)
47 def _get_is_active(self):
48 return True
50 def get_submissions(self):
51 return self._annotate_submission_query(self.submissions_queryset).annotate(
52 user_is_editor=Value(False)
53 )
55 def show_no_actions_message(self, submission: Submission) -> bool:
56 return not self.can_edit_submission(submission)
58 def can_access_submission(self, submission):
59 return submission.created_by == self.user
61 def can_edit_submission(self, submission):
62 result = self.can_access_submission(submission)
63 result = (
64 result
65 and submission.is_draft
66 or submission.state == SubmissionState.REVISION_REQUESTED.value
67 )
68 return result
70 def can_create_submission(self):
71 return True
73 def can_create_version(self, submission):
74 if not self.can_access_submission(submission):
75 return False
76 current_version = submission.versions.first()
77 # The author must have entered at least 1 submission author to proceed
78 # with the submission version.
79 if submission.is_draft and current_version is None:
80 return submission.authors.exists()
81 return (
82 submission.state == SubmissionState.REVISION_REQUESTED.value
83 and current_version is not None
84 and current_version.submitted
85 )
87 def can_access_version(self, version):
88 return self.can_access_submission(version.submission)
90 def can_edit_version(self, version):
91 return not version.submitted and self.can_access_submission(version.submission)
93 def can_access_reviews(self, version):
94 """
95 An author can access reviews when the review process is closed.
96 """
97 return not version.review_open and self.can_access_submission(version.submission)
99 def can_access_review(self, submission, review):
100 """
101 An author can access review with at least one accessible file.
102 """
103 return (
104 self.can_access_reviews(review.version)
105 and review.submitted
106 and review.recommendation is not None
107 and any(self.can_access_review_file(f) for f in review.additional_files.all())
108 )
110 def can_access_review_file(self, file):
111 return file.author_access
113 def get_submission_status(self, submission):
114 """
115 For an author, the submission status is one of the following:
116 - "Submission is still in draft", True - when STATUS_OPENED
117 - "A new version must be submitted", True - when STATUS_REVISIONS_REQUESTED
118 - "Submission accepted", False - when STATUS_ACCEPTED
119 - "Submission declined", False - when STATUS_DECLINED
120 - "Waiting for editorial actions" - when STATUS_SUBMITTED or STATUS_REVISIONS_SUBMITTED
121 - "Under review", False - For other state
122 """
123 # Should not happen ?
124 if not self.can_access_submission(submission):
125 return SubmissionStatusData(
126 submission=submission, status=SubmissionStatus.ERROR, description=""
127 )
129 if submission.state in [
130 SubmissionState.SUBMITTED.value,
131 SubmissionState.REVISION_SUBMITTED.value,
132 ]:
133 return SubmissionStatusData(
134 submission=submission,
135 status=SubmissionStatus.WAITING,
136 description=_("Under review"),
137 )
138 elif submission.state == SubmissionState.OPENED.value:
139 status_data = SubmissionStatusData(
140 submission=submission,
141 status=SubmissionStatus.TODO,
142 description=_("Complete your submission"),
143 )
144 return status_data
146 elif submission.state == SubmissionState.REVISION_REQUESTED.value:
147 status_data = SubmissionStatusData(
148 submission=submission,
149 status=SubmissionStatus.TODO,
150 description=_("Submit a new version"),
151 )
152 # if submission.state == SubmissionState.OPENED.value and self.can_submit_submission(
153 # submission
154 # ):
155 # status_data.shortcut_actions.append(
156 # Button(
157 # id=f"submission-submit-{submission.pk}",
158 # title=_("Resume submission"),
159 # icon_class="fa-list-check",
160 # attrs={
161 # "href": [
162 # reverse_lazy(
163 # "mesh:submission_resume", kwargs={"pk": submission.pk}
164 # )
165 # ],
166 # "class": ["as-button"],
167 # },
168 # )
169 # )
170 # elif (
171 # submission.state == SubmissionState.REVISION_REQUESTED.value
172 # and self.can_create_version(submission)
173 # ):
174 # status_data.shortcut_actions.append(
175 # Button(
176 # id=f"submission-submit-{submission.pk}",
177 # title=_("Submit revisions"),
178 # icon_class="fa-plus",
179 # attrs={
180 # "href": [
181 # reverse_lazy(
182 # "mesh:submission_version_create",
183 # kwargs={"submission_pk": submission.pk},
184 # )
185 # ],
186 # "class": ["as-button"],
187 # },
188 # )
189 # )
190 # elif (
191 # submission.state == SubmissionState.REVISION_REQUESTED.value
192 # and submission.current_version
193 # and not submission.current_version.submitted
194 # ):
195 # status_data.shortcut_actions.append(
196 # Button(
197 # id=f"submission-submit-{submission.pk}",
198 # title=_("Resume revisions"),
199 # icon_class="fa-list-check",
200 # attrs={
201 # "href": [
202 # reverse_lazy(
203 # "mesh:submission_version_update",
204 # kwargs={"pk": submission.current_version.pk},
205 # )
206 # ],
207 # "class": ["as-button"],
208 # },
209 # )
210 # )
211 return status_data
213 elif submission.state == SubmissionState.ON_REVIEW.value:
214 return SubmissionStatusData(
215 submission=submission,
216 status=SubmissionStatus.WAITING,
217 description=_("Submission is under review"),
218 )
219 elif submission.state in [
220 SubmissionState.ACCEPTED.value,
221 SubmissionState.REJECTED.value,
222 ]:
223 return SubmissionStatusData(
224 submission=submission,
225 status=SubmissionStatus.DONE,
226 description=_("Submission") + " " + submission.get_state_display(),
227 )
229 return SubmissionStatusData(
230 submission=submission, status=SubmissionStatus.ERROR, description=""
231 )
233 def annotate_submission_status(self, qs):
234 return qs.annotate(
235 computed_status=Case(
236 When(
237 state__in=[
238 SubmissionState.OPENED.value,
239 SubmissionState.REVISION_REQUESTED.value,
240 ],
241 then=Value(SubmissionStatus.TODO.value),
242 ),
243 When(
244 state__in=[
245 SubmissionState.SUBMITTED.value,
246 SubmissionState.REVISION_SUBMITTED.value,
247 SubmissionState.ON_REVIEW.value,
248 ],
249 then=Value(SubmissionStatus.WAITING.value),
250 ),
251 When(
252 state__in=[
253 SubmissionState.ACCEPTED.value,
254 SubmissionState.REJECTED.value,
255 ],
256 then=Value(SubmissionStatus.DONE.value),
257 ),
258 default=Value(SubmissionStatus.ERROR.value),
259 output_field=CharField(),
260 )
261 )
263 def can_access_last_activity(self) -> bool:
264 return False