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
« prev ^ index » next coverage.py v7.9.0, created at 2025-09-10 11:20 +0000
1from __future__ import annotations
3from datetime import date
4from typing import TYPE_CHECKING, Any
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 _
13from mesh.models.review_models import Review, ReviewAdditionalFile
14from mesh.views.components.ckeditor_config import DEFAULT_CKEDITOR_CONFIG, EMAIL_CKEDITOR_CONFIG
16from .base_forms import FileModelForm, MeshFormMixin, SubmittableModelForm
17from .fields import CKEditorFormField, FileField
19if TYPE_CHECKING: 19 ↛ 20line 19 didn't jump to line 20 because the condition on line 19 was never true
20 pass
22UserModel = get_user_model()
25class ReviewCreateForm(MeshFormMixin, forms.ModelForm):
26 """
27 Form used to create a new review, ie. request a new review from another user.
28 """
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 )
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 )
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 ]
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
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"
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."))
113 return date_input
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."))
120 return date_input
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
135 def clean(self) -> dict[str, Any]:
136 cleaned_data = super().clean()
138 if self.errors:
139 return cleaned_data
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 )
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 )
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"]
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"]
175 if request_email == "" or request_email_subject == "":
176 raise ValidationError(_("You must fill the email subject and content"))
178 return cleaned_data
181class ReviewAcceptForm(MeshFormMixin, forms.ModelForm):
182 """
183 Accept/decline form for the reviewer.
184 """
186 custom_display = "block"
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"))
196 class Meta:
197 model = Review
198 fields = [
199 "date_response_due_display",
200 "date_review_due_display",
201 "accept_comment",
202 ]
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"
214 def clean(self) -> dict[str, Any]:
215 cleaned_data = super().clean()
216 return cleaned_data
219class ReviewDeclineForm(MeshFormMixin, forms.ModelForm):
220 """
221 Accept/decline form for the reviewer.
222 """
224 custom_display = "block"
226 accept_comment = CKEditorFormField(DEFAULT_CKEDITOR_CONFIG, required=False, label=_("Comment"))
228 class Meta:
229 model = Review
230 fields = [
231 "accept_comment",
232 ]
234 def __init__(self, *args, **kwargs):
235 """
236 Set date fields input type.
237 """
238 super().__init__(*args, **kwargs)
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
249class ReviewSubmitForm(SubmittableModelForm, FileModelForm):
250 """
251 Form used to submit/complete a review.
252 """
254 custom_display = "block"
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 )
270 class Meta:
271 model = Review
272 fields = [
273 "date_review_due_display",
274 "recommendation",
275 "additional_files",
276 "comment",
277 ]
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"
286 def clean(self) -> dict[str, Any]:
287 cleaned_data = super().clean()
289 if cleaned_data["recommendation"] is None:
290 raise ValidationError(_("You must choose a recommendation value."))
292 return cleaned_data
295class ReviewConfirmForm(MeshFormMixin, forms.Form):
296 custom_display = "inline"
298 confirm = forms.BooleanField(label=_("Confirm"), required=True)
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 """
307 custom_display = "block"
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 )
314 class Meta:
315 model = Review
316 fields = [
317 "request_email_subject",
318 "request_email",
319 "date_review_due",
320 ]
322 def __init__(self, *args, **kwargs):
323 super().__init__(*args, **kwargs)
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"
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."))
334 return date_input