mirror of
https://codeberg.org/Mo8it/AdvLabDB.git
synced 2024-12-26 23:51:03 +00:00
Use app factory pattern
This commit is contained in:
parent
e1e9c0e42e
commit
bdabc9f32a
15 changed files with 179 additions and 132 deletions
|
@ -1,47 +1,51 @@
|
||||||
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 flask_migrate import Migrate
|
||||||
|
from flask_security import SQLAlchemyUserDatastore
|
||||||
|
|
||||||
from .config import set_config
|
from .config import get_settings
|
||||||
|
from .models import db, User, Role
|
||||||
|
|
||||||
app = Flask(__name__)
|
migrate = Migrate()
|
||||||
|
|
||||||
# Config
|
settings = get_settings()
|
||||||
settings = set_config(app)
|
|
||||||
|
|
||||||
# Setup Flask-SQLAlchemy
|
user_datastore = SQLAlchemyUserDatastore(db, User, Role)
|
||||||
db = SQLAlchemy(app)
|
|
||||||
fsqla.FsModels.set_db_info(db)
|
|
||||||
|
|
||||||
# Setup Flask-Migrate
|
|
||||||
migrate = Migrate(app, db)
|
|
||||||
|
|
||||||
# Setup Flask-Admin
|
def create_app(create_for_server=True):
|
||||||
from .custom_classes import SecureAdminIndexView, SecureAssistantIndexView
|
from flask import Flask
|
||||||
|
|
||||||
adminSpace = Admin(
|
app = Flask(__name__)
|
||||||
app,
|
|
||||||
name="Admin@AdvLabDB",
|
|
||||||
url="/admin",
|
|
||||||
template_mode="bootstrap3",
|
|
||||||
index_view=SecureAdminIndexView(name="Home", url="/admin", endpoint="admin"),
|
|
||||||
)
|
|
||||||
assistantSpace = Admin(
|
|
||||||
app,
|
|
||||||
name="Assistant@AdvLabDB",
|
|
||||||
url="/assistant",
|
|
||||||
template_mode="bootstrap3",
|
|
||||||
index_view=SecureAssistantIndexView(name="Home", url="/assistant", endpoint="assistant"),
|
|
||||||
)
|
|
||||||
|
|
||||||
from . import models
|
# Config
|
||||||
|
from .config import set_config
|
||||||
|
|
||||||
user_datastore = SQLAlchemyUserDatastore(db, models.User, models.Role)
|
set_config(app)
|
||||||
Security(app, user_datastore)
|
|
||||||
|
|
||||||
from . import routes, adminModelViews, assistantModelViews
|
# Setup Flask-SQLAlchemy
|
||||||
|
|
||||||
|
db.init_app(app)
|
||||||
|
|
||||||
|
# Setup Flask-Migrate
|
||||||
|
migrate.init_app(app, db)
|
||||||
|
|
||||||
|
# Setup Flask-Security-Too
|
||||||
|
from flask_security import Security
|
||||||
|
|
||||||
|
Security(app, user_datastore)
|
||||||
|
|
||||||
|
if create_for_server:
|
||||||
|
# Setup views
|
||||||
|
from .adminModelViews import init_admin_model_views
|
||||||
|
|
||||||
|
init_admin_model_views(app)
|
||||||
|
|
||||||
|
from .assistantModelViews import init_assistant_model_views
|
||||||
|
|
||||||
|
init_assistant_model_views(app)
|
||||||
|
|
||||||
|
# Register blueprints
|
||||||
|
from .routes import bp as routes_bp
|
||||||
|
|
||||||
|
app.register_blueprint(routes_bp)
|
||||||
|
|
||||||
|
return app
|
||||||
|
|
|
@ -4,6 +4,7 @@ from pathlib import Path
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
from flask import flash, has_request_context, redirect, url_for
|
from flask import flash, has_request_context, redirect, url_for
|
||||||
|
from flask_admin import Admin as FlaskAdmin
|
||||||
from flask_admin import expose
|
from flask_admin import expose
|
||||||
from flask_admin.contrib.sqla.fields import QuerySelectField, QuerySelectMultipleField
|
from flask_admin.contrib.sqla.fields import QuerySelectField, QuerySelectMultipleField
|
||||||
from flask_admin.contrib.sqla.filters import BooleanEqualFilter, FilterEqual
|
from flask_admin.contrib.sqla.filters import BooleanEqualFilter, FilterEqual
|
||||||
|
@ -28,7 +29,7 @@ from wtforms.fields import (
|
||||||
from wtforms.validators import URL, DataRequired, Email, NumberRange, Optional
|
from wtforms.validators import URL, DataRequired, Email, NumberRange, Optional
|
||||||
from wtforms.widgets import NumberInput
|
from wtforms.widgets import NumberInput
|
||||||
|
|
||||||
from . import adminSpace, assistantSpace, db, user_datastore
|
from . import user_datastore
|
||||||
from .admin_link_formatters import (
|
from .admin_link_formatters import (
|
||||||
admin_formatter,
|
admin_formatter,
|
||||||
appointment_date_formatter,
|
appointment_date_formatter,
|
||||||
|
@ -56,7 +57,12 @@ from .advlabdb_independent_funs import (
|
||||||
flashRandomPassword,
|
flashRandomPassword,
|
||||||
str_without_semester_formatter,
|
str_without_semester_formatter,
|
||||||
)
|
)
|
||||||
from .custom_classes import SecureAdminBaseView, SecureAdminModelView
|
from .assistantModelViews import assistantSpace
|
||||||
|
from .custom_classes import (
|
||||||
|
SecureAdminBaseView,
|
||||||
|
SecureAdminIndexView,
|
||||||
|
SecureAdminModelView,
|
||||||
|
)
|
||||||
from .database_import import importFromFile
|
from .database_import import importFromFile
|
||||||
from .exceptions import ModelViewException
|
from .exceptions import ModelViewException
|
||||||
from .model_dependent_funs import (
|
from .model_dependent_funs import (
|
||||||
|
@ -86,6 +92,15 @@ from .models import (
|
||||||
SemesterExperiment,
|
SemesterExperiment,
|
||||||
Student,
|
Student,
|
||||||
User,
|
User,
|
||||||
|
db,
|
||||||
|
)
|
||||||
|
|
||||||
|
adminSpace = FlaskAdmin(
|
||||||
|
name="Admin@AdvLabDB",
|
||||||
|
url="/admin",
|
||||||
|
template_mode="bootstrap4",
|
||||||
|
static_url_path="/static/a",
|
||||||
|
index_view=SecureAdminIndexView(name="Home", url="/admin", endpoint="admin"),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -356,7 +371,7 @@ class SemesterView(SecureAdminModelView):
|
||||||
categoryText = "Active semester"
|
categoryText = "Active semester"
|
||||||
link = MenuLink(
|
link = MenuLink(
|
||||||
name=str(newSemester),
|
name=str(newSemester),
|
||||||
url=url_for("set_semester") + "?semester_id=" + str(newSemester.id),
|
url=url_for("main.set_semester") + "?semester_id=" + str(newSemester.id),
|
||||||
category=categoryText,
|
category=categoryText,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -1282,7 +1297,7 @@ class ImportView(SecureAdminBaseView):
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
flash(str(ex), "error")
|
flash(str(ex), "error")
|
||||||
|
|
||||||
return redirect(url_for("index"))
|
return redirect(url_for("main.index"))
|
||||||
|
|
||||||
return self.render("import.html", form=form)
|
return self.render("import.html", form=form)
|
||||||
|
|
||||||
|
@ -1304,7 +1319,7 @@ class ActionsView(SecureAdminBaseView):
|
||||||
|
|
||||||
flash("Manually updated all final experiment and part marks", "success")
|
flash("Manually updated all final experiment and part marks", "success")
|
||||||
|
|
||||||
return redirect(url_for("index"))
|
return redirect(url_for("main.index"))
|
||||||
|
|
||||||
return self.render("actions.html", form=form)
|
return self.render("actions.html", form=form)
|
||||||
|
|
||||||
|
@ -1469,23 +1484,26 @@ class DocsView(SecureAdminBaseView):
|
||||||
return self.render("docs/docs.html", role="admin")
|
return self.render("docs/docs.html", role="admin")
|
||||||
|
|
||||||
|
|
||||||
adminSpace.add_view(StudentView(Student, url="student"))
|
def init_admin_model_views(app):
|
||||||
adminSpace.add_view(PartStudentView(PartStudent, url="part_student"))
|
adminSpace.init_app(app)
|
||||||
adminSpace.add_view(GroupView(Group, url="group"))
|
|
||||||
adminSpace.add_view(GroupExperimentView(GroupExperiment, url="group_experiment"))
|
|
||||||
adminSpace.add_view(AppointmentView(Appointment, url="appointment"))
|
|
||||||
adminSpace.add_view(ExperimentMarkView(ExperimentMark, url="experiment_mark"))
|
|
||||||
adminSpace.add_view(ExperimentView(Experiment, url="experiment"))
|
|
||||||
adminSpace.add_view(SemesterExperimentView(SemesterExperiment, url="semester_experiment"))
|
|
||||||
adminSpace.add_view(SemesterView(Semester, url="semester"))
|
|
||||||
adminSpace.add_view(PartView(Part, url="part"))
|
|
||||||
adminSpace.add_view(AssistantView(Assistant, url="assistant"))
|
|
||||||
adminSpace.add_view(AdminView(Admin, url="admin"))
|
|
||||||
adminSpace.add_view(UserView(User, url="user"))
|
|
||||||
adminSpace.add_view(ProgramView(Program, url="program"))
|
|
||||||
adminSpace.add_view(ImportView(name="Import", url="import"))
|
|
||||||
adminSpace.add_view(ActionsView(name="Actions", url="actions"))
|
|
||||||
adminSpace.add_view(AnalysisView(name="Analysis", url="analysis"))
|
|
||||||
adminSpace.add_view(DocsView(name="Docs", url="docs"))
|
|
||||||
|
|
||||||
initActiveSemesterMenuLinks(adminSpace)
|
adminSpace.add_view(StudentView(Student, url="student"))
|
||||||
|
adminSpace.add_view(PartStudentView(PartStudent, url="part_student"))
|
||||||
|
adminSpace.add_view(GroupView(Group, url="group"))
|
||||||
|
adminSpace.add_view(GroupExperimentView(GroupExperiment, url="group_experiment"))
|
||||||
|
adminSpace.add_view(AppointmentView(Appointment, url="appointment"))
|
||||||
|
adminSpace.add_view(ExperimentMarkView(ExperimentMark, url="experiment_mark"))
|
||||||
|
adminSpace.add_view(ExperimentView(Experiment, url="experiment"))
|
||||||
|
adminSpace.add_view(SemesterExperimentView(SemesterExperiment, url="semester_experiment"))
|
||||||
|
adminSpace.add_view(SemesterView(Semester, url="semester"))
|
||||||
|
adminSpace.add_view(PartView(Part, url="part"))
|
||||||
|
adminSpace.add_view(AssistantView(Assistant, url="assistant"))
|
||||||
|
adminSpace.add_view(AdminView(Admin, url="admin"))
|
||||||
|
adminSpace.add_view(UserView(User, url="user"))
|
||||||
|
adminSpace.add_view(ProgramView(Program, url="program"))
|
||||||
|
adminSpace.add_view(ImportView(name="Import", url="import"))
|
||||||
|
adminSpace.add_view(ActionsView(name="Actions", url="actions"))
|
||||||
|
adminSpace.add_view(AnalysisView(name="Analysis", url="analysis"))
|
||||||
|
adminSpace.add_view(DocsView(name="Docs", url="docs"))
|
||||||
|
|
||||||
|
initActiveSemesterMenuLinks(adminSpace, app)
|
||||||
|
|
|
@ -1,17 +1,21 @@
|
||||||
from flask import flash, redirect, request, url_for
|
from flask import flash, redirect, request, url_for
|
||||||
|
from flask_admin import Admin as FlaskAdmin
|
||||||
from flask_admin import expose
|
from flask_admin import expose
|
||||||
from flask_admin.model.template import EndpointLinkRowAction
|
from flask_admin.model.template import EndpointLinkRowAction
|
||||||
from flask_security import admin_change_password, current_user
|
from flask_security import admin_change_password, current_user
|
||||||
from flask_wtf import FlaskForm
|
from flask_wtf import FlaskForm
|
||||||
|
|
||||||
from . import assistantSpace, db
|
|
||||||
from .advlabdb_independent_funs import (
|
from .advlabdb_independent_funs import (
|
||||||
deep_getattr,
|
deep_getattr,
|
||||||
experiment_marks_missing_formatter,
|
experiment_marks_missing_formatter,
|
||||||
flashRandomPassword,
|
flashRandomPassword,
|
||||||
str_formatter,
|
str_formatter,
|
||||||
)
|
)
|
||||||
from .custom_classes import SecureAssistantBaseView, SecureAssistantModelView
|
from .custom_classes import (
|
||||||
|
SecureAssistantBaseView,
|
||||||
|
SecureAssistantIndexView,
|
||||||
|
SecureAssistantModelView,
|
||||||
|
)
|
||||||
from .exceptions import ModelViewException
|
from .exceptions import ModelViewException
|
||||||
from .forms import assistant_group_experiment_form_factory
|
from .forms import assistant_group_experiment_form_factory
|
||||||
from .model_dependent_funs import (
|
from .model_dependent_funs import (
|
||||||
|
@ -21,7 +25,15 @@ from .model_dependent_funs import (
|
||||||
user_info_fields,
|
user_info_fields,
|
||||||
)
|
)
|
||||||
from .model_independent_funs import randomPassword, reportBadAttempt
|
from .model_independent_funs import randomPassword, reportBadAttempt
|
||||||
from .models import Assistant, GroupExperiment, SemesterExperiment, User
|
from .models import Assistant, GroupExperiment, SemesterExperiment, User, db
|
||||||
|
|
||||||
|
assistantSpace = FlaskAdmin(
|
||||||
|
name="Assistant@AdvLabDB",
|
||||||
|
url="/assistant",
|
||||||
|
template_mode="bootstrap4",
|
||||||
|
static_url_path="/static/a",
|
||||||
|
index_view=SecureAssistantIndexView(name="Home", url="/assistant", endpoint="assistant"),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class AssistantGroupExperimentView(SecureAssistantModelView):
|
class AssistantGroupExperimentView(SecureAssistantModelView):
|
||||||
|
@ -89,7 +101,7 @@ class AssistantGroupExperimentView(SecureAssistantModelView):
|
||||||
try:
|
try:
|
||||||
group_experiment = db.session.get(GroupExperiment, int(group_experiment_id_str))
|
group_experiment = db.session.get(GroupExperiment, int(group_experiment_id_str))
|
||||||
except Exception:
|
except Exception:
|
||||||
red = url_for("index") + "assistant/group_experiment"
|
red = url_for("main.index") + "assistant/group_experiment"
|
||||||
flash("No valid group experiment id")
|
flash("No valid group experiment id")
|
||||||
return redirect(red)
|
return redirect(red)
|
||||||
|
|
||||||
|
@ -146,7 +158,7 @@ class AssistantGroupExperimentView(SecureAssistantModelView):
|
||||||
|
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
red = url_for("index") + "assistant/group_experiment"
|
red = url_for("main.index") + "assistant/group_experiment"
|
||||||
return redirect(red)
|
return redirect(red)
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
flash(str(ex), "error")
|
flash(str(ex), "error")
|
||||||
|
@ -211,8 +223,11 @@ class AssistantDocsView(SecureAssistantBaseView):
|
||||||
return self.render("docs/docs.html", role="assistant")
|
return self.render("docs/docs.html", role="assistant")
|
||||||
|
|
||||||
|
|
||||||
assistantSpace.add_view(AssistantGroupExperimentView(GroupExperiment, url="group_experiment"))
|
def init_assistant_model_views(app):
|
||||||
assistantSpace.add_view(AssistantUserView(User, url="user"))
|
assistantSpace.init_app(app)
|
||||||
assistantSpace.add_view(AssistantDocsView(name="Docs", url="docs"))
|
|
||||||
|
|
||||||
initActiveSemesterMenuLinks(assistantSpace)
|
assistantSpace.add_view(AssistantGroupExperimentView(GroupExperiment, url="group_experiment"))
|
||||||
|
assistantSpace.add_view(AssistantUserView(User, url="user"))
|
||||||
|
assistantSpace.add_view(AssistantDocsView(name="Docs", url="docs"))
|
||||||
|
|
||||||
|
initActiveSemesterMenuLinks(assistantSpace, app)
|
||||||
|
|
|
@ -3,14 +3,14 @@ from configparser import ConfigParser
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
def load_config(app, *files):
|
def load_config(*files):
|
||||||
config = ConfigParser()
|
config = ConfigParser()
|
||||||
|
|
||||||
for file in files:
|
for file in files:
|
||||||
file = Path(file)
|
file = Path(file)
|
||||||
|
|
||||||
if not file.is_file():
|
if not file.is_file():
|
||||||
app.logger.critical(str(file) + " is missing")
|
print(f"{file} is missing!")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
config.read(file)
|
config.read(file)
|
||||||
|
@ -18,8 +18,15 @@ def load_config(app, *files):
|
||||||
return config
|
return config
|
||||||
|
|
||||||
|
|
||||||
|
def get_settings():
|
||||||
|
config = load_config("settings.ini")
|
||||||
|
settings = config["Settings"]
|
||||||
|
|
||||||
|
return settings
|
||||||
|
|
||||||
|
|
||||||
def set_config(app):
|
def set_config(app):
|
||||||
config = load_config(app, "secrets.ini", "settings.ini")
|
config = load_config("secrets.ini", "settings.ini")
|
||||||
secrets = config["Secrets"]
|
secrets = config["Secrets"]
|
||||||
settings = config["Settings"]
|
settings = config["Settings"]
|
||||||
|
|
||||||
|
@ -55,5 +62,3 @@ def set_config(app):
|
||||||
app.config["SECURITY_PASSWORD_SALT"] = secrets["SECURITY_PASSWORD_SALT"]
|
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"] =
|
# TODO: app.config["SECURITY_LOGIN_USER_TEMPLATE"] =
|
||||||
|
|
||||||
return settings
|
|
||||||
|
|
|
@ -6,9 +6,8 @@ from flask_admin.model.helpers import get_mdict_item_or_list
|
||||||
from flask_security import current_user
|
from flask_security import current_user
|
||||||
from sqlalchemy import select
|
from sqlalchemy import select
|
||||||
|
|
||||||
from . import db
|
|
||||||
from .exceptions import DataBaseException, ModelViewException
|
from .exceptions import DataBaseException, ModelViewException
|
||||||
from .model_independent_funs import get_count, reportBadAttempt
|
from .model_independent_funs import reportBadAttempt
|
||||||
from .models import (
|
from .models import (
|
||||||
Assistant,
|
Assistant,
|
||||||
ExperimentMark,
|
ExperimentMark,
|
||||||
|
@ -16,6 +15,8 @@ from .models import (
|
||||||
Part,
|
Part,
|
||||||
PartStudent,
|
PartStudent,
|
||||||
SemesterExperiment,
|
SemesterExperiment,
|
||||||
|
db,
|
||||||
|
get_count,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -5,9 +5,8 @@ from shutil import copy2
|
||||||
from flask import flash, has_request_context
|
from flask import flash, has_request_context
|
||||||
from sqlalchemy import select
|
from sqlalchemy import select
|
||||||
|
|
||||||
from . import db, settings
|
from . import settings
|
||||||
from .exceptions import DataBaseImportException
|
from .exceptions import DataBaseImportException
|
||||||
from .model_independent_funs import get_first
|
|
||||||
from .models import (
|
from .models import (
|
||||||
Appointment,
|
Appointment,
|
||||||
Assistant,
|
Assistant,
|
||||||
|
@ -21,6 +20,8 @@ from .models import (
|
||||||
SemesterExperiment,
|
SemesterExperiment,
|
||||||
Student,
|
Student,
|
||||||
User,
|
User,
|
||||||
|
db,
|
||||||
|
get_first,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -10,11 +10,11 @@ from flask_security import current_user
|
||||||
from wtforms.fields import BooleanField, IntegerField, SelectField, StringField
|
from wtforms.fields import BooleanField, IntegerField, SelectField, StringField
|
||||||
from wtforms.validators import DataRequired, NumberRange, Optional
|
from wtforms.validators import DataRequired, NumberRange, Optional
|
||||||
|
|
||||||
from . import app, settings
|
from . import settings
|
||||||
from .models import MAX_MARK, MIN_MARK, Semester
|
from .models import MAX_MARK, MIN_MARK, Semester
|
||||||
|
|
||||||
|
|
||||||
def initActiveSemesterMenuLinks(space):
|
def initActiveSemesterMenuLinks(space, app):
|
||||||
with app.app_context():
|
with app.app_context():
|
||||||
try:
|
try:
|
||||||
semesters = Semester.sortedSemestersStartingWithNewest()
|
semesters = Semester.sortedSemestersStartingWithNewest()
|
||||||
|
|
|
@ -5,25 +5,15 @@ Functions not dependent on advlabdb.models.
|
||||||
import secrets
|
import secrets
|
||||||
from string import ascii_letters, digits
|
from string import ascii_letters, digits
|
||||||
|
|
||||||
from sqlalchemy import func, select
|
from flask import current_app
|
||||||
|
|
||||||
from . import app, db
|
|
||||||
|
|
||||||
PASSWORD_CHARS: str = ascii_letters + digits + "!%*+=?"
|
PASSWORD_CHARS: str = ascii_letters + digits + "!%*+=?"
|
||||||
|
|
||||||
|
|
||||||
def randomPassword() -> str:
|
def randomPassword() -> str:
|
||||||
password_length = app.config["SECURITY_PASSWORD_LENGTH_MIN"]
|
password_length = current_app.config["SECURITY_PASSWORD_LENGTH_MIN"]
|
||||||
return "".join(secrets.choice(PASSWORD_CHARS) for i in range(password_length))
|
return "".join(secrets.choice(PASSWORD_CHARS) for i in range(password_length))
|
||||||
|
|
||||||
|
|
||||||
def reportBadAttempt(message: str) -> None:
|
def reportBadAttempt(message: str) -> None:
|
||||||
print("BAD ATTEMPT:", message) # TODO: Log
|
print("BAD ATTEMPT:", message) # TODO: Log
|
||||||
|
|
||||||
|
|
||||||
def get_count(table):
|
|
||||||
return db.session.scalar(select(func.count()).select_from(table))
|
|
||||||
|
|
||||||
|
|
||||||
def get_first(table):
|
|
||||||
return db.session.execute(table.limit(1)).scalars().first()
|
|
||||||
|
|
|
@ -10,12 +10,12 @@ from decimal import ROUND_HALF_UP, Decimal
|
||||||
|
|
||||||
from flask import flash
|
from flask import flash
|
||||||
from flask_security import current_user
|
from flask_security import current_user
|
||||||
|
from flask_security.models import fsqla_v2 as fsqla
|
||||||
from flask_security.models.fsqla_v2 import FsRoleMixin, FsUserMixin
|
from flask_security.models.fsqla_v2 import FsRoleMixin, FsUserMixin
|
||||||
from sqlalchemy import select
|
from flask_sqlalchemy import SQLAlchemy
|
||||||
|
from sqlalchemy import func, select
|
||||||
|
|
||||||
from . import db
|
|
||||||
from .exceptions import DataBaseException
|
from .exceptions import DataBaseException
|
||||||
from .model_independent_funs import get_first
|
|
||||||
|
|
||||||
MIN_MARK = 0
|
MIN_MARK = 0
|
||||||
MAX_MARK = 15
|
MAX_MARK = 15
|
||||||
|
@ -27,6 +27,19 @@ MIN_GROUP_NUMBER = 1
|
||||||
MIN_DURATION_IN_DAYS = 1
|
MIN_DURATION_IN_DAYS = 1
|
||||||
MIN_PART_NUMBER = 1
|
MIN_PART_NUMBER = 1
|
||||||
|
|
||||||
|
db = SQLAlchemy()
|
||||||
|
|
||||||
|
# For Flask-Security-Too
|
||||||
|
fsqla.FsModels.set_db_info(db)
|
||||||
|
|
||||||
|
|
||||||
|
def get_count(table):
|
||||||
|
return db.session.scalar(select(func.count()).select_from(table))
|
||||||
|
|
||||||
|
|
||||||
|
def get_first(table):
|
||||||
|
return db.session.execute(table.limit(1)).scalars().first()
|
||||||
|
|
||||||
|
|
||||||
def roundHalfUpToInt(number):
|
def roundHalfUpToInt(number):
|
||||||
return int(Decimal(number).quantize(Decimal(0), rounding=ROUND_HALF_UP))
|
return int(Decimal(number).quantize(Decimal(0), rounding=ROUND_HALF_UP))
|
||||||
|
|
|
@ -1,19 +1,21 @@
|
||||||
from flask import flash, redirect, request, url_for
|
from flask import Blueprint, flash, redirect, request, url_for
|
||||||
from flask_security import auth_required, current_user
|
from flask_security import auth_required, current_user
|
||||||
|
|
||||||
from . import app, db
|
|
||||||
from .model_dependent_funs import active_semester_str
|
from .model_dependent_funs import active_semester_str
|
||||||
from .models import Semester
|
from .models import Semester, db
|
||||||
|
|
||||||
|
bp = Blueprint("main", __name__, root_path="/", template_folder="templates")
|
||||||
|
|
||||||
|
|
||||||
@app.context_processor
|
@bp.app_context_processor
|
||||||
def util_processor():
|
def util_processor():
|
||||||
author_email = "mobitar@students.uni-mainz.de"
|
author_email = "mobitar@students.uni-mainz.de"
|
||||||
footer = f"<hr><p style='font-size:14px;'>This website is still under development (beta release)! If you have any questions, find any bugs or want some feature, please write a formless email (german/english) to Mo Bitar: <a href='mailto:{author_email}'>{author_email}</a>. Feedback is also welcome :)</p><br>"
|
footer = f"<hr><p style='font-size:14px;'>This website is still under development (beta release)! If you have any questions, find any bugs or want some feature, please write a formless email (german/english) to Mo Bitar: <a href='mailto:{author_email}'>{author_email}</a>. Feedback is also welcome :)</p><br>"
|
||||||
|
|
||||||
return dict(active_semester_str=active_semester_str, current_user=current_user, footer=footer)
|
return dict(active_semester_str=active_semester_str, current_user=current_user, footer=footer)
|
||||||
|
|
||||||
|
|
||||||
@app.route("/")
|
@bp.route("/")
|
||||||
def index():
|
def index():
|
||||||
if current_user.has_role("admin"):
|
if current_user.has_role("admin"):
|
||||||
endpoint_base = "admin"
|
endpoint_base = "admin"
|
||||||
|
@ -30,7 +32,7 @@ def index():
|
||||||
return redirect(url)
|
return redirect(url)
|
||||||
|
|
||||||
|
|
||||||
@app.route("/set_semester")
|
@bp.route("/set_semester")
|
||||||
@auth_required()
|
@auth_required()
|
||||||
def set_semester():
|
def set_semester():
|
||||||
try:
|
try:
|
||||||
|
@ -41,5 +43,5 @@ def set_semester():
|
||||||
semester = db.session.get(Semester, semesterId)
|
semester = db.session.get(Semester, semesterId)
|
||||||
current_user.setActiveSemester(semester)
|
current_user.setActiveSemester(semester)
|
||||||
|
|
||||||
red = request.referrer or url_for("index")
|
red = request.referrer or url_for("main.index")
|
||||||
return redirect(red)
|
return redirect(red)
|
||||||
|
|
|
@ -1,18 +1,20 @@
|
||||||
{% macro information(current_user, active_semester_str, role) %}
|
{% macro information(current_user, active_semester_str, role) %} User:
|
||||||
User: <a href="{{ url_for('index') }}{{ role }}/user/details/?id={{ current_user.id }}">{{ current_user }}</a>
|
<a
|
||||||
|
href="{{ url_for('main.index') }}{{ role }}/user/details/?id={{ current_user.id }}"
|
||||||
|
>{{ current_user }}</a
|
||||||
|
>
|
||||||
|
|
||||||
| Active semester: {{ active_semester_str() }}
|
| Active semester: {{ active_semester_str() }} {% if (role == "admin") and
|
||||||
|
(current_user.has_role("assistant")) %} |
|
||||||
{% if (role == "admin") and (current_user.has_role("assistant")) %}
|
<a href="{{ url_for('main.index') }}assistant">Assistant space</a>. {% elif
|
||||||
| <a href="{{ url_for('index') }}assistant">Assistant space</a>.
|
(role == "assistant") and (current_user.has_role("admin")) %} |
|
||||||
{% elif (role == "assistant") and (current_user.has_role("admin")) %}
|
<a href="{{ url_for('main.index') }}admin">Admin space</a>. {% endif %} {%
|
||||||
| <a href="{{ url_for('index') }}admin">Admin space</a>.
|
endmacro %} {% macro
|
||||||
{% endif %}
|
missing_final_experiment_marks(number_of_missing_final_experiment_marks,
|
||||||
{% endmacro %}
|
number_of_all_experiment_marks) %}
|
||||||
|
<p>
|
||||||
{% macro missing_final_experiment_marks(number_of_missing_final_experiment_marks, number_of_all_experiment_marks) %}
|
Number of <strong>missing</strong> final experiment marks: {{
|
||||||
<p>
|
number_of_missing_final_experiment_marks }} / {{
|
||||||
Number of <strong>missing</strong> final experiment marks:
|
number_of_all_experiment_marks }}
|
||||||
{{ number_of_missing_final_experiment_marks }} / {{ number_of_all_experiment_marks }}
|
</p>
|
||||||
</p>
|
|
||||||
{% endmacro %}
|
{% endmacro %}
|
||||||
|
|
|
@ -117,7 +117,7 @@ cd ~/advlabdb
|
||||||
+
|
+
|
||||||
[source,bash]
|
[source,bash]
|
||||||
----
|
----
|
||||||
poetry run python3 manage.py setup initialize-database
|
poetry run python3 manage.py setup init-db
|
||||||
----
|
----
|
||||||
|
|
||||||
.. *Done!* Now go to your SERVER_NAME using a browser to verify that everything is working.
|
.. *Done!* Now go to your SERVER_NAME using a browser to verify that everything is working.
|
||||||
|
|
17
manage.py
17
manage.py
|
@ -12,9 +12,9 @@ from flask_admin import __file__ as flask_admin_path
|
||||||
from flask_security import admin_change_password, hash_password
|
from flask_security import admin_change_password, hash_password
|
||||||
from sqlalchemy import select
|
from sqlalchemy import select
|
||||||
|
|
||||||
from advlabdb import app, db, settings, user_datastore
|
from advlabdb import create_app, settings, user_datastore
|
||||||
from advlabdb.model_independent_funs import randomPassword
|
from advlabdb.model_independent_funs import randomPassword
|
||||||
from advlabdb.models import MAX_YEAR, MIN_YEAR, Admin, Semester, User
|
from advlabdb.models import MAX_YEAR, MIN_YEAR, Admin, Semester, User, db
|
||||||
|
|
||||||
|
|
||||||
def run(command: str, **kwargs):
|
def run(command: str, **kwargs):
|
||||||
|
@ -76,23 +76,16 @@ def generate_secrets():
|
||||||
short_help="Initialize the database.",
|
short_help="Initialize the database.",
|
||||||
help="Initialize the database if it does not already exist.",
|
help="Initialize the database if it does not already exist.",
|
||||||
)
|
)
|
||||||
def initialize_database():
|
def init_db():
|
||||||
db_file = Path(settings["SQLITE_DB_PATH"])
|
db_file = Path(settings["SQLITE_DB_PATH"])
|
||||||
if db_file.is_file():
|
if db_file.is_file():
|
||||||
click.echo(f"Skipping database initialization because the database does already exist at {db_file}.")
|
click.echo(f"Skipping database initialization because the database does already exist at {db_file}.")
|
||||||
return
|
return
|
||||||
|
|
||||||
click.echo("\nThis script should only be used to initialize the database after setting up a server")
|
app = create_app(create_for_server=False)
|
||||||
click.echo(click.style("The old database will be DELETED and a new database will be created!", bg="red"))
|
|
||||||
|
|
||||||
if not click.confirm(click.style("Are you sure that you want to continue?", fg="red"), default=False):
|
|
||||||
click.echo(click.style("Aborted!", fg="yellow"))
|
|
||||||
return
|
|
||||||
|
|
||||||
with app.app_context():
|
with app.app_context():
|
||||||
with db.session.begin():
|
with db.session.begin():
|
||||||
# Delete old database
|
|
||||||
db.drop_all()
|
|
||||||
# Create new database
|
# Create new database
|
||||||
db.create_all()
|
db.create_all()
|
||||||
|
|
||||||
|
@ -170,6 +163,8 @@ def maintain():
|
||||||
def reset_admin_password():
|
def reset_admin_password():
|
||||||
click.echo("This script will generate a new random password for a chosen admin.\n")
|
click.echo("This script will generate a new random password for a chosen admin.\n")
|
||||||
|
|
||||||
|
app = create_app(create_for_server=False)
|
||||||
|
|
||||||
with app.app_context():
|
with app.app_context():
|
||||||
with db.session.begin():
|
with db.session.begin():
|
||||||
admins = db.session.execute(select(Admin).join(User).where(User.active == True)).scalars().all()
|
admins = db.session.execute(select(Admin).join(User).where(User.active == True)).scalars().all()
|
||||||
|
|
|
@ -167,7 +167,7 @@ commands = [
|
||||||
# Install Python requirements in the container
|
# Install Python requirements in the container
|
||||||
"buildah run builder -- pip3 install -r requirements.txt",
|
"buildah run builder -- pip3 install -r requirements.txt",
|
||||||
"buildah run builder -- python3 manage.py setup generate-secrets",
|
"buildah run builder -- python3 manage.py setup generate-secrets",
|
||||||
"buildah run builder -- python3 manage.py setup initialize-database",
|
"buildah run builder -- python3 manage.py setup init-db",
|
||||||
"buildah config --cmd 'gunicorn --bind 0.0.0.0:80 --workers 5 --log-file /volumes/logs/gunicorn.log run:app' builder",
|
"buildah config --cmd 'gunicorn --bind 0.0.0.0:80 --workers 5 --log-file /volumes/logs/gunicorn.log run:app' builder",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
3
run.py
3
run.py
|
@ -1,4 +1,5 @@
|
||||||
from advlabdb import app
|
from advlabdb import create_app
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
app = create_app()
|
||||||
app.run(debug=True)
|
app.run(debug=True)
|
||||||
|
|
Loading…
Reference in a new issue