Skip to content
Snippets Groups Projects
Commit 5d80f7b3 authored by Jiří Setnička's avatar Jiří Setnička
Browse files

Detail, přidávání a editace uživatelů

parent 3e8de9dd
No related branches found
No related tags found
No related merge requests found
This commit is part of merge request !5. Comments created here will be created in the context of that merge request.
...@@ -16,6 +16,8 @@ class Right(Enum): ...@@ -16,6 +16,8 @@ class Right(Enum):
upload_solutions = auto() upload_solutions = auto()
upload_feedback = auto() upload_feedback = auto()
edit_points = auto() edit_points = auto()
edit_users = auto()
edit_orgs = auto()
@dataclass @dataclass
...@@ -33,6 +35,8 @@ roles: List[Role] = [ ...@@ -33,6 +35,8 @@ roles: List[Role] = [
Right.assign_rights, Right.assign_rights,
Right.edit_place, Right.edit_place,
Right.manage_contest, Right.manage_contest,
Right.edit_users,
Right.edit_orgs,
}, },
), ),
Role( Role(
...@@ -42,6 +46,8 @@ roles: List[Role] = [ ...@@ -42,6 +46,8 @@ roles: List[Role] = [
Right.assign_rights, Right.assign_rights,
Right.edit_place, Right.edit_place,
Right.manage_contest, Right.manage_contest,
Right.edit_users,
Right.edit_orgs,
}, },
), ),
Role( Role(
...@@ -51,6 +57,7 @@ roles: List[Role] = [ ...@@ -51,6 +57,7 @@ roles: List[Role] = [
Right.assign_rights, Right.assign_rights,
Right.edit_place, Right.edit_place,
Right.manage_contest, Right.manage_contest,
Right.edit_users,
}, },
), ),
Role( Role(
......
from operator import or_ from flask import render_template, g, redirect, url_for, flash, request
from flask import render_template, redirect, url_for, flash, request
from flask_wtf import FlaskForm from flask_wtf import FlaskForm
import werkzeug.exceptions import werkzeug.exceptions
import wtforms import wtforms
from sqlalchemy import or_
from sqlalchemy.orm import joinedload
from typing import Optional, List from typing import Optional, List
from wtforms.validators import Email, Required
import mo import mo
import mo.db as db import mo.db as db
import mo.rights import mo.rights
import mo.util import mo.util
import mo.users
from mo.web import app from mo.web import app
...@@ -38,6 +42,9 @@ class UsersFilterForm(PagerForm): ...@@ -38,6 +42,9 @@ class UsersFilterForm(PagerForm):
@app.route('/org/users/') @app.route('/org/users/')
def org_users(): def org_users():
sess = db.get_session() 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) q = sess.query(db.User).filter_by(is_admin=False, is_org=False)
filter = UsersFilterForm(request.args) filter = UsersFilterForm(request.args)
...@@ -115,7 +122,10 @@ def org_users(): ...@@ -115,7 +122,10 @@ def org_users():
q = q.limit(filter.limit.data) q = q.limit(filter.limit.data)
users = q.all() 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): class OrgsFilterForm(PagerForm):
...@@ -126,6 +136,9 @@ class OrgsFilterForm(PagerForm): ...@@ -126,6 +136,9 @@ class OrgsFilterForm(PagerForm):
@app.route('/org/users/orgs/') @app.route('/org/users/orgs/')
def org_users_orgs(): def org_users_orgs():
sess = db.get_session() 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)) q = sess.query(db.User).filter(or_(db.User.is_admin, db.User.is_org))
filter = OrgsFilterForm(request.args) filter = OrgsFilterForm(request.args)
...@@ -147,20 +160,137 @@ def org_users_orgs(): ...@@ -147,20 +160,137 @@ def org_users_orgs():
q = q.limit(filter.limit.data) q = q.limit(filter.limit.data)
users = q.all() 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>/') @app.route('/org/user/<int:id>/')
def org_user(id: int): 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): 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()
  • Tohle tu vidím podruhé, nebylo by to lepší vytáhnout do funkce testující právo na editaci daného uživatele?

    Stejně tak hledání uživatele podle ID se opakuje ... zkus se podívat, jak jsem to vyřešil pro contesty.

  • Author Maintainer

    Funkci ne testování práv pro editaci jsem vytvořil v rights.Right.

    Zabalení vytahování uživatele podle ID jsem nakonec nechal tak, jak je - po rozdělení práv na čtyři ({add,edit}_{users,orgs}) by moc neušetřila.

  • Please register or sign in to reply
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]): 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')
  • V e-mailu by bylo dobré říci, že je to nový účet, aby se uživatel nedivil :)

    Něco podobného asi bude potřeba i u účtů založených automaticky importem...

  • Jiří Setnička @setnicka

    created #1 (closed) to continue this discussion

    ·

    created #1 (closed) to continue this discussion

    Toggle commit list
  • Please register or sign in to reply
return redirect(url_for('org_user', id=new_user.user_id))
return render_template('org_user_new.html', form=form)
{% 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 %}
{% 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 %}
{% 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 %}
...@@ -38,16 +38,19 @@ ...@@ -38,16 +38,19 @@
<table class="table"> <table class="table">
<thead> <thead>
<tr> <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>
  • Co znamená nejvyšší? My máme na rolích nějaké lineární uspořádání?

  • Jiří Setnička @setnicka

    changed this line in version 4 of the diff

    ·

    changed this line in version 4 of the diff

    Toggle commit list
  • Author Maintainer

    Uspořádání chápu jako garant > garant_kraj > garant_okres > dozor > opravovatel a záměrem bylo tady zobrazovat nejvyšší roli podle tohoto uspořádání pro získání představy, co to je za orga.

    Ale možná bude lepší tam ve zkrácené formě napsat role (ve tvaru 70-P-garant_okres). Zkusil jsem to předělat, jak ti to přijde teď?

  • Ta šipečka mi teď přijde hodně matoucí. Co kdybychom tam slovy vyjmenovali názvy všech rolí, které dotyčný má?

  • Author Maintainer

    Šipečka byla copy&paste chyba :innocent:, v !6 (merged) je pokus o trochu jiný přístup.

  • Please register or sign in to reply
</tr> </tr>
</thead> </thead>
{% for user in users %} {% for user in users %}
<tr> <tr>
<td>{{ user.first_name }}</td><td>{{ user.last_name }}</td> <td>{{ user.first_name }}</td><td>{{ user.last_name }}</td>
<td><a href="mailto:{{ user.email }}">{{ user.email }}</a></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'> <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', 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> </td>
</tr> </tr>
{% endfor %} {% endfor %}
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment