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
« prev ^ index » next coverage.py v7.7.0, created at 2025-04-28 07:45 +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()
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
38class ReviewCreateForm(MeshFormMixin, forms.ModelForm):
39 """
40 Form used to create a new review, ie. request a new review from another user.
41 """
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 )
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 ]
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
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"
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."))
118 return date_input
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."))
125 return date_input
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
140 def clean(self) -> dict[str, Any]:
141 cleaned_data = super().clean()
142 if self.errors:
143 return cleaned_data
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 )
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 )
168 return cleaned_data
171class ReviewAcceptForm(MeshFormMixin, forms.ModelForm):
172 """
173 Accept/decline form for the reviewer.
174 """
176 custom_display = "block"
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"))
186 class Meta:
187 model = Review
188 fields = [
189 "date_response_due_display",
190 "date_review_due_display",
191 "accepted",
192 "accept_comment",
193 ]
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"
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
214class ReviewSubmitForm(SubmittableModelForm, FileModelForm):
215 """
216 Form used to submit/complete a review.
217 """
219 custom_display = "block"
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 )
235 class Meta:
236 model = Review
237 fields = [
238 "date_review_due_display",
239 "recommendation",
240 "additional_files",
241 "comment",
242 ]
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"
251 def clean(self) -> dict[str, Any]:
252 cleaned_data = super().clean()
254 if cleaned_data["recommendation"] is None:
255 raise ValidationError(_("You must choose a recommendation value."))
257 return cleaned_data
260class ReviewConfirmForm(MeshFormMixin, forms.Form):
261 custom_display = "inline"
263 confirm = forms.BooleanField(label=_("Confirm"), required=True)