def to_current(workflow: Workflow, stage: Stage, ident: str, flash: bool = True) -> Response: next_stage = workflow.current_stage if flash: alerts.flash_warning(f'Please {next_stage.label} before proceeding.') return to_stage(workflow, next_stage, ident)
def test_safe_flash_message(self, mock_flash): """Flash a simple message that is HTML-safe.""" alerts.flash_warning('The message', safe=True) (data, category), kwargs = mock_flash.call_args self.assertEqual(data['message'], 'The message') self.assertIsInstance(data['message'], Markup) self.assertTrue(data['dismissable']) self.assertEqual(category, alerts.WARNING)
def test_flash_message_no_dismiss(self, mock_flash): """Flash a simple message that can't be dismissed.""" alerts.flash_warning('The message', dismissable=False) (data, category), kwargs = mock_flash.call_args self.assertEqual(data['message'], 'The message') self.assertIsInstance(data['message'], str) self.assertFalse(data['dismissable']) self.assertEqual(category, alerts.WARNING)
def test_flash_message(self, mock_flash): """Just flash a simple message.""" alerts.flash_warning('The message') (data, category), kwargs = mock_flash.call_args self.assertEqual(data['message'], 'The message') self.assertIsInstance(data['message'], str) self.assertTrue(data['dismissable']) self.assertEqual(category, alerts.WARNING)
def _new_file(params: MultiDict, pointer: FileStorage, session: Session, submission: Submission, rdata: Dict[str, Any], token: str) \ -> Response: """ Handle a POST request with a new file to add to an existing upload package. This occurs in the case that there is already an upload workspace associated with the submission. See the :attr:`Submission.source_content` attribute, which is set using :class:`SetUploadPackage`. Parameters ---------- params : :class:`MultiDict` The form data from the request. pointer : :class:`FileStorage` The file upload stream. session : :class:`Session` The authenticated session for the request. submission : :class:`Submission` The submission for which the upload is being made. Returns ------- dict Response data, to render in template. int HTTP status code. This should be ``303``, unless something goes wrong. dict Extra headers to add/update on the response. This should include the `Location` header for use in the 303 redirect response. """ submitter, client = user_and_client_from_session(session) fm = FileManager.current_session() upload_id = submission.source_content.identifier # Using a form object provides some extra assurance that this is a legit # request; provides CSRF goodies. params['file'] = pointer form = UploadForm(params) rdata['form'] = form if not form.validate(): logger.error('Invalid upload form: %s', form.errors) alerts.flash_failure("Something went wrong. Please try again.", title="Whoops") # redirect = url_for('ui.file_upload', # submission_id=submission.submission_id) # return {}, status.SEE_OTHER, {'Location': redirect} logger.debug('Invalid form data') raise BadRequest(rdata) ancillary: bool = form.ancillary.data try: stat = fm.add_file(upload_id, pointer, token, ancillary=ancillary) except exceptions.RequestFailed as e: try: e_data = e.response.json() except Exception: e_data = None if e_data is not None and 'reason' in e_data: alerts.flash_failure( Markup('There was a problem carrying out your request:' f' {e_data["reason"]}. {PLEASE_CONTACT_SUPPORT}')) raise BadRequest(rdata) alerts.flash_failure( Markup('There was a problem carrying out your request. Please try' f' again. {PLEASE_CONTACT_SUPPORT}')) logger.debug('Failed to add file: %s', ) logger.error(traceback.format_exc()) raise InternalServerError(rdata) from e submission = _update(form, submission, stat, submitter, client, rdata) converted_size = tidy_filesize(stat.size) if stat.status is Upload.Status.READY: alerts.flash_success( f'Uploaded {pointer.filename} successfully. Total submission' f' package size is {converted_size}', title='Upload successful') elif stat.status is Upload.Status.READY_WITH_WARNINGS: alerts.flash_warning( f'Uploaded {pointer.filename} successfully. Total submission' f' package size is {converted_size}. See below for warnings.', title='Upload complete, with warnings') elif stat.status is Upload.Status.ERRORS: alerts.flash_warning( f'Uploaded {pointer.filename} successfully. Total submission' f' package size is {converted_size}. See below for errors.', title='Upload complete, with errors') status_data = stat.to_dict() alerts.flash_hidden(status_data, '_status') loc = url_for('ui.file_upload', submission_id=submission.submission_id) return {}, status.SEE_OTHER, {'Location': loc}
def delete(method: str, params: MultiDict, session: Session, submission_id: int, token: Optional[str] = None, **kwargs) -> Response: """ Handle a request to delete a file. The file will only be deleted if a POST request is made that also contains the ``confirmed`` parameter. The process can be initiated with a GET request that contains the ``path`` (key) for the file to be deleted. For example, a button on the upload interface may link to the deletion route with the file path as a query parameter. This will generate a deletion confirmation form, which can be POSTed to complete the action. Parameters ---------- method : str ``GET`` or ``POST`` params : :class:`MultiDict` The query or form data from the request. session : :class:`Session` The authenticated session for the request. submission_id : int The identifier of the submission for which the deletion is being made. token : str The original (encrypted) auth token on the request. Used to perform subrequests to the file management service. Returns ------- dict Response data, to render in template. int HTTP status code. This should be ``200`` or ``303``, unless something goes wrong. dict Extra headers to add/update on the response. This should include the `Location` header for use in the 303 redirect response, if applicable. """ if token is None: logger.debug('Missing auth token') raise BadRequest('Missing auth token') submission, submission_events = load_submission(submission_id) upload_id = submission.source_content.identifier submitter, client = user_and_client_from_session(session) rdata = {'submission': submission, 'submission_id': submission_id} if method == 'GET': # The only thing that we want to get from the request params on a GET # request is the file path. This way there is no way for a GET request # to trigger actual deletion. The user must explicitly indicate via # a valid POST that the file should in fact be deleted. params = MultiDict({'file_path': params['path']}) form = DeleteFileForm(params) rdata.update({'form': form}) if method == 'POST': if not (form.validate() and form.confirmed.data): logger.debug('Invalid form data') raise BadRequest(rdata) stat: Optional[Upload] = None try: file_path = form.file_path.data stat = FileManager.delete_file(upload_id, file_path, token) alerts.flash_success( f'File <code>{form.file_path.data}</code> was deleted' ' successfully', title='Deleted file successfully', safe=True) except exceptions.RequestForbidden: alerts.flash_failure( Markup( 'There was a problem authorizing your request. Please try' f' again. {PLEASE_CONTACT_SUPPORT}')) except exceptions.BadRequest: alerts.flash_warning( Markup('Something odd happened when processing your request.' f'{PLEASE_CONTACT_SUPPORT}')) except exceptions.RequestFailed: alerts.flash_failure( Markup( 'There was a problem carrying out your request. Please try' f' again. {PLEASE_CONTACT_SUPPORT}')) if stat is not None: command = UpdateUploadPackage(creator=submitter, checksum=stat.checksum, uncompressed_size=stat.size, source_format=stat.source_format) if not validate_command(form, command, submission): logger.debug('Command validation failed') raise BadRequest(rdata) commands = [command] if submission.submitter_compiled_preview: commands.append(UnConfirmCompiledPreview(creator=submitter)) try: submission, _ = save(*commands, submission_id=submission_id) except SaveError: alerts.flash_failure( Markup( 'There was a problem carrying out your request. Please try' f' again. {PLEASE_CONTACT_SUPPORT}')) redirect = url_for('ui.file_upload', submission_id=submission_id) return {}, status.SEE_OTHER, {'Location': redirect} return rdata, status.OK, {}
def delete_all(method: str, params: MultiDict, session: Session, submission_id: int, token: Optional[str] = None, **kwargs) -> Response: """ Handle a request to delete all files in the workspace. Parameters ---------- method : str ``GET`` or ``POST`` params : :class:`MultiDict` The query or form data from the request. session : :class:`Session` The authenticated session for the request. submission_id : int The identifier of the submission for which the deletion is being made. token : str The original (encrypted) auth token on the request. Used to perform subrequests to the file management service. Returns ------- dict Response data, to render in template. int HTTP status code. This should be ``200`` or ``303``, unless something goes wrong. dict Extra headers to add/update on the response. This should include the `Location` header for use in the 303 redirect response, if applicable. """ if token is None: logger.debug('Missing auth token') raise BadRequest('Missing auth token') submission, submission_events = load_submission(submission_id) upload_id = submission.source_content.identifier submitter, client = user_and_client_from_session(session) rdata = {'submission': submission, 'submission_id': submission_id} if method == 'GET': form = DeleteAllFilesForm() rdata.update({'form': form}) return rdata, status.OK, {} elif method == 'POST': form = DeleteAllFilesForm(params) rdata.update({'form': form}) if not (form.validate() and form.confirmed.data): logger.debug('Invalid form data') raise BadRequest(rdata) try: stat = FileManager.delete_all(upload_id, token) except exceptions.RequestForbidden as e: alerts.flash_failure( Markup( 'There was a problem authorizing your request. Please try' f' again. {PLEASE_CONTACT_SUPPORT}')) logger.error('Encountered RequestForbidden: %s', e) except exceptions.BadRequest as e: alerts.flash_warning( Markup('Something odd happened when processing your request.' f'{PLEASE_CONTACT_SUPPORT}')) logger.error('Encountered BadRequest: %s', e) except exceptions.RequestFailed as e: alerts.flash_failure( Markup( 'There was a problem carrying out your request. Please try' f' again. {PLEASE_CONTACT_SUPPORT}')) logger.error('Encountered RequestFailed: %s', e) command = UpdateUploadPackage(creator=submitter, client=client, checksum=stat.checksum, uncompressed_size=stat.size, source_format=stat.source_format) commands = [command] if submission.submitter_compiled_preview: commands.append(UnConfirmCompiledPreview(creator=submitter)) if not validate_command(form, command, submission): logger.debug('Command validation failed') raise BadRequest(rdata) try: submission, _ = save(*commands, submission_id=submission_id) except SaveError: alerts.flash_failure( Markup( 'There was a problem carrying out your request. Please try' f' again. {PLEASE_CONTACT_SUPPORT}')) redirect = url_for('ui.file_upload', submission_id=submission_id) return {}, status.SEE_OTHER, {'Location': redirect} raise MethodNotAllowed('Method not supported')
def cross_list(method: str, params: MultiDict, session: Session, submission_id: int, **kwargs) -> Response: submitter, client = user_and_client_from_session(session) submission, submission_events = load_submission(submission_id) form = ClassificationForm(params) form.operation._value = lambda: form.operation.data form.filter_choices(submission, session) # Create a formset to render removal option. # # We need forms for existing secondaries, to generate removal requests. # When the forms in the formset are submitted, they are handled as the # primary form in the POST request to this controller. formset = ClassificationForm.formset(submission) _primary_category = submission.primary_classification.category _primary = taxonomy.CATEGORIES[_primary_category] response_data = { 'submission_id': submission_id, 'submission': submission, 'submitter': submitter, 'client': client, 'form': form, 'formset': formset, 'primary': { 'id': submission.primary_classification.category, 'name': _primary['name'] }, } if method == 'POST': # Make sure that the user is not attempting to move to a different # step. # # Since the interface provides an "add" button to add cross-list # categories, we only want to handle the form data if the user is not # attempting to move to a different step. action = params.get('action') if not action: if not form.validate(): raise BadRequest(response_data) if form.operation.data == form.REMOVE: command_type = RemoveSecondaryClassification else: command_type = AddSecondaryClassification command = command_type(category=form.category.data, creator=submitter, client=client) if not validate_command(form, command, submission, 'category'): raise BadRequest(response_data) try: submission, _ = save(command, submission_id=submission_id) response_data['submission'] = submission except SaveError as e: raise InternalServerError(response_data) from e # Re-build the formset to reflect changes that we just made, and # generate a fresh form for adding another secondary. The POSTed # data should now be reflected in the formset. response_data['formset'] = ClassificationForm.formset(submission) form = ClassificationForm() form.operation._value = lambda: form.operation.data form.filter_choices(submission, session) response_data['form'] = form # Warn the user if they have too many secondaries. if len(submission.secondary_categories) > 3: alerts.flash_warning( Markup( 'Adding more than three cross-list classifications will' ' result in a delay in the acceptance of your submission.' )) if action in ['previous', 'save_exit', 'next']: return response_data, status.SEE_OTHER, {} return response_data, status.OK, {}