見出し画像

【Python】毎朝の小学校出欠アンケート入力を自動化してみた

 こんにちは。自立に向けてとにかく手を動かしたいとっとです。 

 先日、長男の通う小学校からこんな連絡がきました。

小学校からの依頼(抜粋)

 コロナ禍の中、様々な状況の変化に対応するため、学校では環境整備に取り組んでいます。その取り組みの一つとして、児童一人一人の健康状態の確認や把握をより確実に行うため、出欠確認や健康観察を学校ホームページにある保護者専用ページでの連絡に変更します。
 ~中略~
 フォーム未入力児童や児童自身が「保護者の入力」が確認出来ない場合は、校門での検温、健康観察が必要となります。フォームへの入力とお子様と「入力済みの確認」をしていただけると助かります。

【使用方法】
 1.学校ホームページに入る。
 2.保護者専用ページにログインする。
   アカウント パスワードは、メールにて再度お知らせいたします。
 3.出欠確認・健康観察フォームをクリックする。
 4.各学年、各クラスの連絡フォームに入る。
 5.回答し、送信する。
 *毎日8時までに送信してください。
  9月21日(火)から実施いたします。

 これまでは、連絡帳の紙に体温、気分、体調を記入してもっていき、かつ小学校でも登校時に体温を測定していました。

 今回の小学校側の提案によると、事前に朝8時までに健康観察フォームを入力しておくことで、教室に入る前の体温測定を省略し、少しでも登校時に児童が滞留して密になるのを避けよう、という素晴らしいアイデアだなと思い、おおいに賛同です!

 と同時に、これはPythonプログラミングの良い練習(?)になるかも、と思い、頼まれてもいないのに、フォーム入力を自動化するツールを制作することにしました。


まずは入力フォームを見てみます

 小学校のホームページにログインすると、以下のようなフォーム画面でした。


トップページ

 出席番号、名前、平熱、出欠選択、を入力し、次へ。

 出席の場合と欠席の場合で次の入力フォームが変わります。

googleフォームトップ

出席の場合

 体温、気分、体調、家族に風邪症状がある人の有無、を入力します。最後に「送信」ボタンを押すと、学校に送信されます。

googleフォーム出席の場合

欠席の場合

 欠席の理由、症状(体調不良の場合のみ)を選択し、次へ。次の画面では、送信ボタンが出てくるのみで、OKなら「送信」ボタンを押すと、学校に送信されます。

googleフォーム欠席の場合


ツール化の構想

 ツール化により何を楽にしたいかを考えました。

 いくつか入力画面があり、画面遷移を伴いながら、都度回答を入力する必要があるので、この操作の手間を改善したいと思います。

 やり方としては、ツールですべての質問画面を前もって用意し、その一画面上で先に全部入力して、最後に入力ボタンを押すようにすることで、人が入力する作業を最初の1ステップに集約しようとおもいます。

 また、今回色々調べてみてわかったこととして、この入力フォームはGoogleフォームというもので作られており、URLさえわかれば小学校のホームページからログインせずとも、いきなり入力フォームの入力からできるようでした。


 上記を踏まえ、以下のように人の操作が変わります。

改善前

①小学校のページにログインする
②入力フォームを開く(学年やクラスを選択していく必要あり)
③トップページに回答する
④出欠に応じて次の画面に遷移し、追加回答する
⑤送信ボタンをおす。

改善後

(1)ツールを開く(改善前の①②に相当)
(2)ツール画面上のすべての質問に回答を入力する(改善前の②③に相当)
(3)入力ボタンを押す。(ツールが入力フォームに入力してくれる)
(4)入力フォーム内容を確認し、よければフォーム上で送信ボタンを押す。

 最後の(4)は、ツール動作の確認が取れるまでの暫定処置としてあえて人の操作を残しました。ねんのために目視で確認してから人がちゃんと入力フォーム上で送信ボタンを押した方が良いとおもったためです。ツールの動作実績確認がとれていけば、送信ボタンクリックまでツールに取り込み、④は無くしていこうと思います。


こんなツールになりました

 できたツール画面です。

出席の場合

ツール画面_出席の場合

欠席の場合

ツール画面_欠席の場合


 PythonのGUIライブラリの一つであるTKinterというモジュールをインポートして、ひとつひとつ部品を配置してつくりました。

 出欠は、OptionMenuという部品で選択式にしています。画面右側に、出席の場合と欠席の場合で別のタブ(Notebookという部品)を用意し、それぞれの質問内容をまとめました。


工夫した点

■自明な情報はあらかじめツールで入力

 生徒情報の部分はあらかじめ入力しておくことで、その日の体温だけ書き換えればいいようにしています。

 また画面右側の、気分や体調などの質問に対する選択も、基本的には(願いも込めて)元気であることを前提にあらかじめ選択しておき、すぐれないときだけ変えればいいようにしています。


■過去の経験上感じた課題を一部克服

 はじめてPythonでプログラミングしたときの記事で、課題をあげていました。

この記事で挙げていた課題です。

①実行させたときの動作が、本当に人がマウスやキーボードで入力しているかのように画面遷移していくので、遅い。
②画面遷移を伴うのでsleepで次の動作まで一呼吸おいている箇所があるが、ネット回線の状況によってはsleepで待つ秒数が足りなかったりすることがあり、動作が不安定。
③作るだけでなく、使うだけでも、Python環境のインストールが必要なのが面倒ではないか。

===

 ①と②については、Seleniumというブラウザ操作を行うモジュールを使う以上避けてとおれないようなので、いかにSeleniumを使わずにコードを書くかを考えてみたのですが、素人におもいつくはずもなく、調べてみたところ、こんな記事を発見しました。

 この記事を参考に、最初のトップページの入力(生徒情報)に関しては、GoogleフォームのURLを開く際にパラメータとして生徒情報を一緒に与えることで素早く入力できたのですが、次へボタンでの画面遷移がどうしてもSeleniumでないとできず、途中からSeleniumに頼るしかありませんでした。

 Seleniumをつかわずに、Googleフォームの別のセクションを開くやりかたが分かる方いらっしゃれば、コメントいただけるとありがたいです。

===

 ③についてですが、こちらは作ったPythonコードをexe化して一つの実行ファイルにすることで、解決できました。exe化にはいくつかやり方(モジュール)があるようなのですが、わたしはPyinstallerにしました。

 exe化したファイルを、2階のリビングにある長男用のパソコンで実行したところ、メイン画面は開くのですが、入力するボタンを押してもGoogleフォームに記入されません。

 リビングのパソコンにはPython環境はなく、しらべてみたところ、Seleniumをつかったコードの場合、exe化する際にwebdriverも一緒に組み込んであげる必要があるということでした。

 あとは、コード中でwebdriverを呼び出す際も、実行ファイルのあるパスを自動で取得するようにし、exeに組み込んだwebdriverが他の端末でもきちんと呼び出されるようにしなければなりません。ここが一番手間取りました。


■余談

 これは工夫ではなく、単にわたしのPythonの練習のためなのですが、メイン画面になにか画像を貼り付けたいとおもい、我が家のオリジナルキャラクターであるバッチグーぶた(自作)を貼り付けました。意味はありません。

 ちなみに長男の反応は、「またとっとふざけて・・」と白けた様子。小さいころはおもしろがってくれていたのに。。

 あ、手の数がおかしいことになっているのは気にしないでください。


課題です

 リビングにあるパソコンでの動作も含めて、目的の機能は達成できたのですが、今回このツールをつくっているなかで妥協している点がありますので、挙げてみます。

■Googleフォームへの入力

 さきほども記載しましたように、Googleフォームの次のセクションへの入力がどうしてもSelenium頼りなので、あまりスマートではないです。

■フォントが正しく表示されない

 冒頭でご紹介したツールのメイン画面では、フォントをHGS創英角ポップ体にしているのですが、exe化したファイルを長男のPCで実行すると、デフォルトのフォントでしか表示されない状態となります。

 こちらもexe化する際になにか注意点があるのか、継続して調べてみたいとおもいます。


ツール構成・コード紹介

 最後に、今回制作したツールのコード紹介をしたいとおもいます。

■ファイル構成

 今回つくったコードは以下3つとなりました。

main.py メイン画面(GUI)を作成し、ユーザ入力をまとめます
input_form.py 入力ボタンにより実際にGoogleフォームに入力します
common_data.py 上記二者間で入力内容を共有する変数たちを定義します

 main.pyでユーザ入力を行い、その情報をinput_form.pyで読み取ってGoogleフォームに入力していきます。両者のコード間で入力情報を共有するのに、common_data.pyに変数定義しました。

 この両者間で共有している変数は、コード中でcom.XXXとなっている箇所なのですが、今回やっていて気づいたこととして、common_data.pyに定義しなくてもうまく動くということです。ほんとうはきちんと宣言するべきなのかもしれません。ということで、common_data.pyのコードは実質中身が無いので割愛します。

■ソースコード

main.py

こちらは、冒頭で示しました画面を作成するコードです。

入力するボタンが押されると、input_form.pyが発動します。

import tkinter as tk
from tkinter import ttk
import tkinter.font as fnt
import sys
import os
from PIL import Image, ImageTk

#自作モジュールのインポート
import common_data as com
import input_form as inp

#--------------------------------------------------------------------------------
#初期設定
com.window_size = '1100x850'           #画面サイズ
com.text_height = 30
com.text_width = 50   #半角50文字分

#--------------------------------------------------------------------------------
#入力した値をフォームに入力する関数
def func_exe_after():
 frm_main.after(1000,inp.func_exe)


#メイン画面を作成
frm_main = tk.Tk()
frm_main.title('小学校 朝の健康観察・欠席連絡 入力フォーム')    # 画面タイトル
frm_main.geometry(com.window_size)

#frame1を作成
#フォント設定

#フォント
#文字の大きさ
#文字の太さ("normal" or "bold")
#斜体にするかどうか("roman" or "italic")
#下線の有無("normal" or "underline")
#取消線の有無("normal" or "overstrike")

LblFrmFont = fnt.Font(
 family='HGS創英角ポップ体',
 size = 15,
 weight = "normal",
 slant = "roman",
 underline = False,
 overstrike = False
 )

LblFont = fnt.Font(
 family='HGS創英角ポップ体',
 size = 15,
 weight = "normal",
 slant = "roman",
 underline = False,
 overstrike = False
 )

OptFont = fnt.Font(
 family='HGS創英角ポップ体',
 size = 15,
 weight = "normal",
 slant = "roman",
 underline = False,
 overstrike = False
 )

EntFont = fnt.Font(
 family='HGS創英角ポップ体',
 size = 15,
 weight = "normal",
 slant = "roman",
 underline = False,
 overstrike = False
 )

SitumonFont = fnt.Font(
 family='HGS創英角ポップ体',
 size = 15,
 weight = "normal",
 slant = "roman",
 underline = False,
 overstrike = False
 )

RdoFont = fnt.Font(
 family='HGS創英角ポップ体',
 size = 13,
 weight = "normal",
 slant = "roman",
 underline = False,
 overstrike = False
 )

CbFont = fnt.Font(
 family='HGS創英角ポップ体',
 size = 13,
 weight = "normal",
 slant = "roman",
 underline = False,
 overstrike = False
 )

BtnFont = fnt.Font(
 family='HGS創英角ポップ体',
 size = 15,
 weight = "normal",
 slant = "roman",
 underline = False,
 overstrike = False
 )
 
frm1 = tk.LabelFrame(frm_main,text="生徒情報", font = LblFrmFont)
frm1.configure(fg = 'blue')
frm1.grid(row=0,column=0 , pady = 10, padx = 20, sticky = 'n'+'we')

#バッチグーぶた用のキャンバスを作成
def resource_path(relative_path):
 try:
   base_path = sys._MEIPASS
 except Exception:
   base_path = os.path.dirname(__file__)
 return os.path.join(base_path, relative_path)

print(resource_path('./image/batchgoobuta.jpg'))

img = Image.open(resource_path('./image/batchgoobuta.jpg'))

w = img.width
h = img.height
w_adj = int(w * (250/w))
h_adj = int(h * (250/w))

img = img.resize((w_adj, h_adj))
img = ImageTk.PhotoImage(img)

canvas = tk.Canvas(frm_main, bg="black", width = w_adj-2, height = h_adj-2)
canvas.grid(row = 1, column = 0)
canvas.create_image(w_adj/2, h_adj/2, image = img)

#frame2を作成
frm2 = tk.LabelFrame(frm_main,text="操作ボタン", font = LblFrmFont)
frm2.configure(fg = 'blue')
frm2.grid(row = 2, column=0, pady = 10, padx = 20, sticky = 's'+'we')

com.Lbl_btnmsg = tk.Label(frm_main,text ='↑このボタンは\n最後に押してね', font = ('HGS創英角ポップ体', 30), fg = 'red')
com.Lbl_btnmsg.grid(row = 3, column = 0, pady = 5, padx = 20, sticky = 'n')

#タブを作成
notebook = ttk.Notebook(frm_main)
notebook.grid(row=0, column=1, pady = 10, padx = 20, rowspan = 4, sticky = 'n' + 'we')
tab_syusseki = tk.Frame(notebook)
tab_kesseki = tk.Frame(notebook)

notebook.add(tab_syusseki, text="出席の場合")
notebook.add(tab_kesseki, text="欠席の場合")
notebook.tab(tab_syusseki, state = "normal")
notebook.tab(tab_kesseki, state = "disable")

#frm1の構成
#クラス情報の入力
Lbl_kurasu = ttk.Label(frm1,text ='クラス', font = LblFont)
Lbl_kurasu.grid(row =  1, column = 0, pady = 5, padx = 20, sticky = 'w')

kurasu_list = [
 '1年1組',
 '1年2組',
 '1年3組',

 '2年1組',
 '2年2組',
 '2年3組',

 '3年1組',
 '3年2組',
 '3年3組',

 '4年1組',
 '4年2組',
 '4年3組',

 '5年1組',
 '5年2組',
 '5年3組',

 '6年1組',
 '6年2組',
 '6年3組',
]

com.SV_kurasu = tk.StringVar()
com.SV_kurasu.set(kurasu_list[0])

com.Opt_kurasu = tk.OptionMenu(frm1, com.SV_kurasu, *kurasu_list)
com.Opt_kurasu.config(width=7, font=OptFont)
com.Opt_kurasu.grid(row = 1, column = 1, pady = 5, padx = 0, columnspan = 1, sticky = "w")


#出席番号の入力
Lbl_syusseki_no = ttk.Label(frm1, text ='出席番号',font = LblFont)
Lbl_syusseki_no.grid(row = 2, column = 0, pady = 5, padx = 20, sticky = 'w')

com.Ent_syusseki_no = ttk.Entry(frm1, width=10, font = EntFont)
com.Ent_syusseki_no.grid(row = 2, column = 1, pady = 5, padx = 0, sticky = tk.W)
com.Ent_syusseki_no.insert(0,"30")

#名前の入力
Lbl_namae = ttk.Label(frm1, text ='名前',font = LblFont)
Lbl_namae.grid(row =  3, column = 0, pady = 5, padx = 20, sticky = 'w')

com.Ent_namae = ttk.Entry(frm1, width=10, font = EntFont)
com.Ent_namae.grid(row =  3, column = 1, pady = 5, padx = 0, sticky = tk.W)
com.Ent_namae.insert(0,"YYYY XXXX")


#平熱の入力
Lbl_heinetsu = ttk.Label(frm1, text ='平熱',font = LblFont)
Lbl_heinetsu.grid(row =  4, column = 0, pady = 5, padx = 20, sticky = 'w')

com.Ent_heinetsu = ttk.Entry(frm1, width=10, font = EntFont)
com.Ent_heinetsu.grid(row =  4, column = 1, pady = 5, padx = 0, sticky = tk.W)
com.Ent_heinetsu.insert(0,"36.7")

#出欠の選択
def func_select_syukketu(event):
 if com.SV_syukketu.get() == '出席':
   notebook.select(tab_syusseki)
   notebook.tab(tab_syusseki, state = "normal")
   notebook.tab(tab_kesseki, state = "disable")
   com.Ent_taion["state"] = "normal"

 else:
   notebook.select(tab_kesseki)
   notebook.tab(tab_kesseki, state = "normal")
   notebook.tab(tab_syusseki, state = "disable")
   com.Ent_taion["state"] = "disable"

Lbl_syukketu = ttk.Label(frm1,text ='出欠',font = LblFont)
Lbl_syukketu.grid(row = 5, column = 0, pady = 5, padx = 20, sticky = 'w')

syukketu_list = [
 '出席',
 '欠席'
]

com.SV_syukketu = tk.StringVar()
com.SV_syukketu.set(syukketu_list[0])

com.Opt_syukketu = tk.OptionMenu(frm1, com.SV_syukketu, *syukketu_list, command = func_select_syukketu)
com.Opt_syukketu.config(width=7, font=OptFont)
com.Opt_syukketu.grid(row = 5, column = 1, pady = 5, padx = 0, columnspan = 1, sticky = 'w')

#今日の体温の入力
Lbl_taion = ttk.Label(frm1, text ='今日の体温',font = LblFont)
Lbl_taion.grid(row =  6, column = 0, pady = 5, padx = 20, sticky = 'w')

com.Ent_taion = ttk.Entry(frm1, width=10, font = EntFont)
com.Ent_taion.grid(row =  6, column = 1, pady = 5, padx = 0, sticky = tk.W)
com.Ent_taion.insert(0,"36.7")

#frm2の構成
#入力実行ボタン
Btn_exe = tk.Button(frm2, text='入力する', font = BtnFont, bg = 'cornflower blue',command=func_exe_after)
Btn_exe.grid(row = 0, column = 0, pady = 5, padx = 5, sticky = 'we')


#出席の場合に入力するフォーム-------------------------------------------------------------
#frm_syusseki1を作成
frm_syusseki1 = tk.LabelFrame(tab_syusseki,text="今日の気分はどうですか?", font = LblFrmFont)
frm_syusseki1.config(fg = 'blue')
frm_syusseki1.grid(row=0,column=1, pady = 10, padx = 20, rowspan = 1, sticky = 'n'+'we')

#frm_syusseki2を作成
frm_syusseki2 = tk.LabelFrame(tab_syusseki,text="今日の体調はどうですか?", font = LblFrmFont)
frm_syusseki2.config(fg = 'blue')
frm_syusseki2.grid(row=1,column=1, pady = 10, padx = 20,  sticky = 'n'+'we')

#frm_syusseki3を作成
frm_syusseki3 = tk.LabelFrame(tab_syusseki,text="家族でかぜ症状がある人はいませんか?\n(咳、のどが痛い、鼻水が出る、たんが出る、\n 息苦しい、動けないようなだるさ)", font = LblFrmFont)
frm_syusseki3.config(fg = 'blue')
frm_syusseki3.grid(row=2,column=1, pady = 10, padx = 20, columnspan = 1, sticky = 'n'+'we')

#frm_syusseki1の構成
#今日の気分
com.SV_kibun = tk.StringVar()
com.SV_kibun.set("とても気分が良い")

com.Rdo_kibun1 = tk.Radiobutton(frm_syusseki1, text='とても気分が良い(バッチグー)', value = "とても気分が良い", variable=com.SV_kibun, font = RdoFont)
com.Rdo_kibun1.grid(row =  1, column = 0, pady = 5, padx = 0, sticky = tk.W)

com.Rdo_kibun2 = tk.Radiobutton(frm_syusseki1, text='気分が良い', value = "気分が良い", variable=com.SV_kibun, font = RdoFont)
com.Rdo_kibun2.grid(row =  2, column = 0, pady = 5, padx = 0, sticky = tk.W)

com.Rdo_kibun3 = tk.Radiobutton(frm_syusseki1,text='普通の気分', value = "普通の気分", variable=com.SV_kibun, font = RdoFont)
com.Rdo_kibun3.grid(row =  3, column = 0, pady = 5, padx = 0, sticky = tk.W)

com.Rdo_kibun4 = tk.Radiobutton(frm_syusseki1,text='気分が悪い', value = "気分が悪い", variable=com.SV_kibun, font = RdoFont)
com.Rdo_kibun4.grid(row =  4, column = 0, pady = 5, padx = 0, sticky = tk.W)

com.Rdo_kibun5 = tk.Radiobutton(frm_syusseki1,text='とても気分が悪い', value = "とても気分が悪い", variable=com.SV_kibun, font = RdoFont)
com.Rdo_kibun5.grid(row =  5, column = 0, pady = 5, padx = 0, sticky = tk.W)

#frm_syusseki2の構成
#今日の体調
com.BV_taityou1 = tk.BooleanVar()
com.BV_taityou1.set(True)
com.Cb_taiyou1 = tk.Checkbutton(frm_syusseki2, text='健康です!(バッチグー)', variable=com.BV_taityou1, font = CbFont)
com.Cb_taiyou1.grid(row =  1, column = 0, pady = 5, padx = 0, sticky = tk.W)

com.BV_taityou2 = tk.BooleanVar()
com.BV_taityou2.set(False)
com.Cb_taiyou2 = tk.Checkbutton(frm_syusseki2, text='だるい', variable=com.BV_taityou2 ,font = CbFont)
com.Cb_taiyou2.grid(row =  2, column = 0, pady = 5, padx = 0, sticky = tk.W)

com.BV_taityou3 = tk.BooleanVar()
com.BV_taityou3.set(False)
com.Cb_taiyou3 = tk.Checkbutton(frm_syusseki2, text='食べたくない(食欲がない)', variable=com.BV_taityou3 ,font = CbFont)
com.Cb_taiyou3.grid(row =  3, column = 0, pady = 5, padx = 0, sticky = tk.W)

com.BV_taityou4 = tk.BooleanVar()
com.BV_taityou4.set(False)
com.Cb_taiyou4 = tk.Checkbutton(frm_syusseki2, text='咳', variable=com.BV_taityou4 ,font = CbFont)
com.Cb_taiyou4.grid(row =  4, column = 0, pady = 5, padx = 0, sticky = tk.W)

com.BV_taityou5 = tk.BooleanVar()
com.BV_taityou5.set(False)
com.Cb_taiyou5 = tk.Checkbutton(frm_syusseki2, text='のどが痛い', variable=com.BV_taityou5 ,font = CbFont)
com.Cb_taiyou5.grid(row =  5, column = 0, pady = 5, padx = 0, sticky = tk.W)

com.BV_taityou6 = tk.BooleanVar()
com.BV_taityou6.set(False)
com.Cb_taiyou6 = tk.Checkbutton(frm_syusseki2, text='鼻水が出る(アレルギーは除く)', variable=com.BV_taityou6 ,font = CbFont)
com.Cb_taiyou6.grid(row =  6, column = 0, pady = 5, padx = 0, sticky = tk.W)

com.BV_taityou7 = tk.BooleanVar()
com.BV_taityou7.set(False)
com.Cb_taiyou7 = tk.Checkbutton(frm_syusseki2, text='たんが出る', variable=com.BV_taityou7 ,font = CbFont)
com.Cb_taiyou7.grid(row =  7, column = 0, pady = 5, padx = 0, sticky = tk.W)

com.BV_taityou8 = tk.BooleanVar()
com.BV_taityou8.set(False)
com.Cb_taiyou8 = tk.Checkbutton(frm_syusseki2, text='息苦しい', variable=com.BV_taityou8 ,font = CbFont)
com.Cb_taiyou8.grid(row =  8, column = 0, pady = 5, padx = 0, sticky = tk.W)

com.BV_taityou9 = tk.BooleanVar()
com.BV_taityou9.set(False)
com.Cb_taiyou9 = tk.Checkbutton(frm_syusseki2, text='動けないようなだるさ', variable=com.BV_taityou9 ,font = CbFont)
com.Cb_taiyou9.grid(row =  9, column = 0, pady = 5, padx = 0, sticky = tk.W)

#frm_syusseki3の構成
#家族の状況
com.SV_kazoku = tk.StringVar()
com.SV_kazoku.set("いません")

com.Rdo_kazoku1 = tk.Radiobutton(frm_syusseki3, text='います', value = "います", variable=com.SV_kazoku, font = RdoFont)
com.Rdo_kazoku1.grid(row =  1, column = 0, pady = 5, padx = 0, sticky = tk.W)

com.Rdo_kazoku2 = tk.Radiobutton(frm_syusseki3, text='いません', value = "いません", variable=com.SV_kazoku, font = RdoFont)
com.Rdo_kazoku2.grid(row =  2, column = 0, pady = 5, padx = 0, sticky = tk.W)


#欠席の場合に入力するフォーム-------------------------------------------------------------
frm_kesseki1 = tk.LabelFrame(tab_kesseki,text="欠席の理由を教えてください。", font = LblFrmFont)
frm_kesseki1.config(fg = 'blue')
frm_kesseki1.grid(row=0,column=0, pady = 10, padx = 20, sticky = "n"+"ew")

frm_kesseki2 = tk.LabelFrame(tab_kesseki,text="「体調不良のため」を選択された方は症状を選択してください。\n(それ以外の方は「該当なし」を選択してください。)", font = LblFrmFont)
frm_kesseki2.config(fg = 'blue')
frm_kesseki2.grid(row=1,column=0, pady = 10, padx = 20, sticky = "n"+"ew")


#frm_kesseki1の構成
def func_riyuu():
 #体調不良の場合は症状を記入し、それ以外は症状欄は該当なし
 if com.SV_riyuu.get() == "体調不良のため":
   com.Rdo_syojou1.configure(state = "normal")
   com.Rdo_syojou2.configure(state = "normal")
   com.Rdo_syojou3.configure(state = "normal")
   com.Rdo_syojou4.configure(state = "normal")
   com.Rdo_syojou5.configure(state = "normal")
   com.Rdo_syojou6.configure(state = "normal")
   com.Rdo_syojou7.configure(state = "normal")
   com.Rdo_syojou8.configure(state = "normal")
   com.Rdo_syojou9.configure(state = "normal")
   com.Rdo_syojou10.configure(state = "normal")
   com.Rdo_syojou11.configure(state = "normal")

   if com.SV_syojou.get() == "その他":
     com.Ent_syojou11.configure(state = "normal")
   else:
     com.Ent_syojou11.configure(state = "disable")

 else:
   com.SV_syojou.set('該当なし')

   com.Rdo_syojou1.configure(state = "disabled")
   com.Rdo_syojou2.configure(state = "disabled")
   com.Rdo_syojou3.configure(state = "disabled")
   com.Rdo_syojou4.configure(state = "disabled")
   com.Rdo_syojou5.configure(state = "disabled")
   com.Rdo_syojou6.configure(state = "disabled")
   com.Rdo_syojou7.configure(state = "disabled")
   com.Rdo_syojou8.configure(state = "disabled")
   com.Rdo_syojou9.configure(state = "disabled")
   #com.Rdo_syojou10.configure(state = "disabled")
   com.Rdo_syojou11.configure(state = "disabled")

   com.Ent_syojou11.configure(state = "disabled")

 if com.SV_riyuu.get() == "その他":
   com.Ent_riyuu5.configure(state = "normal")
 else:
   com.Ent_riyuu5.configure(state = "disable")

com.SV_riyuu = tk.StringVar()
com.SV_riyuu.set("体調不良のため")

com.Rdo_riyuu1 = tk.Radiobutton(frm_kesseki1, text='体調不良のため', value = "体調不良のため", variable=com.SV_riyuu, font = RdoFont, command = func_riyuu)
com.Rdo_riyuu1.grid(row = 1, column = 0, pady = 5, padx = 0, sticky = tk.W)

com.Rdo_riyuu2 = tk.Radiobutton(frm_kesseki1, text='登校に不安があるため', value = "登校に不安があるため", variable=com.SV_riyuu, font = RdoFont, command = func_riyuu)
com.Rdo_riyuu2.grid(row = 2, column = 0, pady = 5, padx = 0, sticky = tk.W)

com.Rdo_riyuu3 = tk.Radiobutton(frm_kesseki1,text='家庭の事情', value = "家庭の事情", variable=com.SV_riyuu, font = RdoFont, command = func_riyuu)
com.Rdo_riyuu3.grid(row = 3, column = 0, pady = 5, padx = 0, sticky = tk.W)

com.Rdo_riyuu4 = tk.Radiobutton(frm_kesseki1,text='身内に不幸があったため', value = "身内に不幸があったため", variable=com.SV_riyuu, font = RdoFont, command = func_riyuu)
com.Rdo_riyuu4.grid(row = 4, column = 0, pady = 5, padx = 0, sticky = tk.W)

com.Rdo_riyuu5 = tk.Radiobutton(frm_kesseki1,text='その他', value = "その他", variable=com.SV_riyuu, font = RdoFont, command = func_riyuu)
com.Rdo_riyuu5.grid(row = 5, column = 0, pady = 5, padx = 0, sticky = 'w')
com.Ent_riyuu5 = ttk.Entry(frm_kesseki1, width=30, font = EntFont)
com.Ent_riyuu5.grid(row = 5, column = 0, pady = 5, padx = 100, sticky = 'e')
if com.SV_riyuu.get() == "その他":
 com.Ent_riyuu5.configure(state = "normal")
else:
 com.Ent_riyuu5.configure(state = "disable")


#frm_kesseki2の構成
def func_syojou():
 if com.SV_syojou.get() == "その他":
   com.Ent_syojou11.configure(state = "normal")
 else:
   com.Ent_syojou11.configure(state = "disable")


com.SV_syojou = tk.StringVar()
com.SV_syojou.set("風邪")

com.Rdo_syojou1 = tk.Radiobutton(frm_kesseki2, text='風邪', value = "風邪", variable=com.SV_syojou, font = RdoFont, command = func_syojou)
com.Rdo_syojou1.grid(row =  1, column = 0, pady = 5, padx = 0, sticky = tk.W)

com.Rdo_syojou2 = tk.Radiobutton(frm_kesseki2, text='熱', value = "熱", variable=com.SV_syojou, font = RdoFont, command = func_syojou)
com.Rdo_syojou2.grid(row =  2, column = 0, pady = 5, padx = 0, sticky = tk.W)

com.Rdo_syojou3 = tk.Radiobutton(frm_kesseki2, text='頭痛', value = "頭痛", variable=com.SV_syojou, font = RdoFont, command = func_syojou)
com.Rdo_syojou3.grid(row =  3, column = 0, pady = 5, padx = 0, sticky = tk.W)

com.Rdo_syojou4 = tk.Radiobutton(frm_kesseki2, text='腹痛', value = "腹痛", variable=com.SV_syojou, font = RdoFont, command = func_syojou)
com.Rdo_syojou4.grid(row =  4, column = 0, pady = 5, padx = 0, sticky = tk.W)

com.Rdo_syojou5 = tk.Radiobutton(frm_kesseki2, text='体調不良', value = "体調不良", variable=com.SV_syojou, font = RdoFont, command = func_syojou)
com.Rdo_syojou5.grid(row =  5, column = 0, pady = 5, padx = 0, sticky = tk.W)

com.Rdo_syojou6 = tk.Radiobutton(frm_kesseki2, text='不明', value = "不明", variable=com.SV_syojou, font = RdoFont, command = func_syojou)
com.Rdo_syojou6.grid(row =  6, column = 0, pady = 5, padx = 0, sticky = tk.W)

com.Rdo_syojou7 = tk.Radiobutton(frm_kesseki2, text='歯の病気', value = "歯の病気", variable=com.SV_syojou, font = RdoFont, command = func_syojou)
com.Rdo_syojou7.grid(row =  7, column = 0, pady = 5, padx = 0, sticky = tk.W)

com.Rdo_syojou8 = tk.Radiobutton(frm_kesseki2, text='耳の病気', value = "耳の病気", variable=com.SV_syojou, font = RdoFont, command = func_syojou)
com.Rdo_syojou8.grid(row =  8, column = 0, pady = 5, padx = 0, sticky = tk.W)

com.Rdo_syojou9 = tk.Radiobutton(frm_kesseki2, text='けが', value = "けが", variable=com.SV_syojou, font = RdoFont, command = func_syojou)
com.Rdo_syojou9.grid(row =  9, column = 0, pady = 5, padx = 0, sticky = tk.W)

com.Rdo_syojou10 = tk.Radiobutton(frm_kesseki2, text='該当なし', value = "該当なし", variable=com.SV_syojou, font = RdoFont, command = func_syojou)
com.Rdo_syojou10.grid(row =  10, column = 0, pady = 5, padx = 0, sticky = tk.W)

com.Rdo_syojou11 = tk.Radiobutton(frm_kesseki2, text='その他', value = "その他", variable=com.SV_syojou, font = RdoFont, command = func_syojou)
com.Rdo_syojou11.grid(row =  11, column = 0, pady = 5, padx = 0, sticky = tk.W)
com.Ent_syojou11 = ttk.Entry(frm_kesseki2, width=30, font = EntFont)
com.Ent_syojou11.grid(row = 11, column = 0, pady = 5, padx = 100, sticky = tk.W)
if com.SV_syojou.get() == "その他":
 com.Ent_syojou11.configure(state = "normal")
else:
 com.Ent_syojou11.configure(state = "disable")

# 画面をそのまま表示
frm_main.mainloop()


input_form.py

 こちらで、入力された情報を実際にGoogleフォームに入力していきます。

 ※Googleフォームのパスは伏せています。

import requests
from bs4 import BeautifulSoup
import time                                 
from time import sleep
from tkinter import messagebox
from selenium import webdriver
import sys
import os

#自作モジュールのインポート
import common_data as com

def func_init():

 #入力チェック
 #出席の場合
 if com.SV_syukketu.get() == "出席":
   if int(com.Ent_syusseki_no.get()) < 1 or 30 < int(com.Ent_syusseki_no.get()):
     return "出席番号あってる?"

   elif float(com.Ent_heinetsu.get()) < 36 or 37 < float(com.Ent_heinetsu.get()):
     return "平熱あってる?"

   elif float(com.Ent_taion.get()) < 35 or 40 < float(com.Ent_taion.get()):
     return "体温あってる?"

   else:
     return "OK"

 #欠席の場合
 else:
   if int(com.Ent_syusseki_no.get()) < 1 or 30 < int(com.Ent_syusseki_no.get()):
     return "出席番号あってる?"

   elif float(com.Ent_heinetsu.get()) < 36 or 37 < float(com.Ent_heinetsu.get()):
     return "平熱あってる?"

   elif com.SV_riyuu.get() == "その他":
     if com.Ent_riyuu5.get() == "":
       return "欠席する理由を入れてね"
     else:
       return "OK"
   
   elif com.SV_syojou.get() == "その他":
     if com.Ent_syojou11.get() == "":
       return "症状を入れてね"
     else:
       return "OK"

   else:
     return "OK"

def func_exe():
 msg = func_init()

 if not func_init() == "OK":
   messagebox.showinfo('確認', msg)
   return

 #学年を選択する
 gakunen_id = {
   '1年1組':'https://docs.google.com/forms/xxxxx',
   '1年2組':'https://docs.google.com/forms/xxxxx',
   '1年3組':'https://docs.google.com/forms/xxxxx',
   
   '2年1組':'https://docs.google.com/forms/xxxxx',
   '2年2組':'https://docs.google.com/forms/xxxxx',
   '2年3組':'https://docs.google.com/forms/xxxxx',
   
   '3年1組':'https://docs.google.com/forms/xxxxx',
   '3年2組':'https://docs.google.com/forms/xxxxx',
   '3年3組':'https://docs.google.com/forms/xxxxx',

   '4年1組':'https://docs.google.com/forms/xxxxx',
   '4年2組':'https://docs.google.com/forms/xxxxx',
   '4年3組':'https://docs.google.com/forms/xxxxx',

   '5年1組':'https://docs.google.com/forms/xxxxx',
   '5年2組':'https://docs.google.com/forms/xxxxx',
   '5年3組':'https://docs.google.com/forms/xxxxx',

   '6年1組':'https://docs.google.com/forms/xxxxx',
   '6年2組':'https://docs.google.com/forms/xxxxx',
   '6年3組':'https://docs.google.com/forms/xxxxx'
 }

 target_url = gakunen_id[com.SV_kurasu.get()]

 parameter0 = '?usp=pp_url'
 parameter1 = '&entry.240976875=' + com.Ent_syusseki_no.get()
 parameter2 = '&entry.611293682=' + com.Ent_namae.get()
 parameter3 = '&entry.157904834=' + com.Ent_heinetsu.get()
 parameter4 = '&entry.1931217194=' + com.SV_syukketu.get()


 def resource_path(relative_path):
   try:
     base_path = sys._MEIPASS
   except Exception:
     base_path = os.path.dirname(__file__)
   return os.path.join(base_path, relative_path)

 driver = webdriver.Chrome(resource_path('./driver/chromedriver.exe'))

 print(resource_path('./driver/chromedriver.exe'))

 driver.get(target_url + parameter0 + parameter1 + parameter2 + parameter3 + parameter4) 

 sleep(1)

 #次のフォームに遷移する
 next_button = driver.find_element_by_xpath('//*[@id="mG61Hd"]/div[2]/div/div[3]/div[1]/div[1]/div/span/span')
 next_button.click()
 sleep(2)

 #出席の場合---------------------------------------------------------------
 if com.SV_syukketu.get() == '出席':
   #当日の体温を入力する
   heinetsu_input = driver.find_element_by_xpath('//*[@id="mG61Hd"]/div[2]/div/div[2]/div[2]/div/div/div[3]/div/div[1]/div/div[1]/input')
   heinetsu_input.send_keys(com.Ent_taion.get())
   #sleep(1)

   #今日の気分を選択する
   kibun_id = {
     'とても気分が良い':'i9',
     '気分が良い':'i12',
     '普通の気分':'i15',
     '気分が悪い':'i18',
     'とても気分が悪い':'i21',
   }

   kibun_input = driver.find_element_by_id(kibun_id[com.SV_kibun.get()])
   kibun_input.click()
   #sleep(1)

   #今日の体調を選択する
   if com.BV_taityou1.get() == True:
     taityou1_input = driver.find_element_by_id('i29')
     taityou1_input.click()
     #sleep(1)

   if com.BV_taityou2.get() == True:
     taityou2_input = driver.find_element_by_id('i32')
     taityou2_input.click()
     #sleep(1)

   if com.BV_taityou3.get() == True:
     taityou3_input = driver.find_element_by_id('i35')
     taityou3_input.click()
     #sleep(1)

   if com.BV_taityou4.get() == True:
     taityou4_input = driver.find_element_by_id('i38')
     taityou4_input.click()
     #sleep(1)

   if com.BV_taityou5.get() == True:
     taityou5_input = driver.find_element_by_id('i41')
     taityou5_input.click()
     #sleep(1)

   if com.BV_taityou6.get() == True:
     taityou6_input = driver.find_element_by_id('i44')
     taityou6_input.click()
     #sleep(1)

   if com.BV_taityou7.get() == True:
     taityou7_input = driver.find_element_by_id('i47')
     taityou7_input.click()
     #sleep(1)

   if com.BV_taityou8.get() == True:
     taityou8_input = driver.find_element_by_id('i50')
     taityou8_input.click()
     #sleep(1)

   if com.BV_taityou9.get() == True:
     taityou9_input = driver.find_element_by_id('i53')
     taityou9_input.click()
     #sleep(1)

   #家族のかぜ症状有無を入力する
   kazoku_id = {
     'います':'i61',
     'いません':'i64'
   }

   kazoku_input = driver.find_element_by_id(kazoku_id[com.SV_kazoku.get()])
   kazoku_input.click()
   #sleep(1)


 #欠席の場合---------------------------------------------------------------
 else:
   #欠席の理由を選択する
   riyuu_id = {
     '体調不良のため':'i5',
     '登校に不安があるため':'i8',
     '家庭の事情':'i11',
     '身内に不幸があったため':'i14',
     'その他':'i17',
   }

   riyuu_input = driver.find_element_by_id(riyuu_id[com.SV_riyuu.get()])
   riyuu_input.click()

   if com.SV_riyuu.get() == 'その他':
     riyuu_sonota_input = driver.find_element_by_xpath('//*[@id="mG61Hd"]/div[2]/div/div[2]/div[2]/div/div/div[2]/div/div/span/div/div[5]/div/span/div/div/div[1]/input')
     riyuu_sonota_input.send_keys(com.Ent_riyuu5.get())

   #症状を選択する
   syojou_id = {
     '風邪':'i24',
     '熱':'i27',
     '頭痛':'i30',
     '腹痛':'i33',
     '体調不良':'i36',
     '不明':'i39',
     '歯の病気':'i42',
     '耳の病気':'i45',
     'けが':'i48',
     '該当なし':'i51',
     'その他':'i54'
   }

   syojou_input = driver.find_element_by_id(syojou_id[com.SV_syojou.get()])
   syojou_input.click()

   if com.SV_syojou.get() == 'その他':
     syojou_sonota_input = driver.find_element_by_xpath('//*[@id="mG61Hd"]/div[2]/div/div[2]/div[3]/div/div/div[2]/div/div/span/div/div[11]/div/span/div/div/div[1]/input')
     syojou_sonota_input.send_keys(com.Ent_syojou11.get())

   #次のフォームに遷移する
   next_button = driver.find_element_by_xpath('//*[@id="mG61Hd"]/div[2]/div/div[3]/div[1]/div[1]/div[2]/span/span')
   next_button.click()
   #sleep(2)

 messagebox.showinfo('確認', "入力したよ。\n内容を確認したら「送信」ボタンを押してね。")
 #sleep(1)

 driver.close()
 driver.quit()

 return "break"


おわりに

 冒頭の小学校からの連絡を受けてから、パソコン操作の練習も兼ねて、長男と一緒に小学校のホームページログインからGoogleフォームの入力をしていたのですが、

 制作したツールを家庭内リリースしてここ二日ほど、長男につかってもらい、さきほど感想をきいてみると、「このツールのほうがいい」とのことで、一応つくった甲斐がありました!(笑)

 

 今回、色々とGoogleフォームへの自動入力についてしらべてみると、同じように会社から提出を指示された入力フォームを、毎朝決まった時間に自動で実行させてみたり、その日の体温も36~37℃の間でランダムに設定させ、人の手による入力作業そのものを無くしている方もいらっしゃるようでした。

 さすがにそれはやりすぎですので、どこまで何を自動化したいか、を考えて自動化するのが肝要だとおもった次第です。


 最後までみていただき、ありがとうございました!



この記事が参加している募集

この記事が気に入ったらサポートをしてみませんか?