from django.db import models from django.contrib.auth.models import User from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.fields import GenericForeignKey from django.urls import reverse from generic.mixins import AuthorMixin class Survey(models.Model, AuthorMixin): name = models.CharField(max_length=200, help_text="Title of the survey") description = models.TextField(blank=True, help_text="Introductory text or description") before_text = models.TextField(blank=True, help_text="Text shown to the user before they start the survey") after_text = models.TextField(blank=True, help_text="Text shown to the user after they complete the survey") created_by = models.ForeignKey( User, on_delete=models.SET_NULL, null=True, blank=True, related_name="created_surveys" ) author = models.ManyToManyField( User, blank=True, related_name="surveys", help_text="Author/Manager(s) of the survey" ) created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) active = models.BooleanField( default=True, help_text="If inactive, users will not be able to fill out the survey." ) anonymous = models.BooleanField( default=False, help_text="If checked, responses will not link to the user account or candidate details." ) authors_only = models.BooleanField( default=False, help_text="If true, only survey authors will be able to view or manage this survey." ) class Meta: ordering = ["-created_at"] def __str__(self): return self.name def get_absolute_url(self): return reverse("survey:survey_detail", kwargs={"pk": self.pk}) class SurveyQuestion(models.Model): class QuestionType(models.TextChoices): TEXT = "TEXT", "Free Text" CHOICE = "CHOICE", "Multiple Choice (Single Option)" RATING = "RATING", "Rating (1-5 Likert scale)" INTEGER = "INTEGER", "Integer / Number" DATE = "DATE", "Date" survey = models.ForeignKey( Survey, on_delete=models.CASCADE, related_name="questions" ) text = models.TextField(help_text="The question text to display to users") question_type = models.CharField( max_length=20, choices=QuestionType.choices, default=QuestionType.TEXT ) choices = models.TextField( blank=True, help_text="For Multiple Choice questions, enter options separated by a new line." ) required = models.BooleanField( default=True, help_text="If checked, users must answer this question to submit." ) position = models.IntegerField( default=0, help_text="Ordering position of the question." ) validation_rules = models.JSONField( default=dict, blank=True, null=True, help_text="Extensible validation rules in JSON format (e.g. min_value, max_value, min_date, max_date)" ) class Meta: ordering = ["position", "id"] def __str__(self): return f"{self.survey.name} - Question {self.position}: {self.text[:50]}" def get_choices_list(self): if not self.choices: return [] return [c.strip() for c in self.choices.split("\n") if c.strip()] class SurveyResponse(models.Model): class SurveyContext(models.TextChoices): PRE = "PRE", "Pre-take survey" POST = "POST", "Post-take survey" GENERAL = "GENERAL", "General feedback" survey = models.ForeignKey( Survey, on_delete=models.CASCADE, related_name="responses" ) user = models.ForeignKey( User, on_delete=models.SET_NULL, null=True, blank=True, related_name="survey_responses" ) cid = models.IntegerField( null=True, blank=True, help_text="Candidate ID if taken by an anonymous candidate login." ) # Generic relationship to associate the response with a CaseCollection or Exam content_type = models.ForeignKey( ContentType, on_delete=models.SET_NULL, null=True, blank=True ) object_id = models.PositiveIntegerField(null=True, blank=True) content_object = GenericForeignKey("content_type", "object_id") pre_or_post = models.CharField( max_length=10, choices=SurveyContext.choices, default=SurveyContext.GENERAL ) created_at = models.DateTimeField(auto_now_add=True) class Meta: ordering = ["-created_at"] def __str__(self): user_str = self.user.username if self.user else (f"CID {self.cid}" if self.cid else "Anonymous") return f"Response for {self.survey.name} by {user_str} ({self.pre_or_post})" class SurveyAnswer(models.Model): response = models.ForeignKey( SurveyResponse, on_delete=models.CASCADE, related_name="answers" ) question = models.ForeignKey( SurveyQuestion, on_delete=models.CASCADE, related_name="answers" ) text_answer = models.TextField(blank=True, null=True) choice_answer = models.CharField(max_length=255, blank=True, null=True) rating_answer = models.IntegerField(blank=True, null=True) integer_answer = models.IntegerField(blank=True, null=True) date_answer = models.DateField(blank=True, null=True) def __str__(self): return f"Answer to {self.question.id} for response {self.response.id}"