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

153 statements  

« prev     ^ index     » next       coverage.py v7.9.0, created at 2025-09-10 11:20 +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 

25class ReviewCreateForm(MeshFormMixin, forms.ModelForm): 

26 """ 

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

28 """ 

29 

30 custom_display = "block" 

31 reviewer_select = forms.ChoiceField( 

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

33 ) 

34 request_email = CKEditorFormField(EMAIL_CKEDITOR_CONFIG, required=False, label=_("E-mail")) 

35 request_email_subject = forms.CharField( 

36 label=_("E-mail subject"), max_length=256, required=False 

37 ) 

38 quick_request_email = CKEditorFormField( 

39 EMAIL_CKEDITOR_CONFIG, required=False, label=_("E-mail") 

40 ) 

41 quick_request_email_subject = forms.CharField( 

42 label=_("E-mail subject"), max_length=256, required=False 

43 ) 

44 

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

46 reviewer_first_name = forms.CharField( 

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

48 max_length=150, 

49 required=False, 

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

51 ) 

52 reviewer_last_name = forms.CharField( 

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

54 max_length=150, 

55 required=False, 

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

57 ) 

58 reviewer_email = forms.EmailField( 

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

60 required=False, 

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

62 ) 

63 

64 class Meta: 

65 model = Review 

66 fields = [ 

67 "suggested_user", 

68 "reviewer_first_name", 

69 "reviewer_last_name", 

70 "reviewer_email", 

71 "request_email_subject", 

72 "request_email", 

73 "quick", 

74 "date_response_due", 

75 "date_review_due", 

76 ] 

77 

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

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

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

81 choices = kwargs.pop("_choices") 

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

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

84 select_field = self.fields["suggested_user"] 

85 select_field.choices = choices 

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

87 select_field.required = False 

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

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

90 select_field.empty_label = None 

91 

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

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

94 # reviewer_field.queryset = reviewers 

95 # reviewer_field.required = False 

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

97 # reviewer_field.separator_after_or = True 

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

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

100 # reviewer_field.empty_label = None 

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

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

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

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

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

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

107 

108 def clean_date_response_due(self) -> date: 

109 date_input = self.cleaned_data["date_response_due"] 

110 if date_input < date.today(): 

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

112 

113 return date_input 

114 

115 def clean_date_review_due(self) -> date: 

116 date_input = self.cleaned_data["date_review_due"] 

117 if date_input < date.today(): 

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

119 

120 return date_input 

121 

122 def clean_reviewer_email(self) -> str: 

123 email = self.cleaned_data["reviewer_email"] 

124 if not email: 

125 return email 

126 if ( 

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

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

129 ): 

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

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

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

133 return email 

134 

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

136 cleaned_data = super().clean() 

137 

138 if self.errors: 

139 return cleaned_data 

140 

141 # Check dates validity 

142 response_date = cleaned_data["date_response_due"] 

143 review_date = cleaned_data["date_review_due"] 

144 if response_date > review_date: 

145 raise ValidationError( 

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

147 ) 

148 

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

150 suggested_user = cleaned_data["suggested_user"] 

151 reviewer_select = cleaned_data["reviewer_select"] 

152 first_name = cleaned_data["reviewer_first_name"] 

153 last_name = cleaned_data["reviewer_last_name"] 

154 email = cleaned_data["reviewer_email"] 

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

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

157 ): 

158 raise ValidationError( 

159 _( 

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

161 ) 

162 ) 

163 

164 quick = cleaned_data["quick"] 

165 if quick: 

166 quick_request_email = cleaned_data["quick_request_email"] 

167 quick_request_email_subject = cleaned_data["quick_request_email_subject"] 

168 

169 if quick_request_email == "" or quick_request_email_subject == "": 

170 raise ValidationError(_("You must fill the email subject and content")) 

171 else: 

172 request_email = cleaned_data["request_email"] 

173 request_email_subject = cleaned_data["request_email_subject"] 

174 

175 if request_email == "" or request_email_subject == "": 

176 raise ValidationError(_("You must fill the email subject and content")) 

177 

178 return cleaned_data 

179 

180 

181class ReviewAcceptForm(MeshFormMixin, forms.ModelForm): 

182 """ 

183 Accept/decline form for the reviewer. 

184 """ 

185 

186 custom_display = "block" 

187 

188 date_response_due_display = forms.DateField( 

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

190 ) 

191 date_review_due_display = forms.DateField( 

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

193 ) 

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

195 

196 class Meta: 

197 model = Review 

198 fields = [ 

199 "date_response_due_display", 

200 "date_review_due_display", 

201 "accept_comment", 

202 ] 

203 

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

205 """ 

206 Set date fields input type. 

207 """ 

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

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

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

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

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

213 

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

215 cleaned_data = super().clean() 

216 return cleaned_data 

217 

218 

219class ReviewDeclineForm(MeshFormMixin, forms.ModelForm): 

220 """ 

221 Accept/decline form for the reviewer. 

222 """ 

223 

224 custom_display = "block" 

225 

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

227 

228 class Meta: 

229 model = Review 

230 fields = [ 

231 "accept_comment", 

232 ] 

233 

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

235 """ 

236 Set date fields input type. 

237 """ 

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

239 

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

241 cleaned_data = super().clean() 

242 if not cleaned_data["accept_comment"]: 

243 raise ValidationError( 

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

245 ) 

246 return cleaned_data 

247 

248 

249class ReviewSubmitForm(SubmittableModelForm, FileModelForm): 

250 """ 

251 Form used to submit/complete a review. 

252 """ 

253 

254 custom_display = "block" 

255 

256 date_review_due_display = forms.DateField( 

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

258 ) 

259 additional_files = FileField( 

260 required=True, 

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

262 allow_multiple_selected=True, 

263 model_class=ReviewAdditionalFile, 

264 related_name="additional_files", 

265 ) 

266 comment = CKEditorFormField( 

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

268 ) 

269 

270 class Meta: 

271 model = Review 

272 fields = [ 

273 "date_review_due_display", 

274 "recommendation", 

275 "additional_files", 

276 "comment", 

277 ] 

278 

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

280 """ 

281 Sets the date fields input type. 

282 """ 

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

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

285 

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

287 cleaned_data = super().clean() 

288 

289 if cleaned_data["recommendation"] is None: 

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

291 

292 return cleaned_data 

293 

294 

295class ReviewConfirmForm(MeshFormMixin, forms.Form): 

296 custom_display = "inline" 

297 

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

299 

300 

301class ReviewAutoCreateForm(MeshFormMixin, forms.ModelForm): 

302 """ 

303 Form used to create new reviews, based on the previous round 

304 Reviewers who submitted a "positive" review (Accept ou Revision request) are auto assigned (no acceptance deadline) 

305 """ 

306 

307 custom_display = "block" 

308 

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

310 request_email_subject = forms.CharField( 

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

312 ) 

313 

314 class Meta: 

315 model = Review 

316 fields = [ 

317 "request_email_subject", 

318 "request_email", 

319 "date_review_due", 

320 ] 

321 

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

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

324 

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

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

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

328 

329 def clean_date_review_due(self) -> date: 

330 date_input = self.cleaned_data["date_review_due"] 

331 if date_input < date.today(): 

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

333 

334 return date_input