Files
2026-06-29 09:35:12 +01:00

203 lines
8.7 KiB
Python

import datetime
from django import forms
from django.forms import ModelMultipleChoiceField
from django.contrib.auth.models import User
from generic.widgets import UserSearchWidget
from .models import Survey, SurveyQuestion, SurveyResponse, SurveyAnswer
class SurveyForm(forms.ModelForm):
author = ModelMultipleChoiceField(
queryset=User.objects.all(),
widget=UserSearchWidget(),
required=False,
help_text="Users who can view and manage this survey when it is set to private."
)
class Meta:
model = Survey
fields = [
"name",
"description",
"before_text",
"after_text",
"active",
"anonymous",
"authors_only",
"author"
]
widgets = {
"description": forms.Textarea(attrs={"rows": 3}),
"before_text": forms.Textarea(attrs={"rows": 3, "placeholder": "Instructions shown before the survey starts..."}),
"after_text": forms.Textarea(attrs={"rows": 3, "placeholder": "Thank you message shown after submission..."}),
}
class SurveyQuestionForm(forms.ModelForm):
# Form-only fields for validation rules
min_value = forms.IntegerField(required=False, label="Minimum Value (Integer)")
max_value = forms.IntegerField(required=False, label="Maximum Value (Integer)")
min_date = forms.DateField(required=False, widget=forms.DateInput(attrs={"type": "date"}), label="Minimum Date")
max_date = forms.DateField(required=False, widget=forms.DateInput(attrs={"type": "date"}), label="Maximum Date")
class Meta:
model = SurveyQuestion
fields = ["text", "question_type", "choices", "required", "position"]
widgets = {
"choices": forms.Textarea(attrs={"rows": 4, "placeholder": "Enter each choice on a new line"}),
}
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# Populate initial values for validation fields from validation_rules dict
if self.instance and self.instance.pk and self.instance.validation_rules:
rules = self.instance.validation_rules
self.fields["min_value"].initial = rules.get("min_value")
self.fields["max_value"].initial = rules.get("max_value")
min_date_val = rules.get("min_date")
if min_date_val:
try:
self.fields["min_date"].initial = datetime.date.fromisoformat(min_date_val)
except ValueError:
pass
max_date_val = rules.get("max_date")
if max_date_val:
try:
self.fields["max_date"].initial = datetime.date.fromisoformat(max_date_val)
except ValueError:
pass
def save(self, commit=True):
instance = super().save(commit=False)
# Build validation_rules dict
rules = {}
min_val = self.cleaned_data.get("min_value")
max_val = self.cleaned_data.get("max_value")
min_dt = self.cleaned_data.get("min_date")
max_dt = self.cleaned_data.get("max_date")
if min_val is not None:
rules["min_value"] = min_val
if max_val is not None:
rules["max_value"] = max_val
if min_dt is not None:
rules["min_date"] = min_dt.isoformat()
if max_dt is not None:
rules["max_date"] = max_dt.isoformat()
instance.validation_rules = rules
if commit:
instance.save()
return instance
class SurveyTakeForm(forms.Form):
def __init__(self, *args, **kwargs):
self.survey = kwargs.pop("survey")
self.questions = list(self.survey.questions.all())
super().__init__(*args, **kwargs)
for q in self.questions:
field_name = f"question_{q.pk}"
if q.question_type == SurveyQuestion.QuestionType.TEXT:
self.fields[field_name] = forms.CharField(
label=q.text,
required=q.required,
widget=forms.Textarea(attrs={"rows": 3, "class": "form-control"})
)
elif q.question_type == SurveyQuestion.QuestionType.CHOICE:
choices = [(c, c) for c in q.get_choices_list()]
self.fields[field_name] = forms.ChoiceField(
label=q.text,
required=q.required,
choices=[("", "Select an option")] + choices,
widget=forms.Select(attrs={"class": "form-select"})
)
elif q.question_type == SurveyQuestion.QuestionType.RATING:
self.fields[field_name] = forms.ChoiceField(
label=q.text,
required=q.required,
choices=[(str(i), str(i)) for i in range(1, 6)],
widget=forms.RadioSelect(attrs={"class": "form-check-input"}),
)
elif q.question_type == SurveyQuestion.QuestionType.INTEGER:
rules = q.validation_rules or {}
self.fields[field_name] = forms.IntegerField(
label=q.text,
required=q.required,
min_value=rules.get("min_value"),
max_value=rules.get("max_value"),
widget=forms.NumberInput(attrs={"class": "form-control"})
)
elif q.question_type == SurveyQuestion.QuestionType.DATE:
rules = q.validation_rules or {}
attrs = {"type": "date", "class": "form-control"}
if rules.get("min_date"):
attrs["min"] = rules["min_date"]
if rules.get("max_date"):
attrs["max"] = rules["max_date"]
self.fields[field_name] = forms.DateField(
label=q.text,
required=q.required,
widget=forms.DateInput(attrs=attrs)
)
def clean(self):
cleaned_data = super().clean()
for q in self.questions:
field_name = f"question_{q.pk}"
val = cleaned_data.get(field_name)
if val is not None and val != "":
rules = q.validation_rules or {}
if q.question_type == SurveyQuestion.QuestionType.INTEGER:
min_val = rules.get("min_value")
max_val = rules.get("max_value")
if min_val is not None and val < min_val:
self.add_error(field_name, f"Value must be at least {min_val}.")
if max_val is not None and val > max_val:
self.add_error(field_name, f"Value cannot exceed {max_val}.")
elif q.question_type == SurveyQuestion.QuestionType.DATE:
min_dt_str = rules.get("min_date")
max_dt_str = rules.get("max_date")
if min_dt_str:
min_dt = datetime.date.fromisoformat(min_dt_str)
if val < min_dt:
self.add_error(field_name, f"Date cannot be before {min_dt_str}.")
if max_dt_str:
max_dt = datetime.date.fromisoformat(max_dt_str)
if val > max_dt:
self.add_error(field_name, f"Date cannot be after {max_dt_str}.")
return cleaned_data
def save(self, user=None, cid=None, content_type=None, object_id=None, pre_or_post='GENERAL'):
response_user = None if self.survey.anonymous else user
response_cid = None if self.survey.anonymous else cid
response = SurveyResponse.objects.create(
survey=self.survey,
user=response_user,
cid=response_cid,
content_type=content_type,
object_id=object_id,
pre_or_post=pre_or_post
)
for q in self.questions:
field_name = f"question_{q.pk}"
val = self.cleaned_data.get(field_name)
if val is not None and val != "":
ans = SurveyAnswer(response=response, question=q)
if q.question_type == SurveyQuestion.QuestionType.TEXT:
ans.text_answer = val
elif q.question_type == SurveyQuestion.QuestionType.CHOICE:
ans.choice_answer = val
elif q.question_type == SurveyQuestion.QuestionType.RATING:
ans.rating_answer = int(val)
elif q.question_type == SurveyQuestion.QuestionType.INTEGER:
ans.integer_answer = int(val)
elif q.question_type == SurveyQuestion.QuestionType.DATE:
ans.date_answer = val
ans.save()
return response