203 lines
8.7 KiB
Python
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
|