Commit d3e328dd by Dmitry Viskov

Description fields for all uploaded files

parent 179f685b
......@@ -12,15 +12,20 @@
<div class="{{ class_prefix }}__display__file {% if not file_urls %}is--hidden{% endif %} submission__{{ file_upload_type }}__upload" data-upload-type="{{ file_upload_type }}">
<div class="submission__answer__files">
{% for file_url in file_urls %}
<div class="submission__answer__file__block__{{ forloop.counter0 }}">
{% for file_url, file_description in file_urls %}
<div class="submission__answer__file__block submission__answer__file__block__{{ forloop.counter0 }}">
{% if file_upload_type == "image" %}
<img class="submission__answer__file submission--image"
alt="{% trans "The image associated with this submission:" %} #{{ forloop.counter }}"
src="{{ file_url }}" />
{% if file_description %}
<div class="submission__file__description__label">{{ file_description }}:</div>
{% endif %}
<div><img class="submission__answer__file submission--image" src="{{ file_url }}" /></div>
{% elif file_upload_type == "pdf-and-image" or file_upload_type == "custom" %}
<a href="{{ file_url }}" class="submission__answer__file submission--file" target="_blank">
{% trans "View the files associated with this submission:" %} #{{ forloop.counter }}
{% if file_description %}
{{ file_description }}
{% else %}
{% trans "View the files associated with this submission:" %} #{{ forloop.counter }}
{% endif %}
</a>
{% endif %}
</div>
......
......@@ -68,7 +68,7 @@
{% trans "Your peer's response to the question above" as translated_label %}
{% include "openassessmentblock/oa_submission_answer.html" with answer=peer_submission.answer answer_text_label=translated_label %}
{% trans "Associated File" as translated_header %}
{% trans "Associated Files" as translated_header %}
{% include "openassessmentblock/oa_uploaded_file.html" with file_upload_type=file_upload_type file_urls=peer_file_urls header=translated_header class_prefix="peer-assessment" show_warning="true" %}
</div>
......
......@@ -51,7 +51,7 @@
{% trans "Your peer's response to the question above" as translated_label %}
{% include "openassessmentblock/oa_submission_answer.html" with answer=peer_submission.answer answer_text_label=translated_label %}
{% trans "Associated File" as translated_header %}
{% trans "Associated Files" as translated_header %}
{% include "openassessmentblock/oa_uploaded_file.html" with file_upload_type=file_upload_type file_urls=peer_file_urls header=translated_header class_prefix="peer-assessment" show_warning="true" %}
</div>
......
......@@ -95,17 +95,19 @@
<li class="field">
<div class="upload__error">
<div class="message message--inline message--error message--error-server" tabindex="-1">
<h5 class="message__title">{% trans "We could not upload this file" %}</h5>
<h5 class="message__title">{% trans "We could not upload files" %}</h5>
<div class="message__content"></div>
</div>
</div>
<label class="sr" for="submission_answer_upload_{{ xblock_id }}">{% trans "Select a file to upload for this submission." %}</label>
<input type="file" class="submission__answer__upload file--upload" id="submission_answer_upload_{{ xblock_id }}" multiple="">
<div class="files__descriptions"></div>
<button type="submit" class="file__upload action action--upload" disabled>{% trans "Upload files" %}</button>
</li>
{% endif %}
<li class="field">
{% include "openassessmentblock/oa_uploaded_file.html" with file_upload_type=file_upload_type file_urls=file_urls class_prefix="submission__answer"%}
</li>
</ol>
<span class="tip" id="submission__answer__tip__{{ xblock_id }}">{% trans "You may continue to work on your response until you submit it." %}</span>
......
......@@ -51,7 +51,7 @@
{% trans "Your response" as translated_label %}
{% include "openassessmentblock/oa_submission_answer.html" with answer=self_submission.answer answer_text_label=translated_label %}
{% trans "Associated File" as translated_header %}
{% trans "Associated Files" as translated_header %}
{% include "openassessmentblock/oa_uploaded_file.html" with file_upload_type=file_upload_type file_urls=self_file_urls header=translated_header class_prefix="self-assessment" %}
</article>
......
......@@ -25,7 +25,7 @@
{% trans "The learner's response to the question above" as translated_label %}
{% include "openassessmentblock/oa_submission_answer.html" with answer=submission.answer answer_text_label=translated_label %}
{% trans "Associated File" as translated_header %}
{% trans "Associated Files" as translated_header %}
{% include "openassessmentblock/oa_uploaded_file.html" with file_upload_type=file_upload_type file_urls=staff_file_urls header=translated_header class_prefix="staff-assessment" show_warning="true" %}
</div>
......
......@@ -24,7 +24,7 @@
{% trans "The learner's response to the question above" as translated_label %}
{% include "openassessmentblock/oa_submission_answer.html" with answer=submission.answer answer_text_label=translated_label %}
{% trans "Associated File" as translated_header %}
{% trans "Associated Files" as translated_header %}
{% include "openassessmentblock/oa_uploaded_file.html" with file_upload_type=file_upload_type file_urls=staff_file_urls header=translated_header class_prefix="staff-assessment" show_warning="true" %}
</div>
......
......@@ -46,7 +46,7 @@
{% trans "The learner's response to the question above" as translated_label %}
{% include "openassessmentblock/oa_submission_answer.html" with answer=submission.answer answer_text_label=translated_label %}
{% trans "Associated File" as translated_header %}
{% trans "Associated Files" as translated_header %}
{% include "openassessmentblock/oa_uploaded_file.html" with file_upload_type=file_upload_type file_urls=staff_file_urls header=translated_header class_prefix="staff-assessment" show_warning="true" %}
</div>
{% endif %}
......
......@@ -216,6 +216,12 @@ class OpenAssessmentBlock(MessageMixin,
help="Saved response submission for the current user."
)
saved_files_descriptions = String(
default=u"",
scope=Scope.user_state,
help="Saved descriptions for each uploaded file."
)
no_peers = Boolean(
default=False,
scope=Scope.user_state,
......
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
......@@ -96,7 +96,8 @@
},
"save_status": "This response has not been saved.",
"submit_enabled": false,
"submission_due": ""
"submission_due": "",
"file_upload_type": "image"
},
"output": "oa_response.html"
},
......
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
......@@ -140,6 +140,12 @@ describe("OpenAssessment.ResponseView", function() {
defer.resolve();
});
});
spyOn(view, 'saveFilesDescriptions').and.callFake(function() {
return $.Deferred(function(defer) {
view.removeFilesDescriptions();
defer.resolve();
});
});
});
afterEach(function() {
......@@ -511,7 +517,7 @@ describe("OpenAssessment.ResponseView", function() {
it("uploads two images using a one-time URL", function() {
var files = [{type: 'image/jpeg', size: 1024, name: 'picture1.jpg', data: ''},
{type: 'image/jpeg', size: 1024, name: 'picture2.jpg', data: ''}];
view.prepareUpload(files, 'image');
view.prepareUpload(files, 'image', ['text1', 'text2']);
view.uploadFiles();
expect(fileUploader.uploadArgs[0].url).toEqual(FAKE_URL);
expect(fileUploader.uploadArgs[0].data).toEqual(files[0]);
......@@ -521,7 +527,7 @@ describe("OpenAssessment.ResponseView", function() {
it("uploads a PDF using a one-time URL", function() {
var files = [{type: 'application/pdf', size: 1024, name: 'application.pdf', data: ''}];
view.prepareUpload(files, 'pdf-and-image');
view.prepareUpload(files, 'pdf-and-image', ['text']);
view.uploadFiles();
expect(fileUploader.uploadArgs[0].url).toEqual(FAKE_URL);
expect(fileUploader.uploadArgs[0].data).toEqual(files[0]);
......@@ -529,7 +535,7 @@ describe("OpenAssessment.ResponseView", function() {
it("uploads a arbitrary type file using a one-time URL", function() {
var files = [{type: 'text/html', size: 1024, name: 'index.html', data: ''}];
view.prepareUpload(files, 'custom');
view.prepareUpload(files, 'custom', ['text']);
view.uploadFiles();
expect(fileUploader.uploadArgs[0].url).toEqual(FAKE_URL);
expect(fileUploader.uploadArgs[0].data).toEqual(files[0]);
......@@ -542,7 +548,7 @@ describe("OpenAssessment.ResponseView", function() {
// Attempt to upload a file
var files = [{type: 'image/jpeg', size: 1024, name: 'picture.jpg', data: ''}];
view.prepareUpload(files, 'image');
view.prepareUpload(files, 'image', ['text']);
view.uploadFiles();
// Expect an error to be displayed
......@@ -562,4 +568,48 @@ describe("OpenAssessment.ResponseView", function() {
// Expect an error to be displayed
expect(view.baseView.toggleActionError).toHaveBeenCalledWith('upload', 'ERROR');
});
it("disables the upload button if any file description is not set", function() {
function getFileUploadField() {
return $(view.element).find('.file__upload').first();
}
spyOn(view, 'updateFilesDescriptionsFields').and.callThrough();
var files = [{type: 'image/jpeg', size: 1024, name: 'picture1.jpg', data: ''},
{type: 'image/jpeg', size: 1024, name: 'picture2.jpg', data: ''}];
view.prepareUpload(files, 'image');
expect(getFileUploadField().is(':disabled')).toEqual(true);
expect(view.updateFilesDescriptionsFields).toHaveBeenCalledWith(files, undefined);
// set the first description field (the second is still empty)
// and check that upload button is disabled
var firstDescriptionField1 = $(view.element).find('.file__description__0').first();
$(firstDescriptionField1).val('test');
view.checkFilesDescriptions();
expect(getFileUploadField().is(':disabled')).toEqual(true);
// set the second description field (now both descriptions are not empty)
// and check that upload button is enabled
var firstDescriptionField2 = $(view.element).find('.file__description__1').first();
$(firstDescriptionField2).val('test2');
view.checkFilesDescriptions();
expect(getFileUploadField().is(':disabled')).toEqual(false);
// remove value in the first upload field
// and check that upload button is disabled
$(firstDescriptionField1).val('');
view.checkFilesDescriptions();
expect(getFileUploadField().is(':disabled')).toEqual(true);
});
it("removes description fields after files upload", function() {
var files = [{type: 'image/jpeg', size: 1024, name: 'picture1.jpg', data: ''},
{type: 'image/jpeg', size: 1024, name: 'picture2.jpg', data: ''}];
view.prepareUpload(files, 'image', ['test1', 'test2']);
expect($(view.element).find('.file__description').length).toEqual(2);
view.uploadFiles();
expect($(view.element).find('.file__description').length).toEqual(0);
});
});
......@@ -167,6 +167,34 @@ describe("OpenAssessment.Server", function() {
});
});
it("removes uploaded files", function() {
stubAjax(true, {'success': true, 'msg': ''});
var success = false;
server.removeUploadedFiles().done(function() { success = true; });
expect(success).toBe(true);
expect($.ajax).toHaveBeenCalledWith({
url: "/remove_all_uploaded_files",
type: "POST",
data: JSON.stringify({}),
contentType : jsonContentType
});
});
it("saves files descriptions", function() {
stubAjax(true, {'success': true, 'msg': ''});
var success = false;
server.saveFilesDescriptions(['test1', 'test2']).done(function() { success = true; });
expect(success).toBe(true);
expect($.ajax).toHaveBeenCalledWith({
url: "/save_files_descriptions",
type: "POST",
data: JSON.stringify({descriptions: ['test1', 'test2']}),
contentType : jsonContentType
});
});
it("sends a peer-assessment to the XBlock", function() {
stubAjax(true, {success: true, msg: ''});
......
......@@ -18,6 +18,7 @@ OpenAssessment.ResponseView = function(element, server, fileUploader, baseView,
this.baseView = baseView;
this.savedResponse = [];
this.files = null;
this.filesDescriptions = [];
this.filesType = null;
this.lastChangeTime = Date.now();
this.errorOnLastSave = false;
......@@ -130,8 +131,16 @@ OpenAssessment.ResponseView.prototype = {
function(eventObject) {
// Override default form submission
eventObject.preventDefault();
var previouslyUploadedFiles = sel.find('.submission__answer__file').length ? true : false;
$('.submission__answer__display__file', view.element).removeClass('is--hidden');
view.uploadFiles();
if (previouslyUploadedFiles) {
var msg = gettext('After you upload new files all your previously uploaded files will be overwritten. Continue?'); // jscs:ignore maximumLineLength
if (confirm(msg)) {
view.uploadFiles();
}
} else {
view.uploadFiles();
}
}
);
},
......@@ -382,6 +391,9 @@ OpenAssessment.ResponseView.prototype = {
var msg = gettext('Do you want to upload your file before submitting?');
if (confirm(msg)) {
fileDefer = view.uploadFiles();
if (fileDefer === false) {
return;
}
} else {
view.submitEnabled(true);
return;
......@@ -475,7 +487,7 @@ OpenAssessment.ResponseView.prototype = {
file or custom.
**/
prepareUpload: function(files, uploadType) {
prepareUpload: function(files, uploadType, descriptions) {
this.files = null;
this.filesType = uploadType;
this.filesUploaded = false;
......@@ -535,14 +547,94 @@ OpenAssessment.ResponseView.prototype = {
if (!errorCheckerTriggered) {
this.baseView.toggleActionError('upload', null);
this.files = files;
this.updateFilesDescriptionsFields(files, descriptions);
}
if (this.files === null) {
sel.find('.file__upload').prop('disabled', true);
}
},
/**
Render textarea fields to input description for each uploaded file.
*/
updateFilesDescriptionsFields: function(files, descriptions) {
var filesDescriptions = $(this.element).find('.files__descriptions').first();
var mainDiv = null;
var div1 = null;
var div2 = null;
var textarea = null;
var descriptionsExists = true;
this.filesDescriptions = descriptions || [];
$(filesDescriptions).show().html('');
for (var i = 0; i < files.length; i++) {
mainDiv = $('<div/>');
div1 = $('<div/>');
div1.addClass('submission__file__description__label');
div1.text(gettext("Describe ") + files[i].name + ' ' + gettext("(required):"));
div1.appendTo(mainDiv);
div2 = $('<div/>');
div2.addClass('submission__file__description');
textarea = $('<textarea />');
if ((this.filesDescriptions.indexOf(i) !== -1) && (this.filesDescriptions[i] !== '')) {
textarea.val(this.filesDescriptions[i]);
} else {
descriptionsExists = false;
}
textarea.addClass('file__description file__description__' + i);
textarea.appendTo(div2);
div2.appendTo(mainDiv);
mainDiv.appendTo(filesDescriptions);
textarea.on("change keyup drop paste", $.proxy(this, "checkFilesDescriptions"));
}
$(this.element).find('.file__upload').prop('disabled', !descriptionsExists);
},
/**
When user type something in some file description field this function check input
and block/unblock "Upload" button
*/
checkFilesDescriptions: function() {
var isError = false;
var filesDescriptions = [];
$(this.element).find('.file__description').each(function() {
var filesDescriptionVal = $(this).val();
if (filesDescriptionVal) {
filesDescriptions.push(filesDescriptionVal);
} else {
isError = true;
}
});
$(this.element).find('.file__upload').prop('disabled', isError);
if (!isError) {
this.filesDescriptions = filesDescriptions;
}
sel.find('.file__upload').prop('disabled', this.files === null);
},
/**
Clear field with files descriptions.
*/
removeFilesDescriptions: function() {
var filesDescriptions = $(this.element).find('.files__descriptions').first();
$(filesDescriptions).hide().html('');
},
/**
Remove previously uploaded files.
**/
*/
removeUploadedFiles: function() {
var view = this;
var sel = $('.step--response', this.element);
......@@ -559,6 +651,24 @@ OpenAssessment.ResponseView.prototype = {
},
/**
Sends request to server to save all file descriptions.
*/
saveFilesDescriptions: function() {
var view = this;
var sel = $('.step--response', this.element);
return this.server.saveFilesDescriptions(this.filesDescriptions).done(
function() {
view.removeFilesDescriptions();
}
).fail(function(errMsg) {
view.baseView.toggleActionError('upload', errMsg);
sel.find('.file__upload').prop('disabled', false);
});
},
/**
Manages file uploads for submission attachments.
**/
......@@ -571,6 +681,9 @@ OpenAssessment.ResponseView.prototype = {
sel.find('.file__upload').prop('disabled', true);
promise = view.removeUploadedFiles();
promise = promise.then(function() {
return view.saveFilesDescriptions();
});
$.each(view.files, function(index, file) {
promise = promise.then(function() {
......@@ -605,6 +718,7 @@ OpenAssessment.ResponseView.prototype = {
view.baseView.toggleActionError('upload', null);
if (finalUpload) {
view.filesUploaded = true;
sel.find('input[type=file]').val('');
}
})
.fail(handleError);
......@@ -622,32 +736,39 @@ OpenAssessment.ResponseView.prototype = {
view.server.getDownloadUrl(filenum).done(function(url) {
var className = 'submission__answer__file__block__' + filenum;
var file = null;
var img = null;
var fileBlock = null;
var fileBlockExists = sel.find("." + className).length ? true : false;
var div1 = null;
var div2 = null;
if (!fileBlockExists) {
fileBlock = $('<div/>');
fileBlock.addClass('submission__answer__file__block ' + className);
fileBlock.appendTo(sel.find('.submission__answer__files').first());
}
if (view.filesType === 'image') {
file = $('<img />');
file.addClass('submission__answer__file submission--image');
file.attr('alt', gettext("The image associated with this submission:") + ' #' + (filenum + 1));
file.attr('src', url);
div1 = $('<div/>');
div1.addClass('submission__file__description__label');
div1.text(view.filesDescriptions[filenum] + ':');
div1.appendTo(fileBlock);
img = $('<img />');
img.addClass('submission__answer__file submission--image');
img.attr('src', url);
div2 = $('<div/>');
div2.html(img);
div2.appendTo(fileBlock);
} else {
file = $('<a />', {
href: url,
text: gettext("View the file associated with this submission:") + ' #' + (filenum + 1)
text: view.filesDescriptions[filenum]
});
file.addClass('submission__answer__file submission--file');
file.attr('target', '_blank');
}
if (file) {
if (fileBlockExists) {
sel.find("." + className).html(file);
} else {
fileBlock = $('<div/>');
fileBlock.addClass(className);
file.appendTo(fileBlock);
fileBlock.appendTo(sel.find('.submission__answer__files').first());
}
file.appendTo(fileBlock);
}
return url;
......
......@@ -519,8 +519,29 @@ if (typeof OpenAssessment.Server === "undefined" || !OpenAssessment.Server) {
url: url,
data: JSON.stringify({}),
contentType: jsonContentType
}).done(function() {
defer.resolve();
}).done(function(data) {
if (data.success) { defer.resolve(); }
else { defer.rejectWith(this, [data.msg]); }
}).fail(function() {
defer.rejectWith(this, [gettext('Server error.')]);
});
}).promise();
},
/**
* Sends request to server to save descriptions for each uploaded file.
*/
saveFilesDescriptions: function(descriptions) {
var url = this.url('save_files_descriptions');
return $.Deferred(function(defer) {
$.ajax({
type: "POST",
url: url,
data: JSON.stringify({descriptions: descriptions}),
contentType: jsonContentType
}).done(function(data) {
if (data.success) { defer.resolve(); }
else { defer.rejectWith(this, [data.msg]); }
}).fail(function() {
defer.rejectWith(this, [gettext('Server error.')]);
});
......
......@@ -1048,6 +1048,7 @@
@extend %action-2;
@include text-align(center);
@include float(right);
display: inline-block;
margin: ($baseline-v/2) 0;
box-shadow: none;
......
......@@ -554,6 +554,14 @@
}
}
.submission__file__description__label {
margin-bottom: 5px;
}
.submission__answer__file__block {
margin-bottom: 8px;
}
// --------------------
// response
// --------------------
......@@ -573,9 +581,26 @@
@extend %text-sr;
}
textarea {
@extend %ui-content-longanswer;
min-height: ($baseline-v*10);
.files__descriptions {
display: none;
.submission__file__description {
padding-bottom: 10px;
}
}
.submission__answer__part__text {
textarea {
@extend %ui-content-longanswer;
min-height: ($baseline-v*10);
}
}
.submission__file__description {
textarea {
@extend %ui-content-longanswer;
min-height: ($baseline-v*4);
}
}
.tip {
......
......@@ -104,9 +104,14 @@ class SubmissionMixin(object):
status_text = self._(u'Multiple submissions are not allowed.')
if not workflow:
try:
try:
saved_files_descriptions = json.loads(self.saved_files_descriptions)
except ValueError:
saved_files_descriptions = None
submission = self.create_submission(
student_item_dict,
student_sub_data
student_sub_data,
saved_files_descriptions
)
except api.SubmissionRequestError as err:
......@@ -187,25 +192,73 @@ class SubmissionMixin(object):
else:
return {'success': False, 'msg': self._(u"This response was not submitted.")}
def create_submission(self, student_item_dict, student_sub_data):
@XBlock.json_handler
def save_files_descriptions(self, data, suffix=''):
"""
Save the descriptions for each uploaded file.
Args:
data (dict): Data should have a single key 'descriptions' that contains
the texts for each uploaded file.
suffix (str): Not used.
Returns:
dict: Contains a bool 'success' and unicode string 'msg'.
"""
if 'descriptions' in data:
descriptions = data['descriptions']
if isinstance(descriptions, list):
all_description_correct = True
for description in descriptions:
if not isinstance(description, basestring):
all_description_correct = False
break
if all_description_correct:
try:
self.saved_files_descriptions = json.dumps(descriptions)
# Emit analytics event...
self.runtime.publish(
self,
"openassessmentblock.save_files_descriptions",
{"saved_response": self.saved_files_descriptions}
)
except:
return {'success': False, 'msg': self._(u"Files descriptions could not be saved.")}
else:
return {'success': True, 'msg': u''}
return {'success': False, 'msg': self._(u"Files descriptions were not submitted.")}
def create_submission(self, student_item_dict, student_sub_data, files_descriptions=None):
# Store the student's response text in a JSON-encodable dict
# so that later we can add additional response fields.
files_descriptions = files_descriptions if files_descriptions else []
student_sub_dict = prepare_submission_for_serialization(student_sub_data)
if self.file_upload_type:
student_sub_dict['file_keys'] = []
student_sub_dict['files_descriptions'] = []
for i in range(self.MAX_FILES_COUNT):
key_to_save = ''
file_description = ''
item_key = self._get_student_item_key(i)
try:
url = file_upload_api.get_download_url(item_key)
if url:
key_to_save = item_key
try:
file_description = files_descriptions[i]
except IndexError:
pass
except FileUploadError:
pass
if key_to_save:
student_sub_dict['file_keys'].append(key_to_save)
student_sub_dict['files_descriptions'].append(file_description)
else:
break
......@@ -355,18 +408,24 @@ class SubmissionMixin(object):
"""
urls = []
if 'file_keys' in submission['answer']:
keys = submission['answer'].get('file_keys', '')
for key in keys:
keys = submission['answer'].get('file_keys', [])
descriptions = submission['answer'].get('files_descriptions', [])
for idx, key in enumerate(keys):
url = self._get_url_by_file_key(key)
if url:
urls.append(url)
description = ''
try:
description = descriptions[idx]
except IndexError:
pass
urls.append((url, description))
else:
break
elif 'file_key' in submission['answer']:
key = submission['answer'].get('file_key', '')
url = self._get_url_by_file_key(key)
if url:
urls.append(url)
urls.append((url, ''))
return urls
@staticmethod
......@@ -455,11 +514,21 @@ class SubmissionMixin(object):
context['allow_latex'] = self.allow_latex
if self.file_upload_type:
try:
saved_files_descriptions = json.loads(self.saved_files_descriptions)
except ValueError:
saved_files_descriptions = []
context['file_urls'] = []
for i in range(self.MAX_FILES_COUNT):
file_url = self._get_download_url(i)
file_description = ''
if file_url:
context['file_urls'].append(file_url)
try:
file_description = saved_files_descriptions[i]
except IndexError:
pass
context['file_urls'].append((file_url, file_description))
else:
break
if self.file_upload_type == 'custom':
......
<openassessment>
<openassessment file_upload_type="pdf-and-image">
<title>Open Assessment Test</title>
<prompts>
<prompt>
......
# -*- coding: utf-8 -*-
"""
Test that the student can save a files descriptions.
"""
import json
import mock
from .base import XBlockHandlerTestCase, scenario
class SaveFilesDescriptionsTest(XBlockHandlerTestCase):
@scenario('data/save_scenario.xml', user_id="Daniels")
def test_save_files_descriptions_blank(self, xblock):
resp = self.request(xblock, 'save_files_descriptions', json.dumps({}))
self.assertIn('descriptions were not submitted', resp)
@scenario('data/save_scenario.xml', user_id="Perleman")
def test_save_files_descriptions(self, xblock):
# Save the response
descriptions = [u"Ѕраѓтаиѕ! ГоиіБЂт, Щэ ↁіиэ іи Нэll!", u"Ѕраѓтаиѕ! ГоиіБЂт, Щэ ↁіиэ іи Нэll!"]
payload = json.dumps({'descriptions': descriptions})
resp = self.request(xblock, 'save_files_descriptions', payload, response_format="json")
self.assertTrue(resp['success'])
self.assertEqual(resp['msg'], u'')
# Reload the submission UI
xblock._get_download_url = mock.MagicMock(side_effect=lambda i: "https://img-url/%d" % i)
resp = self.request(xblock, 'render_submission', json.dumps({}))
self.assertIn(descriptions[0], resp.decode('utf-8'))
self.assertIn(descriptions[1], resp.decode('utf-8'))
@scenario('data/save_scenario.xml', user_id="Valchek")
def test_overwrite_files_descriptions(self, xblock):
descriptions1 = [u"Ѕраѓтаиѕ! ГоиіБЂт, Щэ ↁіиэ іи Нэll!", u"Ѕраѓтаиѕ! ГоиіБЂт, Щэ ↁіиэ іи Нэll!"]
payload = json.dumps({'descriptions': descriptions1})
self.request(xblock, 'save_files_descriptions', payload, response_format="json")
descriptions2 = [u"test1", u"test2"]
payload = json.dumps({'descriptions': descriptions2})
self.request(xblock, 'save_files_descriptions', payload, response_format="json")
# Reload the submission UI
xblock._get_download_url = mock.MagicMock(side_effect=lambda i: "https://img-url/%d" % i)
resp = self.request(xblock, 'render_submission', json.dumps({}))
self.assertNotIn(descriptions1[0], resp.decode('utf-8'))
self.assertNotIn(descriptions1[1], resp.decode('utf-8'))
self.assertIn(descriptions2[0], resp.decode('utf-8'))
self.assertIn(descriptions2[1], resp.decode('utf-8'))
......@@ -408,7 +408,8 @@ class TestCourseStaff(XBlockHandlerTestCase):
# Create an image submission for Bob, and corresponding workflow.
self._create_submission(bob_item, {
'text': "Bob Answer",
'file_keys': ["test_key"]
'file_keys': ["test_key"],
'files_descriptions': ["test_description"]
}, ['self'])
# Mock the file upload API to avoid hitting S3
......@@ -423,7 +424,7 @@ class TestCourseStaff(XBlockHandlerTestCase):
file_api.get_download_url.assert_called_with("test_key")
# Check the context passed to the template
self.assertEquals(['http://www.example.com/image.jpeg'], context['staff_file_urls'])
self.assertEquals([('http://www.example.com/image.jpeg', 'test_description')], context['staff_file_urls'])
self.assertEquals('image', context['file_upload_type'])
# Check the fully rendered template
......@@ -446,13 +447,15 @@ class TestCourseStaff(XBlockHandlerTestCase):
bob_item["item_id"] = xblock.scope_ids.usage_id
file_keys = ["test_key0", "test_key1", "test_key2"]
files_descriptions = ["test_description0", "test_description1", "test_description2"]
images = ["http://www.example.com/image%d.jpeg" % i for i in range(3)]
file_keys_with_images = dict(zip(file_keys, images))
# Create an image submission for Bob, and corresponding workflow.
self._create_submission(bob_item, {
'text': "Bob Answer",
'file_keys': file_keys
'file_keys': file_keys,
'files_descriptions': files_descriptions
}, ['self'])
# Mock the file upload API to avoid hitting S3
......@@ -470,7 +473,8 @@ class TestCourseStaff(XBlockHandlerTestCase):
file_api.get_download_url.assert_has_calls(calls)
# Check the context passed to the template
self.assertEquals(images, context['staff_file_urls'])
self.assertEquals([(image, "test_description%d" % i) for i, image in enumerate(images)],
context['staff_file_urls'])
self.assertEquals('image', context['file_upload_type'])
# Check the fully rendered template
......@@ -478,6 +482,7 @@ class TestCourseStaff(XBlockHandlerTestCase):
resp = self.request(xblock, "render_student_info", payload)
for i in range(3):
self.assertIn("http://www.example.com/image%d.jpeg" % i, resp)
self.assertIn("test_description%d" % i, resp)
@scenario('data/self_only_scenario.xml', user_id='Bob')
def test_staff_area_student_info_file_download_url_error(self, xblock):
......
......@@ -158,6 +158,38 @@ class SubmissionPage(OpenAssessmentPage):
self.wait_for_element_visibility(".submission__answer__upload", "File select button is present")
self.q(css=".submission__answer__upload").results[0].send_keys(file_path_name)
def add_file_description(self, file_num, description):
"""
Submit a description for some file.
Args:
file_num (integer): file number
description (string): file description
"""
textarea_element = self._bounded_selector("textarea.file__description__%d" % file_num)
self.wait_for_element_visibility(textarea_element, "Textarea is present")
self.q(css=textarea_element).fill(description)
@property
def upload_file_button_is_enabled(self):
"""
Check if 'Upload files' button is enabled
Returns:
bool
"""
return self.q(css="button.file__upload").attrs('disabled') == ['false']
@property
def upload_file_button_is_disabled(self):
"""
Check if 'Upload files' button is disabled
Returns:
bool
"""
return self.q(css="button.file__upload").attrs('disabled') == ['true']
def upload_file(self):
"""
Upload the selected file
......
......@@ -743,9 +743,18 @@ class FileUploadTest(OpenAssessmentTest):
# trying to upload a acceptable file
readme1 = os.path.dirname(os.path.realpath(__file__)) + '/README.rst'
readme2 = readme1.replace('test/acceptance/', '') # There's another README located at ../../
files = ', '.join([readme1, readme2])
self.submission_page.visit().select_file(files)
self.assertFalse(self.submission_page.has_file_error)
self.assertTrue(self.submission_page.upload_file_button_is_disabled)
self.submission_page.add_file_description(0, 'file description 1')
self.assertTrue(self.submission_page.upload_file_button_is_disabled)
self.submission_page.add_file_description(1, 'file description 2')
self.assertTrue(self.submission_page.upload_file_button_is_enabled)
self.submission_page.upload_file()
self.assertTrue(self.submission_page.have_files_uploaded)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment