Skip to content
GitLab
Explore
Sign in
Primary navigation
Search or go to…
Project
Odevzdávací Systém MO
Manage
Activity
Members
Labels
Plan
Issues
Issue boards
Milestones
Code
Merge requests
Repository
Branches
Commits
Tags
Repository graph
Compare revisions
Deploy
Model registry
Analyze
Model experiments
Help
Help
Support
GitLab documentation
Compare GitLab plans
GitLab community forum
Contribute to GitLab
Provide feedback
Keyboard shortcuts
?
Snippets
Groups
Projects
Show more breadcrumbs
Martin Mareš
Odevzdávací Systém MO
Commits
754adc40
Commit
754adc40
authored
3 years ago
by
Martin Mareš
Browse files
Options
Downloads
Patches
Plain Diff
Hierarchie: Kola a statistiky
parent
081abf58
Branches
Branches containing commit
No related tags found
1 merge request
!95
Reforma orgovského rozhraní ke kolům a soutěžím
Changes
4
Show whitespace changes
Inline
Side-by-side
Showing
4 changed files
mo/db.py
+1
-0
1 addition, 0 deletions
mo/db.py
mo/web/org_contest.py
+8
-0
8 additions, 0 deletions
mo/web/org_contest.py
mo/web/org_round.py
+93
-35
93 additions, 35 deletions
mo/web/org_round.py
mo/web/templates/org_round.html
+60
-32
60 additions, 32 deletions
mo/web/templates/org_round.html
with
162 additions
and
67 deletions
mo/db.py
+
1
−
0
View file @
754adc40
...
@@ -718,6 +718,7 @@ class RegionContestStat(Base):
...
@@ -718,6 +718,7 @@ class RegionContestStat(Base):
round_id
=
Column
(
Integer
,
ForeignKey
(
'
rounds.round_id
'
),
primary_key
=
True
)
round_id
=
Column
(
Integer
,
ForeignKey
(
'
rounds.round_id
'
),
primary_key
=
True
)
region
=
Column
(
Integer
,
ForeignKey
(
'
places.place_id
'
),
primary_key
=
True
)
region
=
Column
(
Integer
,
ForeignKey
(
'
places.place_id
'
),
primary_key
=
True
)
state
=
Column
(
Enum
(
RoundState
,
name
=
'
round_state
'
),
primary_key
=
True
)
count
=
Column
(
Integer
,
nullable
=
False
)
count
=
Column
(
Integer
,
nullable
=
False
)
round
=
relationship
(
'
Round
'
)
round
=
relationship
(
'
Round
'
)
...
...
This diff is collapsed.
Click to expand it.
mo/web/org_contest.py
+
8
−
0
View file @
754adc40
...
@@ -93,6 +93,14 @@ class Context:
...
@@ -93,6 +93,14 @@ class Context:
for
p
in
parents
[
1
:]:
for
p
in
parents
[
1
:]:
elements
.
append
((
url_for
(
'
org_round
'
,
round_id
=
self
.
round_id
,
hier_id
=
p
.
place_id
),
p
.
name
or
'
???
'
))
elements
.
append
((
url_for
(
'
org_round
'
,
round_id
=
self
.
round_id
,
hier_id
=
p
.
place_id
),
p
.
name
or
'
???
'
))
if
self
.
contest
:
if
self
.
contest
:
if
self
.
round
.
level
>=
2
:
parents
=
g
.
gatekeeper
.
get_parents
(
self
.
contest
.
place
)
parents
=
sorted
(
parents
,
key
=
lambda
p
:
p
.
level
)
for
i
in
range
(
1
,
len
(
parents
)
-
1
):
p
=
parents
[
i
]
if
p
.
level
>=
3
:
break
elements
.
append
((
url_for
(
'
org_round
'
,
round_id
=
self
.
round_id
,
hier_id
=
p
.
place_id
),
db
.
Place
.
get_code
(
p
)))
elements
.
append
((
url_for
(
'
org_contest
'
,
ct_id
=
self
.
ct_id
),
self
.
contest
.
place
.
name
or
'
???
'
))
elements
.
append
((
url_for
(
'
org_contest
'
,
ct_id
=
self
.
ct_id
),
self
.
contest
.
place
.
name
or
'
???
'
))
if
self
.
site
:
if
self
.
site
:
elements
.
append
((
url_for
(
'
org_contest
'
,
ct_id
=
self
.
ct_id
,
site_id
=
self
.
site_id
),
f
"
soutěžní místo
{
self
.
site
.
name
}
"
))
elements
.
append
((
url_for
(
'
org_contest
'
,
ct_id
=
self
.
ct_id
,
site_id
=
self
.
site_id
),
f
"
soutěžní místo
{
self
.
site
.
name
}
"
))
...
...
This diff is collapsed.
Click to expand it.
mo/web/org_round.py
+
93
−
35
View file @
754adc40
from
dataclasses
import
dataclass
,
field
import
decimal
import
decimal
from
flask
import
render_template
,
g
,
redirect
,
flash
,
request
from
flask
import
render_template
,
g
,
redirect
,
flash
,
request
import
locale
import
locale
...
@@ -8,9 +9,9 @@ from bleach.sanitizer import ALLOWED_TAGS
...
@@ -8,9 +9,9 @@ from bleach.sanitizer import ALLOWED_TAGS
import
markdown
import
markdown
import
os
import
os
from
sqlalchemy
import
func
from
sqlalchemy
import
func
from
sqlalchemy.orm
import
joinedload
from
sqlalchemy.orm
import
joinedload
,
aliased
from
sqlalchemy.sql.functions
import
coalesce
from
sqlalchemy.sql.functions
import
coalesce
from
typing
import
Optional
from
typing
import
Optional
,
List
,
Dict
,
Tuple
,
Set
import
werkzeug.exceptions
import
werkzeug.exceptions
import
wtforms
import
wtforms
from
wtforms
import
validators
,
ValidationError
from
wtforms
import
validators
,
ValidationError
...
@@ -157,44 +158,95 @@ def create_subcontests(master_round: db.Round, master_contest: db.Contest):
...
@@ -157,44 +158,95 @@ def create_subcontests(master_round: db.Round, master_contest: db.Contest):
app
.
logger
.
info
(
f
"
Podsoutěž #
{
subcontest
.
contest_id
}
založena:
{
db
.
row2dict
(
subcontest
)
}
"
)
app
.
logger
.
info
(
f
"
Podsoutěž #
{
subcontest
.
contest_id
}
založena:
{
db
.
row2dict
(
subcontest
)
}
"
)
@app.route
(
'
/org/contest/r/<int:round_id>/
'
,
methods
=
(
'
GET
'
,
'
POST
'
))
@dataclass
def
org_round
(
round_id
:
int
):
class
ContestStat
:
region
:
db
.
Place
contest
:
Optional
[
db
.
Contest
]
=
None
num_contests
:
int
=
0
contest_states
:
Set
[
db
.
RoundState
]
=
field
(
default_factory
=
set
)
num_active_pants
:
int
=
0
num_unconfirmed_pants
:
int
=
0
def
region_stats
(
round
:
db
.
Round
,
region
:
db
.
Place
)
->
List
[
ContestStat
]:
stats
:
Dict
[
int
,
ContestStat
]
=
{}
sess
=
db
.
get_session
()
sess
=
db
.
get_session
()
ctx
=
get_context
(
round_id
=
round_id
)
round
=
ctx
.
round
rights
=
ctx
.
rights
participants_count
=
sess
.
query
(
if
region
.
level
>
round
.
level
:
db
.
Participation
.
contest_id
,
return
[]
func
.
count
(
db
.
Participation
.
user_id
).
label
(
'
count
'
)
).
group_by
(
db
.
Participation
.
contest_id
).
subquery
()
if
(
region
.
level
>=
round
.
level
-
1
or
region
.
level
==
2
and
round
.
level
==
4
):
# Účastníci jsou jen pod master contesty
# List individual contests
contests_counts
=
(
sess
.
query
(
q
=
sess
.
query
(
db
.
Contest
).
filter_by
(
round
=
round
)
db
.
Contest
,
q
=
db
.
filter_place_nth_parent
(
q
,
db
.
Contest
.
place_id
,
round
.
level
-
region
.
level
,
region
.
place_id
)
coalesce
(
participants_count
.
c
.
count
,
0
)
q
=
q
.
options
(
joinedload
(
db
.
Contest
.
place
))
).
outerjoin
(
participants_count
,
db
.
Contest
.
master_contest_id
==
participants_count
.
c
.
contest_id
)
for
c
in
q
.
all
():
.
filter
(
db
.
Contest
.
round
==
round
)
s
=
ContestStat
(
region
=
c
.
place
,
contest
=
c
,
num_contests
=
1
)
.
options
(
joinedload
(
db
.
Contest
.
place
))
stats
[
c
.
place
.
place_id
]
=
s
have_contests
=
True
else
:
# List sub-regions
regs
=
sess
.
query
(
db
.
Place
).
filter
(
db
.
Place
.
parent_place
==
region
).
all
()
for
r
in
regs
:
s
=
ContestStat
(
region
=
r
)
stats
[
r
.
place_id
]
=
s
have_contests
=
False
region_ids
=
[
s
.
region
.
place_id
for
s
in
stats
.
values
()]
if
not
have_contests
:
rcs
=
(
sess
.
query
(
db
.
RegionContestStat
)
.
filter_by
(
round
=
round
)
.
filter
(
db
.
RegionContestStat
.
region
.
in_
(
region_ids
))
.
all
())
.
all
())
for
r
in
rcs
:
stats
[
r
.
region
].
num_contests
+=
r
.
count
stats
[
r
.
region
].
contest_states
.
add
(
r
.
state
)
contests_counts
.
sort
(
key
=
lambda
c
:
locale
.
strxfrm
(
c
[
0
].
place
.
name
))
rs
=
(
sess
.
query
(
db
.
RegionParticipantStat
)
.
filter_by
(
round_id
=
round
.
master_round_id
)
sol_counts_q
=
(
.
filter
(
db
.
RegionParticipantStat
.
region
.
in_
(
region_ids
))
sess
.
query
(
db
.
Solution
.
task_id
,
func
.
count
(
db
.
Solution
.
task_id
))
.
all
())
.
filter
(
db
.
Solution
.
task_id
.
in_
(
for
r
in
rs
:
sess
.
query
(
db
.
Task
.
task_id
).
filter_by
(
round
=
round
)
if
r
.
state
==
db
.
PartState
.
active
:
))
stats
[
r
.
region
].
num_active_pants
=
r
.
count
elif
r
.
state
==
db
.
PartState
.
registered
:
stats
[
r
.
region
].
num_unconfirmed_pants
=
r
.
count
out
=
list
(
stats
.
values
())
out
.
sort
(
key
=
lambda
s
:
locale
.
strxfrm
(
s
.
region
.
name
or
""
))
return
out
def
region_totals
(
region
:
db
.
Place
,
stats
:
List
[
ContestStat
])
->
ContestStat
:
return
ContestStat
(
region
=
region
,
num_contests
=
sum
(
s
.
num_contests
for
s
in
stats
),
num_active_pants
=
sum
(
s
.
num_active_pants
for
s
in
stats
),
num_unconfirmed_pants
=
sum
(
s
.
num_unconfirmed_pants
for
s
in
stats
),
)
)
sol_counts
=
{}
for
task_id
,
count
in
sol_counts_q
.
group_by
(
db
.
Solution
.
task_id
).
all
():
sol_counts
[
task_id
]
=
count
def
task_stats
(
round
:
db
.
Round
,
region
:
db
.
Place
)
->
List
[
Tuple
[
db
.
Task
,
int
]]:
sess
=
db
.
get_session
()
tasks
=
sess
.
query
(
db
.
Task
).
filter_by
(
round
=
round
).
all
()
tasks
=
sess
.
query
(
db
.
Task
).
filter_by
(
round
=
round
).
all
()
tasks
.
sort
(
key
=
lambda
t
:
t
.
code
)
tasks
.
sort
(
key
=
lambda
t
:
t
.
code
)
for
task
in
tasks
:
task
.
sol_count
=
sol_counts
[
task
.
task_id
]
if
task
.
task_id
in
sol_counts
else
0
ts
=
(
sess
.
query
(
db
.
RegionTaskStat
)
.
filter_by
(
round
=
round
,
region
=
region
.
place_id
)
.
all
())
count_by_id
=
{
s
.
task_id
:
s
.
count
for
s
in
ts
}
return
[(
t
,
count_by_id
.
get
(
t
.
task_id
,
0
))
for
t
in
tasks
]
@app.route
(
'
/org/contest/r/<int:round_id>/
'
,
methods
=
(
'
GET
'
,
'
POST
'
))
@app.route
(
'
/org/contest/r/<int:round_id>/h/<int:hier_id>
'
,
methods
=
(
'
GET
'
,
'
POST
'
))
def
org_round
(
round_id
:
int
,
hier_id
:
Optional
[
int
]
=
None
):
ctx
=
get_context
(
round_id
=
round_id
,
hier_id
=
hier_id
)
round
=
ctx
.
round
rights
=
ctx
.
rights
form_delete_task
=
TaskDeleteForm
()
form_delete_task
=
TaskDeleteForm
()
if
rights
.
have_right
(
Right
.
manage_round
)
and
delete_task
(
round_id
,
form_delete_task
):
if
rights
.
have_right
(
Right
.
manage_round
)
and
delete_task
(
round_id
,
form_delete_task
):
...
@@ -208,13 +260,19 @@ def org_round(round_id: int):
...
@@ -208,13 +260,19 @@ def org_round(round_id: int):
group_rounds
=
round
.
get_group_rounds
(
True
)
group_rounds
=
round
.
get_group_rounds
(
True
)
group_rounds
.
sort
(
key
=
lambda
r
:
r
.
round_code
())
group_rounds
.
sort
(
key
=
lambda
r
:
r
.
round_code
())
region
=
ctx
.
hier_place
or
db
.
get_root_place
()
reg_stats
=
region_stats
(
round
,
region
)
reg_total
=
region_totals
(
region
,
reg_stats
)
task_info
=
task_stats
(
round
,
region
)
return
render_template
(
return
render_template
(
'
org_round.html
'
,
'
org_round.html
'
,
ctx
=
ctx
,
rights
=
rights
,
ctx
=
ctx
,
rights
=
rights
,
round
=
round
,
group_rounds
=
group_rounds
,
round
=
round
,
group_rounds
=
group_rounds
,
roles
=
[
r
.
friendly_name
()
for
r
in
rights
.
get_roles
()],
roles
=
[
r
.
friendly_name
()
for
r
in
rights
.
get_roles
()],
contests_counts
=
contests_counts
,
reg_stats
=
reg_stats
,
reg_total
=
reg_total
,
tasks
=
tasks
,
form_delete_task
=
form_delete_task
,
task_info
=
task_info
,
form_delete_task
=
form_delete_task
,
form_add_contest
=
form_add_contest
,
form_add_contest
=
form_add_contest
,
statement_exists
=
mo
.
web
.
util
.
task_statement_exists
(
round
),
statement_exists
=
mo
.
web
.
util
.
task_statement_exists
(
round
),
)
)
...
...
This diff is collapsed.
Click to expand it.
mo/web/templates/org_round.html
+
60
−
32
View file @
754adc40
{% extends "base.html" %}
{% extends "base.html" %}
{% import "bootstrap/wtf.html" as wtf %}
{% import "bootstrap/wtf.html" as wtf %}
{% set can_manage_round = rights.have_right(Right.manage_round) %}
{% set in_hier = ctx.hier_id != None %}
{% set can_manage_round = rights.have_right(Right.manage_round) and not in_hier %}
{% set can_manage_contest = rights.have_right(Right.manage_contest) %}
{% set can_manage_contest = rights.have_right(Right.manage_contest) %}
{% set can_view_contestants = rights.have_right(Right.view_contestants) %}
{% set can_view_contestants = rights.have_right(Right.view_contestants) %}
{% set can_handle_submits = rights.have_right(Right.view_submits) %}
{% set can_handle_submits = rights.have_right(Right.view_submits) %}
...
@@ -8,7 +9,13 @@
...
@@ -8,7 +9,13 @@
{% set can_view_statement = rights.can_view_statement() %}
{% set can_view_statement = rights.can_view_statement() %}
{% set can_add_contest = g.gatekeeper.rights_generic().have_right(Right.add_contest) %}
{% set can_add_contest = g.gatekeeper.rights_generic().have_right(Right.add_contest) %}
{% block title %}{{ round.name }} {{ round.round_code() }}{% endblock %}
{% block title %}
{% if in_hier %}
{{ round.round_code() }}: {{ ctx.hier_place.name }}
{% else %}
{{ round.name }} {{ round.round_code() }}
{% endif %}
{% endblock %}
{% block breadcrumbs %}
{% block breadcrumbs %}
{{ ctx.breadcrumbs() }}
{{ ctx.breadcrumbs() }}
{% endblock %}
{% endblock %}
...
@@ -17,7 +24,7 @@
...
@@ -17,7 +24,7 @@
<table
class=
data
style=
"float: left; margin-right: 10px;"
>
<table
class=
data
style=
"float: left; margin-right: 10px;"
>
<thead>
<thead>
<tr><th
colspan=
2
>
Parametry kola
<i>
(nelze editovat)
</i>
<tr><th
colspan=
2
>
Parametry kola
{% if can_manage_round %}
<i>
(nelze editovat)
</i>
{% endif %}
</thead>
</thead>
<tr><td>
Ročník
<td>
{{ round.year }}
<tr><td>
Ročník
<td>
{{ round.year }}
<tr><td>
Kategorie
<td>
{{ round.category }}
<tr><td>
Kategorie
<td>
{{ round.category }}
...
@@ -81,7 +88,7 @@
...
@@ -81,7 +88,7 @@
{% if can_view_contestants %}
{% if can_view_contestants %}
<a
class=
"btn btn-primary"
href=
'{{ ctx.url_for('
org_generic_list
')
}}'
>
Seznam účastníků
</a>
<a
class=
"btn btn-primary"
href=
'{{ ctx.url_for('
org_generic_list
')
}}'
>
Seznam účastníků
</a>
{% endif %}
{% endif %}
{% if can_view_contestants and round.state in [RoundState.grading, RoundState.closed, RoundState.delegate] %}
{% if can_view_contestants and round.state in [RoundState.grading, RoundState.closed, RoundState.delegate]
and not in_hier
%}
<a
class=
"btn btn-primary"
href=
'{{ ctx.url_for('
org_score
')
}}'
>
Výsledky
</a>
<a
class=
"btn btn-primary"
href=
'{{ ctx.url_for('
org_score
')
}}'
>
Výsledky
</a>
{% endif %}
{% endif %}
{% if can_manage_contest %}
{% if can_manage_contest %}
...
@@ -94,35 +101,56 @@
...
@@ -94,35 +101,56 @@
{% if round.has_messages %}
{% if round.has_messages %}
<a
class=
"btn btn-default"
href=
'{{ ctx.url_for('
org_round_messages
')
}}'
>
Zprávičky
</a>
<a
class=
"btn btn-default"
href=
'{{ ctx.url_for('
org_round_messages
')
}}'
>
Zprávičky
</a>
{% endif %}
{% endif %}
{% if g.user.is_admin %}
{% if g.user.is_admin
and not in_hier
%}
<a
class=
"btn btn-default"
href=
'{{ log_url('
round
',
round.round_id
)
}}'
>
Historie
</a>
<a
class=
"btn btn-default"
href=
'{{ log_url('
round
',
round.round_id
)
}}'
>
Historie
</a>
{% endif %}
{% endif %}
</div>
</div>
{% endif %}
{% endif %}
<h3>
Soutěže
</h3>
<h3>
Soutěže
</h3>
{% if contests_counts %}
{% if reg_total.num_contests %}
{% set show_contests = reg_stats[0].contest != None %}
<table
class=
data
>
<table
class=
data
>
<thead>
<thead>
<tr>
<tr>
{% if show_contests %}
<th>
{{ round.get_level().name|capitalize }}
<th>
{{ round.get_level().name|capitalize }}
<th>
Stav
<th>
Stav
{% else %}
<th>
{{ reg_stats[0].region.type_name()|capitalize }}
<th>
Počet soutěží
<th>
Stavy soutěží
{% endif %}
<th>
Počet účastníků
<th>
Počet účastníků
<th>
Počet přihlášek
</tr>
</tr>
</thead>
</thead>
{% for
(c, count) in contests_coun
ts %}
{% for
rs in reg_sta
ts %}
<tr>
<tr>
<td><a
href=
'{{ url_for('
org_contest
',
ct_id=
c.contest_id)
}}'
>
{{ c.place.name }}
</a>
{% if show_contests %}
{% with state=c.state %}
<td><a
href=
'{{ url_for('
org_contest
',
ct_id=
rs.contest.contest_id)
}}'
>
{{ rs.region.name }}
</a>
{% with state=rs.contest.state %}
<td
class=
'rstate-{{state.name}}'
>
{{ state.friendly_name() }}
<td
class=
'rstate-{{state.name}}'
>
{{ state.friendly_name() }}
{% endwith %}
{% endwith %}
<td>
{{ count }}
{% else %}
<td><a
href=
'{{ ctx.url_for('
org_round
',
hier_id=
rs.region.place_id)
}}'
>
{{ rs.region.name }}
</a>
<td>
{{ rs.num_contests }}
<td>
{% for s in rs.contest_states %}
<span
class=
'rstate-{{s.name}}'
>
{{ s.friendly_name() }}
</span>
{% endfor %}
{% endif %}
<td>
{{ rs.num_active_pants }}
<td>
{{ rs.num_unconfirmed_pants }}
{% endfor %}
{% endfor %}
<tfoot>
<tfoot>
<tr>
<tr>
<th>
Celkem
<th>
Celkem
{% if show_contests %}
<th>
<th>
<th>
{{ contests_counts|sum(attribute=1) }}
{% else %}
<th>
{{ reg_total.num_contests }}
<th>
{% endif %}
<th>
{{ reg_total.num_active_pants }}
<th>
{{ reg_total.num_unconfirmed_pants }}
</tr>
</tr>
</tfoot>
</tfoot>
</table>
</table>
...
@@ -139,7 +167,7 @@
...
@@ -139,7 +167,7 @@
{% endif %}
{% endif %}
<h3>
Úlohy
</h3>
<h3>
Úlohy
</h3>
{% if task
s
%}
{% if task
_info
%}
<table
class=
data
>
<table
class=
data
>
<thead>
<thead>
<tr>
<tr>
...
@@ -151,11 +179,11 @@
...
@@ -151,11 +179,11 @@
{% if can_handle_submits or can_upload %}
<th>
Dávkové operace{% endif %}
{% if can_handle_submits or can_upload %}
<th>
Dávkové operace{% endif %}
</tr>
</tr>
</thead>
</thead>
{% for task in task
s
%}
{% for task
, sol_count
in task
_info
%}
<tr>
<tr>
<td>
{{ task.code }}
<td>
{{ task.code }}
<td>
{{ task.name }}
<td>
{{ task.name }}
<td>
{{
task.
sol_count }}
<td>
{{ sol_count }}
<td>
{{ task.max_points|decimal|none_value('–') }}
<td>
{{ task.max_points|decimal|none_value('–') }}
{% if can_manage_round %}
{% if can_manage_round %}
<td><div
class=
"btn-group"
>
<td><div
class=
"btn-group"
>
...
...
This diff is collapsed.
Click to expand it.
Preview
0%
Loading
Try again
or
attach a new file
.
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Save comment
Cancel
Please
register
or
sign in
to comment