Coverage for src/mesh/views/forms/review_forms.py: 41%

113 statements  

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

1from __future__ import annotations 

2 

3from datetime import date 

4from typing import TYPE_CHECKING, Any 

5 

6from allauth.account.models import EmailAddress 

7from django import forms 

8from django.contrib.auth import get_user_model 

9from django.core.exceptions import ValidationError 

10from django.forms.widgets import RadioSelect 

11from django.utils.translation import gettext_lazy as _ 

12 

13from mesh.models.review_models import Review, ReviewAdditionalFile 

14from mesh.views.components.ckeditor_config import DEFAULT_CKEDITOR_CONFIG, EMAIL_CKEDITOR_CONFIG 

15 

16from .base_forms import FileModelForm, MeshFormMixin, SubmittableModelForm 

17from .fields import CKEditorFormField, FileField 

18 

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

20 pass 

21 

22UserModel = get_user_model() 

23 

24 

25# def get_suggested_users_choices(suggestions, existing_reviewers): 

26# choices = [] 

27# for suggestion in suggestions: 

28# suggested_user = suggestion.suggested_user if suggestion.suggested_user is not None else suggestion.suggested_reviewer 

29# if suggested_user in existing_reviewers: 

30# choices.append( {"value": suggestion.pk, 

31# "label": str(suggestion.suggested_user), 

32# "disabled": True}) 

33# else: 

34# choices.append( {"value": suggestion.pk, "label": str(suggestion.suggested_user)} ) 

35# return choices 

36 

37 

38class ReviewCreateForm(MeshFormMixin, forms.ModelForm): 

39 """ 

40 Form used to create a new review, ie. request a new review from another user. 

41 """ 

42 

43 custom_display = "block" 

44 reviewer_select = forms.ChoiceField( 

45 required=False, choices=[("shortlist", "Shortlist"), ("new", "New")], widget=RadioSelect 

46 ) 

47 request_email = CKEditorFormField(EMAIL_CKEDITOR_CONFIG, required=True, label=_("E-mail")) 

48 request_email_subject = forms.CharField( 

49 label=_("E-mail subject"), max_length=256, required=True 

50 ) 

51 suggested_user = forms.ChoiceField(choices=[]) 

52 reviewer_first_name = forms.CharField( 

53 label=_("Reviewer - First name"), 

54 max_length=150, 

55 required=False, 

56 help_text=_("Leave empty for an existing user"), 

57 ) 

58 reviewer_last_name = forms.CharField( 

59 label=_("Reviewer - Last name"), 

60 max_length=150, 

61 required=False, 

62 help_text=_("Leave empty for an existing user"), 

63 ) 

64 reviewer_email = forms.EmailField( 

65 label=_("Reviewer - E-mail address"), 

66 required=False, 

67 help_text=_("Leave empty for an existing user"), 

68 ) 

69 

70 class Meta: 

71 model = Review 

72 fields = [ 

73 "suggested_user", 

74 "reviewer_first_name", 

75 "reviewer_last_name", 

76 "reviewer_email", 

77 "request_email_subject", 

78 "request_email", 

79 "date_response_due", 

80 "date_review_due", 

81 ] 

82 

83 def __init__(self, *args, **kwargs): 

84 # self.suggestions = kwargs.pop("_suggestions") 

85 # self.existing_reviewers = kwargs.pop("_existing_reviewers") 

86 choices = kwargs.pop("_choices") 

87 self.version = kwargs.pop("_version") 

88 super().__init__(*args, **kwargs) 

89 select_field = self.fields["suggested_user"] 

90 select_field.choices = choices 

91 # select_field.choices = get_suggested_users_choices(self.suggestions, self.existing_reviewers) 

92 select_field.required = False 

93 # select_field.widget.attrs["size"] = 10 

94 # select_field.widget.attrs["class"] = "form-select form-select-lg" 

95 select_field.empty_label = None 

96 

97 # reviewer_field.help_text = _("Existing user") 

98 # reviewer_field = self.fields["reviewer"] 

99 # reviewer_field.queryset = reviewers 

100 # reviewer_field.required = False 

101 # reviewer_field.help_text = _("Existing user") 

102 # reviewer_field.separator_after_or = True 

103 # reviewer_field.widget.attrs["size"] = 10 

104 # reviewer_field.widget.attrs["class"] = "form-select form-select-lg" 

105 # reviewer_field.empty_label = None 

106 self.fields["reviewer_email"].separator_after = True 

107 self.fields["date_response_due"].widget.input_type = "date" 

108 self.fields["date_response_due"].widget.attrs["class"] = "form-control form-control-lg" 

109 self.fields["date_review_due"].widget.input_type = "date" 

110 self.fields["date_review_due"].widget.attrs["class"] = "form-control form-control-lg" 

111 self.fields["request_email"].widget.attrs["class"] = "form-control" 

112 

113 def clean_date_response_due(self) -> date: 

114 date_input = self.cleaned_data["date_response_due"] 

115 if date_input < date.today(): 

116 raise ValidationError(_("The response due date must be later than today.")) 

117 

118 return date_input 

119 

120 def clean_date_review_due(self) -> date: 

121 date_input = self.cleaned_data["date_review_due"] 

122 if date_input < date.today(): 

123 raise ValidationError(_("The response due date must be later than today.")) 

124 

125 return date_input 

126 

127 def clean_reviewer_email(self) -> str: 

128 email = self.cleaned_data["reviewer_email"] 

129 if not email: 

130 return email 

131 if ( 

132 EmailAddress.objects.filter(email=email).exists() 

133 or UserModel.objects.filter(email=email).exists() 

134 ): 

135 raise ValidationError(_("The given e-mail address is already used.")) 

136 if self.version.reviews.filter(reviewer__email=email).exists(): 

137 raise ValidationError(_("The user is already assigned for a review.")) 

138 return email 

139 

140 def clean(self) -> dict[str, Any]: 

141 cleaned_data = super().clean() 

142 if self.errors: 

143 return cleaned_data 

144 

145 # Check dates validity 

146 response_date = cleaned_data["date_response_due"] 

147 review_date = cleaned_data["date_review_due"] 

148 if response_date > review_date: 

149 raise ValidationError( 

150 _("The response due date must be earlier than the review due date.") 

151 ) 

152 

153 # Check reviewer validity (either existing user or name + email fields) 

154 suggested_user = cleaned_data["suggested_user"] 

155 reviewer_select = cleaned_data["reviewer_select"] 

156 first_name = cleaned_data["reviewer_first_name"] 

157 last_name = cleaned_data["reviewer_last_name"] 

158 email = cleaned_data["reviewer_email"] 

159 if (reviewer_select == "shortlist" and not suggested_user) or ( 

160 reviewer_select == "new" and not (first_name and last_name and email) 

161 ): 

162 raise ValidationError( 

163 _( 

164 "You must either pick an existing user OR fill the first name, last name and e-mail fields." 

165 ) 

166 ) 

167 

168 return cleaned_data 

169 

170 

171class ReviewAcceptForm(MeshFormMixin, forms.ModelForm): 

172 """ 

173 Accept/decline form for the reviewer. 

174 """ 

175 

176 custom_display = "block" 

177 

178 date_response_due_display = forms.DateField( 

179 label=_("Response due date"), disabled=True, required=False 

180 ) 

181 date_review_due_display = forms.DateField( 

182 label=_("Review due date"), disabled=True, required=False 

183 ) 

184 accept_comment = CKEditorFormField(DEFAULT_CKEDITOR_CONFIG, required=False, label=_("Comment")) 

185 

186 class Meta: 

187 model = Review 

188 fields = [ 

189 "date_response_due_display", 

190 "date_review_due_display", 

191 "accepted", 

192 "accept_comment", 

193 ] 

194 

195 def __init__(self, *args, **kwargs): 

196 """ 

197 Set date fields input type. 

198 """ 

199 super().__init__(*args, **kwargs) 

200 self.fields["date_response_due_display"].widget.input_type = "date" 

201 # self.fields["date_response_due_display"].custom_display = "inline" 

202 self.fields["date_review_due_display"].widget.input_type = "date" 

203 # self.fields["date_review_due_display"].custom_display = "inline" 

204 

205 def clean(self) -> dict[str, Any]: 

206 cleaned_data = super().clean() 

207 if cleaned_data["accepted"] is False and not cleaned_data["accept_comment"]: 

208 raise ValidationError( 

209 _("You must fill the comment field when declining the review request.") 

210 ) 

211 return cleaned_data 

212 

213 

214class ReviewSubmitForm(SubmittableModelForm, FileModelForm): 

215 """ 

216 Form used to submit/complete a review. 

217 """ 

218 

219 custom_display = "block" 

220 

221 date_review_due_display = forms.DateField( 

222 label=_("Review due date"), disabled=True, required=False 

223 ) 

224 additional_files = FileField( 

225 required=True, 

226 label=_("Review file(s)"), 

227 allow_multiple_selected=True, 

228 model_class=ReviewAdditionalFile, 

229 related_name="additional_files", 

230 ) 

231 comment = CKEditorFormField( 

232 DEFAULT_CKEDITOR_CONFIG, label=_("Comment / Message"), required=False 

233 ) 

234 

235 class Meta: 

236 model = Review 

237 fields = [ 

238 "date_review_due_display", 

239 "recommendation", 

240 "additional_files", 

241 "comment", 

242 ] 

243 

244 def __init__(self, *args, **kwargs) -> None: 

245 """ 

246 Sets the date fields input type. 

247 """ 

248 super().__init__(*args, **kwargs) 

249 self.fields["date_review_due_display"].widget.input_type = "date" 

250 

251 def clean(self) -> dict[str, Any]: 

252 cleaned_data = super().clean() 

253 

254 if cleaned_data["recommendation"] is None: 

255 raise ValidationError(_("You must choose a recommendation value.")) 

256 

257 return cleaned_data 

258 

259 

260class ReviewConfirmForm(MeshFormMixin, forms.Form): 

261 custom_display = "inline" 

262 

263 confirm = forms.BooleanField(label=_("Confirm"), required=True)