class Player(BasePlayer): points = models.TextField() x_coordinate_form_pick = models.IntegerField() y_coordinate_form_pick = models.IntegerField() x_coordinate_form_guess = models.IntegerField() y_coordinate_form_guess = models.IntegerField() guess = models.TextField()
class FormFieldModel(otree.models.BaseGroup): null_boolean = models.BooleanField() big_integer = models.BigIntegerField() boolean = models.BooleanField(default=False) char = models.CharField() comma_separated_integer = models.CommaSeparatedIntegerField(max_length=100) date = models.DateField() date_time = models.DateTimeField() alt_date_time = models.DateTimeField( widget=otree.forms.SplitDateTimeWidget) decimal = models.DecimalField(max_digits=5, decimal_places=2) email = models.EmailField() file = models.FileField(upload_to='_tmp/uploads') file_path = models.FilePathField() float = models.FloatField() integer = models.IntegerField() generic_ip_address = models.GenericIPAddressField() positive_integer = models.PositiveIntegerField() positive_small_integer = models.PositiveSmallIntegerField() slug = models.SlugField() small_integer = models.SmallIntegerField() text = models.TextField() alt_text = models.TextField(widget=otree.forms.TextInput) time = models.TimeField() url = models.URLField() many_to_many = models.ManyToManyField('SimpleModel', related_name='+') one_to_one = models.OneToOneField('SimpleModel', related_name='+') currency = models.CurrencyField() currency_choice = models.CurrencyField(choices=[('0.01', '0.01'), ('1.20', '1.20')]) sent_amount = models.CurrencyField(choices=currency_range(0, 0.75, 0.05)) slider_widget = models.IntegerField(widget=widgets.SliderInput())
class Player(BasePlayer): transcription_1 = models.TextField( validators=[MaxLengthValidator(Constants.transcription_max_length)]) transcription_2 = models.TextField( validators=[MaxLengthValidator(Constants.transcription_max_length)]) distance_1 = models.PositiveIntegerField() distance_2 = models.PositiveIntegerField() def set_payoff(self): self.payoff = 0
class Player(BasePlayer): time_Demographics = models.TextField(widget=widgets.HiddenInput( attrs={'id': 'arrive_time'})) time_CognitiveReflectionTest = models.TextField(widget=widgets.HiddenInput( attrs={'id': 'arrive_time'})) def set_payoff(self): """Calculate payoff, which is zero for the survey""" self.payoff = 0 q_country = models.CharField(verbose_name='What country are you from?') q_age = models.PositiveIntegerField(verbose_name='What is your age?', choices=range(13, 125), initial=None) q_station = models.PositiveIntegerField( verbose_name= 'What is your computer station number? (see white sticker on computer or ask experimenter)', choices=range(1, 17), initial=None) q_gender = models.CharField(initial=None, choices=['Male', 'Female', 'Other'], verbose_name='What is your gender?', widget=widgets.RadioSelect()) q_income = models.PositiveIntegerField( verbose_name='What is the approximate annual income of your family?', choices=[ [1, 'less than $15,000'], [2, '$15,000 - $29,999'], [3, '$30,000 - $59,999'], [4, '$60,000 - $99,999'], [5, '$100,000 - $199,999'], [6, '$200,000 or more'], [7, 'I rather not answer this question'], ]) q_zipcode = models.PositiveIntegerField( verbose_name='What is the zip code where you grew up?') q_opinion = models.CharField( initial=None, verbose_name= 'Were the instructions provided in this experiment clear and useful?', choices=['Yes', 'No'], widget=widgets.RadioSelect()) crt_bat = models.PositiveIntegerField() crt_widget = models.PositiveIntegerField() crt_lake = models.PositiveIntegerField()
class Player(BasePlayer): time_Instructions = models.TextField(widget=widgets.HiddenInput( attrs={'id': 'arrive_time'})) time_Transcription = models.TextField(widget=widgets.HiddenInput( attrs={'id': 'arrive_time'})) transcribed_text = models.TextField() distance_1 = models.PositiveIntegerField() levenshtein_distance = models.PositiveIntegerField() time_Instructions = models.LongStringField() def set_payoff(self): self.payoff = 0
class Player(BasePlayer): points = models.TextField() x_coordinate_form_pick = models.IntegerField() y_coordinate_form_pick = models.IntegerField() x_coordinate_form_guess = models.IntegerField() y_coordinate_form_guess = models.IntegerField() box_1_offset_left_form = models.IntegerField() box_1_offset_top_form = models.IntegerField() box_2_offset_left_form = models.IntegerField() box_2_offset_top_form = models.IntegerField() box_3_offset_left_form = models.IntegerField() box_3_offset_top_form = models.IntegerField() guess = models.TextField()
class Player(otree.models.BasePlayer): # <built-in> group = models.ForeignKey(Group, null=True) subsession = models.ForeignKey(Subsession) # </built-in> transcription_1 = models.TextField(validators=[MaxLengthValidator(Constants.transcription_max_length)]) transcription_2 = models.TextField(validators=[MaxLengthValidator(Constants.transcription_max_length)]) distance_1 = models.PositiveIntegerField() distance_2 = models.PositiveIntegerField() def set_payoff(self): self.payoff = 0
class Player(otree.models.BasePlayer): # <built-in> group = models.ForeignKey(Group, null=True) subsession = models.ForeignKey(Subsession) # </built-in> name = models.CharField(max_length=255, verbose_name="Your name") age = models.CharField(max_length=255, verbose_name="Your age") email = models.EmailField(verbose_name="Your email address") gender = models.CharField(verbose_name="Are you", max_length=255, widget=widgets.RadioSelect(), choices=["Male", "Female"]) major = models.CharField(max_length=1000, verbose_name="What is your major?") location_of_your_partners_influence_your_decisions = models.TextField( verbose_name=("Did the location of your partners influence your " "decisions of how much to contribute to the individual " "account versus the team account? If yes, how?")) working_in_a_location_of_their_choice_more_less_to_the_team = models.BooleanField( widget=widgets.RadioSelect(), verbose_name=("When you were partnered with two people working in a " "location of their choice, did you want to give more " "to the team or less than when you were partnered " "with two people working in a lab?")) partners_in_location_their_choice_worked_harder_than_the_lab = models.BooleanField( widget=widgets.RadioSelect(), verbose_name=("Do you believe your partners participation in a " "location of their choice gave more to the group than " "your partners working the lab?")) I_work_best_in = models.CharField( verbose_name="Are you", max_length=255, widget=widgets.RadioSelect(), choices=["Structured environments", "flexible environments"]) risks_in_everyday_life = models.PositiveIntegerField( min=1, max=10, widget=widgets.SliderInput(), verbose_name=("In general, do you take more or less risks in everyday " "life? ('1' = take less risk and '10' take more risk.)")) risks_in_financial_decision = models.PositiveIntegerField( min=1, max=10, widget=widgets.SliderInput(), default=5, verbose_name= (" In general, do you take more or less risks in financial decisions? " "life? ('1' = take less risk and '10' take more risk.)"))
class Player(otree.models.BasePlayer): # <built-in> group = models.ForeignKey(Group, null=True) subsession = models.ForeignKey(Subsession) # </built-in> transcription_1 = models.TextField( validators=[MaxLengthValidator(Constants.transcription_max_length)]) transcription_2 = models.TextField( validators=[MaxLengthValidator(Constants.transcription_max_length)]) distance_1 = models.PositiveIntegerField() distance_2 = models.PositiveIntegerField() def transcription_1_error_message(self, text_user): if not text_is_close_enough(text_user, Constants.reference_texts[0], Constants.error_rate_transcription_1): if Constants.error_rate_transcription_1 == 0.0: return Constants.transcription_error_0 else: return Constants.transcription_error_positive else: self.distance_1 = levenshtein(Constants.reference_texts[0], text_user) def transcription_2_error_message(self, text_user): if not text_is_close_enough(text_user, Constants.reference_texts[1], Constants.error_rate_transcription_2): if Constants.error_rate_transcription_2 == 0.0: return Constants.transcription_error_0 else: return Constants.transcription_error_positive else: self.distance_2 = levenshtein(Constants.reference_texts[1], text_user) def set_payoff(self): self.payoff = 0
class Session(models.OTreeModel): class Meta: app_label = "otree" # if i don't set this, it could be in an unpredictable order ordering = ['pk'] _ft = FieldTrackerWithVarsSupport() vars: dict = models._PickleField(default=dict) config: dict = models._PickleField(default=dict, null=True) # label of this session instance label = models.CharField(max_length=300, null=True, blank=True, help_text='For internal record-keeping') code = models.CharField( default=random_chars_8, max_length=16, null=False, unique=True, doc="Randomly generated unique identifier for the session.", ) mturk_HITId = models.CharField( max_length=300, null=True, blank=True, help_text='Hit id for this session on MTurk', ) mturk_HITGroupId = models.CharField( max_length=300, null=True, blank=True, help_text='Hit id for this session on MTurk', ) is_mturk = models.BooleanField(default=False) def mturk_num_workers(self): assert self.is_mturk return int(self.num_participants / settings.MTURK_NUM_PARTICIPANTS_MULTIPLE) mturk_use_sandbox = models.BooleanField( default=True, help_text="Should this session be created in mturk sandbox?") # use Float instead of DateTime because DateTime # is a pain to work with (e.g. naive vs aware datetime objects) # and there is no need here for DateTime mturk_expiration = models.FloatField(null=True) mturk_qual_id = models.CharField(default='', max_length=50) archived = models.BooleanField( default=False, db_index=True, doc=("If set to True the session won't be visible on the " "main ViewList for sessions"), ) comment = models.TextField(blank=True) _anonymous_code = models.CharField(default=random_chars_10, max_length=10, null=False, db_index=True) is_demo = models.BooleanField(default=False) _admin_report_app_names = models.TextField(default='') _admin_report_num_rounds = models.CharField(default='', max_length=255) num_participants = models.PositiveIntegerField() def __unicode__(self): return self.code @property def participation_fee(self): '''This method is deprecated from public API, but still useful internally (like data export)''' return self.config['participation_fee'] @property def real_world_currency_per_point(self): '''This method is deprecated from public API, but still useful internally (like data export)''' return self.config['real_world_currency_per_point'] @property def use_browser_bots(self): return self.config.get('use_browser_bots', False) def mock_exogenous_data(self): ''' It's for any exogenous data: - participant labels (which are not passed in through REST API) - participant vars - session vars (if we enable that) ''' if self.config.get('mock_exogenous_data'): import shared_out as user_utils with otree.db.idmap.use_cache(): user_utils.mock_exogenous_data(self) otree.db.idmap.save_objects() # need to save self because it's not in the idmap cache self.save() def get_subsessions(self): lst = [] app_sequence = self.config['app_sequence'] for app in app_sequence: models_module = otree.common.get_models_module(app) subsessions = models_module.Subsession.objects.filter( session=self).order_by('round_number') lst.extend(list(subsessions)) return lst def get_participants(self): return list(self.participant_set.order_by('id_in_session')) def mturk_worker_url(self): # different HITs # get the same preview page, because they are lumped into the same # "hit group". This is not documented, but it seems HITs are lumped # if a certain subset of properties are the same: # https://forums.aws.amazon.com/message.jspa?messageID=597622#597622 # this seems like the correct design; the only case where this will # not work is if the HIT was deleted from the server, but in that case, # the HIT itself should be canceled. # 2018-06-04: # the format seems to have changed to this: # https://worker.mturk.com/projects/{group_id}/tasks?ref=w_pl_prvw # but the old format still works. # it seems I can't replace groupId by hitID, which i would like to do # because it's more precise. subdomain = "workersandbox" if self.mturk_use_sandbox else 'www' return "https://{}.mturk.com/mturk/preview?groupId={}".format( subdomain, self.mturk_HITGroupId) def mturk_is_expired(self): # self.mturk_expiration is offset-aware, so therefore we must compare # it against an offset-aware value. return self.mturk_expiration and self.mturk_expiration < time.time() def mturk_is_active(self): return self.mturk_HITId and not self.mturk_is_expired() def advance_last_place_participants(self): # django.test takes 0.5 sec to import, # if this is done globally then it adds to each startup # it's only used here, and often isn't used at all. # so best to do it only here # it gets cached import django.test client = django.test.Client() participants = self.get_participants() # in case some participants haven't started unvisited_participants = [] for p in participants: if p._index_in_pages == 0: unvisited_participants.append(p) client.get(p._start_url(), follow=True) if unvisited_participants: # that's it -- just visit the start URL, advancing by 1 return last_place_page_index = min([p._index_in_pages for p in participants]) last_place_participants = [ p for p in participants if p._index_in_pages == last_place_page_index ] for p in last_place_participants: try: current_form_page_url = p._current_form_page_url if current_form_page_url: resp = client.post( current_form_page_url, data={ otree.constants.timeout_happened: True, otree.constants.admin_secret_code: ADMIN_SECRET_CODE, }, follow=True, ) # not sure why, but many users are getting HttpResponseNotFound if resp.status_code >= 400: msg = ('Submitting page {} failed, ' 'returned HTTP status code {}.'.format( current_form_page_url, resp.status_code)) content = resp.content if len(content) < 600: msg += ' response content: {}'.format(content) raise AssertionError(msg) else: # it's possible that the slowest user is on a wait page, # especially if their browser is closed. # because they were waiting for another user who then # advanced past the wait page, but they were never # advanced themselves. start_url = p._start_url() resp = client.get(start_url, follow=True) except: logging.exception("Failed to advance participants.") raise # do the auto-advancing here, # rather than in increment_index_in_pages, # because it's only needed here. otree.channels.utils.sync_group_send_wrapper( type='auto_advanced', group=auto_advance_group(p.code), event={}) def get_room(self): from otree.room import ROOM_DICT try: room_name = RoomToSession.objects.get(session=self).room_name return ROOM_DICT[room_name] except RoomToSession.DoesNotExist: return None def _get_payoff_plus_participation_fee(self, payoff): '''For a participant who has the given payoff, return their payoff_plus_participation_fee Useful to define it here, for data export ''' return self.config[ 'participation_fee'] + payoff.to_real_world_currency(self) def _set_admin_report_app_names(self): admin_report_app_names = [] num_rounds_list = [] for app_name in self.config['app_sequence']: models_module = otree.common.get_models_module(app_name) app_label = get_app_label_from_name(app_name) try: select_template([ f'{app_label}/admin_report.html', f'{app_label}/AdminReport.html' ]) except TemplateDoesNotExist: pass else: admin_report_app_names.append(app_name) num_rounds_list.append(models_module.Constants.num_rounds) self._admin_report_app_names = ';'.join(admin_report_app_names) self._admin_report_num_rounds = ';'.join( str(n) for n in num_rounds_list) def _admin_report_apps(self): return self._admin_report_app_names.split(';') def _admin_report_num_rounds_list(self): return [int(num) for num in self._admin_report_num_rounds.split(';')] def has_admin_report(self): return bool(self._admin_report_app_names)
class Player(otree.models.BasePlayer): # <built-in> group = models.ForeignKey(Group, null=True) subsession = models.ForeignKey(Subsession) # </built-in> player_re_type = models.IntegerField(min=min(Constants.player_types), max=max(Constants.player_types)) transcription = models.TextField() share = models.BooleanField(default=False, widget=widgets.HiddenInput()) training_skip = models.BooleanField(default=False, widget=widgets.HiddenInput()) training_start_time = models.DateTimeField() training_idx = models.PositiveIntegerField(default=0) training_intents = models.JSONField() round_1_start_time = models.DateTimeField() round_1_idx = models.PositiveIntegerField(default=0) round_1_transcription_texts = models.JSONField() round_1_intents = models.JSONField() round_1_a_payoff = models.CurrencyField() round_2_start_time = models.DateTimeField() round_2_idx = models.PositiveIntegerField(default=0) round_2_transcription_texts = models.JSONField() round_2_intents = models.JSONField() round_2_shared = models.PositiveIntegerField(default=0) round_2_a_payoff = models.CurrencyField() round_2_b_payoff = models.CurrencyField() round_3_start_time = models.DateTimeField() round_3_idx = models.PositiveIntegerField(default=0) round_3_transcription_texts = models.JSONField() round_3_intents = models.JSONField() round_3_shared = models.PositiveIntegerField(default=0) round_3_a_payoff = models.CurrencyField() round_3_b_payoff = models.CurrencyField() def set_payoff(self): self.payoff = 0 @property def training_transcription_texts(self): return Constants.reference_texts def training_png(self, idx): return Constants.reference_pngs[idx] def round_1_png(self, idx): if not hasattr(self, "__round_1_png"): self.__round_1_png = {} if idx not in self.__round_1_png: text = self.round_1_transcription_texts[idx] self.__round_1_png[idx] = txt2png.render( text, encoding=Constants.png_encoding) return self.__round_1_png[idx] def set_round_1_payoff(self): self.round_1_a_payoff = Constants.a_payoff * self.round_1_idx def round_1_time_left(self): start = self.round_1_start_time now = timezone.now() time_left = Constants.round_1_seconds - (now - start).seconds return time_left if time_left > 0 else 0 def round_2_png(self, idx): if not hasattr(self, "__round_2_png"): self.__round_2_png = {} if idx not in self.__round_2_png: text = self.round_2_transcription_texts[idx] self.__round_2_png[idx] = txt2png.render( text, encoding=Constants.png_encoding) return self.__round_2_png[idx] def set_round_2_payoff(self): count_b = self.round_2_shared if count_b > 0: self.round_2_b_payoff = Constants.b_payoff * count_b else: self.round_2_b_payoff = 0 count_a = self.round_2_idx - count_b if count_a > 0: self.round_2_a_payoff = Constants.a_payoff * count_a else: self.round_2_a_payoff = 0 def round_2_time_left(self): start = self.round_2_start_time now = timezone.now() time_left = Constants.round_2_seconds - (now - start).seconds return time_left if time_left > 0 else 0 def round_3_png(self, idx): if not hasattr(self, "__round_3_png"): self.__round_3_png = {} if idx not in self.__round_3_png: text = self.round_3_transcription_texts[idx] self.__round_3_png[idx] = txt2png.render( text, encoding=Constants.png_encoding) return self.__round_3_png[idx] def set_round_3_payoff(self): count_b = self.round_3_shared if count_b > 0: self.round_3_b_payoff = Constants.b_payoff * count_b else: self.round_3_b_payoff = 0 count_a = self.round_3_idx - count_b if count_a > 0: self.round_3_a_payoff = Constants.a_payoff * count_a else: self.round_3_a_payoff = 0 def round_3_time_left(self): start = self.round_3_start_time now = timezone.now() time_left = Constants.round_3_seconds - (now - start).seconds return time_left if time_left > 0 else 0
class Session(ModelWithVars): class Meta: # if i don't set this, it could be in an unpredictable order ordering = ['pk'] app_label = "otree" config = models.JSONField( default=dict, null=True, doc=("the session config dict, as defined in the " "programmer's settings.py.")) # label of this session instance label = models.CharField(max_length=300, null=True, blank=True, help_text='For internal record-keeping') experimenter_name = models.CharField( max_length=300, null=True, blank=True, help_text='For internal record-keeping') code = models.RandomCharField( db_index=True, length=8, doc="Randomly generated unique identifier for the session.") time_scheduled = models.DateTimeField( null=True, doc="The time at which the session is scheduled", help_text='For internal record-keeping', blank=True) time_started = models.DateTimeField( null=True, doc="The time at which the experimenter started the session") mturk_HITId = models.CharField( max_length=300, null=True, blank=True, help_text='Hit id for this session on MTurk') mturk_HITGroupId = models.CharField( max_length=300, null=True, blank=True, help_text='Hit id for this session on MTurk') mturk_qualification_type_id = models.CharField( max_length=300, null=True, blank=True, help_text='Qualification type that is ' 'assigned to each worker taking hit') # since workers can drop out number of participants on server should be # greater than number of participants on mturk # value -1 indicates that this session it not intended to run on mturk mturk_num_participants = models.IntegerField( default=-1, help_text="Number of participants on MTurk") mturk_sandbox = models.BooleanField( default=True, help_text="Should this session be created in mturk sandbox?") archived = models.BooleanField( default=False, db_index=True, doc=("If set to True the session won't be visible on the " "main ViewList for sessions")) git_commit_timestamp = models.CharField( max_length=200, null=True, doc=( "Indicates the version of the code (as recorded by Git) that was " "used to run the session, so that the session can be replicated " "later.\n Search through the Git commit log to find a commit that " "was made at this time.")) comment = models.TextField(blank=True) _ready_to_play = models.BooleanField(default=False) _anonymous_code = models.RandomCharField(length=10) special_category = models.CharField( db_index=True, max_length=20, null=True, doc="whether it's a test session, demo session, etc.") # whether someone already viewed this session's demo links demo_already_used = models.BooleanField(default=False, db_index=True) # indicates whether a session has been fully created (not only has the # model itself been created, but also the other models in the hierarchy) ready = models.BooleanField(default=False) _pre_create_id = models.CharField(max_length=300, db_index=True, null=True) def __unicode__(self): return self.code @property def participation_fee(self): '''This method is deprecated from public API, but still useful internally (like data export)''' return self.config['participation_fee'] @property def real_world_currency_per_point(self): '''This method is deprecated from public API, but still useful internally (like data export)''' return self.config['real_world_currency_per_point'] @property def session_type(self): '''2015-07-10: session_type is deprecated this shim method will be removed eventually''' return self.config def is_open(self): return GlobalSingleton.objects.get().default_session == self def is_for_mturk(self): return (not self.is_demo()) and (self.mturk_num_participants > 0) def is_demo(self): return (self.special_category == constants_internal.session_special_category_demo) def get_subsessions(self): lst = [] app_sequence = self.config['app_sequence'] for app in app_sequence: models_module = otree.common_internal.get_models_module(app) subsessions = models_module.Subsession.objects.filter( session=self).order_by('round_number') lst.extend(list(subsessions)) return lst def delete(self, using=None): for subsession in self.get_subsessions(): subsession.delete() super(Session, self).delete(using) def get_participants(self): return self.participant_set.all() def _create_groups_and_initialize(self): # group_by_arrival_time code used to be here for subsession in self.get_subsessions(): subsession._create_groups() subsession._initialize() subsession.save() self._ready_to_play = True # assert self is subsession.session self.save() def mturk_requester_url(self): if self.mturk_sandbox: requester_url = ( "https://requestersandbox.mturk.com/mturk/manageHITs") else: requester_url = "https://requester.mturk.com/mturk/manageHITs" return requester_url def mturk_worker_url(self): if self.mturk_sandbox: worker_url = ( "https://workersandbox.mturk.com/mturk/preview?groupId={}" ).format(self.mturk_HITGroupId) else: worker_url = ( "https://www.mturk.com/mturk/preview?groupId={}").format( self.mturk_HITGroupId) return worker_url def advance_last_place_participants(self): participants = self.get_participants() c = django.test.Client() # in case some participants haven't started unvisited_participants = [] for p in participants: if not p._current_form_page_url: unvisited_participants.append(p) c.get(p._start_url(), follow=True) if unvisited_participants: from otree.models import Participant for p in unvisited_participants: p.save() Participant.flush_cached_instance(p) # that's it -- just visit the start URL, advancing # by 1 return last_place_page_index = min([p._index_in_pages for p in participants]) last_place_participants = [ p for p in participants if p._index_in_pages == last_place_page_index ] for p in last_place_participants: if not p._current_form_page_url: # what if first page is wait page? raise resp = c.post(p._current_form_page_url, data={constants_internal.auto_submit: True}, follow=True) assert resp.status_code < 400 def build_participant_to_player_lookups(self): subsession_app_names = self.config['app_sequence'] num_pages_in_each_app = {} for app_name in subsession_app_names: views_module = otree.common_internal.get_views_module(app_name) num_pages = len(views_module.page_sequence) num_pages_in_each_app[app_name] = num_pages for participant in self.get_participants(): participant.build_participant_to_player_lookups( num_pages_in_each_app)
class Player(otree.models.BasePlayer): # <built-in> group = models.ForeignKey(Group, null=True) subsession = models.ForeignKey(Subsession) # </built-in> name = models.CharField(max_length=255, verbose_name="Your name") age = models.CharField(max_length=255, verbose_name="Your age") email = models.EmailField(verbose_name="Your email address") gender = models.CharField(verbose_name="Are you", max_length=255, widget=widgets.RadioSelect(), choices=["Male", "Female"]) major = models.CharField(max_length=1000, verbose_name="What is your major?") working_in_a_location_of_their_choice_more_less_to_the_team = models.CharField( verbose_name= "When you were in a group with two TELECOMMUTERS working in a " "location and time of their choice, did you want to give more " "to the group account than when you were partnered " "with two LAB PARTICIPANTS?", max_length=500, widget=widgets.RadioSelect(), choices=[ "More to the Group Account with TELECOMMUTERS", "Less to the Group Account with TELECOMMUTERS", "Didn't Matter" ]) partners_in_location_their_choice_worked_harder_than_the_lab = models.CharField( verbose_name= "Do you believe your TELECOMMUTER group members gave more/less/the same to the group " "account than your LAB PARTICIPANT group members?", max_length=500, widget=widgets.RadioSelect(), choices=[ "TELECOMMUTERS gave more", "TELECOMMUTERS gave less", "Both types gave the same" ]) location_of_your_partners_influence_your_decisions = models.TextField( verbose_name=( "Did your group members' type (TELECOMMUTERS or LAB PARTICIPANTS) " "influence your decisions today in a way not accounted for above? " "If so, please explain.")) I_work_best_in = models.CharField( verbose_name="Which do you prefer working in?", max_length=255, widget=widgets.RadioSelect(), choices=["Structured environments", "flexible environments"]) I_work_prefer = models.CharField( verbose_name="Which type of work do you prefer?", max_length=255, widget=widgets.RadioSelect(), choices=["Working Alone", "Working in Groups"]) risks_in_everyday_life = models.PositiveIntegerField( min=1, max=10, widget=widgets.SliderInput(), verbose_name=("In general, do you take more or less risks in everyday " "life? ('1' = take less risk and '10' take more risk.)")) risks_in_financial_decision = models.PositiveIntegerField( min=1, max=10, widget=widgets.SliderInput(), default=5, verbose_name= (" In general, do you take more or less risks in financial decisions? " "life? ('1' = take less risk and '10' take more risk.)"))
class Session(ModelWithVars): class Meta: app_label = "otree" # if i don't set this, it could be in an unpredictable order ordering = ['pk'] _pickle_fields = ['vars', 'config'] config = models._PickleField(default=dict, null=True) # type: dict # label of this session instance label = models.CharField(max_length=300, null=True, blank=True, help_text='For internal record-keeping') experimenter_name = models.CharField( max_length=300, null=True, blank=True, help_text='For internal record-keeping') ready = models.BooleanField(default=False) code = models.CharField( default=random_chars_8, max_length=16, # set non-nullable, until we make our CharField non-nullable null=False, unique=True, doc="Randomly generated unique identifier for the session.") mturk_HITId = models.CharField( max_length=300, null=True, blank=True, help_text='Hit id for this session on MTurk') mturk_HITGroupId = models.CharField( max_length=300, null=True, blank=True, help_text='Hit id for this session on MTurk') # since workers can drop out number of participants on server should be # greater than number of participants on mturk # value -1 indicates that this session it not intended to run on mturk mturk_num_participants = models.IntegerField( default=-1, help_text="Number of participants on MTurk") mturk_use_sandbox = models.BooleanField( default=True, help_text="Should this session be created in mturk sandbox?") archived = models.BooleanField( default=False, db_index=True, doc=("If set to True the session won't be visible on the " "main ViewList for sessions")) comment = models.TextField(blank=True) _anonymous_code = models.CharField(default=random_chars_10, max_length=10, null=False, db_index=True) _pre_create_id = models.CharField(max_length=255, db_index=True, null=True) use_browser_bots = models.BooleanField(default=False) # if the user clicks 'start bots' twice, this will prevent the bots # from being run twice. _cannot_restart_bots = models.BooleanField(default=False) _bots_finished = models.BooleanField(default=False) _bots_errored = models.BooleanField(default=False) _bot_case_number = models.PositiveIntegerField() is_demo = models.BooleanField(default=False) # whether SOME players are bots has_bots = models.BooleanField(default=False) _admin_report_app_names = models.TextField(default='') _admin_report_num_rounds = models.CharField(default='', max_length=255) num_participants = models.PositiveIntegerField() def __unicode__(self): return self.code @property def participation_fee(self): '''This method is deprecated from public API, but still useful internally (like data export)''' return self.config['participation_fee'] @property def real_world_currency_per_point(self): '''This method is deprecated from public API, but still useful internally (like data export)''' return self.config['real_world_currency_per_point'] def is_for_mturk(self): return (not self.is_demo) and (self.mturk_num_participants > 0) def get_subsessions(self): lst = [] app_sequence = self.config['app_sequence'] for app in app_sequence: models_module = otree.common_internal.get_models_module(app) subsessions = models_module.Subsession.objects.filter( session=self).order_by('round_number') lst.extend(list(subsessions)) return lst def get_participants(self): return self.participant_set.all() def _create_groups_and_initialize(self): # group_by_arrival_time_time code used to be here for subsession in self.get_subsessions(): subsession._create_groups() subsession.before_session_starts() subsession.creating_session() subsession.save() def mturk_requester_url(self): if self.mturk_use_sandbox: requester_url = ( "https://requestersandbox.mturk.com/mturk/manageHITs") else: requester_url = "https://requester.mturk.com/mturk/manageHITs" return requester_url def mturk_worker_url(self): if self.mturk_use_sandbox: return ("https://workersandbox.mturk.com/mturk/preview?groupId={}" ).format(self.mturk_HITGroupId) return ("https://www.mturk.com/mturk/preview?groupId={}").format( self.mturk_HITGroupId) def advance_last_place_participants(self): participants = self.get_participants() # in case some participants haven't started unvisited_participants = [] for p in participants: if p._index_in_pages == 0: unvisited_participants.append(p) client.get(p._start_url(), follow=True) if unvisited_participants: # that's it -- just visit the start URL, advancing by 1 return last_place_page_index = min([p._index_in_pages for p in participants]) last_place_participants = [ p for p in participants if p._index_in_pages == last_place_page_index ] for p in last_place_participants: try: current_form_page_url = p._current_form_page_url if current_form_page_url: resp = client.post( current_form_page_url, data={ constants_internal.timeout_happened: True, constants_internal.admin_secret_code: ADMIN_SECRET_CODE }, follow=True) # not sure why, but many users are getting HttpResponseNotFound if resp.status_code >= 400: msg = ('Submitting page {} failed, ' 'returned HTTP status code {}.'.format( current_form_page_url, resp.status_code)) content = resp.content if len(content) < 600: msg += ' response content: {}'.format(content) raise AssertionError(msg) else: # it's possible that the slowest user is on a wait page, # especially if their browser is closed. # because they were waiting for another user who then # advanced past the wait page, but they were never # advanced themselves. start_url = p._start_url() resp = client.get(start_url, follow=True) except: logging.exception("Failed to advance participants.") raise # do the auto-advancing here, # rather than in increment_index_in_pages, # because it's only needed here. channels.Group('auto-advance-{}'.format(p.code)).send( {'text': json.dumps({'auto_advanced': True})}) def pages_auto_reload_when_advanced(self): # keep it enable until I determine # (a) the usefulness of the feature # (b) the impact on performance return True # return settings.DEBUG or self.is_demo def build_participant_to_player_lookups(self): subsession_app_names = self.config['app_sequence'] views_modules = {} for app_name in subsession_app_names: views_modules[app_name] = ( otree.common_internal.get_views_module(app_name)) def views_module_for_player(player): return views_modules[player._meta.app_config.name] records_to_create = [] for participant in self.get_participants(): page_index = 0 for player in participant.get_players(): for View in views_module_for_player(player).page_sequence: page_index += 1 records_to_create.append( ParticipantToPlayerLookup( participant=participant, page_index=page_index, app_name=player._meta.app_config.name, player_pk=player.pk, url=reverse(View.url_name(), args=[participant.code, page_index]))) # technically could be stored at the session level participant._max_page_index = page_index participant.save() ParticipantToPlayerLookup.objects.bulk_create(records_to_create) def get_room(self): from otree.room import ROOM_DICT try: room_name = RoomToSession.objects.get(session=self).room_name return ROOM_DICT[room_name] except RoomToSession.DoesNotExist: return None def _get_payoff_plus_participation_fee(self, payoff): '''For a participant who has the given payoff, return their payoff_plus_participation_fee Useful to define it here, for data export ''' return (self.config['participation_fee'] + payoff.to_real_world_currency(self)) def _set_admin_report_app_names(self): admin_report_app_names = [] num_rounds_list = [] for app_name in self.config['app_sequence']: models_module = otree.common_internal.get_models_module(app_name) app_label = get_app_label_from_name(app_name) try: get_template('{}/AdminReport.html'.format(app_label)) admin_report_app_names.append(app_name) num_rounds_list.append(models_module.Constants.num_rounds) except TemplateDoesNotExist: pass self._admin_report_app_names = ';'.join(admin_report_app_names) self._admin_report_num_rounds = ';'.join( str(n) for n in num_rounds_list) def _admin_report_apps(self): return self._admin_report_app_names.split(';') def _admin_report_num_rounds_list(self): return [int(num) for num in self._admin_report_num_rounds.split(';')] def has_admin_report(self): return bool(self._admin_report_app_names)
class Player(otree.models.BasePlayer): # <built-in> group = models.ForeignKey(Group, null=True) subsession = models.ForeignKey(Subsession) # </built-in> player_name = models.CharField(max_length=255) avatar = models.CharField(max_length=255) virtual_oponent = models.CharField(max_length=255, choices=list( Constants.virtual_comb.keys())) n_simple_propuesta = models.CurrencyField( choices=range(0, 201), verbose_name="¿Cuánto le gustaría ofrecer?", default=0) n_simple_respuesta = models.CharField( widget=widgets.RadioSelectHorizontal(), max_length=250, choices=["Aceptar", "Rechazar"], default="Aceptar") n_empresa_trabajador_propuesta = models.CurrencyField( verbose_name="¿Cuánto le gustaría ofrecer como salario?", choices=range(0, 201), default=0) n_empresa_trabajador_propuestas = models.TextField(default="[]") n_empresa_trabajador_respuesta = models.CharField( widget=widgets.RadioSelectHorizontal(), max_length=250, choices=["Aceptar", "Rechazar"], default="Aceptar") n_empresa_trabajador_contrapropuesta = models.CurrencyField( verbose_name="Contraoferta de salario:", choices=range(0, 201), default=0) n_empresa_trabajador_contrapropuestas = models.TextField(default="[]") n_empresa_trabajador_finalizacion_forzada = models.BooleanField( default=False) def all_propuestas(self): return json.loads(self.n_empresa_trabajador_propuestas) def add_propuesta(self, v): lista = self.all_propuestas() lista.append(int(v)) self.n_empresa_trabajador_propuestas = json.dumps(lista) def all_contrapropuestas(self): return json.loads(self.n_empresa_trabajador_contrapropuestas) def add_contrapropuesta(self, v): lista = self.all_contrapropuestas() lista.append(int(v)) self.n_empresa_trabajador_contrapropuestas = json.dumps(lista) def role(self): if self.subsession.get_current_game() == Constants.n_simple: if self.id_in_group == 1: return Constants.proponente elif self.id_in_group == 2: return Constants.respondente elif self.subsession.get_current_game( ) == Constants.n_empresa_trabajador: if self.id_in_group == 1: return Constants.empresa elif self.id_in_group == 2: return Constants.trabajador def avatarb64(self): if not self.avatar: return Constants.default_avatar_b64 if not hasattr(self, "_avatarb64"): path = os.path.join(settings.BASE_DIR, "participants_conf", self.avatar) with open(path) as fp: self._avatarb64 = "data:image/{};base64,{}".format( self.avatar.rsplit(".", 1)[1].lower(), fp.read().encode("base64")) return self._avatarb64
class Session(ModelWithVars): class Meta: app_label = "otree" # if i don't set this, it could be in an unpredictable order ordering = ['pk'] _pickle_fields = ['vars', 'config'] config = models._PickleField(default=dict, null=True) # type: dict # label of this session instance label = models.CharField(max_length=300, null=True, blank=True, help_text='For internal record-keeping') experimenter_name = models.CharField( max_length=300, null=True, blank=True, help_text='For internal record-keeping') ready_for_browser = models.BooleanField(default=False) code = models.CharField( default=random_chars_8, max_length=16, # set non-nullable, until we make our CharField non-nullable null=False, unique=True, doc="Randomly generated unique identifier for the session.") mturk_HITId = models.CharField( max_length=300, null=True, blank=True, help_text='Hit id for this session on MTurk') mturk_HITGroupId = models.CharField( max_length=300, null=True, blank=True, help_text='Hit id for this session on MTurk') # since workers can drop out number of participants on server should be # greater than number of participants on mturk # value -1 indicates that this session it not intended to run on mturk mturk_num_participants = models.IntegerField( default=-1, help_text="Number of participants on MTurk") mturk_use_sandbox = models.BooleanField( default=True, help_text="Should this session be created in mturk sandbox?") archived = models.BooleanField( default=False, db_index=True, doc=("If set to True the session won't be visible on the " "main ViewList for sessions")) comment = models.TextField(blank=True) _anonymous_code = models.CharField(default=random_chars_10, max_length=10, null=False, db_index=True) _pre_create_id = models.CharField(max_length=255, db_index=True, null=True) def use_browser_bots(self): return self.participant_set.filter(is_browser_bot=True).exists() is_demo = models.BooleanField(default=False) _admin_report_app_names = models.TextField(default='') _admin_report_num_rounds = models.CharField(default='', max_length=255) num_participants = models.PositiveIntegerField() def __unicode__(self): return self.code @property def participation_fee(self): '''This method is deprecated from public API, but still useful internally (like data export)''' return self.config['participation_fee'] @property def real_world_currency_per_point(self): '''This method is deprecated from public API, but still useful internally (like data export)''' return self.config['real_world_currency_per_point'] def is_for_mturk(self): return (not self.is_demo) and (self.mturk_num_participants > 0) def get_subsessions(self): lst = [] app_sequence = self.config['app_sequence'] for app in app_sequence: models_module = otree.common_internal.get_models_module(app) subsessions = models_module.Subsession.objects.filter( session=self).order_by('round_number') lst.extend(list(subsessions)) return lst def get_participants(self): return list(self.participant_set.order_by('id_in_session')) def mturk_requester_url(self): subdomain = 'requestersandbox' if self.mturk_use_sandbox else 'requester' return "https://{}.mturk.com/mturk/manageHITs".format(subdomain) def mturk_worker_url(self): # different HITs # get the same preview page, because they are lumped into the same # "hit group". This is not documented, but it seems HITs are lumped # if a certain subset of properties are the same: # https://forums.aws.amazon.com/message.jspa?messageID=597622#597622 # this seems like the correct design; the only case where this will # not work is if the HIT was deleted from the server, but in that case, # the HIT itself should be canceled. # 2018-06-04: # the format seems to have changed to this: # https://worker.mturk.com/projects/{group_id}/tasks?ref=w_pl_prvw # but the old format still works. # it seems I can't replace groupId by hitID, which i would like to do # because it's more precise. subdomain = "workersandbox" if self.mturk_use_sandbox else 'www' return "https://{}.mturk.com/mturk/preview?groupId={}".format( subdomain, self.mturk_HITGroupId) def advance_last_place_participants(self): # django.test takes 0.5 sec to import, # if this is done globally then it adds to each startup # it's only used here, and often isn't used at all. # so best to do it only here # it gets cached import django.test client = django.test.Client() participants = self.get_participants() # in case some participants haven't started unvisited_participants = [] for p in participants: if p._index_in_pages == 0: unvisited_participants.append(p) client.get(p._start_url(), follow=True) if unvisited_participants: # that's it -- just visit the start URL, advancing by 1 return last_place_page_index = min([p._index_in_pages for p in participants]) last_place_participants = [ p for p in participants if p._index_in_pages == last_place_page_index ] for p in last_place_participants: try: current_form_page_url = p._current_form_page_url if current_form_page_url: resp = client.post( current_form_page_url, data={ constants_internal.timeout_happened: True, constants_internal.admin_secret_code: ADMIN_SECRET_CODE }, follow=True) # not sure why, but many users are getting HttpResponseNotFound if resp.status_code >= 400: msg = ('Submitting page {} failed, ' 'returned HTTP status code {}.'.format( current_form_page_url, resp.status_code)) content = resp.content if len(content) < 600: msg += ' response content: {}'.format(content) raise AssertionError(msg) else: # it's possible that the slowest user is on a wait page, # especially if their browser is closed. # because they were waiting for another user who then # advanced past the wait page, but they were never # advanced themselves. start_url = p._start_url() resp = client.get(start_url, follow=True) except: logging.exception("Failed to advance participants.") raise # do the auto-advancing here, # rather than in increment_index_in_pages, # because it's only needed here. channels.Group('auto-advance-{}'.format(p.code)).send( {'text': json.dumps({'auto_advanced': True})}) def get_room(self): from otree.room import ROOM_DICT try: room_name = RoomToSession.objects.get(session=self).room_name return ROOM_DICT[room_name] except RoomToSession.DoesNotExist: return None def _get_payoff_plus_participation_fee(self, payoff): '''For a participant who has the given payoff, return their payoff_plus_participation_fee Useful to define it here, for data export ''' return (self.config['participation_fee'] + payoff.to_real_world_currency(self)) def _set_admin_report_app_names(self): admin_report_app_names = [] num_rounds_list = [] for app_name in self.config['app_sequence']: models_module = otree.common_internal.get_models_module(app_name) app_label = get_app_label_from_name(app_name) try: get_template('{}/AdminReport.html'.format(app_label)) admin_report_app_names.append(app_name) num_rounds_list.append(models_module.Constants.num_rounds) except TemplateDoesNotExist: pass self._admin_report_app_names = ';'.join(admin_report_app_names) self._admin_report_num_rounds = ';'.join( str(n) for n in num_rounds_list) def _admin_report_apps(self): return self._admin_report_app_names.split(';') def _admin_report_num_rounds_list(self): return [int(num) for num in self._admin_report_num_rounds.split(';')] def has_admin_report(self): return bool(self._admin_report_app_names)
class Player(otree.models.BasePlayer): # <built-in> group = models.ForeignKey(Group, null=True) subsession = models.ForeignKey(Subsession) # </built-in> gender = models.CharField( max_length=255, choices=['Female', 'Male', 'Other', 'Decline'], verbose_name="What gender do you identify with?", widget=widgets.RadioSelect()) age = models.CharField( max_length=255, choices=[ "under 18 years old", "18-20 years old", "21-22 years old", "23-25 years old", "over 25 years old"], verbose_name="What is your age?", widget=widgets.RadioSelect()) ethnicity = models.TextField( widget=widgets.HiddenInput(), verbose_name="Which of the following BEST describes your ethnic background? Please TICK ALL THAT APPLY.") education_level = models.CharField( verbose_name=( "What is the highest degree or level of school you have completed?" " If currently enrolled, highest degree received."), max_length=255, widget=widgets.RadioSelect(), choices=[ "High school graduate, diploma or the equivalent", "Some college credit, no degree", "Trade/technical/vocational training", "Bachelors degree", "Masters degree", "Other"]) marital_status = models.CharField( verbose_name="What is your marital status?", choices=[ "Single, never married", "Married or domestic partnership", "Other"], max_length=255, widget=widgets.RadioSelect()) employment_status = models.CharField( verbose_name="What is your current employment status?", widget=widgets.RadioSelect(), max_length=255, choices=["no-job", "part-time", "full-time"]) student_status = models.CharField( verbose_name=( "What is your current student status at the University of Guelph?"), widget=widgets.RadioSelect(), max_length=255, choices=["part-time", "full-time", "co-op", "other"]) enrolled_type = models.CharField( verbose_name=( "What type of program are you enrolled in at the University of Guelph?"), widget=widgets.RadioSelect(), max_length=255, choices=["undergraduate degree", "graduate degree", "other"]) major_type = models.CharField( verbose_name=( "Which College at the University of Guelph are you registered with?"), widget=widgets.RadioSelect(), max_length=255, choices=[" College of Business and Economics", "College of Physical and Engineering Science", "College of Social and Applied Human Sciences","Ontario Agricultural College","Other"]) class_type = models.CharField( verbose_name=( "How many Business and Economics classes have you taken so far (including those you are currently taking)?"), widget=widgets.RadioSelect(), max_length=255, choices=["0","Between 1 and 2", "Between 3 and 5", "Between 6 and 10", "More than 10"]) auction_buy = models.CharField( verbose_name="Do you typically use auctions to buy goods?", widget=widgets.RadioSelect(), max_length=255, choices=["never", "occasionally", "frequently"]) auctions_experience = models.CharField( verbose_name="How would you rate you previous experience with auctions?", widget=widgets.RadioSelect(), max_length=255, choices=[ "no previous experience with auctions", "occasional use of auctions", "frequent use of auctions"]) competitive_type = models.CharField( verbose_name=( "Evaluate the following statement: `When I play games for enjoyment, winning is very important to me' "), widget=widgets.RadioSelect(), max_length=255, choices=["I strongly disagree", "I disagree", "I neither disagree or agree", "I agree", "I strongly agree"]) gamble = models.PositiveIntegerField(min=1, max=6)
class Session(ModelWithVars): class Meta: # if i don't set this, it could be in an unpredictable order ordering = ['pk'] app_label = "otree" config = models.JSONField( default=dict, null=True, doc=("the session config dict, as defined in the " "programmer's settings.py.")) # label of this session instance label = models.CharField(max_length=300, null=True, blank=True, help_text='For internal record-keeping') experimenter_name = models.CharField( max_length=300, null=True, blank=True, help_text='For internal record-keeping') code = models.RandomCharField( length=8, doc="Randomly generated unique identifier for the session.") time_scheduled = models.DateTimeField( null=True, doc="The time at which the session is scheduled", help_text='For internal record-keeping', blank=True) time_started = models.DateTimeField( null=True, doc="The time at which the experimenter started the session") mturk_HITId = models.CharField( max_length=300, null=True, blank=True, help_text='Hit id for this session on MTurk') mturk_HITGroupId = models.CharField( max_length=300, null=True, blank=True, help_text='Hit id for this session on MTurk') mturk_qualification_type_id = models.CharField( max_length=300, null=True, blank=True, help_text='Qualification type that is ' 'assigned to each worker taking hit') # since workers can drop out number of participants on server should be # greater than number of participants on mturk # value -1 indicates that this session it not intended to run on mturk mturk_num_participants = models.IntegerField( default=-1, help_text="Number of participants on MTurk") mturk_sandbox = models.BooleanField( default=True, help_text="Should this session be created in mturk sandbox?") archived = models.BooleanField( default=False, doc=("If set to True the session won't be visible on the " "main ViewList for sessions")) git_commit_timestamp = models.CharField( max_length=200, null=True, doc=( "Indicates the version of the code (as recorded by Git) that was " "used to run the session, so that the session can be replicated " "later.\n Search through the Git commit log to find a commit that " "was made at this time.")) comment = models.TextField(blank=True) _ready_to_play = models.BooleanField(default=False) _anonymous_code = models.RandomCharField(length=10) special_category = models.CharField( max_length=20, null=True, doc="whether it's a test session, demo session, etc.") # whether someone already viewed this session's demo links demo_already_used = models.BooleanField(default=False) # indicates whether a session has been fully created (not only has the # model itself been created, but also the other models in the hierarchy) ready = models.BooleanField(default=False) _pre_create_id = models.CharField(max_length=300, null=True) def __unicode__(self): return self.code @property def participation_fee(self): '''This method is deprecated from public API, but still useful internally (like data export)''' return self.config['participation_fee'] @property def real_world_currency_per_point(self): '''This method is deprecated from public API, but still useful internally (like data export)''' return self.config['real_world_currency_per_point'] @property def session_type(self): '''2015-07-10: session_type is deprecated this shim method will be removed eventually''' return self.config def is_open(self): return GlobalSingleton.objects.get().default_session == self def is_for_mturk(self): return (not self.is_demo()) and (self.mturk_num_participants > 0) def is_demo(self): return (self.special_category == constants_internal.session_special_category_demo) def subsession_names(self): names = [] for subsession in self.get_subsessions(): app_name = subsession._meta.app_config.name name = '{} {}'.format( otree.common_internal.app_name_format(app_name), subsession.name()) names.append(name) if names: return ', '.join(names) else: return '[empty sequence]' def get_subsessions(self): lst = [] app_sequence = self.config['app_sequence'] for app in app_sequence: models_module = otree.common_internal.get_models_module(app) subsessions = models_module.Subsession.objects.filter( session=self).order_by('round_number') lst.extend(list(subsessions)) return lst def delete(self, using=None): for subsession in self.get_subsessions(): subsession.delete() super(Session, self).delete(using) def get_participants(self): return self.participant_set.all() def payments_ready(self): for participants in self.get_participants(): if not participants.payoff_is_complete(): return False return True payments_ready.boolean = True def _create_groups_and_initialize(self): # if ppg is None, then the arrival time doesn't matter because # everyone is assigned to one big group. # otherwise, even in single-player games, you would have to wait # for other players to arrive # the drawback of this approach is that id_in_group is # predetermined, rather than by arrival time. # alternative design: # instead of checking ppg, we could also check if the game # contains a wait page # another alternative: # allow players to start even if the rest of the group hasn't arrived # but this might break some assumptions such as len(grp.get_players()) # also, what happens if you get to the next round before # another player has started the first? you can't clone the # previous round's groups for subsession in self.get_subsessions(): cond = (self.config.get('group_by_arrival_time') and subsession._Constants.players_per_group is not None) if cond: if subsession.round_number == 1: subsession._set_players_per_group_list() subsession._create_empty_groups() else: subsession._create_groups() subsession._initialize() subsession.save() self._ready_to_play = True # assert self is subsession.session self.save() def mturk_requester_url(self): if self.mturk_sandbox: requester_url = ( "https://requestersandbox.mturk.com/mturk/manageHITs") else: requester_url = "https://requester.mturk.com/mturk/manageHITs" return requester_url def mturk_worker_url(self): if self.mturk_sandbox: worker_url = ( "https://workersandbox.mturk.com/mturk/preview?groupId={}" ).format(self.mturk_HITGroupId) else: worker_url = ( "https://www.mturk.com/mturk/preview?groupId={}").format( self.mturk_HITGroupId) return worker_url def advance_last_place_participants(self): participants = self.get_participants() c = django.test.Client() # in case some participants haven't started some_participants_not_visited = False for p in participants: if not p.visited: some_participants_not_visited = True c.get(p._start_url(), follow=True) if some_participants_not_visited: # refresh from DB so that _current_form_page_url gets set participants = self.participant_set.all() last_place_page_index = min([p._index_in_pages for p in participants]) last_place_participants = [ p for p in participants if p._index_in_pages == last_place_page_index ] for p in last_place_participants: # what if current_form_page_url hasn't been set yet? resp = c.post(p._current_form_page_url, data={constants_internal.auto_submit: True}, follow=True) assert resp.status_code < 400 def build_session_user_to_user_lookups(self): subsession_app_names = self.config['app_sequence'] num_pages_in_each_app = {} for app_name in subsession_app_names: views_module = otree.common_internal.get_views_module(app_name) num_pages = len(views_module.page_sequence) num_pages_in_each_app[app_name] = num_pages for participant in self.get_participants(): participant.build_session_user_to_user_lookups( num_pages_in_each_app)
class Session(ModelWithVars): class Meta: app_label = "otree" # if i don't set this, it could be in an unpredictable order ordering = ['pk'] config = models.JSONField(default=dict, null=True) # type: dict vars = models.JSONField(default=dict) # type: dict # label of this session instance label = models.CharField(max_length=300, null=True, blank=True, help_text='For internal record-keeping') experimenter_name = models.CharField( max_length=300, null=True, blank=True, help_text='For internal record-keeping') ready = models.BooleanField(default=False) code = models.CharField( default=random_chars_8, max_length=16, # set non-nullable, until we make our CharField non-nullable null=False, unique=True, doc="Randomly generated unique identifier for the session.") time_scheduled = models.DateTimeField( null=True, doc="The time at which the session is scheduled", help_text='For internal record-keeping', blank=True) time_started = models.DateTimeField( null=True, doc="The time at which the experimenter started the session") mturk_HITId = models.CharField( max_length=300, null=True, blank=True, help_text='Hit id for this session on MTurk') mturk_HITGroupId = models.CharField( max_length=300, null=True, blank=True, help_text='Hit id for this session on MTurk') mturk_qualification_type_id = models.CharField( max_length=300, null=True, blank=True, help_text='Qualification type that is ' 'assigned to each worker taking hit') # since workers can drop out number of participants on server should be # greater than number of participants on mturk # value -1 indicates that this session it not intended to run on mturk mturk_num_participants = models.IntegerField( default=-1, help_text="Number of participants on MTurk") mturk_sandbox = models.BooleanField( default=True, help_text="Should this session be created in mturk sandbox?") archived = models.BooleanField( default=False, db_index=True, doc=("If set to True the session won't be visible on the " "main ViewList for sessions")) comment = models.TextField(blank=True) _anonymous_code = models.CharField(default=random_chars_10, max_length=10, null=False, db_index=True) _pre_create_id = models.CharField(max_length=300, db_index=True, null=True) use_browser_bots = models.BooleanField(default=False) # if the user clicks 'start bots' twice, this will prevent the bots # from being run twice. _cannot_restart_bots = models.BooleanField(default=False) _bots_finished = models.BooleanField(default=False) _bots_errored = models.BooleanField(default=False) _bot_case_number = models.PositiveIntegerField() is_demo = models.BooleanField(default=False) # whether SOME players are bots has_bots = models.BooleanField(default=False) def __unicode__(self): return self.code @property def participation_fee(self): '''This method is deprecated from public API, but still useful internally (like data export)''' return self.config['participation_fee'] @property def real_world_currency_per_point(self): '''This method is deprecated from public API, but still useful internally (like data export)''' return self.config['real_world_currency_per_point'] def is_for_mturk(self): return (not self.is_demo) and (self.mturk_num_participants > 0) def get_subsessions(self): lst = [] app_sequence = self.config['app_sequence'] for app in app_sequence: models_module = otree.common_internal.get_models_module(app) subsessions = models_module.Subsession.objects.filter( session=self).order_by('round_number') lst.extend(list(subsessions)) return lst def delete(self, using=None): for subsession in self.get_subsessions(): subsession.delete() super(Session, self).delete(using) def get_participants(self): return self.participant_set.all() def get_human_participants(self): return self.participant_set.filter(_is_bot=False) def _create_groups_and_initialize(self): # group_by_arrival_time code used to be here for subsession in self.get_subsessions(): subsession._create_groups() subsession.before_session_starts() subsession.save() def mturk_requester_url(self): if self.mturk_sandbox: requester_url = ( "https://requestersandbox.mturk.com/mturk/manageHITs") else: requester_url = "https://requester.mturk.com/mturk/manageHITs" return requester_url def mturk_worker_url(self): if self.mturk_sandbox: return ("https://workersandbox.mturk.com/mturk/preview?groupId={}" ).format(self.mturk_HITGroupId) return ("https://www.mturk.com/mturk/preview?groupId={}").format( self.mturk_HITGroupId) def advance_last_place_participants(self): # can't auto-advance bots, because that could break their # pre-determined logic # consider pros/cons of doing this participants = self.get_human_participants() # in case some participants haven't started unvisited_participants = [] for p in participants: if p._index_in_pages == 0: unvisited_participants.append(p) client.get(p._start_url(), follow=True) if unvisited_participants: # that's it -- just visit the start URL, advancing by 1 return last_place_page_index = min([p._index_in_pages for p in participants]) last_place_participants = [ p for p in participants if p._index_in_pages == last_place_page_index ] for p in last_place_participants: # what if first page is wait page? # that shouldn't happen, because then they must be # waiting for some other players who are even further back assert p._current_form_page_url try: resp = client.post(p._current_form_page_url, data={constants_internal.auto_submit: True}, follow=True) except: logging.exception("Failed to advance participants.") raise assert resp.status_code < 400 def build_participant_to_player_lookups(self): subsession_app_names = self.config['app_sequence'] views_modules = {} for app_name in subsession_app_names: views_modules[app_name] = ( otree.common_internal.get_views_module(app_name)) def views_module_for_player(player): return views_modules[player._meta.app_config.name] records_to_create = [] for participant in self.get_participants(): page_index = 0 for player in participant.get_players(): for View in views_module_for_player(player).page_sequence: page_index += 1 records_to_create.append( ParticipantToPlayerLookup( participant=participant, page_index=page_index, app_name=player._meta.app_config.name, player_pk=player.pk, url=reverse(View.url_name(), args=[participant.code, page_index]))) # technically could be stored at the session level participant._max_page_index = page_index participant.save() ParticipantToPlayerLookup.objects.bulk_create(records_to_create) def get_room(self): from otree.room import ROOM_DICT try: room_name = RoomToSession.objects.get(session=self).room_name return ROOM_DICT[room_name] except RoomToSession.DoesNotExist: return None
class Player(BasePlayer): transcribed_text = models.TextField() levenshtein_distance = models.PositiveIntegerField()