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

66 statements  

« prev     ^ index     » next       coverage.py v7.7.0, created at 2025-04-28 07:45 +0000

1from __future__ import annotations 

2 

3from collections.abc import Callable, Iterable 

4from typing import TYPE_CHECKING, Any, 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_lazy 

11from ptf.url_utils import add_query_parameters_to_url 

12from ptf.utils import send_email, template_from_string 

13 

14from mesh.model.roles.role_handler import RoleHandler 

15 

16from ..app_settings import app_settings 

17from ..models.submission_models import Submission 

18from ..models.user_models import USER_TOKEN_QUERY_PARAM, Suggestion, User, UserToken 

19 

20if TYPE_CHECKING: 20 ↛ 21line 20 didn't jump to line 21 because the condition on line 20 was never true

21 from ..models.review_models import Review 

22 

23# The below is correct for python > 3.11 instead of declaring a TypeVar 

24# def group_by[_T](dataset: Iterable[_T], key: Callable[[_T], Any]) -> dict[Any, list[_T]]: 

25 

26_T = TypeVar("_T") 

27 

28 

29def group_by(dataset: Iterable[_T], key: Callable[[_T], Any]) -> dict[Any, list[_T]]: 

30 """ 

31 Group the given dataset using the provided key function to get the value 

32 to group on. 

33 

34 Params: 

35 - `dataset` The dataset to group by. 

36 - `key` The function used to get the grouping value for each item 

37 if the dataset. 

38 Returns: 

39 - `dict` The dictionnary with keys being the group key, and 

40 values the list of items in the group. 

41 """ 

42 grouped_data: dict[Any, list[_T]] = {} 

43 for item in dataset: 

44 value = key(item) 

45 if value not in grouped_data: 

46 grouped_data[value] = [] 

47 grouped_data[value].append(item) 

48 

49 return grouped_data 

50 

51 

52def create_new_user( 

53 email: str, first_name: str, last_name: str, password: str | None = None 

54) -> User: 

55 """ 

56 Create a new User and a new EmailAddress using the given e-mail address. 

57 No verification are made regarding the provided e-mail address 

58 """ 

59 user = User.objects.create_user( 

60 email, password=password, first_name=first_name, last_name=last_name 

61 ) 

62 EmailAddress.objects.create(user=user, email=email, primary=True, verified=False) 

63 

64 return user 

65 

66 

67def get_review_request_email( 

68 submission: Submission, base_url: str, template_name: str = "" 

69) -> str: 

70 """ 

71 Render the default e-mail template for the review request. 

72 """ 

73 template_name = template_name or "mesh/emails/review/referee_request.html" 

74 if base_url[-1] == "/": 

75 base_url = base_url[:-1] 

76 context = { 

77 "submission_name": submission.name, 

78 "submission_abstract": submission.abstract, 

79 "submission_url": base_url 

80 + reverse_lazy("mesh:submission_details", kwargs={"pk": submission.pk}), 

81 } 

82 

83 return render_to_string(template_name, context) 

84 

85 

86def send_review_request_email(review: Review, email: str, subject: str, base_url: str): 

87 """ 

88 Sends the provided email to the review's assigned reviewer. 

89 Params: 

90 - `review` The review object. 

91 - `email` The e-mail content (HTML). 

92 - `subject` The e-mail subject. 

93 - `base_url` The base URL to use when generating links. 

94 """ 

95 recipient: User = review.reviewer 

96 to = [recipient.email] 

97 if base_url[-1] == "/": 

98 base_url = base_url[:-1] 

99 

100 submission = review.version.submission 

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

102 

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

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

105 role_handler = RoleHandler(recipient, partial_init=True) 

106 

107 if role_handler.token_authentication_allowed(): 

108 token = UserToken.get_token(recipient) 

109 context["TOKEN_AUTH_URL"] = add_query_parameters_to_url( 

110 base_url + reverse_lazy("mesh:token_login"), 

111 { 

112 USER_TOKEN_QUERY_PARAM: [token.key], 

113 REDIRECT_FIELD_NAME: [ 

114 reverse_lazy("mesh:submission_details", kwargs={"pk": submission.pk}) 

115 ], 

116 }, 

117 ) 

118 email_template = template_from_string(email) 

119 rendered_email = email_template.render(context) 

120 

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

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

123 reply_to = [review.created_by.email] # type:ignore 

124 

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

126 

127 review.request_email = rendered_email 

128 review.save() 

129 

130 

131def get_suggestion(submission): 

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

133 selected_reviewers = [] 

134 for suggestion in qs: 

135 if suggestion.suggested_user is not None: 

136 reviewer = suggestion.suggested_user 

137 reviewer.type = "user" 

138 else: 

139 reviewer = suggestion.suggested_reviewer 

140 reviewer.type = "reviewer" 

141 selected_reviewers.append(reviewer) 

142 return selected_reviewers