Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: mfe file actions with legacy refactor #2059

Merged
merged 20 commits into from
Oct 16, 2023
Merged
Show file tree
Hide file tree
Changes from 19 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions openassessment/xblock/apis/submissions/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,15 @@ class StudioPreviewException(Exception):

class MultipleSubmissionsException(Exception):
pass


class DeleteNotAllowed(Exception):
pass


class OnlyOneFileAllowedException(Exception):
pass


class UnsupportedFileTypeException(Exception):
pass
11 changes: 11 additions & 0 deletions openassessment/xblock/apis/submissions/file_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -147,3 +147,14 @@ def get_all_upload_urls_for_user(self, username_or_email):

def get_allowed_file_types_or_preset(self):
return self._block.get_allowed_file_types_or_preset

def has_any_file_in_upload_space(self):
# Here we check if there are existing file uploads by checking for
# an existing download url for any of the upload slots.
# Note that we can't use self.saved_files_descriptions because that
# is populated before files are actually uploaded
for potential_file_index in range(self.max_allowed_uploads):
file_url = self.get_download_url(potential_file_index)
if file_url:
return True
return False
107 changes: 104 additions & 3 deletions openassessment/xblock/apis/submissions/submissions_actions.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,26 @@

import json
import logging
import os
from submissions.api import Submission, SubmissionError, SubmissionRequestError

from openassessment.fileupload.exceptions import FileUploadError
from openassessment.workflow.errors import AssessmentWorkflowError
from openassessment.xblock.apis.submissions.errors import (
DeleteNotAllowed,
EmptySubmissionError,
NoTeamToCreateSubmissionForError,
DraftSaveException,
OnlyOneFileAllowedException,
SubmissionValidationException,
AnswerTooLongException,
StudioPreviewException,
MultipleSubmissionsException,
SubmitInternalError
SubmitInternalError,
UnsupportedFileTypeException
)
from openassessment.xblock.utils.validation import validate_submission

from openassessment.workflow.errors import AssessmentWorkflowError
from openassessment.xblock.utils.validation import validate_submission
from openassessment.xblock.utils.data_conversion import (
format_files_for_submission,
prepare_submission_for_serialization,
Expand Down Expand Up @@ -256,3 +261,99 @@ def save_submission_draft(student_submission_data, block_config_data, block_subm
)
except Exception as e: # pylint: disable=broad-except
raise DraftSaveException from e


def append_file_data(file_data, block_config, submission_info):
"""
Appends a list of file data to the current block state

Args:
block_config (ORAConfigAPI)
submission_info (SubmissionAPI)
files_to_append (list of {
'description': (str)
'name': (str)
'size': (int)
})
"""
try:
new_files = submission_info.files.file_manager.append_uploads(*file_data)
except FileUploadError as exc:
logger.exception(
"append_file_data: file description for data %s failed with error %s", file_data, exc, exc_info=True
)
raise
except Exception as exc: # pylint: disable=broad-except
logger.exception(
"append_file_data: unhandled exception for data %s. Error: %s", file_data, exc, exc_info=True
)
raise FileUploadError(exc) from exc

# Emit analytics event...
block_config.publish_event(
"openassessmentblock.save_files_descriptions",
{"saved_response": submission_info.files.saved_files_descriptions},
)
return new_files


def remove_uploaded_file(file_index, block_config, submission_info):
"""
Removes uploaded user file.
"""
file_key = submission_info.files.get_file_key(file_index)
if not submission_info.files.can_delete_file(file_index):
raise DeleteNotAllowed()
try:
submission_info.files.delete_uploaded_file(file_index)
# Emit analytics event...
block_config.publish_event(
"openassessmentblock.remove_uploaded_file",
{"student_item_key": file_key},
)
logger.debug("Deleted file %s", file_key)
except FileUploadError as exc:
logger.exception(
"FileUploadError: Error when deleting file %s : %s",
file_key,
exc,
exc_info=True,
)
raise
except Exception as exc: # pylint: disable=broad-except
logger.exception(
"FileUploadError: unhandled exception for %s. Error: %s",
file_key,
exc,
exc_info=True,
)
raise FileUploadError(exc) from exc


def get_upload_url(content_type, file_name, file_index, block_config, submission_info):
"""
Request a URL to be used for uploading content for a given file

Returns:
A URL to be used to upload content associated with this submission.

"""
if not block_config.allow_multiple_files:
if submission_info.files.has_any_file_in_upload_space():
jansenk marked this conversation as resolved.
Show resolved Hide resolved
raise OnlyOneFileAllowedException()

_, file_ext = os.path.splitext(file_name)
file_ext = file_ext.strip(".") if file_ext else None

# Validate that there are no data issues and file type is allowed
if not submission_info.files.is_supported_upload_type(file_ext, content_type):
raise UnsupportedFileTypeException(file_ext)

# Attempt to upload
try:
key = submission_info.files.get_file_key(file_index)
url = submission_info.files.get_upload_url(key, content_type)
return url
except FileUploadError:
logger.exception("FileUploadError:Error retrieving upload URL")
raise
108 changes: 87 additions & 21 deletions openassessment/xblock/ui_mixins/legacy/handlers_mixin.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,35 +5,41 @@
from xblock.core import XBlock
from submissions import api as submissions_api

from openassessment.fileupload.exceptions import FileUploadError
from openassessment.xblock.apis.submissions import submissions_actions
from openassessment.xblock.apis.submissions.errors import (
EmptySubmissionError,
AnswerTooLongException,
DeleteNotAllowed,
DraftSaveException,
EmptySubmissionError,
MultipleSubmissionsException,
OnlyOneFileAllowedException,
StudioPreviewException,
SubmissionValidationException,
AnswerTooLongException,
SubmitInternalError,
StudioPreviewException,
MultipleSubmissionsException
UnsupportedFileTypeException
)
from openassessment.xblock.staff_area_mixin import require_course_staff
from openassessment.xblock.ui_mixins.legacy.peer_assessments.actions import peer_assess
from openassessment.xblock.ui_mixins.legacy.self_assessments.actions import self_assess
from openassessment.xblock.ui_mixins.legacy.staff_assessments.actions import (
do_staff_assessment,
staff_assess,
)
from openassessment.xblock.ui_mixins.legacy.staff_assessments.actions import do_staff_assessment, staff_assess
from openassessment.xblock.ui_mixins.legacy.student_training.actions import training_assess
from openassessment.xblock.ui_mixins.legacy.submissions.serializers import SaveFilesDescriptionRequestSerializer
from openassessment.xblock.utils.data_conversion import verify_assessment_parameters
from openassessment.xblock.ui_mixins.legacy.submissions.file_actions import (
save_files_descriptions,
upload_url,
download_url,
remove_uploaded_file,
)
from openassessment.xblock.apis.submissions import submissions_actions

logger = logging.getLogger(__name__)


def _safe_read_file_index(data, default):
""" Helper for remove_uploaded_file handler """
file_index = data.get("filenum", default)
try:
file_index = int(file_index)
except ValueError:
file_index = default
return file_index


class LegacyHandlersMixin:
"""
Exposes actions (@XBlock.json_handlers) used in our legacy ORA UI
Expand Down Expand Up @@ -111,25 +117,85 @@ def save_submission(self, data, suffix=""): # pylint: disable=unused-argument
return {'success': True, 'msg': ''}

# File uploads

@XBlock.json_handler
def save_files_descriptions(self, data, suffix=""): # pylint: disable=unused-argument
return save_files_descriptions(self.config_data, self.submission_data, data)
serializer = SaveFilesDescriptionRequestSerializer(data=data)
if not serializer.is_valid():
return {
'success': False,
'msg': self.config_data.translate('File descriptions were not submitted.'),
}
nsprenkle marked this conversation as resolved.
Show resolved Hide resolved

try:
submissions_actions.append_file_data(
serializer.validated_data['fileMetadata'],
self.config_data,
self.submission_data,
)
except FileUploadError:
return {
'success': False,
'msg': self.config_data.translate("Files metadata could not be saved.")
}
else:
return {'success': True, 'msg': ''}

@XBlock.json_handler
def upload_url(self, data, suffix=""): # pylint: disable=unused-argument
return upload_url(self.config_data, self.submission_data, data)
if "contentType" not in data or "filename" not in data:
return {
"success": False,
"msg": self.config_data.translate("There was an error uploading your file."),
}
file_index = _safe_read_file_index(data, 0)
try:
url = submissions_actions.get_upload_url(
data['contentType'],
data['filename'],
file_index,
self.config_data,
self.submission_data,
)
except OnlyOneFileAllowedException:
return {
"success": False,
"msg": self.config_data.translate("Only a single file upload is allowed for this assessment."),
}
except UnsupportedFileTypeException:
return {
"success": False,
"msg": self.config_data.translate(
"File upload failed: unsupported file type."
"Only the supported file types can be uploaded."
"If you have questions, please reach out to the course team."
),
}

except FileUploadError:
return {
"success": False,
"msg": self.config_data.translate("Error retrieving upload URL."),
}
else:
return {'success': True, 'url': url}

@XBlock.json_handler
def download_url(self, data, suffix=""): # pylint: disable=unused-argument
return download_url(self.submission_data, data)
file_index = _safe_read_file_index(data, 0)
file_url = self.submission_data.files.get_download_url(file_index)
return {"success": True, "url": file_url}

@XBlock.json_handler
def remove_uploaded_file(self, data, suffix=""): # pylint: disable=unused-argument
return remove_uploaded_file(self.config_data, self.submission_data, data)
file_index = _safe_read_file_index(data, -1)
try:
submissions_actions.remove_uploaded_file(file_index, self.config_data, self.submission_data, )
except (FileUploadError, DeleteNotAllowed):
return {'success': False}
else:
return {'success': True}

# Assessments

@XBlock.json_handler
@verify_assessment_parameters
def peer_assess(self, data, suffix=""): # pylint: disable=unused-argument
Expand Down
Loading
Loading