From 527aaa41c75742f43b4d5d7f6952dc9130e0d69e Mon Sep 17 00:00:00 2001 From: borzechof99 <borzechof99@mi.fu-berlin.de> Date: Sun, 27 Jun 2021 16:23:58 +0200 Subject: [PATCH] Add QuestionOrder-Model, Tests, and Seeding --- .../quiz/management/commands/seed_db.py | 10 ++ .../quiz/migrations/0007_questionorder.py | 22 +++ unisportomat/quiz/models.py | 93 +++++++++++ unisportomat/quiz/tests.py | 151 ++++++++++++++++++ 4 files changed, 276 insertions(+) create mode 100644 unisportomat/quiz/migrations/0007_questionorder.py diff --git a/unisportomat/quiz/management/commands/seed_db.py b/unisportomat/quiz/management/commands/seed_db.py index ded2a91..f74da5c 100644 --- a/unisportomat/quiz/management/commands/seed_db.py +++ b/unisportomat/quiz/management/commands/seed_db.py @@ -15,6 +15,7 @@ from quiz.models import ( Question, CallToMove, KnowledgeSnack, + QuestionOrder, ) @@ -171,3 +172,12 @@ class Command(BaseCommand): activate("en") k_s.text = text[1] k_s.save() + + # Create Entries in the QuestionOrder DB + + QuestionOrder.objects.create_entry_at_end("question", 1) + QuestionOrder.objects.create_entry_at_end("snack") + QuestionOrder.objects.create_entry_at_end("question", 3) + QuestionOrder.objects.create_entry_at_end("activity") + QuestionOrder.objects.create_entry_at_end("question", 2) + QuestionOrder.objects.create_entry_at_end("question", 4) diff --git a/unisportomat/quiz/migrations/0007_questionorder.py b/unisportomat/quiz/migrations/0007_questionorder.py new file mode 100644 index 0000000..e4b79e3 --- /dev/null +++ b/unisportomat/quiz/migrations/0007_questionorder.py @@ -0,0 +1,22 @@ +# Generated by Django 3.2 on 2021-06-27 14:00 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('quiz', '0006_auto_20210612_1230'), + ] + + operations = [ + migrations.CreateModel( + name='QuestionOrder', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('order_id', models.IntegerField(null=True)), + ('type_of_slot', models.TextField(choices=[('question', 'question'), ('snack', 'snack'), ('activity', 'activity')], default='snack')), + ('question_id', models.IntegerField(default=-1)), + ], + ), + ] diff --git a/unisportomat/quiz/models.py b/unisportomat/quiz/models.py index d582994..5b7041c 100644 --- a/unisportomat/quiz/models.py +++ b/unisportomat/quiz/models.py @@ -92,3 +92,96 @@ class Question(models.Model): def __str__(self): return self.text + + +class QuestionOrderManager(models.Manager): + """ + Manages Handling of QuestionOrder Objects + Includes create_entry_at_end, which handles Creation of new Entries + and delete_entry, which deletes one entry and changes the order_id of the following entries + """ + + def create_entry_at_end(self, type_of_slot, question_id=None): + + if type_of_slot == "question" and question_id is None: + raise ValueError( + "A Question ID must be given if the Type of the Slot is Question" + ) + elif type_of_slot not in ["question", "snack", "activity"]: + raise ValueError( + f'{type_of_slot} not in valid choice list ["question", "snack", "activity"]' + ) + + entry = QuestionOrder() + + entry.type_of_slot = type_of_slot + + # If question_id is None, fill it out as -1, else take the question_id value + if entry.type_of_slot == "question": + entry.question_id = question_id + + # If no Entry exists, highest_current_order is 0, otherwise highest order_id + if self.count() == 0: + highest_current_order = 0 + else: + # "latest" returns highest value (normally used for dates, I believe) + highest_current_order = self.latest("order_id").order_id + + entry.order_id = highest_current_order + 1 + + entry.save() + + return entry + + def delete_entry(self, given_order_id): + + # Delete must be called on an instance, not a queryset + entry = self.get(order_id = given_order_id) + entry.delete() + + + larger_entries = self.filter(order_id__gte=given_order_id) + + # The Primary Key of an Object cannot be changed, really, instead a new Object is created when the PK changes + # This means that we need to delete the original Object so that we only have the new pk + + for entry in larger_entries.iterator(): + entry.order_id = entry.order_id - 1 + entry.save() + + def delete_entry_by_question_id(self, given_question_id): + + + queryset = self.filter(question_id = given_question_id) + + if queryset.count() == 0: + # If the Question doesn't exist in the Order, we don't need to do anything + return + + # If the question is in the Order more than once for some reason, sweat not, we will delete them all + for entry in queryset.iterator(): + self.delete_entry(entry.order_id) + + +class QuestionOrder(models.Model): + """ + Defines the order of the Questions, Snacks, and Activities given in the Quiz + Default Choice for type_of_slot is "snack" instead of "question", + because "snack" doesn't need a question_id and is therefore a safer choice + """ + + objects = QuestionOrderManager() + + order_id = models.IntegerField(null=True) + type_of_slot = models.TextField( + choices=[ + ("question", "question"), + ("snack", "snack"), + ("activity", "activity"), + ], + default="snack", + ) + question_id = models.IntegerField(default=-1) + + def __str__(self): + return f"Entry {self.order_id}: {self.type_of_slot}" diff --git a/unisportomat/quiz/tests.py b/unisportomat/quiz/tests.py index 542d2c5..0a236a5 100644 --- a/unisportomat/quiz/tests.py +++ b/unisportomat/quiz/tests.py @@ -10,6 +10,7 @@ from django.utils.translation import get_language, activate from django.test import TestCase, override_settings from django.conf import settings from .models import ( + QuestionOrder, Sport, Criterion, CriterionRating, @@ -379,3 +380,153 @@ class Modeltranslation_Two_Languages_Test(TestCase): self.assertEqual(self.question.text, self.question.text_en) self.assertNotEqual(self.question.text, self.question.text_de) + + +class QuestionOrder_Test(TestCase): + """ + Tests the QuestionOrder Model and its Manager + """ + + def setUp(self): + """ + Sets up DB with Five Default Questions from seed_db + """ + + # Setup the Database with seeded values + call_command("seed_db", ["--yes", "--no-superuser"]) + + # We get Five Questions, that's all we need + # Also, we assume that we are working with an empty QuestionOrder Table + QuestionOrder.objects.all().delete() + + def test_check_empty_oder_table(self): + """ + Checks Whether Initial QuestionOrder Table is Empty + """ + + self.assertEqual(QuestionOrder.objects.count(), 0) + + def test_add_question_to_table(self): + """ + Tests adding a Question to the Table + """ + + entry = QuestionOrder.objects.create_entry_at_end("question", question_id=2) + + self.assertEqual(QuestionOrder.objects.count(), 1) + self.assertEqual(entry.order_id, 1) + self.assertEqual(entry.type_of_slot, "question") + self.assertEqual(entry.question_id, 2) + + def test_add_snack_activity_to_table(self): + """ + Tests adding a Snack and Activity to the Table + """ + + entry = QuestionOrder.objects.create_entry_at_end("snack") + + self.assertEqual(QuestionOrder.objects.count(), 1) + self.assertEqual(entry.order_id, 1) + self.assertEqual(entry.type_of_slot, "snack") + self.assertEqual(entry.question_id, -1) + + # Add Question_ID to activity to see whether it is correctly ignored + entry = QuestionOrder.objects.create_entry_at_end("activity", question_id=3) + + self.assertEqual(QuestionOrder.objects.count(), 2) + self.assertEqual(entry.order_id, 2) + self.assertEqual(entry.type_of_slot, "activity") + self.assertEqual(entry.question_id, -1) + + def test_autoincrementing_id(self): + """ + When Entries are deleted, do the automatically generated PK-Fields reuse the IDs or do they stil increment? + """ + + # Create First and Second Entry + entry = QuestionOrder.objects.create_entry_at_end("question", question_id=1) + self.assertEqual(QuestionOrder.objects.count(), 1) + self.assertEqual(entry.order_id, 1) + + entry = QuestionOrder.objects.create_entry_at_end("question", question_id=2) + self.assertEqual(QuestionOrder.objects.count(), 2) + self.assertEqual(entry.order_id, 2) + + # Check Second Entry + most_recent_entry = QuestionOrder.objects.get(order_id=2) + self.assertEqual(most_recent_entry.order_id, 2) + + # Delete Second Entry + QuestionOrder.objects.delete_entry(2) + self.assertEqual(QuestionOrder.objects.count(), 1) + + # Create Third Entry and Check whether the ID of the Second Entry is Reused (it should be) + entry = QuestionOrder.objects.create_entry_at_end("question", question_id=3) + self.assertEqual(QuestionOrder.objects.count(), 2) + self.assertEqual(entry.order_id, 2) + + def test_removing_entry_in_middle(self): + """ + When an Entry in the Middle is Removed, the order_ids larger than the removed entry should be decremented + """ + # Create Three Questions + first_entry = QuestionOrder.objects.create_entry_at_end( + "question", question_id=1 + ) + self.assertEqual(QuestionOrder.objects.count(), 1) + self.assertEqual(first_entry.order_id, 1) + + second_entry = QuestionOrder.objects.create_entry_at_end( + "question", question_id=2 + ) + self.assertEqual(QuestionOrder.objects.count(), 2) + self.assertEqual(second_entry.order_id, 2) + + third_entry = QuestionOrder.objects.create_entry_at_end( + "question", question_id=3 + ) + self.assertEqual(QuestionOrder.objects.count(), 3) + self.assertEqual(third_entry.order_id, 3) + + # Use Custom Deletion Method to remove second Entry + QuestionOrder.objects.delete_entry(2) + + # Check whether Question Higher than the deleted one went down one slot + self.assertEqual(QuestionOrder.objects.count(), 2) + + last_entry = QuestionOrder.objects.last() + self.assertEqual(last_entry.order_id, 2) + self.assertEqual(last_entry.question_id, 3) + + def test_removing_entry_in_middle_by_question_id(self): + """ + When an Entry in the Middle is Removed, the order_ids larger than the removed entry should be decremented + """ + # Create Three Questions + first_entry = QuestionOrder.objects.create_entry_at_end( + "question", question_id=1 + ) + self.assertEqual(QuestionOrder.objects.count(), 1) + self.assertEqual(first_entry.order_id, 1) + + second_entry = QuestionOrder.objects.create_entry_at_end( + "question", question_id=2 + ) + self.assertEqual(QuestionOrder.objects.count(), 2) + self.assertEqual(second_entry.order_id, 2) + + third_entry = QuestionOrder.objects.create_entry_at_end( + "question", question_id=3 + ) + self.assertEqual(QuestionOrder.objects.count(), 3) + self.assertEqual(third_entry.order_id, 3) + + # Use Custom Deletion Method to remove second Entry + QuestionOrder.objects.delete_entry_by_question_id(2) + + # Check whether Question Higher than the deleted one went down one slot + self.assertEqual(QuestionOrder.objects.count(), 2) + + last_entry = QuestionOrder.objects.last() + self.assertEqual(last_entry.order_id, 2) + self.assertEqual(last_entry.question_id, 3) -- GitLab