diff --git a/app/forms.py b/app/forms.py index 8f9729c..1989db9 100644 --- a/app/forms.py +++ b/app/forms.py @@ -4,11 +4,16 @@ from wtforms import ( PasswordField, BooleanField, SubmitField, + SelectField, ) from wtforms.validators import ( DataRequired, Length, + EqualTo, + Email, + ValidationError, ) +from app.models import User class LoginForm(FlaskForm): @@ -22,6 +27,43 @@ class LoginForm(FlaskForm): submit = SubmitField() -class BenchmarkSelect(FlaskForm): +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 BenchmarkSelect(FlaskForm): submit = SubmitField("Select") diff --git a/app/main.py b/app/main.py index 9185dd1..faf5d9c 100644 --- a/app/main.py +++ b/app/main.py @@ -12,7 +12,7 @@ from flask import ( ) from flask_login import login_user, current_user, logout_user, login_required from werkzeug.urls import url_parse -from .forms import LoginForm +from .forms import LoginForm, UserForm from .models import User, Benchmark, db main = Blueprint("main", __name__) @@ -74,6 +74,8 @@ def login(): flash("Invalid username or password", "danger") return redirect(url_for("main.login")) login_user(user, remember=form.remember_me.data) + user.last_login = db.func.now() + db.session.commit() flash("Logged in successfully.") next_page = request.args.get("next") if not next_page or url_parse(next_page).netloc != "": @@ -84,5 +86,103 @@ def login(): @main.route("/logout") def logout(): - logout_user() + 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" + ) diff --git a/app/models.py b/app/models.py index 53a1a68..0cde669 100644 --- a/app/models.py +++ b/app/models.py @@ -15,6 +15,8 @@ class User(UserMixin, db.Model): password_hash = db.Column(db.String(128)) benchmark_id = db.Column(db.Integer, db.ForeignKey("benchmark.id")) benchmark = db.relationship("Benchmark") + date_created = db.Column(db.DateTime, default=db.func.now()) + last_login = db.Column(db.DateTime, default=db.func.now()) def __repr__(self): return "".format(self.username, self.email) diff --git a/app/templates/_nav.html b/app/templates/_nav.html index afa8224..3979b6a 100644 --- a/app/templates/_nav.html +++ b/app/templates/_nav.html @@ -37,6 +37,7 @@ diff --git a/app/templates/base.html b/app/templates/base.html index a06f579..060311a 100644 --- a/app/templates/base.html +++ b/app/templates/base.html @@ -22,13 +22,15 @@
{% include "_nav.html" %}
{% with messages = get_flashed_messages(with_categories=true) %} {% if messages %} - {% for category, message in messages %} - {% if category == 'message' %} - {% set category = "info" %} - {% endif %} - - {% endfor %} +
+ {% for category, message in messages %} + {% if category == 'message' %} + {% set category = "info" %} + {% endif %} + + {% endfor %} +
{% endif %} {% endwith %}
diff --git a/app/templates/index.html b/app/templates/index.html index a79ade0..661f183 100644 --- a/app/templates/index.html +++ b/app/templates/index.html @@ -1,7 +1,7 @@ {% extends "base.html" %} {% block content %} {% if benchmarks %} - {% include "_benchmarks.html" %} +
{% include "_benchmarks.html" %}
{% else %} +

{{ title }}

+ +
+
{{ render_form(form) }}
+
+
+{% endblock %} diff --git a/app/templates/users.html b/app/templates/users.html new file mode 100644 index 0000000..ffad9fd --- /dev/null +++ b/app/templates/users.html @@ -0,0 +1,59 @@ +{% extends "report_tables.html" %} +{% block content %} +
+ +
+ + + + + + + + + + + + + + {% 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 }} + + +
+
+
+ +{% endblock content %} diff --git a/requeriments.txt b/requeriments.txt new file mode 100644 index 0000000..c337b9b --- /dev/null +++ b/requeriments.txt @@ -0,0 +1 @@ +email-validator