def async_save(self, *events: Event, submission_id: int = -1) -> None:
    """
    Save/emit new events.

    Parameters
    ----------
    events
        Each item is an :class:`.Event`.
    submission_id : int
        Identifier of the submission to which the commands/events apply.

    """
    if submission_id < 0:
        raise RuntimeError('Invalid submission ID')
    try:
        save(*events, submission_id=submission_id)
    except NothingToDo as e:
        logger.debug('No events to save, move along: %s', e)
    except classic.Unavailable as e:
        raise Recoverable('Database is not available; try again') from e
    except classic.ConsistencyError as e:
        logger.error('Encountered a ConsistencyError; could not save: %s', e)
        raise Failed('Encountered a consistency error') from e
    except Exception as e:
        raise Failed('Unhandled exception: %s' % e) from e
示例#2
0
def cancel_request(method: str, params: MultiDict, session: Session,
                   submission_id: int, request_id: str, **kwargs) -> Response:
    submission, submission_events = load_submission(submission_id)

    # if request_type == WithdrawalRequest.NAME.lower():
    #     request_klass = WithdrawalRequest
    # elif request_type == CrossListClassificationRequest.NAME.lower():
    #     request_klass = CrossListClassificationRequest
    if request_id in submission.user_requests:
        user_request = submission.user_requests[request_id]
    else:
        raise NotFound('No such request')

    # # Get the most recent user request of this type.
    # this_request: Optional[UserRequest] = None
    # for user_request in submission.active_user_requests[::-1]:
    #     if isinstance(user_request, request_klass):
    #         this_request = user_request
    #         break
    # if this_request is None:
    #     raise NotFound('No such request')

    if not user_request.is_pending():
        raise BadRequest(f'Request is already {user_request.status}')

    response_data = {
        'submission': submission,
        'submission_id': submission.submission_id,
        'request_id': user_request.request_id,
        'user_request': user_request,
    }

    if method == 'GET':
        form = CancelRequestForm()
        response_data.update({'form': form})
        return response_data, status.OK, {}
    elif method == 'POST':
        form = CancelRequestForm(params)
        response_data.update({'form': form})
        if form.validate() and form.confirmed.data:
            user, client = user_and_client_from_session(session)
            command = CancelRequest(request_id=request_id,
                                    creator=user,
                                    client=client)
            if not validate_command(form, command, submission, 'confirmed'):
                raise BadRequest(response_data)

            try:
                save(command, submission_id=submission_id)
            except Exception as e:
                alerts.flash_failure("Whoops!" + str(e))
                raise InternalServerError(response_data) from e
            redirect = url_for('ui.create_submission')
            return {}, status.SEE_OTHER, {'Location': redirect}
        response_data.update({'form': form})
        raise BadRequest(response_data)
示例#3
0
def create(method: str, params: MultiDict, session: Session, *args,
           **kwargs) -> Response:
    """Create a new submission, and redirect to workflow."""
    submitter, client = user_and_client_from_session(session)
    response_data = {}
    if method == 'GET':     # Display a splash page.
        response_data['user_submissions'] \
            = _load_submissions_for_user(session.user.user_id)
        params = MultiDict()

    # We're using a form here for CSRF protection.
    form = CreateSubmissionForm(params)
    response_data['form'] = form

    if method == 'POST':
        if not form.validate():
            raise BadRequest(response_data)

        command = CreateSubmission(creator=submitter, client=client)
        if not validate_command(form, command):
            raise BadRequest(response_data)

        try:
            submission, _ = save(command)
        except SaveError as e:
            logger.error('Could not save command: %s', e)
            raise InternalServerError(response_data) from e

        loc = url_for('ui.verify_user', submission_id=submission.submission_id)
        return {}, status.SEE_OTHER, {'Location': loc}
    return response_data, status.OK, {}
示例#4
0
def _check_status(params: MultiDict, session: Session,  submission_id: int,
                  token: str, **kwargs) -> None:
    """
    Check for cases in which the preview already exists.

    This will catch cases in which the submission is PDF-only, or otherwise
    requires no further compilation.
    """
    submitter, client = user_and_client_from_session(session)
    submission, submission_events = load_submission(submission_id)

    if not submission.submitter_compiled_preview \
            and not _must_process(submission):

        form = CompilationForm(params)  # Providing CSRF protection.
        if not form.validate():
            raise BadRequest('Invalid request; please try again.')

        command = ConfirmCompiledPreview(creator=submitter, client=client)
        try:
            submission, _ = save(command, submission_id=submission_id)
        except SaveError as e:
            alerts.flash_failure(Markup(
                'There was a problem carrying out your request. Please'
                f' try again. {SUPPORT}'
            ))
            logger.error('Error while saving command %s: %s',
                         command.event_id, e)
            raise InternalServerError('Could not save changes') from e
示例#5
0
def request_withdrawal(method: str, params: MultiDict, session: Session,
                       submission_id: int, **kwargs) -> Response:
    """Request withdrawal of a paper."""
    submitter, client = user_and_client_from_session(session)
    logger.debug(f'method: {method}, submission: {submission_id}. {params}')

    # Will raise NotFound if there is no such submission.
    submission, _ = load_submission(submission_id)

    # The submission must be announced for this to be a withdrawal request.
    if not submission.is_announced:
        alerts.flash_failure(
            Markup(
                "Submission must first be announced. See "
                "<a href='https://arxiv.org/help/withdraw'>the arXiv help pages"
                "</a> for details."))
        loc = url_for('ui.create_submission')
        return {}, status.SEE_OTHER, {'Location': loc}

    # The form should be prepopulated based on the current state of the
    # submission.
    if method == 'GET':
        params = MultiDict({})

    params.setdefault("confirmed", False)
    form = WithdrawalForm(params)
    response_data = {
        'submission_id': submission_id,
        'submission': submission,
        'form': form,
    }

    if method == 'POST':
        # We require the user to confirm that they wish to proceed.
        if not form.validate():
            raise BadRequest(response_data)

        cmd = RequestWithdrawal(reason=form.withdrawal_reason.data,
                                creator=submitter,
                                client=client)
        if not validate_command(form, cmd, submission, 'withdrawal_reason'):
            raise BadRequest(response_data)

        if not form.confirmed.data:
            response_data['require_confirmation'] = True
            return response_data, status.OK, {}

        response_data['require_confirmation'] = True
        try:
            # Save the events created during form validation.
            submission, _ = save(cmd, submission_id=submission_id)
        except SaveError as e:
            raise InternalServerError(response_data) from e

        # Success! Send user back to the submission page.
        alerts.flash_success("Withdrawal request submitted.")
        status_url = url_for('ui.create_submission')
        return {}, status.SEE_OTHER, {'Location': status_url}
    logger.debug('Nothing to do, return 200')
    return response_data, status.OK, {}
示例#6
0
 def run(self, trigger: Trigger) -> None:
     """Execute the process synchronously."""
     events = []
     self.process.before_start(trigger, events.append)
     result = None
     logger.debug('%s started', self.process.name)
     for step in self.process.steps:
         try:
             result = self.do(step.name, result, trigger, events.append)
             self.process.on_success(step.name, trigger, events.append)
             logger.debug('%s:%s succeeded', self.process.name, step.name)
         except Exception:
             self.process.on_failure(step.name, trigger, events.append)
             logger.debug('%s:%s failed', self.process.name, step.name)
         finally:
             save(*events, submission_id=self.process.submission_id)
             events.clear()
示例#7
0
def _update(form: UploadForm,
            submission: Submission,
            stat: Upload,
            submitter: User,
            client: Optional[Client] = None,
            rdata: Dict[str, Any] = {}) -> Submission:
    """
    Update the :class:`.Submission` after an upload-related action.

    The submission is linked to the upload workspace via the
    :attr:`Submission.source_content` attribute. This is set using a
    :class:`SetUploadPackage` command. If the workspace identifier changes
    (e.g. on first upload), we want to execute :class:`SetUploadPackage` to
    make the association.

    Parameters
    ----------
    submission : :class:`Submission`
    _status : :class:`Upload`
    submitter : :class:`User`
    client : :class:`Client` or None

    """
    existing_upload = getattr(submission.source_content, 'identifier', None)

    commands = []
    if existing_upload == stat.identifier:
        command = UpdateUploadPackage(creator=submitter,
                                      client=client,
                                      checksum=stat.checksum,
                                      uncompressed_size=stat.size,
                                      compressed_size=stat.compressed_size,
                                      source_format=stat.source_format)
        commands.append(command)
        if submission.submitter_compiled_preview:
            commands.append(UnConfirmCompiledPreview(creator=submitter))
    else:
        command = SetUploadPackage(creator=submitter,
                                   client=client,
                                   identifier=stat.identifier,
                                   checksum=stat.checksum,
                                   compressed_size=stat.compressed_size,
                                   uncompressed_size=stat.size,
                                   source_format=stat.source_format)
        commands.append(command)

    if not validate_command(form, command, submission):
        logger.debug('Command validation failed')
        raise BadRequest(rdata)

    try:
        submission, _ = save(*commands, submission_id=submission.submission_id)
    except SaveError:
        alerts.flash_failure(
            Markup('There was a problem carrying out your request. Please try'
                   f' again. {PLEASE_CONTACT_SUPPORT}'))
    rdata['submission'] = submission
    return submission
示例#8
0
def authorship(method: str, params: MultiDict, session: Session,
               submission_id: int, **kwargs) -> Response:
    """Handle the authorship assertion view."""
    submitter, client = user_and_client_from_session(session)

    # Will raise NotFound if there is no such submission.
    submission, submission_events = load_submission(submission_id)

    # The form should be prepopulated based on the current state of the
    # submission.
    if method == 'GET':
        # Update form data based on the current state of the submission.
        if submission.submitter_is_author is not None:
            if submission.submitter_is_author:
                params['authorship'] = AuthorshipForm.YES
            else:
                params['authorship'] = AuthorshipForm.NO
            if submission.submitter_is_author is False:
                params['proxy'] = True

    form = AuthorshipForm(params)
    response_data = {
        'submission_id': submission_id,
        'form': form,
        'submission': submission,
        'submitter': submitter,
        'client': client,
    }

    if method == 'POST':
        if not form.validate():
            raise BadRequest(response_data)
        value = (form.authorship.data == form.YES)

        # No need to do this more than once.
        if submission.submitter_is_author != value:
            command = ConfirmAuthorship(creator=submitter,
                                        client=client,
                                        submitter_is_author=value)

            if not validate_command(form, command, submission, 'authorship'):
                raise BadRequest(response_data)

            try:
                # Create ConfirmAuthorship event
                submission, _ = save(command, submission_id=submission_id)
                response_data['submission'] = submission
            except SaveError as e:
                logger.error('Could not save command: %s', e)
                raise InternalServerError(response_data) from e

        if params.get('action') in ['previous', 'save_exit', 'next']:
            return response_data, status.SEE_OTHER, {}
    return response_data, status.OK, {}
示例#9
0
def delete(method: str, params: MultiDict, session: Session,
           submission_id: int, **kwargs) -> Response:
    """
    Delete a submission, replacement, or other request.

    We never really DELETE-delete anything. The workhorse is
    :class:`.Rollback`. For new submissions, this just makes the submission
    inactive (disappear from user views). For replacements, or other kinds of
    requests that happen after the first version is announced, the submission
    is simply reverted back to the state of the last announcement.

    """
    submission, submission_events = load_submission(submission_id)
    response_data = {
        'submission': submission,
        'submission_id': submission.submission_id,
    }

    if method == 'GET':
        form = DeleteForm()
        response_data.update({'form': form})
        return response_data, status.OK, {}
    elif method == 'POST':
        form = DeleteForm(params)
        response_data.update({'form': form})
        if form.validate() and form.confirmed.data:
            user, client = user_and_client_from_session(session)
            command = Rollback(creator=user, client=client)
            if not validate_command(form, command, submission, 'confirmed'):
                raise BadRequest(response_data)

            try:
                save(command, submission_id=submission_id)
            except Exception as e:
                alerts.flash_failure("Whoops!")
                raise InternalServerError(response_data) from e
            redirect = url_for('ui.create_submission')
            return {}, status.SEE_OTHER, {'Location': redirect}
        response_data.update({'form': form})
        raise BadRequest(response_data)
示例#10
0
def verify(method: str, params: MultiDict, session: Session,
           submission_id: int, **kwargs) -> Response:
    """
    Prompt the user to verify their contact information.

    Generates a `ConfirmContactInformation` event when valid data are POSTed.
    """
    logger.debug(f'method: {method}, submission: {submission_id}. {params}')
    submitter, client = user_and_client_from_session(session)

    # Will raise NotFound if there is no such submission.
    submission, _ = load_submission(submission_id)

    # Initialize the form with the current state of the submission.
    if method == 'GET':
        if submission.submitter_contact_verified:
            params['verify_user'] = '******'

    form = VerifyUserForm(params)
    response_data = {
        'submission_id': submission_id,
        'form': form,
        'submission': submission,
        'submitter': submitter,
        'user': session.user,  # We want the most up-to-date representation.
    }

    if method == 'POST':
        if not (form.validate() and form.verify_user.data):
            # Either the form data were invalid, or the user did not check the
            # "verify" box.
            raise BadRequest(response_data)

        logger.debug(f'Form is valid: {form.verify_user.data}')

        # Now that we have a submission, we can verify the user's contact
        # information. There is no need to do this more than once.
        if not submission.submitter_contact_verified:
            cmd = ConfirmContactInformation(creator=submitter, client=client)
            if not validate_command(form, cmd, submission, 'verify_user'):
                raise BadRequest(response_data)

            try:
                submission, _ = save(cmd, submission_id=submission_id)
            except SaveError as e:
                raise InternalServerError(response_data) from e
            response_data['submission'] = submission

        return response_data, status.SEE_OTHER, {}
    return response_data, status.OK, {}
示例#11
0
def classification(method: str, params: MultiDict, session: Session,
                   submission_id: int, **kwargs) -> Response:
    """Handle primary classification requests."""
    submitter, client = user_and_client_from_session(session)
    submission, submission_events = load_submission(submission_id)

    if method == 'GET':
        # Prepopulate the form based on the state of the submission.
        if submission.primary_classification \
                and submission.primary_classification.category:
            params['category'] = submission.primary_classification.category

        # Use the user's default category as the default for the form.
        params.setdefault('category', session.user.profile.default_category)

    params['operation'] = PrimaryClassificationForm.ADD

    form = PrimaryClassificationForm(params)
    form.filter_choices(submission, session)

    response_data = {
        'submission_id': submission_id,
        'submission': submission,
        'submitter': submitter,
        'client': client,
        'form': form
    }

    if method == 'POST':
        if not form.validate():
            raise BadRequest(response_data)

        command = SetPrimaryClassification(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:
            logger.error('Could not save command: %s', e)
            raise InternalServerError(response_data) from e

        if params.get('action') in ['previous', 'save_exit', 'next']:
            return response_data, status.SEE_OTHER, {}
    return response_data, status.OK, {}
示例#12
0
def metadata(method: str, params: MultiDict, session: Session,
             submission_id: int, **kwargs) -> Response:
    """Update metadata on the submission."""
    submitter, client = user_and_client_from_session(session)
    logger.debug(f'method: {method}, submission: {submission_id}. {params}')

    # Will raise NotFound if there is no such submission.
    submission, submission_events = load_submission(submission_id)
    # The form should be prepopulated based on the current state of the
    # submission.
    if method == 'GET':
        params = _data_from_submission(params, submission, CoreMetadataForm)

    form = CoreMetadataForm(params)
    response_data = {
        'submission_id': submission_id,
        'form': form,
        'submission': submission
    }

    if method == 'POST':
        if not form.validate():
            raise BadRequest(response_data)

        logger.debug('Form is valid, with data: %s', str(form.data))

        commands, valid = _commands(form, submission, submitter, client)
        # We only want to apply an UpdateMetadata if the metadata has
        # actually changed.
        if commands:  # Metadata has changed.
            if not all(valid):
                logger.debug('Not all commands are valid')
                response_data['form'] = form
                raise BadRequest(response_data)

            try:
                # Save the events created during form validation.
                submission, _ = save(*commands, submission_id=submission_id)
            except SaveError as e:
                raise InternalServerError(response_data) from e
            response_data['submission'] = submission

    if params.get('action') in ['previous', 'save_exit', 'next']:
        return response_data, status.SEE_OTHER, {}
    return response_data, status.OK, {}
示例#13
0
def license(method: str, params: MultiDict, session: Session,
            submission_id: int, **kwargs) -> Response:
    """Convert license form data into a `SetLicense` event."""
    submitter, client = user_and_client_from_session(session)

    logger.debug(f'method: {method}, submission: {submission_id}. {params}')

    # Will raise NotFound if there is no such submission.
    submission, submission_events = load_submission(submission_id)

    if method == 'GET' and submission.license:
        # The form should be prepopulated based on the current state of the
        # submission.
        params['license'] = submission.license.uri

    form = LicenseForm(params)
    response_data = {
        'submission_id': submission_id,
        'form': form,
        'submission': submission
    }

    if method == 'POST':
        if form.validate():
            logger.debug('Form is valid, with data: %s', str(form.data))
            license_uri = form.license.data

            # If already selected, nothing more to do.
            if not submission.license or submission.license.uri != license_uri:
                command = SetLicense(creator=submitter, client=client,
                                     license_uri=license_uri)
                if not validate_command(form, command, submission, 'license'):
                    raise BadRequest(response_data)

                try:
                    # Create SetLicense event
                    submission, _ = save(command, submission_id=submission_id)
                except SaveError as e:
                    raise InternalServerError(response_data) from e
        else:   # Form data were invalid.
            raise BadRequest(response_data)

    if params.get('action') in ['previous', 'save_exit', 'next']:
        return response_data, status.SEE_OTHER, {}
    return response_data, status.OK, {}
示例#14
0
def finalize(method: str, params: MultiDict, session: Session,
             submission_id: int, **kwargs) -> Response:
    submitter, client = user_and_client_from_session(session)

    logger.debug(f'method: {method}, submission: {submission_id}. {params}')
    submission, submission_events = load_submission(submission_id)

    form = FinalizationForm(params)

    # The abs preview macro expects a specific struct for submission history.
    submission_history = [{
        'submitted_date': s.created,
        'version': s.version
    } for s in submission.versions]
    response_data = {
        'submission_id': submission_id,
        'form': form,
        'submission': submission,
        'submission_history': submission_history
    }

    if method == 'POST':
        if form.validate():
            logger.debug('Form is valid, with data: %s', str(form.data))
            proofread_confirmed = form.proceed.data
            if proofread_confirmed:
                command = FinalizeSubmission(creator=submitter)
                if not validate_command(form, command, submission):
                    raise BadRequest(response_data)

                try:
                    # Create ConfirmPolicy event
                    submission, stack = save(  # pylint: disable=W0612
                        command,
                        submission_id=submission_id)
                except SaveError as e:
                    logger.error('Could not save primary event')
                    raise InternalServerError(response_data) from e
            if params.get('action') in ['previous', 'save_exit', 'next']:
                return response_data, status.SEE_OTHER, {}
        else:  # Form data were invalid.
            raise BadRequest(response_data)

    return response_data, status.OK, {}
示例#15
0
def create_submission(data: dict, headers: dict, agents: Dict[str, Agent],
                      token: Optional[str]) -> Response:
    """
    Create a new submission.

    Implements the hook for :meth:`sword.SWORDCollection.add_submission`.

    Parameters
    ----------
    data : dict
        Deserialized compact JSON-LD document.
    headers : dict
        Request headers from the client.

    Returns
    -------
    dict
        Response data.
    int
        HTTP status code.
    dict
        Headers to add to the response.
    """
    logger.debug(f'Received request to create submission, {agents}')
    create = ev.CreateSubmission(**agents)
    events = handlers.handle_submission(data, agents)
    try:
        submission, events = ev.save(create, *events)
    except ev.InvalidEvent as e:
        raise BadRequest(str(e)) from e
    except ev.SaveError as e:
        logger.error('Problem interacting with database: (%s) %s',
                     str(type(e)), str(e))
        raise InternalServerError('Problem interacting with database') from e

    response_headers = {
        'Location': url_for('submission.get_submission',
                            submission_id=submission.submission_id)
    }
    return asdict(submission), status.CREATED, response_headers
示例#16
0
def policy(method: str, params: MultiDict, session: Session,
           submission_id: int, **kwargs) -> Response:
    """Convert policy form data into an `ConfirmPolicy` event."""
    submitter, client = user_and_client_from_session(session)

    logger.debug(f'method: {method}, submission: {submission_id}. {params}')
    submission, submission_events = load_submission(submission_id)

    if method == 'GET' and submission.submitter_accepts_policy:
        params['policy'] = 'true'

    form = PolicyForm(params)
    response_data = {
        'submission_id': submission_id,
        'form': form,
        'submission': submission
    }

    if method == 'POST':
        if not form.validate():
            raise BadRequest(response_data)

        logger.debug('Form is valid, with data: %s', str(form.data))

        accept_policy = form.policy.data
        if accept_policy and not submission.submitter_accepts_policy:
            command = ConfirmPolicy(creator=submitter, client=client)
            if not validate_command(form, command, submission, 'policy'):
                raise BadRequest(response_data)

            try:
                submission, _ = save(command, submission_id=submission_id)
            except SaveError as e:
                raise InternalServerError(response_data) from e
            response_data['submission'] = submission

        if params.get('action') in ['previous', 'save_exit', 'next']:
            return response_data, status.SEE_OTHER, {}
    return response_data, status.OK, {}
示例#17
0
def update_submission(data: dict, headers: dict, agents: Dict[str, Agent],
                      token: str, submission_id: int) -> Response:
    """Update the submission."""
    events = handlers.handle_submission(data, agents)
    if not data:
        raise BadRequest('No data in request body')

    try:
        submission, events = ev.save(*events, submission_id=submission_id)
    except ev.NoSuchSubmission as e:
        raise NotFound(f"No submission found with id {submission_id}")
    except ev.InvalidEvent as e:
        raise BadRequest(str(e)) from e
    except ev.SaveError as e:
        raise InternalServerError('Problem interacting with database') from e

    response_headers = {
        'Location': url_for('submission.get_submission',
                            creator=agents['creator'].native_id,
                            submission_id=submission.submission_id)
    }
    return asdict(submission), status.OK, response_headers
示例#18
0
def replace(method: str, params: MultiDict, session: Session,
            submission_id: int, **kwargs) -> Response:
    """Create a new version, and redirect to workflow."""
    submitter, client = user_and_client_from_session(session)
    submission, submission_events = load_submission(submission_id)
    response_data = {
        'submission_id': submission_id,
        'submission': submission,
        'submitter': submitter,
        'client': client,
    }

    if method == 'GET':     # Display a splash page.
        response_data['form'] = CreateSubmissionForm()

    if method == 'POST':
        # We're using a form here for CSRF protection.
        form = CreateSubmissionForm(params)
        response_data['form'] = form
        if not form.validate():
            raise BadRequest('Invalid request')

        submitter, client = user_and_client_from_session(session)
        submission, _ = load_submission(submission_id)
        command = CreateSubmissionVersion(creator=submitter, client=client)
        if not validate_command(form, command, submission):
            raise BadRequest({})

        try:
            submission, _ = save(command, submission_id=submission_id)
        except SaveError as e:
            logger.error('Could not save command: %s', e)
            raise InternalServerError({}) from e

        loc = url_for('ui.verify_user', submission_id=submission.submission_id)
        return {}, status.SEE_OTHER, {'Location': loc}
    return response_data, status.OK, {}
示例#19
0
def request_cross(method: str, params: MultiDict, session: Session,
                  submission_id: int, **kwargs) -> Response:
    """Request cross-list classification for an announced e-print."""
    submitter, client = user_and_client_from_session(session)
    logger.debug(f'method: {method}, submission: {submission_id}. {params}')

    # Will raise NotFound if there is no such submission.
    submission, submission_events = load_submission(submission_id)

    # The submission must be announced for this to be a cross-list request.
    if not submission.is_announced:
        alerts.flash_failure(
            Markup("Submission must first be announced. See <a"
                   " href='https://arxiv.org/help/cross'>the arXiv help"
                   " pages</a> for details."))
        status_url = url_for('ui.create_submission')
        return {}, status.SEE_OTHER, {'Location': status_url}

    if method == 'GET':
        params = MultiDict({})

    params.setdefault("confirmed", False)
    params.setdefault("operation", CrossListForm.ADD)
    form = CrossListForm(params)
    selected = [v for v in form.selected.data if v]
    form.filter_choices(submission, session, exclude=selected)

    response_data = {
        'submission_id': submission_id,
        'submission': submission,
        'form': form,
        'selected': selected,
        'formset': CrossListForm.formset(selected)
    }
    if submission.primary_classification:
        response_data['primary'] = \
            CATEGORIES[submission.primary_classification.category]

    if method == 'POST':
        if not form.validate():
            raise BadRequest(response_data)

        if form.confirmed.data:     # Stop adding new categories, and submit.
            response_data['form'].operation.data = CrossListForm.ADD
            response_data['require_confirmation'] = True

            command = RequestCrossList(creator=submitter, client=client,
                                       categories=form.selected.data)
            if not validate_command(form, command, submission, 'category'):
                alerts.flash_failure(Markup(
                    "There was a problem with your request. Please try again."
                    f" {CONTACT_SUPPORT}"
                ))
                raise BadRequest(response_data)

            try:    # Submit the cross-list request.
                save(command, submission_id=submission_id)
            except SaveError as e:
                # This would be due to a database error, or something else
                # that likely isn't the user's fault.
                logger.error('Could not save cross list request event')
                alerts.flash_failure(Markup(
                    "There was a problem processing your request. Please try"
                    f" again. {CONTACT_SUPPORT}"
                ))
                raise InternalServerError(response_data) from e

            # Success! Send user back to the submission page.
            alerts.flash_success("Cross-list request submitted.")
            status_url = url_for('ui.create_submission')
            return {}, status.SEE_OTHER, {'Location': status_url}
        else:   # User is adding or removing a category.
            if form.operation.data:
                if form.operation.data == CrossListForm.REMOVE:
                    selected.remove(form.category.data)
                elif form.operation.data == CrossListForm.ADD:
                    selected.append(form.category.data)
                # Update the "remove" formset to reflect the change.
                response_data['formset'] = CrossListForm.formset(selected)
                response_data['selected'] = selected
            # Now that we've handled the request, get a fresh form for adding
            # more categories or submitting the request.
            response_data['form'] = CrossListForm()
            response_data['form'].filter_choices(submission, session,
                                                 exclude=selected)
            response_data['form'].operation.data = CrossListForm.ADD
            response_data['require_confirmation'] = True
            return response_data, status.OK, {}
    return response_data, status.OK, {}
示例#20
0
    def setUp(self):
        """Create an application instance."""
        self.app = create_ui_web_app()
        os.environ['JWT_SECRET'] = self.app.config.get('JWT_SECRET')
        _, self.db = tempfile.mkstemp(suffix='.db')
        self.app.config['CLASSIC_DATABASE_URI'] = f'sqlite:///{self.db}'
        self.user = User('1234',
                         '*****@*****.**',
                         endorsements=['astro-ph.GA', 'astro-ph.CO'])
        self.token = generate_token(
            '1234',
            '*****@*****.**',
            'foouser',
            scope=[
                scopes.CREATE_SUBMISSION, scopes.EDIT_SUBMISSION,
                scopes.VIEW_SUBMISSION, scopes.READ_UPLOAD,
                scopes.WRITE_UPLOAD, scopes.DELETE_UPLOAD_FILE
            ],
            endorsements=[
                Category('astro-ph.GA'),
                Category('astro-ph.CO'),
            ])
        self.headers = {'Authorization': self.token}
        self.client = self.app.test_client()

        # Create and announce a submission.
        with self.app.app_context():
            classic.create_all()
            session = classic.current_session()

            cc0 = 'http://creativecommons.org/publicdomain/zero/1.0/'
            self.submission, _ = save(
                CreateSubmission(creator=self.user),
                ConfirmContactInformation(creator=self.user),
                ConfirmAuthorship(creator=self.user, submitter_is_author=True),
                SetLicense(creator=self.user,
                           license_uri=cc0,
                           license_name='CC0 1.0'),
                ConfirmPolicy(creator=self.user),
                SetPrimaryClassification(creator=self.user,
                                         category='astro-ph.GA'),
                SetUploadPackage(
                    creator=self.user,
                    checksum="a9s9k342900skks03330029k",
                    source_format=SubmissionContent.Format.TEX,
                    identifier=123,
                    uncompressed_size=593992,
                    compressed_size=59392,
                ), SetTitle(creator=self.user, title='foo title'),
                SetAbstract(creator=self.user, abstract='ab stract' * 20),
                SetComments(creator=self.user, comments='indeed'),
                SetReportNumber(creator=self.user, report_num='the number 12'),
                SetAuthors(creator=self.user,
                           authors=[
                               Author(order=0,
                                      forename='Bob',
                                      surname='Paulson',
                                      email='*****@*****.**',
                                      affiliation='Fight Club')
                           ]), FinalizeSubmission(creator=self.user))

            # announced!
            db_submission = session.query(classic.models.Submission) \
                .get(self.submission.submission_id)
            db_submission.status = classic.models.Submission.ANNOUNCED
            db_document = classic.models.Document(paper_id='1234.5678')
            db_submission.doc_paper_id = '1234.5678'
            db_submission.document = db_document
            session.add(db_submission)
            session.add(db_document)
            session.commit()

        self.submission_id = self.submission.submission_id
示例#21
0
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')
示例#22
0
def process_submission(s):
    """Process a submission from a tsvfile row."""
    # TODO: Make sure forename surname separation are better
    try:
        forename, surname = s['submitter_name'].rsplit(maxsplit=1)
    except ValueError:
        forename = ''
        surname = s['submitter_name']
    submitter = domain.User(s['submitter_id'], email=s['submitter_email'],
                            forename=forename, surname=surname,
                            endorsements=[s['category']])

    metadata = dict([
        ('title', s['title']),
        ('abstract', s['abstract']),
        ('comments', s['comments']),
        ('report_num', s['report_num']),
        ('doi', s['doi']),
        ('journal_ref', s['journal_ref'])
    ])

    submission, stack = save(
        CreateSubmission(creator=submitter)
    )

    if s.get('is_author') == '1':
        submission, stack = save(
            ConfirmAuthorship(
                creator=submitter,
                submitter_is_author=True
            ),
            submission_id=submission.submission_id
        )
    else:
        submission, stack = save(
            ConfirmAuthorship(
                creator=submitter,
                submitter_is_author=False
            ),
            submission_id=submission.submission_id
        )

    if s.get('agree_policy') == '1':
        submission, stack = save(
            ConfirmPolicy(creator=submitter),
            submission_id=submission.submission_id
        )

    if s.get('userinfo') == '1':
        submission, stack = save(
            ConfirmContactInformation(creator=submitter),
            submission_id=submission.submission_id
        )

    submission, stack = save(
        SetAuthors(
            authors_display=s['authors'],
            creator=submitter
        ),
        SetPrimaryClassification(creator=submitter, category=s['category']),
        submission_id=submission.submission_id
    )
    if s['title']:
        submission, stack = save(
            SetTitle(creator=submitter, title=metadata['title']),
            submission_id=submission.submission_id
        )
    if s['abstract']:
        submission, stack = save(
            SetAbstract(creator=submitter, abstract=metadata['abstract']),
            submission_id=submission.submission_id
        )
    if metadata['comments']:
        submission, stack = save(
            SetComments(creator=submitter, comments=metadata['comments']),
            submission_id=submission.submission_id
        )

    if metadata['journal_ref']:
        submission, stack = save(
            SetJournalReference(creator=submitter,
                                journal_ref=metadata['journal_ref']),
            submission_id=submission.submission_id
        )

    if metadata['doi']:
        submission, stack = save(
            SetDOI(creator=submitter, doi=metadata['doi']),
            submission_id=submission.submission_id
        )

    if metadata['report_num']:
        submission, stack = save(
            SetReportNumber(creator=submitter,
                            report_num=metadata['report_num']),
            submission_id=submission.submission_id
        )

    # Parse the license
    license_uri = s.get('license')
    if license_uri:
        submission, stack = save(
            SetLicense(creator=submitter, license_uri=license_uri),
            submission_id=submission.submission_id
        )

    if s.get('package'):
        submission, stack = save(
            SetUploadPackage(
                format=s['source_format'],
                checksum='0',
                identifier=1,
                creator=submitter
            ),
            submission_id=submission.submission_id
        )

    if s.get('status') not in INVALID_STATUSES:
        submission, stack = save(
            FinalizeSubmission(creator=submitter),
            submission_id=submission.submission_id
        )

    return submission.submission_id
示例#23
0
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, {}
示例#24
0
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, {}
示例#25
0
def compile_status(params: MultiDict, session: Session, submission_id: int,
                   token: str, **kwargs) -> Response:
    """
    Returns the status of a compilation.

    Parameters
    ----------
    session : :class:`Session`
        The authenticated session for the request.
    submission_id : int
        The identifier of the submission for which the upload 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.

    """
    submitter, client = user_and_client_from_session(session)
    submission, submission_events = load_submission(submission_id)
    form = CompilationForm()
    response_data = {
        'submission_id': submission_id,
        'submission': submission,
        'form': form,
        'status': None,
        'must_process': _must_process(submission)
    }

    # Not all submissions require processing.
    if not _must_process(submission):
        logger.debug('No processing required')
        return response_data, status.OK, {}

    # Determine whether the current state of the uploaded source content has
    # been compiled.
    source_id = submission.source_content.identifier
    source_state = submission.source_content.checksum
    try:
        compilation = Compiler.get_status(source_id, source_state, token)
    except exceptions.NotFound:     # Nothing to do.
        logger.debug('No such compilation')
        return response_data, status.OK, {}

    response_data['status'] = compilation.status
    response_data['current_compilation'] = compilation

    if compilation.status is Compilation.Status.SUCCEEDED:
        command = ConfirmCompiledPreview(creator=submitter, client=client)
        try:
            submission, _ = save(command, submission_id=submission_id)
        except SaveError:
            alerts.flash_failure(Markup(
                'There was a problem carrying out your request. Please try'
                f' again. {SUPPORT}'
            ))

    # if Compilation failure, then show errors, opportunity to restart.
    # if Compilation success, then show preview.
    terminal_states = [Compilation.Status.FAILED, Compilation.Status.SUCCEEDED]
    if compilation.status in terminal_states:
        response_data.update(_get_log(submission.source_content.identifier,
                                      submission.source_content.checksum,
                                      token))
    return response_data, status.OK, {}
示例#26
0
def jref(method: str, params: MultiDict, session: Session, submission_id: int,
         **kwargs) -> Response:
    """Set journal reference metadata on a announced submission."""
    creator, client = user_and_client_from_session(session)
    logger.debug(f'method: {method}, submission: {submission_id}. {params}')

    # Will raise NotFound if there is no such submission.
    submission, submission_events = load_submission(submission_id)

    # The submission must be announced for this to be a real JREF submission.
    if not submission.is_announced:
        alerts.flash_failure(
            Markup("Submission must first be announced. See "
                   "<a href='https://arxiv.org/help/jref'>"
                   "the arXiv help pages</a> for details."))
        status_url = url_for('ui.create_submission')
        return {}, status.SEE_OTHER, {'Location': status_url}

    # The form should be prepopulated based on the current state of the
    # submission.
    if method == 'GET':
        params = MultiDict({
            'doi': submission.metadata.doi,
            'journal_ref': submission.metadata.journal_ref,
            'report_num': submission.metadata.report_num
        })

    params.setdefault("confirmed", False)
    form = JREFForm(params)
    response_data = {
        'submission_id': submission_id,
        'submission': submission,
        'form': form,
    }

    if method == 'POST':
        # We require the user to confirm that they wish to proceed. We show
        # them a preview of what their paper's abs page will look like after
        # the proposed change. They can either make further changes, or
        # confirm and submit.
        if not form.validate():
            logger.debug('Invalid form data; return bad request')
            raise BadRequest(response_data)

        if not form.confirmed.data:
            response_data['require_confirmation'] = True
            logger.debug('Not confirmed')
            return response_data, status.OK, {}

        commands, valid = _generate_commands(form, submission, creator, client)

        if commands:  # Metadata has changed; we have things to do.
            if not all(valid):
                raise BadRequest(response_data)

            response_data['require_confirmation'] = True
            logger.debug('Form is valid, with data: %s', str(form.data))
            try:
                # Save the events created during form validation.
                submission, _ = save(*commands, submission_id=submission_id)
            except SaveError as e:
                logger.error('Could not save metadata event')
                raise InternalServerError(response_data) from e
            response_data['submission'] = submission

            # Success! Send user back to the submission page.
            alerts.flash_success("Journal reference updated")
            status_url = url_for('ui.create_submission')
            return {}, status.SEE_OTHER, {'Location': status_url}
    logger.debug('Nothing to do, return 200')
    return response_data, status.OK, {}