diff --git a/mo/web/org_contest.py b/mo/web/org_contest.py index fb69c52daaa1e440b2c48f3301bdd7f8700364ac..37d88c21175eabbbd90debd11b899b93293b5719 100644 --- a/mo/web/org_contest.py +++ b/mo/web/org_contest.py @@ -9,11 +9,13 @@ from sqlalchemy.orm import joinedload, aliased from sqlalchemy.orm.query import Query from sqlalchemy.dialects.postgresql import insert as pgsql_insert from typing import Any, List, Tuple, Optional, Sequence, Dict +import urllib.parse import werkzeug.exceptions import wtforms import mo from mo.csv import FileFormat +import mo.config as config import mo.db as db from mo.imports import ImportType, create_import import mo.jobs.submit @@ -435,9 +437,11 @@ def org_contest_import(id: int): @app.route('/org/contest/c/<int:id>/ucastnici', methods=('GET', 'POST')) @app.route('/org/contest/c/<int:id>/site/<int:site_id>/ucastnici', methods=('GET', 'POST')) +@app.route('/org/contest/c/<int:id>/ucastnici/emails', endpoint="org_contest_list_emails") +@app.route('/org/contest/c/<int:id>/site/<int:site_id>/ucastnici/emails', endpoint="org_contest_list_emails") def org_contest_list(id: int, site_id: Optional[int] = None): contest, master_contest, site, rr = get_contest_site_rr(id, site_id, Right.view_contestants) - can_edit = rr.have_right(Right.manage_contest) + can_edit = rr.have_right(Right.manage_contest) and request.endpoint != 'org_contest_list_emails' format = request.args.get('format', "") filter = ParticipantsFilterForm(request.args) @@ -458,14 +462,27 @@ def org_contest_list(id: int, site_id: Optional[int] = None): return redirect(request.url) if format == "": - # (count, query) = filter.apply_limits(query, pagesize=50) - count = db.get_count(query) + table = None + emails = None + mailto_link = None + if request.endpoint == 'org_contest_list_emails': + users = [pion.user for (pion, _, _) in query.all()] + emails = [f'{u.first_name} {u.last_name} <{u.email}>' for u in users] + mailto_link = ( + 'mailto:' + urllib.parse.quote(config.MAIL_CONTACT, safe='@') + + '?subject=' + urllib.parse.quote('[OSMO] Zpráva pro účastníky') + + '&bcc=' + ','.join([urllib.parse.quote(email, safe='@') for email in emails]) + ) + count = len(users) + else: + # (count, query) = filter.apply_limits(query, pagesize=50) + count = db.get_count(query) + table = make_contestant_table(query, add_checkbox=can_edit) - table = make_contestant_table(query, add_checkbox=can_edit) return render_template( 'org_contest_list.html', contest=contest, site=site, - table=table, + table=table, emails=emails, mailto_link=mailto_link, filter=filter, count=count, action_form=action_form, ) else: diff --git a/mo/web/org_round.py b/mo/web/org_round.py index 766193bef61cd865bc5c5ce7413758181e1df9ab..2853dbc82330fdeb78dbbd0b0703fa9e97c3fdef 100644 --- a/mo/web/org_round.py +++ b/mo/web/org_round.py @@ -5,12 +5,14 @@ from sqlalchemy import func from sqlalchemy.orm import joinedload from sqlalchemy.sql.functions import coalesce from typing import Optional, Tuple +import urllib.parse import werkzeug.exceptions import wtforms from wtforms import validators, ValidationError from wtforms.fields.html5 import IntegerField import mo +import mo.config as config import mo.db as db import mo.imports from mo.rights import Right, RoundRights @@ -338,8 +340,10 @@ def org_round_task_batch_points(round_id: int, task_id: int): @app.route('/org/contest/r/<int:id>/list', methods=('GET', 'POST')) +@app.route('/org/contest/r/<int:id>/list/emails', endpoint="org_round_list_emails") def org_round_list(id: int): - round, master_round, r = get_round_rr(id, Right.view_contestants, True) + round, master_round, rr = get_round_rr(id, Right.view_contestants, True) + can_edit = rr.have_right(Right.manage_round) and request.endpoint != 'org_round_list_emails' format = request.args.get('format', "") filter = ParticipantsFilterForm(request.args) @@ -352,20 +356,36 @@ def org_round_list(id: int): participation_state=filter.f_participation_state, ) - action_form = ParticipantsActionForm() - if action_form.do_action(round=master_round, query=query): - # Action happened, redirect - return redirect(request.url) + action_form = None + if can_edit: + action_form = ParticipantsActionForm() + if action_form.do_action(round=master_round, query=query): + # Action happened, redirect + return redirect(request.url) if format == "": - (count, query) = filter.apply_limits(query, pagesize=50) - # count = db.get_count(query) + table = None + emails = None + mailto_link = None + if request.endpoint == 'org_round_list_emails': + users = [pion.user for (pion, _, _) in query.all()] + emails = [f'{u.first_name} {u.last_name} <{u.email}>' for u in users] + emails.append("můj@supeč---email.cz") + mailto_link = ( + 'mailto:' + urllib.parse.quote(config.MAIL_CONTACT, safe='@') + + '?subject=' + urllib.parse.quote('[OSMO] Zpráva pro účastníky') + + '&bcc=' + ','.join([urllib.parse.quote(email, safe='@') for email in emails]) + ) + count = len(users) + else: + (count, query) = filter.apply_limits(query, pagesize=50) + # count = db.get_count(query) + table = make_contestant_table(query, add_contest_column=True, add_checkbox=True) - table = make_contestant_table(query, add_contest_column=True, add_checkbox=True) return render_template( 'org_round_list.html', round=round, - table=table, + table=table, emails=emails, mailto_link=mailto_link, filter=filter, count=count, action_form=action_form, ) else: diff --git a/mo/web/templates/org_contest_list.html b/mo/web/templates/org_contest_list.html index 3f817038d356c560a25fe8d42ccd1100057b4eff..a2f85591a86dc80a8b3a45a2c5dde6becd4d48a1 100644 --- a/mo/web/templates/org_contest_list.html +++ b/mo/web/templates/org_contest_list.html @@ -8,6 +8,8 @@ Seznam účastníků {% if site %}soutěžního místa {{ site.name }}{% else %} {% block breadcrumbs %} {{ contest_breadcrumbs(round=round, contest=contest, site=site, action="Seznam účastníků") }} {% endblock %} +{% set id = contest.contest_id %} +{% set site_id = site.place_id if site else None %} {% block body %} <div class="form-frame"> @@ -20,8 +22,10 @@ Seznam účastníků {% if site %}soutěžního místa {{ site.name }}{% else %} {{ wtf.form_field(filter.participation_state) }} <div class="btn-group"> {{ wtf.form_field(filter.submit, class='btn btn-primary') }} + {% if table %} <button class="btn btn-default" name="format" value="cs_csv" title="Stáhnout celý výsledek v CSV">↓ CSV</button> <button class="btn btn-default" name="format" value="tsv" title="Stáhnout celý výsledek v TSV">↓ TSV</button> + {% endif %} </div> {% if not site %} </div> diff --git a/mo/web/templates/org_round_list.html b/mo/web/templates/org_round_list.html index 417006a92f9cc1c543874a4bc3d150c5c0bce603..544a9eb5ee5c2a5999829a1a63ca2de817979817 100644 --- a/mo/web/templates/org_round_list.html +++ b/mo/web/templates/org_round_list.html @@ -5,6 +5,7 @@ {% block breadcrumbs %} {{ contest_breadcrumbs(round=round, action="Seznam účastníků") }} {% endblock %} +{% set id = round.round_id %} {% block body %} <div class="form-frame"> @@ -18,10 +19,12 @@ <div class="form-row" style="margin-top: 5px;"> <div class="btn-group"> {{ wtf.form_field(filter.submit, class='btn btn-primary') }} + {% if table %} <button class="btn btn-default" name="format" value="cs_csv" title="Stáhnout celý výsledek v CSV">↓ CSV</button> <button class="btn btn-default" name="format" value="tsv" title="Stáhnout celý výsledek v TSV">↓ TSV</button> + {% endif %} </div> - + {% if table %} <div style="float: right"> Stránka {{ filter.offset.data // filter.limit.data + 1}} z {{ (count / filter.limit.data)|round(0, 'ceil')|int }}: <div class="btn-group"> @@ -41,6 +44,9 @@ </div> {% set max = filter.offset.data + filter.limit.data if filter.offset.data + filter.limit.data < count else count %} Zobrazuji záznamy <b>{{filter.offset.data + 1}}</b> až <b>{{ max }}</b> z <b>{{count}} nalezených účastníků</b>. + {% else %} + Celkem {{count}} nalezených účastníků. + {% endif %} </div> </form> </div> diff --git a/mo/web/templates/parts/org_participants_table_actions.html b/mo/web/templates/parts/org_participants_table_actions.html index dc98b92903e4d0cd2978b840a025a42e22e83d39..d06c8922675b976e5076fb8bec759e1709624d7d 100644 --- a/mo/web/templates/parts/org_participants_table_actions.html +++ b/mo/web/templates/parts/org_participants_table_actions.html @@ -1,7 +1,18 @@ {% if action_form %} <form action="" method="POST" class="form form-horizontal" role="form"> +{% endif %} + +{% if table %} {{ table.to_html() }} + <a class="btn btn-primary pull-right" + title="Zobrazí emailové adresy ve snadno zkopírovatelném formátu" + href="{{ url_for('org_contest_list_emails', id=id, site_id=site_id, **request.args) if contest else url_for('org_round_list_emails', id=id, **request.args) }}"> + Vypsat emailové adresy + </a> +{% endif %} + +{% if action_form %} {{ action_form.csrf_token }} <h3>Provést akci</h3> <div class="form-frame"> @@ -44,9 +55,28 @@ </div> </div> </form> -{% else %} -{{ table.to_html() }} -<p> -<i>Nemáte právo k editaci účastníků v této oblasti.</i> -</p> +{% elif table %} +<p><i>Nemáte právo k editaci účastníků v této oblasti.</i></p> +{% elif emails %} +<a class="btn btn-default pull-right" style="margin-top: 15px;" + href="{{ url_for('org_contest_list', id=id, site_id=site_id, **request.args) if contest else url_for('org_round_list', id=id, **request.args) }}"> + Zpět na tabulku účastníků +</a> + +<h3>Emailové adresy</h3> +<div class="form-frame"> +<p>Pokud máte emailového klienta, který umí <code>mailto:</code> odkazy, tak vám následující tlačítko předvyplní nový email: +<a class="btn btn-primary" href="{{ mailto_link }}">Vytvořit email ({{ count|inflected("adresát", "adresáti", "adresátů") }})</a> + +<p>Emailové adresy si také můžete zkopírovat z následujícího pole. Prosím posílejte jako <b>skrytou kopii</b>, ať účastníci nevidí navzájem své emaily.</p> +<code><textarea id="emails-textarea" class="form-control" readonly style="resize: none;" onclick="this.focus(); this.select();"> +{% for email in emails %} +{{ email|escape }} +{% endfor %} +</textarea></code> +</div> +<script type="text/javascript"> +document.getElementById('emails-textarea').style.height = (document.getElementById('emails-textarea').scrollHeight + 5) + "px"; +</script> + {% endif %}