diff --git a/decide/store/views.py b/decide/store/views.py index d279d87e5..016bbd39c 100644 --- a/decide/store/views.py +++ b/decide/store/views.py @@ -76,7 +76,10 @@ def post(self, request): return Response({}, status=status.HTTP_401_UNAUTHORIZED) with transaction.atomic(): - if voting[0]["question"]["question_type"] == "S": + if ( + voting[0]["question"]["question_type"] == "S" + or voting[0]["question"]["question_type"] == "B" + ): v, _ = Vote.objects.get_or_create(voting_id=vid, voter_id=uid) # Delete previous options VoteOption.objects.filter(vote=v).delete() diff --git a/decide/voting/migrations/0011_alter_question_question_type.py b/decide/voting/migrations/0011_alter_question_question_type.py new file mode 100644 index 000000000..23f5d13b1 --- /dev/null +++ b/decide/voting/migrations/0011_alter_question_question_type.py @@ -0,0 +1,21 @@ +# Generated by Django 4.1 on 2023-12-16 13:56 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("voting", "0010_question_voteblank"), + ] + + operations = [ + migrations.AlterField( + model_name="question", + name="question_type", + field=models.CharField( + choices=[("S", "Single"), ("M", "Multiple"), ("B", "Boolean")], + default="S", + max_length=1, + ), + ), + ] diff --git a/decide/voting/models.py b/decide/voting/models.py index b2d926e4b..0da673ac4 100644 --- a/decide/voting/models.py +++ b/decide/voting/models.py @@ -4,13 +4,11 @@ from django.db.models import JSONField from django.db.models.signals import post_save from django.dispatch import receiver +from django.core.exceptions import BadRequest class Question(models.Model): - QUESTION_TYPES = ( - ("S", "Single"), - ("M", "Multiple"), - ) + QUESTION_TYPES = (("S", "Single"), ("M", "Multiple"), ("B", "Boolean")) question_type = models.CharField(max_length=1, choices=QUESTION_TYPES, default="S") desc = models.TextField() @@ -32,6 +30,43 @@ def save(self, **kwargs): ) enBlanco.save() self.options.add(enBlanco) + if self.question_type == "B": + if ( + QuestionOption.objects.filter( + question__id=self.id, option__startswith="Sí" + ).count() + == 0 + ): + op1 = QuestionOption( + question=self, number=self.options.count() + 1, option="Sí" + ) + op1.save() + self.options.add(op1) + if ( + QuestionOption.objects.filter( + question__id=self.id, option__startswith="No" + ).count() + == 0 + ): + op2 = QuestionOption( + question=self, number=self.options.count() + 1, option="No" + ) + op2.save() + self.options.add(op2) + if ( + self.voteBlank + and QuestionOption.objects.filter( + question_id=self.id, option__startswith="Voto En Blanco" + ).count() + == 0 + ): + enBlanco = QuestionOption( + question=self, + number=self.options.count() + 1, + option="Voto En Blanco", + ) + enBlanco.save() + self.options.add(enBlanco) return super().save() def __str__(self): @@ -47,6 +82,18 @@ class QuestionOption(models.Model): hidden = models.BooleanField(default=False) def save(self): + if self.question.question_type == "B" and self.question.options.count() > 1: + if self.question.voteBlank: + if ( + self.option != "Sí" + and self.option != "No" + and self.option != "Voto En Blanco" + ): + raise BadRequest( + "Boolean questions with white votes can only have 'Sí', 'No', or 'Voto En Blanco' options." + ) + else: + raise BadRequest("Boolean questions cannot have any options.") if not self.number: self.number = self.question.options.count() + 1 else: diff --git a/decide/voting/tests.py b/decide/voting/tests.py index 9bb31fdc8..e87460b4b 100644 --- a/decide/voting/tests.py +++ b/decide/voting/tests.py @@ -14,6 +14,7 @@ from selenium.webdriver.common.by import By from voting.models import Question, QuestionOption, Voting from selenium.webdriver.chrome.webdriver import WebDriver +from django.core.exceptions import BadRequest class VotingTestCase(BaseTestCase): @@ -238,6 +239,32 @@ def test_create_voting_from_api(self): response = self.client.post("/voting/", data, format="json") self.assertEqual(response.status_code, 201) + def test_create_boolean_voting_from_api(self): + data = {"name": "Example boolean"} + response = self.client.post("/voting/", data, format="json") + self.assertEqual(response.status_code, 401) + + # login with user no admin + self.login(user="noadmin") + response = mods.post("voting", params=data, response=True) + self.assertEqual(response.status_code, 403) + + # login with user admin + self.login() + response = mods.post("voting", params=data, response=True) + self.assertEqual(response.status_code, 400) + + data = { + "name": "Example boolean", + "desc": "Description example boolean", + "question_type": "B", + "question": "Do you agree?", + "question_opt": ["Sí", "No"], + } + + response = self.client.post("/voting/", data, format="json") + self.assertEqual(response.status_code, 201) + def test_update_voting(self): voting = self.create_voting() @@ -591,3 +618,57 @@ def createCensusEmptyError(self): self.cleaner.current_url == self.live_server_url + "/admin/voting/question/add/" ) + + +class BooleanQuestionTestCase(BaseTestCase): + def setUp(self): + self.question = Question.objects.create( + question_type="B", desc="What is your favorite color?", voteBlank=False + ) + self.question.save() + + def test_save_boolean_question(self): + self.assertEqual(QuestionOption.objects.count(), 2) + self.assertEqual(QuestionOption.objects.first().option, "Sí") + self.assertEqual(QuestionOption.objects.last().option, "No") + + def test_str_representation(self): + self.assertEqual(str(self.question), "What is your favorite color?") + + def test_one_option_boolean_question(self): + self.question = Question.objects.create( + question_type="B", desc="What is your favorite color?", voteBlank=False + ) + self.question.save() + + opt = QuestionOption(question=self.question, option="option {}".format(1)) + with self.assertRaises(BadRequest): + opt.save() + + def test_multiple_option_boolean_question(self): + self.question = Question.objects.create( + question_type="B", desc="What is your favorite color?", voteBlank=False + ) + self.question.save() + + opt1 = QuestionOption(question=self.question, option="option {}".format(1)) + opt2 = QuestionOption(question=self.question, option="option {}".format(2)) + opt3 = QuestionOption(question=self.question, option="option {}".format(3)) + with self.assertRaises(BadRequest): + opt1.save() + opt2.save() + opt3.save() + + +class BooleanWhiteTestCase(BaseTestCase): + def setUp(self): + self.question = Question.objects.create( + question_type="B", desc="What is your favorite color?", voteBlank=True + ) + self.question.save() + + def test_boolean_question_and_white(self): + self.assertEqual(QuestionOption.objects.count(), 3) + self.assertEqual(QuestionOption.objects.first().option, "Sí") + self.assertEqual(QuestionOption.objects.all()[1].option, "No") + self.assertEqual(QuestionOption.objects.last().option, "Voto En Blanco")