Coverage for src/mesh/views/forms/review_forms.py: 42%
130 statements
« prev ^ index » next coverage.py v7.8.2, created at 2025-06-03 13:52 +0000
« prev ^ index » next coverage.py v7.8.2, created at 2025-06-03 13:52 +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=True, label=_("E-mail"))
35 request_email_subject = forms.CharField(
36 label=_("E-mail subject"), max_length=256, required=True
37 )
38 suggested_user = forms.ChoiceField(choices=[])
39 reviewer_first_name = forms.CharField(
40 label=_("Reviewer - First name"),
41 max_length=150,
42 required=False,
43 help_text=_("Leave empty for an existing user"),
44 )
45 reviewer_last_name = forms.CharField(
46 label=_("Reviewer - Last name"),
47 max_length=150,
48 required=False,
49 help_text=_("Leave empty for an existing user"),
50 )
51 reviewer_email = forms.EmailField(
52 label=_("Reviewer - E-mail address"),
53 required=False,
54 help_text=_("Leave empty for an existing user"),
55 )
57 class Meta:
58 model = Review
59 fields = [
60 "suggested_user",
61 "reviewer_first_name",
62 "reviewer_last_name",
63 "reviewer_email",
64 "request_email_subject",
65 "request_email",
66 "date_response_due",
67 "date_review_due",
68 ]
70 def __init__(self, *args, **kwargs):
71 # self.suggestions = kwargs.pop("_suggestions")
72 # self.existing_reviewers = kwargs.pop("_existing_reviewers")
73 choices = kwargs.pop("_choices")
74 self.version = kwargs.pop("_version")
75 super().__init__(*args, **kwargs)
76 select_field = self.fields["suggested_user"]
77 select_field.choices = choices
78 # select_field.choices = get_suggested_users_choices(self.suggestions, self.existing_reviewers)
79 select_field.required = False
80 # select_field.widget.attrs["size"] = 10
81 # select_field.widget.attrs["class"] = "form-select form-select-lg"
82 select_field.empty_label = None
84 # reviewer_field.help_text = _("Existing user")
85 # reviewer_field = self.fields["reviewer"]
86 # reviewer_field.queryset = reviewers
87 # reviewer_field.required = False
88 # reviewer_field.help_text = _("Existing user")
89 # reviewer_field.separator_after_or = True
90 # reviewer_field.widget.attrs["size"] = 10
91 # reviewer_field.widget.attrs["class"] = "form-select form-select-lg"
92 # reviewer_field.empty_label = None
93 self.fields["reviewer_email"].separator_after = True
94 self.fields["date_response_due"].widget.input_type = "date"
95 self.fields["date_response_due"].widget.attrs["class"] = "form-control form-control-lg"
96 self.fields["date_review_due"].widget.input_type = "date"
97 self.fields["date_review_due"].widget.attrs["class"] = "form-control form-control-lg"
98 self.fields["request_email"].widget.attrs["class"] = "form-control"
100 def clean_date_response_due(self) -> date:
101 date_input = self.cleaned_data["date_response_due"]
102 if date_input < date.today():
103 raise ValidationError(_("The response due date must be later than today."))
105 return date_input
107 def clean_date_review_due(self) -> date:
108 date_input = self.cleaned_data["date_review_due"]
109 if date_input < date.today():
110 raise ValidationError(_("The response due date must be later than today."))
112 return date_input
114 def clean_reviewer_email(self) -> str:
115 email = self.cleaned_data["reviewer_email"]
116 if not email:
117 return email
118 if (
119 EmailAddress.objects.filter(email=email).exists()
120 or UserModel.objects.filter(email=email).exists()
121 ):
122 raise ValidationError(_("The given e-mail address is already used."))
123 if self.version.reviews.filter(reviewer__email=email).exists():
124 raise ValidationError(_("The user is already assigned for a review."))
125 return email
127 def clean(self) -> dict[str, Any]:
128 cleaned_data = super().clean()
129 if self.errors:
130 return cleaned_data
132 # Check dates validity
133 response_date = cleaned_data["date_response_due"]
134 review_date = cleaned_data["date_review_due"]
135 if response_date > review_date:
136 raise ValidationError(
137 _("The response due date must be earlier than the review due date.")
138 )
140 # Check reviewer validity (either existing user or name + email fields)
141 suggested_user = cleaned_data["suggested_user"]
142 reviewer_select = cleaned_data["reviewer_select"]
143 first_name = cleaned_data["reviewer_first_name"]
144 last_name = cleaned_data["reviewer_last_name"]
145 email = cleaned_data["reviewer_email"]
146 if (reviewer_select == "shortlist" and not suggested_user) or (
147 reviewer_select == "new" and not (first_name and last_name and email)
148 ):
149 raise ValidationError(
150 _(
151 "You must either pick an existing user OR fill the first name, last name and e-mail fields."
152 )
153 )
155 return cleaned_data
158class ReviewAcceptForm(MeshFormMixin, forms.ModelForm):
159 """
160 Accept/decline form for the reviewer.
161 """
163 custom_display = "block"
165 date_response_due_display = forms.DateField(
166 label=_("Response due date"), disabled=True, required=False
167 )
168 date_review_due_display = forms.DateField(
169 label=_("Review due date"), disabled=True, required=False
170 )
171 accept_comment = CKEditorFormField(DEFAULT_CKEDITOR_CONFIG, required=False, label=_("Comment"))
173 class Meta:
174 model = Review
175 fields = [
176 "date_response_due_display",
177 "date_review_due_display",
178 "accepted",
179 "accept_comment",
180 ]
182 def __init__(self, *args, **kwargs):
183 """
184 Set date fields input type.
185 """
186 super().__init__(*args, **kwargs)
187 self.fields["date_response_due_display"].widget.input_type = "date"
188 # self.fields["date_response_due_display"].custom_display = "inline"
189 self.fields["date_review_due_display"].widget.input_type = "date"
190 # self.fields["date_review_due_display"].custom_display = "inline"
192 def clean(self) -> dict[str, Any]:
193 cleaned_data = super().clean()
194 if cleaned_data["accepted"] is False and not cleaned_data["accept_comment"]:
195 raise ValidationError(
196 _("You must fill the comment field when declining the review request.")
197 )
198 return cleaned_data
201class ReviewSubmitForm(SubmittableModelForm, FileModelForm):
202 """
203 Form used to submit/complete a review.
204 """
206 custom_display = "block"
208 date_review_due_display = forms.DateField(
209 label=_("Review due date"), disabled=True, required=False
210 )
211 additional_files = FileField(
212 required=True,
213 label=_("Review file(s)"),
214 allow_multiple_selected=True,
215 model_class=ReviewAdditionalFile,
216 related_name="additional_files",
217 )
218 comment = CKEditorFormField(
219 DEFAULT_CKEDITOR_CONFIG, label=_("Comment / Message"), required=False
220 )
222 class Meta:
223 model = Review
224 fields = [
225 "date_review_due_display",
226 "recommendation",
227 "additional_files",
228 "comment",
229 ]
231 def __init__(self, *args, **kwargs) -> None:
232 """
233 Sets the date fields input type.
234 """
235 super().__init__(*args, **kwargs)
236 self.fields["date_review_due_display"].widget.input_type = "date"
238 def clean(self) -> dict[str, Any]:
239 cleaned_data = super().clean()
241 if cleaned_data["recommendation"] is None:
242 raise ValidationError(_("You must choose a recommendation value."))
244 return cleaned_data
247class ReviewConfirmForm(MeshFormMixin, forms.Form):
248 custom_display = "inline"
250 confirm = forms.BooleanField(label=_("Confirm"), required=True)
253class ReviewAutoCreateForm(MeshFormMixin, forms.ModelForm):
254 """
255 Form used to create new reviews, based on the previous round
256 Reviewers who submitted a "positive" review (Accept ou Revision request) are auto assigned (no acceptance deadline)
257 """
259 custom_display = "block"
261 request_email = CKEditorFormField(EMAIL_CKEDITOR_CONFIG, required=True, label=_("E-mail"))
262 request_email_subject = forms.CharField(
263 label=_("E-mail subject"), max_length=256, required=True
264 )
266 class Meta:
267 model = Review
268 fields = [
269 "request_email_subject",
270 "request_email",
271 "date_review_due",
272 ]
274 def __init__(self, *args, **kwargs):
275 super().__init__(*args, **kwargs)
277 self.fields["date_review_due"].widget.input_type = "date"
278 self.fields["date_review_due"].widget.attrs["class"] = "form-control form-control-lg"
279 self.fields["request_email"].widget.attrs["class"] = "form-control"
281 def clean_date_review_due(self) -> date:
282 date_input = self.cleaned_data["date_review_due"]
283 if date_input < date.today():
284 raise ValidationError(_("The response due date must be later than today."))
286 return date_input