Coverage for src/mesh/views/components/button.py: 57%
81 statements
« prev ^ index » next coverage.py v7.7.0, created at 2025-04-28 07:45 +0000
« prev ^ index » next coverage.py v7.7.0, created at 2025-04-28 07:45 +0000
1from dataclasses import dataclass, field
3from django import forms
4from django.urls import reverse, reverse_lazy
5from django.utils.translation import gettext_lazy as _
6from ptf.url_utils import add_query_parameters_to_url
8from mesh.model.roles.role_handler import RoleHandler
10from ...models.review_models import Review, ReviewState
11from ...models.submission_models import Submission
14@dataclass
15class Button:
16 """
17 Interface for a button component.
18 """
20 # Unique ID for the button - not used for now
21 id: str
22 # Title to display as text in the button
23 title: str
24 # Optional font-awesome 6 icon code/class to be added next to the title.
25 icon_class: str = field(default="")
26 # Additional HTML attributes (ex: `{"type": ["submit"]})`
27 attrs: dict[str, list[str]] = field(default_factory=dict)
28 # A Django form - If not None, the button will be rendered as a full form
29 # with this unique button. You must provide the "href" HTML attribute for the form
30 # to be considered as one.
31 form: forms.Form | None = field(default=None)
33 def is_form(self) -> bool:
34 """
35 Whether the button should be displayed as a form.
36 """
37 return bool(self.form) and bool(self.attrs.get("href", False))
39 def is_link(self) -> bool:
40 """
41 Whether the button is a link.
42 """
43 return bool(self.attrs.get("href", False) or self.attrs.get("data-modal-href", False))
45 def is_modal_link(self):
46 """
47 Whether the button is a link.
48 """
49 return bool(self.attrs.get("data-modal-href", False))
51 def add_attr(self, attr: str, values: list[str]):
52 """
53 Add the given value to the given attribute values array.
55 Ex:
56 `button.add_attr("class", ["btn", "btn-primary"])`
57 `button.add_attr("class", ["btn"])`
58 """
59 if attr not in self.attrs:
60 self.attrs[attr] = []
62 for value in values:
63 if value in self.attrs[attr]:
64 continue
65 self.attrs[attr].append(value)
67 def set_attr(self, attr: str, values: list[str]):
68 """
69 Set the given value for the given attribute.
71 Ex:
72 `button.set_attr("class", ["btn", "btn-primary"])`
73 `button.set_attr("href", ["http://myserver"])`
74 """
75 self.attrs[attr] = values
77 def remove_attr(self, attr: str):
78 """
79 Remove the given attribute from the attributes array.
80 """
81 if attr in self.attrs:
82 del self.attrs[attr]
85@dataclass
86class SubmissionActionList:
87 title: str
88 actions: list[Button] = field(default_factory=list)
91def build_submission_actions(
92 submission: Submission, role_handler: RoleHandler, *args, **kwargs
93) -> list[SubmissionActionList]:
94 """
95 Returns the complete list of available actions to the current user.
96 """
97 submission_actions: list[Button] = []
98 manage_actions: list[Button] = []
99 btn_classes = ["as-button"] if kwargs.get("display_as_btn", False) else []
100 shortlist = kwargs.get("shortlist", True)
102 if role_handler.check_rights("can_access_submission_log", submission) and shortlist: 102 ↛ 119line 102 didn't jump to line 119 because the condition on line 102 was always true
103 manage_actions.append(
104 Button(
105 id=f"submission-view-{submission.pk}",
106 title=_("VIEW SUBMISSION"),
107 icon_class="fa-eye",
108 attrs={
109 "href": [
110 reverse_lazy("mesh:submission_details", kwargs={"pk": submission.pk})
111 ],
112 "class": btn_classes,
113 },
114 )
115 )
117 #### [BEGIN] Author actions ####
118 # Resume submit process
119 can_submit_submission = role_handler.check_rights("can_submit_submission", submission)
120 if can_submit_submission: 120 ↛ 121line 120 didn't jump to line 121 because the condition on line 120 was never true
121 submission_actions.append(
122 Button(
123 id=f"submission-submit-{submission.pk}",
124 title=_("RESUME SUBMISSION"),
125 icon_class="fa-list-check",
126 attrs={
127 "href": [reverse_lazy("mesh:submission_resume", kwargs={"pk": submission.pk})],
128 "class": btn_classes,
129 },
130 )
131 )
133 # Edit submission metadata & authors
134 elif role_handler.check_rights("can_edit_submission", submission): 134 ↛ 164line 134 didn't jump to line 164 because the condition on line 134 was always true
135 if not shortlist or can_submit_submission: 135 ↛ 136line 135 didn't jump to line 136 because the condition on line 135 was never true
136 submission_actions.append(
137 Button(
138 id=f"submission-edit-metadata-{submission.pk}",
139 title=_("EDIT METADATA"),
140 icon_class="fa-pen-to-square",
141 attrs={
142 "href": [
143 reverse_lazy("mesh:submission_update", kwargs={"pk": submission.pk})
144 ],
145 "class": btn_classes,
146 },
147 )
148 )
149 submission_actions.append(
150 Button(
151 id=f"submission-edit-authors-{submission.pk}",
152 title=_("EDIT AUTHOR"),
153 icon_class="fa-pen-to-square",
154 attrs={
155 "href": [
156 reverse_lazy("mesh:submission_authors", kwargs={"pk": submission.pk})
157 ],
158 "class": btn_classes,
159 },
160 )
161 )
163 # Create a new submission version
164 if not can_submit_submission and role_handler.check_rights("can_create_version", submission): 164 ↛ 165line 164 didn't jump to line 165 because the condition on line 164 was never true
165 submission_actions.append(
166 Button(
167 id=f"submission-create-version-{submission.pk}",
168 title=_("SUBMIT REVISIONS"),
169 icon_class="fa-plus",
170 attrs={
171 "href": [
172 reverse_lazy(
173 "mesh:submission_version_create",
174 kwargs={"submission_pk": submission.pk},
175 )
176 ],
177 "class": btn_classes,
178 },
179 )
180 )
182 # Edit the current submission version
183 current_version = submission.current_version
184 if ( 184 ↛ 189line 184 didn't jump to line 189 because the condition on line 184 was never true
185 not can_submit_submission
186 and current_version
187 and role_handler.check_rights("can_edit_version", current_version)
188 ):
189 submission_actions.append(
190 Button(
191 id=f"submission-edit-version-{submission.pk}",
192 title=_("RESUME REVISIONS"),
193 icon_class="fa-list-check",
194 attrs={
195 "href": [
196 reverse_lazy(
197 "mesh:submission_version_update",
198 kwargs={"pk": current_version.pk},
199 )
200 ],
201 "class": btn_classes,
202 },
203 )
204 )
205 #### [END] Author actions ####
207 #### [BEGIN] Editor actions ####
208 if role_handler.check_rights("can_assign_editor", submission): 208 ↛ 245line 208 didn't jump to line 245 because the condition on line 208 was always true
209 """
210 <a id="assigned-editors-edit" class="as-button" href="{% url "mesh:submission_editors" pk=submission.pk%}">
211 <i class="fa-solid fa-plus"></i>
212 {% translate "Assign editor" %}
213 </a>
214 """
215 manage_actions.append(
216 Button(
217 id=f"assigned-editors-edit-{submission.pk}",
218 title=_("ASSIGN EDITOR"),
219 icon_class="fa-plus",
220 attrs={
221 "class": btn_classes,
222 "data-modal-href": [
223 reverse_lazy("mesh:async_assign_editor", kwargs={"pk": submission.pk})
224 ],
225 "data-reload": ["true"] if kwargs.get("reload_page", False) else ["false"],
226 "data-reload-id": [kwargs.get("reload_id", "")],
227 "data-reload-href": (
228 [
229 reverse_lazy(
230 "mesh:api_fetch_submission",
231 kwargs={
232 "pk": submission.pk,
233 "row_id": kwargs.get("reload_id", ""),
234 },
235 )
236 ]
237 if kwargs.get("reload_id", "") != ""
238 else [""]
239 ),
240 },
241 )
242 )
244 # Send the submission version to review
245 if role_handler.check_rights("can_start_review_process", submission): 245 ↛ 246line 245 didn't jump to line 246 because the condition on line 245 was never true
246 from ...views.forms.editorial_forms import StartReviewProcessForm
248 form = StartReviewProcessForm(initial={"process": True})
250 manage_actions.append(
251 Button(
252 id=f"submission-send-to-review-{submission.pk}",
253 title=_("SEND TO REVIEW"),
254 icon_class="fa-file-import",
255 form=form,
256 attrs={
257 "href": [
258 reverse_lazy(
259 "mesh:submission_start_review_process",
260 kwargs={"pk": submission.pk},
261 )
262 ],
263 "class": ["as-link"],
264 },
265 )
266 )
268 # Make an editorial decision
269 if role_handler.check_rights("can_create_editorial_decision", submission): 269 ↛ 288line 269 didn't jump to line 288 because the condition on line 269 was always true
270 manage_actions.append(
271 Button(
272 id=f"submission-editorial-decision-{submission.pk}",
273 title=_("EDITORIAL DECISION"),
274 icon_class="fa-plus",
275 attrs={
276 "href": [
277 reverse_lazy(
278 "mesh:editorial_decision_create",
279 kwargs={"submission_pk": submission.pk},
280 )
281 ],
282 "class": btn_classes,
283 },
284 )
285 )
287 # Request a new review
288 if current_version and role_handler.check_rights("can_invite_reviewer", current_version): 288 ↛ 289line 288 didn't jump to line 289 because the condition on line 288 was never true
289 manage_actions.append(
290 Button(
291 id=f"submission-referee-request-{submission.pk}",
292 title=_("REQUEST REVIEW"),
293 icon_class="fa-user-group",
294 attrs={
295 "href": [
296 reverse_lazy(
297 "mesh:review_create",
298 kwargs={"version_pk": submission.current_version.pk}, # type:ignore
299 )
300 ],
301 "class": btn_classes,
302 },
303 )
304 )
306 # if role_handler.check_rights("can_access_submission_log", submission) and not shortlist:
307 # manage_actions.append(
308 # Button(
309 # id=f"submission-log-{submission.pk}",
310 # title=_("VIEW HISTORY"),
311 # icon_class="fa-clock-rotate-left",
312 # attrs={
313 # "href": [reverse_lazy("mesh:submission_log", kwargs={"pk": submission.pk})],
314 # "class": btn_classes,
315 # },
316 # )
317 # )
318 #### [END] Editor actions ####
320 #### [BEGIN] Reviewer actions ####
321 # Submit / Edit the user current review
322 current_review: Review | None = role_handler.get_from_rights(
323 "get_current_open_review", current_version
324 )
325 if current_review is not None and current_review.accepted is None: 325 ↛ 327line 325 didn't jump to line 327 because the condition on line 325 was never true
326 # Display 2 buttons: Accept & Decline if the review has not been accepted yet.
327 manage_actions.extend(
328 [
329 Button(
330 id=f"review-accept-{current_review.pk}",
331 title=_("Accept review"),
332 icon_class="fa-check",
333 attrs={
334 "href": [
335 add_query_parameters_to_url(
336 reverse(
337 "mesh:review_accept",
338 kwargs={"pk": current_review.pk},
339 ),
340 {"accepted": ["true"]},
341 )
342 ],
343 "class": ["as-button", "button-success"],
344 },
345 ),
346 Button(
347 id=f"review-accept-{current_review.pk}",
348 title=_("Decline review"),
349 icon_class="fa-xmark",
350 attrs={
351 "href": [
352 add_query_parameters_to_url(
353 reverse(
354 "mesh:review_accept",
355 kwargs={"pk": current_review.pk},
356 ),
357 {"accepted": ["false"]},
358 )
359 ],
360 "class": ["as-button", "button-error"],
361 },
362 ),
363 ]
364 )
365 elif current_review is not None: 365 ↛ 366line 365 didn't jump to line 366 because the condition on line 365 was never true
366 action_name = _("SUBMIT REVIEW")
367 if current_review.state in [
368 ReviewState.SUBMITTED.value,
369 ReviewState.DECLINED.value,
370 ]:
371 action_name = _("EDIT REVIEW")
372 manage_actions.append(
373 Button(
374 id=f"review-{current_review.pk}",
375 title=action_name,
376 icon_class="fa-pen-to-square",
377 attrs={
378 "href": [reverse_lazy("mesh:review_update", kwargs={"pk": current_review.pk})],
379 "class": btn_classes,
380 },
381 )
382 )
383 #### [END] Reviewer actions ####
384 action_lists = []
385 if submission_actions: 385 ↛ 386line 385 didn't jump to line 386 because the condition on line 385 was never true
386 action_lists.append(
387 SubmissionActionList(title=_("Submission"), actions=submission_actions)
388 )
390 if manage_actions: 390 ↛ 393line 390 didn't jump to line 393 because the condition on line 390 was always true
391 action_lists.append(SubmissionActionList(title=_("Manage"), actions=manage_actions))
393 return action_lists