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

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

102 

103 submission = review.version.submission 

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

105 

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) 

109 

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) 

123 

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] 

127 

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

129 

130 review.request_email = rendered_email 

131 review.save() 

132 

133 

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 

146 

147 

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() 

160 

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 ) 

169 

170 

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 )