From bc57f6f747b5bfd94e620d10c20a0d1083c74942 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ricardo=20Monta=C3=B1ana?= Date: Thu, 8 Jun 2023 19:58:07 +0200 Subject: [PATCH] admin blueprint --- app/admin/forms.py | 74 ++++++++++++ app/admin/main_admin.py | 139 +++++++++++++++++++++++ app/admin/templates/_table_users.html | 40 +++++++ app/{ => admin}/templates/password.html | 0 app/{ => admin}/templates/user.html | 2 +- app/admin/templates/users.html | 20 ++++ app/app.py | 2 + app/config.py | 1 + app/forms.py | 53 --------- app/main.py | 143 ++---------------------- app/templates/_nav.html | 6 +- app/templates/users.html | 61 ---------- 12 files changed, 288 insertions(+), 253 deletions(-) create mode 100644 app/admin/forms.py create mode 100644 app/admin/main_admin.py create mode 100644 app/admin/templates/_table_users.html rename app/{ => admin}/templates/password.html (100%) rename app/{ => admin}/templates/user.html (80%) create mode 100644 app/admin/templates/users.html delete mode 100644 app/templates/users.html diff --git a/app/admin/forms.py b/app/admin/forms.py new file mode 100644 index 0000000..7b2f39c --- /dev/null +++ b/app/admin/forms.py @@ -0,0 +1,74 @@ +from flask_wtf import FlaskForm +from wtforms import ( + StringField, + PasswordField, + BooleanField, + SubmitField, + SelectField, +) +from wtforms.validators import ( + DataRequired, + Length, + EqualTo, + Email, + ValidationError, +) +from app.models import User + + +class UserForm(FlaskForm): + username = StringField( + "Username", validators=[DataRequired(), Length(1, 20)] + ) + email = StringField("Email", validators=[DataRequired(), Email()]) + password = PasswordField( + "Password", validators=[DataRequired(), Length(4, 150)] + ) + password2 = PasswordField( + "Password", + validators=[DataRequired(), Length(4, 150), EqualTo("password")], + ) + admin = BooleanField("Admin") + benchmark_id = SelectField("Benchmark", coerce=int) + + submit = SubmitField() + + def validate_username(self, username): + user = User.query.filter_by(username=username.data).first() + message = "Already taken. Please use a different one." + if user is not None: + if self.user_id is None: + raise ValidationError(message) + else: + if user.id != int(self.user_id): + raise ValidationError(message) + + def validate_email(self, email): + user = User.query.filter_by(email=email.data).first() + message = "Already taken. Please use a different one." + if user is not None: + if self.user_id is None: + raise ValidationError(message) + else: + if user.id != int(self.user_id): + raise ValidationError(message) + + +class UpdatePasswordForm(FlaskForm): + password = PasswordField( + "Password", validators=[DataRequired(), Length(4, 150)] + ) + password2 = PasswordField( + "Password", + validators=[DataRequired(), Length(4, 150), EqualTo("password")], + ) + submit = SubmitField() + + +class BenchmarkForm(FlaskForm): + name = StringField("Name", validators=[DataRequired(), Length(1, 20)]) + description = StringField( + "Description", validators=[DataRequired(), Length(1, 20)] + ) + folder = StringField("Folder", validators=[DataRequired(), Length(1, 20)]) + submit = SubmitField() diff --git a/app/admin/main_admin.py b/app/admin/main_admin.py new file mode 100644 index 0000000..83c53fe --- /dev/null +++ b/app/admin/main_admin.py @@ -0,0 +1,139 @@ +from flask import ( + Blueprint, + render_template, + url_for, + flash, + redirect, + current_app, +) +from flask_login import current_user, login_required +from .forms import UserForm, UpdatePasswordForm +from ..models import User, Benchmark, db + +admin = Blueprint("admin", __name__, template_folder="templates") + + +@admin.route("/users") +@login_required +def users(): + if not current_user.admin: + flash("You are not an admin.", "danger") + return redirect(url_for(current_app.config["INDEX"])) + users = User.query.all() + return render_template("users.html", users=users) + + +@admin.route("/user_edit/", methods=["GET", "POST"]) +@login_required +def user_edit(user_id): + if user_id != current_user.id and not current_user.admin: + flash("You are not an admin.", "danger") + return redirect(url_for(current_app.config["INDEX"])) + form = UserForm(obj=User.query.filter_by(id=user_id).first()) + form.benchmark_id.choices = [ + (b.id, b.name) for b in Benchmark.query.order_by("name") + ] + del form.password + del form.password2 + form.user_id = user_id + form.submit.label.text = "Edit User" + if form.validate_on_submit(): + form.populate_obj(User.query.filter_by(id=user_id).first()) + db.session.commit() + flash("User edited successfully.") + return redirect(url_for("admin.users")) + return render_template( + "user.html", + form=form, + alert_type="primary", + title="Edit User", + ) + + +@admin.route("/user_delete/", methods=["GET", "POST"]) +@login_required +def user_delete(user_id): + if user_id != current_user.id and not current_user.admin: + flash("You are not an admin.", "danger") + return redirect(url_for(INDEX)) + user = User.query.filter_by(id=user_id).first() + form = UserForm(obj=user) + del form.password + del form.password2 + for field in form: + if field.type != "SubmitField" and field.type != "CSRFTokenField": + if field.type == "SelectField" or field.type == "BooleanField": + field.render_kw = {"disabled": True} + else: + field.render_kw = {"readonly": True} + + form.benchmark_id.choices = [ + (b.id, b.name) for b in Benchmark.query.order_by("name") + ] + form.submit.label.text = "Delete User" + form.user_id = user_id + if form.validate_on_submit(): + flash("User deleted successfully.") + db.session.delete(user) + db.session.commit() + return redirect(url_for("admin.users")) + return render_template( + "user.html", + form=form, + alert_type="danger", + title="Delete User", + ) + + +@admin.route("/user_new", methods=["GET", "POST"]) +@login_required +def user_new(): + if not current_user.admin: + flash("You are not an admin.", "danger") + return redirect(url_for(INDEX)) + form = UserForm() + user = User() + form.user_id = None + form.benchmark_id.choices = [ + (b.id, b.name) for b in Benchmark.query.order_by("name") + ] + form.submit.label.text = "New User" + if form.validate_on_submit(): + form.populate_obj(user) + user.set_password(form.password.data) + db.session.add(user) + db.session.commit() + flash("User created successfully.") + return redirect(url_for("admin.users")) + return render_template( + "user.html", form=form, alert_type="info", title="New User" + ) + + +@admin.route( + "/password//", + methods=["GET", "POST"], +) +@admin.route( + "/password/", + defaults={"back": "None"}, + methods=["GET", "POST"], +) +@login_required +def password(user_id, back): + if not current_user.admin and user_id != current_user.id: + flash("You are not an admin.", "danger") + return redirect(url_for(current_app.config["INDEX"])) + form = UpdatePasswordForm() + user = User.query.filter_by(id=user_id).first() + form.submit.label.text = "Update Password" + destination = current_app.config["INDEX"] if back == "None" else back + if form.validate_on_submit(): + form.populate_obj(user) + user.set_password(form.password.data) + db.session.commit() + flash("Password updated successfully.") + return redirect(url_for(destination)) + return render_template( + "password.html", form=form, back=destination, user_name=user.username + ) diff --git a/app/admin/templates/_table_users.html b/app/admin/templates/_table_users.html new file mode 100644 index 0000000..038736a --- /dev/null +++ b/app/admin/templates/_table_users.html @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + {% for user in users %} + + + + + + + + + + {% endfor %} + +
UsernameEmailDate CreatedLast LoginIs AdminBenchmarkActions
{{ user.username }}{{ user.email }}{{ user.date_created.strftime("%d-%m-%Y, %T") }}{{ user.last_login.strftime("%d-%m-%Y, %T") }} + + {{ user.benchmark.name }} + + + +
diff --git a/app/templates/password.html b/app/admin/templates/password.html similarity index 100% rename from app/templates/password.html rename to app/admin/templates/password.html diff --git a/app/templates/user.html b/app/admin/templates/user.html similarity index 80% rename from app/templates/user.html rename to app/admin/templates/user.html index 4658a96..45c9783 100644 --- a/app/templates/user.html +++ b/app/admin/templates/user.html @@ -4,7 +4,7 @@ +{% endblock content %} diff --git a/app/app.py b/app/app.py index ef9f764..8e3aad2 100644 --- a/app/app.py +++ b/app/app.py @@ -6,6 +6,7 @@ from .config import Config from .models import User, db from .results.main_select import results +from .admin.main_admin import admin from .main import main bootstrap = Bootstrap5() @@ -32,6 +33,7 @@ def create_app(): login_manager.login_view = "main.login" app.jinja_env.auto_reload = True app.register_blueprint(results, url_prefix="/results") + app.register_blueprint(admin, url_prefix="/admin") app.register_blueprint(main) app.shell_context_processor(make_shell_context) with app.app_context(): diff --git a/app/config.py b/app/config.py index 216ad8a..5516de2 100644 --- a/app/config.py +++ b/app/config.py @@ -14,3 +14,4 @@ class Config(object): "DATABASE_URL" ) or "sqlite:///" + os.path.join(basedir, "app.db") SQLALCHEMY_TRACK_MODIFICATIONS = False + INDEX = "main.index" diff --git a/app/forms.py b/app/forms.py index ec5c208..1ec7887 100644 --- a/app/forms.py +++ b/app/forms.py @@ -9,11 +9,7 @@ from wtforms import ( from wtforms.validators import ( DataRequired, Length, - EqualTo, - Email, - ValidationError, ) -from app.models import User class LoginForm(FlaskForm): @@ -27,54 +23,5 @@ class LoginForm(FlaskForm): submit = SubmitField() -class UserForm(FlaskForm): - username = StringField( - "Username", validators=[DataRequired(), Length(1, 20)] - ) - email = StringField("Email", validators=[DataRequired(), Email()]) - password = PasswordField( - "Password", validators=[DataRequired(), Length(4, 150)] - ) - password2 = PasswordField( - "Password", - validators=[DataRequired(), Length(4, 150), EqualTo("password")], - ) - admin = BooleanField("Admin") - benchmark_id = SelectField("Benchmark", coerce=int) - - submit = SubmitField() - - def validate_username(self, username): - user = User.query.filter_by(username=username.data).first() - message = "Already taken. Please use a different one." - if user is not None: - if self.user_id is None: - raise ValidationError(message) - else: - if user.id != int(self.user_id): - raise ValidationError(message) - - def validate_email(self, email): - user = User.query.filter_by(email=email.data).first() - message = "Already taken. Please use a different one." - if user is not None: - if self.user_id is None: - raise ValidationError(message) - else: - if user.id != int(self.user_id): - raise ValidationError(message) - - -class UpdatePasswordForm(FlaskForm): - password = PasswordField( - "Password", validators=[DataRequired(), Length(4, 150)] - ) - password2 = PasswordField( - "Password", - validators=[DataRequired(), Length(4, 150), EqualTo("password")], - ) - submit = SubmitField() - - class BenchmarkSelect(FlaskForm): submit = SubmitField("Select") diff --git a/app/main.py b/app/main.py index b85f980..18b4f7c 100644 --- a/app/main.py +++ b/app/main.py @@ -8,16 +8,15 @@ from flask import ( flash, redirect, request, + current_app, ) from flask_login import login_user, current_user, logout_user, login_required from werkzeug.urls import url_parse -from .forms import LoginForm, UserForm, UpdatePasswordForm +from .forms import LoginForm from .models import User, Benchmark, db main = Blueprint("main", __name__) -INDEX = "main.index" - @main.route("/") @main.route("/index") @@ -41,17 +40,17 @@ def index(): def set_benchmark(benchmark_id): if int(benchmark_id) == current_user.benchmark_id: flash("Benchmark already selected.") - return redirect(url_for(INDEX)) + return redirect(url_for(current_app.config["INDEX"])) if current_user.admin: benchmark = Benchmark.query.filter_by(id=benchmark_id).first() if benchmark is None: flash("Benchmark not found.", "danger") - return redirect(url_for(INDEX)) + return redirect(url_for(current_app.config["INDEX"])) current_user.benchmark = benchmark db.session.commit() else: flash("You are not an admin.", "danger") - return redirect(url_for(INDEX)) + return redirect(url_for(current_app.config["INDEX"])) @main.route("/config") @@ -65,7 +64,7 @@ def config(): @main.route("/login", methods=["GET", "POST"]) def login(): if current_user.is_authenticated: - return redirect(url_for(INDEX)) + return redirect(url_for(current_app.config["INDEX"])) form = LoginForm() if form.validate_on_submit(): user = User.query.filter_by(username=form.username.data).first() @@ -78,7 +77,7 @@ def login(): flash("Logged in successfully.") next_page = request.args.get("next") if not next_page or url_parse(next_page).netloc != "": - next_page = url_for(INDEX) + next_page = url_for(current_app.config["INDEX"]) return redirect(next_page) return render_template("login.html", title="Sign In", form=form) @@ -87,130 +86,4 @@ def login(): def logout(): if current_user.is_authenticated: logout_user() - return redirect(url_for(INDEX)) - - -@main.route("/users") -@login_required -def users(): - if not current_user.admin: - flash("You are not an admin.", "danger") - return redirect(url_for(INDEX)) - users = User.query.all() - return render_template("users.html", users=users) - - -@main.route("/user_edit/", methods=["GET", "POST"]) -@login_required -def user_edit(user_id): - if user_id != current_user.id and not current_user.admin: - flash("You are not an admin.", "danger") - return redirect(url_for(INDEX)) - form = UserForm(obj=User.query.filter_by(id=user_id).first()) - form.benchmark_id.choices = [ - (b.id, b.name) for b in Benchmark.query.order_by("name") - ] - del form.password - del form.password2 - form.user_id = user_id - form.submit.label.text = "Edit User" - if form.validate_on_submit(): - form.populate_obj(User.query.filter_by(id=user_id).first()) - db.session.commit() - flash("User edited successfully.") - return redirect(url_for("main.users")) - return render_template( - "user.html", - form=form, - alert_type="primary", - title="Edit User", - ) - - -@main.route("/user_delete/", methods=["GET", "POST"]) -@login_required -def user_delete(user_id): - if user_id != current_user.id and not current_user.admin: - flash("You are not an admin.", "danger") - return redirect(url_for(INDEX)) - user = User.query.filter_by(id=user_id).first() - form = UserForm(obj=user) - del form.password - del form.password2 - for field in form: - if field.type != "SubmitField" and field.type != "CSRFTokenField": - if field.type == "SelectField" or field.type == "BooleanField": - field.render_kw = {"disabled": True} - else: - field.render_kw = {"readonly": True} - - form.benchmark_id.choices = [ - (b.id, b.name) for b in Benchmark.query.order_by("name") - ] - form.submit.label.text = "Delete User" - form.user_id = user_id - if form.validate_on_submit(): - flash("User deleted successfully.") - db.session.delete(user) - db.session.commit() - return redirect(url_for("main.users")) - return render_template( - "user.html", - form=form, - alert_type="danger", - title="Delete User", - ) - - -@main.route("/user_new", methods=["GET", "POST"]) -@login_required -def user_new(): - if not current_user.admin: - flash("You are not an admin.", "danger") - return redirect(url_for(INDEX)) - form = UserForm() - user = User() - form.user_id = None - form.benchmark_id.choices = [ - (b.id, b.name) for b in Benchmark.query.order_by("name") - ] - form.submit.label.text = "New User" - if form.validate_on_submit(): - form.populate_obj(user) - user.set_password(form.password.data) - db.session.add(user) - db.session.commit() - flash("User created successfully.") - return redirect(url_for("main.users")) - return render_template( - "user.html", form=form, alert_type="info", title="New User" - ) - - -@main.route( - "/password//", - methods=["GET", "POST"], -) -@main.route( - "/password/", - defaults={"back": "None"}, - methods=["GET", "POST"], -) -@login_required -def password(user_id, back): - if not current_user.admin and user_id != current_user.id: - flash("You are not an admin.", "danger") - return redirect(url_for(INDEX)) - form = UpdatePasswordForm() - user = User.query.filter_by(id=user_id).first() - form.submit.label.text = "Update Password" - destination = "main.index" if back == "None" else back - if form.validate_on_submit(): - form.populate_obj(user) - user.set_password(form.password.data) - db.session.commit() - flash("Password updated successfully.") - return redirect(url_for(destination)) - return render_template( - "password.html", form=form, back=destination, user_name=user.username - ) + return redirect(url_for(current_app.config["INDEX"])) diff --git a/app/templates/_nav.html b/app/templates/_nav.html index e5682e5..c4b13df 100644 --- a/app/templates/_nav.html +++ b/app/templates/_nav.html @@ -15,7 +15,7 @@