예제 #1
0
def validate_team(db,
                  team_id,
                  round_id,
                  now,
                  with_member=None,
                  without_member=None):
    """ Raise an exception if the team is invalid for the round.
        If with_member is a user, check with the user added the team.
    """
    team_members = db.tables.team_members
    tm_query = db.query(team_members) \
        .where(team_members.team_id == team_id)
    n_members = db.count(tm_query.fields(team_members.user_id))
    n_qualified = db.count(
        tm_query.where(team_members.is_qualified).fields(team_members.user_id))
    if with_member is not None:
        n_members += 1
        if with_member['is_qualified']:
            n_qualified += 1
    if without_member is not None:
        n_members -= 1
        if with_member['is_qualified']:
            n_qualified -= 1
    round_ = load_round(db, round_id, now=now)
    if n_members < round_['min_team_size']:
        raise ModelError('team too small')
    if n_members > round_['max_team_size']:
        raise ModelError('team too large')
    if n_qualified < n_members * round_['min_team_ratio']:
        raise ModelError('not enough qualified members')
예제 #2
0
def validate_team(
        db, team_id, round_id, now, with_member=None, without_member=None):
    """ Raise an exception if the team is invalid for the round.
        If with_member is a user, check with the user added the team.
    """
    team_members = db.tables.team_members
    tm_query = db.query(team_members) \
        .where(team_members.team_id == team_id)
    n_members = db.count(
        tm_query.fields(team_members.user_id))
    n_qualified = db.count(
        tm_query.where(team_members.is_qualified)
        .fields(team_members.user_id))
    if with_member is not None:
        n_members += 1
        if with_member['is_qualified']:
            n_qualified += 1
    if without_member is not None:
        n_members -= 1
        if with_member['is_qualified']:
            n_qualified -= 1
    round_ = load_round(db, round_id, now=now)
    if n_members < round_['min_team_size']:
        raise ModelError('team too small')
    if n_members > round_['max_team_size']:
        raise ModelError('team too large')
    if n_qualified < n_members * round_['min_team_ratio']:
        raise ModelError('not enough qualified members')
예제 #3
0
def submit_user_attempt_answer_action(request):
    now = datetime.utcnow()
    attempt_id = request.context.attempt_id
    attempt = load_attempt(request.db, attempt_id)
    participation = load_participation(request.db, attempt['participation_id'])
    round_ = load_round(request.db, participation['round_id'])
    if round_['status'] != 'open':
        return {'success': False, 'error': 'round not open'}
    if participation['started_at'] is not None and round_[
            'duration'] is not None:
        duration = timedelta(minutes=round_['duration'])
        deadline = participation['started_at'] + duration
        if now > deadline:
            return {'success': False, 'error': 'past deadline'}
    submitter_id = request.context.user_id
    query = request.json_body
    answer = query['answer']
    revision_id = None
    if 'data' in query:
        revision = query['data']
        revision_id = store_revision_query(request.db, submitter_id,
                                           attempt_id, revision)
    answer, feedback = grade_answer(request.db,
                                    attempt_id,
                                    submitter_id,
                                    revision_id,
                                    answer,
                                    now=now)
    return {
        'success': True,
        'answer_id': answer['id'],
        'revision_id': revision_id,
        'feedback': feedback,
        'score': answer['score']
    }
예제 #4
0
def create_team_action(request):
    """ Create a team for the context's user.
        An administrator can also perform the action on a user's behalf.
    """
    # Create the team.
    now = datetime.utcnow()
    user_id = request.context.user_id
    user = load_user(request.db, user_id)
    # Select a round based on the user's badges.
    round_ids = find_round_ids_with_badges(request.db, user['badges'], now)
    if len(round_ids) == 0:
        # The user does not have access to any open round.
        raise ApiError('not qualified for any open round')
    if len(round_ids) > 1:
        # XXX The case where a user has badges for multiple open rounds
        # is currently handled by picking the first one, which is the
        # one that has the greatest id.  This is unsatisfactory.
        pass
    round_id = round_ids[0]
    round_ = load_round(request.db, round_id, now)
    if not round_['is_registration_open']:
        raise ApiError('registration is closed')
    # Create the team.
    team_id = create_user_team(request.db, user_id, now)
    # Create a participation.
    create_participation(request.db, team_id, round_id, now=now)
    # Ensure the user gets team credentials.
    reset_user_principals(request)
    return {'success': True}
예제 #5
0
def leave_team(db, user_id, team_id, now):
    """ Remove a user from their team.
    """
    user = load_user(db, user_id, for_update=True)
    team_id = user['team_id']
    # The user must be member of a team.
    if team_id is None:
        raise ModelError('no team')
    team = load_team(db, team_id)
    # Load the participation.
    participation_id = get_team_latest_participation_id(db, team_id)
    participation = load_participation(db, participation_id)
    round_id = participation['round_id']
    round_ = load_round(db, round_id)
    # If the team already has an attempt (is_locked=True), verify
    # that the team remains valid if the user is removed.
    if team['is_locked']:
        if round_['allow_team_changes']:
            validate_team(db, team_id, round_id, without_member=user, now=now)
        else:
            raise ModelError('team is locked')
    # Clear the user's team_id.
    set_user_team_id(db, user_id, None)
    # Delete the team_members row.
    team_members = db.tables.team_members
    tm_query = db.query(team_members) \
        .where(team_members.team_id == team_id) \
        .where(team_members.user_id == user_id)
    (is_creator,) = db.first(
        tm_query.fields(team_members.is_creator))
    db.delete(tm_query)
    # If the user was the team creator, select the earliest member
    # as the new creator.
    if is_creator:
        query = db.query(team_members) \
            .where(team_members.team_id == team_id)
        row = db.first(
            query.fields(team_members.user_id)
                 .order_by(team_members.joined_at),
            for_update=True)
        if row is None:
            # Team has become empty, delete it.
            teams = db.tables.teams
            team_query = db.query(teams) \
                .where(teams.id == team_id)
            db.delete(team_query)
        else:
            # Promote the selected user as the creator.
            new_creator_id = row[0]
            db.update(
                query.where(team_members.user_id == new_creator_id),
                {team_members.is_creator: True})
예제 #6
0
def get_task_instance_hint(db, attempt_id, query, now):

    # Load the attempt and check that it is still open.
    attempt = load_attempt(db, attempt_id, now)
    if attempt['is_closed']:
        raise ModelError('attempt is closed')

    # Load the participation and verify that the round is still open.
    participation = load_participation(db, attempt['participation_id'])
    round_ = load_round(db, participation['round_id'], now)
    if round_['status'] != 'open':
        raise ModelError('round not open')

    # Obtain task backend URL and Authorization header.
    round_task = load_round_task(db, attempt['round_task_id'])
    task = load_task(db, round_task['task_id'])
    backend_url = task['backend_url']
    auth = task['backend_auth']

    # Load the task instance.
    task_instance = load_task_instance(db, attempt_id, for_update=True)
    if task_instance is None:
        raise ModelError('no task instance')
    full_data = task_instance['full_data']
    team_data = task_instance['team_data']

    # Get the task backend to validate the hint request and apply the hint
    # from full_data onto team_data.
    print('grantHint query {}'.format(query))
    result = task_grant_hint(backend_url, full_data, team_data, query, auth)
    # result: {success, task, full_task}
    print('grantHint result {}'.format(result))
    team_data = result.get('task', team_data)
    full_data = result.get('full_task', full_data)

    # If successful, update the task instance.
    # 'full_data' is also updated in case the task needs to store extra private
    # information there.
    if result['success']:
        attrs = {}
        if team_data is not task_instance['team_data']:
            attrs['team_data'] = db.dump_json(team_data)
        if full_data is not task_instance['full_data']:
            attrs['full_data'] = db.dump_json(full_data)
        if len(attrs) != 0:
            attrs['updated_at'] = now
            task_instances = db.tables.task_instances
            db.update_row(task_instances, {'attempt_id': attempt_id}, attrs)

    return result['success']
예제 #7
0
def leave_team(db, user_id, team_id, now):
    """ Remove a user from their team.
    """
    user = load_user(db, user_id, for_update=True)
    team_id = user['team_id']
    # The user must be member of a team.
    if team_id is None:
        raise ModelError('no team')
    team = load_team(db, team_id)
    # Load the participation.
    participation_id = get_team_latest_participation_id(db, team_id)
    participation = load_participation(db, participation_id)
    round_id = participation['round_id']
    round_ = load_round(db, round_id)
    # If the team already has an attempt (is_locked=True), verify
    # that the team remains valid if the user is removed.
    if team['is_locked']:
        if round_['allow_team_changes']:
            validate_team(db, team_id, round_id, without_member=user, now=now)
        else:
            raise ModelError('team is locked')
    # Clear the user's team_id.
    set_user_team_id(db, user_id, None)
    # Delete the team_members row.
    team_members = db.tables.team_members
    tm_query = db.query(team_members) \
        .where(team_members.team_id == team_id) \
        .where(team_members.user_id == user_id)
    (is_creator, ) = db.first(tm_query.fields(team_members.is_creator))
    db.delete(tm_query)
    # If the user was the team creator, select the earliest member
    # as the new creator.
    if is_creator:
        query = db.query(team_members) \
            .where(team_members.team_id == team_id)
        row = db.first(query.fields(team_members.user_id).order_by(
            team_members.joined_at),
                       for_update=True)
        if row is None:
            # Team has become empty, delete it.
            teams = db.tables.teams
            team_query = db.query(teams) \
                .where(teams.id == team_id)
            db.delete(team_query)
        else:
            # Promote the selected user as the creator.
            new_creator_id = row[0]
            db.update(query.where(team_members.user_id == new_creator_id),
                      {team_members.is_creator: True})
예제 #8
0
def join_team(db, user_id, team_id, now):
    """ Add a user to a team.
        Registration for the team's round must be open.
        Return a boolean indicating if the team member was added.
    """
    # Verify that the user does not already belong to a team.
    user = load_user(db, user_id, for_update=True)
    if user['team_id'] is not None:
        raise ModelError('already in a team')
    # Verify that the team exists.
    team = load_team(db, team_id)
    # Verify that the team is open.
    if not team['is_open']:
        # Team is closed (by its creator).
        raise ModelError('team is closed')
    # Load the participation.
    participation_id = get_team_latest_participation_id(db, team_id)
    participation = load_participation(db, participation_id)
    round_id = participation['round_id']
    # Look up the badges that grant access to the team's round, to
    # figure out whether the user is qualified for that round.
    user_badges = user['badges']
    badges = db.tables.badges
    if len(user_badges) == 0:
        is_qualified = False
    else:
        query = db.query(badges) \
            .where(badges.round_id == round_id) \
            .where(badges.symbol.in_(user_badges)) \
            .where(badges.is_active) \
            .fields(badges.id)
        is_qualified = db.scalar(query) is not None
    # If the team has already accessed a task instance (is_locked=True),
    # verify that the team remains valid if the user is added.
    if team['is_locked']:
        round_ = load_round(round_id)
        if round_['allow_team_changes']:
            user['is_qualified'] = is_qualified
            validate_team(db, team_id, round_id, with_member=user, now=now)
        else:
            raise ModelError('team is locked')
    # Create the team_members row.
    user_id = user['id']
    add_team_member(db, team_id, user_id, now=now, is_qualified=is_qualified)
    # Update the user's team_id.
    set_user_team_id(db, user_id, team_id)
예제 #9
0
def join_team(db, user_id, team_id, now):
    """ Add a user to a team.
        Registration for the team's round must be open.
        Return a boolean indicating if the team member was added.
    """
    # Verify that the user does not already belong to a team.
    user = load_user(db, user_id, for_update=True)
    if user['team_id'] is not None:
        raise ModelError('already in a team')
    # Verify that the team exists.
    team = load_team(db, team_id)
    # Verify that the team is open.
    if not team['is_open']:
        # Team is closed (by its creator).
        raise ModelError('team is closed')
    # Load the participation.
    participation_id = get_team_latest_participation_id(db, team_id)
    participation = load_participation(db, participation_id)
    round_id = participation['round_id']
    # Look up the badges that grant access to the team's round, to
    # figure out whether the user is qualified for that round.
    user_badges = user['badges']
    badges = db.tables.badges
    if len(user_badges) == 0:
        is_qualified = False
    else:
        query = db.query(badges) \
            .where(badges.round_id == round_id) \
            .where(badges.symbol.in_(user_badges)) \
            .where(badges.is_active) \
            .fields(badges.id)
        is_qualified = db.scalar(query) is not None
    # If the team has already accessed a task instance (is_locked=True),
    # verify that the team remains valid if the user is added.
    if team['is_locked']:
        round_ = load_round(round_id)
        if round_['allow_team_changes']:
            user['is_qualified'] = is_qualified
            validate_team(db, team_id, round_id, with_member=user, now=now)
        else:
            raise ModelError('team is locked')
    # Create the team_members row.
    user_id = user['id']
    add_team_member(db, team_id, user_id, now=now, is_qualified=is_qualified)
    # Update the user's team_id.
    set_user_team_id(db, user_id, team_id)
예제 #10
0
def create_attempt(db, participation_id, round_task_id, now):
    # Load the participation and round_task, check consistency.
    participation = load_participation(db, participation_id)
    round_task = load_round_task(db, round_task_id)
    if participation['round_id'] != round_task['round_id']:
        raise ModelError('round mismatch')
    # Check that round is open.
    round_ = load_round(db, round_task['round_id'], now=now)
    if round_['status'] != 'open':
        raise ModelError('round not open')
    attempt_id = get_current_attempt_id(db, participation_id, round_task_id)
    is_training = False
    if attempt_id is None:
        # Create the initial attempt (which may or may not be a training
        # attempt, depending on the task_round's options).
        # The team is not locked at this time and may still be changed
        # (depending on round options).  This requires adding an access
        # code when a new member joins the team, and deleting an access
        # code when a member leaves the team.
        is_training = round_task['have_training_attempt']
    else:
        attempt = load_attempt(db, attempt_id, now=now, for_update=True)
        if attempt['is_training']:
            # Current attempt is training.  Team must pass training to
            # create a timed attempt.
            if attempt['is_unsolved']:
                raise ModelError('must pass training')
        else:
            # Timed attempts allow starting the next attempt immediately
            # only if completed.
            # if not attempt['is_completed']:
            most_recent_allowed = now - timedelta(minutes=5)
            if have_attempt_after(db, participation_id, round_task_id, most_recent_allowed):
                raise ModelError('attempt too soon')
        # Optionally limit the number of timed attempts.
        if round_task['max_timed_attempts'] is not None:
            n_attempts = count_timed_attempts(db, participation_id)
            if n_attempts >= round_task['max_timed_attempts']:
                raise ModelError('too many attempts')
        # Reset the is_current flag on the current attempt.
        set_attempt_current(db, attempt_id, is_current=False)
    # Find the greatest attempt ordinal for the (participation, round_task).
    attempts = db.tables.attempts
    query = db.query(attempts) \
        .where(attempts.participation_id == participation_id) \
        .where(attempts.round_task_id == round_task_id) \
        .order_by(attempts.ordinal.desc()) \
        .fields(attempts.ordinal)
    row = db.first(query)
    ordinal = 1 if row is None else (row[0] + 1)
    attempt_id = db.insert_row(attempts, {
        'participation_id': participation_id,
        'round_task_id': round_task_id,
        'ordinal': ordinal,
        'created_at': now,
        'started_at': None,   # set when task is accessed
        'closes_at': None,
        'is_current': True,
        'is_training': is_training,
        'is_unsolved': True
    })
    generate_access_codes(db, participation['team_id'], attempt_id)
    return attempt_id
예제 #11
0
def view_requesting_user(
        db, user_id=None, participation_id=None, attempt_id=None,
        is_admin=False):

    now = datetime.utcnow()
    view = {
        'now': now,
        'is_admin': is_admin
    }
    if user_id is None:
        return view

    #
    # Add the user.
    #
    user = load_user(db, user_id)
    if user is None:
        return view
    view['user_id'] = user_id
    view['user'] = view_user(user)
    team_id = user['team_id']

    #
    # Quick return path when the user has no team.
    #
    if team_id is None:
        # If the user has no team, we look for a round to which a
        # badge grants access.
        badges = user['badges']
        round_ids = find_round_ids_with_badges(db, badges, now)
        if len(round_ids) > 0:
            # TODO: resolve this somehow, for example by returning
            # the round views to the user and letting them choose.
            # For now, pick the first one (which has the greatest id).
            round_id = round_ids[0]
            round_ = load_round(db, round_id, now)
            view['round'] = view_round(round_)
        return view

    #
    # Add the team and team members.
    #
    team = load_team(db, team_id)
    members = load_team_members(db, team['id'], users=True)
    team_view = view['team'] = view_team(team, members)

    #
    # Add the team's participations.
    #
    participations = load_team_participations(db, team_id)
    round_ids = set()
    for participation in participations:
        round_ids.add(participation['round_id'])
    rounds = load_rounds(db, round_ids, now)
    view['participations'] = [
        view_team_participation(
            participation,
            rounds[participation['round_id']])
        for participation in participations
    ]
    if len(participations) == 0:
        return view

    # Mark the lastest (or selected) participation as current.
    if participation_id is None:
        participation = participations[-1]
    else:
        participation = get_by_id(participations, participation_id)
        if participation is None:
            return view
    view['participation_id'] = participation['id']
    for pview in view['participations']:
        if pview['id'] == participation['id']:
            pview['is_current'] = True

    #
    # Add the current participation's round.
    #
    round_id = participation['round_id']
    round_ = rounds[round_id]
    view['round'] = view_round(round_)

    #
    # Add the tasks for the current round.
    #
    round_tasks = load_round_tasks(db, round_id)
    view['round']['task_ids'] = [str(rt['id']) for rt in round_tasks]
    round_task_views = view['round_tasks'] = {
        str(rt['id']): view_round_task(rt) for rt in round_tasks
    }

    region_id = team['region_id']

    if round_['status'] == 'closed' and team['region_id'] is not None:
        region = load_region(db, region_id)
        national_count = count_teams_in_round(db, round_id)
        big_region_count = count_teams_in_round_big_region(
            db, round_id, region['big_region_code'])
        region_count = count_teams_in_round_region(db, round_id, region_id)
        view['ranking'] = {
            'national': {
                'rank': participation['rank_national'],
                'count': national_count
            },
            'big_region': {
                'name': region['big_region_name'],
                'rank': participation['rank_big_regional'],
                'count': big_region_count
            },
            'region': {
                'name': region['name'],
                'rank': participation['rank_regional'],
                'count': region_count
            }
        }

    # XXX A team's validity should be checked against settings for a
    #     competition rather than a round.
    causes = validate_members_for_round(members, round_)
    team_view['round_access'] = list(causes.keys())
    team_view['is_invalid'] = len(causes) != 0

    # Do not return attempts if the team is invalid.
    if team_view['is_invalid']:
        return view

    # Load the participation attempts.
    attempts = load_participation_attempts(db, participation['id'], now)
    view_task_attempts(attempts, round_task_views)
    print("attempts {} {}".format(attempt_id, attempts))

    # Find the requested attempt.
    current_attempt = get_by_id(attempts, attempt_id)
    if current_attempt is None:
        return view
    view['attempt_id'] = attempt_id

    # Focus on the current attempt.
    current_round_task = round_task_views[str(current_attempt['round_task_id'])]
    current_attempt_view = None
    for attempt_view in current_round_task['attempts']:
        if attempt_id == attempt_view.get('id'):
            current_attempt_view = attempt_view
    view['attempt'] = current_attempt_view
    view['round_task'] = current_round_task  # XXX duplicates attempts :(

    if False:  # Access codes are disabled
        members_view = view['team']['members']
        access_codes = load_unlocked_access_codes(db, attempt_id)
        add_members_access_codes(members_view, access_codes)
        if current_attempt['is_training']:
            needs_codes = not have_one_code(members_view)
        else:
            needs_codes = not have_code_majority(members_view)
        current_attempt_view['needs_codes'] = needs_codes

    # Add task instance data, if available.
    try:
        # XXX Previously load_task_instance_team_data which did not parse
        #     full_data.
        # /!\ task contains sensitive data
        # XXX If the round is closed, load and pass full_data?
        task_instance = load_user_task_instance(db, attempt_id)
    except ModelError:
        return view

    # If the round has a time limit, return the countdown.
    if round_['duration'] is not None:
        started_at = participation['started_at']
        if started_at is not None:
            duration = timedelta(minutes=round_['duration'])
            countdown = started_at + duration
            view['countdown'] = started_at + duration
            if countdown < now:
                return view

    view['team_data'] = task_instance['team_data']

    # Add a list of the workspace revisions for this attempt.
    add_revisions(db, view, attempt_id)

    # Give the user the id of their latest revision for the
    # current attempt, to be loaded into the crypto tab on
    # first access.
    revision_id = load_user_latest_revision_id(
        db, user_id, attempt_id)
    view['my_latest_revision_id'] = revision_id

    return view
예제 #12
0
def assign_task_instance(db, attempt_id, now):
    """ Assign a task to the attempt.
        The team performing the attempt must be valid, otherwise
        an exception is raised.
    """
    attempt = load_attempt(db, attempt_id, now=now)
    if attempt['started_at'] is not None:
        return ModelError('already have a task')
    participation_id = attempt['participation_id']
    participation = load_participation(db, participation_id)
    # The team must be valid to obtain a task.
    team_id = participation['team_id']
    round_id = participation['round_id']
    validate_team(db, team_id, round_id, now=now)
    # Verify that the round is open for training (and implicitly for
    # timed attempts).
    round_ = load_round(db, round_id, now=now)
    if round_['status'] != 'open':
        raise ModelError('round not open')
    if now < round_['training_opens_at']:
        raise ModelError('training is not open')
    # Load the round_task
    round_task_id = attempt['round_task_id']
    round_task = load_round_task(db, round_task_id)
    # TODO: check round_task['have_training_attempt'] if next ordinal is 1
    # TODO: check round_task['max_timed_attempts'] if next ordinal is >1

    task = load_task(db, round_task['task_id'])  # backend_url
    backend_url = task['backend_url']
    auth = task['backend_auth']
    task_params = round_task['generate_params']
    seed = str(attempt_id)  # TODO add a participation-specific key to the seed
    team_data, full_data = task_generate(backend_url, task_params, seed, auth)

    try:
        # Lock the task_instances table to prevent concurrent inserts.
        db.execute('LOCK TABLES task_instances WRITE, attempts READ').close()
        attrs = {
            'attempt_id': attempt_id,
            'created_at': now,
            'updated_at': now,
            'full_data': db.dump_json(full_data),
            'team_data': db.dump_json(team_data)
        }
        task_instances = db.tables.task_instances
        db.insert_row(task_instances, attrs)
    finally:
        db.execute('UNLOCK TABLES').close()

    # Update the attempt.
    attempt_attrs = {'started_at': now}
    attempt_duration = round_task['attempt_duration']
    if attempt_duration is not None:
        # Set the closing time on the attempt.
        attempt_attrs['closes_at'] = now + timedelta(minutes=attempt_duration)
    attempts = db.tables.attempts
    db.update_row(attempts, attempt_id, attempt_attrs)

    # If starting the first timed attempt, set started_at on the participation.
    if attempt['is_training'] == 0 and attempt['ordinal'] == 1:
        update_participation(db, participation_id, {'started_at': now})

    # Create an initial workspace for the attempt.
    create_attempt_workspace(db, attempt_id, now)
예제 #13
0
def view_requesting_user(db,
                         user_id=None,
                         participation_id=None,
                         attempt_id=None,
                         is_admin=False):

    now = datetime.utcnow()
    view = {'now': now, 'is_admin': is_admin}
    if user_id is None:
        return view

    #
    # Add the user.
    #
    user = load_user(db, user_id)
    if user is None:
        return view
    view['user_id'] = user_id
    view['user'] = view_user(user)
    team_id = user['team_id']

    #
    # Quick return path when the user has no team.
    #
    if team_id is None:
        # If the user has no team, we look for a round to which a
        # badge grants access.
        badges = user['badges']
        round_ids = find_round_ids_with_badges(db, badges, now)
        if len(round_ids) > 0:
            # TODO: resolve this somehow, for example by returning
            # the round views to the user and letting them choose.
            # For now, pick the first one (which has the greatest id).
            round_id = round_ids[0]
            round_ = load_round(db, round_id, now)
            view['round'] = view_round(round_)
        return view

    #
    # Add the team and team members.
    #
    team = load_team(db, team_id)
    members = load_team_members(db, team['id'], users=True)
    team_view = view['team'] = view_team(team, members)

    #
    # Add the team's participations.
    #
    participations = load_team_participations(db, team_id)
    round_ids = set()
    for participation in participations:
        round_ids.add(participation['round_id'])
    rounds = load_rounds(db, round_ids, now)
    view['participations'] = [
        view_team_participation(participation,
                                rounds[participation['round_id']])
        for participation in participations
    ]
    if len(participations) == 0:
        return view

    # Mark the lastest (or selected) participation as current.
    if participation_id is None:
        participation = participations[-1]
    else:
        participation = get_by_id(participations, participation_id)
        if participation is None:
            return view
    view['participation_id'] = participation['id']
    for pview in view['participations']:
        if pview['id'] == participation['id']:
            pview['is_current'] = True

    #
    # Add the current participation's round.
    #
    round_id = participation['round_id']
    round_ = rounds[round_id]
    view['round'] = view_round(round_)

    #
    # Add the tasks for the current round.
    #
    round_tasks = load_round_tasks(db, round_id)
    view['round']['task_ids'] = [str(rt['id']) for rt in round_tasks]
    round_task_views = view['round_tasks'] = {
        str(rt['id']): view_round_task(rt)
        for rt in round_tasks
    }

    region_id = team['region_id']

    if round_['status'] == 'closed' and team['region_id'] is not None:
        region = load_region(db, region_id)
        national_count = count_teams_in_round(db, round_id)
        big_region_count = count_teams_in_round_big_region(
            db, round_id, region['big_region_code'])
        region_count = count_teams_in_round_region(db, round_id, region_id)
        view['ranking'] = {
            'national': {
                'rank': participation['rank_national'],
                'count': national_count
            },
            'big_region': {
                'name': region['big_region_name'],
                'rank': participation['rank_big_regional'],
                'count': big_region_count
            },
            'region': {
                'name': region['name'],
                'rank': participation['rank_regional'],
                'count': region_count
            }
        }

    # XXX A team's validity should be checked against settings for a
    #     competition rather than a round.
    causes = validate_members_for_round(members, round_)
    team_view['round_access'] = list(causes.keys())
    team_view['is_invalid'] = len(causes) != 0

    # Do not return attempts if the team is invalid.
    if team_view['is_invalid']:
        return view

    # Load the participation attempts.
    attempts = load_participation_attempts(db, participation['id'], now)
    view_task_attempts(attempts, round_task_views)
    print("attempts {} {}".format(attempt_id, attempts))

    # Find the requested attempt.
    current_attempt = get_by_id(attempts, attempt_id)
    if current_attempt is None:
        return view
    view['attempt_id'] = attempt_id

    # Focus on the current attempt.
    current_round_task = round_task_views[str(
        current_attempt['round_task_id'])]
    current_attempt_view = None
    for attempt_view in current_round_task['attempts']:
        if attempt_id == attempt_view.get('id'):
            current_attempt_view = attempt_view
    view['attempt'] = current_attempt_view
    view['round_task'] = current_round_task  # XXX duplicates attempts :(

    if False:  # Access codes are disabled
        members_view = view['team']['members']
        access_codes = load_unlocked_access_codes(db, attempt_id)
        add_members_access_codes(members_view, access_codes)
        if current_attempt['is_training']:
            needs_codes = not have_one_code(members_view)
        else:
            needs_codes = not have_code_majority(members_view)
        current_attempt_view['needs_codes'] = needs_codes

    # Add task instance data, if available.
    try:
        # XXX Previously load_task_instance_team_data which did not parse
        #     full_data.
        # /!\ task contains sensitive data
        # XXX If the round is closed, load and pass full_data?
        task_instance = load_user_task_instance(db, attempt_id)
    except ModelError:
        return view

    # If the round has a time limit, return the countdown.
    if round_['duration'] is not None:
        started_at = participation['started_at']
        if started_at is not None:
            duration = timedelta(minutes=round_['duration'])
            countdown = started_at + duration
            view['countdown'] = started_at + duration
            if countdown < now:
                return view

    view['team_data'] = task_instance['team_data']

    # Add a list of the workspace revisions for this attempt.
    add_revisions(db, view, attempt_id)

    # Give the user the id of their latest revision for the
    # current attempt, to be loaded into the crypto tab on
    # first access.
    revision_id = load_user_latest_revision_id(db, user_id, attempt_id)
    view['my_latest_revision_id'] = revision_id

    return view
예제 #14
0
def grade_answer(db, attempt_id, submitter_id, revision_id, data, now):
    attempt = load_attempt(db, attempt_id, now)
    is_training = attempt['is_training']
    participation_id = attempt['participation_id']
    participation = load_participation(
        db, participation_id, for_update=True)
    round_task = load_round_task(db, attempt['round_task_id'])
    task = load_task(db, round_task['task_id'])  # backend_url
    # Fail if the attempt is closed.
    if attempt['is_closed']:
        raise ModelError('attempt is closed')
    # Get the greatest ordinal and nth most recent submitted_at.
    (prev_ordinal, nth_submitted_at) = \
        get_attempt_latest_answer_infos(db, attempt_id, nth=2)
    # Fail if timed(not training) and there are more answers than
    # allowed.
    round_ = load_round(db, participation['round_id'], now)
    if round_['status'] != 'open':
        raise ModelError('round not open')
    max_answers = round_task['max_attempt_answers']
    if (not is_training and max_answers is not None and
            prev_ordinal >= max_answers):
        raise ModelError('too many answers')
    # Fail if answer was submitted too recently.
    if nth_submitted_at is not None:
        if now < nth_submitted_at + timedelta(minutes=1):
            raise ModelError('too soon')
    ordinal = prev_ordinal + 1

    # Perform grading.
    backend_url = task['backend_url']
    auth = task['backend_auth']
    task_instance = load_task_instance(db, attempt_id)
    full_data = task_instance['full_data']
    team_data = task_instance['team_data']
    grading = task_grade_answer(backend_url, full_data, team_data, data, auth)
    # grading: {feedback, score, is_solution, is_full_solution}

    # Update the attempt to indicate if solved, fully solved.
    update_attempt_with_grading(db, attempt_id, grading)

    # Store the answer and grading.
    answers = db.tables.answers
    answer = {
        'attempt_id': attempt_id,
        'submitter_id': submitter_id,
        'ordinal': ordinal,
        'created_at': now,
        'answer': db.dump_json(data),
        'grading': db.dump_json(grading),
        'score': grading['score'],
        'is_solution': grading['is_solution'],
        'is_full_solution': grading['is_full_solution'],
        'revision_id': revision_id
    }
    answer['id'] = db.insert_row(answers, answer)
    # Best score for the participation?
    new_score = Decimal(answer['score'])
    if not is_training and (participation['score'] is None or
                            new_score > participation['score']):
        update_participation(
            db, participation_id, {'score': new_score})
    return (answer, grading.get('feedback'))
예제 #15
0
def grade_answer(db, attempt_id, submitter_id, revision_id, data, now):
    attempt = load_attempt(db, attempt_id, now)
    is_training = attempt['is_training']
    participation_id = attempt['participation_id']
    participation = load_participation(db, participation_id, for_update=True)
    round_task = load_round_task(db, attempt['round_task_id'])
    task = load_task(db, round_task['task_id'])  # backend_url
    # Fail if the attempt is closed.
    if attempt['is_closed']:
        raise ModelError('attempt is closed')
    # Get the greatest ordinal and nth most recent submitted_at.
    (prev_ordinal, nth_submitted_at) = \
        get_attempt_latest_answer_infos(db, attempt_id, nth=2)
    # Fail if timed(not training) and there are more answers than
    # allowed.
    round_ = load_round(db, participation['round_id'], now)
    if round_['status'] != 'open':
        raise ModelError('round not open')
    max_answers = round_task['max_attempt_answers']
    if (not is_training and max_answers is not None
            and prev_ordinal >= max_answers):
        raise ModelError('too many answers')
    # Fail if answer was submitted too recently.
    if nth_submitted_at is not None:
        if now < nth_submitted_at + timedelta(minutes=1):
            raise ModelError('too soon')
    ordinal = prev_ordinal + 1

    # Perform grading.
    backend_url = task['backend_url']
    auth = task['backend_auth']
    task_instance = load_task_instance(db, attempt_id)
    full_data = task_instance['full_data']
    team_data = task_instance['team_data']
    grading = task_grade_answer(backend_url, full_data, team_data, data, auth)
    # grading: {feedback, score, is_solution, is_full_solution}

    # Update the attempt to indicate if solved, fully solved.
    update_attempt_with_grading(db, attempt_id, grading)

    # Store the answer and grading.
    answers = db.tables.answers
    answer = {
        'attempt_id': attempt_id,
        'submitter_id': submitter_id,
        'ordinal': ordinal,
        'created_at': now,
        'answer': db.dump_json(data),
        'grading': db.dump_json(grading),
        'score': grading['score'],
        'is_solution': grading['is_solution'],
        'is_full_solution': grading['is_full_solution'],
        'revision_id': revision_id
    }
    answer['id'] = db.insert_row(answers, answer)
    # Best score for the participation?
    new_score = Decimal(answer['score'])
    if not is_training and (participation['score'] is None
                            or new_score > participation['score']):
        update_participation(db, participation_id, {'score': new_score})
    return (answer, grading.get('feedback'))