From f7e605b5ccedd70a6b506c8624528e81c114ad58 Mon Sep 17 00:00:00 2001 From: ozer550 Date: Mon, 26 Dec 2022 22:05:20 +0530 Subject: [PATCH 1/4] Debugging --- .../tests/viewsets/test_assessmentitem.py | 49 +++++++++++++++++-- .../viewsets/assessmentitem.py | 31 +++++++++++- 2 files changed, 75 insertions(+), 5 deletions(-) diff --git a/contentcuration/contentcuration/tests/viewsets/test_assessmentitem.py b/contentcuration/contentcuration/tests/viewsets/test_assessmentitem.py index e1ffa0f9d3..766d4cad2a 100644 --- a/contentcuration/contentcuration/tests/viewsets/test_assessmentitem.py +++ b/contentcuration/contentcuration/tests/viewsets/test_assessmentitem.py @@ -1,5 +1,6 @@ from __future__ import absolute_import +import json import uuid from django.urls import reverse @@ -21,6 +22,7 @@ class SyncTestCase(SyncTestMixin, StudioAPITestCase): @property def assessmentitem_metadata(self): return { + "assessment_id": uuid.uuid4().hex, "contentnode": self.channel.main_tree.get_descendants() .filter(kind_id=content_kinds.EXERCISE) @@ -105,11 +107,14 @@ def test_create_assessmentitem_with_file_answers(self): image_file = testdata.fileobj_exercise_image() image_file.uploaded_by = self.user image_file.save() - answers = "![alt_text](${}/{}.{})".format( + answer = "![alt_text](${}/{}.{})".format( exercises.IMG_PLACEHOLDER, image_file.checksum, image_file.file_format_id ) - assessmentitem["answers"] = answers + answers = [{'answer': answer, 'correct': False, 'order': 1}] + + assessmentitem["answers"] = json.dumps(answers) + response = self.sync_changes( [ generate_create_event( @@ -120,6 +125,7 @@ def test_create_assessmentitem_with_file_answers(self): ) ], ) + print(response.data["errors"]) self.assertEqual(response.status_code, 200, response.content) try: ai = models.AssessmentItem.objects.get( @@ -139,11 +145,16 @@ def test_create_assessmentitem_with_file_hints(self): image_file = testdata.fileobj_exercise_image() image_file.uploaded_by = self.user image_file.save() - hints = "![alt_text](${}/{}.{})".format( + hint = "![alt_text](${}/{}.{})".format( exercises.IMG_PLACEHOLDER, image_file.checksum, image_file.file_format_id ) + hints = [ + {"hint": hint, "order": 1}, + ] + hints = json.dumps(hints) assessmentitem["hints"] = hints + response = self.sync_changes( [ generate_create_event( @@ -154,6 +165,8 @@ def test_create_assessmentitem_with_file_hints(self): ) ], ) + + print(response.data["errors"]) self.assertEqual(response.status_code, 200, response.content) try: ai = models.AssessmentItem.objects.get( @@ -498,6 +511,36 @@ def test_delete_assessmentitems(self): except models.AssessmentItem.DoesNotExist: pass + def test_valid_hints_assessmentitem(self): + self.client.force_authenticate(user=self.user) + assessmentitem = self.assessmentitem_metadata + hints = [ + {"hint": "Hint 1", "order": 1}, + {"hint": "Hint 2", "order": 2}, + {"hint": "Hint 3", "order": 3}, + {"hint": "Hint 4", "order": 4}, + + ] + hints = json.dumps(hints) + assessmentitem["hints"] = hints + response = self.sync_changes( + [ + generate_create_event( + [assessmentitem["contentnode"], assessmentitem["assessment_id"]], + ASSESSMENTITEM, + assessmentitem, + channel_id=self.channel.id, + ), + ], + ) + self.assertEqual(response.status_code, 200, response.content) + try: + models.AssessmentItem.objects.get( + assessment_id=assessmentitem["assessment_id"] + ) + except models.AssessmentItem.DoesNotExist: + self.fail("AssessmentItem was not created") + class CRUDTestCase(StudioAPITestCase): @property diff --git a/contentcuration/contentcuration/viewsets/assessmentitem.py b/contentcuration/contentcuration/viewsets/assessmentitem.py index 978208b5f1..6427d82d96 100644 --- a/contentcuration/contentcuration/viewsets/assessmentitem.py +++ b/contentcuration/contentcuration/viewsets/assessmentitem.py @@ -1,8 +1,10 @@ +import json import re from django.db import transaction from django_bulk_update.helper import bulk_update from le_utils.constants import exercises +from rest_framework import serializers from rest_framework.permissions import IsAuthenticated from rest_framework.serializers import ValidationError @@ -42,6 +44,10 @@ def get_filenames_from_assessment(assessment_item): # Get unique checksums in the assessment item text fields markdown # Coerce to a string, for Python 2, as the stored data is in unicode, and otherwise # the unicode char in the placeholder will not match + print("DEBUGGING AI") + print(assessment_item.question) + print(assessment_item.answers) + print("hints" + assessment_item.hints) return set( exercise_image_filename_regex.findall( str( @@ -74,6 +80,8 @@ def update(self, queryset, all_validated_data): class AssessmentItemSerializer(BulkModelSerializer): # This is set as editable=False on the model so by default DRF does not allow us # to set it. + hints = serializers.CharField(required=False) + answers = serializers.CharField(required=False) assessment_id = UUIDRegexField() contentnode = UserFilteredPrimaryKeyRelatedField( queryset=ContentNode.objects.all(), required=False @@ -98,6 +106,26 @@ class Meta: # Use the contentnode and assessment_id as the lookup field for updates update_lookup_field = ("contentnode", "assessment_id") + def validate_answers(self, value): + answers = json.loads(value) + if answers is not []: + for answer in answers: + if not type(answer) is dict: + self.fail('JSON Data Invalid for answers') + + if not all(k in answer for k in ('answer', 'correct', 'order')): + self.fail('Incorrect field in answers') + + def validate_hints(self, value): + hints = json.loads(value) + print(hints) + for hint in hints: + if not type(hint) is dict: + self.fail('JSON Data Invalid for hints') + + if not all(k in hint for k in ('hint', 'order')): + self.fail('Incorrect field in hints') + def set_files(self, all_objects, all_validated_data=None): # noqa C901 files_to_delete = [] files_to_update = {} @@ -109,8 +137,7 @@ def set_files(self, all_objects, all_validated_data=None): # noqa C901 # have had these fields modified. md_fields_modified = { self.id_value_lookup(ai) for ai in all_validated_data - if "question" in ai or "hints" in ai or "answers" in ai - } + if "question" in ai or "hints" in ai or "answers" in ai} else: # If this is a create operation, just check if these fields are not null. md_fields_modified = { From e28ee04fc26c2f8ac1b533ef89411c1049972a88 Mon Sep 17 00:00:00 2001 From: ozer550 Date: Wed, 4 Jan 2023 23:00:52 +0530 Subject: [PATCH 2/4] update get_filenames_from_assessment --- .../tests/viewsets/test_assessmentitem.py | 87 ++++++++++++++++--- .../viewsets/assessmentitem.py | 30 +++---- 2 files changed, 89 insertions(+), 28 deletions(-) diff --git a/contentcuration/contentcuration/tests/viewsets/test_assessmentitem.py b/contentcuration/contentcuration/tests/viewsets/test_assessmentitem.py index 766d4cad2a..dd80e09291 100644 --- a/contentcuration/contentcuration/tests/viewsets/test_assessmentitem.py +++ b/contentcuration/contentcuration/tests/viewsets/test_assessmentitem.py @@ -125,7 +125,6 @@ def test_create_assessmentitem_with_file_answers(self): ) ], ) - print(response.data["errors"]) self.assertEqual(response.status_code, 200, response.content) try: ai = models.AssessmentItem.objects.get( @@ -166,7 +165,6 @@ def test_create_assessmentitem_with_file_hints(self): ], ) - print(response.data["errors"]) self.assertEqual(response.status_code, 200, response.content) try: ai = models.AssessmentItem.objects.get( @@ -195,7 +193,7 @@ def test_create_assessmentitem_with_file_no_permission(self): ASSESSMENTITEM, assessmentitem, channel_id=self.channel.id, - ) + ), ], ) self.assertEqual(response.status_code, 200, response.content) @@ -514,15 +512,57 @@ def test_delete_assessmentitems(self): def test_valid_hints_assessmentitem(self): self.client.force_authenticate(user=self.user) assessmentitem = self.assessmentitem_metadata - hints = [ - {"hint": "Hint 1", "order": 1}, - {"hint": "Hint 2", "order": 2}, - {"hint": "Hint 3", "order": 3}, - {"hint": "Hint 4", "order": 4}, + assessmentitem["hints"] = json.dumps([{'hint': 'asdasdwdqasd', 'order': 1}, {'hint': 'testing the hint', 'order': 2}]) + response = self.sync_changes( + [ + generate_create_event( + [assessmentitem["contentnode"], assessmentitem["assessment_id"]], + ASSESSMENTITEM, + assessmentitem, + channel_id=self.channel.id, + ), + ], + ) + self.assertEqual(response.status_code, 200, response.content) + try: + models.AssessmentItem.objects.get( + assessment_id=assessmentitem["assessment_id"] + ) + except models.AssessmentItem.DoesNotExist: + self.fail("AssessmentItem was not created") - ] - hints = json.dumps(hints) - assessmentitem["hints"] = hints + def test_invalid_hints_assessmentitem(self): + + self.client.force_authenticate(user=self.user) + assessmentitem = self.assessmentitem_metadata + assessmentitem["hints"] = json.dumps("test invalid string for hints") + response = self.sync_changes( + [ + generate_create_event( + [assessmentitem["contentnode"], assessmentitem["assessment_id"]], + ASSESSMENTITEM, + assessmentitem, + channel_id=self.channel.id, + ), + ], + ) + + self.assertEqual(response.json()["errors"][0]["table"], "assessmentitem") + self.assertEqual(response.json()["errors"][0]["errors"]["hints"][0], "JSON Data Invalid for hints") + self.assertEqual(len(response.json()["errors"]), 1) + + with self.assertRaises(models.AssessmentItem.DoesNotExist, msg="AssessmentItem was created"): + models.AssessmentItem.objects.get( + assessment_id=assessmentitem["assessment_id"] + ) + + def test_valid_answers_assessmentitem(self): + self.client.force_authenticate(user=self.user) + assessmentitem = self.assessmentitem_metadata + assessmentitem["answers"] = json.dumps([{'answer': 'test answer 1 :)', 'correct': False, 'order': 1}, + {'answer': 'test answer 2 :)', 'correct': False, 'order': 2}, + {'answer': 'test answer 3 :)', 'correct': True, 'order': 3} + ]) response = self.sync_changes( [ generate_create_event( @@ -541,6 +581,31 @@ def test_valid_hints_assessmentitem(self): except models.AssessmentItem.DoesNotExist: self.fail("AssessmentItem was not created") + def test_invalid_answers_assessmentitem(self): + + self.client.force_authenticate(user=self.user) + assessmentitem = self.assessmentitem_metadata + assessmentitem["answers"] = json.dumps("test invalid string for answers") + response = self.sync_changes( + [ + generate_create_event( + [assessmentitem["contentnode"], assessmentitem["assessment_id"]], + ASSESSMENTITEM, + assessmentitem, + channel_id=self.channel.id, + ), + ], + ) + + self.assertEqual(response.json()["errors"][0]["table"], "assessmentitem") + self.assertEqual(response.json()["errors"][0]["errors"]["answers"][0], "JSON Data Invalid for answers") + self.assertEqual(len(response.json()["errors"]), 1) + + with self.assertRaises(models.AssessmentItem.DoesNotExist, msg="AssessmentItem was created"): + models.AssessmentItem.objects.get( + assessment_id=assessmentitem["assessment_id"] + ) + class CRUDTestCase(StudioAPITestCase): @property diff --git a/contentcuration/contentcuration/viewsets/assessmentitem.py b/contentcuration/contentcuration/viewsets/assessmentitem.py index 6427d82d96..4f686ede4e 100644 --- a/contentcuration/contentcuration/viewsets/assessmentitem.py +++ b/contentcuration/contentcuration/viewsets/assessmentitem.py @@ -44,16 +44,14 @@ def get_filenames_from_assessment(assessment_item): # Get unique checksums in the assessment item text fields markdown # Coerce to a string, for Python 2, as the stored data is in unicode, and otherwise # the unicode char in the placeholder will not match - print("DEBUGGING AI") - print(assessment_item.question) - print(assessment_item.answers) - print("hints" + assessment_item.hints) + answers = json.loads(assessment_item.answers) + hints = json.loads(assessment_item.hints) return set( exercise_image_filename_regex.findall( str( assessment_item.question - + assessment_item.answers - + assessment_item.hints + + str([a["answer"] for a in answers]) + + str([h["hint"] for h in hints]) ) ) ) @@ -108,23 +106,21 @@ class Meta: def validate_answers(self, value): answers = json.loads(value) - if answers is not []: - for answer in answers: - if not type(answer) is dict: - self.fail('JSON Data Invalid for answers') - - if not all(k in answer for k in ('answer', 'correct', 'order')): - self.fail('Incorrect field in answers') + for answer in answers: + if not type(answer) is dict: + raise ValidationError('JSON Data Invalid for answers') + if not all(k in answer for k in ('answer', 'correct', 'order')): + raise ValidationError('Incorrect field in answers') + return value def validate_hints(self, value): hints = json.loads(value) - print(hints) for hint in hints: if not type(hint) is dict: - self.fail('JSON Data Invalid for hints') - + raise ValidationError('JSON Data Invalid for hints') if not all(k in hint for k in ('hint', 'order')): - self.fail('Incorrect field in hints') + raise ValidationError('Incorrect field in hints') + return value def set_files(self, all_objects, all_validated_data=None): # noqa C901 files_to_delete = [] From 8ab541243cbe5121bdddd33d08dfec1d0dfed546 Mon Sep 17 00:00:00 2001 From: ozer550 Date: Wed, 25 Jan 2023 11:06:22 +0530 Subject: [PATCH 3/4] fix formating --- contentcuration/contentcuration/viewsets/assessmentitem.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/contentcuration/contentcuration/viewsets/assessmentitem.py b/contentcuration/contentcuration/viewsets/assessmentitem.py index 4f686ede4e..ab3b3d6451 100644 --- a/contentcuration/contentcuration/viewsets/assessmentitem.py +++ b/contentcuration/contentcuration/viewsets/assessmentitem.py @@ -133,7 +133,8 @@ def set_files(self, all_objects, all_validated_data=None): # noqa C901 # have had these fields modified. md_fields_modified = { self.id_value_lookup(ai) for ai in all_validated_data - if "question" in ai or "hints" in ai or "answers" in ai} + if "question" in ai or "hints" in ai or "answers" in ai + } else: # If this is a create operation, just check if these fields are not null. md_fields_modified = { From 225666a63709928f7d52e5cc951a78011493c26e Mon Sep 17 00:00:00 2001 From: ozer550 Date: Wed, 25 Jan 2023 11:30:32 +0530 Subject: [PATCH 4/4] fix indentation --- contentcuration/contentcuration/viewsets/assessmentitem.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/contentcuration/contentcuration/viewsets/assessmentitem.py b/contentcuration/contentcuration/viewsets/assessmentitem.py index ab3b3d6451..c78cbace72 100644 --- a/contentcuration/contentcuration/viewsets/assessmentitem.py +++ b/contentcuration/contentcuration/viewsets/assessmentitem.py @@ -132,8 +132,7 @@ def set_files(self, all_objects, all_validated_data=None): # noqa C901 # If this is an update operation, check the validated data for which items # have had these fields modified. md_fields_modified = { - self.id_value_lookup(ai) for ai in all_validated_data - if "question" in ai or "hints" in ai or "answers" in ai + self.id_value_lookup(ai) for ai in all_validated_data if "question" in ai or "hints" in ai or "answers" in ai } else: # If this is a create operation, just check if these fields are not null.