mirror of
https://github.com/aaronleetw/Attendance.git
synced 2024-11-14 19:11:39 -08:00
V2 init
This commit is contained in:
parent
a355887755
commit
22ef472527
29 changed files with 2444 additions and 1051 deletions
345
app.py
345
app.py
|
@ -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':
|
|
||||||
email = request.form['username']
|
|
||||||
if check_login_status():
|
|
||||||
try:
|
|
||||||
if (verify_recaptcha("")):
|
|
||||||
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('/')
|
return redirect('/')
|
||||||
except Exception as e:
|
class MyAdminIndexView(AdminIndexView):
|
||||||
print("Error*Login:", email, str(e), flush=True)
|
def is_accessible(self):
|
||||||
flash(
|
return ((not check_login_status()) and is_admin())
|
||||||
'帳號或密碼錯誤,請重新輸入<br>Incorrect username or password')
|
def inaccessible_callback(self, name, **kwargs):
|
||||||
return redirect('/')
|
|
||||||
else:
|
|
||||||
return redirect('/select')
|
|
||||||
|
|
||||||
|
|
||||||
@app.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':
|
|
||||||
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('/')
|
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
152
db.sql
Normal 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)
|
||||||
|
);
|
105
functions.py
105
functions.py
|
@ -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
329
login.py
Normal 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('/')
|
748
manage.py
748
manage.py
|
@ -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]
|
||||||
|
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()
|
||||||
|
submission = {}
|
||||||
|
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]
|
||||||
|
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]] = 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':
|
elif pl == 'group':
|
||||||
cateData = db.child("Classes").child(
|
db = refresh_db()
|
||||||
"GP_Class").child(session['category']).get(session['token']).val()
|
cursor = db.cursor()
|
||||||
cclass = {
|
cursor.execute("SELECT category, subclass FROM gpclasses WHERE accs LIKE %s", ('%'+session['oldUsername']+'%',))
|
||||||
"name": cateData['Class'][session['class']]['name'],
|
gpclasses = cursor.fetchall()
|
||||||
"category": session['category'],
|
data = {}
|
||||||
"class_id": session['class']
|
|
||||||
}
|
|
||||||
homerooms = cateData['Homerooms']
|
|
||||||
currDate = ""
|
currDate = ""
|
||||||
confirmed = []
|
|
||||||
absData = {}
|
|
||||||
for h in homerooms:
|
|
||||||
h = h.split('^')
|
|
||||||
hrData = db.child("Homerooms").child(
|
|
||||||
h[0]).child(h[1]).get(session['token']).val()
|
|
||||||
tmpAbsData = hrData['Absent']
|
|
||||||
hrData.pop('Absent')
|
|
||||||
if 'placeholder' in hrData:
|
|
||||||
hrData.pop('placeholder')
|
|
||||||
periods = []
|
|
||||||
dow = ""
|
dow = ""
|
||||||
if currDate == "":
|
cursor = db.cursor()
|
||||||
if fCommand == 'date':
|
cursor.execute("SELECT date FROM dates ORDER BY date ASC")
|
||||||
|
dates = cursor.fetchall()
|
||||||
|
if fCommand != "":
|
||||||
currDate = fData
|
currDate = fData
|
||||||
for j in tmpAbsData[currDate]:
|
|
||||||
if j == "dow":
|
|
||||||
dow = tmpAbsData[currDate][j]
|
|
||||||
continue
|
|
||||||
elif j == "confirm":
|
|
||||||
confirmed.append([h[0], h[1]])
|
|
||||||
continue
|
|
||||||
elif j == "notes":
|
|
||||||
continue
|
|
||||||
if (tmpAbsData[currDate][j]['name'] == 'GP' and
|
|
||||||
tmpAbsData[currDate][j]['teacher'] == cclass['category']):
|
|
||||||
periods.append(j)
|
|
||||||
|
|
||||||
else:
|
else:
|
||||||
for i in tmpAbsData:
|
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"):
|
||||||
tmp = False
|
|
||||||
for j in tmpAbsData[i]:
|
|
||||||
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
|
break
|
||||||
else:
|
cursor = db.cursor()
|
||||||
for j in tmpAbsData[currDate]:
|
cursor.execute("SELECT dow FROM dates WHERE date=%s", (currDate, ))
|
||||||
if j == "dow":
|
dow = cursor.fetchone()[0]
|
||||||
dow = tmpAbsData[currDate][j]
|
|
||||||
continue
|
for c in gpclasses:
|
||||||
elif j == "confirm":
|
cursor.execute("SELECT about FROM gpclasses WHERE subclass=%s AND category=%s",
|
||||||
confirmed.append([h[0], h[1]])
|
(c[1], c[0]))
|
||||||
continue
|
cclass = {
|
||||||
elif j == "notes":
|
"name": cursor.fetchone()[0],
|
||||||
continue
|
"category": c[0],
|
||||||
if (tmpAbsData[currDate][j]['name'] == 'GP' and
|
"class_id": c[1]
|
||||||
tmpAbsData[currDate][j]['teacher'] == cclass['category']):
|
|
||||||
periods.append(j)
|
|
||||||
for p in periods:
|
|
||||||
if not p in absData:
|
|
||||||
absData[p] = {}
|
|
||||||
for p in periods:
|
|
||||||
if not h[0] in absData[p]:
|
|
||||||
absData[p][h[0]] = {}
|
|
||||||
absData[p][h[0]][h[1]] = {}
|
|
||||||
if 'notes' in tmpAbsData[currDate][p]:
|
|
||||||
absData[p][h[0]][h[1]
|
|
||||||
]['notes'] = tmpAbsData[currDate][p]['notes']
|
|
||||||
for num in hrData:
|
|
||||||
if (cclass['category'] in hrData[num]['GP_Class'] and
|
|
||||||
hrData[num]['GP_Class'][cclass['category']] == cclass['class_id']):
|
|
||||||
for p in periods:
|
|
||||||
absData[p][h[0]][h[1]][num] = {
|
|
||||||
"name": hrData[num]['name'],
|
|
||||||
"eng_name": hrData[num]['eng_name'],
|
|
||||||
"alr_fill": (('signature' in tmpAbsData[currDate][p]) and
|
|
||||||
(cclass['class_id'] in tmpAbsData[currDate][p]['signature'] or 'STUD_AFFAIR_OFFICE' in tmpAbsData[currDate][p]['signature'])),
|
|
||||||
"absent": False if not num in tmpAbsData[currDate][p] else tmpAbsData[currDate][p][num]
|
|
||||||
}
|
}
|
||||||
return render_template('group_teach.html', dateKeys=sorted(tmpAbsData.keys()), cclass=cclass, absData=absData, dow=dow, currDate=currDate, tmpAbsData=tmpAbsData, confirmed=confirmed)
|
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
|
||||||
|
if p[0] not in data[cclass['category'] + ' ' + cclass['class_id']]:
|
||||||
|
data[cclass['category'] + ' ' + cclass['class_id']][p[0]] = {}
|
||||||
|
if (h not in data[cclass['category'] + ' ' + cclass['class_id']][p[0]]):
|
||||||
|
data[cclass['category'] + ' ' + cclass['class_id']][p[0]][h] = {}
|
||||||
|
cursor = db.cursor()
|
||||||
|
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]))
|
||||||
|
submissionSQL = cursor.fetchone()
|
||||||
|
submitted = False
|
||||||
|
try:
|
||||||
|
if submissionSQL[0] == 'STUD_AFFAIR_OFFICE':
|
||||||
|
submitted = True
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
try:
|
||||||
|
signatures = json.loads(submissionSQL[0])
|
||||||
|
if cclass['class_id'] in signatures:
|
||||||
|
submitted = True
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
hrCfrm = False
|
||||||
|
if not submitted:
|
||||||
|
cursor = db.cursor()
|
||||||
|
cursor.execute("SELECT signature FROM submission WHERE grade=%s AND class_=%s AND date=%s AND period='c'", (hs[0], hs[1], currDate))
|
||||||
|
hrCfrm = True if cursor.fetchone() != None else submitted
|
||||||
|
cursor = db.cursor()
|
||||||
|
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]))
|
||||||
|
absentDataSQL = cursor.fetchall()
|
||||||
|
for x in students:
|
||||||
|
if (str(x[0])==hs[0] and str(x[1])==hs[1]):
|
||||||
|
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')
|
||||||
|
|
|
@ -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
|
|
@ -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;
|
||||||
|
@ -180,3 +203,22 @@ label {
|
||||||
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;
|
||||||
|
}
|
|
@ -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
66
static/nav.css
Normal 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
10
static/nav.js
Normal 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)';
|
||||||
|
})
|
|
@ -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,13 +58,37 @@ 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();
|
||||||
$('#loading').show();
|
$('#loading').show();
|
||||||
|
@ -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("");
|
||||||
|
}
|
|
@ -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("");
|
||||||
}
|
}
|
|
@ -18,3 +18,5 @@
|
||||||
}
|
}
|
||||||
startTime();
|
startTime();
|
||||||
})();
|
})();
|
||||||
|
$(window).resize();
|
||||||
|
$(window).scroll();
|
|
@ -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>
|
|
||||||
{% 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 %}
|
{% 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">
|
||||||
|
|
82
templates/admin/index.html
Normal file
82
templates/admin/index.html
Normal 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 %}
|
|
@ -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,11 +24,10 @@
|
||||||
|
|
||||||
<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>
|
||||||
|
@ -43,10 +42,12 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group row">
|
<div class="form-group row">
|
||||||
<label for="password">New Username 新帳號:</label><br>
|
<label for="new_username">New Username 新帳號:</label><br>
|
||||||
<div class="input-group mb-3">
|
<div class="input-group mb-3" style="margin-bottom: 5px !important;">
|
||||||
<input type="email" class="form-control" id="new_username" name="new_username" required>
|
<input type="email" class="form-control" id="new_username" name="new_username">
|
||||||
</div>
|
</div>
|
||||||
|
<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>
|
||||||
<div class="form-group row">
|
<div class="form-group row">
|
||||||
<label for="new_password">New Password 新密碼:</label><br>
|
<label for="new_password">New Password 新密碼:</label><br>
|
||||||
|
@ -60,19 +61,13 @@
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<small id="passwordHelp" class="form-text text-muted">6 characters
|
<small id="passwordHelp" class="form-text text-muted">6 characters minimum. 最少 6 字元。</small>
|
||||||
minimum.</small>
|
|
||||||
</div>
|
</div>
|
||||||
<button class="btn btn-danger btn-block g-recaptcha" onclick="loadingAnimation()">Change Password
|
<button class="btn btn-danger btn-block g-recaptcha" onclick="loadingAnimation()">Change Password
|
||||||
更改密碼</button>
|
更改密碼</button>
|
||||||
<a href="/"><button type="button" class="btn btn-primary btn-block g-recaptcha"
|
<a href="/"><button type="button" class="btn btn-primary btn-block g-recaptcha"
|
||||||
onclick="loadingAnimation()">Go back 回上一頁</button></a>
|
onclick="loadingAnimation()">Go back 回上一頁</button></a>
|
||||||
</form>
|
</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>
|
|
||||||
{% with messages = get_flashed_messages() %}
|
{% with messages = get_flashed_messages() %}
|
||||||
{% if messages %}
|
{% if messages %}
|
||||||
<div class="alert alert-danger" role="alert">
|
<div class="alert alert-danger" role="alert">
|
||||||
|
@ -82,11 +77,9 @@
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
</div>
|
|
||||||
<div class="col"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% include 'footer.html' %}
|
{% include 'footer.html' %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<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>
|
||||||
|
|
|
@ -5,8 +5,8 @@
|
||||||
</div>
|
</div>
|
||||||
<footer>
|
<footer>
|
||||||
<hr>
|
<hr>
|
||||||
<p style="text-align: center;">© 2021 Attendance (β) | Made by <a target="_blank"
|
<p style="text-align: center;">© 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>
|
|
@ -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,13 +20,23 @@
|
||||||
|
|
||||||
<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 class="input-group mb-3">
|
<div>
|
||||||
|
<input class="form-check-input" type="radio" name="user_type" id="user_type_teacher" value="teacher" checked>
|
||||||
|
<label class="form-check-label" for="user_type_teacher" style="margin-right: 20px;">
|
||||||
|
Teacher 教師 / Admin 管理員
|
||||||
|
</label>
|
||||||
|
<input class="form-check-input" type="radio" name="user_type" id="user_type_student" value="student">
|
||||||
|
<label class="form-check-label" for="user_type_student">
|
||||||
|
Student 學生
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<br>
|
||||||
|
<div class="input-group">
|
||||||
<div class="input-group-prepend">
|
<div class="input-group-prepend">
|
||||||
<span class="input-group-text"><i class="fa fa-user"></i></span>
|
<span class="input-group-text"><i class="fa fa-user"></i></span>
|
||||||
</div>
|
</div>
|
||||||
|
@ -34,6 +44,8 @@
|
||||||
</div>
|
</div>
|
||||||
<button class="btn btn-warning btn-block g-recaptcha" onclick="loadingAnimation()">Confirm
|
<button class="btn btn-warning btn-block g-recaptcha" onclick="loadingAnimation()">Confirm
|
||||||
確認</button>
|
確認</button>
|
||||||
|
<a href="/"><button type="button" class="btn btn-primary btn-block"
|
||||||
|
onclick="loadingAnimation()">Go back 回上一頁</button></a>
|
||||||
</form>
|
</form>
|
||||||
<p>This will send an email to the email address to verify your identity.<br>
|
<p>This will send an email to the email address to verify your identity.<br>
|
||||||
這會傳送一個郵件到指定的信箱,以驗證您的身份
|
這會傳送一個郵件到指定的信箱,以驗證您的身份
|
||||||
|
@ -47,11 +59,9 @@
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
</div>
|
|
||||||
<div class="col"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% include 'footer.html' %}
|
{% include 'footer.html' %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
@ -60,7 +60,7 @@
|
||||||
<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
92
templates/list.html
Normal 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>
|
|
@ -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,24 +19,34 @@
|
||||||
|
|
||||||
<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>
|
||||||
|
<input class="form-check-input" type="radio" name="user_type" id="user_type_teacher" value="teacher" checked>
|
||||||
|
<label class="form-check-label" for="user_type_teacher" style="margin-right: 20px;">
|
||||||
|
Teacher 教師 / Admin 管理員
|
||||||
|
</label>
|
||||||
|
<input class="form-check-input" type="radio" name="user_type" id="user_type_student" value="student">
|
||||||
|
<label class="form-check-label" for="user_type_student">
|
||||||
|
Student 學生
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<br>
|
||||||
<div class="input-group mb-3">
|
<div class="input-group mb-3">
|
||||||
<div class="input-group-prepend">
|
<div class="input-group-prepend">
|
||||||
<span class="input-group-text"><i class="fa fa-user"></i></span>
|
<span class="input-group-text"><i class="fa fa-user"></i></span>
|
||||||
</div>
|
</div>
|
||||||
<input type="text" class="form-control" name="username" id="username" placeholder="Username">
|
<input type="text" class="form-control" name="username" id="username" placeholder="Username 帳號">
|
||||||
</div>
|
</div>
|
||||||
<div class="input-group mb-3">
|
<div class="input-group mb-3">
|
||||||
<div class="input-group-prepend">
|
<div class="input-group-prepend">
|
||||||
<span class="input-group-text"><i class="fa fa-lock"></i></span>
|
<span class="input-group-text"><i class="fa fa-lock"></i></span>
|
||||||
</div>
|
</div>
|
||||||
<input type="password" class="form-control" name="password" id="password"
|
<input type="password" class="form-control" name="password" id="password"
|
||||||
placeholder="Password">
|
placeholder="Password 密碼">
|
||||||
<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>
|
||||||
|
@ -44,15 +54,9 @@
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<button class="btn btn-primary btn-block g-recaptcha" onclick="loadingAnimation()">Login
|
<button class="btn btn-primary btn-block" onclick="loadingAnimation()">Login 登入</button>
|
||||||
登入</button>
|
|
||||||
</form>
|
</form>
|
||||||
<a href="/forgotPassword">Forgot Password 忘記密碼</a>
|
<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>
|
|
||||||
{% with messages = get_flashed_messages() %}
|
{% with messages = get_flashed_messages() %}
|
||||||
{% if messages %}
|
{% if messages %}
|
||||||
<div class="alert alert-danger margin-top" role="alert">
|
<div class="alert alert-danger margin-top" role="alert">
|
||||||
|
@ -62,11 +66,9 @@
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
</div>
|
|
||||||
<div class="col"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% include 'footer.html' %}
|
{% include 'footer.html' %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<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>
|
||||||
|
|
|
@ -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">
|
||||||
|
<div class="img"><img src="/static/favicon.ico" alt="" style="height: 100%"> Select Subuser 選擇子帳號</div> </h1>
|
||||||
|
<h2 class="margin-top">Hi, {{name}}</h2>
|
||||||
<a href="/logout"><button class="btn btn-primary logout margin-top">Logout 登出</button></a>
|
<a href="/logout"><button class="btn btn-primary logout margin-top">Logout 登出</button></a>
|
||||||
<form action="/select" id="subuser_form_sel" method="post">
|
<form action="/select" id="subuser_form_sel" method="post">
|
||||||
<select name="subuser_sel" id="subuser_sel" class="form-select logout" onchange="loadingAnimation();"
|
<select name="subuser_sel" id="subuser_sel" class="form-select logout" onchange="loadingAnimation();"
|
||||||
required>
|
required>
|
||||||
<option value="" selected>Please select</option>
|
<option value="" selected>Please select</option>
|
||||||
{% for key in data %}
|
{% for h in homeroom %}
|
||||||
{% if data[key]['type'] == 'homeroom' %}
|
{% for c in homeroom[h] %}
|
||||||
<option value="homeroom^{{data[key]['homeroom']}}">🏠 {{data[key]['homeroom']}}
|
<option value="homeroom^{{h}}^{{c}}">🏠: {{h}}^{{c}}</option>
|
||||||
</option>
|
|
||||||
{% else %}
|
|
||||||
{% for i in data[key] %}
|
|
||||||
{% if i == 'type' %}
|
|
||||||
{% else %}
|
|
||||||
<option value="group^{{i}}^{{data[key][i]}}">🧑🏫 {{i}}: {{data[key][i]}}</option>
|
|
||||||
{% endif %}
|
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% endif %}
|
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
{% if group %}
|
||||||
|
<option value="group">💼: All Group Classes</option>
|
||||||
|
{% endif %}
|
||||||
</select>
|
</select>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
|
||||||
{% include 'footer.html' %}
|
{% include 'footer.html' %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<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
83
templates/sidebar.html
Normal 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>
|
|
@ -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;">© 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>
|
|
|
@ -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>
|
||||||
|
|
|
@ -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,12 +19,11 @@
|
||||||
|
|
||||||
<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">
|
||||||
|
@ -52,11 +51,9 @@
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
</div>
|
|
||||||
<div class="col"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% include 'footer.html' %}
|
{% include 'footer.html' %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<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
0
tt.py
Normal file
221
upload.py
221
upload.py
|
@ -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)
|
||||||
|
|
Loading…
Reference in a new issue