Skip to content

Commit 04e15ad

Browse files
authored
Merge pull request #5072 from rtibbles/exercises_are_good_for_you_they_say
Assessment item/exercise related fixes and tests
2 parents 78eb578 + 402cb28 commit 04e15ad

7 files changed

Lines changed: 1280 additions & 181 deletions

File tree

Lines changed: 0 additions & 113 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,7 @@
1-
import json
2-
31
import pytest
42
from django.urls import reverse_lazy
5-
from le_utils.constants import content_kinds
6-
from le_utils.constants import exercises
73

84
from .base import BaseAPITestCase
9-
from contentcuration.models import AssessmentItem
10-
from contentcuration.models import ContentNode
11-
from contentcuration.models import File
125
from contentcuration.models import User
136

147
pytestmark = pytest.mark.django_db
@@ -47,109 +40,3 @@ def test_readonly_fields(self):
4740
)
4841
self.channel.refresh_from_db()
4942
self.assertEqual(original_version, self.channel.version)
50-
51-
52-
# TODO: rtibbles - update tests to test sync behaviour.
53-
@pytest.mark.skip
54-
class AssessmentItemTestCase(BaseAPITestCase):
55-
def test_bulk_update(self):
56-
exercise = ContentNode.objects.filter(kind=content_kinds.EXERCISE).first()
57-
item1 = AssessmentItem.objects.create(contentnode=exercise)
58-
item2 = AssessmentItem.objects.create(contentnode=exercise)
59-
item3 = AssessmentItem.objects.create(contentnode=exercise)
60-
item1dict = {}
61-
item2dict = {}
62-
item3dict = {}
63-
for field in AssessmentItem._meta.fields:
64-
attname = field.attname
65-
set_attname = attname
66-
if attname == "contentnode_id":
67-
set_attname = "contentnode"
68-
item1dict[set_attname] = getattr(item1, attname)
69-
item2dict[set_attname] = getattr(item2, attname)
70-
item3dict[set_attname] = getattr(item3, attname)
71-
item1dict["question"] = "test"
72-
item2dict["type"] = "test"
73-
self.client.put(
74-
reverse_lazy("assessmentitem-list"),
75-
json.dumps([item1dict, item2dict, item3dict]),
76-
content_type="application/json",
77-
)
78-
item1.refresh_from_db()
79-
self.assertEqual(item1.question, "test")
80-
item2.refresh_from_db()
81-
self.assertEqual(item2.type, "test")
82-
item3.refresh_from_db()
83-
self.assertEqual(item3.question, item3dict["question"])
84-
85-
def test_bulk_update_non_existent_item(self):
86-
exercise = ContentNode.objects.filter(kind=content_kinds.EXERCISE).first()
87-
item1 = AssessmentItem.objects.create(contentnode=exercise)
88-
item1dict = {}
89-
item2dict = {}
90-
item3dict = {}
91-
for field in AssessmentItem._meta.fields:
92-
attname = field.attname
93-
set_attname = attname
94-
if attname == "contentnode_id":
95-
set_attname = "contentnode"
96-
item1dict[set_attname] = getattr(item1, attname)
97-
item2dict[set_attname] = getattr(item1, attname)
98-
item3dict[set_attname] = getattr(item1, attname)
99-
item2dict["id"] = 10000
100-
item3dict["id"] = 10001
101-
item1dict["question"] = "test"
102-
response = self.client.put(
103-
reverse_lazy("assessmentitem-list"),
104-
json.dumps([item1dict, item2dict, item3dict]),
105-
content_type="application/json",
106-
)
107-
self.assertEqual(response.status_code, 400)
108-
109-
def test_bulk_update_checksum_file_not_associated_create_new_file_object(self):
110-
exercise = ContentNode.objects.filter(kind=content_kinds.EXERCISE).first()
111-
item1 = AssessmentItem.objects.create(contentnode=exercise)
112-
item1dict = {}
113-
for field in AssessmentItem._meta.fields:
114-
attname = field.attname
115-
set_attname = attname
116-
if attname == "contentnode_id":
117-
set_attname = "contentnode"
118-
item1dict[set_attname] = getattr(item1, attname)
119-
checksum = "b6d83d66859b0cf095ef81120ef98e1f"
120-
item1dict["question"] = (
121-
"![I'm an image!]($"
122-
+ exercises.IMG_PLACEHOLDER
123-
+ "/{checksum}.gif)".format(checksum=checksum)
124-
)
125-
File.objects.create(checksum=checksum)
126-
self.client.put(
127-
reverse_lazy("assessmentitem-list"),
128-
json.dumps([item1dict]),
129-
content_type="application/json",
130-
)
131-
self.assertEqual(File.objects.filter(checksum=checksum).count(), 2)
132-
133-
def test_bulk_update_checksum_file_associated_use_existing_file_object(self):
134-
exercise = ContentNode.objects.filter(kind=content_kinds.EXERCISE).first()
135-
item1 = AssessmentItem.objects.create(contentnode=exercise)
136-
item1dict = {}
137-
for field in AssessmentItem._meta.fields:
138-
attname = field.attname
139-
set_attname = attname
140-
if attname == "contentnode_id":
141-
set_attname = "contentnode"
142-
item1dict[set_attname] = getattr(item1, attname)
143-
checksum = "b6d83d66859b0cf095ef81120ef98e1f"
144-
item1dict["question"] = (
145-
"![I'm an image!]($"
146-
+ exercises.IMG_PLACEHOLDER
147-
+ "/{checksum}.gif)".format(checksum=checksum)
148-
)
149-
File.objects.create(checksum=checksum, assessment_item=item1)
150-
self.client.put(
151-
reverse_lazy("assessmentitem-list"),
152-
json.dumps([item1dict]),
153-
content_type="application/json",
154-
)
155-
self.assertEqual(File.objects.filter(checksum=checksum).count(), 1)

contentcuration/contentcuration/tests/testdata.py

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,9 @@
1111

1212
import pytest
1313
from django.core.files.storage import default_storage
14+
from le_utils.constants import exercises
1415
from le_utils.constants import format_presets
16+
from PIL import Image
1517

1618
from contentcuration import models as cc
1719
from contentcuration.tests.utils import mixer
@@ -385,27 +387,31 @@ def create_test_file(filebytes, ext="pdf"):
385387
]
386388

387389

388-
def fileobj_exercise_image():
390+
def fileobj_exercise_image(size=(100, 100), color="red"):
389391
"""
390392
Create a generic exercise image file in storage and return a File model pointing to it.
391393
"""
392-
filecontents = "".join(random.sample(string.printable, 20))
394+
image = Image.new("RGB", size, color=color)
395+
buffer = BytesIO()
396+
image.save(buffer, "JPEG")
393397
temp_file_dict = create_studio_file(
394-
filecontents, preset=format_presets.EXERCISE_IMAGE, ext="jpg"
398+
buffer.getvalue(), preset=format_presets.EXERCISE_IMAGE, ext="jpg"
395399
)
396400
return temp_file_dict["db_file"]
397401

398402

399-
def fileobj_exercise_graphie():
403+
def fileobj_exercise_graphie(original_filename=None):
400404
"""
401405
Create an graphi exercise image file in storage and return a File model pointing to it.
402406
"""
403-
filecontents = "".join(random.sample(string.printable, 20))
407+
svg_content = f"<svg><circle cx='50' cy='50' r='40' />{original_filename or ''.join(random.sample(string.printable, 20))}</svg>"
408+
json_content = '{"version": {"major": 0, "minor": 0}}'
409+
filecontents = svg_content + exercises.GRAPHIE_DELIMITER + json_content
404410
temp_file_dict = create_studio_file(
405411
filecontents,
406412
preset=format_presets.EXERCISE_GRAPHIE,
407413
ext="graphie",
408-
original_filename="theoriginalfilename",
414+
original_filename=original_filename or "theoriginalfilename",
409415
)
410416
return temp_file_dict["db_file"]
411417

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
{
2+
"answerArea": {
3+
"calculator": false,
4+
"chi2Table": false,
5+
"periodicTable": false,
6+
"tTable": false,
7+
"zTable": false
8+
},
9+
"hints": [
10+
{
11+
"content": "The bottom bar lines up to $\\purpleD{6}$. \n\n![](web+graphie://cdn.kastatic.org/ka-perseus-graphie/d855aefe9a722f9a794b0883ebcdb8c37b4ba0c7)\n\nWhich type of fruit has $\\purpleD{6}$ in Luigi's home?",
12+
"images": {
13+
"web+graphie://cdn.kastatic.org/ka-perseus-graphie/d855aefe9a722f9a794b0883ebcdb8c37b4ba0c7": {
14+
"height": 330,
15+
"width": 404
16+
}
17+
},
18+
"replace": false,
19+
"widgets": {}
20+
},
21+
{
22+
"content": "Kind of fruit | Number\n:- | :-: \nOranges | $\\purpleD{6}$ \n\nLuigi has $\\purpleD{6}$ oranges. So, the bottom bar should be labeled $\\purpleD{\\text{Oranges}}$.",
23+
"images": {},
24+
"replace": false,
25+
"widgets": {}
26+
},
27+
{
28+
"content": "Now let's label the other bars to match the table.",
29+
"images": {},
30+
"replace": false,
31+
"widgets": {}
32+
},
33+
{
34+
"content": "Here is the completed graph:\n\n![](web+graphie://cdn.kastatic.org/ka-perseus-graphie/95262ebaf42bdd1929e5d6d1e2853d3eb0a5cc74)",
35+
"images": {
36+
"web+graphie://cdn.kastatic.org/ka-perseus-graphie/95262ebaf42bdd1929e5d6d1e2853d3eb0a5cc74": {
37+
"height": 330,
38+
"width": 404
39+
}
40+
},
41+
"replace": false,
42+
"widgets": {}
43+
}
44+
],
45+
"itemDataVersion": {
46+
"major": 0,
47+
"minor": 1
48+
},
49+
"question": {
50+
"content": "Luigi created a chart and a bar graph to show how many of each type of fruit were in his home.\n\nKind of fruit | Number \n:- | :-: \nApple | $7$ \nStrawberries | $3$ \nOranges | $6$ \nBananas| $2$ \n\n**Label each bar on the bar graph.**\n\n[[☃ label-image 1]]\n",
51+
"images": {},
52+
"widgets": {
53+
"label-image 1": {
54+
"alignment": "default",
55+
"graded": true,
56+
"options": {
57+
"choices": [
58+
"Apple",
59+
"Strawberries",
60+
"Oranges",
61+
"Bananas"
62+
],
63+
"hideChoicesFromInstructions": true,
64+
"imageAlt": "",
65+
"imageHeight": 330,
66+
"imageUrl": "web+graphie://cdn.kastatic.org/ka-perseus-graphie/ab207c6f38c887130b68c078e6158a87aab60c45",
67+
"imageWidth": 404,
68+
"markers": [
69+
{
70+
"answers": [
71+
"Strawberries"
72+
],
73+
"label": "",
74+
"x": 24.1,
75+
"y": 17.7
76+
},
77+
{
78+
"answers": [
79+
"Bananas"
80+
],
81+
"label": "",
82+
"x": 24.4,
83+
"y": 35.7
84+
},
85+
{
86+
"answers": [
87+
"Apple"
88+
],
89+
"label": "",
90+
"x": 23.8,
91+
"y": 52.9
92+
},
93+
{
94+
"answers": [
95+
"Oranges"
96+
],
97+
"label": "",
98+
"x": 24.1,
99+
"y": 70.9
100+
}
101+
],
102+
"multipleAnswers": false,
103+
"static": false
104+
},
105+
"static": false,
106+
"type": "label-image",
107+
"version": {
108+
"major": 0,
109+
"minor": 0
110+
}
111+
}
112+
}
113+
}
114+
}

0 commit comments

Comments
 (0)