Coverage for src / mesh / views / utils.py: 94%
77 statements
« prev ^ index » next coverage.py v7.13.1, created at 2026-05-04 12:41 +0000
« prev ^ index » next coverage.py v7.13.1, created at 2026-05-04 12:41 +0000
1from __future__ import annotations
3from collections.abc import Callable, Iterable
4from typing import TYPE_CHECKING, TypeVar
6from allauth.account.models import EmailAddress
7from django.conf import settings
8from django.contrib.auth import REDIRECT_FIELD_NAME
9from django.template.loader import render_to_string
10from django.urls import reverse
11from django.utils.translation import gettext_lazy as _
12from ptf.url_utils import add_query_parameters_to_url
13from ptf.utils import send_email, template_from_string
15from mesh.app_settings import app_settings
16from mesh.models.orm.editorial_models import EditorSubmissionRight
17from mesh.models.orm.submission_models import Submission, SubmissionLog
18from mesh.models.orm.suggestion_model import Suggestion
19from mesh.models.orm.user_models import USER_TOKEN_QUERY_PARAM, User, UserToken
20from mesh.models.roles.role_handler import RoleHandler
22if TYPE_CHECKING:
23 from mesh.models.orm.review_models import Review
25# The below is correct for python > 3.11 instead of declaring a TypeVar
26# def group_by[_T](dataset: Iterable[_T], key: Callable[[_T], Any]) -> dict[Any, list[_T]]:
28_T = TypeVar("_T")
29_V = TypeVar("_V")
32def group_by(dataset: Iterable[_T], key: Callable[[_T], _V]):
33 """
34 Group the given dataset using the provided key function to get the value
35 to group on.
37 Params:
38 - `dataset` The dataset to group by.
39 - `key` The function used to get the grouping value for each item
40 if the dataset.
41 Returns:
42 - `dict` The dictionnary with keys being the group key, and
43 values the list of items in the group.
44 """
45 grouped_data: dict[_V, list[_T]] = {}
46 for item in dataset:
47 value = key(item)
48 if value not in grouped_data:
49 grouped_data[value] = []
50 grouped_data[value].append(item)
52 return grouped_data
55def create_new_user(
56 email: str, first_name: str, last_name: str, password: str | None = None
57) -> User:
58 """
59 Create a new User and a new EmailAddress using the given e-mail address.
60 No verification are made regarding the provided e-mail address
61 """
62 user = User.objects.create_user(
63 email, password=password, first_name=first_name, last_name=last_name
64 )
65 EmailAddress.objects.create(user=user, email=email, primary=True, verified=False)
67 return user
70def get_review_request_email(
71 submission: Submission, base_url: str, template_name: str = ""
72) -> str:
73 """
74 Render the default e-mail template for the review request.
75 """
76 template_name = template_name or "mesh/emails/review/referee_request.html"
77 if base_url[-1] == "/":
78 base_url = base_url[:-1]
79 context = {
80 "submission_name": submission.name,
81 "submission_abstract": submission.abstract,
82 "submission_url": base_url
83 + reverse("mesh:submission_details", kwargs={"submission_pk": submission.pk}),
84 }
86 return render_to_string(template_name, context)
89def send_review_request_email(review: Review, email: str, subject: str, base_url: str):
90 """
91 Sends the provided email to the review's assigned reviewer.
92 Params:
93 - `review` The review object.
94 - `email` The e-mail content (HTML).
95 - `subject` The e-mail subject.
96 - `base_url` The base URL to use when generating links.
97 """
98 recipient: User = review.reviewer
99 to = [recipient.email]
100 if base_url[-1] == "/":
101 base_url = base_url[:-1]
103 submission = review.version.submission
104 context = {"RECIPIENT": recipient.get_full_name()}
106 # Generate a token authentication URL if the user is allowed to
107 if getattr(recipient, "is_token_authentication_allowed", False):
108 role_handler = RoleHandler(recipient, partial_init=True)
110 if role_handler.token_authentication_allowed():
111 token = UserToken.get_token(recipient)
112 context["TOKEN_AUTH_URL"] = add_query_parameters_to_url(
113 base_url + reverse("mesh:token_login"),
114 {
115 USER_TOKEN_QUERY_PARAM: [token.key],
116 REDIRECT_FIELD_NAME: [
117 reverse("mesh:submission_details", kwargs={"submission_pk": submission.pk})
118 ],
119 },
120 )
121 email_template = template_from_string(email)
122 rendered_email = email_template.render(context)
124 subject = f"{app_settings.EMAIL_PREFIX}{subject}"
125 from_email = f"{review.created_by.get_full_name()} via MESH <{settings.DEFAULT_FROM_EMAIL}>"
126 reply_to = [review.created_by.email]
128 send_email(rendered_email, subject, to, from_email=from_email, reply_to=reply_to)
130 review.request_email = rendered_email
131 review.save()
134def get_suggestion(submission):
135 qs = Suggestion.objects.filter(submission=submission).order_by("seq")
136 selected_reviewers = []
137 for suggestion in qs:
138 reviewer = suggestion.suggested_user
139 if reviewer is not None:
140 reviewer.type = "user"
141 else:
142 reviewer = suggestion.suggested_reviewer
143 reviewer.type = "reviewer"
144 selected_reviewers.append(reviewer)
145 return selected_reviewers
148def assign_editor(submission: Submission, editor: User, manager: User | None, date=None):
149 assignement, created = EditorSubmissionRight.objects.get_or_create(
150 submission=submission, user=editor
151 )
152 if date:
153 assignement.override_saved_date(
154 created_by_user=manager,
155 last_modified_by_user=manager,
156 date_created=date,
157 date_last_modified=date,
158 )
159 assignement.save()
161 return SubmissionLog.add_message(
162 submission,
163 content=_(f"{editor} assigned as an editor."),
164 content_en=f"{editor} assigned as an editor.",
165 user=manager,
166 significant=True,
167 date=date,
168 )
171def remove_editor(self, editor: User, manager: User | None):
172 EditorSubmissionRight.objects.get(submission=self, user=editor).delete()
173 return SubmissionLog.add_message(
174 self,
175 content=_(f"{editor} assigned as an editor."),
176 content_en=f"{editor} assigned as an editor.",
177 user=manager,
178 significant=True,
179 )