示例#1
0
def autodisqualify(user_dao, round_id, request_dict):
    coord_dao = CoordinatorDAO.from_round(user_dao, round_id)
    rnd = coord_dao.get_round(round_id)

    if rnd.status != 'paused':
        raise InvalidAction('round must be paused to disqualify entries')

    dq_by_upload_date = request_dict.get('dq_by_upload_date')
    dq_by_resolution = request_dict.get('dq_by_resolution')
    dq_by_uploader = request_dict.get('dq_by_uploader')
    dq_by_filetype = request_dict.get('dq_by_filetype')

    round_entries = []

    if rnd.config.get('dq_by_upload_date') or dq_by_upload_date:
        dq_upload_date = coord_dao.autodisqualify_by_date(round_id)
        round_entries += dq_upload_date

    if rnd.config.get('dq_by_resolution') or dq_by_resolution:
        dq_resolution = coord_dao.autodisqualify_by_resolution(round_id)
        round_entries += dq_resolution

    if rnd.config.get('dq_by_uploader') or dq_by_uploader:
        dq_uploader = coord_dao.autodisqualify_by_uploader(round_id)
        round_entries += dq_uploader

    if rnd.config.get('dq_by_filetype') or dq_by_filetype:
        dq_filetype = coord_dao.autodisqualify_by_filetype(round_id)
        round_entries += dq_filetype

    data = [re.to_dq_details() for re in round_entries]

    return {'data': data}
示例#2
0
def download_results_csv(user_dao, round_id, request_dict):
    coord_dao = CoordinatorDAO.from_round(user_dao, round_id)
    rnd = coord_dao.get_round(round_id)
    now = datetime.datetime.now().isoformat()
    output_name = 'montage_results-%s-%s.csv' % (slugify(rnd.name, ascii=True), now)

    # TODO: Confirm round is finalized
    # raise DoesNotExist('round results not yet finalized')

    results_by_name = coord_dao.make_vote_table(round_id)

    output = io.BytesIO()
    csv_fieldnames = ['filename', 'average'] + [r.username for r in rnd.jurors]
    csv_writer = unicodecsv.DictWriter(output, fieldnames=csv_fieldnames,
                                       restval=None)
    # na means this entry wasn't assigned

    csv_writer.writeheader()

    for filename, ratings in results_by_name.items():
        csv_row = {'filename': filename}
        valid_ratings = [r for r in ratings.values() if type(r) is not str]
        if valid_ratings:
            # TODO: catch if there are more than a quorum of votes
            ratings['average'] = sum(valid_ratings) / len(valid_ratings)
        else:
            ratings['average'] = 'na'
        csv_row.update(ratings)
        csv_writer.writerow(csv_row)

    ret = output.getvalue()
    resp = Response(ret, mimetype='text/csv')
    resp.mimetype_params['charset'] = 'utf-8'
    resp.headers['Content-Disposition'] = 'attachment; filename=%s' % output_name
    return resp
示例#3
0
def preview_disqualification(user_dao, round_id):
    # Let's you see what will get disqualified, without actually
    # disqualifying any entries
    coord_dao = CoordinatorDAO.from_round(user_dao, round_id)
    # TODO: explain each disqualification
    rnd = coord_dao.get_round(round_id)
    ret = {'config': rnd.config}

    by_upload_date = coord_dao.autodisqualify_by_date(round_id, preview=True)
    ret['by_upload_date'] = [re.entry.to_details_dict(with_uploader=True)
                             for re in by_upload_date]

    by_resolution = coord_dao.autodisqualify_by_resolution(round_id, preview=True)
    ret['by_resolution'] = [re.entry.to_details_dict(with_uploader=True)
                            for re in by_resolution]

    by_uploader = coord_dao.autodisqualify_by_uploader(round_id, preview=True)
    ret['by_uploader'] = [re.entry.to_details_dict(with_uploader=True)
                          for re in by_uploader]

    by_filetype = coord_dao.autodisqualify_by_filetype(round_id, preview=True)
    ret['by_filetype'] = [re.entry.to_details_dict(with_uploader=True)
                          for re in by_filetype]

    return {'data': ret}
示例#4
0
def get_all_flags(user_dao, round_id, request_dict):
    if not request_dict:
        request_dict = {}
    limit = request_dict.get('limit', 10)
    offset = request_dict.get('offset', 0)
    coord_dao = CoordinatorDAO.from_round(user_dao, round_id)
    flags = coord_dao.get_flags(round_id, limit, offset)
    data = [f.to_details_dict() for f in flags]
    return {'data': data}
示例#5
0
def get_flagged_entries(user_dao, round_id):
    # TODO: include a limit?
    coord_dao = CoordinatorDAO.from_round(user_dao, round_id)
    flagged_entries = coord_dao.get_grouped_flags(round_id)
    ret = []
    for fe in flagged_entries:
        entry = fe.entry.to_details_dict()
        entry['flaggings'] = [f.to_details_dict() for f in fe.flaggings]
        ret.append(entry)
    return {'data': ret}
示例#6
0
def advance_round(user_dao, round_id, request_dict):
    """Technical there are four possibilities.

    1. Advancing from yesno/rating to another yesno/rating
    2. Advancing from yesno/rating to ranking
    3. Advancing from ranking to yesno/rating
    4. Advancing from ranking to another ranking

    Especially for the first version of Montage, this function will
    only be written to cover the first two cases. This is because
    campaigns are designed to end with a single ranking round.

    typical advancements are: "yesno -> rating -> ranking" or
    "yesno -> rating -> yesno -> ranking"

    """
    coord_dao = CoordinatorDAO.from_round(user_dao, round_id)
    rnd = coord_dao.get_round(round_id)

    if rnd.vote_method not in ('rating', 'yesno'):
        raise NotImplementedError()  # see docstring above
    threshold = float(request_dict['threshold'])
    _next_round_params = request_dict['next_round']
    nrp = _prepare_round_params(coord_dao, _next_round_params)

    if nrp['vote_method'] == 'ranking' \
       and len(nrp['jurors']) != nrp.get('quorum'):
        # TODO: log
        # (ranking round quorum must match juror count)
        nrp['quorum'] = len(nrp['jurors'])

    # TODO: inherit round config from previous round?
    adv_group = coord_dao.finalize_rating_round(round_id, threshold=threshold)

    next_rnd = coord_dao.create_round(**nrp)
    source = 'round(#%s)' % round_id
    params = {'round': round_id, 'threshold': threshold}
    coord_dao.add_round_entries(next_rnd.id,
                                adv_group,
                                method=ROUND_METHOD,
                                params=params)

    # NOTE: disqualifications are not repeated, as they should have
    # been performed the first round.

    next_rnd_dict = next_rnd.to_details_dict()
    next_rnd_dict['progress'] = next_rnd.get_count_map()

    msg = (
        '%s advanced campaign %r (#%s) from %s round "%s" to %s round "%s"' %
        (user_dao.user.username, rnd.campaign.name, rnd.campaign.id,
         rnd.vote_method, round_id, next_rnd.vote_method, next_rnd.name))
    coord_dao.log_action('advance_round', campaign=rnd.campaign, message=msg)

    return {'data': next_rnd_dict}
示例#7
0
def edit_round(user_dao, round_id, request_dict):
    """
    Summary: Post a new campaign

    Request model:
        campaign_name

    Response model: AdminCampaignDetails
    """
    coord_dao = CoordinatorDAO.from_round(user_dao, round_id)
    new_val_map = coord_dao.edit_round(round_id, request_dict)
    return {'data': new_val_map}
示例#8
0
def activate_round(user_dao, round_id, request_dict):
    """
    Summary: Set the status of a round to active.

    Request model:
        round_id:
            type: int64
    """
    coord_dao = CoordinatorDAO.from_round(user_dao, round_id)
    coord_dao.activate_round(round_id)
    rnd = coord_dao.get_round(round_id)
    ret_data = rnd.get_count_map()
    ret_data['round_id'] = round_id
    return {'data': ret_data}
示例#9
0
def download_round_entries_csv(user_dao, round_id):
    coord_dao = CoordinatorDAO.from_round(user_dao, round_id)
    rnd = coord_dao.get_round(round_id)
    entries = coord_dao.get_round_entries(round_id)
    entry_infos = [e.to_export_dict() for e in entries]
    output_name = 'montage_entries-%s.csv' % slugify(rnd.name, ascii=True)
    output = io.BytesIO()
    csv_fieldnames = sorted(entry_infos[0].keys())
    csv_writer = unicodecsv.DictWriter(output, fieldnames=csv_fieldnames)
    csv_writer.writeheader()
    csv_writer.writerows(entry_infos)
    ret = output.getvalue()
    resp = Response(ret, mimetype='text/csv')
    resp.mimetype_params['charset'] = 'utf-8'
    resp.headers['Content-Disposition'] = 'attachment; filename=%s' % (output_name,)
    return resp
示例#10
0
def get_round_results_preview(user_dao, round_id):
    coord_dao = CoordinatorDAO.from_round(user_dao, round_id)
    rnd = coord_dao.get_round(round_id)

    round_counts = rnd.get_count_map()
    is_closeable = rnd.check_closability()

    data = {
        'round': rnd.to_info_dict(),
        'counts': round_counts,
        'is_closeable': is_closeable
    }

    if rnd.vote_method in ('yesno', 'rating'):
        data['ratings'] = coord_dao.get_round_average_rating_map(round_id)
        try:
            data['thresholds'] = get_threshold_map(data['ratings'])
        except:
            # import pdb;pdb.post_mortem()
            raise
    elif rnd.vote_method == 'ranking':
        if not is_closeable:
            # TODO: What should this return for ranking rounds? The ranking
            # round is sorta an all-or-nothing deal, unlike the rating rounds
            # where you can take a peek at in-progress results
            # import pdb;pdb.set_trace()
            return {
                'status':
                'failure',
                'errors': ('cannot preview results of a ranking '
                           'round until all ballots are '
                           'submitted'),
                'data':
                None
            }

        rankings = coord_dao.get_round_ranking_list(round_id)

        data['rankings'] = [r.to_dict() for r in rankings]

    else:
        raise NotImplementedResponse()

    return {'data': data}
示例#11
0
def get_round(user_dao, round_id):
    """
    Summary: Get admin-level details for a round, identified by round ID.

    Request model:
        round_id

    Response model name: AdminRoundDetails

    Errors:
       403: User does not have permission to access requested round
       404: Round not found
    """
    coord_dao = CoordinatorDAO.from_round(user_dao, round_id)
    rnd = coord_dao.get_round(round_id)
    rnd_stats = rnd.get_count_map()
    # entries_info = user_dao.get_entry_info(round_id) # TODO
    # TODO: joinedload if this generates too many queries
    data = make_admin_round_details(rnd, rnd_stats)
    return {'data': data}
示例#12
0
def get_round_results_preview(user_dao, round_id):
    coord_dao = CoordinatorDAO.from_round(user_dao, round_id)
    rnd = coord_dao.get_round(round_id)

    round_counts = rnd.get_count_map()
    is_closeable = round_counts['total_open_tasks'] == 0

    data = {
        'round': rnd.to_info_dict(),
        'counts': round_counts,
        'is_closeable': is_closeable
    }

    if rnd.vote_method in ('yesno', 'rating'):
        data['ratings'] = coord_dao.get_round_average_rating_map(round_id)
        try:
            data['thresholds'] = get_threshold_map(data['ratings'])
        except:
            # import pdb;pdb.post_mortem()
            raise
    elif rnd.vote_method == 'ranking':
        if not is_closeable:
            # TODO: What should this return for ranking rounds? The ranking
            # round is sorta an all-or-nothing deal, unlike the rating rounds
            # where you can take a peek at in-progress results
            # import pdb;pdb.set_trace()
            raise InvalidAction('round must be closeable to preview results')

        rankings = coord_dao.get_round_ranking_list(round_id)

        data['rankings'] = [r.to_dict() for r in rankings]

    else:
        raise NotImplementedError()

    return {'data': data}
示例#13
0
def get_disqualified(user_dao, round_id):
    coord_dao = CoordinatorDAO.from_round(user_dao, round_id)
    round_entries = coord_dao.get_disqualified(round_id)
    data = [re.to_dq_details() for re in round_entries]
    return {'data': data}
示例#14
0
def disqualify_entry(user_dao, round_id, entry_id, request_dict):
    if not request_dict:
        request_dict = {}
    reason = request_dict.get('reason')
    coord_dao = CoordinatorDAO.from_round(user_dao, round_id)
    coord_dao.disqualify(round_id, entry_id, reason)
示例#15
0
def requalify_entry(user_dao, round_id, entry_id, request_dict):
    coord_dao = CoordinatorDAO.from_round(user_dao, round_id)
    coord_dao.requalify(round_id, entry_id)
示例#16
0
def import_entries(user_dao, round_id, request_dict):
    """
    Summary: Load entries into a round via one of four import methods

    Request model:
      - round_id (in path)
      - import_method:
        - gistcsv
        - category
        - round
        - selected
      - gist_url (if import_method=gistcsv)
      - category (if import_method=category)
      - threshold (if import_method=round)
      - file_names (if import_method=selected)

    Response model name:
      - data:
        - round_id
        - new_entry_count
        - new_round_entry_count
        - total_entries
        - status: success or failure
        - errors: description of the failure (if any)
        - warnings: possible problems to alert the user
          - empty import (no entries)
          - duplicate import (no new entries)
          - all disqualified
    """
    coord_dao = CoordinatorDAO.from_round(user_dao, round_id)
    import_method = request_dict['import_method']

    # loader warnings
    import_warnings = list()

    if import_method == 'csv' or import_method == 'gistcsv':
        if import_method == 'gistcsv':
            csv_url = request_dict['gist_url']
        else:
            csv_url = request_dict['csv_url']

        entries, warnings = coord_dao.add_entries_from_csv(round_id,
                                                           csv_url)
        params = {'csv_url': csv_url}
        if warnings:
            msg = 'unable to load {} files ({!r})'.format(len(warnings), warnings)
            import_warnings.append(msg)
    elif import_method == CATEGORY_METHOD:
        cat_name = request_dict['category']
        entries = coord_dao.add_entries_from_cat(round_id, cat_name)
        params = {'category': cat_name}
    elif import_method == ROUND_METHOD:
        threshold = request_dict['threshold']
        prev_round_id = request_dict['previous_round_id']
        entries = coord_dao.get_rating_advancing_group(prev_round_id, threshold)
        params = {'threshold': threshold,
                  'round_id': prev_round_id}
    elif import_method == SELECTED_METHOD:
        file_names = request_dict['file_names']
        entries, warnings = coord_dao.add_entries_by_name(round_id, file_names)
        if warnings:
            formatted_warnings = '\n'.join([
                '- {}'.format(warning) for warning in warnings
            ])
            msg = 'unable to load {} files:\n{}'.format(len(warnings), formatted_warnings)
            import_warnings.append({'import issues', msg})
        params = {'file_names': file_names}
    else:
        raise NotImplementedResponse()

    new_entry_stats = coord_dao.add_round_entries(round_id, entries,
                                                  method=import_method,
                                                  params=params)
    new_entry_stats['warnings'] = import_warnings

    if not entries:
        new_entry_stats['warnings'].append({'empty import':
                                            'no entries imported'})
    elif not new_entry_stats.get('new_entry_count'):
        new_entry_stats['warnings'].append({'duplicate import':
                                            'no new entries imported'})

    # automatically disqualify entries based on round config
    auto_dq = autodisqualify(user_dao, round_id, request_dict={})
    new_entry_stats['disqualified'] = auto_dq['data']
    if len(new_entry_stats['disqualified']) >= len(entries):
        new_entry_stats['warnings'].append({'all disqualified':
                  'all entries disqualified by round settings'})

    return {'data': new_entry_stats}
示例#17
0
def get_results(user_dao, round_id, request_dict):
    # TODO: Docs
    coord_dao = CoordinatorDAO.from_round(user_dao, round_id)
    results_by_name = coord_dao.make_vote_table(round_id)
    return {'data': results_by_name}
示例#18
0
def cancel_round(user_dao, round_id):
    coord_dao = CoordinatorDAO.from_round(user_dao, round_id)
    rnd = coord_dao.cancel_round(round_id)
    stats = rnd.get_count_map()
    return {'data': stats}
示例#19
0
def get_round_reviews(user_dao, round_id):
    coord_dao = CoordinatorDAO.from_round(user_dao, round_id)
    entries = coord_dao.get_reviews_table(round_id)
    entry_infos = [e.to_details_dict() for e in entries]
    return {'data': entry_infos}
示例#20
0
def get_round_entries(user_dao, round_id):
    coord_dao = CoordinatorDAO.from_round(user_dao, round_id)
    entries = coord_dao.get_round_entries(round_id)
    entry_infos = [e.to_export_dict() for e in entries]
    return {'file_infos': entry_infos}
示例#21
0
def pause_round(user_dao, round_id, request_dict):
    coord_dao = CoordinatorDAO.from_round(user_dao, round_id)
    coord_dao.pause_round(round_id)
    return {'data': 'paused'}