提出機能の使い方
生徒が PyHiroba で学んだ記録を、生徒自身の操作で学校に提出するための機能です。
提出先を作る
はじめに、Google ドライブで新しいスプレッドシートを作ってください。「拡張機能」から「Apps Script」を開き、最初から書かれているコードをすべて消して、次の内容を貼り付けて保存してください。
Apps Script に貼り付けるコード
/**
* PyHiroba の提出を受け取るスクリプト。
* スプレッドシートの「拡張機能 > Apps Script」に、この中身をそのまま貼ります。
*/
const SHEET_NAME = '提出';
/**
* スプレッドシートの1マスに入るのは 50,000 字まで。超えると書き込みが失敗する。
* 印の付いたセルは1マスにまとめて入れるので、見出しのぶんで溢れないように切る。
*/
const CELL_MAX = 50000;
function doPost(e) {
try {
const data = JSON.parse(e.postData.contents);
const sh = getSheet_();
const id = 'R' + Utilities.formatDate(new Date(), 'Asia/Tokyo', 'yyyyMMdd-HHmmss')
+ '-' + Math.floor(Math.random() * 900 + 100);
const who = data.who || {};
const mat = data.material || {};
const p = data.progress || {};
const all = (data.marked || [])
.map(function (m) { return '【セル ' + m.cell + '】\n' + m.text; }).join('\n\n');
const marked = fit_(all);
const note = note_(data, marked !== all);
sh.appendRow([
new Date(), id, data.test ? 'テスト送信' : '',
safe_(who.class), safe_(who.no), safe_(mat.name), safe_(mat.from),
num_(p.code), num_(p.ran), num_(p.ok), num_(p.ng), num_(p.lastRun),
safe_(runs_(p)), safe_(repeats_(p)), safe_(times_(p)), num_(p.runAll),
safe_(when_(p.openedAt)), safe_(when_(p.touchedAt)), safe_(marked), safe_(note)
]);
return json_({ ok: true, id: id });
} catch (err) {
return json_({ ok: false, error: String(err) });
}
}
/**
* セルごとの結果を「2:○ 4:○ 5:×(NameError)」の形にする。
* どのセルで止まっているかを、クラス全員ぶん並べて見るための列。
* COUNTIF(M:M, "*5:×*") で「セル5でつまずいた人数」が数えられる。
*/
function runs_(p) {
return (p.runs || []).map(function (r) {
// ○ = 通った/× = 落ちた(種類つき)/- = 押したが結果が残っていない
// (途中で止めた・出力をクリアした)。**ok が無いものを × にしないこと。**
// 落ちていないセルを「エラー」と読ませてしまう。
if (r.ok === true) return r.cell + ':○';
if (r.ok === false) return r.cell + ':×(' + (r.err || 'エラー') + ')';
return r.cell + ':-';
}).join(' ');
}
/**
* 上限で切れたことを伝える列。空なら何も切れていない。
* これが無いと、切られた中身を「途中までしか書いていない」と読み違える。
*/
/**
* 2回以上やり直したセルだけを「4:3回 5:7回」の形で並べる。
* 1回で通ったセルは書かないので、この列を見ればつまずきがそのまま並ぶ。
* COUNTIF(N:N, "*5:*") で「セル5をやり直した人数」が数えられる。
*/
function repeats_(p) {
return (p.runs || []).filter(function (r) { return (r.n || 0) > 1; })
.map(function (r) { return r.cell + ':' + r.n + '回'; }).join(' ');
}
/**
* 「開いてから何分後に動かしたか」を「4:2.4-11.8」の形で並べる。
* 時刻(何時何分)は届かない。届くのは開いてからの経過分だけで、
* 生徒が読み込み直すと 0 に戻る。
*/
function times_(p) {
return (p.runs || []).filter(function (r) { return r.t0 != null; })
.map(function (r) {
// **必ず「分」を付けること。** 付けないと「6:15」が1つだけ入ったときに、
// スプレッドシートがそれを時刻(6時15分)として読み取ってしまう
return r.cell + ':' + r.t0 + ((r.t1 != null && r.t1 !== r.t0) ? '-' + r.t1 : '') + '分';
}).join(' ');
}
function note_(data, cellCut) {
const n = [];
(data.marked || []).forEach(function (m) {
if (m.cut) n.push('セル ' + m.cell + ' の中身を上限で切りました');
});
if (data.over) n.push('印の付いたセルが ' + data.over + ' 個多いため送っていません');
if (cellCut) n.push('スプレッドシートの1マスに入る 50,000 字で切りました');
return n.join(' / ');
}
/**
* 1マスに入る長さに切る(safe_ が先頭に 1 字足すことがあるので、そのぶん残す)。
* 絵文字などの2つで1文字のものは、半分に割らない。
*/
function fit_(s) {
if (s.length <= CELL_MAX - 1) return s;
let t = s.slice(0, CELL_MAX - 1);
if (/[\uD800-\uDBFF]$/.test(t)) t = t.slice(0, -1);
return t;
}
function doGet() {
return json_({ ok: true, message: 'PyHiroba の提出先です。提出は POST で届きます。' });
}
/**
* 生徒が書いた文字が、そのまま計算式として動かないようにする。
* = + - @ で始まる文字列を Sheets は数式として解釈するため、先頭に ' を付けて
* 文字として入れる(' は表示されない)。この処理を外さないこと。
*/
function safe_(v) {
const s = String(v == null ? '' : v);
return /^[=+\-@\t\r]/.test(s) ? "'" + s : s;
}
/**
* 開いた時刻・最終操作は、世界標準時の文字列(…Z)で届く。そのまま入れると
* A 列の受付日時と 9 時間ずれた別の形で並び、見比べられない。同じ日本時間の形に直す。
*/
function when_(v) {
const d = v ? new Date(v) : null;
return (d && !isNaN(d.getTime()))
? Utilities.formatDate(d, 'Asia/Tokyo', 'yyyy/MM/dd HH:mm:ss') : '';
}
/**
* 数として入れる列。**必ず Number に通すこと。**
*
* `p.code || 0` と書くと、文字列が来たときに**その文字列をそのまま返す**
* (空文字以外は真のため)。提出先の URL は生徒に配るリンクの中に入っていて
* 誰でも知りうるので、そこへ直接 POST すれば、数として扱うはずの列に
* `=IMPORTXML(...)` のような数式を置けてしまう。置かれた数式は、先生が
* シートを開いた時点で動き、ほかの列の中身を外へ持ち出す。
* safe_ は文字の列にしか掛けていないので、ここは num_ で塞ぐこと。
*/
function num_(v) {
const n = Number(v);
return (v !== null && typeof v !== 'object' && isFinite(n)) ? n : 0;
}
function json_(obj) {
return ContentService.createTextOutput(JSON.stringify(obj))
.setMimeType(ContentService.MimeType.JSON);
}
function getSheet_() {
const ss = SpreadsheetApp.getActiveSpreadsheet();
let sh = ss.getSheetByName(SHEET_NAME);
if (!sh) {
sh = ss.insertSheet(SHEET_NAME);
sh.appendRow(['受付日時', '受付番号', '区分', 'クラス', '出席番号', '教材', '配布元',
'コードセル数', '実行したセル', 'エラーなし', 'エラーあり', '最後に実行したセル',
'セルごとの結果', 'くり返し実行', '実行の時間帯(開いてから分)', 'すべて実行の回数',
'開いた時刻', '最終操作', '提出の印が付いたセルの中身', '注記']);
}
return sh;
} ウェブアプリとして公開する
Apps Script の画面の右上にある「デプロイ」から「新しいデプロイ」を選び、種類は「ウェブアプリ」にしてください。次の2つは、必ずこのとおりに設定してください。
- 次のユーザーとして実行
自分(先生のアカウント)にしてください。生徒のアカウントでは、スプレッドシートに書き込めません。 - アクセスできるユーザー
全員にしてください。「自分のみ」や「同じ組織内のユーザー」にすると、生徒の画面から送れません。提出先の URL は誰にも推測できない長い文字列ですので、「全員」にしても、URL を知らない人から届くことはありません。
デプロイすると https://script.google.com/macros/s/…/exec という URL が出ます。手順3で使いますので、控えておいてください。
教材に、提出してほしいセルの印を付ける
提出に含めたいセルに、次の1行を書いてください。テキストセルは、いちばん上の行に書いてください。書いた行はそのまま残してかまいません。Python はコメントとして読み飛ばしますので、Google Colaboratory を使用した場合は、何も起きません。
コードセル
#pyhiroba submit
テキストセル
<!-- #pyhiroba submit -->印は10個までです。
提出先を確かめる
手順1で控えた URL を次の欄に貼ってください。使える形かどうかをその場で判定します。「テストを送る」を押すと、テストの1件を実際に送って、スプレッドシートに行が増えるかを確かめられます。
テストを送ると、スプレッドシートに「テスト送信」の行が1行増えます。増えていれば準備は完了です。増えないときは、手順1の2つの設定を見直してください。
配るリンクを作る
教材の共有リンク、またはファイルIDを次の欄に入れてください。手順3の URL と組み合わせて、生徒に配るリンクを作ります。
生徒がこのリンクで教材を開くと、ノートブックのいちばん下に「提出」が出ます。押すと、何が送られるかを全部表示した確認の画面が出ますので、そこで生徒が押したときだけ送られます。自動では送りません。
集まったものを見る
1件の提出で、スプレッドシートに次の20項目が1行として届きます。
- 提出者情報
- 受付日時、受付番号、区分、クラス、出席番号、教材、配布元
- 進み具合
- コードセル数、実行したセル、エラーなし、エラーあり、最後に実行したセル
- 取り組みの記録
- セルごとの結果、くり返し実行、実行の時間帯、すべて実行の回数、開いた時刻、最終操作
- 記入した内容
- 提出の印が付いたセルの中身、注記
クラスと出席番号は、生徒の自己申告です。
ドキュメントとスライドのセルは、文字の形で届きます(スライドに貼った写真は「[画像]」の枠に置き換わります)。「【セル n】」の次の行からそのセルの終わりまでを PyHiroba のテキストセルに貼り付けると、元の見た目で見られます。
送れなかったときは
提出と同時に、生徒の手元にも同じ内容の .ipynb が保存されます。校内のネットワークの都合で送れなかったときは、そのファイルを集めてください。
ご確認ください
・次のものは送りません。氏名、印を付けていないセルの中身、ノートブック全文、エラーのメッセージ本文、実行した時刻、実行以外の操作の記録、教材の URL。・PyHiroba は送信の手段のみを提供します。生徒の画面には、送信前に送信内容の確認ポップアップが表示されます。詳しくは利用規約 第9条(外部送信について)をご覧ください。
・送信前に、API キーなど送信すべきでないものが含まれていないかを機械で確認します(見落としはありえます)。
・ご不明な点はお問い合わせフォームからご連絡ください。