Beispiel #1
0
def get_rows_for_csv(app_name):
    # need to use app_name and not app_label because the app might have been
    # removed from SESSION_CONFIGS
    models_module = otree.common.get_models_module(app_name)
    Player = models_module.Player
    Group = models_module.Group
    Subsession = models_module.Subsession

    columns_for_models = {
        Model.__name__.lower(): get_fields_for_csv(Model)
        for Model in [Player, Group, Subsession, Participant, Session]
    }

    participant_ids = values_flat(dbq(Player), Player.participant_id)
    session_ids = values_flat(dbq(Subsession), Subsession.session_id)

    players = Player.values_dicts()

    value_dicts = dict(
        group={row['id']: row
               for row in Group.values_dicts()},
        subsession={row['id']: row
                    for row in Subsession.values_dicts()},
        participant={
            row['id']: row
            for row in Participant.values_dicts(
                Participant.id.in_(participant_ids))
        },
        session={
            row['id']: row
            for row in Session.values_dicts(Session.id.in_(session_ids))
        },
    )

    model_order = ['participant', 'player', 'group', 'subsession', 'session']

    # header row
    rows = [[
        f'{m}.{col}' for m in model_order for col in columns_for_models[m]
    ]]

    for player in players:
        tweak_player_values_dict(player)
        row = []

        for model_name in model_order:
            if model_name == 'player':
                obj = player
            else:
                obj = value_dicts[model_name][player[f'{model_name}_id']]
            for colname in columns_for_models[model_name]:
                row.append(sanitize_for_csv(obj[colname]))
        rows.append(row)

    return rows
Beispiel #2
0
def export_page_times(fp):
    write_page_completion_buffer()
    batches = values_flat(
        dbq(PageTimeBatch).order_by('id'), PageTimeBatch.text)
    fp.write(','.join(TIME_SPENT_COLUMNS) + '\n')
    for batch in batches:
        fp.write(batch)
Beispiel #3
0
def _get_session_lookups(session_code) -> Dict[int, PageLookup]:
    session = dbq(Session).filter_by(code=session_code).one()
    pages = {}
    idx = 1
    for app_name in session.config['app_sequence']:
        models = get_models_module(app_name)
        Subsession = models.Subsession
        page_sequence = get_pages_module(app_name).page_sequence
        subsessions = {
            s[0]: s[1]
            for s in Subsession.objects_filter(session=session).with_entities(
                Subsession.round_number, Subsession.id)
        }

        for rd in range(1, models.Constants.num_rounds + 1):
            is_first_in_round = True
            for PageClass in page_sequence:
                pages[idx] = PageLookup(
                    app_name=app_name,
                    page_class=PageClass,
                    round_number=rd,
                    subsession_id=subsessions[rd],
                    # TODO: remove session ID, just use code everywhere
                    session_pk=session.id,
                    name_in_url=models.Constants.name_in_url,
                    is_first_in_round=is_first_in_round,
                )
                is_first_in_round = False
                idx += 1
    return pages
Beispiel #4
0
    def get(self, request):
        buf = StringIO()
        column_names = [
            'session_code',
            'id_in_session',
            'participant_code',
            'channel',
            'nickname',
            'body',
            'timestamp',
        ]

        rows = (
            dbq(ChatMessage)
            .join(Participant)
            .order_by(ChatMessage.timestamp)
            .with_entities(
                Participant._session_code,
                Participant.id_in_session,
                Participant.session_id,
                ChatMessage.channel,
                ChatMessage.nickname,
                ChatMessage.body,
                ChatMessage.timestamp,
            )
        )

        writer = csv.writer(buf)
        writer.writerows([column_names])
        writer.writerows(rows)
        response = get_csv_http_response(buf, 'ChatMessages')
        return response
Beispiel #5
0
 def _gbat_next_group_id_in_subsession(self):
     # 2017-05-05: seems like this can result in id_in_subsession that
     # doesn't start from 1.
     # especially if you do group_by_arrival_time in every round
     # is that a problem?
     Group = self._GroupClass()
     return (dbq(func.max(
         Group.id_in_subsession)).filter_by(session=self.session).scalar() +
             1)
Beispiel #6
0
 def _get_participants_for_this_waitpage(self):
     Player = self.PlayerClass
     fk_field = Player.subsession_id if self.wait_for_all_groups else Player.group_id
     # tried select_from but my filter clause didn't work
     return (
         dbq(Player)
         .join(Participant)
         .filter(fk_field == self._group_or_subsession.id)
         .with_entities(Participant)
     )
Beispiel #7
0
 def _get_group_matrix(self, ids_only=False):
     Player = self._PlayerClass()
     Group = self._GroupClass()
     players = (dbq(Player).join(Group).filter(
         Player.subsession == self).order_by(Group.id_in_subsession,
                                             'id_in_group'))
     d = defaultdict(list)
     for p in players:
         d[p.group.id_in_subsession].append(
             p.id_in_subsession if ids_only else p)
     return list(d.values())
Beispiel #8
0
    def vars_for_template(self):

        # can't use settings.OTREE_APPS, because maybe the app
        # was removed from SESSION_CONFIGS.
        app_names_with_data = set()
        for session in dbq(Session):
            for app_name in session.config['app_sequence']:
                app_names_with_data.add(app_name)

        custom_export_apps = []
        for app_name in app_names_with_data:
            models_module = otree.common.get_models_module(app_name)
            if getattr(models_module, 'custom_export', None):
                custom_export_apps.append(app_name)

        return dict(
            db_is_empty=not bool(dbq(Participant).first()),
            app_names=app_names_with_data,
            chat_messages_exist=bool(dbq(ChatMessage).first()),
            custom_export_apps=custom_export_apps,
        )
Beispiel #9
0
    def is_ready(self, *, app_name, player_id, page_index, session_pk):
        models_module = get_models_module(app_name)
        Player = models_module.Player
        Group = models_module.Group

        [group_id_in_subsession] = (dbq(Player).join(Group).filter(
            Player.id == player_id).with_entities(
                Group.id_in_subsession).one())

        return CompletedGBATWaitPage.objects_exists(
            page_index=page_index,
            id_in_subsession=group_id_in_subsession,
            session_id=session_pk,
        )
Beispiel #10
0
def get_rows_for_wide_csv(session_code):
    if session_code:
        sessions = [Session.objects_get(code=session_code)]
    else:
        sessions = dbq(Session).order_by('id').all()
    session_fields = get_fields_for_csv(Session)
    participant_fields = get_fields_for_csv(Participant)

    session_ids = [session.id for session in sessions]
    pps = (Participant.objects_filter(
        Participant.session_id.in_(session_ids)).order_by(
            Participant.id).all())
    session_cache = {row.id: row for row in sessions}

    session_config_fields = set()
    for session in sessions:
        for field_name in SessionConfig(session.config).editable_fields():
            session_config_fields.add(field_name)
    session_config_fields = list(session_config_fields)

    if not pps:
        # 1 empty row
        return [[]]

    header_row = [f'participant.{fname}' for fname in participant_fields]
    header_row += [f'session.{fname}' for fname in session_fields]
    header_row += [
        f'session.config.{fname}' for fname in session_config_fields
    ]
    rows = [header_row]

    for pp in pps:
        session = session_cache[pp.session_id]
        row = [getattr(pp, fname) for fname in participant_fields]
        row += [getattr(session, fname) for fname in session_fields]
        row += [session.config.get(fname) for fname in session_config_fields]
        rows.append(row)

    order_of_apps = _get_best_app_order(sessions)

    rounds_per_app = OrderedDict()
    for app_name in order_of_apps:
        try:
            models_module = get_models_module(app_name)
        except ModuleNotFoundError:
            # this should only happen with devserver because on production server,
            # you would need to resetdb after renaming an app.
            logger.warning(
                f'Cannot export data for app {app_name}, which existed when the session was run '
                f'but no longer exists.')
            continue

        highest_round_number = dbq(
            func.max(models_module.Subsession.round_number)).scalar()

        if highest_round_number is not None:
            rounds_per_app[app_name] = highest_round_number
    for app_name in rounds_per_app:
        for round_number in range(1, rounds_per_app[app_name] + 1):
            new_rows = get_rows_for_wide_csv_round(app_name, round_number,
                                                   sessions)
            for i in range(len(rows)):
                rows[i].extend(new_rows[i])

    return [[sanitize_for_csv(v) for v in row] for row in rows]
Beispiel #11
0
def create_session(
    session_config_name,
    *,
    num_participants,
    label='',
    room_name=None,
    is_mturk=False,
    is_demo=False,
    modified_session_config_fields=None,
) -> Session:

    num_subsessions = 0

    try:
        session_config = SESSION_CONFIGS_DICT[session_config_name]
    except KeyError:
        msg = 'Session config "{}" not found in settings.SESSION_CONFIGS.'
        raise KeyError(msg.format(session_config_name)) from None
    else:
        # copy so that we don't mutate the original
        # .copy() returns a dict, so need to convert back to SessionConfig
        session_config = SessionConfig(session_config.copy())

        modified_config = modified_session_config_fields or {}
        # this is for API. don't want to mislead people
        # to put stuff in the session config that should be in the session.
        bad_keys = modified_config.keys() & NON_EDITABLE_FIELDS
        if bad_keys:
            raise Exception(
                f'The following session config fields are not editable: {bad_keys}'
            )
        session_config.update(modified_config)

        # check validity and converts serialized decimal & currency values
        # back to their original data type (because they were serialized
        # when passed through channels
        session_config.clean()

    # check that it divides evenly
    session_lcm = session_config.get_lcm()
    if num_participants is None:
        # most games are multiplayer, so if it's under 2, we bump it to 2
        num_participants = max(session_lcm, 2)
    else:
        if num_participants % session_lcm:
            msg = (
                'Session Config {}: Number of participants ({}) is not a multiple '
                'of group size ({})'
            ).format(session_config['name'], num_participants, session_lcm)
            raise ValueError(msg)

    session = Session(
        config=session_config,
        label=label,
        is_demo=is_demo,
        num_participants=num_participants,
        is_mturk=is_mturk,
    )
    db.add(session)
    db.commit()

    session_code = session.code

    participants = [
        Participant(
            id_in_session=id_in_session, session=session, _session_code=session_code,
        )
        for id_in_session in list(range(1, num_participants + 1))
    ]

    db.add_all(participants)
    db.commit()

    # participant_values = (
    #     db.query(Participant)
    #     .filter(Session.id == session.id)
    #     .order_by('id')
    #     .with_entities(Participant.id, Participant.code)
    # ).all()

    participant_values = (
        db.query(Participant)
        .join(Session)
        .filter(Session.id == session.id)
        .order_by(Participant.id)
        .with_entities(Participant.id, Participant.code)
    ).all()

    num_pages = 0

    for app_name in session_config['app_sequence']:

        views_module = common.get_pages_module(app_name)
        models_module = get_models_module(app_name)
        Constants: BaseConstants = models_module.Constants
        num_subsessions += Constants.num_rounds

        round_numbers = list(range(1, Constants.num_rounds + 1))

        num_pages += Constants.num_rounds * len(views_module.page_sequence)

        Subsession = models_module.Subsession
        Group = models_module.Group
        Player = models_module.Player

        subsessions = [
            Subsession(round_number=round_number, session=session)
            for round_number in round_numbers
        ]

        db.add_all(subsessions)
        db.commit()

        subsessions = (
            dbq(Subsession)
            .filter_by(session=session)
            .order_by('round_number')
            .with_entities('id', 'round_number')
        )

        ppg = Constants.players_per_group
        if ppg is None or Subsession._has_group_by_arrival_time():
            ppg = num_participants

        num_groups_per_round = int(num_participants / ppg)

        groups_to_create = []
        for ss_id, ss_rd in subsessions:
            for id_in_subsession in range(1, num_groups_per_round + 1):
                groups_to_create.append(
                    Group(
                        session=session,
                        subsession_id=ss_id,
                        round_number=ss_rd,
                        id_in_subsession=id_in_subsession,
                    )
                )

        db.add_all(groups_to_create)

        groups = (
            dbq(Group).filter_by(session=session).order_by('id_in_subsession')
        ).all()

        groups_lookup = defaultdict(list)

        for group in groups:

            groups_lookup[group.subsession_id].append(group.id)

        players_to_create = []

        for ss_id, ss_rd in subsessions:
            roles = get_roles(Constants)
            participant_index = 0
            for group_id in groups_lookup[ss_id]:
                for id_in_group in range(1, ppg + 1):
                    participant = participant_values[participant_index]
                    players_to_create.append(
                        Player(
                            session=session,
                            subsession_id=ss_id,
                            round_number=ss_rd,
                            participant_id=participant[0],
                            group_id=group_id,
                            id_in_group=id_in_group,
                            _role=get_role(roles, id_in_group),
                        )
                    )
                    participant_index += 1

        # Create players
        db.add_all(players_to_create)

    dbq(Participant).filter_by(session=session).update(
        {Participant._max_page_index: num_pages}
    )

    # make creating_session use the current session,
    # so that session.save() below doesn't overwrite everything
    # set earlier
    for subsession in session.get_subsessions():
        subsession.creating_session()

    # 2017-09-27: moving this inside the transaction
    session._set_admin_report_app_names()

    if room_name is not None:
        from otree.room import ROOM_DICT

        room = ROOM_DICT[room_name]
        room.set_session(session)

    db.commit()

    return session
Beispiel #12
0
    def get(self, request: Request):
        code = request.path_params['code']
        session = self.session = db.get_or_404(Session, code=code)
        GET = request.query_params
        try:
            assignment_id = GET['assignmentId']
            worker_id = GET['workerId']
        except KeyError:
            return Response(
                'URL is missing assignmentId or workerId parameter',
                status_code=404)
        qual_id = session.config['mturk_hit_settings'].get(
            'grant_qualification_id')
        use_sandbox = session.mturk_use_sandbox
        if qual_id and not use_sandbox:
            # this is necessary because MTurk's qualification requirements
            # don't prevent 100% of duplicate participation. See:
            # https://groups.google.com/forum/#!topic/otree/B66HhbFE9ck

            previous_participation = (dbq(Participant).join(Session).filter(
                Participant.session != session,
                Session.mturk_qual_id == qual_id,
                Participant.mturk_worker_id == worker_id,
            ).scalar() is not None)
            if previous_participation:
                return Response('You have already accepted a related HIT')

            # if using sandbox, there is no point in granting quals.
            # https://groups.google.com/forum/#!topic/otree/aAmqTUF-b60

            # don't pass request arg, because we don't want to show a message.
            # using the fully qualified name because that seems to make mock.patch work
            mturk_client = otree.views.mturk.get_mturk_client(
                use_sandbox=use_sandbox)

            # seems OK to assign this multiple times
            mturk_client.associate_qualification_with_worker(
                QualificationTypeId=qual_id,
                WorkerId=worker_id,
                # Mturk complains if I omit IntegerValue
                IntegerValue=1,
            )

        try:
            # just check if this worker already game, but
            # don't filter for assignment, because maybe they already started
            # and returned the previous assignment
            # in this case, we should assign back to the same participant
            # so that we don't get duplicates in the DB, and so people
            # can't snoop and try the HIT first, then re-try to get a bigger bonus
            pp = self.session.pp_set.filter_by(mturk_worker_id=worker_id).one()
        except NoResultFound:
            pp = self.session.pp_set.filter_by(
                visited=False).order_by('id').first()
            if not pp:
                return no_participants_left_http_response()

            # 2014-10-17: needs to be here even if it's also set in
            # the next view to prevent race conditions
            # this needs to be inside the lock
            pp.visited = True
            pp.mturk_worker_id = worker_id
        # reassign assignment_id, even if they are returning, because maybe they accepted
        # and then returned, then re-accepted with a different assignment ID
        # if it's their second time
        pp.mturk_assignment_id = assignment_id
        return RedirectResponse(pp._start_url(), status_code=302)