def pages_function(helper: AppCheckHelper, app_name): pages_module = common.get_pages_module(app_name) try: page_list = pages_module.page_sequence except: helper.add_error('pages.py is missing the variable page_sequence.', numeric_id=21) return else: for i, ViewCls in enumerate(page_list): # there is no good reason to include Page in page_sequence. # As for WaitPage: even though it works fine currently # and can save the effort of subclassing, # we should restrict it, because: # - one user had "class WaitPage(Page):". # - if someone makes "class WaitPage(WaitPage):", they might # not realize why it's inheriting the extra behavior. # overall, I think the small inconvenience of having to subclass # once per app # is outweighed by the unexpected behavior if someone subclasses # it without understanding inheritance. # BUT: built-in Trust game had a wait page called WaitPage. # that was fixed on Aug 24, 2017, need to wait a while... # see below in ensure_no_misspelled_attributes, # we can get rid of a check there also if ViewCls.__name__ == 'Page': msg = "page_sequence cannot contain a class called 'Page'." helper.add_error(msg, numeric_id=22) if ViewCls.__name__ == 'WaitPage' and app_name != 'trust': msg = "page_sequence cannot contain a class called 'WaitPage'." helper.add_error(msg, numeric_id=221) if issubclass(ViewCls, WaitPage): if ViewCls.group_by_arrival_time: if i > 0: helper.add_error( '"{}" has group_by_arrival_time=True, so ' 'it must be placed first in page_sequence.'.format( ViewCls.__name__), numeric_id=23, ) if ViewCls.wait_for_all_groups: helper.add_error( 'Page "{}" has group_by_arrival_time=True, so ' 'it cannot have wait_for_all_groups=True also.'. format(ViewCls.__name__), numeric_id=24, ) if hasattr(ViewCls, 'get_players_for_group'): helper.add_error( 'Page "{}" defines get_players_for_group, which is deprecated. ' 'You should instead define group_by_arrival_time_method on the Subsession. ' ''.format(ViewCls.__name__), numeric_id=25, ) elif issubclass(ViewCls, Page): pass # ok else: msg = '"{}" is not a valid page'.format(ViewCls) helper.add_error(msg, numeric_id=26)
def _get_session_lookups(session_code) -> Dict[int, PageLookup]: session = Session.objects.get(code=session_code) pages = {} idx = 1 for app_name in session.config['app_sequence']: models = get_models_module(app_name) page_sequence = get_pages_module(app_name).page_sequence subsessions = { s['round_number']: s['id'] for s in models.Subsession.objects.filter(session=session).values( 'id', 'round_number' ) } 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.pk, 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
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
def get_urlpatterns(): routes = [] used_names_in_url = set() for app_name in settings.OTREE_APPS: models_module = common.get_models_module(app_name) name_in_url = models_module.Constants.name_in_url if name_in_url in used_names_in_url: msg = ("App {} has Constants.name_in_url='{}', " "which is already used by another app").format( app_name, name_in_url) raise ValueError(msg) used_names_in_url.add(name_in_url) views_module = common.get_pages_module(app_name) routes += url_patterns_from_app_pages(views_module.__name__, name_in_url) routes += url_patterns_from_builtin_module('otree.views.participant') routes += url_patterns_from_builtin_module('otree.views.demo') routes += url_patterns_from_builtin_module('otree.views.admin') routes += url_patterns_from_builtin_module('otree.views.room') routes += url_patterns_from_builtin_module('otree.views.mturk') routes += url_patterns_from_builtin_module('otree.views.export') routes += url_patterns_from_builtin_module('otree.views.rest') routes += websocket_routes routes += [ Mount( '/static', app=OTreeStaticFiles(directory='_static', packages=['otree'] + settings.OTREE_APPS), name="static", ), Route("/favicon.ico", endpoint=Favicon), Route('/', endpoint=HomeRedirect), ] return routes
def get_urlpatterns(): urlpatterns = [ urls.url(r'^$', RedirectView.as_view(url='/demo', permanent=True)), urls.url(r'^accounts/login/$', LoginView.as_view(), name='login'), urls.url(r'^accounts/logout/$', LogoutView.as_view(), name='logout'), urls.url(r'^favicon\.ico$', RedirectView.as_view(url='/static/favicon.ico')), ] urlpatterns += staticfiles_urlpatterns() used_names_in_url = set() for app_name in settings.INSTALLED_OTREE_APPS: models_module = common.get_models_module(app_name) name_in_url = models_module.Constants.name_in_url if name_in_url in used_names_in_url: msg = ( "App {} has Constants.name_in_url='{}', " "which is already used by another app" ).format(app_name, name_in_url) raise ValueError(msg) used_names_in_url.add(name_in_url) views_module = common.get_pages_module(app_name) urlpatterns += url_patterns_from_app_pages(views_module.__name__, name_in_url) urlpatterns += url_patterns_from_builtin_module('otree.views.participant') urlpatterns += url_patterns_from_builtin_module('otree.views.demo') urlpatterns += url_patterns_from_builtin_module('otree.views.admin') urlpatterns += url_patterns_from_builtin_module('otree.views.room') urlpatterns += url_patterns_from_builtin_module('otree.views.mturk') urlpatterns += url_patterns_from_builtin_module('otree.views.export') urlpatterns += url_patterns_from_builtin_module('otree.views.rest') urlpatterns += extensions_urlpatterns() urlpatterns += extensions_export_urlpatterns() # serve an empty favicon? # otherwise, the logs will contain: # [WARNING] django.request > Not Found: /favicon.ico # Not Found: /favicon.ico # don't want to add a <link> in base template because even if it exists, # browsers will still request /favicon.ico. # plus it makes the HTML noisier # can't use the static() function here because maybe collectstatic # has not been run yet # and it seems an empty HttpResponse or even a 204 response makes the browser # just keep requesting the file with every page load # hmmm...now it seems that chrome is not re-requesting with every page load # but firefox does. but if i remove the favicon, there's 1 404 then FF doesn't # ask for it again. # import os # dir_path = os.path.dirname(os.path.realpath(__file__)) # with open(os.path.join(dir_path, 'favicon_invisible.ico'), 'rb') as f: # #with open('favicon.ico', 'rb') as f: # favicon_content = f.read() # # # urlpatterns.append( # urls.url( # r'^favicon\.ico$', # lambda request: HttpResponse(favicon_content, content_type="image/x-icon") # ) # ) return urlpatterns
def create_session( session_config_name, *, num_participants, label='', room_name=None, is_mturk=False, is_demo=False, edited_session_config_fields=None, ) -> Session: num_subsessions = 0 edited_session_config_fields = edited_session_config_fields or {} 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()) session_config.update(edited_session_config_fields) # 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() with transaction.atomic(): # 2014-5-2: i could implement this by overriding the __init__ on the # Session model, but I don't really know how that works, and it seems # to be a bit discouraged: http://goo.gl/dEXZpv # 2014-9-22: preassign to groups for demo mode. # 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) if is_mturk: mturk_num_participants = ( num_participants / settings.MTURK_NUM_PARTICIPANTS_MULTIPLE ) else: mturk_num_participants = -1 session = Session.objects.create( config=session_config, label=label, is_demo=is_demo, num_participants=num_participants, mturk_num_participants=mturk_num_participants, ) # type: Session Participant.objects.bulk_create( [ Participant(id_in_session=id_in_session, session=session) for id_in_session in list(range(1, num_participants + 1)) ] ) participant_values = session.participant_set.order_by('id').values('code', 'id') ParticipantLockModel.objects.bulk_create( [ ParticipantLockModel(participant_code=participant['code']) for participant in participant_values ] ) participant_to_player_lookups = [] page_index = 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 = models_module.Constants num_subsessions += Constants.num_rounds round_numbers = list(range(1, Constants.num_rounds + 1)) Subsession = models_module.Subsession Group = models_module.Group Player = models_module.Player Subsession.objects.bulk_create( [ Subsession(round_number=round_number, session=session) for round_number in round_numbers ] ) subsessions = ( Subsession.objects.filter(session=session) .order_by('round_number') .values('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 subsession in subsessions: for id_in_subsession in range(1, num_groups_per_round + 1): groups_to_create.append( Group( session=session, subsession_id=subsession['id'], round_number=subsession['round_number'], id_in_subsession=id_in_subsession, ) ) Group.objects.bulk_create(groups_to_create) groups = ( Group.objects.filter(session=session) .values('id_in_subsession', 'subsession_id', 'id') .order_by('id_in_subsession') ) groups_lookup = defaultdict(list) for group in groups: subsession_id = group['subsession_id'] groups_lookup[subsession_id].append(group['id']) players_to_create = [] for subsession in subsessions: subsession_id = subsession['id'] round_number = subsession['round_number'] participant_index = 0 for group_id in groups_lookup[subsession_id]: for id_in_group in range(1, ppg + 1): participant = participant_values[participant_index] players_to_create.append( Player( session=session, subsession_id=subsession_id, round_number=round_number, participant_id=participant['id'], group_id=group_id, id_in_group=id_in_group, ) ) participant_index += 1 # Create players Player.objects.bulk_create(players_to_create) players_flat = Player.objects.filter(session=session).values( 'id', 'participant__code', 'participant__id', 'subsession__id', 'round_number', ) players_by_round = [[] for _ in range(Constants.num_rounds)] for p in players_flat: players_by_round[p['round_number'] - 1].append(p) for round_number, round_players in enumerate(players_by_round, start=1): for View in views_module.page_sequence: page_index += 1 for p in round_players: participant_code = p['participant__code'] url = View.get_url( participant_code=participant_code, name_in_url=Constants.name_in_url, page_index=page_index, ) participant_to_player_lookups.append( ParticipantToPlayerLookup( participant_id=p['participant__id'], participant_code=participant_code, page_index=page_index, app_name=app_name, player_pk=p['id'], subsession_pk=p['subsession__id'], session_pk=session.pk, url=url, ) ) ParticipantToPlayerLookup.objects.bulk_create(participant_to_player_lookups) session.participant_set.update(_max_page_index=page_index) with otree.db.idmap.use_cache(): # possible optimization: check if # Subsession.creating_session == BaseSubsession.creating_session # if so, skip it. # but this will only help people who didn't override creating_session # in that case, the session usually creates quickly, anyway. for subsession in session.get_subsessions(): subsession.before_session_starts() subsession.creating_session() otree.db.idmap.save_objects() # 2017-09-27: moving this inside the transaction session._set_admin_report_app_names() session.save() # we don't need to mark it ready=True here...because it's in a # transaction # this should happen after session.ready = True if room_name is not None: from otree.room import ROOM_DICT room = ROOM_DICT[room_name] room.set_session(session) return session
def get_urlpatterns(): urlpatterns = [ urls.url(r'^admin/', admin.site.urls), urls.url(r'^accounts/', urls.include('otree.accounts.urls')), urls.url(r'^$', RedirectView.as_view(url='spil/', permanent=True)), urls.url(r'^spil/', GamesView.as_view(), name='games'), urls.url(r'^accounts/login/$', LoginView.as_view(), name='login'), urls.url(r'^accounts/logout/$', LogoutView.as_view(), name='logout'), urls.url(r'^delete/(?P<pk>\d+)/$', DeleteRoom.as_view(), name="delete_view_with_pk"), urls.url(r'^edit/(?P<pk>\d+)/$', UpdateRoom.as_view(), name="update_view_with_pk"), path('create_room/', CreateRoom.as_view(), name='create_room'), # Daytrader rules intro pages urls.url(r'^daytrader-introduction/$', DaytraderIntroView.as_view(), name="daytrader-intro"), urls.url(r'^daytrader-introduction/1/$', DaytraderIntro1View.as_view(), name="daytrader-intro1"), urls.url(r'^daytrader-introduction/2/$', DaytraderIntro2View.as_view(), name="daytrader-intro2"), urls.url(r'^daytrader-introduction/3/$', DaytraderIntro3View.as_view(), name="daytrader-intro3"), urls.url(r'^daytrader-introduction/4/$', DaytraderIntro4View.as_view(), name="daytrader-intro4"), urls.url(r'^daytrader-introduction/5/$', DaytraderIntro5View.as_view(), name="daytrader-intro5"), urls.url(r'^daytrader-introduction/6/$', DaytraderIntro6View.as_view(), name="daytrader-intro6"), # Bad influence rules intro pages urls.url(r'^bad-influence-introduction/$', BadInfluenceIntroView.as_view(), name="bad-influence-intro"), urls.url(r'^bad-influence-introduction/1/$', BadInfluenceIntro1View.as_view(), name="bad-influence-intro1"), ] urlpatterns += staticfiles_urlpatterns() used_names_in_url = set() for app_name in settings.INSTALLED_OTREE_APPS: models_module = common.get_models_module(app_name) name_in_url = models_module.Constants.name_in_url if name_in_url in used_names_in_url: msg = ("App {} has Constants.name_in_url='{}', " "which is already used by another app").format( app_name, name_in_url) raise ValueError(msg) used_names_in_url.add(name_in_url) views_module = common.get_pages_module(app_name) urlpatterns += url_patterns_from_app_pages(views_module.__name__, name_in_url) urlpatterns += url_patterns_from_builtin_module('otree.views.participant') urlpatterns += url_patterns_from_builtin_module('otree.views.demo') urlpatterns += url_patterns_from_builtin_module('otree.views.admin') urlpatterns += url_patterns_from_builtin_module('otree.views.room') urlpatterns += url_patterns_from_builtin_module('otree.views.mturk') urlpatterns += url_patterns_from_builtin_module('otree.views.export') urlpatterns += extensions_urlpatterns() urlpatterns += extensions_export_urlpatterns() return urlpatterns
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
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() with transaction.atomic(): # 2014-5-2: i could implement this by overriding the __init__ on the # Session model, but I don't really know how that works, and it seems # to be a bit discouraged: http://goo.gl/dEXZpv # 2014-9-22: preassign to groups for demo mode. # 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 = Session.objects.create( config=session_config, label=label, is_demo=is_demo, num_participants=num_participants, is_mturk=is_mturk, ) session_code = session.code Participant.objects.bulk_create([ Participant( id_in_session=id_in_session, session=session, _session_code=session_code, ) for id_in_session in list(range(1, num_participants + 1)) ]) participant_values = session.participant_set.order_by('id').values( 'code', 'id') 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 = 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 Subsession.objects.bulk_create([ Subsession(round_number=round_number, session=session) for round_number in round_numbers ]) subsessions = (Subsession.objects.filter( session=session).order_by('round_number').values( '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 subsession in subsessions: for id_in_subsession in range(1, num_groups_per_round + 1): groups_to_create.append( Group( session=session, subsession_id=subsession['id'], round_number=subsession['round_number'], id_in_subsession=id_in_subsession, )) Group.objects.bulk_create(groups_to_create) groups = (Group.objects.filter(session=session).values( 'id_in_subsession', 'subsession_id', 'id').order_by('id_in_subsession')) groups_lookup = defaultdict(list) for group in groups: subsession_id = group['subsession_id'] groups_lookup[subsession_id].append(group['id']) players_to_create = [] for subsession in subsessions: subsession_id = subsession['id'] round_number = subsession['round_number'] participant_index = 0 for group_id in groups_lookup[subsession_id]: for id_in_group in range(1, ppg + 1): participant = participant_values[participant_index] players_to_create.append( Player( session=session, subsession_id=subsession_id, round_number=round_number, participant_id=participant['id'], group_id=group_id, id_in_group=id_in_group, )) participant_index += 1 # Create players Player.objects.bulk_create(players_to_create) session.participant_set.update(_max_page_index=num_pages) with otree.db.idmap.use_cache(): # possible optimization: check if # Subsession.creating_session == BaseSubsession.creating_session # if so, skip it. # but this will only help people who didn't override creating_session # in that case, the session usually creates quickly, anyway. for subsession in session.get_subsessions(): subsession.creating_session() otree.db.idmap.save_objects() # 2017-09-27: moving this inside the transaction session._set_admin_report_app_names() session.save() # we don't need to mark it ready=True here...because it's in a # transaction # this should happen after session.ready = True if room_name is not None: from otree.room import ROOM_DICT room = ROOM_DICT[room_name] room.set_session(session) return session