実験プログラム製作で気になった点をまとめてみた

# (はじめに)備忘録です
oTreeの実験プログラムを作っていると，よく分からないことがたくさん出てくると思います．今回はその中でも，公式ドキュメントを見たり，人のブログを見たりするなかで，私が個人的に気になった点をまとめています．
誰かのお役に立てるのであれば幸いです．また，間違っているところがありましたら，指摘していただけると嬉しいです．

## 実験プログラムを作るのに使うべきソフトは？
PyCharm，VS Code, Atomなど，世の中にはたくさんのソフトがあります．この中で，一番便利で汎用性があると私が考えるのはVS Codeこと，**Visual Studio Code**です．

https://azure.microsoft.com/ja-jp/products/visual-studio-code/

拡張機能を追加することによって，PythonだけでなくPHPや様々な言語のファイルを扱うことができるのが特徴です．また，ポートフォワード機能を備えているので，SSHでファイルを編集した後のチェックが楽になります．基本的に，これがあれば他のソフトはいらないと思います．

## requirements.txtって必要なの？
:::note info
自分のパソコンや「VPS」上で稼働させるときは不要です．(pip install -U ⚫⚫で用意することができるため)
:::

startprojectコマンドでoTreeのフォルダを作った後に開いてみると，requirements.txtがありますね．これが何のためにあるのかというと，**Azure App service**というPaaS(Platform as a Service)のサービスを使うときに必要だからです．これがあることによって，Azure側で必要なライブラリーを準備してくれます．(Herokuは有料になるので辞めました)

```:ファイル構造
project_name/
　├ _pycache_
　├ _template/
　│　　└ your_app_name/
　│　　　　└ base.html/
　├ _static
　│　├ your_app_name/
　│　│　└ aaaaa.css
　│　│　└ aaaaa.js
　│　└ global/
　│　　　└ xxxxx.css
　│　　　└ xxxxx.js
　├ your_app_name/
　│　├ _init_.py
　│　├ Introduction.html
　│　└ Instruction.html
　├ settings.py
　├ requirements.txt 　←-----------このファイルです
　│
```
## タダで実験したい時はどうすれば良い？
大学に所属している人は、Azure App serviceを使いましょう。簡単かつ無料枠の範囲内ならクレカなしでサーバーを構築できます。(しかも、SSL証明書が標準でついてくる)


|  | 内容 |
|:-:|:-:|
|ステップ１|**Azure for Students**の登録(必要なら)
|ステップ２|Visual Studio CodeにAzureの拡張機能をインストール
|ステップ３|[公式ドキュメント](https://docs.microsoft.com/ja-jp/azure/app-service/quickstart-python?tabs=flask%2Cwindows%2Cvscode-aztools%2Cvscode-deploy%2Cdeploy-instructions-azportal%2Cterminal-bash%2Cdeploy-instructions-zip-azcli)の通りにポチポチ
|ステップ４|Azure Portalで**PORT**の設定(8000で接続可能に変更)
|ステップ５|Azure Portalで**oTreeコマンド**の設定(AUTH_LEVELなど)
|ステップ６|Azure Portalで**スタートアップコマンド**の設定


詳しいことは下の図を参考にしてください。
![Azure.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2511720/119a93bb-bdcb-446e-fd15-074d6969c464.png)

![app settings.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2511720/4950f7bd-0431-e164-6eda-d0726b80f16a.png)
![スクリーンショット 2022-10-05 13.44.04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2511720/95cd20fc-4d63-022d-e369-77720374b04c.png)

## oTreeが起動しない(Python自体反応しない)ときは？
oTreeが起動しない理由は様々ですが、ある程度は予測できます。

:::note warn
１．setting.pyに記載されているアプリが見つからない.　→　使うアプリだけに絞っているか，見直しましょう.
２．プログラムに全角半角が混在していて，呼び出せないものがある.　→　HTMLファイルやinit.pyを確かめましょう.
３．**Microsoft Store**からPythonをダウンロード＆インストールした.　→　**何らかのバグでPATHが消えている**ので，再インストールしましょう.
４．Pythonの公式サイトからダウンロードしてインストールした.　→　**ADD PATHのチェック**をつけ忘れた可能性が高いので，自力で設定しましょう.
:::

環境設定のやり方は、このサイトが分かりやすいと思います。

https://www.javadrive.jp/python/install/index3.html


## ベースとなっているhtmlファイルの中身が知りたい！
[ソースコード](https://github.com/oTree-org/otree-core/blob/master/otree/templates/otree/Base.html)を見ていただくことで分かると思いますが，それぞれの場所にパズルのような形で組み込んでいく作りになっています．最初は意味が分からなくてよく困りました．
(関係ない話ですが，jqueryがv3.2.1なのはどうなんでしょうね... 最新版はv3.6だそうです)

:::note info
全体としては　タイトル→CSSやJSファイル→本文　になっていますね．
:::

>```django:Base.html
><html>
><head>
>    <title>{% block head_title %}{% endblock %}</title>
>    {% block internal_styles %}
>        <meta name="viewport" content="width=device-width, initial-scale=1.0">
>        <link rel="stylesheet" href="{% static 'bootstrap5/css/bootstrap.min.css' %}">
>        <link rel="stylesheet" href="{% static 'otree/css/theme.css' %}">
>        {% comment %}
>        this actually belongs in internal_scripts, but we are in the process
>        of deprecating it, so some people's apps might still rely on jQuery being
>        available within the content block.
>        {% endcomment %}
>        <script src="{% static 'otree/js/jquery-3.2.1.min.js' %}"></script>
>    {% endblock %}
>    {# these blocks are for public API #}
>    {% block global_styles %}{% endblock %}
>    {% block app_styles %}{% endblock %}
>    {% block styles %}{% endblock %}
></head>
><body>
>{% block body_main %}{% endblock %}
>
>{% block internal_scripts %}
>  <script src="{% static 'otree/js/internet-explorer.js' %}"></script>
>  <script src="{% static 'otree/js/reconnecting-websocket-iife.min.js' %}"></script>
>    {% block bootstrap_scripts %}
>    <script src="{% static 'bootstrap5/js/bootstrap.bundle.min.js' %}"></script>
>    {% endblock %}
>    <script src="{% static 'otree/js/common.js' %}"></script>
>{% endblock %}
>{% block live %}{% endblock %}
>{# these blocks are for public API #}
>{% block global_scripts %}{% endblock %}
>{% block app_scripts %}{% endblock %}
>{% block scripts %}{% endblock %}
></body>
></html>
>```

## 全画面縦スクロールの実装で困った時は？
SwiperJSを活用して縦スクロール型のページを作成する場合、スクロール時の表示崩れに悩まされることになります。この事象の原因はBootstrapのCSSにあるため、CSSを修正する必要があります。
```css:css
 .container, .container-fluid, .container-lg, .container-md, .container-sm, .container-xl, .container-xxl {
    width: 100%;
    /*padding-right: var(--bs-gutter-x, .75rem);*/
    /*padding-left: var(--bs-gutter-x, .75rem);*/
    /*margin-right: auto;*/
    /*margin-left:auto*/
}


@media (max-width: 428px) {
    .container, .container-sm {
        max-width:100%
    }
}


@media (min-width: 768px) {
    .container, .container-md, .container-sm {
        max-width:100%
    }
}

@media (min-width: 992px) {
    .container, .container-lg, .container-md, .container-sm {
        max-width:100%
    }
}

@media (min-width: 1200px) {
    .container, .container-lg, .container-md, .container-sm, .container-xl {
        max-width:100%
    }
}

@media (min-width: 1400px) {
    .container, .container-lg, .container-md, .container-sm, .container-xl, .container-xxl {
        max-width: 100%;
    }
}
```

## 味気ない待機中の画面をおしゃれにしたい！
複数人参加型の実験では，他の被験者を待ってもらう必要があるときに待機ページが表示されます．余計なものが表示されないシンプルでいい感じなデザインですよね．しかし，このデザインを変えたいと思う人もいるでしょう(私です)．これは，html/js/cssファイルの配置と_init_.pyの編集で解決できます．

:::note information
＿init＿.pyを編集して独自のカスタムページを表示しよう
:::

https://otree.readthedocs.io/ja/latest/misc/advanced.html#customize-wait-page

余談ですが、最近調べていて良いなと思ったのが、FontawsomeとVideo.js, swiperJS, MathJaxです。このあたりを組み合わせるとモダンな実験ページを作ることができるんじゃないでしょうか。

## 実験の画面を良い感じにしたい!
一例ではありますが、以下のようにnextボタンやタイトルを独自のものに置き換えることで、見栄えが良くなります。

![otree-sample.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2511720/88969f80-38ba-1acd-077a-a03618eb1bf4.png)


## Vue.jsの構文とoTreeのタグが被る問題を解決するには？
oTreeのタグを変更することは難しいので、Vueの方を変更しましょう。delimitersで変更できます。
```javascript:Vue.js ver3.x
const Vue_otreegame = {
    template: 
    `<div id="app">
    </div>`,
    setup() {
    },
    delimiters: ['[[', ']]']
  }
Vue.createApp(Vue_otreegame).mount('#Vuegame-with-oTree')
```


## 答え合わせ機能を実装するには？
OXクイズのような正解判定の仕組みを用意するには、ライブページの機能を使います。
```python:init.py
    def live_method(player: Player, data):
        if ('player_input1' in data):
            if data['player_input1'] != C.ANSWER: #「!マーク」で命題の否定
                return {player.id_in_group: dict(error="答えが違います")}


```


## 動画を使ったページで気をつけるべきこと
単純に< video >タグで動画を埋め込んでも良いですが、ネットワークに負荷がかかることや被験者の負担増加につながることもあるため、できる限り工夫した方が良いです。こだわりが無ければVideo.jsを使いましょう。動画は、あらかじめffmpegなどで細かくしておいてください。
```html:動画ありの場合
{{ block content }}
<script src="https://cdnjs.cloudflare.com/ajax/libs/video.js/7.20.2/video.min.js" integrity="sha512-8GgbC485o4ayA08dtXE6MEtCvm9A8yF3/iZetKydmJabl7MFKhtvM0wUQOKzm1UyVV3S8jNxbLBfVpeF4HDqkw==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/video.js/7.20.2/video-js.min.css" integrity="sha512-kCCb9I/QM9hw+hm+JlN2ounNo2bRFZ4r9guSBv0BYk7RezWV2H8eI1unYnpJrU8+2g773WW1qNG+fSQ0X7M3Tg==" crossorigin="anonymous" referrerpolicy="no-referrer" />
<div class="video-area">
	<video-js id=explain-video width=600 height=480 class="vjs-default-skin" controls>
	  	<source src="{{static '$app_name/$video_name.m3u8'}}" type="application/x-mpegURL">
	</video-js>
</div>
```

## 他のサービスと連携したい！
Googleフォームを使ってアンケートを取るとき、oTreeのsession codeとparticipant codeをフォームに連携することで回答状況をより正確に把握することができます。使用方法は、htmlファイルにURLとタグを挿入するだけです。
```html:HTMLファイル
<a href="事前入力したFormのURL={{session.code}}-{{participant.code}}"></a>
```

また、init.pyに連携用のコードを用意することで外部との通信も可能になります。下のコードはGoogleスプレッドシートから内容を抽出し、表示するためにplayer.○○に格納するというものです。以前の実験参加者に対し、追加のアンケート調査を実施するときに活用しました。
```python:__init__.py
class APIPage(Page):
    def before_next_page(player:Player, timeout_happened):
        from oauth2client.service_account import ServiceAccountCredentials
        import gspread
        scope =['https://spreadsheets.google.com/feeds', 'https://www.googleapis.com/auth/drive']
        credentials = ServiceAccountCredentials.from_json_keyfile_name(C.JSONDATA, scope)
        SPREADSHEET_KEY = "ここにURIの一部をコピペする"
        spreadsheet = gspread.authorize(credentials)
        sheet = spreadsheet.open_by_key(SPREADSHEET_KEY).worksheet("oTree")
        #search name on Spreadsheet
        expt_num = sheet.find(player.your_num)
        row_value = expt_num.row
        rowdata_list = sheet.row_values(row_value)
        player.choice = rowdata_list[31]
        player.name = rowdata_list[11]

        return player.choice, player.name
```
## データベースはSQLiteでもいいの？
Room機能を使って2000件のURLを準備したところ、データベースが消える不具合がみられました。実験本番ではPostgreSQLを使うことを強く推奨します。

## 終わらないエラー地獄はどうしたら抜け出せる？
:::note warn
大抵，というかほぼ確実に_init_.pyもしくはhtmlファイルの記述に問題があると思います
:::

例えば，関数1と関数2を行ったり来たりするように作っていたり，関数の階層を深くしすぎてPythonの**最大再帰数**(呼び出し回数の上限)を突破していたり，関数が定義されていないなどが考えられます．また，文字の大文字・小文字やスペース，記号の不一致によって，指定された関数が呼び出せないこともあります．
HTMLファイルでは，指定されたCSS/JSファイルが読み込めない場合や{ }で指定された関数が呼び出せない場合にエラーが発生します．CSSやJSファイルは「_static」フォルダに配置し，PATHを間違えないようにしましょう．

### 推奨はできませんが...
一応，再帰回数の上限を引き上げるという邪道な方法もあるので紹介しておきますと，sys.setrecursionlimit()で上限を緩和できます．ただし，クラッシュする可能性が出てくるので必要最小限で行ってください．
```python:再帰回数の上限緩和
sys.setrecursionlimit(2500)
```
[Pythonの公式ドキュメント](https://docs.python.org/ja/3/library/sys.html#sys.setrecursionlimit)にもこのような記載があります．
>sys.setrecursionlimit(limit)¶
>Python インタプリタの、スタックの最大の深さを limit に設定します。この制限は Python プログラムが無限に再帰し、C スタックがオーバーフローしてクラッシュすることを防止するために設けられています。
>
>limit の最大値はプラットフォームによって異なります。深い再帰処理が必要な場合にはプラットフォームがサポートしている範囲内でより大きな値を指定することができますが、この値が大きすぎればクラッシュするので注意が必要です。

## 番外編） Ubuntuのアップデートをしたらネットに接続できなくなった
oTreeで実験するために、Ubuntu+nginx+oTree+certbotみたいな組み合わせを用意する方がいると思います。そこで注意したいのがNICの問題です。カーネルをアップデートすることで相性問題が発生し、ifconfigのNICリストから消えて、外部との通信が一切できない状態になることがあります。
大抵の場合は、次の手順で復活できます。
```bash:ifconfigを使った復旧方法
#まずはifconfigで状況確認して問題の切り分け
ifconfig

#lo: flags=73...の項目しか表示されていない場合は、PCIの認識が正常か確認する必要あり。
lspci | grep 'Ethernet'

#01:00.0 Ethernet controller:~みたいなものが表示された場合は認識できているので、lshwの機能を使ってlogical nameを割り出す。
sudo lshw -c Network

#表示された logical name: xxxxx の部分をメモしてifconfig upを実行する
sudo ifconfig xxxxx up

#(DHCP環境)このままではIPアドレスやサブネットの更新ができていないので、更新する。
sudo dhclient  -r
sudo dhclient
```