(はじめに)備忘録です
oTreeの実験プログラムを作っていると,よく分からないことがたくさん出てくると思います.今回はその中でも,公式ドキュメントを見たり,人のブログを見たりするなかで,私が個人的に気になった点をまとめています.
誰かのお役に立てるのであれば幸いです.また,間違っているところがありましたら,指摘していただけると嬉しいです.
実験プログラムを作るのに使うべきソフトは?
PyCharm,VS Code, Atomなど,世の中にはたくさんのソフトがあります.この中で,一番便利で汎用性があると私が考えるのはVS Codeこと,Visual Studio Codeです.
拡張機能を追加することによって,PythonだけでなくPHPや様々な言語のファイルを扱うことができるのが特徴です.また,ポートフォワード機能を備えているので,SSHでファイルを編集した後のチェックが楽になります.基本的に,これがあれば他のソフトはいらないと思います.
requirements.txtって必要なの?
自分のパソコンや「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証明書が標準でついてくる)
内容 | |
---|---|
ステップ1 | Azure for Studentsの登録(必要なら) |
ステップ2 | Visual Studio CodeにAzureの拡張機能をインストール |
ステップ3 | 公式ドキュメントの通りにポチポチ |
ステップ4 | Azure PortalでPORTの設定(8000で接続可能に変更) |
ステップ5 | Azure PortalでoTreeコマンドの設定(AUTH_LEVELなど) |
ステップ6 | Azure Portalでスタートアップコマンドの設定 |
oTreeが起動しない(Python自体反応しない)ときは?
oTreeが起動しない理由は様々ですが、ある程度は予測できます。
1.setting.pyに記載されているアプリが見つからない. → 使うアプリだけに絞っているか,見直しましょう.
2.プログラムに全角半角が混在していて,呼び出せないものがある. → HTMLファイルやinit.pyを確かめましょう.
3.Microsoft StoreからPythonをダウンロード&インストールした. → 何らかのバグでPATHが消えているので,再インストールしましょう.
4.Pythonの公式サイトからダウンロードしてインストールした. → ADD PATHのチェックをつけ忘れた可能性が高いので,自力で設定しましょう.
環境設定のやり方は、このサイトが分かりやすいと思います。
ベースとなっているhtmlファイルの中身が知りたい!
ソースコードを見ていただくことで分かると思いますが,それぞれの場所にパズルのような形で組み込んでいく作りになっています.最初は意味が分からなくてよく困りました.
(関係ない話ですが,jqueryがv3.2.1なのはどうなんでしょうね... 最新版はv3.6だそうです)
全体としては タイトル→CSSやJSファイル→本文 になっていますね.
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を修正する必要があります。
.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の編集で解決できます.
_init_.pyを編集して独自のカスタムページを表示しよう
余談ですが、最近調べていて良いなと思ったのが、FontawsomeとVideo.js, swiperJS, MathJaxです。このあたりを組み合わせるとモダンな実験ページを作ることができるんじゃないでしょうか。
実験の画面を良い感じにしたい!
一例ではありますが、以下のようにnextボタンやタイトルを独自のものに置き換えることで、見栄えが良くなります。
Vue.jsの構文とoTreeのタグが被る問題を解決するには?
oTreeのタグを変更することは難しいので、Vueの方を変更しましょう。delimitersで変更できます。
const Vue_otreegame = {
template:
`<div id="app">
</div>`,
setup() {
},
delimiters: ['[[', ']]']
}
Vue.createApp(Vue_otreegame).mount('#Vuegame-with-oTree')
答え合わせ機能を実装するには?
OXクイズのような正解判定の仕組みを用意するには、ライブページの機能を使います。
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などで細かくしておいてください。
{{ 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とタグを挿入するだけです。
<a href="事前入力したFormのURL={{session.code}}-{{participant.code}}"></a>
また、init.pyに連携用のコードを用意することで外部との通信も可能になります。下のコードはGoogleスプレッドシートから内容を抽出し、表示するためにplayer.○○に格納するというものです。以前の実験参加者に対し、追加のアンケート調査を実施するときに活用しました。
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を使うことを強く推奨します。
終わらないエラー地獄はどうしたら抜け出せる?
大抵,というかほぼ確実に_init_.pyもしくはhtmlファイルの記述に問題があると思います
例えば,関数1と関数2を行ったり来たりするように作っていたり,関数の階層を深くしすぎてPythonの最大再帰数(呼び出し回数の上限)を突破していたり,関数が定義されていないなどが考えられます.また,文字の大文字・小文字やスペース,記号の不一致によって,指定された関数が呼び出せないこともあります.
HTMLファイルでは,指定されたCSS/JSファイルが読み込めない場合や{ }で指定された関数が呼び出せない場合にエラーが発生します.CSSやJSファイルは「_static」フォルダに配置し,PATHを間違えないようにしましょう.
推奨はできませんが...
一応,再帰回数の上限を引き上げるという邪道な方法もあるので紹介しておきますと,sys.setrecursionlimit()で上限を緩和できます.ただし,クラッシュする可能性が出てくるので必要最小限で行ってください.
sys.setrecursionlimit(2500)
Pythonの公式ドキュメントにもこのような記載があります.
sys.setrecursionlimit(limit)¶
Python インタプリタの、スタックの最大の深さを limit に設定します。この制限は Python プログラムが無限に再帰し、C スタックがオーバーフローしてクラッシュすることを防止するために設けられています。limit の最大値はプラットフォームによって異なります。深い再帰処理が必要な場合にはプラットフォームがサポートしている範囲内でより大きな値を指定することができますが、この値が大きすぎればクラッシュするので注意が必要です。
番外編) Ubuntuのアップデートをしたらネットに接続できなくなった
oTreeで実験するために、Ubuntu+nginx+oTree+certbotみたいな組み合わせを用意する方がいると思います。そこで注意したいのがNICの問題です。カーネルをアップデートすることで相性問題が発生し、ifconfigのNICリストから消えて、外部との通信が一切できない状態になることがあります。
大抵の場合は、次の手順で復活できます。
#まずは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