位置情報を取得する出欠登録用のスクリプト

投稿者: | 2020年3月3日

講義の出席は、紙を回して名前を書かせて確認しています。しかし50人を超える受講者の場合、いちいち講義毎に紙から名前を拾って出席をカウントするのが面倒です。同じような筆跡もよく見かけますし。最近では、スマホを持っていない学生もほとんど見かけないので、web上で出席をとってみることにしました。具体的にはQRコードをスクリーンに映してURLを配布し、学生のスマホで名前と学生証番号を登録させます。

環境変数を記録しといて1つの端末で複数登録(代返)していないか、後で確認することとします。QRコードをばらまかれたら、自宅にいても出席登録できてしまいます。教室で登録しているか確認するため、スマホのGPS情報も一緒に記録しておいて確認します。ブラウザに位置情報へのアクセスを求められるので、許可してもらう必要があります。正直、これでも抜け穴はありますが。

このコードに出席登録できる日時と時間の制限、登録後の確認メールの送信、二重登録防止の機能を加えて、4月から3つの講義で実際に使ってみたいと思います。

apache2.4+python3.7で動作確認しています。また、保存する*.logは、外部からアクセスできないよう.htaccessでアクセス制限しておく必要があります。

サンプルページ

jscode.js

緯度と経度を取得するjavascript。Pythonで今いる位置情報やユーザーエージェントなんかを取得を参考にしました。入力した学生証の確認、位置情報取得の可否をチェックしています。

// 現在の位置情報取得を実施
if (navigator.geolocation) {
    navigator.geolocation.getCurrentPosition(
    // 位置情報取得成功時
    function (pos) {
	var location = pos.coords.latitude;
	location += "," + pos.coords.longitude;
	document.getElementById("location").value = location;
    }, 
    // 位置情報取得失敗時
    function (pos) {
	var location ="位置情報が取得できません。<br>";
	location = location + "ブラウザを再起動してやり直してみる。<br>";
	document.getElementById("infotext").innerHTML = location;
	var submit = document.getElementById("submit");
	submit.disabled = "true"
    });
} 
else {
    window.alert("このブラウザはGeolocationに対応していません。");
}

// inputのチェック
function check(){
    if(document.form.name.value == ""){
    	window.alert('名前が入力されていません'); 
    	return false; 
    }
    if(document.form.name.value == ""){
    	window.alert('位置情報が取得できていません'); 
    	return false; 
    }
    if(! document.form.gakuseki.value.match(/^[1-3][0-9][a-zA-Z]{1,2}\d{4}[a-zA-Z]{0,1}$/)){
    	window.alert('学生証番号が正しくありません'); 
    	return false; 
    }
    return true; 
}

check.cgi

出欠登録のためにアクセスさせるページを生成する。名前と学生証番号を入力させ、session-IDを付与してsubmitするとaction.cgiに移動する。

import secrets

INPUT_HTML = """Content-Type: text/html
Set-Cookie: session={session}

<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<link href="style.css" rel="stylesheet" type="text/css">
<title>出欠確認</title>
</head>
<body>
<script src="jscode.js" charset="utf-8"></script>
<form method="POST" action="./action.cgi" name="form" onSubmit="return check()">
<h1>出席確認</h1>
<h2>名前</h2>
<p>フルネームを記入する。</p>
<p><input type="text" name="name" value=""></p>
<h2>学生証番号</h2>
<p>大文字小文字は区別しません。例: 10t0123x</p>
<p><input type="text" name="gakuseki" value="" style="ime-mode:disabled;"></p>
<p id="infotext"></p>
<input type="hidden" name="geo" id="location" value="">
<input type="hidden" name="session" value="{session}">
<br>
<p>フォームに入力したら送信ボタンを押す。</p>
<p style="text-align: center">
<input type="submit" value="送信" id="submit">
</submit>
</form>
</body>
</html>
"""

# HTMLの生成
token = secrets.token_hex(6)
print(INPUT_HTML.format(session=token))

action.cgi

cookieからsession-IDを拾ってページ遷移を確認します。位置情報や入力データ、環境変数一式をlogs/22:07:20:408746_10T0000T.logのようなファイル名で保存します。1登録につき1ファイルで保存します。logs/は自前で作成してchmod 777しときます。

import os
from datetime import datetime
from http import cookies
import cgi

BODY = """Content-Type: text/html\n
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<link href="style.css" rel="stylesheet" type="text/css">
</head>
<body>
<p>{}</p>
</body>
</html>
"""


def CheckSession(dic):
    form = cgi.FieldStorage()
    form_keys = form.keys()
    cookie = cookies.SimpleCookie(os.environ.get('HTTP_COOKIE', None))
    if ("name" in form_keys) is False or\
       ("geo" in form_keys) is False or\
       ("gakuseki" in form_keys) is False or\
       ("session" in form_keys) is False or\
       cookie is None or\
       ("session" in cookie.keys()) is False:
        print(BODY.format("不正遷移"))
        exit()
    dic["name"] = form.getvalue("name", "")
    dic["geo"] = form.getvalue("geo", "")
    dic["id"] = form.getvalue("gakuseki", "").upper()
    dic["session"] = form.getvalue("session", "")
    dic["session_cookie"] = cookie["session"].value
    if dic["session_cookie"] != dic["session"]:
        print(BODY.format("不正遷移"))
        exit()


def WriteLog(now, dic, body):
    time = now.strftime("%Y-%m-%d_%H:%M:%S:%f")
    outfile = "logs/{}_{}.log".format(time, dic["id"])
    o = open(outfile, "w")
    for k, v in dic.items():
        o.write("{}: {}\n".format(k, v))
    for k, v in os.environ.items():
        o.write("{}: {}\n".format(k, v))
    o.close()
    body = "出席登録しました。\n" + body
    print(BODY.format(body.replace("\n", "<br>\n")))


now = datetime.now()
dic = {}
CheckSession(dic)
body = ""
body += "氏名: {}\n".format(dic["name"])
body += "学生証番号: {}\n".format(dic["id"])
gmap = "https://www.google.com/maps?q={}".format(dic["geo"])
body += "位置情報: <a href={}>{}</a>\n".format(gmap, dic["geo"])
WriteLog(now, dic, body)

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です