Skip to content

Commit 03d2e51

Browse files
committed
Fix unable to view ORA responses (files) in leaderboard
EDUCATOR-560
1 parent 93e0ca4 commit 03d2e51

File tree

3 files changed

+127
-58
lines changed

3 files changed

+127
-58
lines changed

AUTHORS

+1
Original file line numberDiff line numberDiff line change
@@ -28,3 +28,4 @@ Mushtaq Ali <[email protected]>
2828
Dmitry Viskov <[email protected]>
2929
Jeff LaJoie <[email protected]>
3030
Albert St. Aubin Jr. <[email protected]>
31+
Awais Jibran <[email protected]>

openassessment/xblock/leaderboard_mixin.py

+27-9
Original file line numberDiff line numberDiff line change
@@ -76,16 +76,18 @@ def render_leaderboard_complete(self, student_item_dict):
7676
for score in scores:
7777
score['files'] = []
7878
if 'file_keys' in score['content']:
79-
for key in score['content']['file_keys']:
80-
url = ''
81-
try:
82-
url = file_upload_api.get_download_url(key)
83-
except FileUploadError:
84-
pass
85-
if url:
86-
score['files'].append(url)
79+
file_keys = score['content'].get('file_keys', [])
80+
descriptions = score['content'].get('files_descriptions', [])
81+
for idx, key in enumerate(file_keys):
82+
file_download_url = self._get_file_download_url(key)
83+
if file_download_url:
84+
file_description = descriptions[idx] if idx < len(descriptions) else ''
85+
score['files'].append((file_download_url, file_description))
86+
8787
elif 'file_key' in score['content']:
88-
score['files'].append(file_upload_api.get_download_url(score['content']['file_key']))
88+
file_download_url = self._get_file_download_url(score['content']['file_key'])
89+
if file_download_url:
90+
score['files'].append((file_download_url, ''))
8991
if 'text' in score['content'] or 'parts' in score['content']:
9092
submission = {'answer': score.pop('content')}
9193
score['submission'] = create_submission_dict(submission, self.prompts)
@@ -112,3 +114,19 @@ def render_leaderboard_incomplete(self):
112114
template_path (string), tuple of context (dict)
113115
"""
114116
return 'openassessmentblock/leaderboard/oa_leaderboard_waiting.html', {'xblock_id': self.get_xblock_id()}
117+
118+
def _get_file_download_url(self, file_key):
119+
"""
120+
Internal function for retrieving the download url at which the file that corresponds
121+
to the file_key can be downloaded.
122+
123+
Arguments:
124+
file_key (string): Corresponding file key.
125+
Returns:
126+
file_download_url (string) or empty string in case of error.
127+
"""
128+
try:
129+
file_download_url = file_upload_api.get_download_url(file_key)
130+
except FileUploadError:
131+
file_download_url = ''
132+
return file_download_url

openassessment/xblock/test/test_leaderboard.py

+99-49
Original file line numberDiff line numberDiff line change
@@ -47,16 +47,16 @@ def test_show_no_submissions(self, xblock):
4747
def test_show_submissions(self, xblock):
4848
# Create some submissions (but fewer than the max that can be shown)
4949
self._create_submissions_and_scores(xblock, [
50-
(prepare_submission_for_serialization(("test answer 1 part 1", "test answer 1 part 2")), 1),
51-
(prepare_submission_for_serialization(("test answer 2 part 1", "test answer 2 part 2")), 2)
50+
(prepare_submission_for_serialization(('test answer 1 part 1', 'test answer 1 part 2')), 1),
51+
(prepare_submission_for_serialization(('test answer 2 part 1', 'test answer 2 part 2')), 2)
5252
])
5353
self._assert_scores(xblock, [
54-
{"score": 2, "submission": create_submission_dict(
55-
{"answer": prepare_submission_for_serialization((u"test answer 2 part 1", u"test answer 2 part 2"))},
54+
{'score': 2, 'files': [], 'submission': create_submission_dict(
55+
{'answer': prepare_submission_for_serialization((u'test answer 2 part 1', u'test answer 2 part 2'))},
5656
xblock.prompts
5757
)},
58-
{"score": 1, "submission": create_submission_dict(
59-
{"answer": prepare_submission_for_serialization((u"test answer 1 part 1", u"test answer 1 part 2"))},
58+
{'score': 1, 'files': [], 'submission': create_submission_dict(
59+
{'answer': prepare_submission_for_serialization((u'test answer 1 part 1', u'test answer 1 part 2'))},
6060
xblock.prompts
6161
)}
6262
])
@@ -68,21 +68,21 @@ def test_show_submissions(self, xblock):
6868

6969
# Create more submissions than the max
7070
self._create_submissions_and_scores(xblock, [
71-
(prepare_submission_for_serialization(("test answer 3 part 1", "test answer 3 part 2")), 0),
72-
(prepare_submission_for_serialization(("test answer 4 part 1", "test answer 4 part 2")), 10),
73-
(prepare_submission_for_serialization(("test answer 5 part 1", "test answer 5 part 2")), 3),
71+
(prepare_submission_for_serialization(('test answer 3 part 1', 'test answer 3 part 2')), 0),
72+
(prepare_submission_for_serialization(('test answer 4 part 1', 'test answer 4 part 2')), 10),
73+
(prepare_submission_for_serialization(('test answer 5 part 1', 'test answer 5 part 2')), 3),
7474
])
7575
self._assert_scores(xblock, [
76-
{"score": 10, "submission": create_submission_dict(
77-
{"answer": prepare_submission_for_serialization((u"test answer 4 part 1", u"test answer 4 part 2"))},
76+
{'score': 10, 'files': [], 'submission': create_submission_dict(
77+
{'answer': prepare_submission_for_serialization((u'test answer 4 part 1', u'test answer 4 part 2'))},
7878
xblock.prompts
7979
)},
80-
{"score": 3, "submission": create_submission_dict(
81-
{"answer": prepare_submission_for_serialization((u"test answer 5 part 1", u"test answer 5 part 2"))},
80+
{'score': 3, 'files': [], 'submission': create_submission_dict(
81+
{'answer': prepare_submission_for_serialization((u'test answer 5 part 1', u'test answer 5 part 2'))},
8282
xblock.prompts
8383
)},
84-
{"score": 2, "submission": create_submission_dict(
85-
{"answer": prepare_submission_for_serialization((u"test answer 2 part 1", u"test answer 2 part 2"))},
84+
{'score': 2, 'files': [], 'submission': create_submission_dict(
85+
{'answer': prepare_submission_for_serialization((u'test answer 2 part 1', u'test answer 2 part 2'))},
8686
xblock.prompts
8787
)}
8888
])
@@ -92,12 +92,12 @@ def test_show_submissions(self, xblock):
9292
def test_show_submissions_that_have_greater_than_0_score(self, xblock):
9393
# Create some submissions (but fewer than the max that can be shown)
9494
self._create_submissions_and_scores(xblock, [
95-
(prepare_submission_for_serialization(("test answer 0 part 1", "test answer 0 part 2")), 0),
96-
(prepare_submission_for_serialization(("test answer 1 part 1", "test answer 1 part 2")), 1)
95+
(prepare_submission_for_serialization(('test answer 0 part 1', 'test answer 0 part 2')), 0),
96+
(prepare_submission_for_serialization(('test answer 1 part 1', 'test answer 1 part 2')), 1)
9797
])
9898
self._assert_scores(xblock, [
99-
{"score": 1, "submission": create_submission_dict(
100-
{"answer": prepare_submission_for_serialization((u"test answer 1 part 1", u"test answer 1 part 2"))},
99+
{'score': 1, 'files': [], 'submission': create_submission_dict(
100+
{'answer': prepare_submission_for_serialization((u'test answer 1 part 1', u'test answer 1 part 2'))},
101101
xblock.prompts
102102
)},
103103
])
@@ -109,16 +109,16 @@ def test_show_submissions_that_have_greater_than_0_score(self, xblock):
109109

110110
# Create more submissions than the max
111111
self._create_submissions_and_scores(xblock, [
112-
(prepare_submission_for_serialization(("test answer 2 part 1", "test answer 2 part 2")), 10),
113-
(prepare_submission_for_serialization(("test answer 3 part 1", "test answer 3 part 2")), 0)
112+
(prepare_submission_for_serialization(('test answer 2 part 1', 'test answer 2 part 2')), 10),
113+
(prepare_submission_for_serialization(('test answer 3 part 1', 'test answer 3 part 2')), 0)
114114
])
115115
self._assert_scores(xblock, [
116-
{"score": 10, "submission": create_submission_dict(
117-
{"answer": prepare_submission_for_serialization((u"test answer 2 part 1", u"test answer 2 part 2"))},
116+
{'score': 10, 'files': [], 'submission': create_submission_dict(
117+
{'answer': prepare_submission_for_serialization((u'test answer 2 part 1', u'test answer 2 part 2'))},
118118
xblock.prompts
119119
)},
120-
{"score": 1, "submission": create_submission_dict(
121-
{"answer": prepare_submission_for_serialization((u"test answer 1 part 1", u"test answer 1 part 2"))},
120+
{'score': 1, 'files': [], 'submission': create_submission_dict(
121+
{'answer': prepare_submission_for_serialization((u'test answer 1 part 1', u'test answer 1 part 2'))},
122122
xblock.prompts
123123
)}
124124
])
@@ -127,60 +127,105 @@ def test_show_submissions_that_have_greater_than_0_score(self, xblock):
127127
@scenario('data/leaderboard_show.xml')
128128
def test_no_text_key_submission(self, xblock):
129129
self.maxDiff = None
130-
# Instead of using the default submission as a dict with "text",
130+
# Instead of using the default submission as a dict with 'text',
131131
# make the submission a string.
132-
self._create_submissions_and_scores(xblock, [("test answer", 1)], submission_key=None)
132+
self._create_submissions_and_scores(xblock, [('test answer', 1)], submission_key=None)
133133

134134
# It should still work
135135
self._assert_scores(xblock, [
136-
{"score": 1}
136+
{'score': 1, 'files': []}
137137
])
138138

139139
@mock_s3
140140
@override_settings(
141141
AWS_ACCESS_KEY_ID='foobar',
142142
AWS_SECRET_ACCESS_KEY='bizbaz',
143-
FILE_UPLOAD_STORAGE_BUCKET_NAME="mybucket"
143+
FILE_UPLOAD_STORAGE_BUCKET_NAME='mybucket'
144144
)
145145
@scenario('data/leaderboard_show.xml')
146146
def test_non_text_submission(self, xblock):
147147
# Create a mock bucket
148148
conn = boto.connect_s3()
149149
bucket = conn.create_bucket('mybucket')
150-
# Create a non-text submission (the submission dict doesn't contain "text")
151-
self._create_submissions_and_scores(xblock, [("s3key", 1)], submission_key="file_key")
150+
# Create a non-text submission (the submission dict doesn't contain 'text')
151+
self._create_submissions_and_scores(xblock, [('s3key', 1)], submission_key='file_key')
152152

153153
# Expect that we default to an empty string for content
154154
self._assert_scores(xblock, [
155-
{"submission": "", "score": 1, "file": ""}
155+
{'score': 1, 'files': [], 'submission': ''}
156156
])
157157

158158
@mock_s3
159159
@override_settings(
160160
AWS_ACCESS_KEY_ID='foobar',
161161
AWS_SECRET_ACCESS_KEY='bizbaz',
162-
FILE_UPLOAD_STORAGE_BUCKET_NAME="mybucket"
162+
FILE_UPLOAD_STORAGE_BUCKET_NAME='mybucket'
163+
)
164+
@scenario('data/leaderboard_show_allowfiles.xml')
165+
def test_image_and_text_submission_multiple_files(self, xblock):
166+
"""
167+
Tests that leaderboard works as expected when multiple files are uploaded
168+
"""
169+
file_keys = ['foo', 'bar']
170+
file_descriptions = ['{}-description'.format(file_key) for file_key in file_keys]
171+
172+
conn = boto.connect_s3()
173+
bucket = conn.create_bucket('mybucket')
174+
for file_key in file_keys:
175+
key = Key(bucket, 'submissions_attachments/{}'.format(file_key))
176+
key.set_contents_from_string("How d'ya do?")
177+
files_url_and_description = [
178+
(api.get_download_url(file_key), file_descriptions[idx])
179+
for idx, file_key in enumerate(file_keys)
180+
]
181+
182+
# Create a image and text submission
183+
submission = prepare_submission_for_serialization(('test answer 1 part 1', 'test answer 1 part 2'))
184+
submission[u'file_keys'] = file_keys
185+
submission[u'files_descriptions'] = file_descriptions
186+
187+
self._create_submissions_and_scores(xblock, [
188+
(submission, 1)
189+
])
190+
191+
self.maxDiff = None
192+
# Expect that we retrieve both the text and the download URL for the file
193+
self._assert_scores(xblock, [
194+
{'score': 1, 'files': files_url_and_description, 'submission': create_submission_dict(
195+
{'answer': submission},
196+
xblock.prompts
197+
)}
198+
])
199+
200+
@mock_s3
201+
@override_settings(
202+
AWS_ACCESS_KEY_ID='foobar',
203+
AWS_SECRET_ACCESS_KEY='bizbaz',
204+
FILE_UPLOAD_STORAGE_BUCKET_NAME='mybucket'
163205
)
164206
@scenario('data/leaderboard_show_allowfiles.xml')
165207
def test_image_and_text_submission(self, xblock):
208+
"""
209+
Tests that text and image submission works as expected
210+
"""
166211
# Create a file and get the download URL
167212
conn = boto.connect_s3()
168213
bucket = conn.create_bucket('mybucket')
169-
key = Key(bucket)
170-
key.key = "submissions_attachments/foo"
214+
key = Key(bucket, 'submissions_attachments/foo')
171215
key.set_contents_from_string("How d'ya do?")
172-
downloadUrl = api.get_download_url("foo")
216+
217+
file_download_url = [(api.get_download_url('foo'), '')]
173218
# Create a image and text submission
174-
submission = prepare_submission_for_serialization(("test answer 1 part 1", "test answer 1 part 2"))
175-
submission[u"file_key"] = "foo"
219+
submission = prepare_submission_for_serialization(('test answer 1 part 1', 'test answer 1 part 2'))
220+
submission[u'file_key'] = 'foo'
176221
self._create_submissions_and_scores(xblock, [
177222
(submission, 1)
178223
])
179224
self.maxDiff = None
180225
# Expect that we retrieve both the text and the download URL for the file
181226
self._assert_scores(xblock, [
182-
{"file": downloadUrl, "score": 1, "submission": create_submission_dict(
183-
{"answer": submission},
227+
{'score': 1, 'files': file_download_url, 'submission': create_submission_dict(
228+
{'answer': submission},
184229
xblock.prompts
185230
)}
186231
])
@@ -209,7 +254,7 @@ def _create_submissions_and_scores(
209254
# to anything without affecting the test.
210255
student_item = xblock.get_student_item_dict()
211256
# adding rand number to the student_id to make it unique.
212-
student_item['student_id'] = "student {num} {num2}".format(num=num, num2=randint(2, 1000))
257+
student_item['student_id'] = 'student {num} {num2}'.format(num=num, num2=randint(2, 1000))
213258
if submission_key is not None:
214259
answer = {submission_key: submission}
215260
else:
@@ -278,9 +323,9 @@ def _assert_path_and_context(self, xblock, expected_path, expected_context, work
278323

279324
# Strip query string parameters from the file URLs, since these are time-dependent
280325
# (expiration and signature)
281-
if "topscores" in expected_context:
282-
context["topscores"] = self._clean_score_filenames(context["topscores"])
283-
expected_context["topscores"] = self._clean_score_filenames(context["topscores"])
326+
if 'topscores' in expected_context:
327+
context['topscores'] = self._clean_score_filenames(context['topscores'])
328+
expected_context['topscores'] = self._clean_score_filenames(expected_context['topscores'])
284329

285330
self.assertEqual(path, expected_path)
286331
self.assertEqual(context, expected_context)
@@ -293,17 +338,22 @@ def _assert_leaderboard_visible(self, xblock, is_visible):
293338
"""
294339
Check that the leaderboard is displayed in the student view.
295340
"""
296-
fragment = self.runtime.render(xblock, "student_view")
341+
fragment = self.runtime.render(xblock, 'student_view')
297342
has_leaderboard = 'step--leaderboard' in fragment.body_html()
298343
self.assertEqual(has_leaderboard, is_visible)
299344

300345
def _clean_score_filenames(self, scores):
301346
"""
302347
Remove querystring parameters from the file name of the score.
303348
"""
304-
for score in scores:
305-
if score.get("file"):
306-
url = urlparse(score["file"])
307-
score["file"] = url.scheme + "://" + url.netloc + url.path
349+
def _clean_query_string(_file_url):
350+
url = urlparse(_file_url)
351+
return url.scheme + '://' + url.netloc + url.path
308352

353+
for score in scores:
354+
if score.get('files'):
355+
score['files'] = [
356+
(_clean_query_string(file_info[0]), file_info[1]) for file_info in score['files']
357+
]
309358
return scores
359+

0 commit comments

Comments
 (0)