1
0
Fork 0
mirror of https://codeberg.org/Mo8it/AdvLabDB.git synced 2024-09-17 18:31:15 +00:00

Move scripts

This commit is contained in:
Mo 2022-08-08 22:51:52 +02:00
parent d2cd1ae9a1
commit d362820c3c
15 changed files with 37 additions and 348 deletions

View file

@ -1,15 +1,18 @@
from os import environ
from flask import Flask
from flask_admin import Admin
from flask_security import Security, SQLAlchemyUserDatastore
from flask_security.models import fsqla_v2 as fsqla
from flask_sqlalchemy import SQLAlchemy
from flask_migrate import Migrate
from .config import set_config
app = Flask(__name__)
set_config(app)
# Config
settings = set_config(app)
# Setup Flask-SQLAlchemy
db = SQLAlchemy(app)
@ -41,10 +44,4 @@ from . import models
user_datastore = SQLAlchemyUserDatastore(db, models.User, models.Role)
Security(app, user_datastore)
try:
from . import routes, adminModelViews, assistantModelViews
except Exception as ex:
print(
"\nYou are probably initializing the database with a script. If not, then you have to worry about not being able to import in __init__.py!\n"
)
print(str(ex))
from . import routes, adminModelViews, assistantModelViews

View file

@ -3,7 +3,7 @@ from configparser import ConfigParser
from pathlib import Path
def load_config(*files):
def load_config(app, *files):
config = ConfigParser()
for file in files:
@ -19,7 +19,7 @@ def load_config(*files):
def set_config(app):
config = load_config("secrets.ini", "settings.ini")
config = load_config(app, "secrets.ini", "settings.ini")
secrets = config["Secrets"]
settings = config["Settings"]
@ -53,5 +53,7 @@ def set_config(app):
"check_deliverability": check_email_deliverability,
}
app.config["SECURITY_PASSWORD_SALT"] = secrets["SECURITY_PASSWORD_SALT"]
app.config["SECURITY_PASSWORD_LENGTH_MIN"] = settings.getint(SECURITY_PASSWORD_LENGTH_MIN, 15)
app.config["SECURITY_PASSWORD_LENGTH_MIN"] = settings.getint("SECURITY_PASSWORD_LENGTH_MIN", 15)
# TODO: app.config["SECURITY_LOGIN_USER_TEMPLATE"] =
return settings

View file

@ -1,12 +1,11 @@
from datetime import datetime
from os import environ
from pathlib import Path
from shutil import copy2
from flask import flash, has_request_context
from sqlalchemy import select
from . import db
from . import db, settings
from .exceptions import DataBaseImportException
from .model_independent_funs import get_first
from .models import (
@ -24,12 +23,6 @@ from .models import (
User,
)
relative_db_dir = Path(environ["RELATIVE_DB_DIR"])
relative_db_path = relative_db_dir / "advlab.db"
relative_db_bk_dir = relative_db_dir / "backups"
relative_db_bk_dir.mkdir(exist_ok=True)
def now():
return datetime.now().strftime("%d_%m_%Y_%H_%M_%S")
@ -54,16 +47,16 @@ def not_nullable(entry):
def importFromFile(filePath):
db_path = Path(settings["SQLITE_DB_PATH"])
db_bk_dir = db_path.parent / "backups"
db_bk_dir.mkdir(exist_ok=True)
if filePath[-4:] != ".txt":
raise DataBaseImportException(
"The import file has to be a text file with txt extension (.txt at the end of the filename)!"
)
if has_request_context():
show = flash
else:
show = print
semesters = {}
parts = {}
students = {}
@ -74,7 +67,7 @@ def importFromFile(filePath):
appointments = {}
with open(filePath) as f:
show("Reading file...")
flash("Reading file...")
expectingTable = True
readHeader = False
@ -139,7 +132,7 @@ def importFromFile(filePath):
try:
# Semester
show("Semester...")
flash("Semester...")
if len(semesters["label"]) != 1:
raise DataBaseImportException("Only one semester is allowed in an import file!")
@ -154,7 +147,7 @@ def importFromFile(filePath):
)
# Part
show("Part...")
flash("Part...")
dbParts = {}
for i, id in enumerate(parts["id"]):
@ -179,7 +172,7 @@ def importFromFile(filePath):
dbParts[id] = dbPart
# Student
show("Student...")
flash("Student...")
dbStudents = {}
for i, studentNumber in enumerate(students["student_number"]):
@ -207,7 +200,7 @@ def importFromFile(filePath):
dbStudents[studentNumber] = dbStudent
# Group
show("Group...")
flash("Group...")
dbGroups = {}
for i, id in enumerate(groups["id"]):
@ -222,7 +215,7 @@ def importFromFile(filePath):
dbGroups[id] = dbGroup
# PartStudent
show("PartStudent...")
flash("PartStudent...")
for i, studentNumber in enumerate(partStudents["student_number"]):
studentNumber = int(not_nullable(studentNumber))
@ -234,7 +227,7 @@ def importFromFile(filePath):
db.session.add(dbPartStudent)
# Experiment
show("Experiment...")
flash("Experiment...")
dbSemesterExperiments = {}
for i, id in enumerate(experiments["id"]):
@ -268,7 +261,7 @@ def importFromFile(filePath):
dbSemesterExperiments[id] = dbSemesterExperiment
# GroupExperiment
show("GroupExperiment...")
flash("GroupExperiment...")
dbGroupExperiments = {}
for i, id in enumerate(groupExperiments["id"]):
@ -281,7 +274,7 @@ def importFromFile(filePath):
dbGroupExperiments[id] = dbGroupExperiment
# Appointment
show("Appointment...")
flash("Appointment...")
for i, date in enumerate(appointments["date"]):
date = not_nullable(date)
@ -302,10 +295,10 @@ def importFromFile(filePath):
db.session.add(dbAppointment)
# Backup
dest = relative_db_bk_dir / f"before_{dbSemester}_import_{now()}.db"
copy2(relative_db_path, dest)
dest = db_bk_dir / f"before_{dbSemester}_import_{now()}.db"
copy2(db_path, dest)
show(f"Made a backup of the database before committing the import at {dest}")
flash(f"Made a backup of the database before committing the import at {dest}")
db.session.commit()
except Exception as ex:
@ -313,10 +306,10 @@ def importFromFile(filePath):
raise ex
dest = relative_db_bk_dir / f"after_{dbSemester}_import_{now()}.db"
copy2(relative_db_path, dest)
dest = db_bk_dir / f"after_{dbSemester}_import_{now()}.db"
copy2(db_path, dest)
show(f"Made a backup of the database after the import at {dest}")
flash(f"Made a backup of the database after the import at {dest}")
show("\nImport done!")
show("Please check that everything worked as expected. Restore the database backup otherwise!")
flash("\nImport done!")
flash("Please check that everything worked as expected. Restore the database backup otherwise!")

View file

@ -3,7 +3,6 @@ Functions dependent on advlabdb.models.
"""
from functools import cache
from os import environ
from flask import flash
from flask_admin.menu import MenuLink
@ -11,7 +10,7 @@ from flask_security import current_user
from wtforms.fields import BooleanField, IntegerField, SelectField, StringField
from wtforms.validators import DataRequired, NumberRange, Optional
from . import app
from . import app, settings
from .models import MAX_MARK, MIN_MARK, Semester
@ -24,7 +23,7 @@ def initActiveSemesterMenuLinks(space):
MenuLink(
name=str(semester),
# Using SERVER_NAME because of missing request context
url=environ["SERVER_NAME"] + "/set_semester" + "?semester_id=" + str(semester.id),
url=settings["SERVER_NAME"] + "/set_semester" + "?semester_id=" + str(semester.id),
category="Active semester",
)
)
@ -33,7 +32,7 @@ def initActiveSemesterMenuLinks(space):
"ERROR: The Semester table does not exist yet! Therefore, menu links could not be generated. You can ignore this error if you are just initializing the database."
)
else:
space.add_link(MenuLink(name="Logout", url=environ["SERVER_NAME"] + "/logout"))
space.add_link(MenuLink(name="Logout", url=settings["SERVER_NAME"] + "/logout"))
def active_semester_str():

View file

@ -1,55 +0,0 @@
from os.path import exists
from shutil import copytree, rmtree
from flask_admin import __file__ as flaskAdminPath
def copyAdminTemplates():
src = flaskAdminPath.removesuffix("__init__.py") + "templates/bootstrap4/admin"
if not exists(src):
print("Templates could not be found in", src)
print("You can also copy them manually.")
return False
dist = "advlabdb/templates/admin"
if exists(dist):
while True:
ans = input(
f"The directory {dist} already exists. Enter 'o' to override it and update the templates or enter 's' to stop the process: "
).lower()
if ans == "s":
print("Process stopped!")
return False
elif ans == "o":
break
rmtree(dist)
print("Old templates deleted!")
copytree(src, dist)
print("Copied", src, "->", dist)
print(
f"""
_________
| WARNING
| -------
|
| You might have to edit the file {dist}/base.html by adding nav in the following way:
| This line:\t<ul class="navbar-nav mr-auto">
| Becomes:\t<ul class="nav navbar-nav mr-auto">
|
| This will prevent the navigation bar from expanding such that some elements can not be seen.
| Refer to this pull request: https://github.com/flask-admin/flask-admin/pull/2233
|
| If the above pull request is merged and flask-admin is on a new release after the merge,
| then this step is not needed.
_________
"""
)
return True
if __name__ == "__main__":
if not copyAdminTemplates():
print("Did not copy!")

View file

@ -1,55 +0,0 @@
from flask_security import admin_change_password
from sqlalchemy import select
from ... import app, db
from ...model_independent_funs import randomPassword
from ...models import Admin, User
from ..terminal_utils import box, validating_input
def main():
print("This script will generate a new random password for a chosen admin.")
print()
with app.app_context():
with db.session.begin():
admins = db.session.execute(select(Admin).join(User).where(User.active == True)).scalars().all()
activate_user = False
if len(admins) == 0:
print("There is no admin with an active user. The user of the chosen admin will be activated")
admins = db.session.execute(select(Admin)).scalars().all()
activate_user = True
num_admins = len(admins)
prompt = "Admins:\n"
for ind, admin in enumerate(admins):
user = admin.user
prompt += f"[{ind}] {user.first_name} {user.last_name}: {user.email}\n"
prompt += "Enter number"
admin_index = validating_input(
prompt,
options=[str(ind) for ind in range(num_admins)],
format_function=lambda ans: int(ans),
)
chosen_admin_user = admins[admin_index].user
new_password = randomPassword()
admin_change_password(
chosen_admin_user, new_password, notify=False
) # Password is automatically hashed with this function
if activate_user:
chosen_admin_user.active = True
box(new_password, "New password")
print("Done!")
if __name__ == "__main__":
main()

View file

@ -1,4 +0,0 @@
import secrets
print(f"SECRET_KEY={secrets.SystemRandom().getrandbits(128)}")
print(f"SECURITY_PASSWORD_SALT={secrets.token_hex()}")

View file

@ -1,85 +0,0 @@
from email_validator import validate_email
from flask_security import hash_password
from ... import app, db, user_datastore
from ...model_independent_funs import randomPassword
from ...models import MAX_YEAR, MIN_YEAR, Admin, Semester
from ..terminal_utils import box, confirm, validating_input
def main():
print("\n")
print("This script should only be used to initialize the database after setting up a server")
print("The old database will be DELETED and a new database will be created.")
if not confirm("Are you sure that you want to continue?"):
print("Aborted!")
return
with app.app_context():
with db.session.begin():
# Delete old database
db.drop_all()
# Create new database
db.create_all()
semester_label = validating_input(
"Enter the label of the current semester",
options=("SS", "WS"),
format_function=lambda ans: ans.upper(),
)
semester_year = validating_input(
f"Enter the year of the current semester (between {MIN_YEAR} and {MAX_YEAR})",
format_function=lambda n_str: int(n_str),
check_constraints_function=lambda n: MIN_YEAR <= n <= MAX_YEAR,
)
semester = Semester(label=semester_label, year=semester_year)
db.session.add(semester)
adminRole = user_datastore.create_role(name="admin")
user_datastore.create_role(name="assistant")
box("The first admin account will now be created")
admin_email = validating_input(
"Enter the admin's email address",
format_function=lambda ans: validate_email(ans).email,
)
admin_first_name = input("Enter the admin's first name: ")
admin_last_name = input("Enter the admin's last name: ")
admin_phone_number = input("Enter the admin's phone number (optional): ")
admin_mobile_phone_number = input("Enter the admin's mobile phone number (optional): ")
admin_building = input("Enter the admin's building (optional): ")
admin_room = input("Enter the admin's room (optional): ")
admin_password = randomPassword()
admin_hashed_password = hash_password(admin_password)
admin_user = user_datastore.create_user(
email=admin_email.strip(),
password=admin_hashed_password,
roles=[adminRole],
first_name=admin_first_name.strip(),
last_name=admin_last_name.strip(),
phone_number=admin_phone_number.strip() or None,
mobile_phone_number=admin_mobile_phone_number.strip() or None,
building=admin_building.strip() or None,
room=admin_room.strip() or None,
active_semester=semester,
)
admin = Admin(user=admin_user)
db.session.add(admin)
box(admin_password, "Admin password")
print("Done database initialization!")
if __name__ == "__main__":
main()

View file

@ -1,103 +0,0 @@
"""
No relative imports allowed in this file to be able to run server_setup.py without packages.
"""
import subprocess # nosec 404
from getpass import getpass
def run(command, **kwargs):
return subprocess.run(command, shell=True, **kwargs) # nosec B602
def box(message, context=None):
text_line = "| "
if context is not None:
text_line += context + ": "
text_line += message + " |"
separator = "=" * len(text_line)
print()
print(separator)
print(text_line)
print(separator)
print()
def step(message):
continue_message = "-> Press ENTER to continue or Ctrl+C to interrupt the script <-"
upper_separator = "_" * len(continue_message)
print()
print(upper_separator)
box(message, "Next step")
print(continue_message)
getpass("")
print()
def spaced_hl():
print("\n\n" + "_" * 20 + "\n\n")
def validating_input(
prompt,
options=None,
format_function=lambda ans: ans,
check_constraints_function=lambda ans: True,
):
if options is not None:
prompt += " ["
for opt in options[:-1]:
prompt += opt + "/"
prompt += options[-1] + "]: "
lowered_options = [opt.lower() for opt in options]
def adj_check_constraints_function(ans):
return ans.lower() in lowered_options and check_constraints_function(ans)
else:
prompt += ": "
adj_check_constraints_function = check_constraints_function
ans = None
first_run = True
done_validation = False
while not done_validation or not adj_check_constraints_function(ans):
if not first_run:
done_validation = False
print("Invalid input!\n")
else:
first_run = False
ans = input(prompt)
try:
ans = format_function(ans)
except Exception: # nosec B112
continue
else:
done_validation = True
return ans
def confirm(prompt):
ans = validating_input(
prompt,
options=("y", "n"),
format_function=lambda ans: ans.lower(),
)
if ans == "y":
return True
else:
return False