diff --git a/project/forms/admin.py b/project/forms/admin.py
index 1bf9a63..931ec2d 100644
--- a/project/forms/admin.py
+++ b/project/forms/admin.py
@@ -2,6 +2,7 @@ from flask_wtf import FlaskForm
from flask_babelex import lazy_gettext
from wtforms import TextAreaField, SubmitField
from wtforms.validators import Optional
+from project.forms.widgets import MultiCheckboxField
class AdminSettingsForm(FlaskForm):
@@ -11,3 +12,8 @@ class AdminSettingsForm(FlaskForm):
privacy = TextAreaField(lazy_gettext("Privacy"), validators=[Optional()])
submit = SubmitField(lazy_gettext("Save"))
+
+
+class UpdateUserForm(FlaskForm):
+ roles = MultiCheckboxField(lazy_gettext("Roles"))
+ submit = SubmitField(lazy_gettext("Update user"))
diff --git a/project/services/user.py b/project/services/user.py
index f1e1fb5..9467329 100644
--- a/project/services/user.py
+++ b/project/services/user.py
@@ -6,20 +6,37 @@ def create_user(email, password):
return user_datastore.create_user(email=email, password=hash_password(password))
-def add_roles_to_user(email, role_names):
+def add_roles_to_user(email, roles):
user = find_user_by_email(email)
if user is None:
raise ValueError("User with given email does not exist.")
- for role_name in role_names:
- user_datastore.add_role_to_user(user, role_name)
+ for role in roles:
+ user_datastore.add_role_to_user(user, role)
def add_admin_roles_to_user(email):
add_roles_to_user(email, ["admin", "event_verifier"])
+def remove_roles_from_user(email, roles):
+ user = find_user_by_email(email)
+
+ for role in roles:
+ user_datastore.remove_role_from_user(user, role)
+
+
+def remove_all_roles_from_user(email):
+ user = find_user_by_email(email)
+ remove_roles_from_user(email, user.roles)
+
+
+def set_roles_for_user(email, roles):
+ remove_all_roles_from_user(email)
+ add_roles_to_user(email, roles)
+
+
def upsert_user_role(role_name, role_title, permissions):
role = user_datastore.find_or_create_role(role_name)
role.title = role_title
diff --git a/project/templates/admin/admin.html b/project/templates/admin/admin.html
index 409f0aa..8edb663 100644
--- a/project/templates/admin/admin.html
+++ b/project/templates/admin/admin.html
@@ -19,6 +19,10 @@
{{ _('Admin Units') }}
+
+ {{ _('Users') }}
+
+
{% endblock %}
\ No newline at end of file
diff --git a/project/templates/admin/update_user.html b/project/templates/admin/update_user.html
new file mode 100644
index 0000000..e429795
--- /dev/null
+++ b/project/templates/admin/update_user.html
@@ -0,0 +1,25 @@
+{% extends "layout.html" %}
+{% from "_macros.html" import render_field_with_errors, render_field %}
+{% block title %}
+{{ _('Update user') }}
+{% endblock %}
+{% block content %}
+
+
{{ _('Update user') }}
+
+
+
+{% endblock %}
diff --git a/project/templates/admin/users.html b/project/templates/admin/users.html
new file mode 100644
index 0000000..b33d76c
--- /dev/null
+++ b/project/templates/admin/users.html
@@ -0,0 +1,36 @@
+{% extends "layout.html" %}
+{% from "_macros.html" import render_pagination %}
+{% block title %}
+{{ _('Users') }}
+{% endblock %}
+{% block content %}
+
+
+
+
+
+
+
+ | {{ _('Email') }} |
+ |
+
+
+
+ {% for user in users %}
+
+ | {{ user.email }} |
+ {{ _('Edit') }} |
+
+ {% endfor %}
+
+
+
+
+{{ render_pagination(pagination) }}
+
+{% endblock %}
\ No newline at end of file
diff --git a/project/views/admin.py b/project/views/admin.py
index f5375d1..ff787b0 100644
--- a/project/views/admin.py
+++ b/project/views/admin.py
@@ -1,15 +1,18 @@
from project import app, db
-from project.models import AdminUnit
+from project.models import AdminUnit, User, Role
from flask import render_template, flash, url_for, redirect
from flask_babelex import gettext
from flask_security import roles_required
-from project.forms.admin import AdminSettingsForm
+from project.forms.admin import AdminSettingsForm, UpdateUserForm
from project.services.admin import upsert_settings
+from project.services.user import set_roles_for_user
from project.views.utils import (
flash_errors,
handleSqlError,
+ get_pagination_urls,
)
from sqlalchemy.exc import SQLAlchemyError
+from sqlalchemy.sql import func
@app.route("/admin")
@@ -44,3 +47,38 @@ def admin_settings():
flash_errors(form)
return render_template("admin/settings.html", form=form)
+
+
+@app.route("/admin/users")
+@roles_required("admin")
+def admin_users():
+ users = User.query.order_by(func.lower(User.email)).paginate()
+ return render_template(
+ "admin/users.html", users=users.items, pagination=get_pagination_urls(users)
+ )
+
+
+@app.route("/admin/user//update", methods=("GET", "POST"))
+@roles_required("admin")
+def admin_user_update(id):
+ user = User.query.get_or_404(id)
+
+ form = UpdateUserForm()
+ form.roles.choices = [
+ (c.name, gettext(c.title)) for c in Role.query.order_by(Role.id).all()
+ ]
+
+ if form.validate_on_submit():
+ set_roles_for_user(user.email, form.roles.data)
+
+ try:
+ db.session.commit()
+ flash(gettext("User successfully updated"), "success")
+ return redirect(url_for("admin_users"))
+ except SQLAlchemyError as e:
+ db.session.rollback()
+ flash(handleSqlError(e), "danger")
+ else:
+ form.roles.data = [c.name for c in user.roles]
+
+ return render_template("admin/update_user.html", user=user, form=form)
diff --git a/tests/views/test_admin.py b/tests/views/test_admin.py
index 8d64b3b..952d4ad 100644
--- a/tests/views/test_admin.py
+++ b/tests/views/test_admin.py
@@ -58,3 +58,52 @@ def test_admin_settings(client, seeder, utils, app, mocker, db_error):
assert settings.legal_notice == "Mein Impressum"
assert settings.contact == "Mein Kontakt"
assert settings.privacy == "Mein Datenschutz"
+
+
+def test_admin_users(client, seeder, utils, app):
+ seeder.create_user(admin=True)
+ user = utils.login()
+ seeder.create_admin_unit(user, "Meine Crew")
+ response = client.get("/admin/users")
+ assert b"test@test.de" in response.data
+
+
+@pytest.mark.parametrize("db_error", [True, False])
+def test_admin_user_update(client, seeder, utils, app, mocker, db, db_error):
+ user_id, admin_unit_id = seeder.setup_base(True)
+ other_user_id = seeder.create_user("other@test.de")
+
+ with app.app_context():
+ from project.models import User
+ from project.services.user import set_roles_for_user
+
+ user = User.query.get_or_404(other_user_id)
+ set_roles_for_user(user.email, ["event_verifier"])
+ db.session.commit()
+
+ url = utils.get_url("admin_user_update", id=other_user_id)
+ response = utils.get_ok(url)
+
+ if db_error:
+ utils.mock_db_commit(mocker)
+
+ response = utils.post_form(
+ url,
+ response,
+ {
+ "roles": "admin",
+ },
+ )
+
+ if db_error:
+ utils.assert_response_db_error(response)
+ return
+
+ utils.assert_response_redirect(response, "admin_users")
+
+ with app.app_context():
+ from project.models import User
+
+ user = User.query.get_or_404(other_user_id)
+ assert len(user.roles) == 1
+ assert any(r.name == "admin" for r in user.roles)