コード例 #1
0
ファイル: admin_endpoints.py プロジェクト: baturin/montage
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
ファイル: admin_endpoints.py プロジェクト: baturin/montage
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
ファイル: admin_endpoints.py プロジェクト: baturin/montage
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
ファイル: admin_endpoints.py プロジェクト: baturin/montage
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
ファイル: admin_endpoints.py プロジェクト: baturin/montage
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
ファイル: admin_endpoints.py プロジェクト: baturin/montage
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
ファイル: admin_endpoints.py プロジェクト: baturin/montage
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
ファイル: admin_endpoints.py プロジェクト: baturin/montage
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
ファイル: admin_endpoints.py プロジェクト: baturin/montage
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
ファイル: admin_endpoints.py プロジェクト: baturin/montage
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
ファイル: admin_endpoints.py プロジェクト: baturin/montage
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
ファイル: admin_endpoints.py プロジェクト: baturin/montage
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
ファイル: admin_endpoints.py プロジェクト: baturin/montage
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
ファイル: admin_endpoints.py プロジェクト: baturin/montage
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
ファイル: admin_endpoints.py プロジェクト: baturin/montage
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
ファイル: admin_endpoints.py プロジェクト: baturin/montage
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'}