diff --git a/mo/rights.py b/mo/rights.py
index 1ab63fc07b9e4a2bd831c1f1567d74d84c9bac15..52ed4f316f086829780929fc6478b09bb17829bb 100644
--- a/mo/rights.py
+++ b/mo/rights.py
@@ -16,6 +16,8 @@ class Right(Enum):
upload_solutions = auto()
upload_feedback = auto()
edit_points = auto()
+ edit_users = auto()
+ edit_orgs = auto()
@dataclass
@@ -33,6 +35,8 @@ roles: List[Role] = [
Right.assign_rights,
Right.edit_place,
Right.manage_contest,
+ Right.edit_users,
+ Right.edit_orgs,
},
),
Role(
@@ -42,6 +46,8 @@ roles: List[Role] = [
Right.assign_rights,
Right.edit_place,
Right.manage_contest,
+ Right.edit_users,
+ Right.edit_orgs,
},
),
Role(
@@ -51,6 +57,7 @@ roles: List[Role] = [
Right.assign_rights,
Right.edit_place,
Right.manage_contest,
+ Right.edit_users,
},
),
Role(
diff --git a/mo/web/org_users.py b/mo/web/org_users.py
index 29b2cc20ebf948973bbc4117ab9ec5c7d6c92433..885b630492ca5e2a3edcf94e43f8d0b66001d8a3 100644
--- a/mo/web/org_users.py
+++ b/mo/web/org_users.py
@@ -1,15 +1,19 @@
-from operator import or_
-from flask import render_template, redirect, url_for, flash, request
+from flask import render_template, g, redirect, url_for, flash, request
from flask_wtf import FlaskForm
import werkzeug.exceptions
import wtforms
+from sqlalchemy import or_
+from sqlalchemy.orm import joinedload
from typing import Optional, List
+from wtforms.validators import Email, Required
+
import mo
import mo.db as db
import mo.rights
import mo.util
+import mo.users
from mo.web import app
@@ -38,6 +42,9 @@ class UsersFilterForm(PagerForm):
@app.route('/org/users/')
def org_users():
sess = db.get_session()
+ rr = mo.rights.Rights(g.user)
+ rr.get_generic()
+ can_edit = rr.have_right(mo.rights.Right.edit_users)
q = sess.query(db.User).filter_by(is_admin=False, is_org=False)
filter = UsersFilterForm(request.args)
@@ -115,7 +122,10 @@ def org_users():
q = q.limit(filter.limit.data)
users = q.all()
- return render_template('org_users.html', users=users, count=count, filter=filter, filter_errors=filter_errors)
+ return render_template(
+ 'org_users.html', users=users, count=count,
+ filter=filter, filter_errors=filter_errors, can_edit=can_edit
+ )
class OrgsFilterForm(PagerForm):
@@ -126,6 +136,9 @@ class OrgsFilterForm(PagerForm):
@app.route('/org/users/orgs/')
def org_users_orgs():
sess = db.get_session()
+ rr = mo.rights.Rights(g.user)
+ rr.get_generic()
+ can_edit = rr.have_right(mo.rights.Right.edit_orgs)
q = sess.query(db.User).filter(or_(db.User.is_admin, db.User.is_org))
filter = OrgsFilterForm(request.args)
@@ -147,20 +160,137 @@ def org_users_orgs():
q = q.limit(filter.limit.data)
users = q.all()
- return render_template('org_users_orgs.html', users=users, count=count, filter=filter, filter_errors=None)
+ return render_template(
+ 'org_users_orgs.html', users=users, count=count,
+ filter=filter, filter_errors=None, can_edit=can_edit,
+ )
@app.route('/org/user/<int:id>/')
def org_user(id: int):
- return render_template('not_implemented.html')
+ sess = db.get_session()
+ user = sess.query(db.User).get(id)
+ if not user:
+ raise werkzeug.exceptions.NotFound()
+
+ rr = mo.rights.Rights(g.user)
+ rr.get_generic()
+ if user.is_admin:
+ can_edit = False
+ elif user.is_org:
+ can_edit = rr.have_right(mo.rights.Right.edit_orgs)
+ else:
+ can_edit = rr.have_right(mo.rights.Right.edit_users)
+
+ participants = sess.query(db.Participant).filter_by(user_id=user.user_id)
+ rounds = sess.query(db.Participation).filter_by(user_id=user.user_id)
+
+ return render_template('org_user.html', user=user, can_edit=can_edit, participants=participants, rounds=rounds)
+
+
+class UserEditForm(FlaskForm):
+ first_name = wtforms.StringField("Jméno", validators=[Required()])
+ last_name = wtforms.StringField("Příjmení", validators=[Required()])
+ note = wtforms.TextAreaField("Poznámka")
+ submit = wtforms.SubmitField("Uložit")
+
+class NewUserForm(UserEditForm):
+ email = wtforms.StringField("E-mail", validators=[Required()])
+ submit = wtforms.SubmitField("Vytvořit")
-@app.route('/org/user/<int:id>/edit')
+
+@app.route('/org/user/<int:id>/edit', methods=("GET", "POST"))
def org_user_edit(id: int):
- return render_template('not_implemented.html')
+ sess = db.get_session()
+ user = mo.users.user_by_uid(id)
+ if not user:
+ raise werkzeug.exceptions.NotFound()
+
+ rr = mo.rights.Rights(g.user)
+ rr.get_generic()
+ if user.is_admin:
+ raise werkzeug.exceptions.Forbidden()
+ elif user.is_org and not rr.have_right(mo.rights.Right.edit_orgs):
+ raise werkzeug.exceptions.Forbidden()
+ elif not rr.have_right(mo.rights.Right.edit_users):
+ raise werkzeug.exceptions.Forbidden()
+
+ form = UserEditForm(obj=user)
+ if form.validate_on_submit():
+ form.populate_obj(user)
+
+ if sess.is_modified(user):
+ changes = db.get_object_changes(user)
+
+ app.logger.info(f"User {id} modified, changes: {changes}")
+ mo.util.log(
+ type=db.LogType.user,
+ what=id,
+ details={'action': 'edit', 'changes': changes},
+ )
+ sess.commit()
+ flash('Změny uživatele uloženy', 'success')
+ else:
+ flash(u'Žádné změny k uložení', 'info')
+
+ return redirect(url_for('org_user', id=id))
+ return render_template('org_user_edit.html', user=user, form=form)
-@app.route('/org/user/new/', defaults={'type': None})
-@app.route('/org/user/new/<type>/')
+
+@app.route('/org/user/new/', defaults={'type': None}, methods=('GET', 'POST'))
+@app.route('/org/user/new/<type>/', methods=('GET', 'POST'))
def org_user_new(type: Optional[str]):
- return render_template('not_implemented.html')
+ sess = db.get_session()
+ rr = mo.rights.Rights(g.user)
+ rr.get_generic()
+
+ if type is not None and type != "org":
+ raise werkzeug.exceptions.BadRequest()
+ if not rr.have_right(mo.rights.Right.edit_users):
+ raise werkzeug.exceptions.Forbidden()
+ if type == 'org' and not rr.have_right(mo.rights.Right.edit_orgs):
+ raise werkzeug.exceptions.Forbidden()
+
+ form = NewUserForm()
+ if form.validate_on_submit():
+ check = True
+
+ if mo.users.user_by_email(form.email.data) is not None:
+ flash('Účet s daným emailem již existuje', 'danger')
+ check = False
+
+ if check:
+ new_user = db.User()
+ form.populate_obj(new_user)
+ new_user.is_org = (type == 'org')
+ sess.add(new_user)
+ sess.flush()
+
+ app.logger.info(f"New user created: {db.row2dict(new_user)}")
+ mo.util.log(
+ type=db.LogType.user,
+ what=new_user.user_id,
+ details={'action': 'new', 'user': db.row2dict(new_user)},
+ )
+
+ sess.commit()
+ flash('Nový uživatel vytvořen', 'success')
+
+ # Send password (re)set link
+ token = mo.users.ask_reset_password(new_user)
+ link = url_for('reset', token=token, _external=True)
+ db.get_session().commit()
+
+ try:
+ mo.util.send_password_reset_email(new_user, link)
+ flash('Email s odkazem pro nastavení hesla odeslán na {}'.format(new_user.email), 'success')
+ except RuntimeError as e:
+ app.logger.error('Login: problém při posílání emailu: {}'.format(e))
+ flash('Problém při odesílání emailu s odkazem pro nastavení hesla', 'danger')
+
+ return redirect(url_for('org_user', id=new_user.user_id))
+
+ return render_template('org_user_new.html', form=form)
+
diff --git a/templates/org_user.html b/templates/org_user.html
new file mode 100644
index 0000000000000000000000000000000000000000..fa4d8892961a50a5d0159ac2796ae47894889b23
--- /dev/null
+++ b/templates/org_user.html
@@ -0,0 +1,88 @@
+{% extends "base.html" %}
+{% block body %}
+<h2>{% if user.is_org %}Organizátor:{% elif user.is_admin %}Správce:{% else %}Soutěžící:{% endif %} {{ user.first_name }} {{ user.last_name }}</h2>
+
+<table class=data>
+<tr><td>Jméno:</td><td>{{ user.first_name }}</td></tr>
+<tr><td>Příjmení:</td><td>{{ user.last_name }}</td></tr>
+<tr><td>E-mail:</td><td><a href="mailto:{{ user.email }}">{{ user.email }}</a></td></tr>
+{% if user.is_admin %}<tr><td>Administrátor:</td><td>ano</td></tr>{% endif %}
+{% if user.is_org %}<tr><td>Organizátor:</td><td>ano</td></tr>{% endif %}
+<tr><td>Poznámka:</td><td style="white-space: pre;">{{ user.note }}</td></tr>
+</table>
+
+{% if can_edit %}
+<div class="btn-group" role="group" style="margin: 10px 0px;">
+ <a class="btn btn-primary" href="{{ url_for('org_user_edit', id=user.user_id) }}">Editovat</a>
+</div>
+{% endif %}
+
+{% if user.is_org or user.is_admin %}
+<h3>Role</h3>
+
+<table class="table">
+ <thead>
+ <tr>
+ <th>Role</th><th>Ročník</th><th>Kategorie</th><th>Kolo</th><th>Oblast</th>
+ </tr>
+ </thead>
+{% for role in user.roles %}
+ <tr>
+ <td>{{ role.role.name }}</td>
+ <td>{{ role.year or '*' }}</td>
+ <td>{{ role.category or '*' }}</td>
+ <td>{{ role.seq or '*' }}</td>
+ <td>{{ role.place.type_name() + ": " + role.place.name or '*' }}</td>
+ </tr>
+{% endfor %}
+</table>
+{% endif %}
+
+<h3>Registrace v ročnících</h3>
+{% if participants.count() %}
+<table class="table">
+ <thead>
+ <tr>
+ <th>Ročník</th><th>Škola</th><th>Třída</th><th>Rok narození</th>
+ </tr>
+ </thead>
+{% for participant in participants %}
+ <tr>
+ <td>{{ participant.year }}</td>
+ <td><a href="{{ url_for('org_place', id=participant.school) }}">{{ participant.school_place.name }}</a></td>
+ <td>{{ participant.grade }}</td>
+ <td>{{ participant.birth_year }}</td>
+ </tr>
+{% endfor %}
+</table>
+{% else %}
+<p>Žádná registrace v ročníku</p>
+{% endif %}
+
+<h3>Účast v kolech</h3>
+{% if rounds.count() %}
+<table class="table">
+ <thead>
+ <tr>
+ <th>Ročník</th><th>Kategorie</th><th>Kolo</th><th>Místo</th><th>Stav účasti</th>
+ </tr>
+ </thead>
+{% for round in rounds %}
+ <tr>
+ <td>{{ round.contest.round.year }}</td>
+ <td>{{ round.contest.round.category }}</td>
+ <td>{{ round.contest.round.seq }}</td>
+ <td><a href="{{ url_for('org_place', id=round.contest.place_id) }}">{{ round.contest.place.name }}</a>
+ {% if round.place_id != round.contest.place_id %}
+ <br>(ale soutěží v <a href="{{ url_for('org_place', id=round.place_id) }}">{{ round.place.name }}</a>)
+ {% endif %}
+ </td>
+ <td>{{ round.state.name }}</td>
+ </tr>
+{% endfor %}
+</table>
+{% else %}
+<p>Žádná účast v kole</p>
+{% endif %}
+
+{% endblock %}
diff --git a/templates/org_user_edit.html b/templates/org_user_edit.html
new file mode 100644
index 0000000000000000000000000000000000000000..147c2e8cbd08732b16e065d66eb22aa80b9ef5a7
--- /dev/null
+++ b/templates/org_user_edit.html
@@ -0,0 +1,25 @@
+{% extends "base.html" %}
+{% import "bootstrap/wtf.html" as wtf %}
+{% block body %}
+<h2>{% if user.is_org %}Org:{% elif user.is_admin %}Admin:{% else %}Soutěžící{% endif %} {{ user.first_name }} {{ user.last_name }}</h2>
+
+<table class=data>
+<tr><td>Jméno:</td><td>{{ user.first_name }}</td></tr>
+<tr><td>Příjmení:</td><td>{{ user.last_name }}</td></tr>
+<tr><td>E-mail:</td><td><a href="mailto:{{ user.email }}">{{ user.email }}</a></td></tr>
+{% if user.is_admin %}<tr><td>Administrátor:</td><td>ano</td></tr>{% endif %}
+{% if user.is_org %}<tr><td>Organizátor:</td><td>ano</td></tr>{% endif %}
+<tr><td>Poznámka:</td><td style="white-space: pre;">{{ user.note }}</td></tr>
+</table>
+
+<a href='{{ url_for('org_user', id=user.user_id) }}'>Zpět na detail</a>
+
+<hr>
+
+<h3>Editace údajů</h3>
+
+<p>Email nelze editovat. Pro jeho změnu kontaktujte někoho ze správců.</p>
+
+{{ wtf.quick_form(form, form_type='horizontal') }}
+
+{% endblock %}
diff --git a/templates/org_user_new.html b/templates/org_user_new.html
new file mode 100644
index 0000000000000000000000000000000000000000..d3529c2b05a6c072b62d9357e7e557abe5bcb235
--- /dev/null
+++ b/templates/org_user_new.html
@@ -0,0 +1,10 @@
+{% extends "base.html" %}
+{% import "bootstrap/wtf.html" as wtf %}
+{% block body %}
+<h2>Nový {% if is_org %}organizátor{% else %}soutěžící{% endif %}</h2>
+
+<p>Na zadaný email dorazí email pro nastavení hesla. Email po vytvoření účtu již nelze měnit.</p>
+
+{{ wtf.quick_form(form, form_type='horizontal') }}
+
+{% endblock %}
diff --git a/templates/org_users_orgs.html b/templates/org_users_orgs.html
index a7e54c460c0fc1cf96721af3079e641328847209..97f2cf88985295fab127b2f12168024770322f7c 100644
--- a/templates/org_users_orgs.html
+++ b/templates/org_users_orgs.html
@@ -38,16 +38,19 @@
<table class="table">
<thead>
<tr>
- <th>Jméno</th><th>Příjmení</th><th>E-mail</th><th>Akce</th>
+ <th>Jméno</th><th>Příjmení</th><th>E-mail</th><th>Nejvyšší role</th><th>Akce</th>
</tr>
</thead>
{% for user in users %}
<tr>
<td>{{ user.first_name }}</td><td>{{ user.last_name }}</td>
<td><a href="mailto:{{ user.email }}">{{ user.email }}</a></td>
+ <td>{% if user.is_admin %}administrátor{% else %}
+ {%- if user.roles|count > 0 %}{{user.roles[0].role.name}}{% else %}<i>žádná role</i>{% endif -%}
+ {% endif %}</td>
<td class='btn-group'>
<a class="btn btn-xs btn-default" href="{{ url_for('org_user', id=user.user_id) }}">Detail</a>
- <a class="btn btn-xs btn-default" href="{{ url_for('org_user_edit', id=user.user_id) }}">Edit</a>
+ {% if can_edit and not user.is_admin %}<a class="btn btn-xs btn-default" href="{{ url_for('org_user_edit', id=user.user_id) }}">Edit</a>{% endif %}
</td>
</tr>
{% endfor %}