def get_context_data(self, **kwargs): form = kwargs['form'] context = super().get_context_data(**kwargs) apps_with_admin_report = self.session._admin_report_apps() app_name = form.cleaned_data.get('app_name') if not app_name or form['app_name'].errors: app_name = apps_with_admin_report[0] round_number = form.cleaned_data.get('round_number') if not round_number or form['round_number'].errors: round_number = 1 models_module = get_models_module(app_name) subsession = models_module.Subsession.objects.get( session=self.session, round_number=round_number, ) context['subsession'] = subsession context['session'] = self.session context['Constants'] = models_module.Constants context.update(subsession.vars_for_admin_report() or {}) context['user_template'] = '{}/AdminReport.html'.format( subsession._meta.app_config.label) return context
def info_about_session_config(session_config): app_sequence = [] seo = set() for app_name in session_config['app_sequence']: models_module = get_models_module(app_name) num_rounds = models_module.Constants.num_rounds formatted_app_name = app_name_format(app_name) if num_rounds > 1: formatted_app_name = '{} ({} rounds)'.format( formatted_app_name, num_rounds) subsssn = { 'doc': getattr(models_module, 'doc', ''), 'source_code': getattr(models_module, 'source_code', ''), 'bibliography': getattr(models_module, 'bibliography', []), 'links': sort_links(getattr(models_module, 'links', {})), 'keywords': keywords_links(getattr(models_module, 'keywords', [])), 'name': formatted_app_name, } seo.update(map(lambda (a, b): a, subsssn["keywords"])) app_sequence.append(subsssn) return { 'doc': session_config['doc'], 'app_sequence': app_sequence, 'page_seo': seo }
def __init__( self, case_number: int, participant_bot: ParticipantBot, lookup: ParticipantToPlayerLookup): app_name = lookup.app_name models_module = get_models_module(app_name) self.PlayerClass = models_module.Player self.GroupClass = models_module.Group self.SubsessionClass = models_module.Subsession self._player_pk = lookup.player_pk self._subsession_pk = lookup.subsession_pk self._session_pk = lookup.session_pk self._participant_pk = lookup.participant_id self.participant_bot = participant_bot if case_number == None: # default to case 0 case_number = 0 cases = self.cases if len(cases) >= 1: self.case = cases[case_number % len(cases)] else: self.case = None
def get_context_data(self, **kwargs): cleaned_data = kwargs['form'].cleaned_data models_module = get_models_module(cleaned_data['app_name']) subsession = models_module.Subsession.objects.get( session=self.session, round_number=cleaned_data['round_number'], ) context = { 'subsession': subsession, 'Constants': models_module.Constants, 'session': self.session, 'user_template': '{}/AdminReport.html'.format(subsession._meta.app_config.label) } vars_for_admin_report = subsession.vars_for_admin_report() or {} self.debug_tables = [ DebugTable(title='vars_for_admin_report', rows=vars_for_admin_report.items()) ] # determine whether to display debug tables self.is_debug = settings.DEBUG context.update(vars_for_admin_report) # this should take priority, in the event of a clash between # a user-defined var and a built-in one context.update(super().get_context_data(**kwargs)) return context
def info_about_session_config(session_config): app_sequence = [] seo = set() for app_name in session_config['app_sequence']: models_module = get_models_module(app_name) num_rounds = models_module.Constants.num_rounds formatted_app_name = app_name_format(app_name) if num_rounds > 1: formatted_app_name = '{} ({} rounds)'.format( formatted_app_name, num_rounds ) subsssn = { 'doc': getattr(models_module, 'doc', ''), 'source_code': getattr(models_module, 'source_code', ''), 'bibliography': getattr(models_module, 'bibliography', []), 'links': sort_links(getattr(models_module, 'links', {})), 'keywords': keywords_links(getattr(models_module, 'keywords', [])), 'name': formatted_app_name, } seo.update(map(lambda (a, b): a, subsssn["keywords"])) app_sequence.append(subsssn) return { 'doc': session_config['doc'], 'app_sequence': app_sequence, 'page_seo': seo }
def ws_receive(message, params): # ASGI WebSocket packet-received and send-packet message types # both have a "text" key for their textual data. print("ws_receive called.") data = json.loads(message['text']) message_type = data['type'] page, group_id, player_id, session_code = params.split(',') player_id = int(player_id) group_id = int(group_id) models_module = get_models_module('channelsmin') print("ws_receive called, page=" + str(page) + ", message_type=" + str(message_type)) if page == 'finished': # check the message_type - if we're doing more complicated things later different messages can mean different things # ... this helps me organize if message_type == 'done': # make sure to do all these operations at once so that there's less chance of threading issues with transaction.atomic(): # set the group as being finished, so we can automatically forward anybody who joins the page late group_object = models_module.Group.objects.get(id=group_id) group_object.firstpage_done = True group_object.save() reply = { 'type': 'done', } # send the click to the other players in the group Group(get_group_name(group_id, session_code)).send( {'text': json.dumps(reply)})
def get_context_data(self, **kwargs): cleaned_data = kwargs['form'].cleaned_data models_module = get_models_module(cleaned_data['app_name']) subsession = models_module.Subsession.objects.get( session=self.session, round_number=cleaned_data['round_number'], ) vars_for_admin_report = subsession.vars_for_admin_report() or {} self.debug_tables = [ DebugTable(title='vars_for_admin_report', rows=vars_for_admin_report.items()) ] # determine whether to display debug tables self.is_debug = settings.DEBUG app_label = subsession._meta.app_config.label user_template = select_template([ f'{app_label}/admin_report.html', f'{app_label}/AdminReport.html', ]) context = super().get_context_data(subsession=subsession, Constants=models_module.Constants, user_template=user_template, **kwargs) # it's passed by parent class assert 'session' in context # this should take priority, in the event of a clash between # a user-defined var and a built-in one context.update(vars_for_admin_report) return context
def __init__(self, case_number: int, participant_bot: ParticipantBot, lookup: ParticipantToPlayerLookup): app_name = lookup.app_name models_module = get_models_module(app_name) self.PlayerClass = models_module.Player self.GroupClass = models_module.Group self.SubsessionClass = models_module.Subsession self._player_pk = lookup.player_pk self._subsession_pk = lookup.subsession_pk self._session_pk = lookup.session_pk self._participant_pk = lookup.participant_id self.participant_bot = participant_bot if case_number == None: # default to case 0 case_number = 0 cases = self.cases if len(cases) >= 1: self.case = cases[case_number % len(cases)] else: self.case = None
def augment_urlpatterns(urlpatterns): urlpatterns += urls.patterns( '', urls.url(r'^$', RedirectView.as_view(url='/demo', permanent=True)), urls.url( r'^accounts/login/$', 'django.contrib.auth.views.login', {'template_name': 'otree/login.html'}, name='login_url', ), urls.url( r'^accounts/logout/$', 'django.contrib.auth.views.logout', {'next_page': 'DemoIndex'}, name='logout', ), ) rest_api_urlpatterns = ( urls.url(r'^ping/$', Ping.as_view(), name="ping"), urls.url( r'^sessions/(?P<session_code>[a-z0-9]+)/participants/$', SessionParticipantsList.as_view(), name="session_participants_list") ) urlpatterns += rest_api_urlpatterns urlpatterns += staticfiles_urlpatterns() used_names_in_url = set() for app_name in settings.INSTALLED_OTREE_APPS: models_module = 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 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_name = '{}.views'.format(app_name) urlpatterns += url_patterns_from_game_module( views_module_name, name_in_url) urlpatterns += url_patterns_from_module('otree.views.participant') urlpatterns += url_patterns_from_module('otree.views.demo') urlpatterns += url_patterns_from_module('otree.views.admin') urlpatterns += url_patterns_from_module('otree.views.room') urlpatterns += url_patterns_from_module('otree.views.mturk') urlpatterns += url_patterns_from_module('otree.views.export') urlpatterns += extensions_urlpatterns() urlpatterns += extensions_export_urlpatterns() return urlpatterns
def get_urlpatterns(): from django.contrib.auth.views import login, logout urlpatterns = [ urls.url(r'^$', RedirectView.as_view(url='/demo', permanent=True)), urls.url( r'^accounts/login/$', login, {'template_name': 'otree/login.html'}, name='login_url', ), urls.url( r'^accounts/logout/$', logout, {'next_page': 'DemoIndex'}, name='logout', ), ] rest_api_urlpatterns = [ urls.url(r'^ping/$', Ping.as_view(), name="ping"), urls.url(r'^sessions/(?P<session_code>[a-z0-9]+)/participants/$', SessionParticipantsList.as_view(), name="session_participants_list") ] urlpatterns += rest_api_urlpatterns urlpatterns += staticfiles_urlpatterns() used_names_in_url = set() for app_name in settings.INSTALLED_OTREE_APPS: models_module = common_internal.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 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_internal.get_views_module(app_name) urlpatterns += url_patterns_from_game_module(views_module.__name__, name_in_url) urlpatterns += url_patterns_from_module('otree.views.participant') urlpatterns += url_patterns_from_module('otree.views.demo') urlpatterns += url_patterns_from_module('otree.views.admin') urlpatterns += url_patterns_from_module('otree.views.room') urlpatterns += url_patterns_from_module('otree.views.mturk') urlpatterns += url_patterns_from_module('otree.views.export') urlpatterns += extensions_urlpatterns() urlpatterns += extensions_export_urlpatterns() return urlpatterns
def augment_urlpatterns(urlpatterns): urlpatterns += urls.patterns( '', urls.url(r'^$', RedirectView.as_view(url='/demo', permanent=True)), urls.url( r'^accounts/login/$', 'django.contrib.auth.views.login', {'template_name': 'otree/login.html'}, name='login_url', ), urls.url( r'^accounts/logout/$', 'django.contrib.auth.views.logout', {'next_page': 'demo_index'}, name='logout', ), ) rest_api_urlpatterns = ( urls.url(r'^ping/$', Ping.as_view(), name="ping"), urls.url( r'^sessions/(?P<session_code>[a-z]+)/participants/$', SessionParticipantsList.as_view(), name="session_participants_list") ) urlpatterns += rest_api_urlpatterns urlpatterns += staticfiles_urlpatterns() used_names_in_url = set() for app_name in settings.INSTALLED_OTREE_APPS: models_module = 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 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_name = '{}.views'.format(app_name) utilities_module_name = '{}._builtin'.format(app_name) urlpatterns += url_patterns_from_module(views_module_name) urlpatterns += url_patterns_from_module(utilities_module_name) urlpatterns += url_patterns_from_module('otree.views.concrete') urlpatterns += url_patterns_from_module('otree.views.demo') urlpatterns += url_patterns_from_module('otree.views.admin') urlpatterns += url_patterns_from_module('otree.views.mturk') urlpatterns += url_patterns_from_module('otree.views.export') return urlpatterns
def get_payoff_cache(): payoff_cache = collections.defaultdict(Decimal) for app_name in settings.INSTALLED_OTREE_APPS: models_module = get_models_module(app_name) Player = models_module.Player for d in Player.objects.values('participant_id', 'session_id').annotate(Sum('payoff')): payoff_cache[d['participant_id']] += (d['payoff__sum'] or 0) return payoff_cache
def get_payoff_cache(): payoff_cache = collections.defaultdict(Decimal) for app_name in settings.INSTALLED_OTREE_APPS: models_module = get_models_module(app_name) Player = models_module.Player for d in Player.objects.values( 'participant_id', 'session_id').annotate(Sum('payoff')): payoff_cache[d['participant_id']] += (d['payoff__sum'] or 0) return payoff_cache
def post_connect(self, app_name, player_id, page_index, session_pk): models_module = get_models_module(app_name) group_id_in_subsession = models_module.Group.objects.filter( player__id=player_id).values_list('id_in_subsession', flat=True)[0] ready = CompletedGroupWaitPage.objects.filter( page_index=page_index, id_in_subsession=int(group_id_in_subsession), session_id=session_pk, ).exists() if ready: self.send({'status': 'ready'})
def get_rows_for_wide_csv(): from collections import OrderedDict from django.conf import settings from django.db.models import Max, Count sessions = Session.objects.order_by('id').annotate( num_participants=Count('participant')).values() session_cache = {row['id']: row for row in sessions} participants = Participant.objects.order_by('id').values() session_fields = get_field_names_for_csv(Session) participant_fields = get_field_names_for_csv(Participant) header_row = ['participant.{}'.format(fname) for fname in participant_fields] header_row += ['session.{}'.format(fname) for fname in session_fields] rows = [header_row] for participant in participants: row = [sanitize_for_csv(participant[fname]) for fname in participant_fields] session = session_cache[participant['session_id']] row += [sanitize_for_csv(session[fname]) for fname in session_fields] rows.append(row) # heuristic to get the most relevant order of apps import json app_sequences = collections.Counter() for session in sessions: config = json.loads(session['config']) app_sequence = config['app_sequence'] app_sequences[tuple(app_sequence)] += session['num_participants'] most_common_app_sequence = app_sequences.most_common(1)[0][0] apps_not_in_popular_sequence = [ app for app in settings.INSTALLED_OTREE_APPS if app not in most_common_app_sequence] order_of_apps = list(most_common_app_sequence) + apps_not_in_popular_sequence rounds_per_app = OrderedDict() for app_name in order_of_apps: models_module = get_models_module(app_name) agg_dict = models_module.Subsession.objects.all().aggregate(Max('round_number')) highest_round_number = agg_dict['round_number__max'] 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 rows
def post_connect(self, app_name, player_id, page_index, session_pk): models_module = get_models_module(app_name) group_id_in_subsession = models_module.Group.objects.filter( player__id=player_id).values_list( 'id_in_subsession', flat=True)[0] ready = CompletedGroupWaitPage.objects.filter( page_index=page_index, id_in_subsession=int(group_id_in_subsession), session_id=session_pk, ).exists() if ready: self.send({'status': 'ready'})
def app_sequence_display(self): app_sequence = [] for app_name in self['app_sequence']: models_module = get_models_module(app_name) num_rounds = models_module.Constants.num_rounds if num_rounds > 1: formatted_app_name = '{} ({} rounds)'.format( app_name, num_rounds) else: formatted_app_name = app_name subsssn = { 'doc': getattr(models_module, 'doc', ''), 'name': formatted_app_name} app_sequence.append(subsssn) return app_sequence
def get_info(self): app_sequence = [] for app_name in self['app_sequence']: models_module = get_models_module(app_name) num_rounds = models_module.Constants.num_rounds formatted_app_name = otree.common_internal.app_name_format( app_name) if num_rounds > 1: formatted_app_name = '{} ({} rounds)'.format( formatted_app_name, num_rounds) subsssn = { 'doc': getattr(models_module, 'doc', ''), 'bibliography': getattr(models_module, 'bibliography', []), 'name': formatted_app_name} app_sequence.append(subsssn) return { 'doc': self['doc'], 'app_sequence': app_sequence, 'name': self['name'], 'display_name': self['display_name'], 'lcm': self.get_lcm()}
def info_about_session_config(session_config): app_sequence = [] for app_name in session_config['app_sequence']: models_module = get_models_module(app_name) num_rounds = models_module.Constants.num_rounds formatted_app_name = app_name_format(app_name) if num_rounds > 1: formatted_app_name = '{} ({} rounds)'.format( formatted_app_name, num_rounds) subsssn = { 'doc': getattr(models_module, 'doc', ''), 'bibliography': getattr(models_module, 'bibliography', []), 'name': formatted_app_name, } app_sequence.append(subsssn) return { 'doc': session_config['doc'], 'app_sequence': app_sequence, 'name': session_config['name'], 'display_name': session_config['display_name'], 'lcm': get_lcm(session_config) }
def connect_group_by_arrival_time(message, params): session_pk, page_index, app_name, player_id = params.split(',') session_pk = int(session_pk) page_index = int(page_index) player_id = int(player_id) group_name = channels_group_by_arrival_time_group_name(session_pk, page_index) group = Group(group_name) group.add(message.reply_channel) models_module = get_models_module(app_name) player = models_module.Player.objects.get(id=player_id) group_id_in_subsession = player.group.id_in_subsession ready = CompletedGroupWaitPage.objects.filter( page_index=page_index, id_in_subsession=int(group_id_in_subsession), session_id=session_pk, fully_completed=True).exists() if ready: message.reply_channel.send( {'text': json.dumps( {'status': 'ready'})})
def info_about_session_config(session_config): app_sequence = [] for app_name in session_config['app_sequence']: models_module = get_models_module(app_name) num_rounds = models_module.Constants.num_rounds formatted_app_name = app_name_format(app_name) if num_rounds > 1: formatted_app_name = '{} ({} rounds)'.format( formatted_app_name, num_rounds ) subsssn = { 'doc': getattr(models_module, 'doc', ''), 'bibliography': getattr(models_module, 'bibliography', []), 'name': formatted_app_name, } app_sequence.append(subsssn) return { 'doc': session_config['doc'], 'app_sequence': app_sequence, 'name': session_config['name'], 'display_name': session_config['display_name'], 'lcm': get_lcm(session_config) }
def connect_wait_page(message, params): app_label, page_index, model_name, model_pk = params.split(',') page_index = int(page_index) model_pk = int(model_pk) group_name = channels_wait_page_group_name( app_label, page_index, model_name, model_pk ) group = Group(group_name) group.add(message.reply_channel) # in case message was sent before this web socket connects # fixme: app name or app label? models_module = common_internal.get_models_module(app_label) GroupOrSubsession = { 'subsession': getattr(models_module, 'Subsession'), 'group': getattr(models_module, 'Group') }[model_name] group_or_subsession = GroupOrSubsession.objects.get(pk=model_pk) participants_for_this_page = set( p.participant for p in group_or_subsession.player_set.all() ) unvisited = set( p for p in participants_for_this_page if p._index_in_pages < page_index ) if not unvisited: message.reply_channel.send( {'text': json.dumps( {'status': 'ready'})})
def send_message(message, session_code, index_in_pages, participant_code, player_pk, group_pk): cursession = Session.objects.get(code=session_code) curparticipant = Participant.objects.get(code=participant_code) url = curparticipant._url_i_should_be_on() Page = get_view_from_url(url) app_name = Page.__module__.split('.')[0] models_module = get_models_module(app_name) curplayer = models_module.Player.objects.get(pk=player_pk) subsession = curplayer.subsession those_with_us = [] if hasattr(Page, 'group_by_arrival_time'): if getattr(Page, 'group_by_arrival_time'): those_with_us = models_module.Player.objects.filter( subsession=subsession, participant___index_in_pages=index_in_pages, _group_by_arrival_time_arrived=True, _group_by_arrival_time_grouped=False, ) else: those_with_us = models_module.Player.objects.filter( subsession=subsession, participant___index_in_pages=index_in_pages, group=curplayer.group, ) how_many_arrived = len(those_with_us) left_to_wait = Constants.players_per_group - how_many_arrived textforgroup = json.dumps({ "how_many_arrived": how_many_arrived, "left_to_wait": left_to_wait, }) Group(get_group_name(session_code, index_in_pages, group_pk)).send({ "text": textforgroup, })
def ws_add(message, params): print("ws_add called. params = " + params) page, group_id, player_id, session_code = params.split(',') player_id = int(player_id) group_id = int(group_id) # add them to the channels group group = Group(get_group_name(group_id, session_code)) group.add(message.reply_channel) # need to check to see if someone in the group has already chosen to move on - if so, send the done message upon joining with transaction.atomic(): models_module = get_models_module('channelsmin') group_object = models_module.Group.objects.get(id=group_id) if page == 'finished' and group_object.firstpage_done: print( "inside ws_add, checking to see if group's first page is done. firstpage_done=" + str(group_object.firstpage_done)) reply = { 'type': 'done', } message.reply_channel.send({'text': json.dumps(reply)})
def get_context_data(self, **kwargs): cleaned_data = kwargs['form'].cleaned_data models_module = get_models_module(cleaned_data['app_name']) subsession = models_module.Subsession.objects.get( session=self.session, round_number=cleaned_data['round_number'], ) context = { 'subsession': subsession, 'Constants': models_module.Constants, 'session': self.session, 'user_template': '{}/AdminReport.html'.format( subsession._meta.app_config.label) } vars_for_admin_report = subsession.vars_for_admin_report() or {} self.debug_tables = [ DebugTable( title='vars_for_admin_report', rows=vars_for_admin_report.items() ) ] # determine whether to display debug tables self.is_debug = settings.DEBUG context.update(vars_for_admin_report) # this should take priority, in the event of a clash between # a user-defined var and a built-in one context.update(super().get_context_data(**kwargs)) return context
def create_session( session_config_name, label='', num_participants=None, _pre_create_id=None, room_name=None, for_mturk=False, use_cli_bots=False, is_demo=False, force_browser_bots=False, honor_browser_bots_config=False, bot_case_number=None): session = None use_browser_bots = False num_subsessions = 0 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. otree.db.idmap.activate_cache() try: session_config = SESSION_CONFIGS_DICT[session_config_name] except KeyError: msg = 'Session config "{}" not found in settings.SESSION_CONFIGS.' raise ValueError(msg.format(session_config_name)) if force_browser_bots: use_browser_bots = True elif (session_config.get('use_browser_bots') and honor_browser_bots_config): use_browser_bots = True else: use_browser_bots = False if use_browser_bots and bot_case_number is None: # choose one randomly num_bot_cases = session_config.get_num_bot_cases() # choose bot case number randomly...maybe reconsider this? # we can only run one. bot_case_number = random.choice(range(num_bot_cases)) session = Session.objects.create( config=session_config, label=label, _pre_create_id=_pre_create_id, use_browser_bots=use_browser_bots, is_demo=is_demo, _bot_case_number=bot_case_number) def bulk_create(model, descriptions): model.objects.bulk_create([ model(session=session, **description) for description in descriptions]) return model.objects.filter(session=session).order_by('pk') # check that it divides evenly session_lcm = session_config.get_lcm() if num_participants % session_lcm: msg = ( 'Session Config {}: Number of participants ({}) does not ' 'divide evenly into group size ({})' ).format(session_config['name'], num_participants, session_lcm) raise ValueError(msg) if for_mturk: session.mturk_num_participants = ( num_participants / settings.MTURK_NUM_PARTICIPANTS_MULTIPLE) start_order = list(range(num_participants)) if session_config.get('random_start_order'): random.shuffle(start_order) participants = bulk_create( Participant, [{ 'id_in_session': id_in_session, 'start_order': j, # check if id_in_session is in the bots ID list '_is_bot': use_cli_bots or use_browser_bots, } for id_in_session, j in enumerate(start_order, start=1)]) ParticipantLockModel.objects.bulk_create([ ParticipantLockModel(participant_code=participant.code) for participant in participants]) try: for app_name in session_config['app_sequence']: models_module = get_models_module(app_name) app_constants = get_app_constants(app_name) num_subsessions += app_constants.num_rounds round_numbers = list(range(1, app_constants.num_rounds + 1)) subs = bulk_create( models_module.Subsession, [{'round_number': round_number} for round_number in round_numbers]) # Create players models_module.Player.objects.bulk_create([ models_module.Player( session=session, subsession=subsession, round_number=round_number, participant=participant) for round_number, subsession in zip(round_numbers, subs) for participant in participants]) session._create_groups_and_initialize() # session.has_bots = any(p.is_bot ...) # handle case where DB has missing column or table # missing table: OperationalError: no such table: pg_subsession # missing column: OperationalError: table pg_player has no column # named contribution2 except OperationalError as exception: exception_str = str(exception) if 'table' in exception_str: ExceptionClass = type(exception) six.reraise( ExceptionClass, ExceptionClass('{} - Try resetting the database.'.format( exception_str)), sys.exc_info()[2]) raise session.build_participant_to_player_lookups() # automatically save all objects since the cache was activated: # Player, Group, Subsession, Participant, Session otree.db.idmap.save_objects() otree.db.idmap.deactivate_cache() if use_browser_bots: # what about clear_browser_bots? if session is created through # UI, when do we run that? it should be run when the session # is deleted try: num_players_total = num_participants * num_subsessions otree.bots.browser.initialize_bots( session.code, num_players_total) except: session.delete() raise session.ready = True session.save() # 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.session = session return session
def create_session( session_config_name, label='', num_participants=None, _pre_create_id=None, room_name=None, for_mturk=False, use_cli_bots=False, is_demo=False, force_browser_bots=False, honor_browser_bots_config=False, bot_case_number=None, edited_session_config_fields=None): session = None use_browser_bots = False participants = [] edited_session_config_fields = edited_session_config_fields or {} 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. otree.db.idmap.activate_cache() 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: # seems i need to copy and convert back to a session config # otherwise .copy() converts it to a simple dict session_config.update(edited_session_config_fields) session_config = SessionConfig(session_config.copy()) # 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() if force_browser_bots: use_browser_bots = True elif (session_config.get('use_browser_bots') and honor_browser_bots_config): use_browser_bots = True else: use_browser_bots = False if use_browser_bots and bot_case_number is None: # choose one randomly num_bot_cases = session_config.get_num_bot_cases() # choose bot case number randomly...maybe reconsider this? # we can only run one. bot_case_number = random.choice(range(num_bot_cases)) session = Session.objects.create( config=session_config, label=label, _pre_create_id=_pre_create_id, use_browser_bots=use_browser_bots, is_demo=is_demo, _bot_case_number=bot_case_number) def bulk_create(model, descriptions): model.objects.bulk_create([ model(session=session, **description) for description in descriptions]) return model.objects.filter(session=session).order_by('pk') # check that it divides evenly session_lcm = session_config.get_lcm() if num_participants % session_lcm: msg = ( 'Session Config {}: Number of participants ({}) does not ' 'divide evenly into group size ({})' ).format(session_config['name'], num_participants, session_lcm) raise ValueError(msg) if for_mturk: session.mturk_num_participants = ( num_participants / settings.MTURK_NUM_PARTICIPANTS_MULTIPLE) start_order = list(range(num_participants)) if session_config.get('random_start_order'): random.shuffle(start_order) participants = bulk_create( Participant, [{ 'id_in_session': id_in_session, 'start_order': j, # check if id_in_session is in the bots ID list '_is_bot': use_cli_bots or use_browser_bots, } for id_in_session, j in enumerate(start_order, start=1)]) ParticipantLockModel.objects.bulk_create([ ParticipantLockModel(participant_code=participant.code) for participant in participants]) for app_name in session_config['app_sequence']: models_module = get_models_module(app_name) app_constants = get_app_constants(app_name) round_numbers = list(range(1, app_constants.num_rounds + 1)) subs = bulk_create( models_module.Subsession, [{'round_number': round_number} for round_number in round_numbers]) # Create players models_module.Player.objects.bulk_create([ models_module.Player( session=session, subsession=subsession, round_number=round_number, participant=participant) for round_number, subsession in zip(round_numbers, subs) for participant in participants]) session._create_groups_and_initialize() session.build_participant_to_player_lookups() # automatically save all objects since the cache was activated: # Player, Group, Subsession, Participant, Session otree.db.idmap.save_objects() otree.db.idmap.deactivate_cache() if use_browser_bots: # what about clear_browser_bots? if session is created through # UI, when do we run that? it should be run when the session # is deleted try: for participant in participants: otree.bots.browser.initialize_bot( participant.code) except: session.delete() raise session.ready = True session.save() # 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.session = session return session
def _Constants(self): return get_models_module(self._meta.app_config.name).Constants
def export_docs(fp, app_name): """Write the dcos of the given app name as csv into the file-like object """ # generate doct_dict models_module = get_models_module(app_name) model_names = ["Participant", "Player", "Group", "Subsession", "Session"] line_break = '\r\n' def choices_readable(choices): lines = [] for value, name in choices: # unicode() call is for lazy translation strings lines.append(u'{}: {}'.format(value, six.text_type(name))) return lines def generate_doc_dict(): doc_dict = OrderedDict() data_types_readable = { 'PositiveIntegerField': 'positive integer', 'IntegerField': 'integer', 'BooleanField': 'boolean', 'CharField': 'text', 'TextField': 'text', 'FloatField': 'decimal', 'DecimalField': 'decimal', 'CurrencyField': 'currency'} for model_name in model_names: if model_name == 'Participant': Model = Participant elif model_name == 'Session': Model = Session else: Model = getattr(models_module, model_name) field_names = set(field.name for field in Model._meta.fields) members = get_field_names_for_csv(Model) doc_dict[model_name] = OrderedDict() for member_name in members: member = getattr(Model, member_name, None) doc_dict[model_name][member_name] = OrderedDict() if member_name == 'id': doc_dict[model_name][member_name]['type'] = [ 'positive integer'] doc_dict[model_name][member_name]['doc'] = ['Unique ID'] elif member_name in field_names: member = Model._meta.get_field_by_name(member_name)[0] internal_type = member.get_internal_type() data_type = data_types_readable.get( internal_type, internal_type) doc_dict[model_name][member_name]['type'] = [data_type] # flag error if the model doesn't have a doc attribute, # which it should unless the field is a 3rd party field doc = getattr(member, 'doc', '[error]') or '' doc_dict[model_name][member_name]['doc'] = [ line.strip() for line in doc.splitlines() if line.strip()] choices = getattr(member, 'choices', None) if choices: doc_dict[model_name][member_name]['choices'] = ( choices_readable(choices)) elif isinstance(member, collections.Callable): doc_dict[model_name][member_name]['doc'] = [ inspect.getdoc(member)] return doc_dict def docs_as_string(doc_dict): first_line = '{}: Documentation'.format(app_name_format(app_name)) second_line = '*' * len(first_line) lines = [ first_line, second_line, '', 'Accessed: {}'.format(datetime.date.today().isoformat()), ''] app_doc = getattr(models_module, 'doc', '') if app_doc: lines += [app_doc, ''] for model_name in doc_dict: lines.append(model_name) for member in doc_dict[model_name]: lines.append('\t{}'.format(member)) for info_type in doc_dict[model_name][member]: lines.append('\t\t{}'.format(info_type)) for info_line in doc_dict[model_name][member][info_type]: lines.append(u'{}{}'.format('\t' * 3, info_line)) output = u'\n'.join(lines) return output.replace('\n', line_break).replace('\t', ' ') doc_dict = generate_doc_dict() doc = docs_as_string(doc_dict) fp.write(doc)
def get_dataframe_for_app(cls, app_name): """ Generate data rows for app `app_name`, also adding rows of custom data models. """ models_module = get_models_module(app_name) # get the standard models Player = models_module.Player Group = models_module.Group Subsession = models_module.Subsession # get custom model configuration, if there is any custom_models_conf = get_custom_models_conf(models_module, for_action='export_data') # identify links between standard and custom models links_to_custom_models = get_links_between_std_and_custom_models( custom_models_conf, for_action='export_data') # find out column names for standard models std_models_colnames = { m.__name__.lower(): get_field_names_for_csv(m) for m in (Session, Subsession, Group, Player, Participant) } std_models_colnames['player'].append('participant_id') # find out column names for custom models custom_models_colnames = cls.custom_columns_builder(custom_models_conf) # create lists of IDs that will be used for the export participant_ids = set( Player.objects.values_list('participant_id', flat=True)) session_ids = set( Subsession.objects.values_list('session_id', flat=True)) filter_in_sess = {'session_id__in': session_ids} std_models_querysets = ( (Session, Session.objects.filter(id__in=session_ids), (None, None)), (Subsession, Subsession.objects.filter(**filter_in_sess), ('session.id', 'subsession.session_id')), (Group, Group.objects.filter(**filter_in_sess), ('subsession.id', 'group.subsession_id')), (Player, Player.objects.filter(**filter_in_sess), ('group.id', 'player.group_id')), (Participant, Participant.objects.filter(id__in=participant_ids), ('player.participant_id', 'participant.id')), ) # create a dataframe for this app's complete data incl. custom models data df = get_dataframe_from_linked_models(std_models_querysets, links_to_custom_models, std_models_colnames, custom_models_colnames) # sanitize each value df = df.applymap(sanitize_pdvalue_for_csv) return df
def get_urlpatterns(): from django.contrib.auth.views import login, logout urlpatterns = [ urls.url(r'^$', RedirectView.as_view(url='/demo', permanent=True)), urls.url( r'^accounts/login/$', login, {'template_name': 'otree/login.html'}, name='login_url', ), urls.url( r'^accounts/logout/$', logout, {'next_page': 'DemoIndex'}, name='logout', ), ] urlpatterns += staticfiles_urlpatterns() used_names_in_url = set() for app_name in settings.INSTALLED_OTREE_APPS: models_module = common_internal.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_internal.get_pages_module(app_name) urlpatterns += url_patterns_from_game_module(views_module.__name__, name_in_url) urlpatterns += url_patterns_from_module('otree.views.participant') urlpatterns += url_patterns_from_module('otree.views.demo') urlpatterns += url_patterns_from_module('otree.views.admin') urlpatterns += url_patterns_from_module('otree.views.room') urlpatterns += url_patterns_from_module('otree.views.mturk') urlpatterns += url_patterns_from_module('otree.views.export') 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 get_rows_for_wide_csv(): sessions = Session.objects.order_by('id').annotate( num_participants=Count('participant')).values() session_cache = {row['id']: row for row in sessions} session_config_fields = set() for row in sessions: config = json_loads(row['config']) config = SessionConfig(config) for field_name in config.editable_fields(): session_config_fields.add(field_name) # store it for later, when we need app_sequence row['config'] = config session_config_fields = list(session_config_fields) participants = Participant.objects.order_by('id').values() if not participants: # 1 empty row return [[]] payoff_cache = get_payoff_cache() payoff_plus_participation_fee_cache = get_payoff_plus_participation_fee_cache( payoff_cache) session_fields = get_field_names_for_csv(Session) participant_fields = get_field_names_for_csv(Participant) participant_fields += ['payoff', 'payoff_plus_participation_fee'] header_row = [ 'participant.{}'.format(fname) for fname in participant_fields ] header_row += ['session.{}'.format(fname) for fname in session_fields] header_row += [ 'session.config.{}'.format(fname) for fname in session_config_fields ] rows = [header_row] for participant in participants: participant['payoff'] = payoff_cache[participant['id']] participant[ 'payoff_plus_participation_fee'] = payoff_plus_participation_fee_cache[ participant['id']] row = [ sanitize_for_csv(participant[fname]) for fname in participant_fields ] session = session_cache[participant['session_id']] row += [sanitize_for_csv(session[fname]) for fname in session_fields] config = session['config'] row += [ sanitize_for_csv(config.get(fname)) for fname in session_config_fields ] rows.append(row) # heuristic to get the most relevant order of apps app_sequences = collections.Counter() for session in sessions: # we loaded the config earlier app_sequence = session['config']['app_sequence'] app_sequences[tuple(app_sequence)] += session['num_participants'] most_common_app_sequence = app_sequences.most_common(1)[0][0] apps_not_in_popular_sequence = [ app for app in settings.INSTALLED_OTREE_APPS if app not in most_common_app_sequence ] order_of_apps = list( most_common_app_sequence) + apps_not_in_popular_sequence rounds_per_app = OrderedDict() for app_name in order_of_apps: models_module = get_models_module(app_name) agg_dict = models_module.Subsession.objects.all().aggregate( Max('round_number')) highest_round_number = agg_dict['round_number__max'] 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 rows
def create_session( session_config_name, *, label='', num_participants=None, pre_create_id=None, room_name=None, for_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. if for_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, _pre_create_id=pre_create_id, is_demo=is_demo, num_participants=num_participants, mturk_num_participants=mturk_num_participants ) # type: Session # check that it divides evenly session_lcm = session_config.get_lcm() 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) 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_internal.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_rows_for_wide_csv(): sessions = Session.objects.order_by('id') 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) participants = Participant.objects.order_by('id').values() if not participants: # 1 empty row return [[]] session_fields = get_field_names_for_csv(Session) participant_fields = get_field_names_for_csv(Participant) participant_fields.append('payoff_plus_participation_fee') header_row = [ 'participant.{}'.format(fname) for fname in participant_fields ] header_row += ['session.{}'.format(fname) for fname in session_fields] header_row += [ 'session.config.{}'.format(fname) for fname in session_config_fields ] rows = [header_row] for participant in participants: session = session_cache[participant['session_id']] participant[ 'payoff_plus_participation_fee'] = get_payoff_plus_participation_fee( session, participant) row = [ sanitize_for_csv(participant[fname]) for fname in participant_fields ] row += [ sanitize_for_csv(getattr(session, fname)) for fname in session_fields ] row += [ sanitize_for_csv(session.config.get(fname)) for fname in session_config_fields ] rows.append(row) # heuristic to get the most relevant order of apps app_sequences = collections.Counter() for session in sessions: # we loaded the config earlier app_sequence = session.config['app_sequence'] app_sequences[tuple(app_sequence)] += session.num_participants most_common_app_sequence = app_sequences.most_common(1)[0][0] # can't use settings.INSTALLED_OTREE_APPS, because maybe the app # was removed from SESSION_CONFIGS. app_names_with_data = set() for session in sessions: for app_name in session.config['app_sequence']: app_names_with_data.add(app_name) apps_not_in_popular_sequence = [ app for app in app_names_with_data if app not in most_common_app_sequence ] order_of_apps = list( most_common_app_sequence) + apps_not_in_popular_sequence rounds_per_app = OrderedDict() for app_name in order_of_apps: models_module = get_models_module(app_name) agg_dict = models_module.Subsession.objects.all().aggregate( Max('round_number')) highest_round_number = agg_dict['round_number__max'] 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 rows
def create_session(session_config_name, label='', num_participants=None, special_category=None, _pre_create_id=None, room=None, for_mturk=False): # 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. otree.db.idmap.activate_cache() try: session_config = SESSION_CONFIGS_DICT[session_config_name] except KeyError: msg = 'Session type "{}" not found in settings.py' raise ValueError(msg.format(session_config_name)) session = Session.objects.create( config=session_config, label=label, special_category=special_category, _pre_create_id=_pre_create_id,) def bulk_create(model, descriptions): model.objects.bulk_create([ model(session=session, **description) for description in descriptions]) return model.objects.filter(session=session).order_by('pk') if num_participants is None: c_special_catdemo = constants_internal.session_special_category_demo c_special_catbots = constants_internal.session_special_category_bots if special_category == c_special_catdemo: num_participants = session_config['num_demo_participants'] elif special_category == c_special_catbots: num_participants = session_config['num_bots'] # check that it divides evenly session_lcm = get_lcm(session_config) if num_participants % session_lcm: msg = ( 'Session Config {}: Number of participants ({}) does not divide ' 'evenly into group size ({})') raise ValueError( msg.format(session_config['name'], num_participants, session_lcm)) if for_mturk: session.mturk_num_participants = ( num_participants / settings.MTURK_NUM_PARTICIPANTS_MULT ) start_order = list(range(num_participants)) if session_config.get('random_start_order'): random.shuffle(start_order) participants = bulk_create( Participant, [{'id_in_session': i + 1, 'start_order': j} for i, j in enumerate(start_order)]) for participant in participants: ParticipantLockModel(participant_code=participant.code).save() for app_name in session_config['app_sequence']: models_module = get_models_module(app_name) app_constants = get_app_constants(app_name) round_numbers = list(range(1, app_constants.num_rounds + 1)) subs = bulk_create( models_module.Subsession, [{'round_number': round_number} for round_number in round_numbers]) # Create players models_module.Player.objects.bulk_create([ models_module.Player( session=session, subsession=subsession, round_number=round_number, participant=participant) for round_number, subsession in zip(round_numbers, subs) for participant in participants]) session._create_groups_and_initialize() session.build_participant_to_player_lookups() if room is not None: room.session = session session.ready = True session.save() otree.db.idmap.deactivate_cache() return session
def create_session(session_config_name, *, label='', num_participants=None, pre_create_id=None, room_name=None, for_mturk=False, is_demo=False, edited_session_config_fields=None) -> Session: session = None 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. otree.db.idmap.activate_cache() session = Session.objects.create( config=session_config, label=label, _pre_create_id=pre_create_id, is_demo=is_demo, num_participants=num_participants, ) # type: Session def bulk_create(model, descriptions): model.objects.bulk_create([ model(session=session, **description) for description in descriptions ]) return model.objects.filter(session=session).order_by('pk') # check that it divides evenly session_lcm = session_config.get_lcm() if num_participants % session_lcm: msg = ('Session Config {}: Number of participants ({}) does not ' 'divide evenly into group size ({})').format( session_config['name'], num_participants, session_lcm) raise ValueError(msg) if for_mturk: session.mturk_num_participants = ( num_participants / settings.MTURK_NUM_PARTICIPANTS_MULTIPLE) # TODO: remove start_order start_order = list(range(num_participants)) if session_config.get('random_start_order'): random.shuffle(start_order) participants = bulk_create(Participant, [{ 'id_in_session': id_in_session, 'start_order': j, } for id_in_session, j in enumerate(start_order, start=1)]) ParticipantLockModel.objects.bulk_create([ ParticipantLockModel(participant_code=participant.code) for participant in participants ]) for app_name in session_config['app_sequence']: models_module = get_models_module(app_name) app_constants = get_app_constants(app_name) num_subsessions += app_constants.num_rounds round_numbers = list(range(1, app_constants.num_rounds + 1)) subs = bulk_create(models_module.Subsession, [{ 'round_number': round_number } for round_number in round_numbers]) # Create players models_module.Player.objects.bulk_create([ models_module.Player(session=session, subsession=subsession, round_number=round_number, participant=participant) for round_number, subsession in zip(round_numbers, subs) for participant in participants ]) session._create_groups_and_initialize() session.build_participant_to_player_lookups() # automatically save all objects since the cache was activated: # Player, Group, Subsession, Participant, Session otree.db.idmap.save_objects() otree.db.idmap.deactivate_cache() # 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.session = session return session
def create_session(session_config_name, label='', num_participants=None, special_category=None, _pre_create_id=None): # 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. try: session_config = get_session_configs_dict()[session_config_name] except KeyError: msg = 'Session type "{}" not found in settings.py' raise ValueError(msg.format(session_config_name)) session = Session.objects.create( config=session_config, label=label, special_category=special_category, _pre_create_id=_pre_create_id,) def bulk_create(model, descriptions): model.objects.bulk_create([ model(session=session, **description) for description in descriptions]) return model.objects.filter(session=session).order_by('pk') if num_participants is None: c_special_catdemo = constants_internal.session_special_category_demo c_special_catbots = constants_internal.session_special_category_bots if special_category == c_special_catdemo: num_participants = session_config['num_demo_participants'] elif special_category == c_special_catbots: num_participants = session_config['num_bots'] # check that it divides evenly session_lcm = get_lcm(session_config) if num_participants % session_lcm: msg = ( 'Session Config {}: Number of participants ({}) does not divide ' 'evenly into group size ({})') raise ValueError( msg.format(session_config['name'], num_participants, session_lcm)) start_order = list(range(num_participants)) if session_config.get('random_start_order'): random.shuffle(start_order) participants = bulk_create( Participant, [{'id_in_session': i + 1, 'start_order': j} for i, j in enumerate(start_order)]) for participant in participants: ParticipantLockModel(participant_code=participant.code).save() for app_name in session_config['app_sequence']: models_module = get_models_module(app_name) app_constants = get_app_constants(app_name) round_numbers = list(range(1, app_constants.num_rounds + 1)) subs = bulk_create( models_module.Subsession, [{'round_number': round_number} for round_number in round_numbers]) # Create players models_module.Player.objects.bulk_create([ models_module.Player( session=session, subsession=subsession, round_number=round_number, participant=participant) for round_number, subsession in zip(round_numbers, subs) for participant in participants]) session._create_groups_and_initialize() session.build_participant_to_player_lookups() session.ready = True session.save() return session
def export_docs(app_name): """ Taken and adapted from the otree core: https://github.com/oTree-org/otree-core/blob/master/otree/export.py Adapting to export to csv and to add non-standard models """ # generate doct_dict models_module = get_models_module(app_name) model_names = [ "Participant", "Player", "Group", "Subsession", "Session", "Ask", "Bid", "Contract", "PriceDim" ] line_break = '\r\n' def choices_readable(choices): lines = [] for value, name in choices: # unicode() call is for lazy translation strings lines.append(u'{}: {}'.format(value, six.text_type(name))) return lines def generate_doc_dict(): doc_dict = OrderedDict() data_types_readable = { 'PositiveIntegerField': 'positive integer', 'IntegerField': 'integer', 'BooleanField': 'boolean', 'CharField': 'text', 'TextField': 'text', 'FloatField': 'decimal', 'DecimalField': 'decimal', 'CurrencyField': 'currency' } for model_name in model_names: if model_name == 'Participant': Model = Participant elif model_name == 'Session': Model = Session else: Model = getattr(models_module, model_name) # print(model_name) field_names = set(field.name for field in Model._meta.fields) members = get_field_names_for_csv(Model) if not members: members = [f for f in inspect_field_names(Model)] doc_dict[model_name] = OrderedDict() for member_name in members: member = getattr(Model, member_name, None) doc_dict[model_name][member_name] = OrderedDict() if member_name == 'id': doc_dict[model_name][member_name]['type'] = [ 'positive integer' ] doc_dict[model_name][member_name]['doc'] = ['Unique ID'] elif member_name in field_names: member = Model._meta.get_field_by_name(member_name)[0] internal_type = member.get_internal_type() data_type = data_types_readable.get( internal_type, internal_type) doc_dict[model_name][member_name]['type'] = [data_type] # flag error if the model doesn't have a doc attribute, # which it should unless the field is a 3rd party field doc = getattr(member, 'doc', '[error]') or '' doc_dict[model_name][member_name]['doc'] = [ line.strip() for line in doc.splitlines() if line.strip() ] choices = getattr(member, 'choices', None) if choices: doc_dict[model_name][member_name]['choices'] = ( choices_readable(choices)) elif isinstance(member, collections.Callable): doc_dict[model_name][member_name]['doc'] = [ inspect.getdoc(member) ] return doc_dict def docs_as_lists(doc_dict): header = ["Model", "Field", "Type", "Description"] body = [['{}: Documentation'.format(app_name_format(app_name))], ['*' * 15], ['Accessed: {}'.format(datetime.date.today().isoformat())], ['']] app_doc = getattr(models_module, 'doc', '') if app_doc: body += [app_doc, ''] for model_name in doc_dict: for member in doc_dict[model_name]: # lines.append('\t{}'.format(member)) for info_type in doc_dict[model_name][member]: # lines.append('\t\t{}'.format(info_type)) for info_line in doc_dict[model_name][member][info_type]: # lines.append(u'{}{}'.format('\t' * 3, info_line)) body += [[model_name, member, info_type, info_line]] return header, body doc_dict = generate_doc_dict() return docs_as_lists(doc_dict)
def create_session( session_config_name, *, label='', num_participants=None, pre_create_id=None, room_name=None, for_mturk=False, use_cli_bots=False, is_demo=False, force_browser_bots=False, # i think bot_case_number is unused. honor_browser_bots_config=False, bot_case_number=None, edited_session_config_fields=None): session = None use_browser_bots = False num_subsessions = 0 edited_session_config_fields = edited_session_config_fields or {} 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. otree.db.idmap.activate_cache() 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() if force_browser_bots: use_browser_bots = True elif (session_config.get('use_browser_bots') and honor_browser_bots_config): use_browser_bots = True else: use_browser_bots = False session = Session.objects.create( config=session_config, label=label, _pre_create_id=pre_create_id, use_browser_bots=use_browser_bots, is_demo=is_demo, num_participants=num_participants, ) # type: Session def bulk_create(model, descriptions): model.objects.bulk_create([ model(session=session, **description) for description in descriptions]) return model.objects.filter(session=session).order_by('pk') # check that it divides evenly session_lcm = session_config.get_lcm() if num_participants % session_lcm: msg = ( 'Session Config {}: Number of participants ({}) does not ' 'divide evenly into group size ({})' ).format(session_config['name'], num_participants, session_lcm) raise ValueError(msg) if for_mturk: session.mturk_num_participants = ( num_participants / settings.MTURK_NUM_PARTICIPANTS_MULTIPLE) # TODO: remove start_order start_order = list(range(num_participants)) if session_config.get('random_start_order'): random.shuffle(start_order) participants = bulk_create( Participant, [{ 'id_in_session': id_in_session, 'start_order': j, # check if id_in_session is in the bots ID list '_is_bot': use_cli_bots or use_browser_bots, } for id_in_session, j in enumerate(start_order, start=1)]) ParticipantLockModel.objects.bulk_create([ ParticipantLockModel(participant_code=participant.code) for participant in participants]) for app_name in session_config['app_sequence']: models_module = get_models_module(app_name) app_constants = get_app_constants(app_name) num_subsessions += app_constants.num_rounds round_numbers = list(range(1, app_constants.num_rounds + 1)) subs = bulk_create( models_module.Subsession, [{'round_number': round_number} for round_number in round_numbers]) # Create players models_module.Player.objects.bulk_create([ models_module.Player( session=session, subsession=subsession, round_number=round_number, participant=participant) for round_number, subsession in zip(round_numbers, subs) for participant in participants]) session._create_groups_and_initialize() session.build_participant_to_player_lookups() # automatically save all objects since the cache was activated: # Player, Group, Subsession, Participant, Session otree.db.idmap.save_objects() otree.db.idmap.deactivate_cache() if use_browser_bots: # what about clear_browser_bots? if session is created through # UI, when do we run that? it should be run when the session # is deleted try: num_players_total = num_participants * num_subsessions otree.bots.browser.initialize_bots( session_pk=session.pk, num_players_total=num_players_total, ) except: session.delete() raise session._set_admin_report_app_names() session.ready = True session.save() # 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.session = session return session
def get_hierarchical_data_for_app(cls, app_name, return_columns=False): """ Generate hierarchical structured data for app `app_name`, optionally returning flattened field names. """ models_module = get_models_module(app_name) # get the standard models Player = models_module.Player Group = models_module.Group Subsession = models_module.Subsession # get the custom models configuration custom_models_conf = get_custom_models_conf(models_module, 'export_data') # build standard models' columns columns_for_models = { m.__name__.lower(): get_field_names_for_csv(m) for m in [Player, Group, Subsession, Participant, Session] } # build custom models' columns columns_for_custom_models = cls.custom_columns_builder( custom_models_conf) custom_models_links = get_links_between_std_and_custom_models( custom_models_conf, for_action='export_data') std_models_select_related = defaultdict(list) for smodel_class, cmodels_links in custom_models_links.items(): smodel_lwr = smodel_class.__name__.lower() for cmodel_class, _ in cmodels_links: std_models_select_related[smodel_lwr].append( cmodel_class.__name__.lower()) # create lists of IDs that will be used for the export participant_ids = set( Player.objects.values_list('participant_id', flat=True)) session_ids = set( Subsession.objects.values_list('session_id', flat=True)) # create standard model querysets qs_participant = Participant.objects.filter(id__in=participant_ids) qs_player = Player.objects.filter(session_id__in=session_ids)\ .order_by('id')\ .select_related(*std_models_select_related.get('player', [])).values() qs_group = Group.objects.filter(session_id__in=session_ids)\ .select_related(*std_models_select_related.get('group', [])) qs_subsession = Subsession.objects.filter(session_id__in=session_ids)\ .select_related(*std_models_select_related.get('subsession', [])) # create prefetch dictionaries from querysets that map IDs to subsets of the data prefetch_filter_ids_for_custom_models = { } # stores IDs per standard oTree model to be used for # custom data prefetching # session ID -> subsession rows for this session prefetch_subsess = _rows_per_key_from_queryset(qs_subsession, 'session_id') prefetch_filter_ids_for_custom_models[ 'subsession'] = _set_of_ids_from_rows_per_key( prefetch_subsess, 'id') # subsession ID -> group rows for this subsession prefetch_group = _rows_per_key_from_queryset(qs_group, 'subsession_id') prefetch_filter_ids_for_custom_models[ 'group'] = _set_of_ids_from_rows_per_key(prefetch_group, 'id') # group ID -> player rows for this group prefetch_player = _rows_per_key_from_queryset(qs_player, 'group_id') prefetch_filter_ids_for_custom_models[ 'player'] = _set_of_ids_from_rows_per_key(prefetch_player, 'id') # prefetch dict for custom data models prefetch_custom = defaultdict( dict ) # standard oTree model name -> custom model name -> data rows for smodel, cmodel_links in custom_models_links.items( ): # per oTree std. model smodel_name_lwr = smodel.__name__.lower() # IDs that occur for that model filter_ids = prefetch_filter_ids_for_custom_models[smodel_name_lwr] # iterate per custom model for model, link_field_name in cmodel_links: # prefetch custom model objects that are linked to these oTree std. model IDs filter_kwargs = {link_field_name + '__in': filter_ids} custom_qs = model.objects.filter(**filter_kwargs).values() # store to the dict m = model.__name__.lower() prefetch_custom[smodel_name_lwr][ m] = _rows_per_key_from_queryset(custom_qs, link_field_name) # build the final nested data structure output_nested = [] ordered_columns_per_model = OrderedDict() # 1. each session for sess in Session.objects.filter(id__in=session_ids).values(): sess_cols = columns_for_models['session'] if 'session' not in ordered_columns_per_model: ordered_columns_per_model['session'] = sess_cols out_sess = _odict_from_row(sess, sess_cols) # 1.1. each subsession in the session out_sess['__subsession'] = [] for subsess in prefetch_subsess[sess['id']]: subsess_cols = columns_for_models['subsession'] if 'subsession' not in ordered_columns_per_model: ordered_columns_per_model['subsession'] = subsess_cols out_subsess = _odict_from_row(subsess, subsess_cols) # 1.1.1. each possible custom models connected to this subsession subsess_custom_models_rows = prefetch_custom.get( 'subsession', {}) for subsess_cmodel_name, subsess_cmodel_rows in subsess_custom_models_rows.items( ): cmodel_cols = columns_for_custom_models[ subsess_cmodel_name] if subsess_cmodel_name not in ordered_columns_per_model: ordered_columns_per_model[ subsess_cmodel_name] = cmodel_cols out_subsess['__' + subsess_cmodel_name] = [ _odict_from_row(cmodel_row, cmodel_cols) for cmodel_row in subsess_cmodel_rows[subsess['id']] ] # 1.1.2. each group in this subsession out_subsess['__group'] = [] for grp in prefetch_group[subsess['id']]: grp_cols = columns_for_models['group'] if 'group' not in ordered_columns_per_model: ordered_columns_per_model['group'] = grp_cols out_grp = _odict_from_row(grp, grp_cols) # 1.1.2.1. each possible custom models connected to this group grp_custom_models_rows = prefetch_custom.get('group', {}) for grp_cmodel_name, grp_cmodel_rows in grp_custom_models_rows.items( ): cmodel_cols = columns_for_custom_models[ grp_cmodel_name] if grp_cmodel_name not in ordered_columns_per_model: ordered_columns_per_model[ grp_cmodel_name] = cmodel_cols out_grp['__' + grp_cmodel_name] = [ _odict_from_row(cmodel_row, cmodel_cols) for cmodel_row in grp_cmodel_rows[grp['id']] ] # 1.1.2.2. each player in this group out_grp['__player'] = [] for player in prefetch_player[grp['id']]: # because player.payoff is a property player['payoff'] = player['_payoff'] player_cols = columns_for_models['player'] + [ 'participant_id' ] if 'player' not in ordered_columns_per_model: ordered_columns_per_model['player'] = player_cols out_player = _odict_from_row(player, player_cols) # 1.1.2.2.1. participant object connected to this player participant_obj = qs_participant.get( id=out_player['participant_id']) out_player['__participant'] = _odict_from_row( participant_obj, columns_for_models['participant'], is_obj=True) # 1.1.2.2.2. each possible custom models connected to this player player_custom_models_rows = prefetch_custom.get( 'player', {}) for player_cmodel_name, player_cmodel_rows in player_custom_models_rows.items( ): cmodel_cols = columns_for_custom_models[ player_cmodel_name] if player_cmodel_name not in ordered_columns_per_model: ordered_columns_per_model[ player_cmodel_name] = cmodel_cols out_player['__' + player_cmodel_name] = [ _odict_from_row(cmodel_row, cmodel_cols) for cmodel_row in player_cmodel_rows[player['id']] ] out_grp['__player'].append(out_player) out_subsess['__group'].append(out_grp) out_sess['__subsession'].append(out_subsess) output_nested.append(out_sess) # generate column names columns_flat = [] for model_name, model_cols in ordered_columns_per_model.items(): columns_flat.extend( ['.'.join((model_name, c)) for c in model_cols]) if return_columns: return output_nested, columns_flat else: return output_nested
def create_session(session_config_name, label="", num_participants=None, special_category=None, _pre_create_id=None): # 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. try: session_config = get_session_configs_dict()[session_config_name] except KeyError: msg = 'Session type "{}" not found in settings.py' raise ValueError(msg.format(session_config_name)) session = Session.objects.create( config=session_config, label=label, special_category=special_category, _pre_create_id=_pre_create_id ) def bulk_create(model, descriptions): model.objects.bulk_create([model(session=session, **description) for description in descriptions]) return model.objects.filter(session=session).order_by("pk") if num_participants is None: c_special_catdemo = constants_internal.session_special_category_demo c_special_catbots = constants_internal.session_special_category_bots if special_category == c_special_catdemo: num_participants = session_config["num_demo_participants"] elif special_category == c_special_catbots: num_participants = session_config["num_bots"] # check that it divides evenly session_lcm = get_lcm(session_config) if num_participants % session_lcm: msg = "Session Config {}: Number of participants ({}) does not divide " "evenly into group size ({})" raise ValueError(msg.format(session_config["name"], num_participants, session_lcm)) start_order = range(num_participants) if session_config.get("random_start_order"): random.shuffle(start_order) participants = bulk_create( Participant, [{"id_in_session": i + 1, "start_order": j} for i, j in enumerate(start_order)] ) for participant in participants: ParticipantLockModel(participant_code=participant.code).save() for app_name in session_config["app_sequence"]: models_module = get_models_module(app_name) app_constants = get_app_constants(app_name) round_numbers = range(1, app_constants.num_rounds + 1) subs = bulk_create(models_module.Subsession, [{"round_number": round_number} for round_number in round_numbers]) # Create players models_module.Player.objects.bulk_create( [ models_module.Player( session=session, subsession=subsession, round_number=round_number, participant=participant ) for round_number, subsession in zip(round_numbers, subs) for participant in participants ] ) session._create_groups_and_initialize() session.build_participant_to_player_lookups() session.ready = True session.save() return session
def create_session(session_config_name, *, label='', num_participants=None, pre_create_id=None, room_name=None, for_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. if for_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, _pre_create_id=pre_create_id, is_demo=is_demo, num_participants=num_participants, mturk_num_participants=mturk_num_participants) # type: Session # check that it divides evenly session_lcm = session_config.get_lcm() if num_participants % session_lcm: msg = ('Session Config {}: Number of participants ({}) does not ' 'divide evenly into group size ({})').format( session_config['name'], num_participants, session_lcm) raise ValueError(msg) 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_internal.get_views_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(): from django.contrib.auth.views import login, logout urlpatterns = [ urls.url(r'^$', RedirectView.as_view(url='/demo', permanent=True)), urls.url( r'^accounts/login/$', login, {'template_name': 'otree/login.html'}, name='login_url', ), urls.url( r'^accounts/logout/$', logout, {'next_page': 'DemoIndex'}, name='logout', ), ] urlpatterns += staticfiles_urlpatterns() used_names_in_url = set() for app_name in settings.INSTALLED_OTREE_APPS: models_module = common_internal.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_internal.get_pages_module(app_name) urlpatterns += url_patterns_from_game_module( views_module.__name__, name_in_url) urlpatterns += url_patterns_from_module('otree.views.participant') urlpatterns += url_patterns_from_module('otree.views.demo') urlpatterns += url_patterns_from_module('otree.views.admin') urlpatterns += url_patterns_from_module('otree.views.room') urlpatterns += url_patterns_from_module('otree.views.mturk') urlpatterns += url_patterns_from_module('otree.views.export') 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 export_docs(fp, app_name): """Write the dcos of the given app name as csv into the file-like object """ # generate doct_dict models_module = get_models_module(app_name) model_names = ["Participant", "Player", "Group", "Subsession", "Session"] line_break = '\r\n' def choices_readable(choices): lines = [] for value, name in choices: # unicode() call is for lazy translation strings lines.append(u'{}: {}'.format(value, six.text_type(name))) return lines def generate_doc_dict(): doc_dict = OrderedDict() data_types_readable = { 'PositiveIntegerField': 'positive integer', 'IntegerField': 'integer', 'BooleanField': 'boolean', 'CharField': 'text', 'TextField': 'text', 'FloatField': 'decimal', 'DecimalField': 'decimal', 'CurrencyField': 'currency' } for model_name in model_names: if model_name == 'Participant': Model = Participant elif model_name == 'Session': Model = Session else: Model = getattr(models_module, model_name) field_names = set(field.name for field in Model._meta.fields) members = get_field_names_for_csv(Model) doc_dict[model_name] = OrderedDict() for member_name in members: member = getattr(Model, member_name, None) doc_dict[model_name][member_name] = OrderedDict() if member_name == 'id': doc_dict[model_name][member_name]['type'] = [ 'positive integer' ] doc_dict[model_name][member_name]['doc'] = ['Unique ID'] elif member_name in field_names: member = Model._meta.get_field(member_name) internal_type = member.get_internal_type() data_type = data_types_readable.get( internal_type, internal_type) doc_dict[model_name][member_name]['type'] = [data_type] # flag error if the model doesn't have a doc attribute, # which it should unless the field is a 3rd party field doc = getattr(member, 'doc', '[error]') or '' doc_dict[model_name][member_name]['doc'] = [ line.strip() for line in doc.splitlines() if line.strip() ] choices = getattr(member, 'choices', None) if choices: doc_dict[model_name][member_name]['choices'] = ( choices_readable(choices)) elif isinstance(member, collections.Callable): doc_dict[model_name][member_name]['doc'] = [ inspect.getdoc(member) ] return doc_dict def docs_as_string(doc_dict): first_line = '{}: Documentation'.format(app_name) second_line = '*' * len(first_line) lines = [ first_line, second_line, '', 'Accessed: {}'.format(datetime.date.today().isoformat()), '' ] app_doc = getattr(models_module, 'doc', '') if app_doc: lines += [app_doc, ''] for model_name in doc_dict: lines.append(model_name) for member in doc_dict[model_name]: lines.append('\t{}'.format(member)) for info_type in doc_dict[model_name][member]: lines.append('\t\t{}'.format(info_type)) for info_line in doc_dict[model_name][member][info_type]: lines.append(u'{}{}'.format('\t' * 3, info_line)) output = u'\n'.join(lines) return output.replace('\n', line_break).replace('\t', ' ') doc_dict = generate_doc_dict() doc = docs_as_string(doc_dict) fp.write(doc)
def get_rows_for_wide_csv(): sessions = Session.objects.order_by('id') 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) participants = Participant.objects.order_by('id').values() if not participants: # 1 empty row return [[]] session_fields = get_field_names_for_csv(Session) participant_fields = get_field_names_for_csv(Participant) participant_fields.append('payoff_plus_participation_fee') header_row = ['participant.{}'.format(fname) for fname in participant_fields] header_row += ['session.{}'.format(fname) for fname in session_fields] header_row += ['session.config.{}'.format(fname) for fname in session_config_fields] rows = [header_row] for participant in participants: session = session_cache[participant['session_id']] participant['payoff_plus_participation_fee'] = get_payoff_plus_participation_fee(session, participant) row = [sanitize_for_csv(participant[fname]) for fname in participant_fields] row += [sanitize_for_csv(getattr(session, fname)) for fname in session_fields] row += [sanitize_for_csv(session.config.get(fname)) for fname in session_config_fields] rows.append(row) # heuristic to get the most relevant order of apps app_sequences = collections.Counter() for session in sessions: # we loaded the config earlier app_sequence = session.config['app_sequence'] app_sequences[tuple(app_sequence)] += session.num_participants most_common_app_sequence = app_sequences.most_common(1)[0][0] apps_not_in_popular_sequence = [ app for app in settings.INSTALLED_OTREE_APPS if app not in most_common_app_sequence] order_of_apps = list(most_common_app_sequence) + apps_not_in_popular_sequence rounds_per_app = OrderedDict() for app_name in order_of_apps: models_module = get_models_module(app_name) agg_dict = models_module.Subsession.objects.all().aggregate(Max('round_number')) highest_round_number = agg_dict['round_number__max'] 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 rows