Пример #1
0
def request_review(project_id):
    """Request an admin's review for my project."""
    project = Project.query.get_or_404(project_id)

    if project.lifetime_stage != LifetimeStage.finalizing:
        abort(400, {'message': 'Only projects being finalized can be reviewed.'})

    if current_user != project.creator and not current_user.is_admin:
        abort(401)

    if project.review_status == ReviewStatus.pending:
        abort(400, {'message': 'Project is already under review.'})

    project.review_status = ReviewStatus.pending

    try:
        db.session.commit()
    except IntegrityError as err:
        db.session.rollback()
        log.exception(err)
        abort(400, {'message': 'Data integrity violated.'})

    admins = Account.query.filter_by(is_admin=True).all()
    notify_all(admins, NotificationType.project_review_requested, {
        'project_id': project.id,
    })

    return NO_PAYLOAD
Пример #2
0
def publish_project(project_id):
    """Publish an existing draft project."""
    project = Project.query.get_or_404(project_id)

    if project.lifetime_stage != LifetimeStage.draft:
        abort(400, {'message': 'Only draft projects can be published.'})

    if not current_user.is_admin and project.creator != current_user:
        abort(403)

    if not project.name:
        abort(400, {'message': 'The project must have a valid name.'})

    external_activity = Activity.query.filter_by(internal=False, draft=False, project_id=project_id)
    if not db.session.query(external_activity.exists()).scalar():
        abort(400, {'message': 'The project must have at least one non-draft activity.'})

    if not all(len(activity.competences) in range(1, 4) for activity in project.activities
               if not activity.internal):
        abort(400, {'message': 'The activities must have from 1 to 3 competences.'})

    project.lifetime_stage = LifetimeStage.ongoing
    db.session.commit()

    moderators = filter(lambda moderator: moderator != project.creator, project.moderators)
    notify_all(moderators, NotificationType.added_as_moderator, {
        'project_id': project.id,
        'account_email': current_user.email,
    })

    return NO_PAYLOAD
Пример #3
0
def create_product():
    """Create a new product."""
    in_schema = ProductSchema(
        exclude=('id', 'addition_time', 'varieties.stock_changes.variety_id',
                 'varieties.product_id', 'varieties.images.variety_id'),
        context={'user': current_user})

    try:
        new_product = in_schema.load(request.json)
    except ValidationError as err:
        abort(400, {'message': err.messages})

    duplicate = Product.query.filter_by(name=new_product.name,
                                        type=new_product.type)
    if db.session.query(duplicate.exists()).scalar():
        abort(400, {'message': 'A product with this name and type exists.'})

    if not new_product.varieties:
        abort(400, {'message': 'Please provide at least one variety.'})

    try:
        for variety in new_product.varieties:
            variety.product = new_product
            for stock_change in variety.stock_changes:
                stock_change.variety_id = variety.id

        db.session.add(new_product)
        db.session.commit()
    except IntegrityError as err:
        db.session.rollback()
        log.exception(err)
        abort(400, {'message': 'Data integrity violated.'})

    # TODO: replace the following with proper debounce
    # Check if a notification has been sent today
    already_sent = Notification.query.filter(
        Notification.type == NotificationType.new_arrivals,
        Notification.timestamp >= date.today()).exists()
    if not db.session.query(already_sent).scalar():
        users = Account.query.filter_by(is_admin=False).all()
        notify_all(users, NotificationType.new_arrivals)

    out_schema = ProductSchema(exclude=('varieties.product_id',
                                        'varieties.product',
                                        'varieties.images.variety_id',
                                        'varieties.images.id',
                                        'varieties.stock_changes'))
    return out_schema.jsonify(new_product)
Пример #4
0
def review_project(project_id):
    """Review a project in its finalizing stage."""
    project = Project.query.get_or_404(project_id)

    if project.lifetime_stage != LifetimeStage.finalizing:
        abort(400, {'message': 'Only projects being finalized can be reviewed.'})

    if project.review_status != ReviewStatus.pending:
        abort(400, {'message': 'Can only review projects pending review.'})

    allowed_states = {
        'approved': ReviewStatus.approved,
        'rejected': ReviewStatus.rejected,
    }

    if request.json.get('review_status') not in allowed_states:
        abort(400, {'message': 'Invalid review status specified.'})

    project.review_status = allowed_states[request.json['review_status']]
    if project.review_status == ReviewStatus.approved:
        project.lifetime_stage = LifetimeStage.finished

    if 'admin_feedback' in request.json:
        project.admin_feedback = request.json['admin_feedback']

    try:
        db.session.commit()
    except IntegrityError as err:
        db.session.rollback()
        log.exception(err)
        abort(400, {'message': 'Data integrity violated.'})

    notify_all(project.moderators, NotificationType.project_review_status_changed, {
        'project_id': project.id,
    })
    if project.review_status == ReviewStatus.approved:
        for activity in project.activities:
            for application in activity.applications:
                notify(application.applicant_email, NotificationType.claim_innopoints, {
                    'project_id': project.id,
                    'activity_id': activity.id,
                    'application_id': application.id,
                })

    return NO_PAYLOAD
Пример #5
0
def leave_feedback(project_id, activity_id, application_id):
    """Leave feedback on a particular volunteering experience."""
    application = Application.query.get_or_404(application_id)
    activity = Activity.query.get_or_404(activity_id)
    project = Project.query.get_or_404(project_id)

    if activity.project != project or application.activity_id != activity.id:
        abort(400, {'message': 'The specified project, activity and application are unrelated.'})

    if application.applicant != current_user:
        abort(401)

    if application.feedback is not None:
        abort(400, {'message': 'Feedback already exists.'})

    if application.status != ApplicationStatus.approved:
        abort(400, {'message': 'Feedback may only be left on approved applications.'})

    if project.lifetime_stage != LifetimeStage.finished:
        abort(400, {'message': 'Feedback may only be left on finished projects.'})

    in_schema = FeedbackSchema(exclude=('time',))
    try:
        new_feedback = in_schema.load(request.json)
    except ValidationError as err:
        abort(400, {'message': err.messages})

    if len(new_feedback.answers) != len(activity.feedback_questions):
        abort(400, {'message': f'Expected {len(activity.feedback_questions)} answer(s), '
                               f'found {len(new_feedback.answers)}.'})
    new_feedback.application_id = application_id
    db.session.add(new_feedback)

    new_transaction = Transaction(account=current_user,
                                  change=application.actual_hours * activity.reward_rate,
                                  feedback_id=new_feedback)
    new_feedback.transaction = new_transaction
    db.session.add(new_transaction)

    notification = Notification.query.filter(
        Notification.recipient_email == current_user.email,
        Notification.type == NotificationType.claim_innopoints,
        Notification.payload.op('->>')('application_id').cast(db.Integer) == application_id,
    ).one_or_none()

    if notification is not None:
        notification.is_read = True

    try:
        db.session.commit()
    except IntegrityError as err:
        db.session.rollback()
        log.exception(err)
        abort(400, {'message': 'Data integrity violated.'})

    all_feedback_in = all(application.feedback is not None
                          for activity in project.activities
                          for application in activity.applications
                          if not activity.internal)
    if all_feedback_in:
        admins = Account.query.filter_by(is_admin=True).all()
        mods = {*project.moderators, *admins}
        notify_all(mods, NotificationType.all_feedback_in, {
            'project_id': project.id,
        })

    out_schema = FeedbackSchema()
    return out_schema.jsonify(new_feedback)
Пример #6
0
def purchase_variety(product_id, variety_id):
    """Purchase a particular variety of a product."""
    purchased_amount = request.json.get('amount')
    if not isinstance(purchased_amount, int):
        abort(400, {
            'message': 'The purchase amount must be specified as an integer.'
        })

    if purchased_amount <= 0:
        abort(400, {'message': 'The purchase amount must be positive.'})

    product = Product.query.get_or_404(product_id)
    variety = Variety.query.get_or_404(variety_id)

    if variety.product != product:
        abort(400,
              {'message': 'The specified product and variety are unrelated.'})

    log.debug(
        f'User with balance {current_user.balance} is trying to buy {purchased_amount} of a '
        f'product with a price of {product.price}. '
        f'Total = {product.price * purchased_amount}')
    if current_user.balance < product.price * purchased_amount:
        log.debug('Purchase refused: not enough points')
        abort(400, {'message': 'Insufficient funds.'})

    if purchased_amount > variety.amount:
        log.debug('Purchase refused: not enough stock')
        abort(400, {'message': 'Insufficient stock.'})

    new_stock_change = StockChange(amount=-purchased_amount,
                                   status=StockChangeStatus.pending,
                                   account=current_user,
                                   variety_id=variety_id)
    db.session.add(new_stock_change)
    new_transaction = Transaction(account=current_user,
                                  change=-product.price * purchased_amount,
                                  stock_change_id=new_stock_change)
    new_stock_change.transaction = new_transaction
    db.session.add(new_transaction)

    try:
        db.session.commit()
    except IntegrityError as err:
        db.session.rollback()
        log.exception(err)
        abort(400, {'message': 'Data integrity violated.'})
    log.debug('Purchase successful')

    admins = Account.query.filter_by(is_admin=True).all()
    notify_all(
        admins, NotificationType.new_purchase, {
            'account_email': current_user.email,
            'product_id': product.id,
            'variety_id': variety.id,
            'stock_change_id': new_stock_change.id,
        })
    if variety.amount <= 0:
        notify_all(admins, NotificationType.out_of_stock, {
            'product_id': product.id,
            'variety_id': variety.id,
        })

    out_schema = StockChangeSchema(exclude=('transaction', 'account',
                                            'account_email', 'product',
                                            'variety'))
    return out_schema.jsonify(new_stock_change)