from otree.api import * import random from django.db import models as djmodels doc = """ Your app description """ class Constants(BaseConstants): name_in_url = 'pggfg' players_per_group = 3 num_others_per_group = players_per_group - 1 num_rounds = 6 instructions_template = 'pggfg/Instructions.html' endowment = 20 efficiency_factor = 1.5 MPCR = efficiency_factor / players_per_group punishment_endowment = 6 punishment_factor = 2 example_payoff = 30 * efficiency_factor yen_rate = 0.3 q1_a = 3 # conditionGSI =1 from django.db.models import Q, F class Subsession(BaseSubsession): conditionGSI = models.IntegerField() def get_game(self): self.conditionGSI = self.session.config['conditionGSI'] def creating_session(self): ps = [] for p in self.get_players(): others_r = random.sample(p.get_others_in_group(), Constants.num_others_per_group) for o in others_r: ps.append(Punishment(sender=p, receiver=o, )) Punishment.objects.bulk_create(ps) class Group(BaseGroup): total_contribution = models.IntegerField() average_contribution = models.FloatField() individual_share = models.FloatField() individual_share_presen1 = models.FloatField() # individual_share = models.CurrencyField() min_contribution = models.FloatField() def set_pd_payoffs(self): contributions = [ p.contribution for p in self.get_players() if p.contribution != None ] self.total_contribution = sum([p.contribution for p in self.get_players()]) self.min_contribution = min(contributions) self.individual_share = ( self.total_contribution * Constants.efficiency_factor / Constants.players_per_group) # self.min_contribution * Constants.efficiency_factor) self.average_contribution = (self.total_contribution / Constants.players_per_group) self.individual_share_presen1 = round(self.individual_share, 1) for p in self.get_players(): p.payoff_before = (Constants.endowment - p.contribution) + self.individual_share p.profit_before = (Constants.endowment - p.contribution) + self.individual_share for p in self.get_players(): if self.average_contribution < 10 and self.subsession.conditionGSI == 1: p.Ipunishment = -2 p.punish_pat = 1 elif self.min_contribution < 10 and self.subsession.conditionGSI == 2: p.Ipunishment = -2 p.punish_pat = 2 elif p.contribution < 10 and self.subsession.conditionGSI == 3: p.Ipunishment = -2 p.punish_pat = 3 elif self.average_contribution >= 10 and self.subsession.conditionGSI == 1: p.Ipunishment = 0 p.punish_pat = 4 elif self.min_contribution >= 10 and self.subsession.conditionGSI == 2: p.Ipunishment = 0 p.punish_pat = 5 elif p.contribution >= 10 and self.subsession.conditionGSI == 3: p.Ipunishment = 0 p.punish_pat = 6 for p in self.get_players(): p.pd_payoff = p.payoff_before + p.Ipunishment p.pd_profit = p.profit_before + p.Ipunishment p.set_punishment_endowment() for p in self.get_players(): p.pd_profit_presen = round(p.pd_profit, 1) p.Ipunishment_presen = p.Ipunishment * -1 # p.Yen_profit = p.participant.pd_payoff * Constants.yen_rate # for p in self.get_players(): # p.pd_payoff = sum([+ Constants.endowment, # - p.contribution, # + self.individual_share, # ]) # p.pd_profit = sum([+ Constants.endowment, # - p.contribution, # + self.individual_share, # ]) # p.set_punishment_endowment() # 元々のpayoff計算 # def set_pd_payoffs(self): # self.total_contribution = sum([p.contribution for p in self.get_players()]) # self.average_contribution = self.total_contribution / Constants.players_per_group # self.individual_share = self.total_contribution * Constants.efficiency_factor / Constants.players_per_group # for p in self.get_players(): # p.pd_payoff = sum([+ Constants.endowment, # - p.contribution, # + self.individual_share, # ]) # p.set_punishment_endowment() def set_punishments(self): for p in self.get_players(): p.set_punishment() for p in self.get_players(): p.set_payoff() for p in self.get_players(): p.profit_presen = round(p.profit, 1) class Player(BasePlayer): # contribution = models.IntegerField(verbose_name='あなたはいくら提供しますか?', # choices=range(0, Constants.endowment+1), # initial=None) Total_DropPC = models.IntegerField() contribution = models.PositiveIntegerField( min=0, max=Constants.endowment, doc="""The amount contributed by the player""", label="あなたはいくら提供しますか?(0点-20点)".format(Constants.endowment) ) punishment_sent = models.IntegerField() punishment_received = models.IntegerField() pd_payoff = models.CurrencyField(doc='to store payoff from contribution stage') punishment_endowment = models.IntegerField(initial=0, doc='punishment endowment') profit_before = models.FloatField() pd_profit = models.FloatField() profit_presen = models.FloatField() pd_profit_presen = models.FloatField() payoff_before = models.CurrencyField() Ipunishment = models.IntegerField() total_profit = models.FloatField() total_payoff = models.CurrencyField() punish_pat = models.IntegerField() Yen_profit = models.CurrencyField() Ipunishment_presen = models.IntegerField() dropWP = models.IntegerField() dropGS = models.IntegerField() dropPC = models.IntegerField(initial=0) dropPP = models.IntegerField() dropPR = models.IntegerField() # pd_profit = models.FloatField() profit = models.FloatField() # profit_presen = models.FloatField() def set_payoff(self): self.payoff = self.pd_payoff - self.punishment_sent - self.punishment_received self.profit = self.pd_profit - self.punishment_sent - self.punishment_received def set_punishment_endowment(self): assert self.pd_payoff is not None, 'You have to set pd_payoff before setting punishment endowment' # self.punishment_endowment = min(self.pd_payoff, Constants.punishment_endowment) self.punishment_endowment = Constants.punishment_endowment def set_punishment(self): self.punishment_sent = sum([i.amount for i in self.punishments_sent.all()]) self.punishment_received = sum( [i.amount for i in self.punishments_received.all()]) * Constants.punishment_factor q11 = models.IntegerField(initial=None, choices=[ [1, '3点'], [2, '5点'], [3, '8点'], ], verbose_name='あなたは、他メンバーの減額に合計2点使用し、他メンバーからあなたに対し合計3点減額に使用されたとします。あなたは、減額ステージでいくら失うことになりますか?', widget=widgets.RadioSelectHorizontal()) q12 = models.IntegerField(initial=None, choices=[ [1, '3点'], [2, '5点'], [3, '8点'], ], verbose_name='あなたは、他メンバーの減額に合計2点使用し、他メンバーからあなたに対し合計3点減額に使用されたとします。あなたは、減額ステージでいくら失うことになりますか?', widget=widgets.RadioSelectHorizontal()) q13 = models.IntegerField(initial=None, choices=[ [1, '3点'], [2, '5点'], [3, '8点'], ], verbose_name='あなたは、他メンバーの減額に合計2点使用し、他メンバーからあなたに対し合計3点減額に使用されたとします。あなたは、減額ステージでいくら失うことになりますか?', widget=widgets.RadioSelectHorizontal()) from django import forms # from .models import Player, Punishment, Constants from django.forms import BaseInlineFormSet, ValidationError, inlineformset_factory from django.core.validators import MaxValueValidator, MinValueValidator class Punishment(djmodels.Model): sender = djmodels.ForeignKey('to=Player', related_name='punishments_sent', on_delete=djmodels.CASCADE) receiver = djmodels.ForeignKey('to=Player', related_name='punishments_received', on_delete=djmodels.CASCADE) amount = models.IntegerField(min=0) class PunishmentForm(forms.ModelForm): amount = forms.IntegerField(min_value=0, required=True, widget=forms.NumberInput(attrs={'required': True})) class Meta: model = Punishment fields = ['amount'] def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) maxval = self.instance.sender.punishment_endowment self.fields['amount'].validators += [MaxValueValidator(maxval)] self.fields['amount'].widget.attrs['max'] = maxval class PunishmentFormset(BaseInlineFormSet): non_field_errors = None def clean(self): super().clean() if any(self.errors): self.non_field_errors = 'Please check your answers' return amounts = [] punishment_endowment = self.instance.punishment_endowment for form in self.forms: amounts.append(form.cleaned_data['amount']) if sum(amounts) > punishment_endowment: self.non_field_errors = "「合計で {endowment} ポイント以上、使用することはできません!」".format( endowment=punishment_endowment) raise ValidationError(self.non_field_errors) PFormset = inlineformset_factory(Player, Punishment, formset=PunishmentFormset, form=PunishmentForm, extra=0, can_delete=False, fk_name='sender', fields=['amount']) # the view to get a list of all sessions class AllSessionsList(TemplateView): template_name = 'pggfg_export/all_session_list.html' url_name = 'pggfg_sessions_list' url_pattern = r'^pggfg_sessions_list/$' display_name = 'Exporting punishment data from PGGFG' def get(self, request, *args, **kwargs): candidates_list = Punishment.objects.filter(amount__isnull=False).values_list('sender__session', flat=True) all_sessions = Session.objects.filter(id__in=candidates_list) pggfg_sessions = [i for i in all_sessions if 'pggfg' in i.config['app_sequence']] return render(request, self.template_name, {'sessions': pggfg_sessions}) class ListPunishmentsView(ListView): template_name = 'pggfg_export/punishments_list.html' url_name = 'punishments_list' url_pattern = r'^session/(?P[a-zA-Z0-9_-]+)/punishments/$' model = Punishment context_object_name = 'punishments' def get_queryset(self): session_code = self.kwargs['pk'] return Punishment.objects.filter(sender__session__code=session_code, amount__isnull=False) class PunishmentCSVExport(TemplateView): template_name = 'pggfg_export/punishments.txt' url_name = 'punishments_export' url_pattern = r'^session/(?P[a-zA-Z0-9_-]+)/punishments_export/$' response_class = HttpResponse content_type = 'text/csv' def get(self, request, *args, **kwargs): ... response = HttpResponse(content_type='text/csv') session_code = self.kwargs['pk'] filename = '{}_punishment_data.csv'.format(session_code) response['Content-Disposition'] = 'attachment; filename="{}"'.format(filename) punishments = Punishment.objects.filter(sender__session__code=session_code, amount__isnull=False) t = loader.get_template(self.template_name) c = { 'punishments': punishments, } response.write(t.render(c)) return response # return render(request, self.template_name, {'sessions': all_sessions }) from . import models from ._builtin import Page, WaitPage from otree.api import Currency as c, currency_range from .forms import PFormset from .models import Constants, Player, Punishment as PunishmentModel from otree.constants import timeout_happened import random import random class WaitPage1(WaitPage): timeout_seconds = 60 # group_by_arrival_time = True def is_displayed(self): return self.round_number == 1 # def after_all_players_arrive(self): # self.subsession.get_game() def before_next_page(self): if self.timeout_happened: self.player.dropWP = 1 body_text = "グループが形成できるまでお待ち下さい。通常は5分以内にグループ人数がそろいますが、長い間そろわない場合、ここであきらめて、ヤフーの画面で「83472」と打ち込めば、30ポイントの基本参加謝礼を獲得して終わりにできます。ただし、その場合、取引で得られる50~130ポイントの追加謝礼を得る機会は失われます。" class GameStart(Page): timeout_seconds = 60 def is_displayed(self): return self.round_number == 1 def before_next_page(self): if self.timeout_happened: self.player.dropGS = 1 pass class CheckPGGPinststart(Page): # timeout_seconds = 30 def is_displayed(self): self.subsession.get_game() # self.player.id_in_group=1 return self.subsession.round_number == 1 class Intro(Page): template_name = 'pggfg/Introduction.html' def is_displayed(self): # self.player.id_in_group=1 return self.subsession.round_number == 1 class page11(Page): form_model = models.Player form_fields = ['q11'] def is_displayed(self): return self.subsession.round_number == 1 #The questions on this page are all about matching protocol and number of repetitions def error_message(self, values): if values['q11'] != 3 : return '回答が間違っています。【全ての説明を表示する/しない】を押して、確認してから回答し直してください。' class page1_ans1(Page): def is_displayed(self): return self.subsession.round_number == 1 class page12(Page): def is_displayed(self): if self.subsession.round_number == 1 and self.player.q11 != 3: return True return False form_model = models.Player form_fields = ['q12'] class page1_ans2(Page): def is_displayed(self): if self.subsession.round_number == 1 and self.player.q11 != 3: return True return False class page13(Page): def is_displayed(self): if self.subsession.round_number == 1 and self.player.q11 != 3 and self.player.q12 != 3: return True return False form_model = models.Player form_fields = ['q13'] class page1_ans3(Page): def is_displayed(self): if self.subsession.round_number == 1 and self.player.q11 != 3 and self.player.q12 != 3: return True return False class after_instP(WaitPage): def after_all_players_arrive(self): return True def is_displayed(self): return self.round_number == 1 body_text = "同じグループの3人全員がそろうまで、少々お待ちください。全員そろったら、取引に入ります。場合によっては(他の参加者の内容理解がゆっくりな場合)、5分以上待つ場合もあります。または、3人のうちで参加を途中で取りやめた人が出た場合、これ以上進まなくなってしまいます。長い間そろわない場合、ここであきらめて、ヤフーの画面で「92731」と打ち込めば、30ポイントの基本参加謝礼を獲得して終わりにできます。その場合、追加謝礼は先ほどまでの総獲得ポイント分のみとなりますので、ご了承ください。" #class after_instP(Page): # def is_displayed(self): # if self.subsession.round_number == 1: # return True # return False class CheckPGGPstart(Page): timeout_seconds = 30 def is_displayed(self): # self.player.id_in_group=1 return self.subsession.round_number == 1 class Contribute(Page): timeout_seconds = 40 form_model = 'player' form_fields = ['contribution'] timeout_submission = {'contribution': Constants.endowment} def before_next_page(self): if self.timeout_happened: self.player.dropPC = 1 class AfterContribWP(WaitPage): after_all_players_arrive = 'set_pd_payoffs' # body_text = "他のプレイヤーが終わるまで、しばらくお待ちください" # body_text = "他のプレイヤーが終わるまで、しばらくお待ちください。5分以上、この画面から進まない場合、システムエラーかマシントラブルだと思われます。その場合、ヤフーの画面で「42376」と打ち込み、50ポイントの基本参加謝礼を獲得して終わりにしてください。追加報酬はここまでの獲得額を元に算出し、1月下旬に「追加謝礼」のタスクを掲載いたします。" body_text = "他のプレイヤーが終わるまで、しばらくお待ちください。3分以上、この画面から進まない場合、システムエラーかマシントラブルだと思われますので、まずは「更新」を押してみてください。それでも進まない場合、ヤフーの画面で「42876」と打ち込んでください(実験開始から1時間が立つとヤフーの画面は閉じてしまうので、早めに打ち込んでください)。追加報酬は参加を取り辞めたところまでの獲得額を元に算出し、2週間以内に「追加謝礼」のタスクを掲載いたします。なお、ヤフーの画面で「42876」と打ち込んだ後でも、実験参加を継続すれば、その分の獲得額も追加報酬に加えられますが、1時間以上の参加になる可能性がありますので、ご理解の上参加を継続するかご判断ください。" class Results1(Page): """Players payoff: How much each has earned""" timeout_seconds = 30 def vars_for_template(self): players = self.player.get_others_in_group() random.shuffle(players) return dict(players=players, ) class Punishment(Page): timeout_seconds = 80 def vars_for_template(self): players = self.player.get_others_in_group() random.shuffle(players) return dict(players=players, ) def post(self): print(self.request.POST) return super().post() def get_formset(self, data=None): return PFormset(instance=self.player, data=data, ) def get_form(self, data=None, files=None, **kwargs): # here if this page was forced by admin to continue we just submit an empty form (with no formset data) # if we need this data later on that can create some problems. But that's the price we pay for autosubmission if data and data.get('timeout_happened'): return super().get_form(data, files, **kwargs) if not data: return self.get_formset() formset = self.get_formset(data=data) return formset def before_next_page(self): if self.timeout_happened: self.player.punishments_sent.all().update(amount=0) self.player.dropPP = 1 class AfterPunishmentWP(WaitPage): after_all_players_arrive = 'set_punishments' # body_text = "他のプレイヤーが終わるまで、しばらくお待ちください" # body_text = "他のプレイヤーが終わるまで、しばらくお待ちください。5分以上、この画面から進まない場合、システムエラーかマシントラブルだと思われます。その場合、ヤフーの画面で「42376」と打ち込み、50ポイントの基本参加謝礼を獲得して終わりにしてください。追加報酬はここまでの獲得額を元に算出し、1月下旬に「追加謝礼」のタスクを掲載いたします。" body_text = "他のプレイヤーが終わるまで、しばらくお待ちください。3分以上、この画面から進まない場合、システムエラーかマシントラブルだと思われますので、まずは「更新」を押してみてください。それでも進まない場合、ヤフーの画面で「42876」と打ち込んでください(実験開始から1時間が立つとヤフーの画面は閉じてしまうので、早めに打ち込んでください)。追加報酬は参加を取り辞めたところまでの獲得額を元に算出し、2週間以内に「追加謝礼」のタスクを掲載いたします。なお、ヤフーの画面で「42876」と打ち込んだ後でも、実験参加を継続すれば、その分の獲得額も追加報酬に加えられますが、1時間以上の参加になる可能性がありますので、ご理解の上参加を継続するかご判断ください。" class Results(Page): timeout_seconds = 40 ... class end_game(Page): def is_displayed(self): if self.subsession.round_number == Constants.num_rounds: return True return False page_sequence = [ # WaitPage1, # GameStart, CheckPGGPinststart, Intro, page11, page1_ans1, # page12, # page1_ans2, # page13, # page1_ans3, after_instP, CheckPGGPstart, Contribute, AfterContribWP, # Results1, Punishment, AfterPunishmentWP, Results, end_game, ]