This commit is contained in:
Aaron Lee 2021-12-12 18:38:10 +08:00
parent a355887755
commit 22ef472527
29 changed files with 2444 additions and 1051 deletions

347
app.py
View file

@ -1,227 +1,148 @@
from functions import * from functions import *
from manage import manage from manage import manage
from upload import upload from upload import upload
from login import login
load_dotenv() load_dotenv()
app = Flask(__name__) app = Flask(__name__)
babel = Babel(app)
app.register_blueprint(manage) app.register_blueprint(manage)
app.register_blueprint(upload) app.register_blueprint(upload)
app.register_blueprint(login)
app.config['SECRET_KEY'] = os.environ.get('SECRET_KEY') app.config['SECRET_KEY'] = os.environ.get('SECRET_KEY')
app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql+pymysql://'+os.environ.get('MYSQL_USER')+':'+os.environ.get('MYSQL_PASSWORD')+'@'+os.environ.get('MYSQL_HOST')+'/attendance'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
app.config['BABEL_DEFAULT_LOCALE'] = 'zh_TW'
app.jinja_env.add_extension('jinja2.ext.loopcontrols')
sdb = SQLAlchemy(app)
class DefaultModelView(ModelView):
@app.after_request restricted = True
def add_header(response): def __init__(self, model, session, restricted=True, name=None, category=None, endpoint=None, url=None, **kwargs):
response.headers['SameSite'] = "Strict" self.restricted = restricted
return response for k, v in kwargs.items():
setattr(self, k, v)
setattr(self, 'can_export', True)
@ app.route('/', methods=['GET', 'POST']) super(DefaultModelView, self).__init__(model, session, name=name, category=category, endpoint=endpoint, url=url)
def index(): def is_accessible(self):
if request.method == 'GET': if self.restricted == True:
if check_login_status(): return ((not check_login_status()) and is_admin() and check_permission())
return render_template('login.html') return ((not check_login_status()) and is_admin())
return redirect('/select') def inaccessible_callback(self, name, **kwargs):
elif request.method == 'POST': return redirect('/')
email = request.form['username'] class MyAdminIndexView(AdminIndexView):
if check_login_status(): def is_accessible(self):
try: return ((not check_login_status()) and is_admin())
if (verify_recaptcha("")): def inaccessible_callback(self, name, **kwargs):
user = auth.sign_in_with_email_and_password(
email, request.form['password'])
usrData = db.child("Users").child(user['localId']).child("permission").get(
user['idToken']).val()
if (usrData == 'realPerson'):
print("RealPerson Login SUCC:", email, flush=True)
session['is_logged_in'] = True
session['email'] = user['email']
session['uid'] = user['localId']
session['token'] = user['idToken']
session['refreshToken'] = user['refreshToken']
session['loginTime'] = datetime.now(tz)
return redirect('/select')
if (usrData == 'admin'):
print("Admin Login SUCC:", email, flush=True)
session['subuser_type'] = 'admin'
session['is_logged_in'] = True
session['email'] = user['email']
session['uid'] = user['localId']
session['token'] = user['idToken']
session['refreshToken'] = user['refreshToken']
session['loginTime'] = datetime.now(tz)
session['showUpload'] = db.child("Users").child(
session['uid']).child("showUpload").get(session['token']).val()
return redirect('/manage')
raise Exception("not real person or admin")
else:
print("ReC Error:", email, flush=True)
flash(
'reCAPTCHA 錯誤,請稍後再試一次<br>reCAPTCHA Failed. Please try again later.')
return redirect('/')
except Exception as e:
print("Error*Login:", email, str(e), flush=True)
flash(
'帳號或密碼錯誤,請重新輸入<br>Incorrect username or password')
return redirect('/')
else:
return redirect('/select')
@app.route('/select', methods=['GET', 'POST'])
def selSubUser():
if check_login_status():
session.clear()
flash("Timeout. 遇時,請重新登入")
return redirect('/') return redirect('/')
refresh_token()
if 'subuser_type' in session and session['subuser_type'] == 'admin':
return redirect('/manage')
if request.method == 'GET':
usrData = db.child("Users").child(session['uid']).get(
session['token']).val()
session['subuser_type'] = ''
return render_template('selSubUser.html', data=usrData['accounts'], name=usrData['name'])
else:
data = request.form['subuser_sel'].split('^')
try:
if (verify_recaptcha("")):
if (data[0] == 'homeroom'):
session['homeroom'] = data[1] + '^' + data[2]
session['subuser_type'] = 'homeroom'
elif (data[0] == 'group'):
session['category'] = data[1]
session['class'] = data[2]
session['subuser_type'] = 'group'
return redirect('/manage')
else:
print("ReC Error:", data, flush=True)
flash(
'reCAPTCHA 錯誤,請稍後再試一次<br>reCAPTCHA Failed. Please try again later.')
return redirect('/select')
except Exception as e:
print("Error*select:", session['email'], str(json.loads(e.args[1])[
'error']['message']), flush=True)
flash(str(json.loads(e.args[1])[
'error']['message']))
return redirect('/select')
@app.route('/chgPassword', methods=['POST', 'GET'])
def chgPassword():
data = {}
if request.method == 'GET':
if not check_login_status():
refresh_token()
return render_template('chgPassword.html')
else:
return abort(404)
elif request.method == 'POST':
oldEmail = session['email']
delUser = False
if not check_login_status():
refresh_token()
try:
if (verify_recaptcha("")):
oldUsr = auth.sign_in_with_email_and_password(
oldEmail, request.form['password'])
print("chgPwd oldUser:", oldEmail, flush=True)
old = {}
old['uid'] = oldUsr['localId']
old['token'] = oldUsr['idToken']
data = db.child("Users").child(
oldUsr['localId']).get(oldUsr['idToken']).val()
auth.delete_user_account(oldUsr['idToken'])
delUser = True
newUsr = auth.create_user_with_email_and_password(
request.form['new_username'], request.form['new_password'])
db.child("Users").child(newUsr['localId']).set(
data, newUsr['idToken'])
db.child("Users").child(oldUsr['localId']).remove(oldUsr['idToken'])
session.clear()
flash(
'修改密碼成功,請重新登入<br>Password changed successfully. Please login again.')
return redirect('/')
else:
print("ReC Error:", oldEmail, flush=True)
flash(
'reCAPTCHA 錯誤,請稍後再試一次<br>reCAPTCHA Failed. Please try again later.')
return redirect('/chgPassword')
except Exception as e:
if delUser:
try:
usr = auth.create_user_with_email_and_password(
oldEmail, request.form['password'])
db.child("Users").child(usr['localId']).set(
data, usr['idToken'])
except:
pass
print("Error*chgPassword:", oldEmail, str(json.loads(e.args[1])[
'error']['message']), flush=True)
flash(str(json.loads(e.args[1])[
'error']['message']))
return redirect('/chgPassword')
@app.route('/forgotPassword', methods=['GET', 'POST'])
def forgotPassword():
if request.method == 'GET':
return render_template('forgotPassword.html')
elif request.method == 'POST':
email = request.form['username']
try:
if (verify_recaptcha("")):
auth.send_password_reset_email(email)
print("forgotPassword email sent:", email, flush=True)
flash(
'重置密碼信件已寄出,請至信箱收取<br>Password reset email has been sent to your email. Please check your email.')
return redirect('/')
else:
print("ReC Error:", email, flush=True)
flash(
'reCAPTCHA 錯誤,請稍後再試一次<br>reCAPTCHA Failed. Please try again later.')
return redirect('/forgotPassword')
except Exception as e:
print("Error*forgotPassword:", email, str(json.loads(e.args[1])[
'error']['message']), flush=True)
flash(str(json.loads(e.args[1])[
'error']['message']))
return redirect('/forgotPassword')
@app.route('/resetPassword', methods=['GET', 'POST'])
def resetPassword():
if request.args.get('oobCode') is None:
return abort(404)
if request.method == 'GET':
return render_template('verifiedChgPassword.html', oobCode=request.args.get('oobCode'))
else:
try:
if (verify_recaptcha("")):
auth.verify_password_reset_code(
request.args.get('oobCode'), request.form['password'])
print("resetPassword success:", flush=True)
session.clear()
flash('重置密碼成功,請重新登入<br>Password reset success. Please login again.')
return redirect('/')
else:
print("ReC Error:", flush=True)
flash(
'reCAPTCHA 錯誤,請稍後再試一次<br>reCAPTCHA Failed. Please try again later.')
return redirect('/resetPassword')
except Exception as e:
print("Error*resetPassword:", request.args.get('oobCode'), str(json.loads(e.args[1])[
'error']['message']), flush=True)
flash(str(json.loads(e.args[1])[
'error']['message']))
return redirect('/resetPassword?mode=resetPassword&oobCode=' + request.args.get('oobCode'))
@ app.route('/logout', methods=['GET'])
def logout():
session.clear()
return redirect('/')
admin = Admin(
app,
name='Attendance 點名系統 後台管理',
template_mode='bootstrap3',
index_view=MyAdminIndexView(),
)
class Users(sdb.Model):
id = sdb.Column(sdb.INT, primary_key=True)
email = sdb.Column(sdb.Text)
name = sdb.Column(sdb.Text)
oldUsername = sdb.Column(sdb.Text)
role = sdb.Column(sdb.CHAR)
password = sdb.Column(sdb.Text)
class Students(sdb.Model):
id = sdb.Column(sdb.INT, primary_key=True)
email = sdb.Column(sdb.INT)
grade = sdb.Column(sdb.INT)
class_ = sdb.Column(sdb.INT)
num = sdb.Column(sdb.INT)
name = sdb.Column(sdb.Text)
ename = sdb.Column(sdb.Text)
classes = sdb.Column(sdb.Text)
password = sdb.Column(sdb.Text)
class Schedule(sdb.Model):
id = sdb.Column(sdb.INT, primary_key=True)
grade = sdb.Column(sdb.INT)
class_ = sdb.Column(sdb.INT)
dow = sdb.Column(sdb.INT)
period = sdb.Column(sdb.CHAR)
subject = sdb.Column(sdb.Text)
teacher = sdb.Column(sdb.Text)
class SpecSchedule(sdb.Model):
__tablename__ = 'specschedule'
id = sdb.Column(sdb.INT, primary_key=True)
grade = sdb.Column(sdb.INT)
class_ = sdb.Column(sdb.INT)
date = sdb.Column(sdb.VARCHAR(11))
period = sdb.Column(sdb.CHAR)
subject = sdb.Column(sdb.Text)
teacher = sdb.Column(sdb.Text)
class GPClasses(sdb.Model):
__tablename__ = 'gpclasses'
id = sdb.Column(sdb.INT, primary_key=True)
category = sdb.Column(sdb.Text)
subclass = sdb.Column(sdb.Text)
about = sdb.Column(sdb.Text)
accs = sdb.Column(sdb.Text)
class Homerooms(sdb.Model):
id = sdb.Column(sdb.INT, primary_key=True)
grade = sdb.Column(sdb.INT)
class_ = sdb.Column(sdb.INT)
accs = sdb.Column(sdb.Text)
class Submission(sdb.Model):
id = sdb.Column(sdb.INT, primary_key=True)
grade = sdb.Column(sdb.INT)
class_ = sdb.Column(sdb.INT)
date = sdb.Column(sdb.VARCHAR(11))
period = sdb.Column(sdb.CHAR)
signature = sdb.Column(sdb.Text)
ds1 = sdb.Column(sdb.INT)
ds2 = sdb.Column(sdb.INT)
ds3 = sdb.Column(sdb.INT)
ds4 = sdb.Column(sdb.INT)
ds5 = sdb.Column(sdb.INT)
ds6 = sdb.Column(sdb.INT)
ds7 = sdb.Column(sdb.INT)
notes = sdb.Column(sdb.Text)
class DS(sdb.Model):
id = sdb.Column(sdb.INT, primary_key=True)
grade = sdb.Column(sdb.INT)
class_ = sdb.Column(sdb.INT)
num = sdb.Column(sdb.INT)
date = sdb.Column(sdb.VARCHAR(11))
period = sdb.Column(sdb.CHAR)
note = sdb.Column(sdb.Text)
status = sdb.Column(sdb.CHAR, default='X')
class Dates(sdb.Model):
id = sdb.Column(sdb.INT, primary_key=True)
date = sdb.Column(sdb.VARCHAR(11))
dow = sdb.Column(sdb.INT)
class Absent(sdb.Model):
id = sdb.Column(sdb.INT, primary_key=True)
grade = sdb.Column(sdb.INT)
class_ = sdb.Column(sdb.INT)
num = sdb.Column(sdb.INT)
date = sdb.Column(sdb.VARCHAR(11))
period = sdb.Column(sdb.CHAR)
status = sdb.Column(sdb.CHAR)
note = sdb.Column(sdb.Text)
class Forgot(sdb.Model):
id = sdb.Column(sdb.INT, primary_key=True)
resetID = sdb.Column(sdb.VARCHAR(11))
email = sdb.Column(sdb.Text)
reqTime = sdb.Column(sdb.VARCHAR(20))
admin.add_view(DefaultModelView(Users, sdb.session, restricted=False, column_exclude_list = ['password'], column_searchable_list = ['name', 'email', 'role']))
admin.add_view(DefaultModelView(Students, sdb.session, restricted=False, column_exclude_list = ['password'], column_searchable_list = ['grade', 'class_', 'num', 'email','name', 'ename', 'classes']))
admin.add_view(DefaultModelView(Schedule, sdb.session, column_searchable_list = ['grade', 'class_', 'dow', 'period', 'subject', 'teacher']))
admin.add_view(DefaultModelView(SpecSchedule, sdb.session, restricted=False, column_searchable_list = ['grade', 'class_', 'date', 'period', 'subject', 'teacher']))
admin.add_view(DefaultModelView(GPClasses, sdb.session, column_searchable_list = ['category', 'subclass', 'about', 'accs']))
admin.add_view(DefaultModelView(Homerooms, sdb.session, column_searchable_list = ['grade', 'class_', 'accs']))
admin.add_view(DefaultModelView(Submission, sdb.session, column_exclude_list=['signature'], column_searchable_list = ['grade', 'class_', 'date', 'period', 'notes']))
admin.add_view(DefaultModelView(DS, sdb.session, restricted=False, column_searchable_list = ['grade', 'class_', 'date', 'period', 'num', 'note', 'status']))
admin.add_view(DefaultModelView(Dates, sdb.session, column_searchable_list = ['date', 'dow']))
admin.add_view(DefaultModelView(Absent, sdb.session, restricted=False, column_searchable_list = ['grade', 'class_', 'date', 'period', 'num', 'status', 'note']))
admin.add_view(DefaultModelView(Forgot, sdb.session, column_searchable_list = ['resetID', 'email', 'reqTime']))
admin.add_link(MenuLink(name='Back to Home 返回一般管理', category='', url='/manage'))
admin.add_link(MenuLink(name='Logout 登出', category='', url='/logout'))
if __name__ == '__main__': if __name__ == '__main__':
app.run(debug=True) app.run(debug=True, host='0.0.0.0', port=80)

152
db.sql Normal file
View file

@ -0,0 +1,152 @@
USE attendance;
CREATE TABLE users (
id INT NOT NULL AUTO_INCREMENT,
PRIMARY KEY (id),
email TEXT,
name TEXT,
oldUsername TEXT,
role CHAR, /* S: SuperAdmin / A: Admin / R: Regular User */
password TEXT
);
CREATE TABLE students (
id INT NOT NULL AUTO_INCREMENT,
PRIMARY KEY (id),
email TEXT,
grade INT,
class_ INT,
num INT,
name TEXT,
ename TEXT,
classes TEXT,
password TEXT
/* update column as wishes for group classes
ALTER TABLE students ADD COLUMN IF NOT EXISTS "" VARCHAR(255); */
);
--- Schedule of different day for different class
CREATE TABLE schedule (
id INT NOT NULL AUTO_INCREMENT,
PRIMARY KEY (id),
grade INT,
class_ INT,
dow INT,
period CHAR,
subject TEXT,
teacher TEXT
);
CREATE TABLE specschedule (
id INT NOT NULL AUTO_INCREMENT,
PRIMARY KEY (id),
grade INT,
class_ INT,
date TEXT,
period CHAR,
subject TEXT,
teacher TEXT
);
CREATE TABLE gpclasses (
id INT NOT NULL AUTO_INCREMENT,
PRIMARY KEY (id),
category TEXT,
subclass TEXT,
about TEXT,
accs TEXT
/* Save as JSON
{
0: 'acc1',
1: 'acc2'
}
*/
);
CREATE TABLE homerooms (
id INT NOT NULL AUTO_INCREMENT,
PRIMARY KEY (id),
grade INT,
class_ INT,
accs TEXT
/* Save as JSON
{
0: 'acc1',
1: 'acc2'
}
*/
);
DELETE FROM submission WHERE grade=9;
CREATE TABLE submission (
id INT NOT NULL AUTO_INCREMENT,
PRIMARY KEY (id),
grade INT,
class_ INT,
date VARCHAR(11),
period CHAR,
signature LONGTEXT,
/*
Save as JSON
{
subClass: "signature",
subClass2: "signature2"
}
or
plain text if not GP
*/
ds1 INT DEFAULT 5,
ds2 INT DEFAULT 5,
ds3 INT DEFAULT 5,
ds4 INT DEFAULT 5,
ds5 INT DEFAULT 5,
ds6 INT DEFAULT 5,
ds7 INT DEFAULT 5,
notes TEXT
/*
Save as JSON
{
'num': 'whatevernote',
'num2': 'morenote'
}
*/
);
CREATE TABLE ds (
id INT NOT NULL AUTO_INCREMENT,
PRIMARY KEY (id),
grade INT,
class_ INT,
date VARCHAR(11),
period CHAR,
num INT,
note TEXT,
status CHAR DEFAULT 'X'
);
CREATE TABLE dates (
id INT NOT NULL AUTO_INCREMENT,
PRIMARY KEY (id),
date VARCHAR(11),
dow INT
);
CREATE TABLE absent (
id INT NOT NULL AUTO_INCREMENT,
PRIMARY KEY (id),
grade INT,
class_ INT,
date VARCHAR(11),
period CHAR,
num INT,
status CHAR, /* L: 遲到 / K: 曠課 / G: 事假 / S: 病假 / F: 喪假 / P: 疫情假 / O: 公假*/
note TEXT
);
CREATE TABLE forgot (
id INT NOT NULL AUTO_INCREMENT,
PRIMARY KEY (id),
userType CHAR, /* T: teacher / S: student */
resetID VARCHAR(11),
email TEXT,
reqTime VARCHAR(20)
);

View file

@ -1,7 +1,6 @@
from flask import * from flask import *
from typing import OrderedDict from typing import OrderedDict
from flask import * from flask import *
import pyrebase
from datetime import datetime from datetime import datetime
import pytz import pytz
import os import os
@ -9,32 +8,65 @@ import base64
import csv import csv
import os import os
import pandas as pd import pandas as pd
from random import randint from random import randint, choices
import string
from dotenv import load_dotenv from dotenv import load_dotenv
from passlib.hash import sha256_crypt
import mysql.connector
import requests
from pprint import pprint
from flask_sqlalchemy import SQLAlchemy
from flask_admin import Admin, AdminIndexView, expose
from flask_admin.menu import MenuLink
from flask_admin.contrib.sqla import ModelView
from flask_babelex import Babel
load_dotenv() load_dotenv()
config = {
"apiKey": os.environ.get('apiKey'),
"authDomain": os.environ.get('authDomain'),
"databaseURL": os.environ.get('databaseURL'),
"storageBucket": os.environ.get('storageBucket'),
"serviceAccount": os.environ.get('serviceAccount'),
"messagingSenderId": os.environ.get('messagingSenderId'),
"appId": os.environ.get('appId'),
"measurementId": os.environ.get('measurementId'),
}
firebase = pyrebase.initialize_app(config)
db = firebase.database()
auth = firebase.auth()
storage = firebase.storage()
tz = pytz.timezone('Asia/Taipei') tz = pytz.timezone('Asia/Taipei')
DSBOARD = [
"上課前秩序",
"上課前禮貌",
"課間秩序",
"板擦清潔",
"講桌乾淨",
"地板整齊",
"桌椅整齊"
]
DSTEXT = [
"",
"",
"",
"",
"",
"",
""
]
DSOFFENSES = {
'A': "把玩物品、不專心聽講",
'B': "書寫或傳遞紙條、物品",
'C': "自言自語或與同學交談",
'D': "接話、大聲笑、起哄、發出怪聲",
'E': "亂動、逗弄同學、影響教學情境",
'F': "閱讀與該堂課無關之書籍",
'G': "不敬師長、態度傲慢",
'H': "其他經任教老師糾正、制止之行為",
'Z': "上課睡覺"
}
def refresh_db():
return mysql.connector.connect(user=os.environ.get('MYSQL_USER'), password=os.environ.get('MYSQL_PASSWORD'),
host=os.environ.get('MYSQL_HOST'),
database='attendance')
def genHash(password):
return sha256_crypt.hash(password)
def verifyPassword(password, hash):
return sha256_crypt.verify(password, hash)
def refresh_token(): def refresh_token():
user = auth.refresh(session['refreshToken'])
session['is_logged_in'] = True session['is_logged_in'] = True
session['token'] = user['idToken']
session['refreshToken'] = user['refreshToken']
session['loginTime'] = datetime.now(tz) session['loginTime'] = datetime.now(tz)
@ -47,6 +79,25 @@ def check_login_status():
session['is_logged_in'] == False or session['is_logged_in'] == False or
(datetime.now(tz) - session['loginTime']).total_seconds() > 3600) (datetime.now(tz) - session['loginTime']).total_seconds() > 3600)
def send_email(to, subject, text):
return requests.post(
"https://api.mailgun.net/v3/mg.aaronlee.tech/messages",
auth=("api", os.environ.get("MG_APIKEY")),
data={"from": "Attendance 點名系統 <attendance@mg.aaronlee.tech>",
"to": [to],
"subject": subject,
"html": text})
def getName(grade, class_, number):
db = refresh_db()
cursor = db.cursor()
print(grade, class_, number)
cursor.execute("SELECT name FROM students WHERE grade=%s AND class=%s AND number=%s", (grade, class_, number))
name = cursor.fetchone()
cursor.close()
db.close()
return name[0]
# LOGIN # LOGIN
@ -63,20 +114,14 @@ def verify_recaptcha(response):
return r.json()['success'] return r.json()['success']
# UPLOAD # UPLOAD
def is_admin():
return 'subuser_type' in session and session['subuser_type'] == 'admin'
def check_permission(): def check_permission():
return (db.child('Users').child(session['uid']).child('permission').get(session['token']).val() == 'admin' and if 'subuser_type' in session and session['subuser_type'] == 'admin':
db.child("Users").child(session['uid']).child("showUpload").get(session['token']).val() == '1') return session['showUpload']
def addZeroesUntil(str, number):
if len(str) >= number:
return str
else: else:
str = str + '0' return False
return addZeroesUntil(str, number)
# MANAGE # MANAGE
def removeprefix(s, prefix): def removeprefix(s, prefix):

329
login.py Normal file
View file

@ -0,0 +1,329 @@
from functions import *
login = Blueprint('login', __name__)
@login.after_request
def add_header(response):
response.headers['SameSite'] = "Strict"
return response
@login.route('/', methods=['GET', 'POST'])
def index():
if request.method == 'GET':
if check_login_status():
return render_template('login.html')
return redirect('/select')
elif request.method == 'POST':
email = request.form['username']
if check_login_status():
try:
if (verify_recaptcha("")):
if request.form['user_type'] == 'teacher':
db = refresh_db()
cursor = db.cursor(buffered=True)
cursor.execute("SELECT name, role, oldUsername, password FROM users WHERE email = %s", (email,))
user = cursor.fetchone()
cursor.close()
if user == None or not verifyPassword(request.form['password'], user[3]):
raise Exception('Invalid Login')
usrRole = user[1]
if (usrRole == 'R'):
print("RealPerson Login SUCC:", email, flush=True)
session['is_logged_in'] = True
session['email'] = email
session['name'] = user[0]
session['oldUsername'] = user[2]
session['loginTime'] = datetime.now(tz)
return redirect('/select')
if (usrRole == 'A' or usrRole == 'S'):
print("Admin Login SUCC:", email, flush=True)
session['subuser_type'] = 'admin'
session['is_logged_in'] = True
session['email'] = email
session['oldUsername'] = user[2]
session['loginTime'] = datetime.now(tz)
session['showUpload'] = True if usrRole == 'S' else False
return redirect('/manage')
raise Exception("not real person or admin")
elif request.form['user_type'] == 'student':
db = refresh_db()
cursor = db.cursor(buffered=True)
cursor.execute("SELECT password, grade, class_, num, name FROM students WHERE email = %s", (email,))
user = cursor.fetchone()
cursor.close()
if user == None or not verifyPassword(request.form['password'], user[0]):
raise Exception('Invalid Login')
print("Student Login SUCC:", email, flush=True)
session['is_logged_in'] = True
session['email'] = email
session['loginTime'] = datetime.now(tz)
session['user_type'] = 'student'
session['name'] = user[4]
session['grade'] = user[1]
session['class'] = user[2]
session['num'] = user[3]
return redirect('/manage')
else:
print("ReC Error:", email, flush=True)
flash(
'reCAPTCHA 錯誤,請稍後再試一次<br>reCAPTCHA Failed. Please try again later.')
return redirect('/')
except Exception as e:
print("Error*Login:", email, str(e), flush=True)
flash(
'帳號或密碼錯誤,請重新輸入<br>Incorrect username or password')
return redirect('/')
else:
return redirect('/select')
@login.route('/select', methods=['GET', 'POST'])
def selSubUser():
if check_login_status():
session.clear()
flash("Timeout. 遇時,請重新登入")
return redirect('/')
refresh_token()
if 'subuser_type' in session and session['subuser_type'] == 'admin' or 'user_type' in session and session['user_type'] == 'student':
return redirect('/manage')
if request.method == 'GET':
db = refresh_db()
cursor = db.cursor(buffered=True)
cursor.execute("SELECT category, subclass FROM gpclasses WHERE accs LIKE %s LIMIT 1", ('%'+session['oldUsername']+'%',))
classes = cursor.fetchone()
cursor.close()
hasGroup = False
if classes != None:
hasGroup = True
db = refresh_db()
cursor = db.cursor(buffered=True)
cursor.execute("SELECT grade, class_ FROM homerooms WHERE accs LIKE %s", ('%'+session['oldUsername']+'%',))
homerooms = cursor.fetchall()
cursor.close()
hrC = {}
for h in homerooms:
hrC[h[0]] = []
hrC[h[0]].append(h[1])
return render_template('selSubUser.html', group=hasGroup, homeroom=hrC, name=session['name'])
else:
data = request.form['subuser_sel'].split('^')
if data == []:
return redirect('/select')
try:
if (verify_recaptcha("")):
if (data[0] == 'homeroom'):
session['homeroom'] = data[1] + '^' + data[2]
session['subuser_type'] = 'homeroom'
elif (data[0] == 'group'):
session['subuser_type'] = 'group'
return redirect('/manage')
else:
print("ReC Error:", data, flush=True)
flash(
'reCAPTCHA 錯誤,請稍後再試一次<br>reCAPTCHA Failed. Please try again later.')
return redirect('/select')
except Exception as e:
print("Error*select:", session['email'], str(e), flush=True)
flash(str(e))
return redirect('/select')
@login.route('/chgPassword', methods=['POST', 'GET'])
def chgPassword():
data = {}
if request.method == 'GET':
if not check_login_status():
refresh_token()
return render_template('chgPassword.html')
else:
return abort(404)
elif request.method == 'POST':
oldEmail = session['email']
if not check_login_status():
refresh_token()
try:
if (verify_recaptcha("")):
db = refresh_db()
cursor = db.cursor(buffered=True)
if ('user_type' in session and session['user_type'] == 'student'):
cursor.execute("SELECT password FROM students WHERE email = %s", (oldEmail,))
else:
cursor.execute("SELECT password FROM users WHERE email = %s", (oldEmail,))
user = cursor.fetchone()
cursor.close()
if user == None or not verifyPassword(request.form['password'], user[0]):
raise Exception('Invalid Login')
if len(request.form['new_password']) < 6:
raise Exception('密碼長度不足<br>Password not long enough')
db = refresh_db()
cursor = db.cursor(buffered=True)
if (request.form['new_username'] != oldEmail and request.form['new_username'] != ''):
if ('user_type' in session and session['user_type'] == 'student'):
cursor.execute("SELECT * FROM students WHERE email = %s", (request.form['new_username'],))
else:
cursor.execute("SELECT * FROM users WHERE email = %s", (request.form['new_username'],))
user = cursor.fetchone()
cursor.close()
if user != None:
raise Exception('帳號已被使用<br>Username already used')
db = refresh_db()
cursor = db.cursor(buffered=True)
if ('user_type' in session and session['user_type'] == 'student'):
cursor.execute("UPDATE students SET password = %s WHERE email = %s", (genHash(request.form['new_password']), oldEmail))
if (request.form['new_username'] != oldEmail and request.form['new_username'] != ''):
cursor.execute("UPDATE students SET email = %s WHERE email = %s", (request.form['new_username'], oldEmail))
else:
cursor.execute("UPDATE users SET password = %s WHERE email = %s", (
genHash(request.form['new_password']), oldEmail))
if (request.form['new_username'] != oldEmail and request.form['new_username'] != ''):
cursor.execute("UPDATE users SET email = %s WHERE email = %s", (request.form['new_username'], oldEmail))
db.commit()
cursor.close()
session.clear()
if (request.form['new_username'] != oldEmail and request.form['new_username'] != ''):
send_email(oldEmail, "Email Changed 信箱已更改",
"""<hr>
Your email was changed at %s to %s.<br>
If you did not change your email, please contact the student affair's office immediately.
<hr>
你的信箱已在 %s 更改為 %s <br>
如果你沒有更改信箱請立即聯絡學務處
<hr>
<small>This email was sent automatically. Please do not reply.<br>
這個郵件是自動發送的請不要回覆</small>
""" % (str(datetime.now(tz)), request.form['new_username'], str(datetime.now(tz)), request.form['new_username']))
flash(
'修改密碼成功,請重新登入<br>Password changed successfully. Please login again.')
return redirect('/')
else:
print("ReC Error:", oldEmail, flush=True)
flash(
'reCAPTCHA 錯誤,請稍後再試一次<br>reCAPTCHA Failed. Please try again later.')
return redirect('/chgPassword')
except Exception as e:
flash(str(e))
return redirect('/chgPassword')
@login.route('/forgotPassword', methods=['GET', 'POST'])
def forgotPassword():
if request.method == 'GET':
return render_template('forgotPassword.html')
elif request.method == 'POST':
email = request.form['username']
try:
if (verify_recaptcha("")):
db = refresh_db()
cursor = db.cursor(buffered=True)
if (request.form['user_type'] == 'student'):
cursor.execute("SELECT * FROM students WHERE email = %s", (email,))
elif (request.form['user_type'] == 'teacher'):
cursor.execute("SELECT email FROM users WHERE email = %s", (email,))
user = cursor.fetchone()
cursor.close()
if user == None:
raise Exception('無此 Email<br>Invalid Email')
exists = True
while exists:
resetID = ''.join(choices(string.ascii_lowercase + string.digits, k=10))
cursor = db.cursor(buffered=True)
cursor.execute("SELECT * FROM forgot WHERE resetID = %s", (resetID,))
user = cursor.fetchone()
cursor.close()
exists = (user != None)
db = refresh_db()
cursor = db.cursor(buffered=True)
cursor.execute("""
INSERT INTO forgot (resetID, email, reqTime, userType)
VALUES (%s, %s, %s, %s)
""", (resetID, email, datetime.strftime(datetime.now(tz), '%Y-%m-%d %H:%M:%S'), 'T' if request.form['user_type'] == 'teacher' else 'S'))
db.commit()
cursor.close()
send_email(email, "Password Reset 重置密碼",
"""<hr>
Please go to the following link to reset your password:<br>
https://abs.aaronlee.tech/resetPassword?resetCode=%s<br>
If you did not request a password reset, please ignore this email.
<hr>
請點選以下連結重置密碼<br>
https://abs.aaronlee.tech/resetPassword?resetCode=%s<br>
如果您沒有要求重置密碼請忽略此郵件
<hr>
<small>This email was sent automatically. Please do not reply.<br>
這個郵件是自動發送的請不要回覆</small>
""" % (resetID, resetID))
print("forgotPassword email sent:", email, flush=True)
flash(
'重置密碼信件已寄出,請至信箱收取<br>Password reset email has been sent to your email. Please check your email.')
return redirect('/')
else:
print("ReC Error:", email, flush=True)
flash(
'reCAPTCHA 錯誤,請稍後再試一次<br>reCAPTCHA Failed. Please try again later.')
return redirect('/forgotPassword')
except Exception as e:
print("Error*forgotPassword:", email, str(escape), flush=True)
flash(str(e))
return redirect('/forgotPassword')
@login.route('/resetPassword', methods=['GET', 'POST'])
def resetPassword():
if request.args.get('resetCode') is None:
return abort(404)
if request.method == 'GET':
return render_template('verifiedChgPassword.html', resetCode=request.args.get('resetCode'))
else:
try:
if (verify_recaptcha("")):
db = refresh_db()
cursor = db.cursor(buffered=True)
cursor.execute("""
SELECT resetID, email, reqTime, userType
FROM forgot
WHERE resetID = %s
""", (request.args.get('resetCode'),))
user = cursor.fetchone()
cursor.close()
if user == None:
raise Exception('無此重置密碼代碼<br>Invalid reset password code')
if (datetime.now(tz) - datetime.strptime(user[2], '%Y-%m-%d %H:%M:%S')).seconds > 3600:
cursor.execute("DELETE FROM forgot WHERE resetID = %s", (user[0],))
db.commit()
cursor.close()
raise Exception('重置密碼代碼已過期<br>Reset password code expired')
if len(request.form['password']) < 6:
raise Exception('密碼長度不足<br>Password not long enough')
db = refresh_db()
cursor = db.cursor(buffered=True)
if (user[3] == 'T'):
cursor.execute("UPDATE users SET password = %s WHERE email = %s", (
genHash(request.form['password']), user[1]))
elif (user[3] == 'S'):
cursor.execute("UPDATE students SET password = %s WHERE email = %s", (
genHash(request.form['password']), user[1]))
db.commit()
cursor.close()
db = refresh_db()
cursor = db.cursor(buffered=True)
cursor.execute("DELETE FROM forgot WHERE resetID = %s", (user[0],))
db.commit()
cursor.close()
session.clear()
flash(
'重置密碼成功,請重新登入<br>Password changed successfully. Please login again.')
return redirect('/')
else:
print("ReC Error:", flush=True)
flash(
'reCAPTCHA 錯誤,請稍後再試一次<br>reCAPTCHA Failed. Please try again later.')
return redirect('/resetPassword')
except Exception as e:
print("Error*resetPassword:", request.args.get('resetCode'), str(e), flush=True)
flash(str(e))
return redirect('/resetPassword?resetCode=' + request.args.get('resetCode'))
@login.route('/logout', methods=['GET'])
def logout():
session.clear()
return redirect('/')

758
manage.py
View file

@ -6,137 +6,230 @@ manage = Blueprint('manage', __name__)
def manageProcess(fCommand, fData): def manageProcess(fCommand, fData):
if (check_login_status()): if (check_login_status()):
return redirect('/logout') return redirect('/logout')
# this is to fix a bug where pyrebase doesnt load the first request
db.child("Users").child(
session['uid']).child("permission").get(session['token']).val()
# end bug fix
refresh_token() refresh_token()
if 'user_type' in session and session['user_type'] == 'student':
return redirect('/student')
pl = session['subuser_type'] pl = session['subuser_type']
if pl == 'admin': if pl == 'admin':
homerooms = db.child("Homerooms").get(session['token']).val() db = refresh_db()
cursor = db.cursor()
cursor.execute("SELECT grade, class_ FROM homerooms ORDER BY grade ASC, class_ ASC")
homeroomsSQL = cursor.fetchall()
homerooms = {}
for h in homeroomsSQL:
if h[0] in homerooms:
homerooms[h[0]].append(h[1])
else:
homerooms[h[0]] = [h[1]]
currRoom = [] currRoom = []
if fCommand == "admin": if fCommand == "admin":
currRoom = fData[0].split("^") currRoom = fData[0].split("^")
else: else:
for i in homerooms: currRoom = [homeroomsSQL[0][0], homeroomsSQL[0][1]]
currRoom.append(i) cursor = db.cursor()
for j in homerooms[i]: cursor.execute("SELECT num,name,ename,classes FROM students WHERE grade=%s AND class_=%s ORDER BY num ASC", (currRoom[0], currRoom[1]))
currRoom.append(j) students = cursor.fetchall()
break studGP = {}
break for s in students:
homeroomData = homerooms[currRoom[0]][currRoom[1]] studGP[s[0]] = json.loads(s[3])
absData = homeroomData["Absent"] cursor = db.cursor()
homeroomData.pop('Absent') cursor.execute("SELECT date FROM dates ORDER BY date ASC")
if 'placeholder' in homeroomData: dates = cursor.fetchall()
homeroomData.pop('placeholder')
currDate = "" currDate = ""
if fCommand != "": if fCommand != "":
currDate = fData[1] currDate = fData[1]
else: else:
for i in absData: for i in dates:
currDate = i currDate = i[0]
if i >= datetime.now(tz).strftime("%Y-%m-%d"): if i[0] >= datetime.now(tz).strftime("%Y-%m-%d"):
break break
return render_template('admin.html', homerooms=homerooms, absData=absData, cursor = db.cursor()
homeroomCode=currRoom, homeroomData=homeroomData, currDate=currDate, periods=['m', '1', '2', '3', '4', cursor.execute("SELECT dow FROM dates WHERE date=%s", (currDate, ))
'n', '5', '6', '7', '8', '9'], showUpload=session['showUpload']) dow = cursor.fetchone()[0]
elif pl == 'group': cursor = db.cursor()
cateData = db.child("Classes").child( cursor.execute("SELECT period, subject, teacher FROM schedule WHERE grade=%s AND class_=%s AND dow=%s", (currRoom[0], currRoom[1], dow))
"GP_Class").child(session['category']).get(session['token']).val() scheduleSQL = cursor.fetchall()
cclass = { schedule = {}
"name": cateData['Class'][session['class']]['name'], for i in scheduleSQL:
"category": session['category'], schedule[i[0]] = {
"class_id": session['class'] "subject": i[1],
} "teacher": i[2],
homerooms = cateData['Homerooms'] }
currDate = "" cursor = db.cursor()
confirmed = [] cursor.execute("SELECT period, subject, teacher FROM specschedule WHERE grade=%s AND class_=%s AND date=%s", (currRoom[0], currRoom[1], currDate))
absData = {} specScheduleSQL = cursor.fetchall()
for h in homerooms: for i in specScheduleSQL:
h = h.split('^') schedule[i[0]] = {
hrData = db.child("Homerooms").child( "subject": i[1],
h[0]).child(h[1]).get(session['token']).val() "teacher": i[2],
tmpAbsData = hrData['Absent'] "special": True
hrData.pop('Absent') }
if 'placeholder' in hrData: cursor = db.cursor()
hrData.pop('placeholder') cursor.execute("SELECT period, signature, notes, ds1,ds2,ds3,ds4,ds5,ds6,ds7 FROM submission WHERE grade=%s AND class_=%s AND date=%s", (currRoom[0], currRoom[1], currDate))
periods = [] submissionSQL = cursor.fetchall()
dow = "" submission = {}
if currDate == "": cursor = db.cursor()
if fCommand == 'date': cursor.execute("SELECT period, num, note FROM ds WHERE grade=%s AND class_=%s AND date=%s", (currRoom[0], currRoom[1], currDate))
currDate = fData idvDSSQL = cursor.fetchall()
for j in tmpAbsData[currDate]: idvDS = {}
if j == "dow": for i in idvDSSQL:
dow = tmpAbsData[currDate][j] if i[0] not in idvDS:
continue idvDS[i[0]] = {}
elif j == "confirm": idvDS[i[0]][i[1]]= i[2]
confirmed.append([h[0], h[1]]) for i in submissionSQL:
continue if i[0] == 'c':
elif j == "notes": submission[i[0]] = {
continue "signature": i[1],
if (tmpAbsData[currDate][j]['name'] == 'GP' and "notes": i[2]
tmpAbsData[currDate][j]['teacher'] == cclass['category']): }
periods.append(j) elif schedule[i[0]]["subject"] == "GP":
submission[i[0]] = OrderedDict()
else: signatures = json.loads(i[1])
for i in tmpAbsData: for j in signatures:
currDate = i submission[i[0]][j] = {
if i >= datetime.now(tz).strftime("%Y-%m-%d"): "signature": signatures[j],
tmp = False }
for j in tmpAbsData[i]: submission[i[0]]["notes"] = i[2]
if j == "dow":
dow = tmpAbsData[i][j]
continue
elif j == "confirm":
confirmed.append([h[0], h[1]])
continue
elif j == 'notes':
continue
if (tmpAbsData[i][j]['name'] == 'GP' and
tmpAbsData[i][j]['teacher'] == cclass['category']):
periods.append(j)
tmp = True
if tmp == True:
break
else: else:
for j in tmpAbsData[currDate]: submission[i[0]] = {
if j == "dow": "signature": i[1],
dow = tmpAbsData[currDate][j] "notes": i[2],
"ds1": i[3],
"ds2": i[4],
"ds3": i[5],
"ds4": i[6],
"ds5": i[7],
"ds6": i[8],
"ds7": i[9]
}
cursor = db.cursor()
cursor.execute("SELECT period, num, status, note FROM absent WHERE grade=%s AND class_=%s AND date=%s", (currRoom[0], currRoom[1], currDate))
absentDataSQL = cursor.fetchall()
absentData = {}
for p in ['m', '1', '2', '3', '4', 'n', '5', '6', '7', '8', '9']:
absentData[p] = {}
for i in absentDataSQL:
absentData[i[0]][i[1]] = i[2]
for i in absentDataSQL:
absentData[i[0]][i[1]] = {
'status': i[2],
'note': i[3],
}
return render_template('admin.html', homerooms=homerooms, currRoom=currRoom, students=students, currDate=currDate, schedule=schedule, submission=submission, studGP=studGP, idvDS=idvDS,
dates=dates, absentData=absentData, periods=['m', '1', '2', '3', '4', 'n', '5', '6', '7', '8', '9'], showUpload=session['showUpload'], dsboard=DSBOARD, dstext=DSTEXT, dsoffenses=DSOFFENSES)
# 'n', '5', '6', '7', '8', '9'], showUpload=session['showUpload'])
elif pl == 'group':
db = refresh_db()
cursor = db.cursor()
cursor.execute("SELECT category, subclass FROM gpclasses WHERE accs LIKE %s", ('%'+session['oldUsername']+'%',))
gpclasses = cursor.fetchall()
data = {}
currDate = ""
dow = ""
cursor = db.cursor()
cursor.execute("SELECT date FROM dates ORDER BY date ASC")
dates = cursor.fetchall()
if fCommand != "":
currDate = fData
else:
for i in dates:
currDate = i[0]
if i[0] >= datetime.now(tz).strftime("%Y-%m-%d"):
break
cursor = db.cursor()
cursor.execute("SELECT dow FROM dates WHERE date=%s", (currDate, ))
dow = cursor.fetchone()[0]
for c in gpclasses:
cursor.execute("SELECT about FROM gpclasses WHERE subclass=%s AND category=%s",
(c[1], c[0]))
cclass = {
"name": cursor.fetchone()[0],
"category": c[0],
"class_id": c[1]
}
data[cclass['category'] + ' ' + cclass['class_id']] = {
"cdata": cclass,
}
# get student list
cursor.execute("SELECT grade,class_,num,name,ename FROM students WHERE classes LIKE " + '\'%\"'+ cclass['category'] + '\": \"' + cclass['class_id'] +'\"%\'' + " ORDER BY grade ASC,class_ ASC,num ASC")
students = cursor.fetchall()
# get student homerooms
homerooms = []
for x in students:
if (str(x[0]) + '^' + str(x[1])) not in homerooms:
homerooms.append(str(x[0]) + '^' + str(x[1]))
# get periods
for h in homerooms:
hs = h.split('^')
cursor.execute("SELECT period FROM schedule WHERE grade=%s AND class_=%s AND dow=%s AND teacher=%s", (hs[0], hs[1], dow, cclass['category']))
scheduleSQL = cursor.fetchall()
cursor.execute("SELECT period FROM specschedule WHERE grade=%s AND class_=%s AND date=%s AND teacher=%s", (hs[0], hs[1], currDate, cclass['category']))
specNTPSQL = cursor.fetchall()
for s in specNTPSQL:
scheduleSQL.append(s)
cursor.execute("SELECT period FROM specschedule WHERE grade=%s AND class_=%s AND date=%s AND teacher!=%s", (hs[0], hs[1], currDate, cclass['category']))
specNTDSQL = cursor.fetchall()
specNTD = {}
for i in specNTDSQL:
specNTD[i[0]] = True
print(h, specNTD, scheduleSQL)
for p in scheduleSQL:
if p[0] in specNTD and specNTD[p[0]] == True:
continue continue
elif j == "confirm": if p[0] not in data[cclass['category'] + ' ' + cclass['class_id']]:
confirmed.append([h[0], h[1]]) data[cclass['category'] + ' ' + cclass['class_id']][p[0]] = {}
continue if (h not in data[cclass['category'] + ' ' + cclass['class_id']][p[0]]):
elif j == "notes": data[cclass['category'] + ' ' + cclass['class_id']][p[0]][h] = {}
continue cursor = db.cursor()
if (tmpAbsData[currDate][j]['name'] == 'GP' and cursor.execute("SELECT signature FROM submission WHERE grade=%s AND class_=%s AND date=%s AND period=%s", (hs[0], hs[1], currDate, p[0]))
tmpAbsData[currDate][j]['teacher'] == cclass['category']): submissionSQL = cursor.fetchone()
periods.append(j) submitted = False
for p in periods: try:
if not p in absData: if submissionSQL[0] == 'STUD_AFFAIR_OFFICE':
absData[p] = {} submitted = True
for p in periods: except:
if not h[0] in absData[p]: pass
absData[p][h[0]] = {} try:
absData[p][h[0]][h[1]] = {} signatures = json.loads(submissionSQL[0])
if 'notes' in tmpAbsData[currDate][p]: if cclass['class_id'] in signatures:
absData[p][h[0]][h[1] submitted = True
]['notes'] = tmpAbsData[currDate][p]['notes'] except:
for num in hrData: pass
if (cclass['category'] in hrData[num]['GP_Class'] and hrCfrm = False
hrData[num]['GP_Class'][cclass['category']] == cclass['class_id']): if not submitted:
for p in periods: cursor = db.cursor()
absData[p][h[0]][h[1]][num] = { cursor.execute("SELECT signature FROM submission WHERE grade=%s AND class_=%s AND date=%s AND period='c'", (hs[0], hs[1], currDate))
"name": hrData[num]['name'], hrCfrm = True if cursor.fetchone() != None else submitted
"eng_name": hrData[num]['eng_name'], cursor = db.cursor()
"alr_fill": (('signature' in tmpAbsData[currDate][p]) and cursor.execute("SELECT num, status, note FROM absent WHERE grade=%s AND class_=%s AND date=%s AND period=%s", (hs[0], hs[1], currDate, p[0]))
(cclass['class_id'] in tmpAbsData[currDate][p]['signature'] or 'STUD_AFFAIR_OFFICE' in tmpAbsData[currDate][p]['signature'])), absentDataSQL = cursor.fetchall()
"absent": False if not num in tmpAbsData[currDate][p] else tmpAbsData[currDate][p][num] for x in students:
} if (str(x[0])==hs[0] and str(x[1])==hs[1]):
return render_template('group_teach.html', dateKeys=sorted(tmpAbsData.keys()), cclass=cclass, absData=absData, dow=dow, currDate=currDate, tmpAbsData=tmpAbsData, confirmed=confirmed) studStatus = [item for item in absentDataSQL if item[0] == x[2]]
status = ""
if submitted:
if studStatus == []:
status = 'present'
else:
status = studStatus[0][1]
else:
if studStatus == []:
if hrCfrm:
status = '--'
else:
status = 'na'
else:
status = studStatus[0][1]
data[cclass['category'] + ' ' + cclass['class_id']][p[0]][h][x[2]] = {
"name": x[3],
"ename": x[4],
"status": status,
"note": '' if studStatus == [] else studStatus[0][2],
}
return render_template('group_teach.html', dates=dates, currDate=currDate, data=data, dsoffenses=DSOFFENSES)
elif pl == 'homeroom': elif pl == 'homeroom':
homeroom = session['homeroom'].split('^') db = refresh_db()
homeroomData = db.child("Homerooms").child(homeroom[0]).child(
homeroom[1]).get(session['token']).val()
times = OrderedDict({ times = OrderedDict({
'm': '00:00', 'm': '00:00',
'1': '08:15', '1': '08:15',
@ -158,20 +251,96 @@ def manageProcess(fCommand, fData):
currTime <= times[next_item(times, i)]): currTime <= times[next_item(times, i)]):
currPeriod = i currPeriod = i
break break
absData = homeroomData["Absent"] currRoom = session['homeroom'].split('^')
homeroomData.pop('Absent') cursor = db.cursor()
if 'placeholder' in homeroomData: cursor.execute("SELECT num,name,ename,classes FROM students WHERE grade=%s AND class_=%s ORDER BY num ASC", (currRoom[0], currRoom[1]))
homeroomData.pop('placeholder') students = cursor.fetchall()
studGP = {}
for s in students:
studGP[s[0]] = json.loads(s[3])
cursor = db.cursor()
cursor.execute("SELECT date FROM dates ORDER BY date ASC")
dates = cursor.fetchall()
currDate = "" currDate = ""
if fCommand == 'date': if fCommand != "":
currDate = fData currDate = fData
else: else:
for i in absData: for i in dates:
currDate = i currDate = i[0]
if i >= datetime.now(tz).strftime("%Y-%m-%d"): if i[0] >= datetime.now(tz).strftime("%Y-%m-%d"):
break break
return render_template('homeroom.html', absData=absData, homeroomCode=homeroom, homeroomData=homeroomData, cursor = db.cursor()
currDate=currDate, dateKeys=sorted(absData.keys()), periods=['m', '1', '2', '3', '4', 'n', '5', '6', '7', '8', '9'], currPeriod=currPeriod) cursor.execute("SELECT dow FROM dates WHERE date=%s", (currDate, ))
dow = cursor.fetchone()[0]
cursor = db.cursor()
cursor.execute("SELECT period, subject, teacher FROM schedule WHERE grade=%s AND class_=%s AND dow=%s", (currRoom[0], currRoom[1], dow))
scheduleSQL = cursor.fetchall()
schedule = {}
for i in scheduleSQL:
schedule[i[0]] = {
"subject": i[1],
"teacher": i[2],
}
cursor = db.cursor()
cursor.execute("SELECT period, subject, teacher FROM specschedule WHERE grade=%s AND class_=%s AND date=%s", (currRoom[0], currRoom[1], currDate))
specScheduleSQL = cursor.fetchall()
for i in specScheduleSQL:
schedule[i[0]] = {
"subject": i[1],
"teacher": i[2],
"special": True
}
cursor = db.cursor()
cursor.execute("SELECT period, signature, notes, ds1,ds2,ds3,ds4,ds5,ds6,ds7 FROM submission WHERE grade=%s AND class_=%s AND date=%s", (currRoom[0], currRoom[1], currDate))
submissionSQL = cursor.fetchall()
cursor = db.cursor()
cursor.execute("SELECT period, num, note FROM ds WHERE grade=%s AND class_=%s AND date=%s", (currRoom[0], currRoom[1], currDate))
idvDSSQL = cursor.fetchall()
idvDS = {}
for i in idvDSSQL:
if i[0] not in idvDS:
idvDS[i[0]] = {}
idvDS[i[0]][i[1]]= i[2]
submission = {}
for i in submissionSQL:
if i[0] == 'c':
submission[i[0]] = {
"signature": i[1],
"notes": i[2]
}
elif schedule[i[0]]["subject"] == "GP":
submission[i[0]] = OrderedDict()
signatures = json.loads(i[1])
for j in signatures:
submission[i[0]][j] = {
"signature": signatures[j],
}
submission[i[0]]["notes"] = i[2]
else:
submission[i[0]] = {
"signature": i[1],
"notes": i[2],
"ds1": i[3],
"ds2": i[4],
"ds3": i[5],
"ds4": i[6],
"ds5": i[7],
"ds6": i[8],
"ds7": i[9],
}
cursor = db.cursor()
cursor.execute("SELECT period, num, status, note FROM absent WHERE grade=%s AND class_=%s AND date=%s", (currRoom[0], currRoom[1], currDate))
absentDataSQL = cursor.fetchall()
absentData = {}
for p in ['m', '1', '2', '3', '4', 'n', '5', '6', '7', '8', '9']:
absentData[p] = {}
for i in absentDataSQL:
absentData[i[0]][i[1]] = {
'status': i[2],
'note': i[3],
}
return render_template('homeroom.html', currRoom=currRoom, students=students, currDate=currDate, schedule=schedule, submission=submission, currPeriod=currPeriod, studGP=studGP,
dates=dates, absentData=absentData, periods=['m', '1', '2', '3', '4', 'n', '5', '6', '7', '8', '9'], dsboard=DSBOARD, dstext=DSTEXT, dsoffenses=DSOFFENSES, idvDS=idvDS)
else: else:
return redirect('/logout') return redirect('/logout')
@ -194,102 +363,206 @@ def manage_admin(g, r, date):
] ]
return manageProcess("admin", data) return manageProcess("admin", data)
@manage.route('/student', methods=['GET'])
def showStudentAbs():
if (check_login_status()):
return redirect('/logout')
refresh_token()
if not ('user_type' in session and session['user_type'] == 'student'):
return redirect('/')
db = refresh_db()
cursor = db.cursor()
cursor.execute("SELECT date, period, num, status, note FROM absent WHERE grade=%s AND class_=%s AND num=%s ORDER BY date DESC, period DESC, num ASC", (session['grade'], session['class'], session['num']))
absentDataSQL = cursor.fetchall()
return render_template("list.html", title="Student Absent List | 學生缺勤紀錄", mode='STUDABS', data=absentDataSQL, currRoom=[session['grade'],session['class']], name=session['name'], num=session['num'])
@manage.route('/student/ds', methods=['GET'])
def showStudentDS():
if (check_login_status()):
return redirect('/logout')
refresh_token()
if not ('user_type' in session and session['user_type'] == 'student'):
return redirect('/')
db = refresh_db()
cursor = db.cursor()
cursor.execute("SELECT date, period, num, note FROM ds WHERE grade=%s AND class_=%s AND num=%s ORDER BY date DESC, period DESC, num ASC", (session['grade'], session['class'], session['num']))
dsDataSQL = cursor.fetchall()
print(dsDataSQL)
return render_template("list.html", title="Student DS List | 學生定心紀錄", mode='STUDDS', data=dsDataSQL, currRoom=[session['grade'],session['class']], name=session['name'], num=session['num'])
@manage.route('/manage/abs', methods=['GET'])
def showAllAbs():
if (check_login_status()):
return redirect('/logout')
refresh_token()
currRoom = session['homeroom'].split('^')
db = refresh_db()
cursor = db.cursor()
cursor.execute("SELECT num,name,ename FROM students WHERE grade=%s AND class_=%s ORDER BY num ASC", (currRoom[0], currRoom[1]))
studentsSQL = cursor.fetchall()
students = {}
for st in studentsSQL:
students[st[0]] = {
'name': st[1],
'ename': st[2],
}
cursor = db.cursor()
cursor.execute("SELECT date, period, num, status, note FROM absent WHERE grade=%s AND class_=%s ORDER BY date DESC, period DESC, num ASC", (currRoom[0], currRoom[1]))
absentDataSQL = cursor.fetchall()
return render_template("list.html", title="Absent List | 缺勤紀錄", mode='ABS', students=students, data=absentDataSQL, currRoom=currRoom)
@manage.route('/manage/ds', methods=['GET'])
def showAllDS():
if (check_login_status()):
return redirect('/logout')
refresh_token()
currRoom = session['homeroom'].split('^')
db = refresh_db()
cursor = db.cursor()
cursor.execute("SELECT num,name,ename FROM students WHERE grade=%s AND class_=%s ORDER BY num ASC", (currRoom[0], currRoom[1]))
studentsSQL = cursor.fetchall()
students = {}
for st in studentsSQL:
students[st[0]] = {
'name': st[1],
'ename': st[2],
}
cursor = db.cursor()
cursor.execute("SELECT date, period, num, note FROM ds WHERE grade=%s AND class_=%s ORDER BY date DESC, period DESC, num ASC", (currRoom[0], currRoom[1]))
dsDataSQL = cursor.fetchall()
return render_template("list.html", title="DS List | 定心紀錄", mode='DS', students=students, data=dsDataSQL, currRoom=currRoom)
@manage.route('/manage/group_teach_publish', methods=['POST']) @manage.route('/manage/group_teach_publish', methods=['POST'])
def group_teach_publish(): def group_teach_publish():
if (check_login_status()): if (check_login_status()):
return redirect('/logout') return redirect('/logout')
refresh_token() refresh_token()
data = request.form.to_dict()
cclass = { cclass = {
"name": db.child("Classes").child("GP_Class").child(session['category']).child( "category": data.pop('category'),
"Class").child(session['class']).child("name").get(session['token']).val(), "class_id": data.pop('class_id')
"category": session['category'],
"class_id": session['class'],
"homerooms": db.child("Classes").child(
"GP_Class").child(session['category']).child("Homerooms").get(session['token']).val()
} }
date = request.form['date'] db = refresh_db()
period = request.form['period'] cursor = db.cursor()
signature = request.form['signatureData'] cursor.execute("SELECT about FROM gpclasses WHERE category=%s AND subclass=%s",
formData = request.form.to_dict() (cclass['category'], cclass['class_id']))
notes = "" cclass["name"] = cursor.fetchone()[0]
if 'notes' in request.form: cursor.execute("SELECT grade,class_,num,name,ename FROM students WHERE classes LIKE " + '\'%\"'+ cclass['category'] + '\": \"' + cclass['class_id'] +'\"%\'' + " ORDER BY grade ASC,class_ ASC,num ASC")
notes = request.form['notes'] students = cursor.fetchall()
formData.pop('notes') homerooms = []
signature = removeprefix(signature, 'data:image/png;base64,') for x in students:
signature = bytes(signature, 'utf-8') if (str(x[0]) + '^' + str(x[1])) not in homerooms:
rand = str(date + '^' + cclass['category'] + homerooms.append(str(x[0]) + '^' + str(x[1]))
'^' + cclass['class_id'] + '^' + period) data.pop('dsnumbers')
rand += ".png" data.pop('dsoffense')
with open(os.path.join('temp', rand), "wb") as fh: data.pop('dsoffenseother')
fh.write(base64.decodebytes(signature)) date = data.pop('date')
storage.child(os.path.join('signatures', rand) period = data.pop('period')
).put(os.path.join('temp', rand), session['token']) signature = data.pop('signatureData')
formData.pop('signatureData') notes = data.pop('notes')
formData.pop('date') absentData = []
formData.pop('period') dsData = []
for i in formData: for x in data:
i = i.split('^') xs = x.split('^')
db.child("Homerooms").child(i[1]).child(i[2]).child( if xs[0] == 'note':
"Absent").child(date).child(period).update({i[3]: int(i[0])}, session['token'])
for h in cclass['homerooms']:
h = h.split('^')
if "confirm" in db.child("Homerooms").child(h[0]).child(h[1]).child("Absent").child(date).get(session['token']).val():
continue continue
db.child("Homerooms").child(h[0]).child(h[1]).child( elif xs[0] == 'ds':
"Absent").child(date).child(period).child("signature").update({cclass['class_id']: str(storage.child(os.path.join('signatures', rand)).get_url(None))}, session['token']) dsData.append([xs[1], xs[2].split('-')[0], xs[2].split('-')[1], data[x]])
db.child("Homerooms").child(h[0]).child(h[1]).child(
"Absent").child(date).child(period).child("names").child(cclass['class_id']).set(cclass['name'], session['token'])
currPeriodData = db.child("Homerooms").child(h[0]).child(h[1]).child(
"Absent").child(date).child(period).get(session['token']).val()
if 'notes' in currPeriodData:
db.child("Homerooms").child(h[0]).child(h[1]).child(
"Absent").child(date).child(period).update({'notes': currPeriodData['notes']+'; '+notes}, session['token'])
else: else:
db.child("Homerooms").child(h[0]).child(h[1]).child( absentData.append([xs[1], xs[2], xs[3], 'K' if xs[0] == '1' else 'L', data['note^'+xs[1]+'^'+xs[2]+'^'+xs[3]]])
"Absent").child(date).child(period).update({'notes': notes}, session['token']) for h in homerooms:
os.remove(os.path.join('temp', rand)) h = h.split('^')
cursor = db.cursor()
cursor.execute("""
SELECT signature, notes FROM submission WHERE grade=%s AND class_=%s AND date=%s AND period=%s
""", (h[0], h[1], date, period))
one = cursor.fetchone()
if one is None:
jSignature = json.dumps({cclass['class_id']: signature})
cursor.execute("""
INSERT INTO submission (grade, class_, date, period, signature, notes)
VALUES (%s, %s, %s, %s, %s, %s)
""", (h[0], h[1], date, period, jSignature, notes))
db.commit()
else:
jSignature = json.loads(one[0])
if cclass['class_id'] in jSignature:
continue
jSignature[cclass['class_id']] = signature
note = one[1] + '; ' + notes
cursor.execute("""
UPDATE submission SET signature=%s, notes=%s WHERE grade=%s AND class_=%s AND date=%s AND period=%s
""", (json.dumps(jSignature), note, h[0], h[1], date, period))
db.commit()
for d in dsData:
cursor = db.cursor()
cursor.execute("""
INSERT INTO ds (grade, class_, num, date, period, note)
VALUES (%s, %s, %s, %s, %s, %s)
""", (d[0], d[1], d[2], date, period, d[3]))
db.commit()
for a in absentData:
cursor = db.cursor()
cursor.execute("""
INSERT INTO absent (grade, class_, num, date, period, status, note)
VALUES (%s, %s, %s, %s, %s, %s, %s)
""", (a[0], a[1], a[2], date, period, a[3], a[4]))
db.commit()
return redirect('/manage') return redirect('/manage')
@manage.route('/manage/homeroom_abs', methods=['POST']) @manage.route('/manage/homeroom_abs', methods=['POST'])
def homeroom_abs_publish(): def homeroom_abs_publish():
if (check_login_status()): if (check_login_status()):
return redirect('/logout') return redirect('/logout')
refresh_token() refresh_token()
date = request.form['date'] db = refresh_db()
homeroom = request.form['homeroom'].split('^') data = request.form.to_dict()
period = request.form['period'] date = data.pop('date')
signature = request.form['signatureData'] period = data.pop('period')
formData = request.form.to_dict() signature = data.pop('signatureData')
notes = "" notes = data.pop('notes')
if "confirm" in db.child("Homerooms").child(homeroom[0]).child(homeroom[1]).child("Absent").child(date).get(session['token']).val(): homeroom = data.pop('homeroom').split('^')
return redirect('/manage') ds1 = data.pop('ds^1')
if 'notes' in request.form: ds2 = data.pop('ds^2')
notes = request.form['notes'] ds3 = data.pop('ds^3')
formData.pop('notes') ds4 = data.pop('ds^4')
signature = removeprefix(signature, 'data:image/png;base64,') ds5 = data.pop('ds^5')
signature = bytes(signature, 'utf-8') ds6 = data.pop('ds^6')
rand = str(date + '^' + homeroom[0] + '^' + homeroom[1] + '^' + period) ds7 = data.pop('ds^7')
rand += ".png" # 2: L / 1: K
with open(os.path.join('temp', rand), "wb") as fh: absentData = {}
fh.write(base64.decodebytes(signature)) dsidv = {}
storage.child(os.path.join('signatures', rand) for x in data:
).put(os.path.join('temp', rand), session['token']) xt = x.split('^')
formData.pop('signatureData') if (xt[0] == 'note'):
formData.pop('date') if xt[2] not in absentData:
formData.pop('homeroom') absentData[xt[2]] = {}
formData.pop('period') absentData[xt[2]]['note'] = data[x]
formData.pop('stype') elif (xt[0] == 'dsidv'):
for i in formData: dsidv[xt[1]] = data[x]
i = i.split('^') else:
db.child("Homerooms").child(homeroom[0]).child( if xt[1] not in absentData:
homeroom[1]).child("Absent").child(date).child(period).update({i[1]: int(i[0])}, session['token']) absentData[xt[1]] = {}
db.child("Homerooms").child(homeroom[0]).child(homeroom[1]).child( absentData[xt[1]]['status'] = 'L' if x[0] == '2' else 'K'
"Absent").child(date).child(period).update({'signature': str(storage.child(os.path.join('signatures', rand)).get_url(None))}, session['token']) cursor = db.cursor()
db.child("Homerooms").child(homeroom[0]).child(homeroom[1]).child( cursor.execute("""
"Absent").child(date).child(period).update({'notes': notes}, session['token']) INSERT INTO submission
os.remove(os.path.join('temp', rand)) (grade, class_, date, period, signature, ds1, ds2, ds3, ds4, ds5, ds6, ds7, notes)
VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)
""", (homeroom[0], homeroom[1], date, period, signature, ds1, ds2, ds3, ds4, ds5, ds6, ds7, notes))
for x in absentData:
cursor.execute("""
INSERT INTO absent
(grade, class_, date, period, num, status, note)
VALUES (%s, %s, %s, %s, %s, %s, %s)
""", (homeroom[0], homeroom[1], date, period, x, absentData[x]['status'], absentData[x]['note']))
for x in dsidv:
cursor.execute("""
INSERT INTO ds
(grade, class_, date, period, num, note)
VALUES (%s, %s, %s, %s, %s, %s)
""", (homeroom[0], homeroom[1], date, period, x, dsidv[x]))
db.commit()
return redirect('/manage') return redirect('/manage')
@ -298,69 +571,26 @@ def edit_abs():
if (check_login_status() or not check_permission()): if (check_login_status() or not check_permission()):
return redirect('/logout') return redirect('/logout')
refresh_token() refresh_token()
date = request.form['date'] data = request.form.to_dict()
homeroom = request.form['homeroom'].split('^') print(data)
period = request.form['period'] return ""
signature = "https://firebasestorage.googleapis.com/v0/b/attendance-be176.appspot.com/o/stud_affairs.png?alt=media"
formData = request.form.to_dict()
notes = ""
oldData = list(db.child("Homerooms").child(homeroom[0]).child(homeroom[1]).child(
"Absent").child(date).child(period).shallow().get(session['token']).val())
for k in oldData:
if k == 'name' or k == 'teacher':
continue
db.child("Homerooms").child(homeroom[0]).child(homeroom[1]).child(
"Absent").child(date).child(period).child(k).remove(session['token'])
cfrmstatus = db.child("Homerooms").child(homeroom[0]).child(
homeroom[1]).child("Absent").child(date).get(session['token']).val()
if "confirm" in cfrmstatus:
db.child("Homerooms").child(homeroom[0]).child(homeroom[1]).child(
"Absent").child(date).update({'notes': cfrmstatus['notes'] + '; (確認後學務處有更改)'}, session['token'])
if 'notes' in request.form:
notes = request.form['notes']
formData.pop('notes')
formData.pop('date')
formData.pop('homeroom')
formData.pop('period')
for i in formData:
i = i.split('^')
db.child("Homerooms").child(homeroom[0]).child(
homeroom[1]).child("Absent").child(date).child(period).update({i[1]: int(i[0])}, session['token'])
db.child("Homerooms").child(homeroom[0]).child(homeroom[1]).child(
"Absent").child(date).child(period).update({'notes': notes}, session['token'])
if cfrmstatus[period]['name'] == 'GP':
db.child("Homerooms").child(homeroom[0]).child(
homeroom[1]).child("Absent").child(date).child(period).child("signature").set({'STUD_AFFAIR_OFFICE': signature}, session['token'])
db.child("Homerooms").child(homeroom[0]).child(
homeroom[1]).child("Absent").child(date).child(period).child("names").set({'STUD_AFFAIR_OFFICE': "學務處已編輯"}, session['token'])
else:
db.child("Homerooms").child(homeroom[0]).child(
homeroom[1]).child("Absent").child(date).child(period).child("signature").set(signature, session['token'])
return redirect('/manage/admin/'+homeroom[0]+'/'+homeroom[1]+'/'+date)
@manage.route('/manage/homeroom_confirm', methods=['POST']) @manage.route('/manage/homeroom_confirm', methods=['POST'])
def homeroom_confirm(): def homeroom_confirm():
if (check_login_status()): if (check_login_status()):
return redirect('/logout') return redirect('/logout')
refresh_token() refresh_token()
homeroom = request.form['homeroom'].split('^') data = request.form.to_dict()
date = request.form['date'] homeroom = data.pop('homeroom').split('^')
if 'notes' in request.form: date = data.pop('date')
notes = request.form['notes'] signature = data.pop('signatureData')
db.child("Homerooms").child(homeroom[0]).child(homeroom[1]).child( notes = data.pop('notes')
"Absent").child(date).update({"notes": notes}, session['token']) db = refresh_db()
cursor = db.cursor()
signature = request.form['signatureData'] cursor.execute("""
signature = removeprefix(signature, 'data:image/png;base64,') INSERT INTO submission
signature = bytes(signature, 'utf-8') (grade, class_, date, period, signature, notes)
rand = str(date + '^' + homeroom[0] + '^' + homeroom[1] + '^' + 'hrCfrm') VALUES (%s, %s, %s, 'c', %s, %s)
rand += ".png" """, (homeroom[0], homeroom[1], date, signature, notes))
with open(os.path.join('temp', rand), "wb") as fh: db.commit()
fh.write(base64.decodebytes(signature))
storage.child(os.path.join('signatures', rand)
).put(os.path.join('temp', rand), session['token'])
db.child("Homerooms").child(homeroom[0]).child(homeroom[1]).child("Absent").child(date).update(
{"confirm": str(storage.child(os.path.join('signatures', rand)).get_url(None))}, session['token'])
os.remove(os.path.join('temp', rand))
return redirect('/manage') return redirect('/manage')

View file

@ -1,19 +1,33 @@
# Automatically generated by https://github.com/damnever/pigar. # Automatically generated by https://github.com/damnever/pigar.
# Attendance/app.py: 2 # Attendance/functions.py: 1,3
Flask == 2.0.1 Flask == 2.0.1
# Attendance/app.py: 3 # Attendance/functions.py: 19,20,21
# Attendance/test.py: 1 Flask_Admin == 1.5.8
Pyrebase4 == 4.5.0
# Attendance/app.py: 8 # Attendance/functions.py: 22
Flask_BabelEx == 0.9.4
# Attendance/functions.py: 18
Flask_SQLAlchemy == 2.5.1
# Attendance/functions.py: 15
mysql_connector_python == 8.0.27
# Attendance/functions.py: 10
pandas == 1.1.3 pandas == 1.1.3
# Attendance/app.py: 11 # Attendance/functions.py: 14
# Attendance/test.py: 3 passlib == 1.7.4
# Attendance/functions.py: 13
python_dotenv == 0.19.0 python_dotenv == 0.19.0
# Attendance/app.py: 5 # Attendance/functions.py: 5
pytz == 2020.1 pytz == 2020.1
# Attendance/functions.py: 16
requests == 2.26.0
gunicorn == 20.1.0 gunicorn == 20.1.0

View file

@ -3,6 +3,9 @@
body { body {
font-family: "Lato", "Noto Sans TC", "Microsoft JhengHei", "sans-serif"; font-family: "Lato", "Noto Sans TC", "Microsoft JhengHei", "sans-serif";
-moz-osx-font-smoothing: grayscale;
-webkit-font-smoothing: antialiased;
font-smooth: always;
} }
div.col .row .col { div.col .row .col {
@ -76,10 +79,30 @@ p.highlightAbs.n-2 {
p.highlightAbs.n-3 { p.highlightAbs.n-3 {
color: rgb(15, 184, 0); color: rgb(15, 184, 0);
} }
.afterSelButton {
margin-top: 0;
}
.modal-footer>.row {
width: 70%;
}
.modal-footer>.row .col {
border: 0 !important;
}
.modal-footer>.row button {
margin-right: 10px;
margin-top: 0;
}
.margin-top { .margin-top {
margin-top: 20px; margin-top: 20px;
} }
.nomargin-top {
margin-top: 0;
}
.nmb {
margin-bottom: 0;
}
input[type="checkbox"].absent, input[type="checkbox"].late { input[type="checkbox"].absent, input[type="checkbox"].late {
-webkit-appearance: initial; -webkit-appearance: initial;
@ -179,4 +202,23 @@ label {
.submitButton { .submitButton {
width: 20vw; width: 20vw;
min-width: auto; min-width: auto;
}
div.ds {
margin-top: 20px;
}
div.dsboard .col, .noborder {
border: 0 !important;
}
div.container {
text-align: center;
}
button {
width: 100%;
margin-top: 10px;
}
.text-red {
color: red;
} }

View file

@ -1,8 +1,39 @@
div.container { html {
text-align: center; height: 100%;
}
body {
height: 100%;
margin: 0;
background-repeat: no-repeat;
background-attachment: fixed;
background: linear-gradient(to bottom right, #e4e4e4 0%, #afafaf 100%);
} }
button { div.loginPanel {
width: 100%; background: #effffc;
margin-top: 10px; margin-top: 100px;
padding: 30px;
box-shadow: 0 0 10px black;
z-index: -1;
}
div.container {
max-width: 730px;
}
h1 .img {
height: calc(1.375rem + 1.5vw);
}
@media (min-width: 1200px) {
h1 .img {
height: 2.5rem;
}
}
h1 .img img {
vertical-align: sub;
}
#subuser_sel {
width: 80%;
margin-left: auto;
margin-right: auto;
} }

66
static/nav.css Normal file
View file

@ -0,0 +1,66 @@
#sidebar {
min-width: 270px;
max-width: 270px;
background: #5b5e69;
color: #fff;
-webkit-transition: all 0.3s;
-o-transition: all 0.3s;
transition: all 0.3s;
position: fixed;
top: 0;
height: 100%; }
#sidebar.active {
min-width: 80px;
max-width: 80px;
text-align: center; }
#sidebar.active ul.components li {
font-size: 14px; }
#sidebar.active ul.components li a {
padding: 10px 0; }
#sidebar.active ul.components li a span {
margin-right: 0;
display: block;
font-size: 24px; }
#sidebar.active .logo {
padding: 10px 0; }
#sidebar.active .footer {
display: none; }
#sidebar .logo {
display: block;
color: #fff;
font-weight: 900;
padding: 10px 30px;
}
#sidebar ul.components {
padding: 0; }
#sidebar ul li {
font-size: 16px; }
#sidebar ul li > ul {
margin-left: 10px; }
#sidebar ul li > ul li {
font-size: 14px; }
#sidebar ul li a {
padding: 10px 30px;
display: block;
color: white;
border-bottom: 1px solid rgba(255, 255, 255, 0.1); }
#sidebar ul li a span {
margin-right: 15px; }
@media (max-width: 991.98px) {
#sidebar ul li a span {
display: block; } }
#sidebar ul li a:hover {
color: #fff; }
#sidebar ul li.active > a {
background: transparent;
color: #fff; }
@media (max-width: 991.98px) {
#sidebar {
min-width: 80px;
max-width: 80px;
text-align: center;
margin-left: -80px !important; }
#sidebar.active {
margin-left: 0 !important; } }
#sidebar a {
text-decoration: none; }

10
static/nav.js Normal file
View file

@ -0,0 +1,10 @@
$(window).resize(function() {
var sidebar = document.getElementById('sidebar');
var container = document.getElementsByClassName('container')[0];
container.style.paddingLeft = 'calc(.75rem + ' + sidebar.offsetWidth + 'px)';
});
document.addEventListener("DOMContentLoaded", function(event){
var sidebar = document.getElementById('sidebar');
var container = document.getElementsByClassName('container')[0];
container.style.paddingLeft = 'calc(.75rem + ' + sidebar.offsetWidth + 'px)';
})

View file

@ -1,6 +1,14 @@
var signaturePad, selPeriod, canvas, width = $(window).width(), modal; var signaturePad, selPeriod, canvas, width = $(window).width(), modal;
var indDS = {};
function submitForm() { function submitForm() {
if (!signaturePad.isEmpty()) { if (!signaturePad.isEmpty()) {
for (var i in indDS) {
var tmp = document.createElement('input');
tmp.type = 'hidden';
tmp.name = 'ds^' + i;
tmp.value = indDS[i];
document.getElementById('attendanceData^' + selPeriod).appendChild(tmp);
}
$('#' + modal).modal('hide'); $('#' + modal).modal('hide');
loadingAnimation(); loadingAnimation();
signaturePad.off(); signaturePad.off();
@ -50,12 +58,36 @@ function viewSignature(period) {
}); });
resizeCanvas(); resizeCanvas();
} }
function unCheckAbs(string) {
document.getElementById('absent^' + string).checked = false;
}
function unCheckLate(string) { function unCheckLate(string) {
document.getElementById('late^' + string).checked = false; document.getElementById('late^' + string).checked = false;
strForNote = string.substring(string.indexOf("^") + 1);
if (document.getElementById("absent^" + string).checked == true && !!!document.getElementById("note^"+strForNote)) {
var tmp = document.createElement('input')
tmp.type = 'text'
tmp.id = 'note^'+strForNote
tmp.name = 'note^'+strForNote
tmp.className = 'form-control'
document.getElementById('input^' + string).appendChild(tmp)
} else if (document.getElementById("absent^" + string).checked == false) {
document.getElementById('note^' + strForNote).remove();
}
} }
function unCheckAbs(string) {
document.getElementById('absent^' + string).checked = false;
strForNote = string.substring(string.indexOf("^") + 1);
if (document.getElementById("late^" + string).checked == true && !!!document.getElementById("note^"+strForNote)) {
var tmp = document.createElement('input')
tmp.type = 'text'
tmp.id = 'note^'+strForNote
tmp.name = 'note^'+strForNote
tmp.className = 'form-control'
document.getElementById('input^' + string).appendChild(tmp)
} else if (document.getElementById("late^" + string).checked == false) {
document.getElementById('note^' + strForNote).remove();
}
}
function loadingAnimation() { function loadingAnimation() {
$("div.container").hide(); $("div.container").hide();
@ -70,3 +102,18 @@ function chgDate(sel) {
document.body.appendChild(new_form); document.body.appendChild(new_form);
new_form.submit(); new_form.submit();
} }
function addDS() {
if ($('#dsnumbersel').val() == "" || $('#dsoffensesel').val() == "") {
return;
}
var text = $('#dsoffensesel').val()
if ($('#dsoffenseother').val() != "") {
text = text.concat(": ", $('#dsoffenseother').val());
}
indDS[$("#dsnumbersel").val()] = text;
$('#inddsview>.col').append('<div class="row"><div class="col">'+$("#dsnumbersel").val()+'</div><div class="col">'+text+'</div></div>')
$('#dsnumbersel option[value="'+$("#dsnumbersel").val()+'"]').remove();
$('#dsoffenseother').val("");
$('#dsoffensesel').val("");
$('#dsnumbersel').val("");
}

View file

@ -1,5 +1,6 @@
var signaturePad, hrCfrm = false, canvas = document.getElementById("signature_pad"); var signaturePad, hrCfrm = false, canvas = document.getElementById("signature_pad");
var width = $(window).width(); var width = $(window).width();
var indDS = {};
function loadingAnimation() { function loadingAnimation() {
$('.container').hide(); $('.container').hide();
$('#loading').show(); $('#loading').show();
@ -25,6 +26,11 @@ function submitForm() {
document.getElementById('homeroom_confirm').submit() document.getElementById('homeroom_confirm').submit()
} else { } else {
var notes = $('#subjectNotes').val(); var notes = $('#subjectNotes').val();
for (var i = 0; i < 7; i++)
document.getElementById('HR-ds'+(i+1)).value = $('.dsboard input[name="ds'+(i+1)+'"]:checked').val();
for (var i in indDS) {
$('#postHomeroomAbs').append('<input type="text" name="dsidv^' + i.split('-')[0] + '" value="'+ indDS[i] +'">')
}
document.getElementById('HR-signatureData').value = data; document.getElementById('HR-signatureData').value = data;
document.getElementById('HR-notes').value = notes; document.getElementById('HR-notes').value = notes;
document.getElementById('postHomeroomAbs').submit(); document.getElementById('postHomeroomAbs').submit();
@ -70,6 +76,8 @@ function afterSelAbs(period) {
$('#postHomeroomAbs').append('<input type="checkbox" name="' + $(this).attr('class').split(' ')[1].split('^')[0] + '^' $('#postHomeroomAbs').append('<input type="checkbox" name="' + $(this).attr('class').split(' ')[1].split('^')[0] + '^'
+ $(this).attr('class').split(' ')[1].split('^')[2] + $(this).attr('class').split(' ')[1].split('^')[2]
+ '" checked="checked">'); + '" checked="checked">');
$('#postHomeroomAbs').append('<input type="text" name="note^' + $(this).attr('class').split(' ')[1].split('^')[0] + '^'
+ $(this).attr('class').split(' ')[1].split('^')[2] + '" value="'+ $('#note-' + $(this).attr('class').split(' ')[1].split('^')[1] + '-' + $(this).attr('class').split(' ')[1].split('^')[2]).val() +'">')
} }
}); });
if (cnt == 0) { if (cnt == 0) {
@ -80,6 +88,7 @@ function afterSelAbs(period) {
} }
function homeroomCfrm() { function homeroomCfrm() {
hrCfrm = true; hrCfrm = true;
$('.ds').attr('hidden', 'hidden');
$('#showSignPeriod').text("HOMEROOM CONFIRM"); $('#showSignPeriod').text("HOMEROOM CONFIRM");
$('#showSignSubjectName').text("班導確認"); $('#showSignSubjectName').text("班導確認");
$('.tobeform').attr('disabled', 'disabled'); $('.tobeform').attr('disabled', 'disabled');
@ -88,7 +97,32 @@ function homeroomCfrm() {
} }
function unCheckLate(string) { function unCheckLate(string) {
document.getElementById('late^' + string).checked = false; document.getElementById('late^' + string).checked = false;
if (document.getElementById("absent^" + string).checked == true && $('#note-'+string.split('^')[0]+'-'+string.split('^')[1]).length == 0) {
$('.input-'+string.split('^')[0]+'-'+string.split('^')[1]).append('<input type="text" class="form-control" id="note-'+string.split('^')[0]+'-'+string.split('^')[1]+'" name="'+ string +'">')
} else if (document.getElementById("absent^" + string).checked == false) {
$('#note-'+string.split('^')[0]+'-'+string.split('^')[1]).remove();
}
} }
function unCheckAbs(string) { function unCheckAbs(string) {
document.getElementById('absent^' + string).checked = false; document.getElementById('absent^' + string).checked = false;
if (document.getElementById("late^" + string).checked == true && $('#note-'+string.split('^')[0]+'-'+string.split('^')[1]).length == 0) {
$('.input-'+string.split('^')[0]+'-'+string.split('^')[1]).append('<input type="text" class="form-control" id="note-'+string.split('^')[0]+'-'+string.split('^')[1]+'" name="'+ string +'">')
} else if (document.getElementById("late^" + string).checked == false) {
$('#note-'+string.split('^')[0]+'-'+string.split('^')[1]).remove();
}
} }
function addDS() {
if ($('#dsnumbersel').val() == "" || $('#dsoffensesel').val() == "") {
return;
}
var text = $('#dsoffensesel').val()
if ($('#dsoffenseother').val() != "") {
text = text.concat(": ", $('#dsoffenseother').val());
}
indDS[$("#dsnumbersel").val()] = text;
$('#inddsview>.col').append('<div class="row"><div class="col">'+$("#dsnumbersel").val()+'</div><div class="col">'+text+'</div></div>')
$('#dsnumbersel option[value="'+$("#dsnumbersel").val()+'"]').remove();
$('#dsoffenseother').val("");
$('#dsoffensesel').val("");
$('#dsnumbersel').val("");
}

View file

@ -17,4 +17,6 @@
}, 500); }, 500);
} }
startTime(); startTime();
})(); })();
$(window).resize();
$(window).scroll();

View file

@ -8,6 +8,7 @@
<title>Admin 管理員 - Attendance 點名</title> <title>Admin 管理員 - Attendance 點名</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.1/dist/css/bootstrap.min.css" rel="stylesheet" <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.1/dist/css/bootstrap.min.css" rel="stylesheet"
integrity="sha384-F3w7mX95PdgyTmZZMECAngseQB83DfGTowi0iMjiWaeVhAn4FJkqJByhZMI3AhiU" crossorigin="anonymous"> integrity="sha384-F3w7mX95PdgyTmZZMECAngseQB83DfGTowi0iMjiWaeVhAn4FJkqJByhZMI3AhiU" crossorigin="anonymous">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.4/css/all.min.css" integrity="sha512-1ycn6IcaQQ40/MKBW2W4Rhis/DbILU74C1vSrLJxCq57o941Ym01SwNsOMqvEBFlcgUa6xLiPY/NS5R+E6ztJQ==" crossorigin="anonymous" referrerpolicy="no-referrer" />
<link rel="stylesheet" href="/static/allpages.css"> <link rel="stylesheet" href="/static/allpages.css">
<link rel="stylesheet" href="/static/homeroom.css"> <link rel="stylesheet" href="/static/homeroom.css">
<link rel="shortcut icon" type="image/x-icon" href="/static/favicon.ico" /> <link rel="shortcut icon" type="image/x-icon" href="/static/favicon.ico" />
@ -18,48 +19,17 @@
<body> <body>
<div class="showTime"><span id="showTime"></span></div> <div class="showTime"><span id="showTime"></span></div>
{% include 'sidebar.html' %}
<div class="container"> <div class="container">
<h1 class="margin-top">Admin View | 管理頁面</h1> <h1 class="margin-top">Admin View | 管理頁面</h1>
<h2 class="margin-top">{{homeroomCode[0]}} {{homeroomCode[1]}}</h2> <h2 class="margin-top">{{currRoom[0]}} {{currRoom[1]}}</h2>
<h2>[{{currDate}}]</h2> <h2>[{{currDate}}]</h2>
{% if 'confirm' in absData[currDate] %} {% if 'c' in submission %}
<h2 style="color: rgb(61, 194, 0); text-align: center;">Homeroom Confirmed 班導已確認</h2> <h2 style="color: rgb(61, 194, 0); text-align: center;">Homeroom Confirmed 班導已確認</h2>
{% else %} {% else %}
<h2 style="color: red; text-align: center;">Homeroom NOT Confirmed 班導尚未確認</h2> <h2 style="color: red; text-align: center;">Homeroom NOT Confirmed 班導尚未確認</h2>
{% endif %} {% endif %}
<a href="/logout"><button class="btn btn-primary logout margin-top">Logout 登出</button></a> <div class="col margin-top">
<div class="container margin-bottom">
<div class="row">
<div class="col">
<select name="grade" id="sel-grade" class="form-select" onchange="getHR()" required>
<option value="">選擇年級</option>
{% for grade in homerooms %}
<option value="{{grade}}">{{grade}}</option>
{% endfor %}
</select>
</div>
<div class="col">
<select name="room" id="sel-room" class="form-select" disabled required>
<option value="">請先選擇年級</option>
</select>
</div>
<div class="col">
<select name="date" id="date" class="form-select">
{% for date in absData %}
{% if date == currDate %}
<option value="{{date}}" selected="selected">{{date}} ★</option>
{% else %}
<option value="{{date}}">{{date}}</option>
{% endif %}
{% endfor %}
</select>
</div>
<div class="col-1">
<button type="button" class="btn btn-primary" onclick="redirAdmin()">查詢</button>
</div>
</div>
</div>
<div class="col">
<div class="sticky-top" style="background-color:white;"> <div class="sticky-top" style="background-color:white;">
<div class="row title"> <div class="row title">
<div class="col">班級</div> <div class="col">班級</div>
@ -77,7 +47,7 @@
<div class="col"></div> <div class="col"></div>
{% for i in periods %} {% for i in periods %}
<div class="col"> <div class="col">
{{absData[currDate][i]['name']}}</div> {{schedule[i]['subject']}} {% if schedule[i]['special'] == True %} <span class="text-red">(換)</span> {% endif %}</div>
{% endfor %} {% endfor %}
</div> </div>
<div class="row title"> <div class="row title">
@ -86,73 +56,101 @@
<div class="col"></div> <div class="col"></div>
<div class="col"></div> <div class="col"></div>
{% for i in periods %} {% for i in periods %}
<div class="col">{{absData[currDate][i]['teacher']}}</div> <div class="col">{{schedule[i]['teacher']}}</div>
{% endfor %} {% endfor %}
</div> </div>
</div> </div>
{% for i in homeroomData %} {% for i in students %}
<div class="row"> <div class="row">
<div class="col">{{homeroomCode[0]}}{{homeroomCode[1]}}</div> <div class="col">{{currRoom[0]}}{{currRoom[1]}}</div>
<div class="col">{{i}}</div> <div class="col">{{ i[0] }}</div>
<div class="col">{{ homeroomData[i]['name'] }}</div> <div class="col">{{ i[1] }}</div>
<div class="col">{{ homeroomData[i]['eng_name'] }}</div> <div class="col">{{ i[2] }}</div>
{% for j in periods %} {% for j in periods %}
<div class="col view-{{j}}"> <div class="col view-{{j}}">
{% if 'signature' in absData[currDate][j] %} {% if schedule[j]['subject'] == 'GP' %}
{% if i in absData[currDate][j] %} {% for k in submission[j] %}
{% if absData[currDate][j][i] == 1 %} {% if studGP[i[0]][schedule[j]['teacher']] == k %}
<p class="highlightAbs n-2 view-n-{{i}}">X</p> {% if i[0] in absentData[j] %}
{% if absentData[j][i[0]]['status'] == 'L' %}
<p class="highlightAbs n-3 view-n-{{i[0]}}">𝜑</p>
{% elif absentData[j][i[0]]['status'] == 'K' %}
<p class="highlightAbs n-2 view-n-{{i[0]}}"></p>
{% elif absentData[j][i[0]]['status'] == 'G' %}
<p class="highlightAbs n-2 view-n-{{i[0]}}"></p>
{% elif absentData[j][i[0]]['status'] == 'S' %}
<p class="highlightAbs n-2 view-n-{{i[0]}}"></p>
{% elif absentData[j][i[0]]['status'] == 'F' %}
<p class="highlightAbs n-2 view-n-{{i[0]}}"></p>
{% elif absentData[j][i[0]]['status'] == 'P' %}
<p class="highlightAbs n-2 view-n-{{i[0]}}"></p>
{% elif absentData[j][i[0]]['status'] == 'O' %}
<p class="highlightAbs n-2 view-n-{{i[0]}}"></p>
{% else %} {% else %}
<p class="highlightAbs n-3 view-n-{{i}}">𝜑</p> <p class="highlightAbs n-2 view-n-{{i[0]}}">{{absentData[j][i[0]]['status']}}</p>
{% endif %} {% endif %}
<p class="highlightAbs">{{absentData[j][i[0]]['note']}}</p>
{% else %} {% else %}
{% if absData[currDate][j]['name'] != 'GP' %} <p class="highlightAbs n-1 view-n-{{i[0]}}">V</p>
<p class="highlightAbs n-1 view-n-{{i}}">V</p> {% if j in idvDS and i[0] in idvDS[j] %}
{% else %} <p class="highlightAbs n-2">{{idvDS[j][i[0]]}}</p>
{% if (homeroomData[i]['GP_Class'][absData[currDate][j]['teacher']] in
absData[currDate][j]['signature'] or 'STUD_AFFAIR_OFFICE' in absData[currDate][j]['signature'])%}
<p class="highlightAbs n-1 view-n-{{i}}">V</p>
{% else %}
<p class="highlightAbs n-2 view-n-{{i}}"></p>
{% endif %} {% endif %}
{% endif %} {% endif %}
{% endif %}
{% elif absData[currDate][j]['name'] == 'GP' %}
<p class="highlightAbs"></p>
{% else %} {% else %}
{% if 'confirm' in absData[currDate] %} <p class="highlightAbs view-n-{{i[0]}}"></p>
<p class="highlightAbs"></p> {% endif %}
{% else %} {% endfor %}
<p class="highlightAbs"></p> {% else %}
{% if j in submission %}
{% if i[0] in absentData[j] %}
{% if absentData[j][i[0]]['status'] == 'L' %}
<p class="highlightAbs n-3 view-n-{{i[0]}}">𝜑</p>
{% elif absentData[j][i[0]]['status'] == 'K' %}
<p class="highlightAbs n-2 view-n-{{i[0]}}"></p>
{% elif absentData[j][i[0]]['status'] == 'G' %}
<p class="highlightAbs n-2 view-n-{{i[0]}}"></p>
{% elif absentData[j][i[0]]['status'] == 'S' %}
<p class="highlightAbs n-2 view-n-{{i[0]}}"></p>
{% elif absentData[j][i[0]]['status'] == 'F' %}
<p class="highlightAbs n-2 view-n-{{i[0]}}"></p>
{% elif absentData[j][i[0]]['status'] == 'P' %}
<p class="highlightAbs n-2 view-n-{{i[0]}}"></p>
{% elif absentData[j][i[0]]['status'] == 'O' %}
<p class="highlightAbs n-2 view-n-{{i[0]}}"></p>
{% else %}
<p class="highlightAbs n-2 view-n-{{i[0]}}">{{absentData[j][i[0]]['status']}}</p>
{% endif %}
<p class="highlightAbs">{{absentData[j][i[0]]['note']}}</p>
{% else %}
<p class="highlightAbs n-1 view-n-{{i[0]}}">V</p>
{% if j in idvDS and i[0] in idvDS[j] %}
<p class="highlightAbs n-2">{{idvDS[j][i[0]]}}</p>
{% endif %}
{% endif %}
{% else %}
<p class="highlightAbs view-n-{{i[0]}}"></p>
{% endif %} {% endif %}
<!-- <input type="checkbox" class="tobeform {{j}}^{{i}}"> -->
{% endif %} {% endif %}
</div> </div>
{% endfor %} {% endfor %}
</div> </div>
{% endfor %} {% endfor %}
{% if showUpload == '1' %} {% for i in range(7) %}
<div class="row"> <div class="row" {%if i==0%}style="border-top: 2px solid black;"{%endif%} {% if i == 6 %} style="border-bottom: 2px solid black;" {% endif %}>
<div class="col"></div> <div class="col"></div>
<div class="col"></div> <div class="col"></div>
<div class="col" style="font-weight: bold;">{{dstext[i]}}</div>
<div class="col">{{dsboard[i]}}</div>
{% for j in periods %}
{% if j in submission and schedule[j] != 'GP' %}
<div class="col">
{{submission[j]['ds' + (i+1)|string]}}</div>
{% else %}
<div class="col"></div> <div class="col"></div>
<div class="col"></div> {% endif %}
{% for i in periods %}
<div id="btns-{{i}}" class="col">
{% if ('signature' in absData[currDate][i] or 'confirm' in absData[currDate][i]) %}
<button class="btn btn-danger afterSelButton" onclick="edit('{{i|string}}')">編輯
<br>{{absData[currDate][i]['name']}}</button>
{% endif %}
</div>
{% endfor %} {% endfor %}
</div> </div>
{% endif %} {% endfor %}
<form action="/manage/edit_abs" id="postHomeroomAbs" hidden="hidden" method="post">
<input type="text" id="HR-date" name="date" value="{{currDate}}">
<input type="text" id="HR-period" name="period" value="">
<input type="text" id="HR-notes" name="notes" value="">
<input type="text" id="HR-homeroom" name="homeroom" value="{{homeroomCode[0]}}^{{homeroomCode[1]}}">
</form>
{% for c in range(periods|length + 1) %} {% for c in range(periods|length + 1) %}
{% if c % 4 == 0 %} {% if c % 4 == 0 %}
<div class="row signatures"> <div class="row signatures">
@ -160,38 +158,41 @@
<div class="col half"> <div class="col half">
{% if c == 0 %} {% if c == 0 %}
<div class="row needborder">Homeroom Teacher 導師</div> <div class="row needborder">Homeroom Teacher 導師</div>
{% if 'confirm' in absData[currDate] %} {% if 'c' in submission %}
<div class="row"><img src="{{absData[currDate]['confirm']}}" alt=""></div> <div class="row"><img src="{{submission['c']['signature']}}" alt=""></div>
<div class="row">備註: {{absData[currDate]['notes']}}</div> <div class="row">備註: {{submission['c']['notes']}}</div>
{% else %} {% else %}
<div class="row"><span style="color:red;">No Signature 導師尚未簽名</span></div> <div class="row"><span style="color:red;">No Signature 導師尚未簽名</span></div>
{% endif %} {% endif %}
{% else %} {% else %}
{% if absData[currDate][periods[c-1]]['name'] == 'GP' %} {% if schedule[periods[c-1]]['subject'] == 'GP' %}
{% if 'signature' in absData[currDate][periods[c-1]] %} {% if periods[c-1] in submission %}
{% for i in absData[currDate][periods[c-1]]['signature'] %} {% for i in submission[periods[c-1]] %}
{% if i != 'notes' %}
<div class="row needborder">{{periods[c-1]}}: <div class="row needborder">{{periods[c-1]}}:
{{absData[currDate][periods[c-1]]['teacher']}}: {{i}}: {{schedule[periods[c-1]]['teacher']}}: {{i}}</div>
{{absData[currDate][periods[c-1]]['names'][i]}}</div> <div class="row"><img src="{{submission[periods[c-1]][i]['signature']}}" alt="">
<div class="row"><img src="{{absData[currDate][periods[c-1]]['signature'][i]}}" alt=""> {% if loop.index == loop.length-1 %}
{% if loop.index == loop.length %} <br>備註: {{submission[periods[c-1]]['notes']}}
<br>備註: {{absData[currDate][periods[c-1]]['notes']}}
{% endif %} {% endif %}
</div> </div>
{% endif %}
{% endfor %} {% endfor %}
{% else %} {% else %}
<div class="row needborder">{{periods[c-1]}}: <div class="row needborder">{{periods[c-1]}}:
{{absData[currDate][periods[c-1]]['teacher']}}: No {{schedule[periods[c-1]]['subject']}}: {{i}}: No
Signature Signature
</div> </div>
<div class="row"></div> <div class="row"></div>
{% endif %} {% endif %}
{% else %} {% else %}
<div class="row needborder">{{periods[c-1]}}: {{absData[currDate][periods[c-1]]['name']}}: <div class="row needborder">{{periods[c-1]}}: {{schedule[periods[c-1]]['subject']}}:
{{absData[currDate][periods[c-1]]['teacher']}} {{schedule[periods[c-1]]['teacher']}}
</div> </div>
<div class="row"><img src="{{absData[currDate][periods[c-1]]['signature']}}" alt=""><br>備註: {% if periods[c-1] in submission %}
{{absData[currDate][periods[c-1]]['notes']}}</div> <div class="row"><img src="{{submission[periods[c-1]]['signature']}}" alt=""><br>備註:
{{submission[periods[c-1]]['notes']}}</div>
{% endif %}
{% endif %} {% endif %}
{% endif %} {% endif %}
</div> </div>
@ -200,29 +201,6 @@
{% endif %} {% endif %}
{% endfor %} {% endfor %}
</div> </div>
<div id="finalCheck" class="modal fade" id="staticBackdrop" data-bs-backdrop="static" data-bs-keyboard="false"
tabindex="-1" aria-labelledby="staticBackdropLabel" aria-hidden="true">
<div class="modal-dialog modal-xl">
<form onsubmit="return false;">
<div class="modal-content">
<div class="modal-body">
<div class="alert alert-danger margin-top" id="allPresentWarning" role="alert">
<h4 class="alert-heading">請確認更改Please confirm that you are editting records!</h4>
</div>
<h3 class="margin-top">Notes 備註欄</h3>
<input type="textarea" class="form-control" name="notes" id="subjectNotes"
placeholder="Enter Notes 請輸入備註" style="width: 80%; margin-left: 10%;" row="3">
</div>
<div class="modal-footer">
<button type="button" class="btn btn-danger" onclick="location.reload();">Cancel
取消</button>
<button type="submit" class="btn btn-primary submitButton" onclick="submitForm()">Submit
提交</button>
</div>
</div>
</form>
</div>
</div>
{% if showUpload == '1' %} {% if showUpload == '1' %}
<div class="row margin-top"> <div class="row margin-top">
<div class="col"> <div class="col">

View file

@ -0,0 +1,82 @@
{% extends 'admin/master.html' %}
{% block body %}
<style>
td {
text-align: center;
border: 1px solid black;
padding: 5px;
}
</style>
<h1><img src="/static/favicon.ico" alt="" height="40px" style="vertical-align: bottom;"> 歡迎 後台管理</h1>
<h2>請利用上方選單選取功能</h2>
<h3>特殊功能</h3>
<ul>
<li>為保障用戶安全Users 的密碼欄為 <code>sha256_crypt.hash</code>,請使用 Python 產生出後輸入,或先改 Email寄忘記密碼信最後再改回來。</li>
<ul>
<li><small><code>password</code> 的 hash 為 <code>$5$rounds=535000$VjoY30Bt535dL/7x$xXmg8BZ3VJe/odbQQ5hd0uPmb1o9EhHkJmwWWTc8K21</code></small></li>
</ul>
<li>為簡化程式Students->Classes, GPClasses->Accs, Homerooms->Accs, 分組課的 Submission->Notes 都使用標準 JSON 格式輸入,請留意</li>
<li>請假代碼<br>
<table>
<tbody>
<tr>
<td>遲到</td>
<td>曠課</td>
<td>事假</td>
<td>病假</td>
<td>喪假</td>
<td>疫情假</td>
<td>公假</td>
</tr>
<tr>
<td>L</td>
<td>K</td>
<td>G</td>
<td>S</td>
<td>F</td>
<td>P</td>
<td>O</td>
</tbody>
</table>
</li>
<li>使用者代碼<br>
<table>
<tbody>
<tr>
<td>超級管理員</td>
<td>管理員</td>
<td>一般使用者</td>
</tr>
<tr>
<td>S</td>
<td>A</td>
<td>R</td>
</tr>
</tbody>
</table>
</li>
</ul>
{% if session['showUpload'] %}
<div class="row">
<div class="col">
<h3 style="color: red;">大量匯入</h3>
</div>
<div class="col">
<a href="/upload/1"><button class="btn btn-danger">1. Add Homeroom</button></a>
</div>
<div class="col">
<a href="/upload/2"><button class="btn btn-danger">2. Add GP Classes</button></a>
</div>
<div class="col">
<a href="/upload/3"><button class="btn btn-danger">3. Add GP Student List</button></a>
</div>
<div class="col">
<a href="/upload/4"><button class="btn btn-danger">4. Period List</button></a>
</div>
<div class="col">
<a href="/upload/dates"><button class="btn btn-warning">Dates</button></a>
</div>
</div>
{% endif %}
{% endblock %}

View file

@ -4,7 +4,7 @@
<meta charset="utf-8"> <meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<title>Attendance 點名系統 (β)</title> <title>Attendance 點名系統 2.0</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.1/dist/css/bootstrap.min.css" rel="stylesheet" <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.1/dist/css/bootstrap.min.css" rel="stylesheet"
integrity="sha384-F3w7mX95PdgyTmZZMECAngseQB83DfGTowi0iMjiWaeVhAn4FJkqJByhZMI3AhiU" crossorigin="anonymous"> integrity="sha384-F3w7mX95PdgyTmZZMECAngseQB83DfGTowi0iMjiWaeVhAn4FJkqJByhZMI3AhiU" crossorigin="anonymous">
<link rel="stylesheet" href="/static/allpages.css"> <link rel="stylesheet" href="/static/allpages.css">
@ -24,69 +24,62 @@
<body> <body>
<div class="showTime"><span id="showTime"></span></div> <div class="showTime"><span id="showTime"></span></div>
<div class="container"> <div class="text-center container">
<h1 class="margin-top margin-bottom">Attendance 點名系統 (β) | Change Password 更改密碼</h1> <div class="loginPanel">
<div class="row"> <h1 class="margin-top margin-bottom">
<div class="col"></div> <div class="img"><img src="/static/favicon.ico" alt="" style="height: 100%"> Change Password 更改密碼</div> </h1>
<div class="col-md-5"> <form action="/chgPassword" id="chgPasswordForm" method="post">
<form action="/chgPassword" id="chgPasswordForm" method="post"> <div class="form-group row">
<div class="form-group row"> <label for="password">Old Password 舊密碼:</label><br>
<label for="password">Old Password 舊密碼:</label><br> <div class="input-group mb-3">
<div class="input-group mb-3"> <input type="password" class="form-control" id="password" name="password" required>
<input type="password" class="form-control" id="password" name="password" required> <div class="input-group-append">
<div class="input-group-append"> <span class="input-group-text toggle-password" onclick="password_show_hide(1);">
<span class="input-group-text toggle-password" onclick="password_show_hide(1);"> <i class="fas fa-eye" id="old_show_eye"></i>
<i class="fas fa-eye" id="old_show_eye"></i> <i class="fas fa-eye-slash d-none" id="old_hide_eye"></i>
<i class="fas fa-eye-slash d-none" id="old_hide_eye"></i> </span>
</span>
</div>
</div> </div>
</div> </div>
<div class="form-group row">
<label for="password">New Username 新帳號:</label><br>
<div class="input-group mb-3">
<input type="email" class="form-control" id="new_username" name="new_username" required>
</div>
</div>
<div class="form-group row">
<label for="new_password">New Password 新密碼:</label><br>
<div class="input-group mb-3 hasSmall">
<input type="password" class="form-control" id="new_password" name="new_password"
aria-describedby="passwordHelp" required>
<div class="input-group-append">
<span class="input-group-text toggle-password" onclick="password_show_hide(2);">
<i class="fas fa-eye" id="new_show_eye"></i>
<i class="fas fa-eye-slash d-none" id="new_hide_eye"></i>
</span>
</div>
</div>
<small id="passwordHelp" class="form-text text-muted">6 characters
minimum.</small>
</div>
<button class="btn btn-danger btn-block g-recaptcha" onclick="loadingAnimation()">Change Password
更改密碼</button>
<a href="/"><button type="button" class="btn btn-primary btn-block g-recaptcha"
onclick="loadingAnimation()">Go back 回上一頁</button></a>
</form>
<div class="disclaimer" hidden="hidden">
This site is protected by reCAPTCHA and the Google
<a target="_blank" href="https://policies.google.com/privacy">Privacy Policy</a> and
<a target="_blank" href="https://policies.google.com/terms">Terms of Service</a> apply.
</div> </div>
{% with messages = get_flashed_messages() %} <div class="form-group row">
{% if messages %} <label for="new_username">New Username 新帳號:</label><br>
<div class="alert alert-danger" role="alert"> <div class="input-group mb-3" style="margin-bottom: 5px !important;">
{% for message in messages %} <input type="email" class="form-control" id="new_username" name="new_username">
{% autoescape false %}{{message}}{% endautoescape %} </div>
{% endfor %} <small class="text-muted">If you don't want to change your username, leave this blank.</small>
<small class="text-muted" style="margin-bottom: 1rem;">如果你不想更改你的帳號,請留空。</small>
</div> </div>
{% endif %} <div class="form-group row">
{% endwith %} <label for="new_password">New Password 新密碼:</label><br>
<div class="input-group mb-3 hasSmall">
<input type="password" class="form-control" id="new_password" name="new_password"
aria-describedby="passwordHelp" required>
<div class="input-group-append">
<span class="input-group-text toggle-password" onclick="password_show_hide(2);">
<i class="fas fa-eye" id="new_show_eye"></i>
<i class="fas fa-eye-slash d-none" id="new_hide_eye"></i>
</span>
</div>
</div>
<small id="passwordHelp" class="form-text text-muted">6 characters minimum. 最少 6 字元。</small>
</div>
<button class="btn btn-danger btn-block g-recaptcha" onclick="loadingAnimation()">Change Password
更改密碼</button>
<a href="/"><button type="button" class="btn btn-primary btn-block g-recaptcha"
onclick="loadingAnimation()">Go back 回上一頁</button></a>
</form>
{% with messages = get_flashed_messages() %}
{% if messages %}
<div class="alert alert-danger" role="alert">
{% for message in messages %}
{% autoescape false %}{{message}}{% endautoescape %}
{% endfor %}
</div> </div>
<div class="col"></div> {% endif %}
{% endwith %}
{% include 'footer.html' %}
</div> </div>
</div> </div>
{% include 'footer.html' %}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
<script src="/static/pagejs/chgPassword.js"></script> <script src="/static/pagejs/chgPassword.js"></script>
<script src="/static/time.js"></script> <script src="/static/time.js"></script>

View file

@ -5,8 +5,8 @@
</div> </div>
<footer> <footer>
<hr> <hr>
<p style="text-align: center;">&copy; 2021 Attendance (β) | Made by <a target="_blank" <p style="text-align: center;">&copy; 2021 Attendance <span style="color: goldenrod;">2.0</span> | Made by Mr. Raymond Tsai 蔡瑋倫老師 and
href="https://github.com/aaronleetw">Aaron Lee 李翊愷</a> and Mr. Raymond Tsai 蔡瑋倫老師 <a target="_blank" href="https://github.com/aaronleetw">Aaron Lee 李翊愷</a>
<br>for <a target="_blank" href="https://www.fhjh.tp.edu.tw">Taipei Fuhsing Private School</a> <br>for <a target="_blank" href="https://www.fhjh.tp.edu.tw">Taipei Fuhsing Private School</a>
</p> </p>
</footer> </footer>

View file

@ -5,7 +5,7 @@
<meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="robots" content="noindex" /> <meta name="robots" content="noindex" />
<title>Attendance 點名系統 (β)</title> <title>Attendance 點名系統 2.0</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.1/dist/css/bootstrap.min.css" rel="stylesheet" <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.1/dist/css/bootstrap.min.css" rel="stylesheet"
integrity="sha384-F3w7mX95PdgyTmZZMECAngseQB83DfGTowi0iMjiWaeVhAn4FJkqJByhZMI3AhiU" crossorigin="anonymous"> integrity="sha384-F3w7mX95PdgyTmZZMECAngseQB83DfGTowi0iMjiWaeVhAn4FJkqJByhZMI3AhiU" crossorigin="anonymous">
<link rel="stylesheet" href="/static/allpages.css"> <link rel="stylesheet" href="/static/allpages.css">
@ -20,38 +20,48 @@
<body> <body>
<div class="showTime"><span id="showTime"></span></div> <div class="showTime"><span id="showTime"></span></div>
<div class="container"> <div class="text-center container">
<h1 class="margin-top margin-bottom">Attendance 點名系統 (β) | Reset Password 忘記密碼</h1> <div class="loginPanel">
<div class="row"> <h1 class="margin-top margin-bottom">
<div class="col"></div> <div class="img"><img src="/static/favicon.ico" alt="" style="height: 100%"> Forgot Password 忘記密碼</div> </h1>
<div class="col-md-5"> <form action="/forgotPassword" id="forgotPassword_sel" method="post">
<form action="/forgotPassword" id="forgotPassword_sel" method="post"> <div>
<div class="input-group mb-3"> <input class="form-check-input" type="radio" name="user_type" id="user_type_teacher" value="teacher" checked>
<div class="input-group-prepend"> <label class="form-check-label" for="user_type_teacher" style="margin-right: 20px;">
<span class="input-group-text"><i class="fa fa-user"></i></span> Teacher 教師 / Admin 管理員
</div> </label>
<input type="text" class="form-control" name="username" id="username" placeholder="Username"> <input class="form-check-input" type="radio" name="user_type" id="user_type_student" value="student">
</div> <label class="form-check-label" for="user_type_student">
<button class="btn btn-warning btn-block g-recaptcha" onclick="loadingAnimation()">Confirm Student 學生
確認</button> </label>
</form>
<p>This will send an email to the email address to verify your identity.<br>
這會傳送一個郵件到指定的信箱,以驗證您的身份
</p>
{% with messages = get_flashed_messages() %}
{% if messages %}
<div class="alert alert-danger" role="alert">
{% for message in messages %}
{% autoescape false %}{{message}}{% endautoescape %}
{% endfor %}
</div> </div>
{% endif %} <br>
{% endwith %} <div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-user"></i></span>
</div>
<input type="text" class="form-control" name="username" id="username" placeholder="Username">
</div>
<button class="btn btn-warning btn-block g-recaptcha" onclick="loadingAnimation()">Confirm
確認</button>
<a href="/"><button type="button" class="btn btn-primary btn-block"
onclick="loadingAnimation()">Go back 回上一頁</button></a>
</form>
<p>This will send an email to the email address to verify your identity.<br>
這會傳送一個郵件到指定的信箱,以驗證您的身份
</p>
{% with messages = get_flashed_messages() %}
{% if messages %}
<div class="alert alert-danger" role="alert">
{% for message in messages %}
{% autoescape false %}{{message}}{% endautoescape %}
{% endfor %}
</div> </div>
<div class="col"></div> {% endif %}
{% endwith %}
{% include 'footer.html' %}
</div> </div>
</div> </div>
{% include 'footer.html' %}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
<script src="/static/pagejs/forgotPassword.js"></script> <script src="/static/pagejs/forgotPassword.js"></script>
<script src="/static/time.js"></script> <script src="/static/time.js"></script>

View file

@ -5,10 +5,10 @@
<meta charset="utf-8"> <meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<title>Group 分組 - Attendance 點名</title> <title>Attendance 點名系統 2.0</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.1/dist/css/bootstrap.min.css" rel="stylesheet" <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.1/dist/css/bootstrap.min.css" rel="stylesheet"
integrity="sha384-F3w7mX95PdgyTmZZMECAngseQB83DfGTowi0iMjiWaeVhAn4FJkqJByhZMI3AhiU" crossorigin="anonymous"> integrity="sha384-F3w7mX95PdgyTmZZMECAngseQB83DfGTowi0iMjiWaeVhAn4FJkqJByhZMI3AhiU" crossorigin="anonymous">
<link rel="stylesheet" href="/static/login.css"> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.4/css/all.min.css" integrity="sha512-1ycn6IcaQQ40/MKBW2W4Rhis/DbILU74C1vSrLJxCq57o941Ym01SwNsOMqvEBFlcgUa6xLiPY/NS5R+E6ztJQ==" crossorigin="anonymous" referrerpolicy="no-referrer" />
<link rel="stylesheet" href="/static/allpages.css"> <link rel="stylesheet" href="/static/allpages.css">
<link rel="shortcut icon" type="image/x-icon" href="/static/favicon.ico" /> <link rel="shortcut icon" type="image/x-icon" href="/static/favicon.ico" />
<!-- Global site tag (gtag.js) - Google Analytics --> <!-- Global site tag (gtag.js) - Google Analytics -->
@ -20,96 +20,113 @@
<div class="showTime"><span id="showTime"></span></div> <div class="showTime"><span id="showTime"></span></div>
<div class="container"> <div class="container">
<h1 class="margin-top">Group Class View | 分組課頁面</h1> <h1 class="margin-top">Group Class View | 分組課頁面</h1>
<h2 class="margin-top">{{cclass['category']}}: {{cclass['class_id']}}: {{cclass['name']}}</h2>
<h2>[{{currDate}}]</h2> <h2>[{{currDate}}]</h2>
<a href="/logout"><button class="btn btn-primary margin-top logout">Logout 登出</button></a> <a href="/logout"><button class="btn btn-primary margin-top logout">Logout 登出</button></a>
<a href="/select"><button class="btn btn-primary margin-top logout">Choose Subuser 選擇其他帳號</button></a> <a href="/select"><button class="btn btn-primary margin-top logout">Choose Subuser 選擇其他帳號</button></a>
<select name="date" id="date" class="form-select logout" onchange="chgDate();"> <select name="date" id="date" class="form-select logout" onchange="chgDate();">
{% for date in range(dateKeys|length) %} {% for date in range(dates|length) %}
{% if dateKeys[date] == currDate %} {% if dates[date][0] == currDate %}
{% if date-2 >= 0 %}<option value="{{dateKeys[date-2]}}">{{dateKeys[date-2]}}</option>{% endif %} {% if date-2 >= 0 %}<option value="{{dates[date-2][0]}}">{{dates[date-2][0]}}</option>{% endif %}
{% if date-1 >= 0 %}<option value="{{dateKeys[date-1]}}">{{dateKeys[date-1]}}</option>{% endif %} {% if date-1 >= 0 %}<option value="{{dates[date-1][0]}}">{{dates[date-1][0]}}</option>{% endif %}
<option value="{{dateKeys[date]}}" selected="selected">{{dateKeys[date]}} ★</option> <option value="{{dates[date][0]}}" selected="selected">{{dates[date][0]}} ★</option>
{% for i in range(1,5) %} {% for i in range(1,5) %}
{% if date+i < dateKeys|length %}<option value="{{dateKeys[date+i]}}">{{dateKeys[date+i]}}</option>{%endif%} {% if date+i < dates|length %}<option value="{{dates[date+i][0]}}">{{dates[date+i][0]}}</option>{%endif%}
{% endfor %} {% endfor %}
{% endif %} {% endif %}
{% endfor %} {% endfor %}
</select> </select>
{% for i in absData %} {% for c in data %}
{% set alr_fill = namespace(found=false) %} {% for p in data[c] %}
<form action="/manage/group_teach_publish" id="attendanceData^{{i}}" method="post"> {% if p == 'cdata' %}
{% continue %}
{% endif %}
<h3 class="nmb margin-top">Period {{p}}: {{data[c]['cdata']['name']}}</h3>
<form action="/manage/group_teach_publish" id="attendanceData^{{p}}" method="post">
<input type="hidden" name="date" value="{{currDate}}"> <input type="hidden" name="date" value="{{currDate}}">
<input type="hidden" name="period" value="{{i}}"> <input type="hidden" name="period" value="{{p}}">
<input type="hidden" class="signatureData" name="signatureData" value=""> <input type="hidden" class="signatureData" name="signatureData" value="">
<div class="col" style="margin-top: 30px;"> <input type="hidden" name="category" value="{{data[c]['cdata']['category']}}">
<input type="hidden" name="class_id" value="{{data[c]['cdata']['class_id']}}">
<div class="col" style="margin-top: 5px;">
<div class="row title sticky-top" style="background-color: white"> <div class="row title sticky-top" style="background-color: white">
<div class="col">Grade 年級</div> <div class="col">Grade 年級</div>
<div class="col">Class 班級</div>
<div class="col">Number 座號</div> <div class="col">Number 座號</div>
<div class="col">Name 姓名</div> <div class="col">Name 姓名</div>
<div class="col">English Name 英文名</div> <div class="col">English Name 英文名</div>
<div class="col">Period {{i}} | 第 {{i}} 節</div> <div class="col">Period {{p}} | 第 {{p}} 節</div>
</div> </div>
{% if data != None %} {% set need_fill = namespace(found=false) %}
{% for grade in absData[i] %} {% if data[c] != None %}
{% for homeroom in absData[i][grade] %} {% for grade in data[c][p] %}
{% for student in absData[i][grade][homeroom] %} {% for student in data[c][p][grade] %}
{% if student != 'notes' %}
<div class="row"> <div class="row">
<div class="col">{{grade}}</div> <div class="col">{{grade}}</div>
<div class="col">{{homeroom}}</div>
<div class="col">{{ student }}</div> <div class="col">{{ student }}</div>
<div class="col">{{ absData[i][grade][homeroom][student]['name'] }}</div> <div class="col">{{ data[c][p][grade][student]['name'] }}</div>
<div class="col">{{ absData[i][grade][homeroom][student]['eng_name'] }}</div> <div class="col">{{ data[c][p][grade][student]['ename'] }}</div>
{% if absData[i][grade][homeroom][student]['alr_fill'] %} {% if data[c][p][grade][student]['status'] == 'L' %}
{% set alr_fill.found = true %}
{% if absData[i][grade][homeroom][student]['absent'] == 1 %}
<div class="col"> <div class="col">
<p class="highlightAbs n-2">X</p> <p class="highlightAbs n-3">𝜑 <span style="color:black">{{data[c][p][grade][student]['note']}}</span></p>
</div> </div>
{% elif absData[i][grade][homeroom][student]['absent'] == 2 %} {% elif data[c][p][grade][student]['status'] == 'K' %}
<div class="col"> <div class="col">
<p class="highlightAbs n-3">𝜑</p> <p class="highlightAbs n-2"><span style="color:black">{{data[c][p][grade][student]['note']}}</span></p>
</div> </div>
{% else %} {% elif data[c][p][grade][student]['status'] == 'G' %}
<div class="col">
<p class="highlightAbs n-2"><span style="color:black">{{data[c][p][grade][student]['note']}}</span></p>
</div>
{% elif data[c][p][grade][student]['status'] == 'S' %}
<div class="col">
<p class="highlightAbs n-2"><span style="color:black">{{data[c][p][grade][student]['note']}}</span></p>
</div>
{% elif data[c][p][grade][student]['status'] == 'F' %}
<div class="col">
<p class="highlightAbs n-2"><span style="color:black">{{data[c][p][grade][student]['note']}}</span></p>
</div>
{% elif data[c][p][grade][student]['status'] == 'P' %}
<div class="col">
<p class="highlightAbs n-2"><span style="color:black">{{data[c][p][grade][student]['note']}}</span></p>
</div>
{% elif data[c][p][grade][student]['status'] == 'O' %}
<div class="col">
<p class="highlightAbs n-2"><span style="color:black">{{data[c][p][grade][student]['note']}}</span></p>
</div>
{% elif data[c][p][grade][student]['status'] == '--' %}
<div class="col">
<p class="highlightAbs">--</p>
</div>
{% elif data[c][p][grade][student]['status'] == 'present' %}
<div class="col"> <div class="col">
<p class="highlightAbs n-1">V</p> <p class="highlightAbs n-1">V</p>
</div> </div>
{% endif %}
{% else %} {% else %}
<div class="col"> <div id="input^{{p}}^{{grade}}^{{student}}" class="col">
{% if [grade,homeroom] in confirmed %} {% set need_fill.found = true %}
<p class="highlightAbs">--</p> <input type="checkbox" class="tobeform {{grade}}^{{student}} late"
{% else %} id="late^{{p}}^{{grade}}^{{student}}"
{% set alr_fill.found = false %} name="2^{{grade}}^{{student}}"
<input type="checkbox" class="tobeform {{grade}}^{{homeroom}}^{{student}} late" onchange="unCheckAbs('{{p}}^{{grade}}^{{student}}')">
id="late^{{i}}^{{grade}}^{{homeroom}}^{{student}}" <input type="checkbox" class="tobeform {{grade}}^{{student}} absent"
name="2^{{grade}}^{{homeroom}}^{{student}}" id="absent^{{p}}^{{grade}}^{{student}}"
onchange="unCheckAbs('{{i}}^{{grade}}^{{homeroom}}^{{student}}')"> name="1^{{grade}}^{{student}}"
<input type="checkbox" class="tobeform {{grade}}^{{homeroom}}^{{student}} absent" onchange="unCheckLate('{{p}}^{{grade}}^{{student}}')">
id="absent^{{i}}^{{grade}}^{{homeroom}}^{{student}}"
name="1^{{grade}}^{{homeroom}}^{{student}}"
onchange="unCheckLate('{{i}}^{{grade}}^{{homeroom}}^{{student}}')">
{% endif %}
</div> </div>
{% endif %} {% endif %}
</div> </div>
{% endif %}
{% endfor %}
{% endfor %} {% endfor %}
{% endfor %} {% endfor %}
{% endif %} {% endif %}
{% if alr_fill.found %} {% if not need_fill.found %}
<button class="btn btn-primary margin-bottom viewSignatureBtn" type="button" <button class="btn btn-primary margin-bottom viewSignatureBtn" type="button"
onclick="viewSignature('{{i}}')" disabled="disabled"> onclick="viewSignature('{{p}}')" disabled="disabled">
Already Submitted</button> Already Submitted</button>
{% else %} {% else %}
<button class="btn btn-primary margin-bottom viewSignatureBtn" type="button" <button class="btn btn-primary margin-bottom viewSignatureBtn" type="button"
onclick="viewSignature('{{i}}')"> onclick="viewSignature('{{p}}')">
↑ Confirm 確認 (Period {{i}}) ↑</button> ↑ Confirm 確認 (Period {{p}}) ↑</button>
{% endif %} {% endif %}
<div id="sign-{{i}}" class="signDiv modal fade" id="staticBackdrop" data-bs-backdrop="static" <div id="sign-{{p}}" class="signDiv modal fade" id="staticBackdrop" data-bs-backdrop="static"
data-bs-keyboard="false" tabindex="-1" aria-labelledby="staticBackdropLabel" aria-hidden="true"> data-bs-keyboard="false" tabindex="-1" aria-labelledby="staticBackdropLabel" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered modal-xl"> <div class="modal-dialog modal-dialog-centered modal-xl">
<div class="modal-content"> <div class="modal-content">
@ -117,14 +134,52 @@
<h5 class="modal-title" id="staticBackdropLabel">Please Sign Below 請在下方簽名</h5> <h5 class="modal-title" id="staticBackdropLabel">Please Sign Below 請在下方簽名</h5>
</div> </div>
<div class="modal-body"> <div class="modal-body">
<div class="alert alert-warning margin-top" id="allPresentWarning-{{i}}" role="alert" <div class="alert alert-warning margin-top" id="allPresentWarning-{{p}}" role="alert"
hidden="hidden"> hidden="hidden">
<h4 class="alert-heading">請確認是否全班全到Please check if everyone is present!</h4> <h4 class="alert-heading">請確認是否全班全到Please check if everyone is present!</h4>
</div> </div>
<div class="forSign"><canvas id="signature_pad^{{i}}"></canvas></div> <div class="forSign"><canvas id="signature_pad^{{p}}"></canvas></div>
<h3 class="margin-top">Notes 備註欄</h3> <h3 class="margin-top">Notes 備註欄</h3>
<input type="textarea" class="form-control" name="notes" id="subjectNotes^{{i}}" <input type="textarea" class="form-control" name="notes" id="subjectNotes^{{p}}"
placeholder="Enter Notes 請輸入備註" style="width: 80%; margin-left: 10%;" row="3"> placeholder="Enter Notes 請輸入備註" style="width: 80%; margin-left: 10%;" row="3">
<style>
#indds div {
padding-left: 0;
}
#indds button {
margin-top: 0;
}
</style>
<div class="ds">
<h3 class="margin-top">定心專案</h3>
<div id="indds" class="row margin-top" style="width: 90%; margin-left: 5%">
<div class="col-2">
<select class="form-select" name="dsnumbers" id="dsnumbersel">
<option value="">選擇號碼</option>
{% for grade in data[c][p] %}
{% for i in data[c][p][grade] %}
{% if data[c][p][grade][i]['status'] == 'na' %}
<option value="{{grade}}-{{i}}-{{data[c][p][grade][i]['name']}}-{{data[c][p][grade][i]['ename']}}">{{grade}}-{{i}}-{{data[c][p][grade][i]['name']}}-{{data[c][p][grade][i]['ename']}}</option>
{% endif %}
{% endfor %}
{% endfor %}
</select>
</div>
<div class="col-4">
<select name="dsoffense" id="dsoffensesel" class="form-select">
<option value="">選擇違規事由</option>
{% for i in dsoffenses %}
<option value="{{i}}-{{dsoffenses[i]}}">{{i}}-{{dsoffenses[i]}}</option>
{% endfor %}
</select>
</div>
<div class="col-5">
<input type="text" class="form-control" name="dsoffenseother" id="dsoffenseother" placeholder="Other">
</div>
<div class="col noborder"><button type="button" class="btn btn-secondary" onclick="addDS()"><i class="fas fa-plus"></i></button></div>
</div>
<div id="inddsview" class="margin-top" style="width: 90%; margin-left: 5%;"><div class="col"></div></div>
</div>
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<button class="btn btn-secondary" type="button" onclick="signaturePad.clear()">Clear <button class="btn btn-secondary" type="button" onclick="signaturePad.clear()">Clear
@ -140,6 +195,7 @@
</div> </div>
</form> </form>
{% endfor %} {% endfor %}
{% endfor %}
</div> </div>
<script src=" https://cdn.jsdelivr.net/npm/signature_pad@2.3.2/dist/signature_pad.min.js"></script> <script src=" https://cdn.jsdelivr.net/npm/signature_pad@2.3.2/dist/signature_pad.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>

View file

@ -5,9 +5,10 @@
<meta charset="utf-8"> <meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<title>Homeroom 班級 {{homeroomCode[0]}}{{homeroomCode[1]}}) - Attendance 點名</title> <title>Attendance 點名系統 2.0</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.1/dist/css/bootstrap.min.css" rel="stylesheet" <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.1/dist/css/bootstrap.min.css" rel="stylesheet"
integrity="sha384-F3w7mX95PdgyTmZZMECAngseQB83DfGTowi0iMjiWaeVhAn4FJkqJByhZMI3AhiU" crossorigin="anonymous"> integrity="sha384-F3w7mX95PdgyTmZZMECAngseQB83DfGTowi0iMjiWaeVhAn4FJkqJByhZMI3AhiU" crossorigin="anonymous">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.4/css/all.min.css" integrity="sha512-1ycn6IcaQQ40/MKBW2W4Rhis/DbILU74C1vSrLJxCq57o941Ym01SwNsOMqvEBFlcgUa6xLiPY/NS5R+E6ztJQ==" crossorigin="anonymous" referrerpolicy="no-referrer" />
<link rel="stylesheet" href="/static/allpages.css"> <link rel="stylesheet" href="/static/allpages.css">
<link rel="stylesheet" href="/static/homeroom.css"> <link rel="stylesheet" href="/static/homeroom.css">
<link rel="shortcut icon" type="image/x-icon" href="/static/favicon.ico" /> <link rel="shortcut icon" type="image/x-icon" href="/static/favicon.ico" />
@ -18,27 +19,26 @@
<body> <body>
<div class="showTime"><span id="showTime"></span></div> <div class="showTime"><span id="showTime"></span></div>
{% include 'sidebar.html' %}
<div class="container"> <div class="container">
<h1 class="margin-top">Homeroom View | 班級主頁</h1> <h1 class="margin-top">Homeroom View | 班級主頁</h1>
<h2 class="margin-top">{{homeroomCode[0]}}{{homeroomCode[1]}}</h2> <h2 class="margin-top">{{currRoom[0]}}{{currRoom[1]}}</h2>
<h2>[{{currDate}}]</h2> <h2>[{{currDate}}]</h2>
<a href="/logout"><button class="btn btn-primary logout margin-top">Logout 登出</button></a>
<a href="/select"><button class="btn btn-primary margin-top logout">Choose Subuser 選擇其他帳號</button></a>
<select name="date" id="date" class="form-select logout" onchange="chgDate();"> <select name="date" id="date" class="form-select logout" onchange="chgDate();">
{% for date in range(dateKeys|length) %} {% for date in range(dates|length) %}
{% if dateKeys[date] == currDate %} {% if dates[date][0] == currDate %}
{% if date-2 >= 0 %}<option value="{{dateKeys[date-2]}}">{{dateKeys[date-2]}}</option>{% endif %} {% if date-2 >= 0 %}<option value="{{dates[date-2][0]}}">{{dates[date-2][0]}}</option>{% endif %}
{% if date-1 >= 0 %}<option value="{{dateKeys[date-1]}}">{{dateKeys[date-1]}}</option>{% endif %} {% if date-1 >= 0 %}<option value="{{dates[date-1][0]}}">{{dates[date-1][0]}}</option>{% endif %}
<option value="{{dateKeys[date]}}" selected="selected">{{dateKeys[date]}} ★</option> <option value="{{dates[date][0]}}" selected="selected">{{dates[date][0]}} ★</option>
{% for i in range(1,5) %} {% for i in range(1,5) %}
{% if date+i < dateKeys|length %}<option value="{{dateKeys[date+i]}}">{{dateKeys[date+i]}}</option>{%endif%} {% if date+i < dates|length %}<option value="{{dates[date+i][0]}}">{{dates[date+i][0]}}</option>{%endif%}
{% endfor %} {% endfor %}
{% endif %} {% endif %}
{% endfor %} {% endfor %}
</select> </select>
<form action="/manage/homeroom_confirm" id="homeroom_confirm" method="post"> <form action="/manage/homeroom_confirm" id="homeroom_confirm" method="post">
<input type="hidden" name="date" value="{{currDate}}"> <input type="hidden" name="date" value="{{currDate}}">
<input type="hidden" name="homeroom" value="{{homeroomCode[0]}}^{{homeroomCode[1]}}"> <input type="hidden" name="homeroom" value="{{currRoom[0]}}^{{currRoom[1]}}">
<input type="hidden" id="hrCfrm-sign" name="signatureData" value=""> <input type="hidden" id="hrCfrm-sign" name="signatureData" value="">
<input type="hidden" id="hrCfrm-notes" name="notes" value=""> <input type="hidden" id="hrCfrm-notes" name="notes" value="">
</form> </form>
@ -50,7 +50,7 @@
<div class="col">姓名</div> <div class="col">姓名</div>
<div class="col">英文姓名</div> <div class="col">英文姓名</div>
{% for i in periods %} {% for i in periods %}
<div class="col" {% if currPeriod==i %} style="background-color: #ffdf81;" {% endif %}>{{i}}</div> <div class="col" {% if currPeriod==i %}style="background-color: #ffdf81;"{%endif%}>{{i}}</div>
{% endfor %} {% endfor %}
</div> </div>
<div class="row title"> <div class="row title">
@ -59,8 +59,8 @@
<div class="col"></div> <div class="col"></div>
<div class="col"></div> <div class="col"></div>
{% for i in periods %} {% for i in periods %}
<div class="col" {% if currPeriod==i %} style="background-color: #ffdf81;" {% endif %}> <div class="col" {% if currPeriod==i %}style="background-color: #ffdf81;"{%endif%}>
{{absData[currDate][i]['name']}}</div> {{schedule[i]['subject']}} {% if schedule[i]['special'] == True %} <span class="text-red">(換)</span> {% endif %}</div>
{% endfor %} {% endfor %}
</div> </div>
<div class="row title"> <div class="row title">
@ -69,50 +69,87 @@
<div class="col"></div> <div class="col"></div>
<div class="col"></div> <div class="col"></div>
{% for i in periods %} {% for i in periods %}
<div class="col" {% if currPeriod==i %} style="background-color: #ffdf81;" {% endif %}> <div class="col" {% if currPeriod==i %}style="background-color: #ffdf81;"{%endif%}>{{schedule[i]['teacher']}}</div>
{{absData[currDate][i]['teacher']}}</div>
{% endfor %} {% endfor %}
</div> </div>
</div> </div>
{% for i in homeroomData %} {% for i in students %}
<div class="row"> <div class="row">
<div class="col">{{homeroomCode[0]}}{{homeroomCode[1]}}</div> <div class="col">{{currRoom[0]}}{{currRoom[1]}}</div>
<div class="col">{{i}}</div> <div class="col">{{ i[0] }}</div>
<div class="col">{{ homeroomData[i]['name'] }}</div> <div class="col">{{ i[1] }}</div>
<div class="col">{{ homeroomData[i]['eng_name'] }}</div> <div class="col">{{ i[2] }}</div>
{% for j in periods %} {% for j in periods %}
<div class="col view-{{j}}" {% if currPeriod==j %} style="background-color: #ffdf81;" {% endif %}> <div class="col view-{{j}}" {% if currPeriod==j %}style="background-color: #ffdf81;"{%endif%}>
{% if 'signature' in absData[currDate][j] %} {% if schedule[j]['subject'] == 'GP' %}
{% if i in absData[currDate][j] %} {% for k in submission[j] %}
{% if absData[currDate][j][i] == 1 %} {% if studGP[i[0]][schedule[j]['teacher']] == k %}
<p class="highlightAbs n-2 view-n-{{i}}">X</p> {% if i[0] in absentData[j] %}
{% if absentData[j][i[0]]['status'] == 'L' %}
<p class="highlightAbs n-3 view-n-{{i[0]}}">𝜑</p>
{% elif absentData[j][i[0]]['status'] == 'K' %}
<p class="highlightAbs n-2 view-n-{{i[0]}}"></p>
{% elif absentData[j][i[0]]['status'] == 'G' %}
<p class="highlightAbs n-2 view-n-{{i[0]}}"></p>
{% elif absentData[j][i[0]]['status'] == 'S' %}
<p class="highlightAbs n-2 view-n-{{i[0]}}"></p>
{% elif absentData[j][i[0]]['status'] == 'F' %}
<p class="highlightAbs n-2 view-n-{{i[0]}}"></p>
{% elif absentData[j][i[0]]['status'] == 'P' %}
<p class="highlightAbs n-2 view-n-{{i[0]}}"></p>
{% elif absentData[j][i[0]]['status'] == 'O' %}
<p class="highlightAbs n-2 view-n-{{i[0]}}"></p>
{% else %} {% else %}
<p class="highlightAbs n-3 view-n-{{i}}">𝜑</p> <p class="highlightAbs n-2 view-n-{{i[0]}}">{{absentData[j][i[0]]['status']}}</p>
{% endif %}
<p class="highlightAbs">{{absentData[j][i[0]]['note']}}</p>
{% else %}
<p class="highlightAbs n-1 view-n-{{i[0]}}">V</p>
{% if j in idvDS and i[0] in idvDS[j] %}
<p class="highlightAbs n-2">{{idvDS[j][i[0]]}}</p>
{% endif %}
{% endif %} {% endif %}
{% else %} {% else %}
{% if absData[currDate][j]['name'] != 'GP' %} <p class="highlightAbs view-n-{{i[0]}}"></p>
<p class="highlightAbs n-1 view-n-{{i}}">V</p> {% endif %}
{% endfor %}
{% else %} {% else %}
{% if (homeroomData[i]['GP_Class'][absData[currDate][j]['teacher']] in {% if j in submission or j in absentData %}
absData[currDate][j]['signature'] or 'STUD_AFFAIR_OFFICE' in absData[currDate][j]['signature'])%} {% if i[0] in absentData[j] %}
<p class="highlightAbs n-1 view-n-{{i}}">V</p> {% if absentData[j][i[0]]['status'] == 'L' %}
<p class="highlightAbs n-3 view-n-{{i[0]}}">𝜑</p>
{% elif absentData[j][i[0]]['status'] == 'K' %}
<p class="highlightAbs n-2 view-n-{{i[0]}}"></p>
{% elif absentData[j][i[0]]['status'] == 'G' %}
<p class="highlightAbs n-2 view-n-{{i[0]}}"></p>
{% elif absentData[j][i[0]]['status'] == 'S' %}
<p class="highlightAbs n-2 view-n-{{i[0]}}"></p>
{% elif absentData[j][i[0]]['status'] == 'F' %}
<p class="highlightAbs n-2 view-n-{{i[0]}}"></p>
{% elif absentData[j][i[0]]['status'] == 'P' %}
<p class="highlightAbs n-2 view-n-{{i[0]}}"></p>
{% elif absentData[j][i[0]]['status'] == 'O' %}
<p class="highlightAbs n-2 view-n-{{i[0]}}"></p>
{% else %} {% else %}
<p class="highlightAbs n-2 view-n-{{i}}"></p> <p class="highlightAbs n-2 view-n-{{i[0]}}">{{absentData[j][i[0]]['status']}}</p>
{% endif %}
<p class="highlightAbs">{{absentData[j][i[0]]['note']}}</p>
{% else %}
{% if j in submission %}
<p class="highlightAbs n-1 view-n-{{i[0]}}">V</p>
{% if j in idvDS and i[0] in idvDS[j] %}
<p class="highlightAbs n-2">{{idvDS[j][i[0]]}}</p>
{% endif %}
{% else %}
<div class="input-{{j}}-{{i[0]}}">
<input type="checkbox" class="tobeform 2^{{j}}^{{i[0]}} late" id="late^{{j}}^{{i[0]}}"
onchange="unCheckAbs('{{j}}^{{i[0]}}')">
<input type="checkbox" class="tobeform 1^{{j}}^{{i[0]}} absent" id="absent^{{j}}^{{i[0]}}"
onchange="unCheckLate('{{j}}^{{i[0]}}')">
</div>
{% endif %} {% endif %}
{% endif %} {% endif %}
{% endif %} {% endif %}
{% elif absData[currDate][j]['name'] == 'GP' %}
<p class="highlightAbs"></p>
{% else %}
{% if 'confirm' in absData[currDate] %}
<p class="highlightAbs"></p>
{% else %}
<input type="checkbox" class="tobeform 2^{{j}}^{{i}} late" id="late^{{j}}^{{i}}"
onchange="unCheckAbs('{{j}}^{{i}}')">
<input type="checkbox" class="tobeform 1^{{j}}^{{i}} absent" id="absent^{{j}}^{{i}}"
onchange="unCheckLate('{{j}}^{{i}}')">
{% endif %}
<!-- <input type="checkbox" class="tobeform {{j}}^{{i}}"> -->
{% endif %} {% endif %}
</div> </div>
{% endfor %} {% endfor %}
@ -125,25 +162,46 @@
<div class="col"></div> <div class="col"></div>
{% for i in periods %} {% for i in periods %}
<div id="btns-{{i}}" class="col" {% if currPeriod==i %} style="background-color: #ffdf81;" {% endif %}> <div id="btns-{{i}}" class="col" {% if currPeriod==i %} style="background-color: #ffdf81;" {% endif %}>
{% if (absData[currDate][i]['name'] == 'GP' or 'confirm' in absData[currDate] or 'signature' in {% if (schedule[i]['subject'] == 'GP' or i in submission or 'c' in submission) %}
absData[currDate][i]) %}
<button class="btn btn-primary afterSelButton" disabled="disabled"></button> <button class="btn btn-primary afterSelButton" disabled="disabled"></button>
{% else %} {% else %}
<button class="btn btn-primary afterSelButton" <button class="btn btn-primary afterSelButton"
onclick="afterSelAbs('{{i|string}}', 'newSubmit')">Confirm<br>{{absData[currDate][i]['name']}}</button> onclick="afterSelAbs('{{i|string}}', 'newSubmit')">Confirm<br>{{schedule[i]['subject']}}</button>
{% endif %} {% endif %}
</div> </div>
{% endfor %} {% endfor %}
</div> </div>
{% for i in range(7) %}
<div class="row" {%if i==0%}style="border-top: 2px solid black;"{%endif%} {% if i == 6 %} style="border-bottom: 2px solid black;" {% endif %}>
<div class="col"></div>
<div class="col"></div>
<div class="col" style="font-weight: bold;">{{dstext[i]}}</div>
<div class="col">{{dsboard[i]}}</div>
{% for j in periods %}
{% if j in submission and schedule[j] != 'GP' %}
<div class="col" {% if currPeriod==j %} style="background-color: #ffdf81;" {% endif %}>
{{submission[j]['ds' + (i+1)|string]}}</div>
{% else %}
<div class="col" {% if currPeriod==j %} style="background-color: #ffdf81;" {% endif %}></div>
{% endif %}
{% endfor %}
</div>
{% endfor %}
<form action="/manage/homeroom_abs" id="postHomeroomAbs" hidden="hidden" method="post"> <form action="/manage/homeroom_abs" id="postHomeroomAbs" hidden="hidden" method="post">
<input type="text" id="HR-date" name="date" value="{{currDate}}"> <input type="text" id="HR-date" name="date" value="{{currDate}}">
<input type="text" id="HR-period" name="period" value=""> <input type="text" id="HR-period" name="period" value="">
<input type="text" id="HR-signatureData" name="signatureData" value=""> <input type="text" id="HR-signatureData" name="signatureData" value="">
<input type="text" id="HR-notes" name="notes" value=""> <input type="text" id="HR-notes" name="notes" value="">
<input type="text" id="HR-homeroom" name="homeroom" value="{{homeroomCode[0]}}^{{homeroomCode[1]}}"> <input type="text" id="HR-homeroom" name="homeroom" value="{{currRoom[0]}}^{{currRoom[1]}}">
<input type="text" id="HR-type" name="stype" value=""> <input type="text" id="HR-ds1" name="ds^1" value="">
<input type="text" id="HR-ds2" name="ds^2" value="">
<input type="text" id="HR-ds3" name="ds^3" value="">
<input type="text" id="HR-ds4" name="ds^4" value="">
<input type="text" id="HR-ds5" name="ds^5" value="">
<input type="text" id="HR-ds6" name="ds^6" value="">
<input type="text" id="HR-ds7" name="ds^7" value="">
</form> </form>
{% if 'confirm' in absData[currDate] %} {% if 'c' in submission %}
<button class="btn btn-primary margin-top afterSelButton" onclick="homeroomCfrm()" disabled="disabled"> <button class="btn btn-primary margin-top afterSelButton" onclick="homeroomCfrm()" disabled="disabled">
Homeroom Teacher Already Confirmed | 班導已確認</button> Homeroom Teacher Already Confirmed | 班導已確認</button>
{% else %} {% else %}
@ -165,17 +223,55 @@
<h4 class="alert-heading">請確認是否全班全到Please check if everyone is present!</h4> <h4 class="alert-heading">請確認是否全班全到Please check if everyone is present!</h4>
</div> </div>
<div class="forSign"><canvas id="signature_pad"></canvas></div> <div class="forSign"><canvas id="signature_pad"></canvas></div>
<div class="ds">
<h3 class="margin-top">定心專案 DS</h3>
{% for i in range(dsboard|length) %}
<div class="row dsboard" style="width: 40%; margin-left: 30%;">
<div class="col col-4">
<span>{{dsboard[i]}}</span>
</div>
{% for j in range(6) %}
<div class="col">
<input class="form-check-input" type="radio" name="ds{{i+1}}" id="ds{{i+1}}{{j}}" value="{{j}}" {%if j==5%}checked{%endif%}>
<label class="form-check-label" for="ds{{i+1}}{{j}}">{{j}}</label>
</div>
{% endfor %}
</div>
{% endfor %}
<div id="indds" class="row margin-top" style="width: 90%; margin-left: 5%">
<div class="col-2">
<select class="form-select" name="dsnumbers" id="dsnumbersel">
<option value="">選擇號碼</option>
{% for i in students %}
<option value="{{i[0]}}-{{i[1]}}-{{i[2]}}">{{i[0]}}-{{i[1]}}-{{i[2]}}</option>
{% endfor %}
</select>
</div>
<div class="col-4">
<select name="dsoffense" id="dsoffensesel" class="form-select">
<option value="">選擇違規事由</option>
{% for i in dsoffenses %}
<option value="{{i}}-{{dsoffenses[i]}}">{{i}}-{{dsoffenses[i]}}</option>
{% endfor %}
</select>
</div>
<div class="col-5">
<input type="text" class="form-control" name="dsoffenseother" id="dsoffenseother" placeholder="Other">
</div>
<div class="col noborder"><button class="btn btn-secondary nomargin-top" onclick="addDS()"><i class="fas fa-plus"></i></button></div>
</div>
<div id="inddsview" class="margin-top" style="width: 90%; margin-left: 5%;"><div class="col"></div></div>
</div>
<h3 class="margin-top">Notes 備註欄</h3> <h3 class="margin-top">Notes 備註欄</h3>
<input type="textarea" class="form-control" name="notes" id="subjectNotes" <input type="textarea" class="form-control" name="notes" id="subjectNotes"
placeholder="Enter Notes 請輸入備註" style="width: 80%; margin-left: 10%;" row="3"> placeholder="Enter Notes 請輸入備註" style="width: 80%; margin-left: 10%;" row="3">
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<button class="btn btn-secondary" type="button" onclick="signaturePad.clear()">Clear <div class="row">
Signature 清除簽名</button> <div class="col"><button class="btn btn-secondary" type="button" onclick="signaturePad.clear()">Clear Signature 清除簽名</button></div>
<button type="button" class="btn btn-danger" onclick="location.reload();">Cancel <div class="col"><button type="button" class="btn btn-danger" onclick="location.reload();">Cancel 取消</button></div>
取消</button> <div class="col"><button type="button" class="btn btn-primary" onclick="submitForm()">Submit 提交</button></div>
<button type="button" class="btn btn-primary submitButton" onclick="submitForm()">Submit </div>
提交</button>
</div> </div>
</div> </div>
</div> </div>
@ -187,36 +283,41 @@
<div class="col half"> <div class="col half">
{% if c == 0 %} {% if c == 0 %}
<div class="row needborder">Homeroom Teacher 導師</div> <div class="row needborder">Homeroom Teacher 導師</div>
{% if 'confirm' in absData[currDate] %} {% if 'c' in submission %}
<div class="row"><img src="{{absData[currDate]['confirm']}}" alt=""></div> <div class="row"><img src="{{submission['c']['signature']}}" alt=""></div>
<div class="row">備註: {{absData[currDate]['notes']}}</div> <div class="row">備註: {{submission['c']['notes']}}</div>
{% else %} {% else %}
<div class="row"><span style="color:red;">No Signature 導師尚未簽名</span></div> <div class="row"><span style="color:red;">No Signature 導師尚未簽名</span></div>
{% endif %} {% endif %}
{% else %} {% else %}
{% if absData[currDate][periods[c-1]]['name'] == 'GP' %} {% if schedule[periods[c-1]]['subject'] == 'GP' %}
{% if 'signature' in absData[currDate][periods[c-1]] %} {% if periods[c-1] in submission %}
{% for i in absData[currDate][periods[c-1]]['signature'] %} {% for i in submission[periods[c-1]] %}
<div class="row needborder">{{periods[c-1]}}: {{absData[currDate][periods[c-1]]['teacher']}}: {{i}}: {% if i != 'notes' %}
{{absData[currDate][periods[c-1]]['names'][i]}}</div> <div class="row needborder">{{periods[c-1]}}:
<div class="row"><img src="{{absData[currDate][periods[c-1]]['signature'][i]}}" alt=""> {{schedule[periods[c-1]]['teacher']}}: {{i}}</div>
{% if loop.index == loop.length %} <div class="row"><img src="{{submission[periods[c-1]][i]['signature']}}" alt="">
<br>備註: {{absData[currDate][periods[c-1]]['notes']}} {% if loop.index == loop.length-1 %}
<br>備註: {{submission[periods[c-1]]['notes']}}
{% endif %} {% endif %}
</div> </div>
{% endif %}
{% endfor %} {% endfor %}
{% else %} {% else %}
<div class="row needborder">{{periods[c-1]}}: {{absData[currDate][periods[c-1]]['teacher']}}: No <div class="row needborder">{{periods[c-1]}}:
{{schedule[periods[c-1]]['teacher']}}: {{i}}: No
Signature Signature
</div> </div>
<div class="row"></div> <div class="row"></div>
{% endif %} {% endif %}
{% else %} {% else %}
<div class="row needborder">{{periods[c-1]}}: {{absData[currDate][periods[c-1]]['name']}}: <div class="row needborder">{{periods[c-1]}}: {{schedule[periods[c-1]]['subject']}}:
{{absData[currDate][periods[c-1]]['teacher']}} {{schedule[periods[c-1]]['teacher']}}
</div> </div>
<div class="row"><img src="{{absData[currDate][periods[c-1]]['signature']}}" alt=""><br>備註: {% if periods[c-1] in submission %}
{{absData[currDate][periods[c-1]]['notes']}}</div> <div class="row"><img src="{{submission[periods[c-1]]['signature']}}" alt=""><br>備註:
{{submission[periods[c-1]]['notes']}}</div>
{% endif %}
{% endif %} {% endif %}
{% endif %} {% endif %}
</div> </div>
@ -235,7 +336,7 @@
<script> <script>
var periodData = {} var periodData = {}
{% for i in periods %} {% for i in periods %}
periodData['{{i}}'] = '{{ absData[currDate][i]['name'] }}' periodData['{{i}}'] = '{{ schedule[i]['subject'] }}'
{% endfor %} {% endfor %}
</script> </script>
<script src="/static/pagejs/homeroom.js"></script> <script src="/static/pagejs/homeroom.js"></script>

92
templates/list.html Normal file
View file

@ -0,0 +1,92 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Attendance 點名系統 2.0</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.1/dist/css/bootstrap.min.css" rel="stylesheet"
integrity="sha384-F3w7mX95PdgyTmZZMECAngseQB83DfGTowi0iMjiWaeVhAn4FJkqJByhZMI3AhiU" crossorigin="anonymous">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.4/css/all.min.css" integrity="sha512-1ycn6IcaQQ40/MKBW2W4Rhis/DbILU74C1vSrLJxCq57o941Ym01SwNsOMqvEBFlcgUa6xLiPY/NS5R+E6ztJQ==" crossorigin="anonymous" referrerpolicy="no-referrer" />
<link rel="stylesheet" href="/static/allpages.css">
<link rel="stylesheet" href="/static/homeroom.css">
<link rel="shortcut icon" type="image/x-icon" href="/static/favicon.ico" />
<!-- Global site tag (gtag.js) - Google Analytics -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-H6D61RSBHR"></script>
<script src="/static/gtag.js"></script>
</head>
<body>
<div class="showTime"><span id="showTime"></span></div>
{% include 'sidebar.html' %}
<div class="container">
<h1 class="margin-top">{{title}}</h1>
<h2 class="margin-top">{{currRoom[0]}}{{currRoom[1]}}
{% if mode == 'STUDABS' or mode == 'STUDDS' %}
{{num}}號 {{name}}
{% endif %}
</h2>
<div class="col">
<div class="row">
<div class="col col-1">Date 日期</div>
<div class="col col-1">Period 節次</div>
{% if not (mode == 'STUDABS' or mode == 'STUDDS') %}
<div class="col col-1">Number 座號</div>
<div class="col col-1">Name 姓名</div>
<div class="col col-1">English Name</div>
{% endif %}
{% if mode == 'ABS' or mode == 'STUDABS' %}
<div class="col col-1">Status 狀態</div>
<div class="col">Notes 備註</div>
{% elif mode == 'DS' or mode == 'STUDDS' %}
<div class="col">Description 敘述</div>
{% endif %}
</div>
{% for a in data %}
<div class="row">
<div class="col col-1">{{a[0]}}</div>
<div class="col col-1">{{a[1]}}</div>
{% if not (mode == 'STUDABS' or mode == 'STUDDS') %}
<div class="col col-1">{{a[2]}}</div>
<div class="col col-1">{{students[a[2]]['name']}}</div>
<div class="col col-1">{{students[a[2]]['ename']}}</div>
{% endif %}
{% if mode == 'ABS' or mode == 'STUDABS' %}
<div class="col col-1">
{% if a[3]== 'L' %}
<p class="highlightAbs n-3">𝜑</p>
{% elif a[3]== 'K' %}
<p class="highlightAbs n-2"></p>
{% elif a[3]== 'G' %}
<p class="highlightAbs n-2"></p>
{% elif a[3]== 'S' %}
<p class="highlightAbs n-2"></p>
{% elif a[3]== 'F' %}
<p class="highlightAbs n-2"></p>
{% elif a[3]== 'P' %}
<p class="highlightAbs n-2"></p>
{% elif a[3]== 'O' %}
<p class="highlightAbs n-2"></p>
{% else %}
<p class="highlightAbs n-2">{{a[3]}}</p>
{% endif %}</div>
<div class="col">{{a[4]}}</div>
{% elif mode == 'DS' or mode == 'STUDDS' %}
<div class="col">{{a[3]}}</div>
{% endif %}
</div>
{% endfor %}
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/signature_pad@2.3.2/dist/signature_pad.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"
integrity="sha384-ka7Sk0Gln4gmtz2MlQnikT1wXgYsOg+OMhuP+IlRH9sENBO0LRn5q+8nbTov4+1p"
crossorigin="anonymous"></script>
{% include 'footer.html' %}
<script src="/static/pagejs/homeroom.js"></script>
<script src="/static/time.js"></script>
</body>
</html>

View file

@ -5,7 +5,7 @@
<meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="robots" content="noindex" /> <meta name="robots" content="noindex" />
<title>Attendance 點名系統 (β)</title> <title>Attendance 點名系統 2.0</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.1/dist/css/bootstrap.min.css" rel="stylesheet" <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.1/dist/css/bootstrap.min.css" rel="stylesheet"
integrity="sha384-F3w7mX95PdgyTmZZMECAngseQB83DfGTowi0iMjiWaeVhAn4FJkqJByhZMI3AhiU" crossorigin="anonymous"> integrity="sha384-F3w7mX95PdgyTmZZMECAngseQB83DfGTowi0iMjiWaeVhAn4FJkqJByhZMI3AhiU" crossorigin="anonymous">
<link rel="stylesheet" href="/static/allpages.css"> <link rel="stylesheet" href="/static/allpages.css">
@ -19,54 +19,56 @@
<body> <body>
<div class="showTime"><span id="showTime"></span></div> <div class="showTime"><span id="showTime"></span></div>
<div class="container"> <div class="text-center container">
<h1 class="margin-top margin-bottom">Attendance 點名系統 (β) | Login 登入</h1> <div class="loginPanel">
<div class="row"> <h1 class="margin-top margin-bottom">
<div class="col"></div> <div class="img"><img src="/static/favicon.ico" alt="" style="height: 100%"> Attendance 點名系統 <span style="color: goldenrod;">2.0</span></div> </h1>
<div class="col-md-5"> <form action="/" id="loginForm" method="post">
<form action="/" id="loginForm" method="post"> <div>
<div class="input-group mb-3"> <input class="form-check-input" type="radio" name="user_type" id="user_type_teacher" value="teacher" checked>
<div class="input-group-prepend"> <label class="form-check-label" for="user_type_teacher" style="margin-right: 20px;">
<span class="input-group-text"><i class="fa fa-user"></i></span> Teacher 教師 / Admin 管理員
</div> </label>
<input type="text" class="form-control" name="username" id="username" placeholder="Username"> <input class="form-check-input" type="radio" name="user_type" id="user_type_student" value="student">
</div> <label class="form-check-label" for="user_type_student">
<div class="input-group mb-3"> Student 學生
<div class="input-group-prepend"> </label>
<span class="input-group-text"><i class="fa fa-lock"></i></span>
</div>
<input type="password" class="form-control" name="password" id="password"
placeholder="Password">
<div class="input-group-append">
<span class="input-group-text toggle-password" onclick="password_show_hide();">
<i class="fas fa-eye" id="show_eye"></i>
<i class="fas fa-eye-slash d-none" id="hide_eye"></i>
</span>
</div>
</div>
<button class="btn btn-primary btn-block g-recaptcha" onclick="loadingAnimation()">Login
登入</button>
</form>
<a href="/forgotPassword">Forgot Password 忘記密碼</a>
<div class="disclaimer" hidden="hidden">
This site is protected by reCAPTCHA and the Google
<a target="_blank" href="https://policies.google.com/privacy">Privacy Policy</a> and
<a target="_blank" href="https://policies.google.com/terms">Terms of Service</a> apply.
</div> </div>
{% with messages = get_flashed_messages() %} <br>
{% if messages %} <div class="input-group mb-3">
<div class="alert alert-danger margin-top" role="alert"> <div class="input-group-prepend">
{% for message in messages %} <span class="input-group-text"><i class="fa fa-user"></i></span>
{% autoescape false %}{{message}}{% endautoescape %} </div>
{% endfor %} <input type="text" class="form-control" name="username" id="username" placeholder="Username 帳號">
</div> </div>
{% endif %} <div class="input-group mb-3">
{% endwith %} <div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-lock"></i></span>
</div>
<input type="password" class="form-control" name="password" id="password"
placeholder="Password 密碼">
<div class="input-group-append">
<span class="input-group-text toggle-password" onclick="password_show_hide();">
<i class="fas fa-eye" id="show_eye"></i>
<i class="fas fa-eye-slash d-none" id="hide_eye"></i>
</span>
</div>
</div>
<button class="btn btn-primary btn-block" onclick="loadingAnimation()">Login 登入</button>
</form>
<a href="/forgotPassword">Forgot Password 忘記密碼</a>
{% with messages = get_flashed_messages() %}
{% if messages %}
<div class="alert alert-danger margin-top" role="alert">
{% for message in messages %}
{% autoescape false %}{{message}}{% endautoescape %}
{% endfor %}
</div> </div>
<div class="col"></div> {% endif %}
{% endwith %}
{% include 'footer.html' %}
</div> </div>
</div> </div>
{% include 'footer.html' %}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
<script src="/static/pagejs/login.js"></script> <script src="/static/pagejs/login.js"></script>
<script src="/static/time.js"></script> <script src="/static/time.js"></script>

View file

@ -5,11 +5,11 @@
<meta charset="utf-8"> <meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<title>Select Subuser 選擇子帳號</title> <title>Attendance 點名系統 2.0</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.1/dist/css/bootstrap.min.css" rel="stylesheet" <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.1/dist/css/bootstrap.min.css" rel="stylesheet"
integrity="sha384-F3w7mX95PdgyTmZZMECAngseQB83DfGTowi0iMjiWaeVhAn4FJkqJByhZMI3AhiU" crossorigin="anonymous"> integrity="sha384-F3w7mX95PdgyTmZZMECAngseQB83DfGTowi0iMjiWaeVhAn4FJkqJByhZMI3AhiU" crossorigin="anonymous">
<link rel="stylesheet" href="/static/allpages.css"> <link rel="stylesheet" href="/static/allpages.css">
<link rel="stylesheet" href="/static/homeroom.css"> <link rel="stylesheet" href="/static/login.css">
<link rel="shortcut icon" type="image/x-icon" href="/static/favicon.ico" /> <link rel="shortcut icon" type="image/x-icon" href="/static/favicon.ico" />
<!-- Global site tag (gtag.js) - Google Analytics --> <!-- Global site tag (gtag.js) - Google Analytics -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-H6D61RSBHR"></script> <script async src="https://www.googletagmanager.com/gtag/js?id=G-H6D61RSBHR"></script>
@ -18,31 +18,29 @@
<body> <body>
<div class="showTime"><span id="showTime"></span></div> <div class="showTime"><span id="showTime"></span></div>
<div class="container"> <div class="text-center container">
<h1 class="margin-top">Select Subuser 選擇子帳號</h1> <div class="loginPanel">
<h2 class="margin-top">{{name}}</h2> <h1 class="margin-top margin-bottom">
<a href="/logout"><button class="btn btn-primary logout margin-top">Logout 登出</button></a> <div class="img"><img src="/static/favicon.ico" alt="" style="height: 100%"> Select Subuser 選擇子帳號</div> </h1>
<form action="/select" id="subuser_form_sel" method="post"> <h2 class="margin-top">Hi, {{name}}</h2>
<select name="subuser_sel" id="subuser_sel" class="form-select logout" onchange="loadingAnimation();" <a href="/logout"><button class="btn btn-primary logout margin-top">Logout 登出</button></a>
required> <form action="/select" id="subuser_form_sel" method="post">
<option value="" selected>Please select</option> <select name="subuser_sel" id="subuser_sel" class="form-select logout" onchange="loadingAnimation();"
{% for key in data %} required>
{% if data[key]['type'] == 'homeroom' %} <option value="" selected>Please select</option>
<option value="homeroom^{{data[key]['homeroom']}}">🏠 {{data[key]['homeroom']}} {% for h in homeroom %}
</option> {% for c in homeroom[h] %}
{% else %} <option value="homeroom^{{h}}^{{c}}">🏠: {{h}}^{{c}}</option>
{% for i in data[key] %} {% endfor %}
{% if i == 'type' %} {% endfor %}
{% else %} {% if group %}
<option value="group^{{i}}^{{data[key][i]}}">🧑‍🏫 {{i}}: {{data[key][i]}}</option> <option value="group">💼: All Group Classes</option>
{% endif %} {% endif %}
{% endfor %} </select>
{% endif %} </form>
{% endfor %} {% include 'footer.html' %}
</select> </div>
</form>
</div> </div>
{% include 'footer.html' %}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
<script src="/static/pagejs/selSubUser.js"></script> <script src="/static/pagejs/selSubUser.js"></script>
<script src="/static/time.js"></script> <script src="/static/time.js"></script>

83
templates/sidebar.html Normal file
View file

@ -0,0 +1,83 @@
<link rel="stylesheet" href="/static/nav.css">
{% if session['subuser_type'] == 'homeroom' %}
<nav id="sidebar" class="active">
<h1><a href="/manage" class="logo"><img src="/static/favicon.ico" alt="" width="100%"></a></h1>
<ul class="list-unstyled components mb-5">
<li class="active">
<a href="/manage"><span class="fa fa-home"></span> Home 主頁</a>
</li>
<li>
<a href="/manage/abs"><span class="fa fa-user-times"></span> All Absent<br>所有缺勤</a>
</li>
<li>
<a href="/manage/ds"><span class="fa fa-skull-crossbones"></span> All DS<br>所有定心</a>
</li>
<li style="position:absolute; bottom: 2px; left:3px;">
<a href="/select"><span class="fa fa-people-arrows"></span> Switch User<br>切換帳號</a>
<a href="/logout"><span class="fa fa-sign-out-alt"></span> Logout<br>登出</a>
</li>
</ul>
</nav>
{% elif session['subuser_type'] == 'admin' %}
<nav id="sidebar" class="active" style="max-width: 150px;">
<h1><a href="/manage" class="logo"><img src="/static/favicon.ico" alt="" width="100%"></a></h1>
<ul class="list-unstyled components mb-5">
<li class="active">
<a href="/manage"><span class="fa fa-home"></span> Home 主頁</a>
<div class="col">
<div class="row">
<select name="grade" id="sel-grade" class="form-select" onchange="getHR()" required>
<option value="">選擇年級</option>
{% for grade in homerooms %}
<option value="{{grade}}">{{grade}}</option>
{% endfor %}
</select>
</div>
<div class="row">
<select name="room" id="sel-room" class="form-select" disabled required>
<option value="">請先選擇年級</option>
</select>
</div>
<div class="row">
<select name="date" id="date" class="form-select">
{% for date in dates %}
{% if date[0] == currDate %}
<option value="{{date[0]}}" selected="selected">{{date[0]}} ★</option>
{% else %}
<option value="{{date[0]}}">{{date[0]}}</option>
{% endif %}
{% endfor %}
</select>
</div>
<div class="row-1">
<button type="button" class="btn btn-primary" onclick="redirAdmin()">查詢</button>
</div>
</div>
</li>
<li>
<a href="/admin"><span class="fa fa-toolbox"></span> Admin<br>資料庫管理</a>
</li>
<li style="position:absolute; bottom: 2px; left:50px;">
<a href="/logout"><span class="fa fa-sign-out-alt"></span> Logout<br>登出</a>
</li>
</ul>
</nav>
{% elif session['user_type'] == 'student' %}
<nav id="sidebar" class="active">
<h1><a href="/student" class="logo"><img src="/static/favicon.ico" alt="" width="100%"></a></h1>
<ul class="list-unstyled components mb-5">
<li>
<a href="/student"><span class="fa fa-user-times"></span> All Absent<br>所有缺勤</a>
</li>
<li>
<a href="/student/ds"><span class="fa fa-skull-crossbones"></span> All DS<br>所有定心</a>
</li>
<li style="position:absolute; bottom: 2px; left:10px;">
<a href="/chgPassword"><span class="fa fa-user-cog"></span>Chg Pswd<br>更改帳密</a>
<a href="/logout"><span class="fa fa-sign-out-alt"></span> Logout<br>登出</a>
</li>
</ul>
</nav>
{% endif %}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
<script src="/static/nav.js"></script>

View file

@ -1,16 +0,0 @@
<video id="loading" oncontextmenu="return false;" width="100%" height="100%" loop autoplay muted style="display: none;">
<source src="/static/loading.webm" type="video/webm">
</video>
<footer>
<hr>
<p style="text-align: center;">&copy; 2021 Attendance (β) | Made by <a target="_blank"
href="https://github.com/aaronleetw">Aaron Lee 李翊愷</a> and Mr. Raymond Tsai 蔡瑋倫老師
<br>for <a target="_blank" href="https://www.fhjh.tp.edu.tw">Taipei Fuhsing Private School</a>
</p>
</footer>
<script>
function loadingAnimation() {
$('#loading').show();
}
</script>

View file

@ -4,11 +4,10 @@
<meta charset="utf-8"> <meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<title>Attendance 點名 - Upload</title> <title>Attendance 點名系統 2.0</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.1/dist/css/bootstrap.min.css" rel="stylesheet" <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.1/dist/css/bootstrap.min.css" rel="stylesheet"
integrity="sha384-F3w7mX95PdgyTmZZMECAngseQB83DfGTowi0iMjiWaeVhAn4FJkqJByhZMI3AhiU" crossorigin="anonymous"> integrity="sha384-F3w7mX95PdgyTmZZMECAngseQB83DfGTowi0iMjiWaeVhAn4FJkqJByhZMI3AhiU" crossorigin="anonymous">
<link rel="stylesheet" href="/static/allpages.css"> <link rel="stylesheet" href="/static/allpages.css">
<link rel="stylesheet" href="/static/login.css">
<link rel="shortcut icon" type="image/x-icon" href="/static/favicon.ico" /> <link rel="shortcut icon" type="image/x-icon" href="/static/favicon.ico" />
<!-- Global site tag (gtag.js) - Google Analytics --> <!-- Global site tag (gtag.js) - Google Analytics -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-H6D61RSBHR"></script> <script async src="https://www.googletagmanager.com/gtag/js?id=G-H6D61RSBHR"></script>

View file

@ -4,7 +4,7 @@
<meta charset="utf-8"> <meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<title>Attendance 點名系統 (β)</title> <title>Attendance 點名系統 2.0</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.1/dist/css/bootstrap.min.css" rel="stylesheet" <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.1/dist/css/bootstrap.min.css" rel="stylesheet"
integrity="sha384-F3w7mX95PdgyTmZZMECAngseQB83DfGTowi0iMjiWaeVhAn4FJkqJByhZMI3AhiU" crossorigin="anonymous"> integrity="sha384-F3w7mX95PdgyTmZZMECAngseQB83DfGTowi0iMjiWaeVhAn4FJkqJByhZMI3AhiU" crossorigin="anonymous">
<link rel="stylesheet" href="/static/allpages.css"> <link rel="stylesheet" href="/static/allpages.css">
@ -19,44 +19,41 @@
<body> <body>
<div class="showTime"><span id="showTime"></span></div> <div class="showTime"><span id="showTime"></span></div>
<div class="container"> <div class="text-center container">
<h1 class="margin-top margin-bottom">Attendance 點名系統 (β) | Reset Password 忘記密碼</h1> <div class="loginPanel">
<div class="row"> <h1 class="margin-top margin-bottom">
<div class="col"></div> <div class="img"><img src="/static/favicon.ico" alt="" style="height: 100%"> Reset Password 重置密碼</div> </h1>
<div class="col-md-5"> <form action="/resetPassword?resetCode={{resetCode}}" id="password_form" method="post">
<form action="/resetPassword?mode=resetPassword&oobCode={{oobCode}}" id="password_form" method="post"> <div class="form-group row" style="margin-bottom: 10px;">
<div class="form-group row" style="margin-bottom: 10px;"> <label for="password">New Password 新密碼:</label><br>
<label for="password">New Password 新密碼:</label><br> <div class="input-group mb-3 hasSmall">
<div class="input-group mb-3 hasSmall"> <input type="password" class="form-control" id="password" name="password"
<input type="password" class="form-control" id="password" name="password" aria-describedby="passwordHelp" required>
aria-describedby="passwordHelp" required> <div class="input-group-append">
<div class="input-group-append"> <span class="input-group-text toggle-password" onclick="password_show_hide();">
<span class="input-group-text toggle-password" onclick="password_show_hide();"> <i class="fas fa-eye" id="show_eye"></i>
<i class="fas fa-eye" id="show_eye"></i> <i class="fas fa-eye-slash d-none" id="hide_eye"></i>
<i class="fas fa-eye-slash d-none" id="hide_eye"></i> </span>
</span>
</div>
</div> </div>
<small id="passwordHelp" class="form-text text-muted">6 characters
minimum.</small>
</div> </div>
<button class="btn btn-warning btn-block g-recaptcha" onclick="loadingAnimation()">Confirm <small id="passwordHelp" class="form-text text-muted">6 characters
確認</button> minimum.</small>
</form>
{% with messages = get_flashed_messages() %}
{% if messages %}
<div class="alert alert-danger" role="alert">
{% for message in messages %}
{% autoescape false %}{{message}}{% endautoescape %}
{% endfor %}
</div> </div>
{% endif %} <button class="btn btn-warning btn-block g-recaptcha" onclick="loadingAnimation()">Confirm
{% endwith %} 確認</button>
</form>
{% with messages = get_flashed_messages() %}
{% if messages %}
<div class="alert alert-danger" role="alert">
{% for message in messages %}
{% autoescape false %}{{message}}{% endautoescape %}
{% endfor %}
</div> </div>
<div class="col"></div> {% endif %}
{% endwith %}
{% include 'footer.html' %}
</div> </div>
</div> </div>
{% include 'footer.html' %}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
<script src="/static/pagejs/verifiedChgPassword.js"></script> <script src="/static/pagejs/verifiedChgPassword.js"></script>
<script src="/static/time.js"></script> <script src="/static/time.js"></script>

0
tt.py Normal file
View file

221
upload.py
View file

@ -17,14 +17,22 @@ def upload_users():
with open(filepath) as file: with open(filepath) as file:
csv_dict = csv.DictReader(file) csv_dict = csv.DictReader(file)
for row in csv_dict: for row in csv_dict:
pwd = addZeroesUntil(row['password'], 6) pwd = row['password']
user = auth.create_user_with_email_and_password( db = refresh_db()
row['username'] + "@group-attendance.fhjh.tp.edu.tw", pwd) cursor = db.cursor(buffered=True)
db.child("Users").child(user['localId']).set({ cursor.execute("""
'permission': 'realPerson', INSERT IGNORE INTO users (email, name, oldUsername, role, password)
'name': row['name'], VALUES (%s, %s, %s, %s, %s)
'origUsername': row['username'], """, (row['username']+'@abs.fhjh.tp.edu.tw', row['name'], row['username'], 'R', genHash(pwd)))
}, session['token']) db.commit()
cursor.close()
# user = auth.create_user_with_email_and_password(
# row['username'] + "@abs.fhjh.tp.edu.tw", pwd)
# db.child("Users").child(user['localId']).set({
# 'permission': 'realPerson',
# 'name': row['name'],
# 'origUsername': row['username'],
# }, session['token'])
os.remove(filepath) os.remove(filepath)
except Exception as e: except Exception as e:
os.remove(filepath) os.remove(filepath)
@ -47,24 +55,37 @@ def upload_homeroom():
csv_file = request.files['csv'] csv_file = request.files['csv']
filepath = os.path.join('./temp', csv_file.filename) filepath = os.path.join('./temp', csv_file.filename)
csv_file.save(filepath) csv_file.save(filepath)
allUsers = db.child("Users").get(session['token']).val() # allUsers = db.child("Users").get(session['token']).val()
with open(filepath) as file: with open(filepath) as file:
csv_dict = csv.DictReader(file) csv_dict = csv.DictReader(file)
db = refresh_db()
cursor = db.cursor(buffered=True)
for row in csv_dict: for row in csv_dict:
if row['number'] == 'teacher' or row['number'] == 'Teacher': if row['number'] == 'teacher' or row['number'] == 'Teacher':
accs = row['name'].split(',') accs = json.dumps(row['name'].split(','))
for key in allUsers: cursor.execute("""
if accs == []: INSERT IGNORE INTO homerooms (grade, class_, accs)
break VALUES (%s, %s, %s)
if (allUsers[key]['origUsername'] in accs): """, (str(gradec), str(classc), accs))
db.child("Users").child(key).child("accounts").child("homeroom^"+gradec+classc+'^'+str(randint(10000, 99999))).update({ # for key in allUsers:
"homeroom": gradec + '^' + classc, # if accs == []:
"type": 'homeroom' # break
}, session['token']) # if (allUsers[key]['origUsername'] in accs):
accs.remove(allUsers[key]['origUsername']) # db.child("Users").child(key).child("accounts").child("homeroom^"+gradec+classc+'^'+str(randint(10000, 99999))).update({
# "homeroom": gradec + '^' + classc,
# "type": 'homeroom'
# }, session['token'])
# accs.remove(allUsers[key]['origUsername'])
else: else:
db.child("Homerooms").child(gradec).child( email = gradec + classc + row['number'] + '@st.fhjh.tp.edu.tw'
classc).child(row['number']).set(row, session['token']) cursor.execute("""
INSERT IGNORE INTO students (grade, class_, num, name, ename, email, password)
VALUES (%s, %s, %s, %s, %s, %s, %s)
""", (gradec, classc, row['number'], row['name'], row['eng_name'], email, genHash(row['eng_name'])))
# db.child("Homerooms").child(gradec).child(
# classc).child(row['number']).set(row, session['token'])
db.commit()
cursor.close()
# row['class'] row['number'] row['name'] row['eng_name'] # row['class'] row['number'] row['name'] row['eng_name']
os.remove(filepath) os.remove(filepath)
except Exception as e: except Exception as e:
@ -87,24 +108,32 @@ def upload_gp_classes():
csv_file.save(filepath) csv_file.save(filepath)
csv_dict = pd.read_csv(filepath) csv_dict = pd.read_csv(filepath)
category_cnt = csv_dict.shape[1] - 1 category_cnt = csv_dict.shape[1] - 1
allUsers = db.child("Users").get(session['token']).val() # allUsers = db.child("Users").get(session['token']).val()
for i in range(category_cnt): for i in range(category_cnt):
tmp_csv = csv_dict[csv_dict.columns[i+1]].tolist() tmp_csv = csv_dict[csv_dict.columns[i+1]].tolist()
for j in range(len(tmp_csv)): for j in range(len(tmp_csv)):
if type(tmp_csv[j]) == float: if type(tmp_csv[j]) == float:
break break
if j % 5 == 0: if j % 5 == 0:
db.child("Classes").child("GP_Class").child(csv_dict.columns[i+1]).child("Class").child( db = refresh_db()
tmp_csv[j]).child("name").set(tmp_csv[j+1] + " : " + tmp_csv[j+2] + " (" + tmp_csv[j+3] + ")", session['token']) cursor = db.cursor(buffered=True)
accs = tmp_csv[j+4].split(',') cursor.execute("""
for key in allUsers: INSERT IGNORE INTO gpclasses (category, subclass, about, accs)
if accs == []: VALUES (%s, %s, %s, %s)
break """, (csv_dict.columns[i+1], tmp_csv[j], tmp_csv[j+1] + " : " + tmp_csv[j+2] + " (" + tmp_csv[j+3] + ")", json.dumps(tmp_csv[j+4].split(','))))
if (allUsers[key]['origUsername'] in accs): db.commit()
db.child("Users").child(key).child("accounts").child("GP_Class^"+csv_dict.columns[i+1]+'^'+str(randint(10000, 99999))).update({ cursor.close()
csv_dict.columns[i+1]: tmp_csv[j], # db.child("Classes").child("GP_Class").child(csv_dict.columns[i+1]).child("Class").child(
"type": 'group' # tmp_csv[j]).child("name").set(tmp_csv[j+1] + " : " + tmp_csv[j+2] + " (" + tmp_csv[j+3] + ")", session['token'])
}, session['token']) # accs = tmp_csv[j+4].split(',')
# for key in allUsers:
# if accs == []:
# break
# if (allUsers[key]['origUsername'] in accs):
# db.child("Users").child(key).child("accounts").child("GP_Class^"+csv_dict.columns[i+1]+'^'+str(randint(10000, 99999))).update({
# csv_dict.columns[i+1]: tmp_csv[j],
# "type": 'group'
# }, session['token'])
os.remove(filepath) os.remove(filepath)
except Exception as e: except Exception as e:
os.remove(filepath) os.remove(filepath)
@ -130,13 +159,23 @@ def upload_stud_in_group():
csv_dict = csv.DictReader(file) csv_dict = csv.DictReader(file)
headers = csv_dict.fieldnames headers = csv_dict.fieldnames
headers = headers[1:] headers = headers[1:]
for h in headers: # for h in headers:
db.child("Classes").child("GP_Class").child( # db.child("Classes").child("GP_Class").child(
h).child("Homerooms").update({gradec+'^'+classc: 0}, session['token']) # h).child("Homerooms").update({gradec+'^'+classc: 0}, session['token'])
for row in csv_dict: for row in csv_dict:
for h in headers: num = row.pop(str(gradec+classc))
db.child("Homerooms").child(gradec).child(classc).child( db = refresh_db()
row[str(gradec+classc)]).child("GP_Class").update({h: row[h]}, session['token']) cursor = db.cursor(buffered=True)
cursor.execute("""
UPDATE students
SET classes = %s
WHERE grade = %s AND class_ = %s AND num = %s
""", (json.dumps(row), gradec, classc, num))
db.commit()
cursor.close()
# for h in headers:
# db.child("Homerooms").child(gradec).child(classc).child(
# row[str(gradec+classc)]).child("GP_Class").update({h: row[h]}, session['token'])
os.remove(filepath) os.remove(filepath)
except Exception as e: except Exception as e:
os.remove(filepath) os.remove(filepath)
@ -166,15 +205,52 @@ def upload_period_list():
for j in range(len(tmp_csv)): for j in range(len(tmp_csv)):
if not (periodCodes[j].endswith('-t')): if not (periodCodes[j].endswith('-t')):
if type(tmp_csv[j]) == float: if type(tmp_csv[j]) == float:
db.child("Classes").child("Homeroom").child(gradec).child(classc).child( db = refresh_db()
str(i+1)).child(periodCodes[j]).update({'name': '--'}, session['token']) cursor = db.cursor(buffered=True)
cursor.execute("""
INSERT IGNORE INTO schedule
(grade, class_, dow, period, subject, teacher)
VALUES (%s, %s, %s, %s, %s, %s)
""", (gradec, classc, str(i+1), periodCodes[j], '--', '--'))
db.commit()
cursor.close()
# db.child("Classes").child("Homeroom").child(gradec).child(classc).child(
# str(i+1)).child(periodCodes[j]).update({'name': '--'}, session['token'])
else: else:
db.child("Classes").child("Homeroom").child(gradec).child(classc).child( db = refresh_db()
str(i+1)).child(periodCodes[j]).update({'name': tmp_csv[j]}, session['token']) cursor = db.cursor(buffered=True)
cursor.execute("""
INSERT IGNORE INTO schedule
(grade, class_, dow, period, subject)
VALUES (%s, %s, %s, %s, %s)
""", (gradec, classc, str(i+1), periodCodes[j], tmp_csv[j]))
db.commit()
cursor.close()
# db.child("Classes").child("Homeroom").child(gradec).child(classc).child(
# str(i+1)).child(periodCodes[j]).update({'name': tmp_csv[j]}, session['token'])
if not(periodCodes[j] == 'm' or periodCodes[j] == 'n'): if not(periodCodes[j] == 'm' or periodCodes[j] == 'n'):
j += 1 j += 1
db.child("Classes").child("Homeroom").child(gradec).child(classc).child( db = refresh_db()
str(i+1)).child(periodCodes[j-1]).update({'teacher': tmp_csv[j]}, session['token']) cursor = db.cursor(buffered=True)
cursor.execute("""
UPDATE schedule
SET teacher = %s
WHERE grade = %s AND class_ = %s AND dow = %s AND period = %s
""", (tmp_csv[j], gradec, classc, str(i+1), periodCodes[j-1]))
db.commit()
cursor.close()
# db.child("Classes").child("Homeroom").child(gradec).child(classc).child(
# str(i+1)).child(periodCodes[j-1]).update({'teacher': tmp_csv[j]}, session['token'])
else:
db = refresh_db()
cursor = db.cursor(buffered=True)
cursor.execute("""
UPDATE schedule
SET teacher = %s
WHERE grade = %s AND class_ = %s AND dow = %s AND period = %s
""", ("--", gradec, classc, str(i+1), periodCodes[j]))
db.commit()
cursor.close()
os.remove(filepath) os.remove(filepath)
except Exception as e: except Exception as e:
os.remove(filepath) os.remove(filepath)
@ -197,17 +273,26 @@ def upload_dates():
with open(filepath) as file: with open(filepath) as file:
csv_dict = csv.DictReader(file) csv_dict = csv.DictReader(file)
headers = csv_dict.fieldnames headers = csv_dict.fieldnames
temp = db.child("Homerooms").get(session['token']).val() db = refresh_db()
cursor = db.cursor(buffered=True)
for row in csv_dict: for row in csv_dict:
for h in headers: for h in headers:
for t in temp: cursor.execute("""
for i in temp[t]: INSERT IGNORE INTO dates (date, dow) VALUES (%s, %s)
periodData = db.child("Classes").child( """, (h, row[h]))
"Homeroom").child(t).child(i).get(session['token']).val() db.commit()
db.child("Homerooms").child(t).child(i).child( cursor.close()
"Absent").child(h).update({"dow": row[h]}, session['token']) # temp = db.child("Homerooms").get(session['token']).val()
db.child("Homerooms").child(t).child(i).child( # for row in csv_dict:
"Absent").child(h).update(periodData[int(row[h])], session['token']) # for h in headers:
# for t in temp:
# for i in temp[t]:
# periodData = db.child("Classes").child(
# "Homeroom").child(t).child(i).get(session['token']).val()
# db.child("Homerooms").child(t).child(i).child(
# "Absent").child(h).update({"dow": row[h]}, session['token'])
# db.child("Homerooms").child(t).child(i).child(
# "Absent").child(h).update(periodData[int(row[h])], session['token'])
os.remove(filepath) os.remove(filepath)
except Exception as e: except Exception as e:
os.remove(filepath) os.remove(filepath)
@ -230,15 +315,25 @@ def upload_admin_acc():
with open(filepath) as file: with open(filepath) as file:
csv_dict = csv.DictReader(file) csv_dict = csv.DictReader(file)
for row in csv_dict: for row in csv_dict:
auth.create_user_with_email_and_password( pwd = row['password']
row['username'] + '@group-attendance.fhjh.tp.edu.tw', row['password']) role = 'S' if row['permission'] == '1' else 'A'
user = auth.sign_in_with_email_and_password( db = refresh_db()
row['username'] + '@group-attendance.fhjh.tp.edu.tw', row['password']) cursor = db.cursor(buffered=True)
db.child("Users").child(user['localId']).update({ cursor.execute("""
'permission': 'admin', INSERT IGNORE INTO users (email, name, oldUsername, role, password)
'username': row['username'], VALUES (%s, %s, %s, %s, %s)
'showUpload': row['permission'] """, (row['username']+'@abs.fhjh.tp.edu.tw', row['name'], row['username'], role, genHash(pwd)))
}, session['token']) db.commit()
cursor.close()
# auth.create_user_with_email_and_password(
# row['username'] + '@group-attendance.fhjh.tp.edu.tw', row['password'])
# user = auth.sign_in_with_email_and_password(
# row['username'] + '@group-attendance.fhjh.tp.edu.tw', row['password'])
# db.child("Users").child(user['localId']).update({
# 'permission': 'admin',
# 'username': row['username'],
# 'showUpload': row['permission']
# }, session['token'])
os.remove(filepath) os.remove(filepath)
except Exception as e: except Exception as e:
os.remove(filepath) os.remove(filepath)