Coverage for src / mesh / views / utils.py: 93%

75 statements  

« prev     ^ index     » next       coverage.py v7.13.1, created at 2026-07-09 13:14 +0000

1from __future__ import annotations 

2 

3from collections.abc import Callable, Iterable 

4from typing import TYPE_CHECKING, TypeVar 

5 

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 

14 

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 

21 

22if TYPE_CHECKING: 

23 from mesh.models.orm.review_models import Review 

24 

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]]: 

27 

28_T = TypeVar("_T") 

29_V = TypeVar("_V") 

30 

31 

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. 

36 

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) 

51 

52 return grouped_data 

53 

54 

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) 

66 

67 return user 

68 

69 

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 } 

85 

86 return render_to_string(template_name, context) 

87 

88 

89def send_review_request_email(review: Review, email: str, subject: 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 

101 submission = review.version.submission 

102 context = {"RECIPIENT": recipient.get_full_name()} 

103 

104 # Generate a token authentication URL if the user is allowed to 

105 if getattr(recipient, "is_token_authentication_allowed", False): 

106 role_handler = RoleHandler(recipient, partial_init=True) 

107 

108 if role_handler.token_authentication_allowed(): 

109 token = UserToken.get_token(recipient) 

110 context["TOKEN_AUTH_URL"] = add_query_parameters_to_url( 

111 reverse("mesh:token_login", kwargs={"submission_pk": submission.pk}), 

112 { 

113 USER_TOKEN_QUERY_PARAM: [token.key], 

114 REDIRECT_FIELD_NAME: [ 

115 reverse("mesh:submission_details", kwargs={"submission_pk": submission.pk}) 

116 ], 

117 }, 

118 ) 

119 email_template = template_from_string(email) 

120 rendered_email = email_template.render(context) 

121 

122 subject = f"{app_settings.EMAIL_PREFIX}{subject}" 

123 from_email = f"{review.created_by.get_full_name()} via MESH <{settings.DEFAULT_FROM_EMAIL}>" 

124 reply_to = [review.created_by.email] 

125 

126 send_email(rendered_email, subject, to, from_email=from_email, reply_to=reply_to) 

127 

128 review.request_email = rendered_email 

129 review.save() 

130 

131 

132def get_suggestion(submission): 

133 qs = Suggestion.objects.filter(submission=submission).order_by("seq") 

134 selected_reviewers = [] 

135 for suggestion in qs: 

136 reviewer = suggestion.suggested_user 

137 if reviewer is not None: 

138 reviewer.type = "user" 

139 else: 

140 reviewer = suggestion.suggested_reviewer 

141 reviewer.type = "reviewer" 

142 selected_reviewers.append(reviewer) 

143 return selected_reviewers 

144 

145 

146def assign_editor(submission: Submission, editor: User, manager: User | None, date=None): 

147 assignement, created = EditorSubmissionRight.objects.get_or_create( 

148 submission=submission, user=editor 

149 ) 

150 if date: 

151 assignement.override_saved_date( 

152 created_by_user=manager, 

153 last_modified_by_user=manager, 

154 date_created=date, 

155 date_last_modified=date, 

156 ) 

157 assignement.save() 

158 

159 return SubmissionLog.add_message( 

160 submission, 

161 content=_(f"{editor} assigned as an editor."), 

162 content_en=f"{editor} assigned as an editor.", 

163 user=manager, 

164 significant=True, 

165 date=date, 

166 ) 

167 

168 

169def remove_editor(self, editor: User, manager: User | None): 

170 EditorSubmissionRight.objects.get(submission=self, user=editor).delete() 

171 return SubmissionLog.add_message( 

172 self, 

173 content=_(f"{editor} assigned as an editor."), 

174 content_en=f"{editor} assigned as an editor.", 

175 user=manager, 

176 significant=True, 

177 )