Browse Source

Merge branch 'vysledkovky2' into develop

zadavatko_problemu
Jonas Havelka 2 years ago
parent
commit
4aa67d6151
  1. 15
      deploy_v2/admin_org_prava.json
  2. 35
      odevzdavatko/forms.py
  3. 12
      odevzdavatko/templates/odevzdavatko/detail.html
  4. 6
      odevzdavatko/templates/odevzdavatko/detail_resitele.html
  5. 4
      odevzdavatko/templates/odevzdavatko/prehled_reseni.html
  6. 2
      odevzdavatko/templates/odevzdavatko/seznam.html
  7. 18
      odevzdavatko/views.py
  8. 2
      personalni/views.py
  9. 14
      seminar/admin.py
  10. 11
      seminar/management/commands/pregeneruj_zmrazene_vysledkovky.py
  11. 81
      seminar/migrations/0103_deadline.py
  12. 86
      seminar/migrations/0104_hodnoceni_deadline_body.py
  13. 50
      seminar/migrations/0105_odstraneni_deadlinu_cisla.py
  14. 27
      seminar/migrations/0106_remove_cislo_verejna_vysledkovka.py
  15. 26
      seminar/migrations/0107_zmrazenavysledkovka.py
  16. 7
      seminar/models/odevzdavatko.py
  17. 4
      seminar/models/personalni.py
  18. 132
      seminar/models/tvorba.py
  19. 24
      seminar/templates/seminar/archiv/cislo.html
  20. 6
      seminar/templates/seminar/archiv/cislo_vysledkovka.tex
  21. 22
      seminar/templates/seminar/archiv/odmeny.html
  22. 6
      seminar/templates/seminar/archiv/rocnik.html
  23. 8
      seminar/templates/seminar/archiv/rocnik_vysledkovka.tex
  24. 6
      seminar/templates/seminar/titulnistrana/titulnistrana.html
  25. 4
      seminar/templates/seminar/zadani/AktualniVysledkovka.html
  26. 22
      seminar/templates/seminar/zadani/AktualniZadani.html
  27. 44
      seminar/templatetags/deadliny.py
  28. 149
      seminar/test_deadlines.py
  29. 2
      seminar/testutils.py
  30. 10
      seminar/urls.py
  31. 142
      seminar/utils.py
  32. 186
      seminar/views/views_all.py
  33. 58
      vysledkovky/templates/vysledkovky/vysledkovka_cisla.html
  34. 6
      vysledkovky/templates/vysledkovky/vysledkovka_rocnik.html
  35. 1
      vysledkovky/templates/vysledkovky/vysledkovka_rocnik_neverejna.html
  36. 914
      vysledkovky/utils.py
  37. 37
      vysledkovky/views.py

15
deploy_v2/admin_org_prava.json

@ -633,5 +633,20 @@
"codename": "view_fotkaurlvazba",
"ct_app_label": "header_fotky",
"ct_model": "fotkaurlvazba"
},
{
"codename": "add_deadline",
"ct_app_label": "seminar",
"ct_model": "deadline"
},
{
"codename": "change_deadline",
"ct_app_label": "seminar",
"ct_model": "deadline"
},
{
"codename": "view_deadline",
"ct_app_label": "seminar",
"ct_model": "deadline"
}
]

35
odevzdavatko/forms.py

@ -2,6 +2,7 @@ from django import forms
from dal import autocomplete
from django.forms import formset_factory
from django.forms.models import inlineformset_factory
from django.utils import timezone
from seminar.models import Resitel
import seminar.models as m
@ -87,7 +88,7 @@ ReseniSPrilohamiFormSet = inlineformset_factory(m.Reseni,m.PrilohaReseni,
class JednoHodnoceniForm(forms.ModelForm):
class Meta:
model = m.Hodnoceni
fields = ('problem', 'body', 'cislo_body')
fields = ('problem', 'body', 'deadline_body')
widgets = {
'problem': autocomplete.ModelSelect2(
url='autocomplete_problem_odevzdatelny', # FIXME: Dovolit i starší?
@ -141,7 +142,6 @@ class OdevzdavatkoTabulkaFiltrForm(forms.Form):
from django.db.utils import OperationalError
try:
aktualni_rocnik = m.Nastaveni.get_solo().aktualni_rocnik
aktualni_cislo = m.Nastaveni.get_solo().aktualni_cislo
except OperationalError:
# django.db.utils.OperationalError: no such table: seminar_nastaveni
# Nemáme databázi, takže to selhalo. Pro jistotu vrátíme aspoň dvě možnosti, ať to nepadá dál
@ -152,31 +152,18 @@ class OdevzdavatkoTabulkaFiltrForm(forms.Form):
# FIXME: Tohle je hnusný monkey patch, mělo by to být nějak zahrnuto výš.
if rocnik is not None:
aktualni_rocnik = rocnik
aktualni_cislo = m.Cislo.objects.filter(rocnik=rocnik).order_by('poradi').last()
result = []
for cislo in m.Cislo.objects.filter(
rocnik=aktualni_rocnik,
poradi__lte=aktualni_cislo.poradi,
).reverse(): # Standardně se řadí od nejnovějšího čísla
# Předem je mi líto kohokoliv, kdo tyhle řádky bude číst...
if cislo.datum_vydani is not None and cislo.datum_vydani <= datetime.date.today():
result.append((
strftime(DATE_FORMAT, cislo.datum_vydani.timetuple()),
f"Vydání {cislo.poradi}. čísla"))
if cislo.datum_preddeadline is not None and cislo.datum_preddeadline <= datetime.date.today():
result.append((
strftime(DATE_FORMAT, cislo.datum_preddeadline.timetuple()),
f"Předdeadline {cislo.poradi}. čísla"))
if cislo.datum_deadline_soustredeni is not None and cislo.datum_deadline_soustredeni <= datetime.date.today():
result.append((
strftime(DATE_FORMAT, cislo.datum_deadline_soustredeni.timetuple()),
f"Sous. deadline {cislo.poradi}. čísla"))
if cislo.datum_deadline is not None and cislo.datum_deadline <= datetime.date.today():
result.append((
strftime(DATE_FORMAT, cislo.datum_deadline.timetuple()),
f"Finální deadline {cislo.poradi}. čísla"))
for deadline in m.Deadline.objects.filter(
deadline__lte=timezone.now(),
cislo__rocnik=aktualni_rocnik
).order_by("deadline"):
result.append((
strftime(DATE_FORMAT, deadline.deadline.timetuple()),
str(deadline)))
result.append((
strftime(DATE_FORMAT, datetime.date.today().timetuple()), f"Dnes"))

12
odevzdavatko/templates/odevzdavatko/detail.html

@ -49,6 +49,10 @@ $(document).ready(function(){
$('.smazat_hodnoceni').click(function(){
deleteForm("form",this);
});
// Copy deadline
if (form_idx !== "0") {
$('#id_form-' + form_idx + '-deadline_body')[0].value = $('#id_form-' + (form_idx - 1) + '-deadline_body')[0].value
}
$('#id_form-TOTAL_FORMS').val(parseInt(form_idx) + 1);
});
$('.smazat_hodnoceni').click(function(){
@ -66,7 +70,7 @@ $(document).ready(function(){
{# https://docs.djangoproject.com/en/3.1/ref/models/instances/#django.db.models.Model.get_FOO_display #}
<p>Forma: {{ object.get_forma_display }}</p>
<p>Doručeno {{ object.cas_doruceni }}, deadline: {{object.cas_doruceni | deadline_html }}</p>
<p>Doručeno {{ object.cas_doruceni }}, deadline: {{object.deadline_reseni | deadline_html }}</p>
{# Soubory: #}
<h3>Přílohy:</h3>
@ -97,13 +101,13 @@ $(document).ready(function(){
{{ form.management_form }}
</table>
<table id="form_set">
<tr><th>Problém</th><th>Body</th><th>Číslo pro body</th></tr>
<tr><th>Problém</th><th>Body</th><th>Deadline pro body</th></tr>
{% for subform in form %}
<tbody>
<tr class="hodnoceni">
<td>{{ subform.problem }}</td>
<td>{{ subform.body }}</td>
<td>{{ subform.cislo_body }}</td>
<td>{{ subform.deadline_body }}</td>
<td><a href="#" class="smazat_hodnoceni" id="id_{{subform.prefix}}-jsremove"><img src="{% static "odevzdavatko/cross.png" %}" alt="Smazat"></a></td>
</tr>
</tbody>
@ -118,7 +122,7 @@ $(document).ready(function(){
<tr class="hodnoceni">
<td>{{ form.empty_form.problem }}</td>
<td>{{ form.empty_form.body }}</td>
<td>{{ form.empty_form.cislo_body }}</td>
<td>{{ form.empty_form.deadline_body }}</td>
<td><a href="#" class="smazat_hodnoceni" id="id_{{form.empty_form.prefix}}-jsremove"><img src="{% static "odevzdavatko/cross.png" %}" alt="Smazat"></a></td>
</tr>
</table>

6
odevzdavatko/templates/odevzdavatko/detail_resitele.html

@ -12,7 +12,7 @@
{# https://docs.djangoproject.com/en/3.1/ref/models/instances/#django.db.models.Model.get_FOO_display #}
<p>Forma: {{ object.get_forma_display }}</p>
<p>Doručeno {{ object.cas_doruceni }}, deadline: {{object.cas_doruceni | deadline_html }}</p>
<p>Doručeno {{ object.cas_doruceni }}, deadline: {{object.deadline_reseni | deadline_html }}</p>
{# Soubory: #}
<h3>Přílohy:</h3>
@ -37,12 +37,12 @@
{# Hodnocení: #}
<h3>Hodnocení:</h3>
<table id="form_set" class="dosla_reseni">
<tr><th>Problém</th><th>Body</th>{# <th>Číslo pro body</th> #}</tr>
<tr><th>Problém</th><th>Body</th>{# <th>Deadline pro body</th> #}</tr>
{% for h in hodnoceni %}
<tr class="hodnoceni">
<td>{{ h.problem }}</td>
<td>{{ h.body }}</td>
{# <td>{{ h.cislo_body }}</td>#}
{# <td>{{ h.deadline_body }}</td>#}
</tr>
{% endfor %}
</table>

4
odevzdavatko/templates/odevzdavatko/prehled_reseni.html

@ -6,7 +6,7 @@
<h3>Označení deadlinů</h3>
<ul>
<li>Ⓢ deadline pro účast na soustředění</li>
<li> 1. deadline</li>
<li> 1. deadline</li>
<li>✓ 2. deadline</li>
</ul>
@ -29,7 +29,7 @@
<td class="problem odevzdanareseni_small"><span title="{{ hodn.problem.nazev }}">{{ hodn.problem.nazev | zkrat_nazev_problemu:27 }}</span></td>
<td class="problem odevzdanareseni_mini"><span title="{{ hodn.problem.nazev }}">{{ hodn.problem.nazev | zkrat_nazev_problemu:10 }}</span></td>
<td>{{ hodn.body|default_if_none:"---" }}</td>
<td>{{ hodn.reseni.cas_doruceni | deadline_html }}</td>
<td>{{ hodn.deadline_body | deadline_html }}</td>
</tr>
{% endfor %}
</table>

2
odevzdavatko/templates/odevzdavatko/seznam.html

@ -4,7 +4,7 @@
{% block content %}
{% for dl, mnozina_reseni in reseni_podle_deadlinu.items %}
<h1>{{ dl.2 | deadline_html }}</h1>
<h1>{{ dl | deadline_html }}</h1>
<ul>
{% for obj in mnozina_reseni %}
<li>{{ obj.sum_body }} b za <a href="{% url 'odevzdavatko_detail_reseni' pk=obj.id %}">{{ obj }}</a> ({{ obj.get_forma_display }} {{ obj.cas_doruceni }})

18
odevzdavatko/views.py

@ -19,7 +19,7 @@ import logging
import seminar.models as m
from . import forms as f
from .forms import OdevzdavatkoTabulkaFiltrForm as FiltrForm
from seminar.utils import resi_v_rocniku, deadline
from seminar.utils import resi_v_rocniku
from seminar.views import formularOKView
logger = logging.getLogger(__name__)
@ -202,7 +202,7 @@ class ReseniProblemuView(MultipleObjectTemplateResponseMixin, MultipleObjectMixi
# XXX: Předat groupby do template nejde: https://stackoverflow.com/questions/6906593/itertools-groupby-in-a-django-template
# Django má {% regroup %}, ale ten potřebuje, aby klíč byl atribut položky: https://docs.djangoproject.com/en/3.2/ref/templates/builtins/#regroup
# Takže rozbalíme groupby do slovníku klíč → seznam sami (dictionary comphrehension)
ctx['reseni_podle_deadlinu'] = {k: list(v) for k,v in groupby(ctx['object_list'], lambda r: deadline(r.cas_doruceni))}
ctx['reseni_podle_deadlinu'] = {k: list(v) for k,v in groupby(ctx['object_list'], lambda r: r.deadline_reseni)}
# Pro sitetree:
ctx["resitel_id"] = self.kwargs['resitel']
@ -216,12 +216,12 @@ class DetailReseniView(DetailView):
def aktualni_hodnoceni(self):
self.reseni = get_object_or_404(m.Reseni, id=self.kwargs['pk'])
result = [] # Slovníky s klíči problem, body, cislo_body -- initial data pro f.OhodnoceniReseniFormSet
result = [] # Slovníky s klíči problem, body, deadline_body -- initial data pro f.OhodnoceniReseniFormSet
for hodn in m.Hodnoceni.objects.filter(reseni=self.reseni):
result.append(
{"problem": hodn.problem,
"body": hodn.body,
"cislo_body": hodn.cislo_body,
"deadline_body": hodn.deadline_body,
})
return result
@ -260,11 +260,11 @@ def hodnoceniReseniView(request, pk, *args, **kwargs):
for form in formset:
problem = form.cleaned_data['problem']
body = form.cleaned_data['body']
cislo_body = form.cleaned_data['cislo_body']
deadline_body = form.cleaned_data['deadline_body']
hodnoceni = m.Hodnoceni(
problem=problem,
body=body,
cislo_body=cislo_body,
deadline_body=deadline_body,
reseni=reseni,
)
logger.info(f"Creating Hodnoceni: {hodnoceni}")
@ -285,7 +285,7 @@ class ResitelReseniView(DetailView):
{
"problem": hodn.problem,
"body": hodn.body,
# "cislo_body": hodn.cislo_body,
# "deadline_body": hodn.deadline_body,
}
)
return result
@ -320,9 +320,8 @@ class PrehledOdevzdanychReseni(ListView):
ctx = super().get_context_data(*args, **kwargs)
# Ročník určujeme podle čísla, do jehož deadlinu došlo řešení.
# Chceme to mít seřazené, takže místo comphrerehsion ručně postavíme pole polí. Django templates neumí použít OrderedDict :-/
# TODO: Funkce deadline vrací deadliny v jiném ročníku, zvlášť pokud se vyrobí řešení až po deadlinu (třeba při poslání mailem)
podle_rocniku = []
for rocnik, hodnoceni in groupby(ctx['object_list'], lambda ho: deadline(ho.reseni.cas_doruceni)[1].rocnik if deadline(ho.reseni.cas_doruceni) is not None else None):
for rocnik, hodnoceni in groupby(ctx['object_list'], lambda ho: ho.deadline_body.cislo.rocnik if ho.deadline_body is not None else None):
podle_rocniku.append((rocnik, list(hodnoceni)))
ctx['podle_rocniku'] = reversed(podle_rocniku) # Od nejnovějšího ročníku
# TODO: Umožnit stažení / zobrazení řešení
@ -412,6 +411,7 @@ class NahrajReseniView(LoginRequiredMixin, CreateView):
self.object = form.save()
self.object.resitele.add(m.Resitel.objects.get(osoba__user = self.request.user))
self.object.cas_doruceni = timezone.now()
self.object.deadline = m.Deadline.objects.filter(deadline__gte=self.object.cas_doruceni).first()
self.object.forma = m.Reseni.FORMA_UPLOAD
self.object.save()

2
personalni/views.py

@ -40,7 +40,7 @@ class OrgoRozcestnikView(TemplateView):
# přes treenody (a dát si přitom pozor na MezicisloNode)
neobodovana_reseni = s.Hodnoceni.objects.filter(body__isnull=True)
reseni_mimo_cislo = s.Hodnoceni.objects.filter(cislo_body__isnull=True)
reseni_mimo_cislo = s.Hodnoceni.objects.filter(deadline_body__isnull=True)
context['pocet_neobodovanych_reseni'] = neobodovana_reseni.count()
context['pocet_reseni_mimo_cislo'] = reseni_mimo_cislo.count()

14
seminar/admin.py

@ -7,14 +7,21 @@ from polymorphic.admin import PolymorphicParentModelAdmin, PolymorphicChildModel
from solo.admin import SingletonModelAdmin
from django.utils.safestring import mark_safe
from seminar.utils import hlavni_problem
# Todo: reversion
import seminar.models as m
admin.site.register(m.Rocnik)
admin.site.register(m.Deadline)
admin.site.register(m.ZmrazenaVysledkovka)
class DeadlineAdminInline(admin.TabularInline):
model = m.Deadline
extra = 0
class CisloForm(ModelForm):
class Meta:
model = m.Cislo
@ -65,6 +72,7 @@ class CisloForm(ModelForm):
class CisloAdmin(admin.ModelAdmin):
form = CisloForm
actions = ['force_publish']
inlines = (DeadlineAdminInline,)
def force_publish(self,request,queryset):
for cislo in queryset:
@ -90,7 +98,7 @@ class CisloAdmin(admin.ModelAdmin):
ch.stav = m.Problem.STAV_ZADANY
ch.save()
hp = hlavni_problem(ch)
hp = ch.hlavni_problem
if hp.stav not in (m.Problem.STAV_ZADANY, m.Problem.STAV_VYRESENY):
hp.stav = m.Problem.STAV_ZADANY
hp.save()

11
seminar/management/commands/pregeneruj_zmrazene_vysledkovky.py

@ -0,0 +1,11 @@
from django.core.management.base import BaseCommand
import seminar.models as m
class Command(BaseCommand):
help = "Všem deadlinům se zveřejněnou výsledkovkou vygeneruj výsledkovku"
def handle(self, *args, **options):
for deadline in m.Deadline.objects.filter(verejna_vysledkovka=True):
deadline.vygeneruj_vysledkovku()

81
seminar/migrations/0103_deadline.py

@ -0,0 +1,81 @@
# Generated by Django 3.2.15 on 2022-10-01 08:44
import datetime
from django.db import migrations, models
import django.db.models.deletion
from django.utils import timezone
import seminar.models as m
def vytvor_deadliny(apps, schema_editor):
Cislo = apps.get_model('seminar', 'Cislo')
Deadline = apps.get_model('seminar', 'Deadline')
for cislo in Cislo.objects.all():
if cislo.rocnik.rocnik < 26:
Deadline.objects.create(
cislo=cislo,
typ=m.Deadline.TYP_CISLA,
deadline=timezone.make_aware(datetime.datetime.combine(datetime.date(1994 + cislo.rocnik.rocnik, 6, int(cislo.poradi[0])), datetime.time.min)),
verejna_vysledkovka=cislo.verejna_vysledkovka,
)
continue
def vytvor_deadline(date: datetime.date, typ):
Deadline.objects.create(
cislo=cislo,
typ=typ,
deadline=timezone.make_aware(datetime.datetime.combine(date, datetime.time.min)) + datetime.timedelta(days=1),
verejna_vysledkovka=True
)
if cislo.datum_deadline_soustredeni and cislo.datum_deadline_soustredeni == cislo.datum_preddeadline:
vytvor_deadline(
date=cislo.datum_deadline_soustredeni,
typ=m.Deadline.TYP_PRVNI_A_SOUS
)
else:
if cislo.datum_deadline_soustredeni:
vytvor_deadline(
date=cislo.datum_deadline_soustredeni,
typ=m.Deadline.TYP_SOUS
)
if cislo.datum_preddeadline:
vytvor_deadline(
date=cislo.datum_preddeadline,
typ=m.Deadline.TYP_PRVNI
)
if cislo.datum_deadline:
vytvor_deadline(
date=cislo.datum_deadline,
typ=m.Deadline.TYP_CISLA
)
class Migration(migrations.Migration):
dependencies = [
('seminar', '0102_osoba_jak_se_dozvedeli'),
]
operations = [
migrations.CreateModel(
name='Deadline',
fields=[
('id', models.AutoField(primary_key=True, serialize=False)),
('deadline', models.DateTimeField(default=timezone.make_aware(datetime.datetime.combine(timezone.now(), datetime.time.max)))),
('typ', models.CharField(choices=[('cisla', 'Deadline celého čísla'), ('prvni', 'První deadline'), ('prvniasous', 'Sousový a první deadline'), ('sous', 'Sousový deadline')], max_length=32, verbose_name='typ deadlinu')),
('verejna_vysledkovka', models.BooleanField(db_column='verejna_vysledkovka', default=False, verbose_name='veřejná výsledkovka')),
('cislo', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='deadline_v_cisle', to='seminar.cislo', verbose_name='deadline v čísle')),
],
options={
'verbose_name': 'Deadline',
'verbose_name_plural': 'Deadliny',
'db_table': 'seminar_deadliny',
'ordering': ['deadline'],
},
),
migrations.RunPython(vytvor_deadliny, migrations.RunPython.noop),
]

86
seminar/migrations/0104_hodnoceni_deadline_body.py

@ -0,0 +1,86 @@
# Generated by Django 3.2.15 on 2022-10-01 09:28
from django.db import migrations, models
import django.db.models.deletion
from logging import getLogger
log = getLogger(__name__)
def prirad_deadliny(apps, schema_editor):
Hodnoceni = apps.get_model('seminar', 'Hodnoceni')
Deadline = apps.get_model('seminar', 'Deadline')
for h in Hodnoceni.objects.all():
if h.cislo_body is not None and h.cislo_body.rocnik.rocnik < 26:
# Deadline připravený v minulé migraci
h.deadline_body = h.cislo_body.deadline_v_cisle.get()
h.save()
continue
p = h.problem
if p.polymorphic_ctype.model == 'tema':
t = p.tema
d = Deadline.objects.filter(cislo__rocnik=t.rocnik, deadline__gte=h.reseni.cas_doruceni).first()
if d is None:
d = Deadline.objects.filter(cislo__rocnik=t.rocnik).last()
if d is not None:
h.deadline_body = d
h.save()
continue
cislo = None
if p.polymorphic_ctype.model == 'uloha':
u = p.uloha
cislo = u.cislo_zadani
if p.polymorphic_ctype.model == 'clanek':
c = p.clanek
if c.cislo is not None:
cislo = c.cislo
if cislo is None:
log.warning(f"Číslo hodnocení {h.id} se nepodařilo určit exaktním způsobem. Dané hodnocení házím do jeho cislo_body: {h.cislo_body}.")
cislo = h.cislo_body
if cislo is not None:
d = Deadline.objects.filter(cislo=cislo, deadline__gte=h.reseni.cas_doruceni).first()
if d is None:
d = Deadline.objects.filter(cislo=cislo).last()
if d is not None:
h.deadline_body = d
h.save()
continue
d = Deadline.objects.filter(deadline__gte=h.reseni.cas_doruceni).first()
h.deadline_body = d
h.save()
log.warning(f"Deadline hodnocení {h.id} se nepodařil určit exaktnějším způsobem. Zkouším další. Přiřazen {h.deadline_body}. Původní cislo_body: {h.cislo_body}.")
# Zběžná kontrola. Předpokládá, že M&M má méně než 10 čísel v ročníku
# a že první znak pořadí je int určující dané pořadí (schroustání 7-8).
if h.cislo_body and h.deadline_body and (
int(h.deadline_body.cislo.poradi[0]) + 2 < int(h.cislo_body.poradi[0])
or int(h.deadline_body.cislo.poradi[0]) > int(h.cislo_body.poradi[0])
):
log.error(f"Hodnocení {h.id} se špatně změnilo číslo z {h.cislo_body} na {h.deadline_body.cislo}")
class Migration(migrations.Migration):
dependencies = [
('seminar', '0103_deadline'),
]
operations = [
migrations.AddField(
model_name='hodnoceni',
name='deadline_body',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='hodnoceni', to='seminar.deadline', verbose_name='deadline pro body'),
),
migrations.RunPython(prirad_deadliny, migrations.RunPython.noop),
]

50
seminar/migrations/0105_odstraneni_deadlinu_cisla.py

@ -0,0 +1,50 @@
# Generated by Django 3.2.15 on 2022-10-09 10:14
from django.db import migrations
from seminar.models import Deadline
def vrat_deadliny(apps, schema_editor):
Cislo = apps.get_model('seminar', 'Cislo')
for cislo in Cislo.objects.all():
prvni_deadline = cislo.deadline_v_cisle.filter(typ=Deadline.TYP_PRVNI).last()
sous_deadline = cislo.deadline_v_cisle.filter(typ=Deadline.TYP_SOUS).last()
prvni_a_sous_deadline = cislo.deadline_v_cisle.filter(typ=Deadline.TYP_PRVNI_A_SOUS).last()
posledni_deadline = cislo.deadline_v_cisle.filter(typ=Deadline.TYP_CISLA).last()
if prvni_a_sous_deadline is not None:
cislo.datum_deadline_soustredeni = prvni_a_sous_deadline.deadline.date()
cislo.datum_preddeadline = prvni_a_sous_deadline.deadline.date()
else:
if sous_deadline is not None:
cislo.datum_deadline_soustredeni = sous_deadline.deadline.date()
if prvni_deadline is not None:
cislo.datum_preddeadline = prvni_deadline.deadline.date()
if posledni_deadline:
cislo.datum_deadline = posledni_deadline.deadline.date()
cislo.save()
class Migration(migrations.Migration):
dependencies = [
('seminar', '0104_hodnoceni_deadline_body'),
]
operations = [
migrations.RunPython(migrations.RunPython.noop, vrat_deadliny),
migrations.RemoveField(
model_name='cislo',
name='datum_deadline',
),
migrations.RemoveField(
model_name='cislo',
name='datum_deadline_soustredeni',
),
migrations.RemoveField(
model_name='cislo',
name='datum_preddeadline',
),
]

27
seminar/migrations/0106_remove_cislo_verejna_vysledkovka.py

@ -0,0 +1,27 @@
# Generated by Django 3.2.15 on 2022-10-09 11:04
from django.db import migrations
from seminar.models import Deadline
def vrat_verejnost(apps, schema_editor):
Cislo = apps.get_model('seminar', 'Cislo')
for cislo in Cislo.objects.all():
cislo.verejna_vysledkovka = cislo.deadline_v_cisle.filter(typ=Deadline.TYP_CISLA, verejna_vysledkovka=True).exists()
cislo.save()
class Migration(migrations.Migration):
dependencies = [
('seminar', '0105_odstraneni_deadlinu_cisla'),
]
operations = [
migrations.RunPython(migrations.RunPython.noop, vrat_verejnost),
migrations.RemoveField(
model_name='cislo',
name='verejna_vysledkovka',
),
]

26
seminar/migrations/0107_zmrazenavysledkovka.py

@ -0,0 +1,26 @@
# Generated by Django 3.2.15 on 2022-10-10 07:23
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('seminar', '0106_remove_cislo_verejna_vysledkovka'),
]
operations = [
migrations.CreateModel(
name='ZmrazenaVysledkovka',
fields=[
('deadline', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, primary_key=True, related_name='vysledkovka_v_deadlinu', serialize=False, to='seminar.deadline')),
('html', models.TextField()),
],
options={
'verbose_name': 'Zmražená výsledkovka',
'verbose_name_plural': 'Zmražené výsledkovky',
'db_table': 'seminar_vysledkovky',
},
),
]

7
seminar/models/odevzdavatko.py

@ -78,6 +78,9 @@ class Reseni(bm.SeminarModelBase):
return "{}({}): {}({})".format(self.resitele.first(),len(self.resitele.all()), self.problem.first() ,len(self.problem.all()))
# NOTE: Potenciální DB HOG (bez select_related)
def deadline_reseni(self):
return am.Deadline.objects.filter(deadline__gte=self.cas_doruceni).order_by("deadline").first()
## Pravdepodobne uz nebude potreba:
# def save(self, *args, **kwargs):
# if ((self.cislo_body is None) and (self.problem.cislo_reseni) and
@ -101,6 +104,10 @@ class Hodnoceni(bm.SeminarModelBase):
cislo_body = models.ForeignKey(am.Cislo, verbose_name='číslo pro body',
related_name='hodnoceni', blank=True, null=True, on_delete=models.PROTECT)
# V ročníku < 26 nastaveno na deadline vygenerovaný pro původní cislo_body
deadline_body = models.ForeignKey(am.Deadline, verbose_name='deadline pro body',
related_name='hodnoceni', blank=True, null=True, on_delete=models.PROTECT)
reseni = models.ForeignKey(Reseni, verbose_name='řešení', on_delete=models.CASCADE)
problem = models.ForeignKey(am.Problem, verbose_name='problém',

4
seminar/models/personalni.py

@ -323,7 +323,7 @@ class Resitel(SeminarModelBase):
# - proto se započítávají dvojnásobně a byly posunuté hranice titulů
# - staré tituly se ale nemají odebrat, pokud řešitel v t.č. minulém (26.) ročníku měl titul, má ho mít pořád.
from .odevzdavatko import Hodnoceni
hodnoceni_do_25_rocniku = Hodnoceni.objects.filter(cislo_body__rocnik__rocnik__lte=25,reseni__in=self.reseni_set.all())
hodnoceni_do_25_rocniku = Hodnoceni.objects.filter(deadline_body__cislo__rocnik__rocnik__lte=25,reseni__in=self.reseni_set.all())
novejsi_hodnoceni = Hodnoceni.objects.filter(reseni__in=self.reseni_set.all()).difference(hodnoceni_do_25_rocniku)
def body_z_hodnoceni(hh : list):
@ -361,7 +361,7 @@ class Resitel(SeminarModelBase):
return Titul.akad
from .odevzdavatko import Hodnoceni
hodnoceni_do_26_rocniku = Hodnoceni.objects.filter(cislo_body__rocnik__rocnik__lte=26,reseni__in=self.reseni_set.all())
hodnoceni_do_26_rocniku = Hodnoceni.objects.filter(deadline_body__cislo__rocnik__rocnik__lte=26,reseni__in=self.reseni_set.all())
novejsi_body = body_z_hodnoceni(
Hodnoceni.objects.filter(reseni__in=self.reseni_set.all())
.difference(hodnoceni_do_26_rocniku)

132
seminar/models/tvorba.py

@ -1,4 +1,5 @@
# -*- coding: utf-8 -*-
import datetime
import os
import subprocess
import pathlib
@ -7,6 +8,8 @@ import logging
from django.contrib.sites.shortcuts import get_current_site
from django.db import models
from django.db.models import Q
from django.template.loader import render_to_string
from django.utils import timezone
from django.conf import settings
from django.urls import reverse
@ -22,7 +25,6 @@ from taggit.managers import TaggableManager
from reversion import revisions as reversion
from seminar.utils import roman
from seminar.utils import hlavni_problem
from treenode import treelib
from unidecode import unidecode # Používám pro získání ID odkazu (ještě je to někde po někom zakomentované)
@ -96,7 +98,7 @@ class Rocnik(SeminarModelBase):
return vc[-1] if vc else None
def verejne_vysledkovky_cisla(self):
vc = list(self.cisla.filter(verejna_vysledkovka=True))
vc = list(self.cisla.filter(deadline_v_cisle__verejna_vysledkovka=True).distinct())
vc.sort(key=lambda c: c.poradi)
return vc
@ -156,27 +158,10 @@ class Cislo(SeminarModelBase):
datum_vydani = models.DateField('datum vydání', blank=True, null=True,
help_text='Datum vydání finální verze')
datum_deadline_soustredeni = models.DateField(
'datum deadline soustředění',
blank=True, null=True,
help_text='Datum pro příjem řešení pro účast na soustředění')
datum_preddeadline = models.DateField('datum předdeadline', blank=True, null=True,
help_text='Datum pro příjem řešení, která se otisknou v dalším čísle')
datum_deadline = models.DateField('datum deadline', blank=True, null=True,
help_text='Datum pro příjem řešení úloh zadaných v tomto čísle')
verejne_db = models.BooleanField('číslo zveřejněno',
db_column='verejne', default=False)
verejna_vysledkovka = models.BooleanField(
'zveřejněna výsledkovka',
default=False,
help_text='Je-li false u veřejného čísla, '
'není výsledkovka zatím veřejná.')
poznamka = models.TextField('neveřejná poznámka', blank=True,
help_text='Neveřejná poznámka k číslu (plain text)')
@ -218,7 +203,7 @@ class Cislo(SeminarModelBase):
def relativni_v_rocniku(self, rel_index):
"Číslo o `index` dále v ročníku. None pokud neexistuje."
cs = self.rocnik.cisla.order_by('cislo').all()
cs = self.rocnik.cisla.order_by('poradi').all()
i = list(cs).index(self) + rel_index
if (i < 0) or (i >= len(cs)):
return None
@ -316,16 +301,97 @@ class Cislo(SeminarModelBase):
from seminar.models.treenode import CisloNode
CisloNode.objects.create(cislo=self)
def clean(self):
# Finální deadline má být až poslední a je povinný, pokud nějaký deadline existuje.
# Existence:
if self.datum_deadline is None and (self.datum_preddeadline is not None or self.datum_deadline_soustredeni is not None):
raise ValidationError({'datum_deadline': "Číslo musí mít finální deadline, pokud má nějaké deadliny"})
if self.datum_deadline is not None:
if self.datum_preddeadline is not None and self.datum_preddeadline > self.datum_deadline:
raise ValidationError({'datum_preddeadline': "Předdeadline musí předcházet finálnímu deadlinu"})
if self.datum_deadline_soustredeni is not None and self.datum_deadline_soustredeni > self.datum_deadline:
raise ValidationError({'datum_deadline_soustredeni': "Soustřeďkový deadline musí předcházet finálnímu deadlinu"})
def zlomovy_deadline_pro_papirove_cislo(self):
prvni_deadline = Deadline.objects.filter(Q(typ=Deadline.TYP_PRVNI) | Q(typ=Deadline.TYP_PRVNI_A_SOUS), cislo=self).first()
if prvni_deadline is None:
posledni_deadline = self.posledni_deadline
if posledni_deadline is None:
# TODO promyslet, co se má stát tady
return Deadline.objects.filter(Q(cislo__poradi__lt=self.poradi, cislo__rocnik=self.rocnik) | Q(cislo__rocnik__rocnik__lt=self.rocnik.rocnik)).order_by("deadline").last()
return posledni_deadline
return prvni_deadline
@property
def posledni_deadline(self):
return self.deadline_v_cisle.all().order_by("deadline").last()
class Deadline(SeminarModelBase):
class Meta:
db_table = 'seminar_deadliny'
verbose_name = 'Deadline'
verbose_name_plural = 'Deadliny'
ordering = ['deadline']
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.__original_verejna_vysledkovka = self.verejna_vysledkovka
id = models.AutoField(primary_key=True)
# V ročníku < 26 nastaveno na datetime.datetime.combine(datetime.date(1994 + cislo.rocnik.rocnik, 6, int(cislo.poradi[0])), datetime.time.min)
deadline = models.DateTimeField(blank=False, default=timezone.make_aware(datetime.datetime.combine(timezone.now(), datetime.time.max)))
cislo = models.ForeignKey(Cislo, verbose_name='deadline v čísle',
related_name='deadline_v_cisle', blank=False,
on_delete=models.CASCADE)
TYP_CISLA = 'cisla'
TYP_PRVNI_A_SOUS = 'prvniasous'
TYP_PRVNI = 'prvni'
TYP_SOUS = 'sous'
TYP_CHOICES = [
(TYP_CISLA, 'Deadline celého čísla'),
(TYP_PRVNI, 'První deadline'),
(TYP_PRVNI_A_SOUS, 'Sousový a první deadline'),
(TYP_SOUS, 'Sousový deadline'),
]
CHOICES_MAP = dict(TYP_CHOICES)
typ = models.CharField('typ deadlinu', max_length=32,
choices=TYP_CHOICES, blank=False)
verejna_vysledkovka = models.BooleanField('veřejná výsledkovka',
db_column='verejna_vysledkovka',
default=False)
def __str__(self):
return self.CHOICES_MAP[self.typ] + " " + str(self.cislo)
def save(self, *args, **kwargs):
super().save(*args, **kwargs)
if self.verejna_vysledkovka and not self.__original_verejna_vysledkovka:
self.vygeneruj_vysledkovku()
if not self.verejna_vysledkovka and hasattr(self, "vysledkovka_v_deadlinu"):
self.vysledkovka_v_deadlinu.delete()
def vygeneruj_vysledkovku(self):
from vysledkovky.utils import VysledkovkaCisla
if hasattr(self, "vysledkovka_v_deadlinu"):
self.vysledkovka_v_deadlinu.delete()
vysledkovka = VysledkovkaCisla(self.cislo, jen_verejne=True, do_deadlinu=self)
if len(vysledkovka.radky_vysledkovky) != 0:
ZmrazenaVysledkovka.objects.create(
deadline=self,
html=render_to_string(
"vysledkovky/vysledkovka_cisla.html",
context={"vysledkovka": vysledkovka, "oznaceni_vysledkovky": self.id}
)
)
class ZmrazenaVysledkovka(SeminarModelBase):
class Meta:
db_table = 'seminar_vysledkovky'
verbose_name = 'Zmražená výsledkovka'
verbose_name_plural = 'Zmražené výsledkovky'
deadline = models.OneToOneField(
Deadline,
on_delete=models.CASCADE,
primary_key=True,
related_name="vysledkovka_v_deadlinu"
)
html = models.TextField(null=False, blank=False)
@reversion.register(ignore_duplicates=True)
@ -437,9 +503,13 @@ class Problem(SeminarModelBase,PolymorphicModel):
def admin_url(self):
return reverse('admin:seminar_problem_change', args=(self.id, ))
@cached_property
def hlavni_problem(self):
""" Pro daný problém vrátí jeho nejvyšší nadproblém."""
return hlavni_problem(self)
problem = self
while not (problem.nadproblem is None):
problem = problem.nadproblem
return problem
# FIXME - k úloze
def body_v_zavorce(self):

24
seminar/templates/seminar/archiv/cislo.html

@ -68,23 +68,27 @@
{% endcomment %}
{% if cislo.verejna_vysledkovka %}
<h2>Výsledkovka</h2>
{% for deadline, nadpis, vysledkovka in deadliny_s_vysledkovkami %}
{% if deadline.vysledkovka_v_deadlinu or vysledkovka.radky_vysledkovky %}
{% else %}
{% if user.je_org %}
{% if not deadline.verejna_vysledkovka %}
<div class='mam-org-only'>
<h2>Výsledkovka (neveřejná)</h2>
<h2>{{ nadpis }} (neveřejná)</h2>
{% else %}
<h2>{{ nadpis }}</h2>
{% endif %}
{% endif %}
{% if cislo.verejna_vysledkovka or user.je_org %}
{% include "vysledkovky/vysledkovka_cisla.html" %}
{% endif %}
{% if deadline.vysledkovka_v_deadlinu %}
{{ deadline.vysledkovka_v_deadlinu.html | safe }}
{% else %}
{% include "vysledkovky/vysledkovka_cisla.html" with oznaceni_vysledkovky=forloop.counter %}
{% endif %}
{% if not cislo.verejna_vysledkovka and user.je_org %}
{% if not deadline.verejna_vysledkovka %}
</div>
{% endif %}
{% endif %}
{% endfor %}
</div>
{% endblock content %}

6
seminar/templates/seminar/archiv/cislo_vysledkovka.tex

@ -1,9 +1,9 @@
\setlength{\tabcolsep}{3pt}
\begin{longtable}{|r|l|c|r|{% for p in problemy %}c@{\hskip.5em}{% endfor %}|r|r|}\hline
& & & & \multicolumn{ {{ problemy|length}} }{c|}{\textbf{Témata}} & & \\\textbf{Poř.}& \textbf{Jméno}& \textbf{R.}& \raisebox{0.7mm}{$\sum_{-1}$}& {% for p in problemy %}\textbf{ {{ p.kod_v_rocniku }} }&{% endfor %}\raisebox{0.7mm}{$\sum_0$}&\raisebox{0.7mm}{$\sum_1$}\\\hline
\begin{longtable}{|r|l|c|r|{% for p in vysledkovka.temata_a_spol %}c@{\hskip.5em}{% endfor %}{% if vysledkovka.je_nejake_ostatni %}|c@{\hskip.5em}{% endif %}|r|r|}\hline
& & & & \multicolumn{ {{ vysledkovka.temata_a_spol|length}} }{c|}{\textbf{Témata}} & & {% if vysledkovka.je_nejake_ostatni %}&{\hskip.5em}{% endif %} \\\textbf{Poř.}& \textbf{Jméno}& \textbf{R.}& \raisebox{0.7mm}{$\sum_{-1}$}& {% for p in vysledkovka.temata_a_spol %}\textbf{ {{ p.kod_v_rocniku }} }&{% endfor %}{% if vysledkovka.je_nejake_ostatni %}\textbf{Ostatní}&{% endif %}\raisebox{0.7mm}{$\sum_0$}&\raisebox{0.7mm}{$\sum_1$}\\\hline
\endhead
\hline
\endfoot
{% for rv in radky_vysledkovky %}{{rv.poradi}}&{% if rv.titul %}\titul{ {{ rv.titul}}}{% endif %}{{rv.resitel.osoba.jmeno|slice:":1"}}. {{rv.resitel.osoba.prijmeni}}&{{rv.rocnik_resitele|default:""}}&{{rv.body_celkem_odjakziva}}&{% for b in rv.body_problemy_sezn %}{{b}}&{% endfor %}{{rv.body_cislo}}&{{rv.body_rocnik|default:0}}\\
{% for rv in vysledkovka.radky_vysledkovky %}{{rv.poradi}}&{% if rv.titul %}\titul{ {{ rv.titul}}}{% endif %}{{rv.resitel.osoba.jmeno|slice:":1"}}. {{rv.resitel.osoba.prijmeni}}&{{rv.rocnik_resitele|default:""}}&{{rv.body_celkem_odjakziva}}&{% for b in rv.body_za_temata_seznam %}{{b}}&{% endfor %}{{rv.body_cislo}}&{{rv.body_rocnik|default:0}}\\
{% endfor %}
\end{longtable}

22
seminar/templates/seminar/archiv/odmeny.html

@ -3,13 +3,29 @@
{% block content %}
<h1>
{% block nadpis1a %}
Odměny {{ cislo }}
Odměny
{% endblock %}
</h1>
<h2>Od prvního deadlinu {{ from_cislo }} do prvního deadlinu {{ to_cislo }}</h2>
<ul>
{% for z in zmeny_prvni_prvni %}
<li> {{z.jmeno}}: {{z.ftitul}} &rarr; {{z.ttitul}}</li>
{% endfor %}
</ul>
<h2>Od {{ from_cislo }} do prvního deadlinu {{ to_cislo }} (pro první číslo)</h2>
<ul>
{% for z in zmeny_posledni_prvni %}
<li> {{z.jmeno}}: {{z.ftitul}} &rarr; {{z.ttitul}}</li>
{% endfor %}
</ul>
<h2>Od prvního deadlinu {{ from_cislo }} do {{ to_cislo }} (pro poslední číslo)</h2>
<ul>
{% for z in zmeny %}
{% for z in zmeny_prvni_posledni %}
<li> {{z.jmeno}}: {{z.ftitul}} &rarr; {{z.ttitul}}</li>
{% endfor %}
</ul>
</ul>
{% endblock content %}

6
seminar/templates/seminar/archiv/rocnik.html

@ -112,7 +112,7 @@
{% endif %}
{% if vysledkovka %}
{% if vysledkovka.radky_vysledkovky %}
<h2>Výsledková listina</h2>
{% include "vysledkovky/vysledkovka_rocnik.html" %}
{% endif %}
@ -120,10 +120,12 @@
{% if user.je_org %}
<div class='mam-org-only'>
<p><a href='vysledkovka.tex' download>Výsledkovka ročníku (LaTeX, včetně neveřejných)</a></p>
<p><a href="tituly.tex" download>Tituly (TeX, do konce ročníku = pro poslední číslo)</a></p>
<p><a href="posledni_vysledkovka.tex" download>Výsledkovka posledního čísla</a></p>
{# FIXME: Sice to sem asi nepatří sémanticky, ale bylo to nejjednodušší… #}
<p><a href='{% url 'seminar_rocnik_resitele_csv' rocnik=rocnik.rocnik %}' download>CSV export řešitelů</a></p>
<h2>Výsledková listina včetně neveřejných bodů</h2>
{% include "vysledkovky/vysledkovka_rocnik_neverejna.html" %}
{% include "vysledkovky/vysledkovka_rocnik.html" with vysledkovka=vysledkovka_neverejna %}
</div>
{% endif %}

8
seminar/templates/seminar/archiv/rocnik_vysledkovka.tex

@ -1,13 +1,13 @@
{% with lb="{" %}
{% with rb="}" %}
{% with radky_vysledkovky=radky_vysledkovky_s_neverejnymi cisla=cisla_s_neverejnymi %}
{% with vysledkovka=vysledkovka_neverejna %}
\setlength{\tabcolsep}{3pt}
\begin{longtable}{|r|l|c|r|{% for cislo in cisla %}c{% if not forloop.last %}@{\hskip.5em}{% endif %}{% endfor %}|r|}\hline
& & & & \multicolumn{{ lb }}{{ cisla|length }}}{c|}{\textbf{Číslo}} & \\\textbf{Poř.} & \textbf{Jméno} & \textbf{R.} & \raisebox{0.7mm}{$\sum_{-1}$} & {% for cislo in cisla %}\textbf{{ lb }}{{ cislo.poradi }}{{ rb }} & {% endfor %}\raisebox{0.7mm}{$\sum_1$} \\\hline
\begin{longtable}{|r|l|c|r|{% for cislo in vysledkovka.cisla_rocniku %}c{% if not forloop.last %}@{\hskip.5em}{% endif %}{% endfor %}|r|}\hline
& & & & \multicolumn{{ lb }}{{ vysledkovka.cisla_rocniku|length }}}{c|}{\textbf{Číslo}} & \\\textbf{Poř.} & \textbf{Jméno} & \textbf{R.} & \raisebox{0.7mm}{$\sum_{-1}$} & {% for cislo in vysledkovka.cisla_rocniku %}\textbf{{ lb }}{{ cislo.poradi }}{{ rb }} & {% endfor %}\raisebox{0.7mm}{$\sum_1$} \\\hline
\endhead
\hline
\endfoot
{% for rv in radky_vysledkovky %}{{ rv.poradi }} & {% if rv.titul %}\titul{{ lb }}{{ rv.titul }}}~{% endif %}{{ rv.resitel.osoba.jmeno|slice:":1" }}.~{{ rv.resitel.osoba.prijmeni }} & {% if rv.rocnik_resitele %}{{ rv.rocnik_resitele }}{% endif %} & {{ rv.body_celkem_odjakziva }} {% for b in rv.body_cisla_sezn %} & {{ b }}{% endfor %} & {{ rv.body_rocnik }} \\
{% for rv in vysledkovka.radky_vysledkovky %}{{ rv.poradi }} & {% if rv.titul %}\titul{{ lb }}{{ rv.titul }}}~{% endif %}{{ rv.resitel.osoba.jmeno|slice:":1" }}.~{{ rv.resitel.osoba.prijmeni }} & {% if rv.rocnik_resitele %}{{ rv.rocnik_resitele }}{% endif %} & {{ rv.body_celkem_odjakziva }} {% for b in rv.body_cisla_seznam %} & {{ b }}{% endfor %} & {{ rv.body_rocnik }} \\
{% endfor %}\end{longtable}
{% endwith %}
{% endwith %}

6
seminar/templates/seminar/titulnistrana/titulnistrana.html

@ -19,17 +19,17 @@ function sousdeadline() {
<hr>
<div class="odpocet">
<b><big>Do
{% if typ_deadline == 'soustredeni' %}
{% if nejblizsi_deadline.typ == nejblizsi_deadline.TYP_SOUS or nejblizsi_deadline.typ == nejblizsi_deadline.TYP_PRVNI_A_SOUS %}
<a href="" onClick="sousdeadline()"
title="Body za řešení, která nám přijdou do tohoto deadlinu, se ještě započítají pro účast na připravovaném soustředění.">
deadlinu</a> odeslání <a href="/aktualni/zadani/">řešení
</a> pro účast na soustředění
{% elif typ_deadline == 'preddeadline' %} <a href="" onClick="preddeadline()"
{% elif nejblizsi_deadline.typ == 'preddeadline' %} <a href="" onClick="preddeadline()"
title="Řešení, která nám přijdou do tohoto deadlinu, se pokusíme opravit co nejdříve, abyste měli ještě šanci si je ještě opravit před definitivním deadlinem čísla.">1. deadlinu</a> aktuálního <a href="/aktualni/zadani/">čísla</a>
{% else %} deadlinu aktuálního <a href="/aktualni/zadani/">čísla</a>
{% endif %}zbývá:
{{nejblizsi_deadline|timeuntil}}</big></b>
{{nejblizsi_deadline.deadline|timeuntil}}</big></b>
</div>
<hr>
{% endif %}

4
seminar/templates/seminar/zadani/AktualniVysledkovka.html

@ -8,7 +8,7 @@
{% endblock %}
</h1>
{% if radky_vysledkovky %}
{% if vysledkovka.radky_vysledkovky %}
{% include "vysledkovky/vysledkovka_rocnik.html" %}
{% else %}
<p>V tomto ročníku zatím žádné výsledky nejsou.</p>
@ -22,7 +22,7 @@
{% if user.je_org and vysledkovka_s_neverejnymi %}
<div class='mam-org-only'>
<h1>Výsledky včetně neveřejných</h1>
{% include "vysledkovky/vysledkovka_rocnik_neverejna.html" %}
{% include "vysledkovky/vysledkovka_rocnik.html" with vysledkovka=vysledkovka_neverejna %}
</div>
{% endif %}

22
seminar/templates/seminar/zadani/AktualniZadani.html

@ -16,18 +16,20 @@
<hr>
<div class="zadani_termin">
Termíny pro odeslání řešení {{ac.poradi}}. série:<br>
{% if ac.datum_deadline_soustredeni %}
<span class="datum">{{ac.datum_deadline_soustredeni}}</span> pro účast na soustředění<br>
{% endif %}
{% if ac.datum_preddeadline %}
<span class="datum">{{ac.datum_preddeadline}}</span> pro otištění v dalším čísle<br>
{% endif %}
{% for deadline in ac.deadline_v_cisle.all %}
{% if deadline.typ == deadline.TYP_SOUS or deadline.typ == deadline.TYP_PRVNI_A_SOUS %}
<span class="datum">{{deadline.deadline.date}}</span> pro účast na soustředění<br>
{% endif %}
{% if ac.datum_deadline %}
<span class="datum">{{ac.datum_deadline}}</span> definitivní deadline<br>
{% endif %}
{% if deadline.typ == deadline.TYP_PRVNI or deadline.typ == deadline.TYP_PRVNI_A_SOUS %}
<span class="datum">{{deadline.deadline.date}}</span> pro otištění v dalším čísle<br>
{% endif %}
{% if deadline.typ == deadline.TYP_CISLA %}
<span class="datum">{{deadline.deadline.date}}</span> definitivní deadline<br>
{% endif %}
{% endfor %}
</div>
<hr>

44
seminar/templatetags/deadliny.py

@ -1,44 +1,32 @@
from django import template
from django.utils.safestring import mark_safe
from seminar.utils import TypDeadline, deadline
register = template.Library()
@register.filter(name='deadline')
def deadline_text(datum):
if deadline(datum) is None:
return 'Neznámý deadline'
typ, cislo, dl = deadline(datum)
strings = {
TypDeadline.PredDeadline: f"1. deadline čísla {cislo} ({dl})",
TypDeadline.SousDeadline: f"Soustřeďkový deadline čísla {cislo} ({dl})",
TypDeadline.FinalDeadline: f"Finální deadline čísla {cislo} ({dl})",
}
return strings[typ]
import seminar.models as m
@register.filter(name='deadline_kratseji')
def deadline_kratsi_text(datum):
if deadline(datum) is None:
def deadline_kratsi_text(deadline: m.Deadline):
if deadline is None:
return 'NONE'
typ, cislo, dl = deadline(datum)
strings = {
TypDeadline.PredDeadline: f"{cislo}",
TypDeadline.SousDeadline: f"{cislo}",
TypDeadline.FinalDeadline: f"{cislo}",
m.Deadline.TYP_PRVNI: f"{deadline.cislo}",
m.Deadline.TYP_SOUS: f"{deadline.cislo}",
m.Deadline.TYP_PRVNI_A_SOUS: f"{deadline.cislo} ⭯Ⓢ",
m.Deadline.TYP_CISLA: f"{deadline.cislo}",
}
return strings[typ]
return strings[deadline.typ]
@register.filter(name='deadline_html')
def deadline_html(datum):
if deadline(datum) is None:
def deadline_html(deadline: m.Deadline):
if deadline is None:
return 'Neznámý deadline'
typ, _, _ = deadline(datum)
text = deadline_kratsi_text(datum)
text = deadline_kratsi_text(deadline)
classes = {
TypDeadline.PredDeadline: 'preddeadline',
TypDeadline.SousDeadline: 'sous_deadline',
TypDeadline.FinalDeadline: 'final_deadline',
m.Deadline.TYP_PRVNI: 'preddeadline',
m.Deadline.TYP_SOUS: 'sous_deadline',
m.Deadline.TYP_PRVNI_A_SOUS: 'sous_deadline',
m.Deadline.TYP_CISLA: 'final_deadline',
}
return mark_safe(f'<span class="{classes[typ]}">{text}</span>')
return mark_safe(f'<span class="{classes[deadline.typ]}">{text}</span>')
@register.filter(name='zkrat_nazev_problemu')
def zkrat_nazev_problemu(nazev,width):

149
seminar/test_deadlines.py

@ -1,149 +0,0 @@
from django.test import TestCase
from datetime import date
import seminar.models as m
from seminar.utils import deadline, TypDeadline
class DeadlineTestCase(TestCase):
def setUp(self):
# Chceme pár ročníků a v nich pár čísel
r1 = m.Rocnik.objects.create(rocnik=1, prvni_rok=2000, exportovat=False)
r2 = m.Rocnik.objects.create(rocnik=2, prvni_rok=2001, exportovat=False)
r3 = m.Rocnik.objects.create(rocnik=3, prvni_rok=2002, exportovat=False)
# První číslo mívá soustřeďkový deadline…
c1_1 = m.Cislo.objects.create(rocnik=r1, poradi='1',
datum_vydani=date.fromisoformat('2000-05-22'),
datum_preddeadline=date.fromisoformat('2000-09-11'),
datum_deadline_soustredeni=date.fromisoformat('2000-09-11'),
datum_deadline=date.fromisoformat('2000-10-01'),
)
c1_2 = m.Cislo.objects.create(rocnik=r1, poradi='2',
datum_vydani=date.fromisoformat('2000-10-19'),
datum_preddeadline=date.fromisoformat('2000-12-05'),
datum_deadline=date.fromisoformat('2001-01-02'),
)
# Některá čísla nemají předdeadline…
c1_3 = m.Cislo.objects.create(rocnik=r1, poradi='3',
datum_vydani=date.fromisoformat('2001-01-28'),
datum_deadline=date.fromisoformat('2001-03-12'),
)
# Poslední číslo nemá ani normální deadline…
c1_4 = m.Cislo.objects.create(rocnik=r1, poradi='4-5',
datum_vydani=date.fromisoformat('2001-04-24'),
)
# První číslo dalšího ročníku se někdy vydá dřív, než poslední minulého…
c2_1 = m.Cislo.objects.create(rocnik=r2, poradi='1',
datum_vydani=date.fromisoformat('2001-04-19'),
datum_deadline_soustredeni=date.fromisoformat('2001-09-26'),
datum_deadline=date.fromisoformat('2001-10-07'),
)
# Tohle číslo má finální deadline až po vydání prvního čísla dalšího ročníku
# To samé se skoro stalo na přelomu (reálných) ročníků 27 a 28.
c2_2 = m.Cislo.objects.create(rocnik=r2, poradi='2',
datum_vydani=date.fromisoformat('2002-03-14'),
datum_preddeadline=date.fromisoformat('2002-05-26'),
datum_deadline=date.fromisoformat('2002-06-30'),
)
# Závěrečné číslo druhého ročníku až na podzim
c2_3 = m.Cislo.objects.create(rocnik=r2, poradi='3',
datum_vydani=date.fromisoformat('2002-09-05'),
)
# Divný případ: sous deadline stejný jako finální
c3_1 = m.Cislo.objects.create(rocnik=r3, poradi='1',
datum_vydani=date.fromisoformat('2002-06-02'),
datum_preddeadline=date.fromisoformat('2002-08-31'),
datum_deadline=date.fromisoformat('2002-09-30'),
datum_deadline_soustredeni=date.fromisoformat('2002-09-30'),
)
# Celkový harmonogram:
# 2000-05-22 začátek 1. ročníku, číslo 1.1
# 2000-09-11 sous a 1. deadline 1.1
# 2000-10-01 finální deadline 1.1
# 2000-10-19 Vydání 1.2
# 2000-12-05 předdeadline 1.2
# 2001-01-02 finální deadline 1.2
# 2001-01-28 vyd 1.3
# 2001-03-12 deadline 1.3
# 2001-04-19 Začátek 2. ročníku, číslo 2.1
# 2001-04-24 Vydání 1.4-5 -- závěrečné číslo 1. roč.
# 2001-09-26 Sous-deadline 2.1
# 2001-10-07 Deadline 2.1
# 2002-03-14 Pí den, vydání 2.2
# 2002-05-26 Předdeadline 2.2
# 2002-06-02 Třetí ročník, vydání 3.1
# 2002-06-30 Deadline 2.2
# 2002-08-31 Předdeadline 3.1
# 2002-09-04 Vydání 2.3, konec 2. roč.
# 2002-09-30 Sous a finální deadline 3.1
def test_deadline_spravne_vysledky(self):
"""V každém intervalu mezi deadliny dostáváme ten správný deadline"""
# První ročník
self.assertEqual(deadline(date.fromisoformat('2000-05-30')), (TypDeadline.PredDeadline, m.Cislo.objects.get(rocnik__rocnik=1, poradi='1'), date.fromisoformat('2000-09-11')))
self.assertEqual(deadline(date.fromisoformat('2000-09-15')), (TypDeadline.FinalDeadline, m.Cislo.objects.get(rocnik__rocnik=1, poradi='1'), date.fromisoformat('2000-10-01')))
# Trochu divný případ, kdy někdo něco pošle před vydáním čísla. Ale článkům se to asi stát může…
self.assertEqual(deadline(date.fromisoformat('2000-10-10')), (TypDeadline.PredDeadline, m.Cislo.objects.get(rocnik__rocnik=1, poradi='2'), date.fromisoformat('2000-12-05')))
self.assertEqual(deadline(date.fromisoformat('2000-10-22')), (TypDeadline.PredDeadline, m.Cislo.objects.get(rocnik__rocnik=1, poradi='2'), date.fromisoformat('2000-12-05')))
self.assertEqual(deadline(date.fromisoformat('2000-12-15')), (TypDeadline.FinalDeadline, m.Cislo.objects.get(rocnik__rocnik=1, poradi='2'), date.fromisoformat('2001-01-02')))
self.assertEqual(deadline(date.fromisoformat('2001-01-08')), (TypDeadline.FinalDeadline, m.Cislo.objects.get(rocnik__rocnik=1, poradi='3'), date.fromisoformat('2001-03-12')))
self.assertEqual(deadline(date.fromisoformat('2001-01-30')), (TypDeadline.FinalDeadline, m.Cislo.objects.get(rocnik__rocnik=1, poradi='3'), date.fromisoformat('2001-03-12')))
self.assertEqual(deadline(date.fromisoformat('2001-03-15')), (TypDeadline.SousDeadline, m.Cislo.objects.get(rocnik__rocnik=2, poradi='1'), date.fromisoformat('2001-09-26')))
self.assertEqual(deadline(date.fromisoformat('2001-04-22')), (TypDeadline.SousDeadline, m.Cislo.objects.get(rocnik__rocnik=2, poradi='1'), date.fromisoformat('2001-09-26')))
self.assertEqual(deadline(date.fromisoformat('2001-04-30')), (TypDeadline.SousDeadline, m.Cislo.objects.get(rocnik__rocnik=2, poradi='1'), date.fromisoformat('2001-09-26')))
# Druhý ročník
# Pro jistotu ještě prázdniny
self.assertEqual(deadline(date.fromisoformat('2001-07-30')), (TypDeadline.SousDeadline, m.Cislo.objects.get(rocnik__rocnik=2, poradi='1'), date.fromisoformat('2001-09-26')))
self.assertEqual(deadline(date.fromisoformat('2001-09-27')), (TypDeadline.FinalDeadline, m.Cislo.objects.get(rocnik__rocnik=2, poradi='1'), date.fromisoformat('2001-10-07')))
self.assertEqual(deadline(date.fromisoformat('2001-12-27')), (TypDeadline.PredDeadline, m.Cislo.objects.get(rocnik__rocnik=2, poradi='2'), date.fromisoformat('2002-05-26')))
self.assertEqual(deadline(date.fromisoformat('2002-03-15')), (TypDeadline.PredDeadline, m.Cislo.objects.get(rocnik__rocnik=2, poradi='2'), date.fromisoformat('2002-05-26')))
self.assertEqual(deadline(date.fromisoformat('2002-03-15')), (TypDeadline.PredDeadline, m.Cislo.objects.get(rocnik__rocnik=2, poradi='2'), date.fromisoformat('2002-05-26')))
self.assertEqual(deadline(date.fromisoformat('2002-05-27')), (TypDeadline.FinalDeadline, m.Cislo.objects.get(rocnik__rocnik=2, poradi='2'), date.fromisoformat('2002-06-30')))
# Tohle je trochu podezřelý případ, protože relevantní deadliny existují dvě… Ale ten pro minulý ročník je těsnější a realističtější
self.assertEqual(deadline(date.fromisoformat('2002-06-03')), (TypDeadline.FinalDeadline, m.Cislo.objects.get(rocnik__rocnik=2, poradi='2'), date.fromisoformat('2002-06-30')))
self.assertEqual(deadline(date.fromisoformat('2002-07-01')), (TypDeadline.PredDeadline, m.Cislo.objects.get(rocnik__rocnik=3, poradi='1'), date.fromisoformat('2002-08-31')))
# Třetí ročník
self.assertEqual(deadline(date.fromisoformat('2002-09-01')), (TypDeadline.SousDeadline, m.Cislo.objects.get(rocnik__rocnik=3, poradi='1'), date.fromisoformat('2002-09-30')))
self.assertEqual(deadline(date.fromisoformat('2002-09-05')), (TypDeadline.SousDeadline, m.Cislo.objects.get(rocnik__rocnik=3, poradi='1'), date.fromisoformat('2002-09-30')))
def test_deadline_ve_zlomove_dny(self):
"""Pro dny, kdy je deadline nebo vydání čísla, pořád dostáváme správné deadliny.
Testuje hlavně přítomnost někde nějakých off-by-one"""
self.assertEqual(deadline(date.fromisoformat('2000-05-22')), (TypDeadline.PredDeadline, m.Cislo.objects.get(rocnik__rocnik=1, poradi='1'), date.fromisoformat('2000-09-11')))
self.assertEqual(deadline(date.fromisoformat('2000-09-11')), (TypDeadline.PredDeadline, m.Cislo.objects.get(rocnik__rocnik=1, poradi='1'), date.fromisoformat('2000-09-11')))
self.assertEqual(deadline(date.fromisoformat('2000-10-01')), (TypDeadline.FinalDeadline, m.Cislo.objects.get(rocnik__rocnik=1, poradi='1'), date.fromisoformat('2000-10-01')))
self.assertEqual(deadline(date.fromisoformat('2000-10-19')), (TypDeadline.PredDeadline, m.Cislo.objects.get(rocnik__rocnik=1, poradi='2'), date.fromisoformat('2000-12-05')))
self.assertEqual(deadline(date.fromisoformat('2000-12-05')), (TypDeadline.PredDeadline, m.Cislo.objects.get(rocnik__rocnik=1, poradi='2'), date.fromisoformat('2000-12-05')))
self.assertEqual(deadline(date.fromisoformat('2001-01-02')), (TypDeadline.FinalDeadline, m.Cislo.objects.get(rocnik__rocnik=1, poradi='2'), date.fromisoformat('2001-01-02')))
self.assertEqual(deadline(date.fromisoformat('2001-01-28')), (TypDeadline.FinalDeadline, m.Cislo.objects.get(rocnik__rocnik=1, poradi='3'), date.fromisoformat('2001-03-12')))
self.assertEqual(deadline(date.fromisoformat('2001-03-12')), (TypDeadline.FinalDeadline, m.Cislo.objects.get(rocnik__rocnik=1, poradi='3'), date.fromisoformat('2001-03-12')))
self.assertEqual(deadline(date.fromisoformat('2001-04-19')), (TypDeadline.SousDeadline, m.Cislo.objects.get(rocnik__rocnik=2, poradi='1'), date.fromisoformat('2001-09-26')))
self.assertEqual(deadline(date.fromisoformat('2001-04-24')), (TypDeadline.SousDeadline, m.Cislo.objects.get(rocnik__rocnik=2, poradi='1'), date.fromisoformat('2001-09-26')))
self.assertEqual(deadline(date.fromisoformat('2001-09-26')), (TypDeadline.SousDeadline, m.Cislo.objects.get(rocnik__rocnik=2, poradi='1'), date.fromisoformat('2001-09-26')))
self.assertEqual(deadline(date.fromisoformat('2001-10-07')), (TypDeadline.FinalDeadline, m.Cislo.objects.get(rocnik__rocnik=2, poradi='1'), date.fromisoformat('2001-10-07')))
self.assertEqual(deadline(date.fromisoformat('2002-03-14')), (TypDeadline.PredDeadline, m.Cislo.objects.get(rocnik__rocnik=2, poradi='2'), date.fromisoformat('2002-05-26')))
self.assertEqual(deadline(date.fromisoformat('2002-05-26')), (TypDeadline.PredDeadline, m.Cislo.objects.get(rocnik__rocnik=2, poradi='2'), date.fromisoformat('2002-05-26')))
self.assertEqual(deadline(date.fromisoformat('2002-06-02')), (TypDeadline.FinalDeadline, m.Cislo.objects.get(rocnik__rocnik=2, poradi='2'), date.fromisoformat('2002-06-30')))
self.assertEqual(deadline(date.fromisoformat('2002-06-30')), (TypDeadline.FinalDeadline, m.Cislo.objects.get(rocnik__rocnik=2, poradi='2'), date.fromisoformat('2002-06-30')))
self.assertEqual(deadline(date.fromisoformat('2002-08-31')), (TypDeadline.PredDeadline, m.Cislo.objects.get(rocnik__rocnik=3, poradi='1'), date.fromisoformat('2002-08-31')))
self.assertEqual(deadline(date.fromisoformat('2002-09-04')), (TypDeadline.SousDeadline, m.Cislo.objects.get(rocnik__rocnik=3, poradi='1'), date.fromisoformat('2002-09-30')))
self.assertEqual(deadline(date.fromisoformat('2002-09-30')), (TypDeadline.SousDeadline, m.Cislo.objects.get(rocnik__rocnik=3, poradi='1'), date.fromisoformat('2002-09-30')))
def test_deadline_pro_datetime(self):
"""Testuje, že i pro datetime dostáváme správné deadliny"""
self.skipTest('Chybí implementace testu')
def test_moc_pozdni_deadline(self):
self.assertIsNone(deadline(date.max))

2
seminar/testutils.py

@ -297,7 +297,7 @@ def gen_reseni_ulohy(rnd, cisla, uloha, pocet_resitelu, poradi_cisla, resitele_c
res_vyber.remove(resitele[0])
# Vytvoření řešení.
if uloha.cislo_zadani.datum_deadline is not None:
if uloha.cislo_zadani.zlomovy_deadline_pro_papirove_cislo() is not None:
# combine, abychom dostali plný čas a ne jen datum
cas_doruceni = datetime.datetime.combine(uloha.cislo_zadani.datum_deadline, datetime.datetime.min.time()) - datetime.timedelta(days=random.randint(0, 40)) - datetime.timedelta(minutes=random.randint(0, 60*24))
# astimezone, protože jinak vyhazuje warning o nenastavené TZ

10
seminar/urls.py

@ -72,6 +72,16 @@ urlpatterns = [
org_required(views.resiteleRocnikuCsvExportView),
name='seminar_rocnik_resitele_csv'
),
path(
'rocnik/<int:rocnik>/tituly.tex',
org_required(views.TitulyViewRocnik),
name='seminar_rocnik_titul'
),
path(
'rocnik/<int:rocnik>/posledni_vysledkovka.tex',
org_required(views.PosledniCisloVysledkovkaView.as_view()),
name='seminar_rocnik_posledni_vysledkovka'
),
path(
'cislo/<int:rocnik>.<str:cislo>/vysledkovka.tex',
org_required(views.CisloVysledkovkaView.as_view()),

142
seminar/utils.py

@ -14,9 +14,6 @@ from django.contrib.auth.models import AnonymousUser
from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import ObjectDoesNotExist
from enum import Enum
from enum import auto
import logging
import seminar.models as m
@ -179,11 +176,11 @@ def resi_v_rocniku(rocnik, cislo=None):
if cislo is None:
# filtrujeme pouze podle ročníku
return m.Resitel.objects.filter(rok_maturity__gte=rocnik.druhy_rok(),
reseni__hodnoceni__cislo_body__rocnik=rocnik).distinct()
reseni__hodnoceni__deadline_body__cislo__rocnik=rocnik).distinct()
else: # filtrujeme podle ročníku i čísla
return m.Resitel.objects.filter(rok_maturity__gte=rocnik.druhy_rok(),
reseni__hodnoceni__cislo_body__rocnik=rocnik,
reseni__hodnoceni__cislo_body__poradi__lte=cislo.poradi).distinct()
reseni__hodnoceni__deadline_body__cislo__rocnik=rocnik,
reseni__hodnoceni__deadline_body__cislo__poradi__lte=cislo.poradi).distinct()
def aktivniResitele(cislo, pouze_letosni=False):
@ -248,139 +245,6 @@ def viewMethodSwitch(get, post):
return NewView.as_view()
def cisla_rocniku(rocnik, jen_verejne=True):
"""
Vrátí všechna čísla daného ročníku.
Parametry:
rocnik (Rocnik): ročník semináře
jen_verejne (bool): zda se mají vrátit jen veřejná, nebo všechna čísla
Vrátí:
seznam objektů typu Cislo
"""
if jen_verejne:
return rocnik.verejne_vysledkovky_cisla()
else:
return rocnik.cisla.all().order_by('poradi')
def hlavni_problem(problem):
""" Pro daný problém vrátí jeho nejvyšší nadproblém."""
while not(problem.nadproblem == None):
problem = problem.nadproblem
return problem
def problemy_rocniku(rocnik, jen_verejne=True):
return m.Problem.objects.filter(hodnoceni__in = m.Hodnoceni.objects.filter(cislo_body__in = cisla_rocniku(rocnik, jen_verejne))).distinct().select_related('nadproblem').select_related('nadproblem__nadproblem')
def problemy_cisla(cislo):
""" Vrátí seznam všech problémů s body v daném čísle. """
return m.Problem.objects.filter(hodnoceni__in = m.Hodnoceni.objects.filter(cislo_body = cislo)).distinct().non_polymorphic().select_related('nadproblem').select_related('nadproblem__nadproblem')
def hlavni_problemy_f(problemy=None):
""" Vrátí seznam všech problémů, které již nemají nadproblém. """
# hlavní problémy čísla
# (mají vlastní sloupeček ve výsledkovce, nemají nadproblém)
hlavni_problemy = set()
for p in problemy:
hlavni_problemy.add(hlavni_problem(p))
# zunikátnění
hlavni_problemy = list(hlavni_problemy)
hlavni_problemy.sort(key=lambda k: k.kod_v_rocniku) # setřídit podle t1, t2, c3, ...
return hlavni_problemy
def podproblemy_v_cislu(cislo, problemy=None, hlavni_problemy=None):
""" Vrátí seznam všech problémů s body v daném čísle v poli 'indexovaném' tématy. """
if problemy is None:
problemy = problemy_cisla(cislo)
if hlavni_problemy is None:
hlavni_problemy = hlavni_problemy_f(problemy)
podproblemy = dict((hp.id, []) for hp in hlavni_problemy)
hlavni_problemy = set(hlavni_problemy)
podproblemy[-1] = []
for problem in problemy:
h_problem = hlavni_problem(problem)
if h_problem in hlavni_problemy:
podproblemy[h_problem.id].append(problem)
else:
podproblemy[-1].append(problem)
for podproblem in podproblemy.keys():
def int_or_zero(p):
try:
return int(p.kod)
except ValueError:
return 0
podproblemy[podproblem] = sorted(podproblemy[podproblem], key=int_or_zero)
return podproblemy
class TypDeadline(Enum):
PredDeadline = auto()
SousDeadline = auto()
FinalDeadline = auto()
def deadline_v_rocniku(datum, rocnik):
"""Funkce pro dohledání, ke kterému deadlinu daného ročníku se datum váže.
Vrací trojici (TypDeadline, Cislo, datumDeadline: date).
V případě nevalidního volání není aktuálně chování definováno(!)
"""
cisla = m.Cislo.objects.filter(rocnik=rocnik)
deadliny = []
for c in cisla:
if c.datum_preddeadline is not None:
deadliny.append((TypDeadline.PredDeadline, c, c.datum_preddeadline))
if c.datum_deadline_soustredeni is not None:
deadliny.append((TypDeadline.SousDeadline, c, c.datum_deadline_soustredeni))
if c.datum_deadline is not None:
deadliny.append((TypDeadline.FinalDeadline, c, c.datum_deadline))
deadliny = sorted(deadliny, key=lambda x: x[2]) # podle data
for dl in deadliny:
if datum <= dl[2]:
# První takový deadline je ten nejtěsnější
return dl
logger.error(f'Pro datum {datum} v ročníku {rocnik} neexistuje deadline.')
def deadline(datum):
"""Funkce pro dohledání, ke kterému deadlinu se datum váže.
Vrací trojici (TypDeadline, Cislo, datumDeadline: date). Pokud se deadline nenajde, vrátí None
"""
if isinstance(datum, datetime.datetime):
datum = datum.date()
rok = datum.year
# Dva ročníky podezřelé z obsahování dat
try:
pozdejsi_rocnik = m.Rocnik.objects.get(prvni_rok=rok)
except m.Rocnik.DoesNotExist:
pozdejsi_rocnik = None
try:
drivejsi_rocnik = m.Rocnik.objects.get(prvni_rok=rok-1)
except m.Rocnik.DoesNotExist:
drivejsi_rocnik = None
if drivejsi_rocnik is not None:
# Předpokládáme, že neexistuje číslo, které má deadline ale nemá finální deadline.
# Seznam čísel je potřeba ručně setřídit chronologicky, protože Model říká, že se řadí od nejnovějšího
posledni_deadline_drivejsiho_rocniku = m.Cislo.objects.filter(rocnik=drivejsi_rocnik, datum_deadline__isnull=False).order_by('poradi').last().datum_deadline
logger.debug(f'Nalezené ročníky: {drivejsi_rocnik}, {pozdejsi_rocnik}')
if drivejsi_rocnik is not None and datum <= posledni_deadline_drivejsiho_rocniku:
logger.debug(f'Hledám v dřívějším ročníku: {drivejsi_rocnik}')
return deadline_v_rocniku(datum, drivejsi_rocnik)
else:
logger.debug(f'Hledám v pozdějším ročníku: {pozdejsi_rocnik}')
return deadline_v_rocniku(datum, pozdejsi_rocnik)
def sync_skoly(base_url):
"""Stáhne všechny školy z mamwebu na adrese <base_url> a uloží je do databáze"""

186
seminar/views/views_all.py

@ -11,14 +11,16 @@ from django.core.exceptions import PermissionDenied
import seminar.models as s
import seminar.models as m
from seminar.models import Problem, Cislo, Reseni, Nastaveni, Rocnik, Organizator, Resitel, Novinky, Tema, Clanek # Tohle je stare a chceme se toho zbavit. Pouzivejte s.ToCoChci
from seminar.models import Problem, Cislo, Reseni, Nastaveni, Rocnik, \
Organizator, Resitel, Novinky, Tema, Clanek, \
Deadline # Tohle je stare a chceme se toho zbavit. Pouzivejte s.ToCoChci
#from .models import VysledkyZaCislo, VysledkyKCisluZaRocnik, VysledkyKCisluOdjakziva
from seminar import utils
from treenode import treelib
import treenode.templatetags as tnltt
import treenode.serializers as vr
from vysledkovky.utils import body_resitelu
from vysledkovky.views import vysledkovka_rocniku, vysledkovka_cisla
from vysledkovky.utils import body_resitelu, VysledkovkaCisla, \
VysledkovkaRocniku, VysledkovkaDoTeXu
from datetime import date, datetime
from django.utils import timezone
@ -206,26 +208,17 @@ def ZadaniAktualniVysledkovkaView(request):
nastaveni = get_object_or_404(Nastaveni)
# Aktualni verejna vysledkovka
rocnik = nastaveni.aktualni_rocnik
context = vysledkovka_rocniku(
rocnik=rocnik,
request=request,
sneverejnou=True
)
context = {'vysledkovka': VysledkovkaRocniku(rocnik, True)}
# kdyz neni verejna vysledkovka, tak zobraz starou
if len(context['cisla']) == 0:
if len(context['vysledkovka'].cisla_rocniku) == 0:
try:
minuly_rocnik = Rocnik.objects.get(
prvni_rok=(rocnik.prvni_rok-1))
rocnik=(rocnik.rocnik-1))
rocnik = minuly_rocnik
# Přepíšeme prázdnou výsledkovku výsledkovkou z minulého ročníku
context = vysledkovka_rocniku(
rocnik=rocnik,
context=context,
request=request,
sneverejnou=True
)
context['vysledkovka'] = VysledkovkaRocniku(rocnik, True)
except ObjectDoesNotExist:
pass
@ -268,23 +261,8 @@ class TitulniStranaView(generic.ListView):
context = super(TitulniStranaView, self).get_context_data(**kwargs)
nastaveni = get_object_or_404(Nastaveni)
deadline_soustredeni = (nastaveni.aktualni_cislo.datum_deadline_soustredeni, "soustredeni")
preddeadline = (nastaveni.aktualni_cislo.datum_preddeadline, "preddeadline")
deadline = (nastaveni.aktualni_cislo.datum_deadline, "deadline")
try:
nejblizsi_deadline = sorted(filter(lambda dl: dl[0] is not None and dl[0] >= date.today(), [deadline_soustredeni, preddeadline, deadline]))[0]
if nejblizsi_deadline[0] == deadline_soustredeni[0]:
nejblizsi_deadline = deadline_soustredeni
except IndexError:
nejblizsi_deadline = (None, None) # neni zadna aktualni deadline
if nejblizsi_deadline[0] is not None:
context['nejblizsi_deadline'] = datetime.combine(nejblizsi_deadline[0], datetime.max.time())
else:
context['nejblizsi_deadline'] = None
context['typ_deadline'] = nejblizsi_deadline[1]
deadline = m.Deadline.objects.filter(deadline__gte=timezone.now()).order_by("deadline").first()
context['nejblizsi_deadline'] = deadline
# Aktuální témata
nazvy_a_odkazy_na_aktualni_temata = []
@ -377,17 +355,10 @@ class RocnikView(generic.DetailView):
return get_object_or_404(queryset,rocnik=self.kwargs.get('rocnik'))
def get_context_data(self, **kwargs):
start = time.time()
context = super(RocnikView, self).get_context_data(**kwargs)
context = vysledkovka_rocniku(
rocnik=context["rocnik"],
context=context,
request=self.request,
sneverejnou=True
)
end = time.time()
print("Kontext:", end-start)
context["vysledkovka"] = VysledkovkaRocniku(context["rocnik"], True)
context["neprazdna_vysledkovka"] = len(context['vysledkovka'].cisla_rocniku) != 0
context["vysledkovka_neverejna"] = VysledkovkaRocniku(context["rocnik"], False)
return context
def resiteleRocnikuCsvExportView(request, rocnik):
@ -452,8 +423,23 @@ class CisloView(generic.DetailView):
cislo = context['cislo']
context['prevcislo'] = Cislo.objects.filter((Q(rocnik__lt=self.object.rocnik) | Q(poradi__lt=self.object.poradi))&Q(rocnik__lte=self.object.rocnik)).first()
# vrátíme context (aktuálně obsahuje jen věci ohledně výsledkovky
return vysledkovka_cisla(cislo, context)
deadliny = Deadline.objects.filter(cislo=cislo).reverse()
deadliny_s_vysledkovkami = []
nadpisy = {
m.Deadline.TYP_CISLA: "Výsledkovka",
m.Deadline.TYP_PRVNI: "Výsledkovka do prvního deadlinu",
m.Deadline.TYP_PRVNI_A_SOUS: "Výsledkovka do prvního deadlinu a deadlinu pro účast na soustředění",
m.Deadline.TYP_SOUS: "Výsledkovka do deadlinu pro účast na soustředění",
}
for deadline in deadliny:
if self.request.user.je_org | deadline.verejna_vysledkovka:
deadliny_s_vysledkovkami.append((deadline, nadpisy[deadline.typ], VysledkovkaCisla(cislo, not self.request.user.je_org, deadline)))
context['deadliny_s_vysledkovkami'] = deadliny_s_vysledkovkami
return context
class ArchivTemataView(generic.ListView):
@ -476,17 +462,41 @@ class OdmenyView(generic.TemplateView):
fromcislo = Cislo.objects.get(rocnik=self.kwargs.get('frocnik'), poradi=self.kwargs.get('fcislo'))
tocislo = Cislo.objects.get(rocnik=self.kwargs.get('trocnik'), poradi=self.kwargs.get('tcislo'))
resitele = aktivniResitele(tocislo)
frombody = body_resitelu(resitele, fromcislo)
tobody = body_resitelu(resitele, tocislo)
outlist = []
for (aid, tbody) in tobody.items():
fbody = frombody.get(aid,0)
resitel = Resitel.objects.get(pk=aid)
ftitul = resitel.get_titul(fbody)
ttitul = resitel.get_titul(tbody)
if ftitul != ttitul:
outlist.append({'jmeno': resitel.osoba.plne_jmeno(), 'ftitul': ftitul, 'ttitul': ttitul})
context['zmeny'] = outlist
def get_diff(from_deadline: Deadline, to_deadline: Deadline):
frombody = body_resitelu(resitele=resitele, jen_verejne=False, do=from_deadline)
tobody = body_resitelu(resitele=resitele, jen_verejne=False, do=to_deadline)
outlist = []
for (aid, tbody) in tobody.items():
fbody = frombody.get(aid,0)
resitel = Resitel.objects.get(pk=aid)
ftitul = resitel.get_titul(fbody)
ttitul = resitel.get_titul(tbody)
if ftitul != ttitul:
outlist.append({'jmeno': resitel.osoba.plne_jmeno(), 'ftitul': ftitul, 'ttitul': ttitul})
return outlist
def posledni_deadline_oprava(cislo: Cislo) -> Deadline:
posledni_deadline = cislo.posledni_deadline
if posledni_deadline is None:
return Deadline.objects.filter(Q(cislo__poradi__lt=cislo.poradi, cislo__rocnik=cislo.rocnik) | Q(cislo__rocnik__rocnik__lt=cislo.rocnik.rocnik)).order_by("deadline").last()
return posledni_deadline
context["from_cislo"] = fromcislo
context["to_cislo"] = tocislo
context["zmeny_prvni_prvni"] = get_diff(
fromcislo.zlomovy_deadline_pro_papirove_cislo(),
tocislo.zlomovy_deadline_pro_papirove_cislo()
)
context["zmeny_prvni_posledni"] = get_diff(
fromcislo.zlomovy_deadline_pro_papirove_cislo(),
posledni_deadline_oprava(tocislo)
)
context["zmeny_posledni_prvni"] = get_diff(
posledni_deadline_oprava(fromcislo),
tocislo.zlomovy_deadline_pro_papirove_cislo()
)
return context
@ -504,6 +514,60 @@ class CisloVysledkovkaView(CisloView):
content_type = 'text/plain; charset=UTF8'
#vypise na stranku textovy obsah vyTeXane vysledkovky k okopirovani
def get_context_data(self, **kwargs):
context = super(CisloVysledkovkaView, self).get_context_data()
cislo = context['cislo']
cislopred = cislo.predchozi()
if cislopred is not None:
context['vysledkovka'] = VysledkovkaDoTeXu(
cislo,
od_vyjma=cislopred.zlomovy_deadline_pro_papirove_cislo(),
do_vcetne=cislo.zlomovy_deadline_pro_papirove_cislo(),
)
else:
context['vysledkovka'] = VysledkovkaCisla(
cislo,
jen_verejne=False,
do_deadlinu=cislo.zlomovy_deadline_pro_papirove_cislo(),
)
return context
# Podle předchozího
class PosledniCisloVysledkovkaView(generic.DetailView):
"""View vytvořené pro zobrazení výsledkovky posledního čísla v TeXu."""
model = Rocnik
template_name = 'seminar/archiv/cislo_vysledkovka.tex'
content_type = 'text/plain; charset=UTF8'
def get_object(self, queryset=None):
if queryset is None:
queryset = self.get_queryset()
rocnik_arg = self.kwargs.get('rocnik')
queryset = queryset.filter(rocnik=rocnik_arg)
try:
obj = queryset.get()
except queryset.model.DoesNotExist:
raise Http404(_("No %(verbose_name)s found matching the query") %
{'verbose_name': queryset.model._meta.verbose_name})
return obj
def get_context_data(self, **kwargs):
context = super(PosledniCisloVysledkovkaView, self).get_context_data()
rocnik = context['rocnik']
cislo = rocnik.cisla.order_by("poradi").last()
cislopred = cislo.predchozi()
context['vysledkovka'] = VysledkovkaDoTeXu(
cislo,
od_vyjma=cislopred.zlomovy_deadline_pro_papirove_cislo(),
do_vcetne=cislo.deadline_v_cisle.order_by("deadline").last(),
)
return context
class RocnikVysledkovkaView(RocnikView):
""" View vytvořené pro stránku zobrazující výsledkovku ročníku v TeXu."""
model = Rocnik
@ -556,17 +620,23 @@ def oldObalkovaniView(request, rocnik, cislo):
### Tituly
def TitulyViewRocnik(request, rocnik):
return TitulyView(request, rocnik, None)
def TitulyView(request, rocnik, cislo):
""" View pro stažení makra titulů v TeXu."""
rocnik_obj = Rocnik.objects.get(rocnik = rocnik)
resitele = Resitel.objects.filter(rok_maturity__gte = rocnik_obj.prvni_rok)
cislo_obj = Cislo.objects.get(rocnik = rocnik_obj, poradi = cislo)
asciijmena = []
jmenovci = False # detekuje, zda jsou dva řešitelé jmenovci (modulo nabodeníčka),
# pokud ano, vrátí se jako true
slovnik_s_body = body_resitelu(resitele, cislo_obj)
if cislo is not None:
cislo_obj = Cislo.objects.get(rocnik=rocnik_obj, poradi=cislo)
slovnik_s_body = body_resitelu(do=cislo_obj.zlomovy_deadline_pro_papirove_cislo(), jen_verejne=False)
else:
slovnik_s_body = body_resitelu(do=Deadline.objects.filter(cislo__rocnik=rocnik_obj).last(), jen_verejne=False)
for resitel in resitele:
resitel.titul = resitel.get_titul(slovnik_s_body[resitel.id])

58
vysledkovky/templates/vysledkovky/vysledkovka_cisla.html

@ -3,21 +3,21 @@
<tr class='border-b'>
<th class='border-r'>#
<th class='border-r'>Jméno
{% for p in problemy %}
<th class='border-r' id="problem{{ forloop.counter0 }}">{# <a href="{{ p.verejne_url }}"> #}{{ p.kod_v_rocniku }}{# </a> #}
{% for p in vysledkovka.temata_a_spol%}
<th class='border-r' id="problem{{ oznaceni_vysledkovky }}_{{ forloop.counter0 }}">{# <a href="{{ p.verejne_url }}"> #}{{ p.kod_v_rocniku }}{# </a> #}
{# TODELETE #}
{% for podproblemy in podproblemy_iter.next %}
<th class='border-r podproblem{{ forloop.parentloop.counter0 }} podproblem'>{# <a href="{{ podproblemy.verejne_url }}"> #}{{ podproblemy.kod_v_rocniku }}{# </a> #}
{% for podproblemy in vysledkovka.podproblemy_iter.next %}
<th class='border-r podproblem{{ oznaceni_vysledkovky }}_{{ forloop.parentloop.counter0 }} podproblem'>{# <a href="{{ podproblemy.verejne_url }}"> #}{{ podproblemy.kod_v_rocniku }}{# </a> #}
{% endfor %}
{# TODELETE #}
{% endfor %}
{% if ostatni %}<th class='border-r' id='problem{{ problemy | length }}'>Ostatní {% endif %}
{% if vysledkovka.je_nejake_ostatni %}<th class='border-r' id='problem{{ oznaceni_vysledkovky }}_{{ vysledkovka.temata_a_spol| length }}'>Ostatní {% endif %}
{# TODELETE #}
{% for podproblemy in podproblemy_iter.next %}
<th class='border-r podproblem{{ problemy | length }} podproblem'>{# <a href="{{ podproblemy.verejne_url }}"> #}{{ podproblemy.kod_v_rocniku }}{# </a> #}
{% for podproblemy in vysledkovka.podproblemy_iter.next %}
<th class='border-r podproblem{{ oznaceni_vysledkovky }}_{{ vysledkovka.temata_a_spol| length }} podproblem'>{# <a href="{{ podproblemy.verejne_url }}"> #}{{ podproblemy.kod_v_rocniku }}{# </a> #}
{% endfor %}
{# TODELETE #}
@ -25,7 +25,7 @@
<th class='border-r'>Za číslo
<th class='border-r'>Za ročník
<th class='border-r'>Odjakživa
{% for rv in radky_vysledkovky %}
{% for rv in vysledkovka.radky_vysledkovky %}
<tr>
<td class='border-r'>{% autoescape off %}{{ rv.poradi }}{% endautoescape %}
<th class='border-r'>
@ -33,14 +33,12 @@
{{ rv.titul }}<sup>MM</sup>
{% endif %}
{{ rv.resitel.osoba.plne_jmeno }}
{% for b in rv.body_problemy_sezn %}
{% for b in rv.body_za_temata_seznam %}
<td class='border-r'>{{ b }}
{# TODELETE #}
{% for body_podproblemu in rv.body_podproblemy_iter.next %}
<td class='border-r podproblem{{ forloop.parentloop.counter0 }} podproblem'>{{ body_podproblemu }}
<td class='border-r podproblem{{ oznaceni_vysledkovky }}_{{ forloop.parentloop.counter0 }} podproblem'>{{ body_podproblemu }}
{% endfor %}
{# TODELETE #}
{% endfor %}
<td class='border-r'>{{ rv.body_cislo }}
@ -55,29 +53,29 @@
{# TODELETE #}
<script>
{% for p in problemy %}
diplayed{{ forloop.counter0 }} = false;
$(".podproblem{{ forloop.counter0 }}").css("display", "none")
$("#problem{{ forloop.counter0 }}")[0].addEventListener('click', podproblem{{ forloop.counter0 }});
function podproblem{{ forloop.counter0 }}(event) {
diplayed{{ forloop.counter0 }} = !diplayed{{ forloop.counter0 }};
if (diplayed{{ forloop.counter0 }}) {
$(".podproblem{{ forloop.counter0 }}").css("display", "");
{% for p in vysledkovka.temata_a_spol%}
displayed{{ oznaceni_vysledkovky }}_{{ forloop.counter0 }} = false;
$(".podproblem{{ oznaceni_vysledkovky }}_{{ forloop.counter0 }}").css("display", "none")
$("#problem{{ oznaceni_vysledkovky }}_{{ forloop.counter0 }}")[0].addEventListener('click', podproblem{{ oznaceni_vysledkovky }}_{{ forloop.counter0 }});
function podproblem{{ oznaceni_vysledkovky }}_{{ forloop.counter0 }}(event) {
displayed{{ oznaceni_vysledkovky }}_{{ forloop.counter0 }} = !displayed{{ oznaceni_vysledkovky }}_{{ forloop.counter0 }};
if (displayed{{ oznaceni_vysledkovky }}_{{ forloop.counter0 }}) {
$(".podproblem{{ oznaceni_vysledkovky }}_{{ forloop.counter0 }}").css("display", "");
} else {
$(".podproblem{{ forloop.counter0 }}").css("display", "none");
$(".podproblem{{ oznaceni_vysledkovky }}_{{ forloop.counter0 }}").css("display", "none");
}
}
{% endfor %}
{% if ostatni %}
diplayed{{ problemy | length }} = false;
$(".podproblem{{ problemy | length }}").css("display", "none")
$("#problem{{ problemy | length }}")[0].addEventListener('click', podproblem{{ problemy | length }});
function podproblem{{ problemy | length }}(event) {
diplayed{{ problemy | length }} = !diplayed{{ problemy | length }};
if (diplayed{{ problemy | length }}) {
$(".podproblem{{ problemy | length }}").css("display", "");
{% if vysledkovka.je_nejake_ostatni %}
displayed{{ oznaceni_vysledkovky }}_{{ vysledkovka.temata_a_spol| length }} = false;
$(".podproblem{{ oznaceni_vysledkovky }}_{{ vysledkovka.temata_a_spol| length }}").css("display", "none")
$("#problem{{ oznaceni_vysledkovky }}_{{ vysledkovka.temata_a_spol| length }}")[0].addEventListener('click', podproblem{{ oznaceni_vysledkovky }}_{{ vysledkovka.temata_a_spol| length }});
function podproblem{{ oznaceni_vysledkovky }}_{{ vysledkovka.temata_a_spol| length }}(event) {
displayed{{ oznaceni_vysledkovky }}_{{ vysledkovka.temata_a_spol| length }} = !displayed{{ oznaceni_vysledkovky }}_{{ vysledkovka.temata_a_spol| length }};
if (displayed{{ oznaceni_vysledkovky }}_{{ vysledkovka.temata_a_spol| length }}) {
$(".podproblem{{ oznaceni_vysledkovky }}_{{ vysledkovka.temata_a_spol| length }}").css("display", "");
} else {
$(".podproblem{{ problemy | length }}").css("display", "none");
$(".podproblem{{ oznaceni_vysledkovky }}_{{ vysledkovka.temata_a_spol| length }}").css("display", "none");
}
}
{% endif %}

6
vysledkovky/templates/vysledkovky/vysledkovka_rocnik.html

@ -4,13 +4,13 @@
<th class='border-r'>Jméno
<th class='border-r'>R.
<th class='border-r'>Odjakživa
{% for c in cisla %}
{% for c in vysledkovka.cisla_rocniku %}
<th class='border-r'><a href="{{ c.verejne_url }}">
{{c.rocnik.rocnik}}.{{ c.poradi }}</a>
{% endfor %}
<th class='border-r'>Celkem
{% for rv in radky_vysledkovky %}
{% for rv in vysledkovka.radky_vysledkovky %}
<tr>
<td class='border-r'>{% autoescape off %}{{ rv.poradi }}{% endautoescape %}
<th class='border-r'>
@ -20,7 +20,7 @@
{{ rv.resitel.osoba.plne_jmeno }}
<td class='border-r'>{{ rv.rocnik_resitele }}
<td class='border-r'>{{ rv.body_celkem_odjakziva }}
{% for b in rv.body_cisla_sezn %}
{% for b in rv.body_cisla_seznam %}
<td class='border-r'>{{ b }}
{% endfor %}
<td class='border-r'><b>{{ rv.body_rocnik }}</b>

1
vysledkovky/templates/vysledkovky/vysledkovka_rocnik_neverejna.html

@ -1 +0,0 @@
{% include "vysledkovky/vysledkovka_rocnik.html" with radky_vysledkovky=radky_vysledkovky_s_neverejnymi cisla=cisla_s_neverejnymi %}

914
vysledkovky/utils.py

@ -1,462 +1,492 @@
import abc
from functools import cached_property
from typing import Union # TODO: s pythonem 3.10 přepsat na '|'
import seminar.models as m
from django.db.models import Q, Sum, Count
from seminar.utils import aktivniResitele, resi_v_rocniku, cisla_rocniku, hlavni_problem, hlavni_problemy_f, problemy_cisla, podproblemy_v_cislu
import time
from django.db.models import Q, Sum
from seminar.utils import resi_v_rocniku
ROCNIK_ZRUSENI_TEMAT = 25
def sloupec_s_poradim(setrizene_body):
"""
Ze seznamu obsahujícího sestupně setřízené body řešitelů za daný ročník
vytvoří seznam s pořadími (včetně 3.-5. a pak 2 volná místa atp.),
podle toho, jak jdou za sebou ve výsledkovce.
Parametr:
setrizene_body (seznam integerů): sestupně setřízená čísla
Výstup:
sloupec_s_poradim (seznam stringů)
"""
# ze seznamu obsahujícího setřízené body spočítáme sloupec s pořadím
aktualni_poradi = 1
sloupec_s_poradim = []
# seskupíme seznam všech bodů podle hodnot
for index in range(0, len(setrizene_body)):
# pokud je pořadí větší než číslo řádku, tak jsme vypsali větší rozsah a chceme
# vypsat už jen prázdné místo, než dojdeme na správný řádek
if (index + 1) < aktualni_poradi:
sloupec_s_poradim.append("")
continue
velikost_skupiny = 0
# zjistíme počet po sobě jdoucích stejných hodnot
while setrizene_body[index] == setrizene_body[index + velikost_skupiny]:
velikost_skupiny = velikost_skupiny + 1
# na konci musíme ošetřit přetečení seznamu
if (index + velikost_skupiny) > len(setrizene_body) - 1:
break
# pokud je velikost skupiny 1, vypíšu pořadí
if velikost_skupiny == 1:
sloupec_s_poradim.append("{}.".format(aktualni_poradi))
# pokud je skupina větší, vypíšu rozsah
else:
sloupec_s_poradim.append("{}.–{}.".format(aktualni_poradi,
aktualni_poradi+velikost_skupiny-1))
# zvětšíme aktuální pořadí o tolik, kolik pozic bylo přeskočeno
aktualni_poradi = aktualni_poradi + velikost_skupiny
return sloupec_s_poradim
def body_resitelu(resitele, za, odjakziva=True, jen_verejne=False):
""" Funkce počítající počty bodů pro zadané řešitele,
buď odjakživa do daného ročníku/čísla anebo za daný ročník/číslo.
Parametry:
resitele (seznam obsahující položky typu Resitel): aktivní řešitelé
za (Rocnik/Cislo): za co se mají počítat body
(generování starších výsledkovek)
odjakziva (bool): zda se mají počítat body odjakživa, nebo jen za číslo/ročník
zadané v "za"
Výstup:
slovník (Resitel.id):body
"""
resitele_id = [r.id for r in resitele]
# Zjistíme, typ objektu v parametru "za"
if isinstance(za, m.Rocnik):
cislo = None
rocnik = za
rok = rocnik.prvni_rok
elif isinstance(za, m.Cislo):
cislo = za
rocnik = None
rok = cislo.rocnik.prvni_rok
else:
assert True, "body_resitelu: za není ani číslo ani ročník."
# Kvůli rychlosti používáme sčítáme body už v databázi, viz
# https://docs.djangoproject.com/en/3.0/topics/db/aggregation/,
# sekce Filtering on annotations (protože potřebujeme filtrovat výsledky
# jen do nějakého data, abychom uměli správně nagenerovat výsledkovky i
# za historická čísla.
# Níže se vytváří dotaz na součet bodů za správně vyfiltrovaná hodnocení,
# který se použije ve výsledném dotazu.
if cislo and odjakziva: # Body se sčítají odjakživa do zadaného čísla.
# Vyfiltrujeme všechna hodnocení, která jsou buď ze starších ročníků,
# anebo ze stejného ročníku, jak je zadané číslo, tam ale sčítáme jen
# pro čísla s pořadím nejvýše stejným, jako má zadané číslo.
body_k_zapocteni = Sum('reseni__hodnoceni__body',
filter=( Q(reseni__hodnoceni__cislo_body__rocnik__prvni_rok__lt=rok) |
Q(reseni__hodnoceni__cislo_body__rocnik__prvni_rok=rok,
reseni__hodnoceni__cislo_body__poradi__lte=cislo.poradi) ))
elif cislo and not odjakziva: # Body se sčítají za dané číslo.
body_k_zapocteni = Sum('reseni__hodnoceni__body',
filter=( Q(reseni__hodnoceni__cislo_body__rocnik__prvni_rok=rok,
reseni__hodnoceni__cislo_body__poradi__lte=cislo.poradi) ))
elif rocnik and odjakziva: # Spočítáme body za starší ročníky až do zadaného včetně.
if jen_verejne:
body_k_zapocteni = Sum('reseni__hodnoceni__body',
filter= Q(reseni__hodnoceni__cislo_body__rocnik__prvni_rok__lte=rok,
reseni__hodnoceni__cislo_body__verejna_vysledkovka=True))
else:
body_k_zapocteni = Sum('reseni__hodnoceni__body',
filter= Q(reseni__hodnoceni__cislo_body__rocnik__prvni_rok__lte=rok))
elif rocnik and not odjakziva: # Spočítáme body za daný ročník.
if jen_verejne:
body_k_zapocteni = Sum('reseni__hodnoceni__body',
filter=Q(reseni__hodnoceni__cislo_body__rocnik=rocnik,
reseni__hodnoceni__cislo_body__verejna_vysledkovka=True))
else:
body_k_zapocteni = Sum('reseni__hodnoceni__body',
filter=Q(reseni__hodnoceni__cislo_body__rocnik=rocnik))
else:
assert True, "body_resitelu: Neplatná kombinace za a odjakživa."
# Následující řádek přidá ke každému řešiteli údaj ".body" se součtem jejich bodů
resitele_s_body = m.Resitel.objects.filter(id__in=resitele_id).annotate(
body=body_k_zapocteni)
# Teď jen z QuerySetu řešitelů anotovaných body vygenerujeme slovník
# indexovaný řešitelským id obsahující body.
# Pokud jsou body None, nahradíme za 0.
slovnik = {int(res.id) : (res.body if res.body else 0) for res in resitele_s_body}
return slovnik
class RadekVysledkovkyRocniku(object):
""" Obsahuje věci, které se hodí vědět při konstruování výsledkovky.
Umožňuje snazší práci v templatu (lepší, než seznam)."""
def __init__(self, poradi, resitel, body_cisla_sezn, body_rocnik, body_odjakziva, rok):
self.poradi = poradi
self.resitel = resitel
self.rocnik_resitele = resitel.rocnik(rok)
self.body_rocnik = body_rocnik
self.body_celkem_odjakziva = body_odjakziva
self.body_cisla_sezn = body_cisla_sezn
self.titul = resitel.get_titul(body_odjakziva)
def setrid_resitele_a_body(slov_resitel_body):
setrizeni_resitele_id = [dvojice[0] for dvojice in slov_resitel_body]
setrizene_body = [dvojice[1] for dvojice in slov_resitel_body]
return setrizeni_resitele_id, setrizene_body
def data_vysledkovky_rocniku(rocnik, jen_verejne=True):
""" Přebírá ročník (např. context["rocnik"]) a vrací výsledkovou listinu ve
formě vhodné pro šablonu "seminar/vysledkovka_rocniku.html"
"""
start = time.time()
## TODO možná chytřeji vybírat aktivní řešitele
# aktivní řešitelé - chceme letos něco poslal, TODO později vyfiltrujeme ty, kdo mají
# u alespoň jedné hodnoty něco jiného než NULL
aktivni_resitele = list(resi_v_rocniku(rocnik))
cisla = cisla_rocniku(rocnik, jen_verejne)
body_cisla_slov = {}
for cislo in cisla:
# získáme body za číslo
_, cislobody = secti_body_za_cislo(cislo, aktivni_resitele)
body_cisla_slov[cislo.id] = cislobody
# získáme body za ročník, seznam obsahuje dvojice (řešitel_id, body) setřízené sestupně
resitel_rocnikbody_sezn = secti_body_za_rocnik(rocnik, aktivni_resitele, jen_verejne=jen_verejne)
# setřídíme řešitele podle počtu bodů a získáme seznam s body od nejvyšších po nenižší
setrizeni_resitele_id, setrizene_body = setrid_resitele_a_body(resitel_rocnikbody_sezn)
poradi = sloupec_s_poradim(setrizene_body)
# získáme body odjakživa
resitel_odjakzivabody_slov = body_resitelu(aktivni_resitele, rocnik, jen_verejne=jen_verejne)
# vytvoříme jednotlivé sloupce výsledkovky
radky_vysledkovky = []
i = 0
setrizeni_resitele_dict = {} # Tento slovnik se vyrab
for r in m.Resitel.objects.filter(id__in=setrizeni_resitele_id).select_related('osoba'):
setrizeni_resitele_dict[r.id] = r
for ar_id in setrizeni_resitele_id:
# seznam počtu bodů daného řešitele pro jednotlivá čísla
body_cisla_sezn = []
for cislo in cisla:
body_cisla_sezn.append(body_cisla_slov[cislo.id][ar_id])
# vytáhneme informace pro daného řešitele
radek = RadekVysledkovkyRocniku(
poradi[i], # pořadí
setrizeni_resitele_dict[ar_id], # řešitel (z id)
body_cisla_sezn, # seznam bodů za čísla
setrizene_body[i], # body za ročník (spočítané výše s pořadím)
resitel_odjakzivabody_slov[ar_id], # body odjakživa
rocnik) # ročník semináře pro získání ročníku řešitele
radky_vysledkovky.append(radek)
i += 1
end = time.time()
print("Vysledkovka rocniku",end-start)
radky_vysledkovky = [radek for radek in radky_vysledkovky if radek.body_rocnik > 0]
return radky_vysledkovky, cisla
class RadekVysledkovkyCisla(object):
"""Obsahuje věci, které se hodí vědět při konstruování výsledkovky.
Umožňuje snazší práci v templatu (lepší, než seznam)."""
def __init__(self, poradi, resitel, body_problemy_sezn,
body_cislo, body_rocnik, body_odjakziva, rok, body_podproblemy, body_podproblemy_iter):
self.resitel = resitel
self.rocnik_resitele = resitel.rocnik(rok)
self.body_cislo = body_cislo
self.body_rocnik = body_rocnik
self.body_celkem_odjakziva = body_odjakziva
self.poradi = poradi
self.body_problemy_sezn = body_problemy_sezn
self.titul = resitel.get_titul(body_odjakziva)
self.body_podproblemy = body_podproblemy
self.body_podproblemy_iter = body_podproblemy_iter # TODELETE
def pricti_body(slovnik, resitel, body):
""" Přiřazuje danému řešiteli body do slovníku. """
# testujeme na None (""), pokud je to první řešení
# daného řešitele, předěláme na 0
# (v dalším kroku přičteme reálný počet bodů),
# rozlišujeme tím mezi 0 a neodevzdaným řešením
# Speciálně pokud jsou body None (hodnocení není obodované), vraťse
# TODO nejde to udělat lépe?
if body is None:
return
if slovnik[resitel.id] == "":
slovnik[resitel.id] = 0
slovnik[resitel.id] += body
def secti_body_za_rocnik(za, aktivni_resitele, jen_verejne):
""" Spočítá body za ročník (celý nebo do daného čísla),
setřídí je sestupně a vrátí jako seznam.
Parametry:
za (typu Rocnik nebo Cislo) spočítá za ročník, nebo za ročník do
daného čísla
"""
# spočítáme všem řešitelům jejich body za ročník (False => ne odjakživa)
resitel_rocnikbody_slov = body_resitelu(aktivni_resitele, za, False, jen_verejne=jen_verejne)
# zeptáme se na dvojice (řešitel, body) za ročník a setřídíme sestupně
resitel_rocnikbody_sezn = sorted(resitel_rocnikbody_slov.items(),
key = lambda x: x[1], reverse = True)
return resitel_rocnikbody_sezn
def secti_body_za_cislo(cislo, aktivni_resitele, hlavni_problemy=None):
""" Spočítá u řešitelů body za číslo a za jednotlivé hlavní problémy (témata)."""
# TODO setřídit hlavní problémy čísla podle id, ať jsou ve stejném pořadí pokaždé
# pro každý hlavní problém zavedeme slovník s body za daný hlavní problém
# pro jednotlivé řešitele (slovník slovníků hlavních problémů)
print("Scitam cislo",cislo)
if hlavni_problemy is None:
hlavni_problemy = hlavni_problemy_f(problemy_cisla(cislo))
def ne_clanek_ne_konfera(problem):
inst = problem.get_real_instance()
return not(isinstance(inst, m.Clanek) or isinstance(inst, m.Konfera))
if cislo.rocnik.rocnik < ROCNIK_ZRUSENI_TEMAT:
temata_a_spol = hlavni_problemy
else:
temata_a_spol = list(filter(ne_clanek_ne_konfera, hlavni_problemy))
hlavni_problemy_slovnik = {}
for hp in temata_a_spol:
hlavni_problemy_slovnik[hp.id] = {}
hlavni_problemy_slovnik[-1] = {}
# zakládání prázdných záznamů pro řešitele
cislobody = {}
for ar in aktivni_resitele:
# řešitele převedeme na řetězec pomocí unikátního id
cislobody[ar.id] = ""
for hp in temata_a_spol:
slovnik = hlavni_problemy_slovnik[hp.id]
slovnik[ar.id] = ""
hlavni_problemy_slovnik[-1][ar.id] = ""
hodnoceni_do_cisla = m.Hodnoceni.objects.prefetch_related('problem', 'reseni', 'reseni__resitele').filter(cislo_body=cislo)
start = time.time()
for hodnoceni in hodnoceni_do_cisla:
prob = hodnoceni.problem
nadproblem = hlavni_problem(prob)
if ne_clanek_ne_konfera(nadproblem):
nadproblem_slovnik = hlavni_problemy_slovnik[nadproblem.id]
else:
nadproblem_slovnik = hlavni_problemy_slovnik[-1]
body = hodnoceni.body
# a mít více řešitelů
for resitel in hodnoceni.reseni.resitele.all():
if resitel not in aktivni_resitele:
print("Skipping {}".format(resitel.id))
continue
pricti_body(cislobody, resitel, body)
pricti_body(nadproblem_slovnik, resitel, body)
end = time.time()
print("for cykly:", end-start)
return hlavni_problemy_slovnik, cislobody
def secti_body_za_cislo_podle_temat(cislo, aktivni_resitele, podproblemy=None, temata=None):
""" Spočítá u řešitelů body za číslo za úlohy v jednotlivých hlavních problémech (témata)."""
if temata is None:
temata = hlavni_problemy_f(problemy_cisla(cislo))
if podproblemy is None:
podproblemy_v_cislu(cislo, hlavni_problemy=temata)
body_slovnik = {}
for tema in temata:
body_slovnik[tema.id] = {}
for problem in podproblemy[tema.id]:
body_slovnik[tema.id][problem.id] = {}
body_slovnik[-1] = {}
for problem in podproblemy[-1]:
body_slovnik[-1][problem.id] = {}
# zakládání prázdných záznamů pro řešitele
for ar in aktivni_resitele:
for tema in temata:
for problem in podproblemy[tema.id]:
body_slovnik[tema.id][problem.id][ar.id] = ""
for problem in podproblemy[-1]:
body_slovnik[-1][problem.id][ar.id] = ""
temata = set(t.id for t in temata)
hodnoceni_do_cisla = m.Hodnoceni.objects.prefetch_related('problem', 'reseni', 'reseni__resitele').filter(cislo_body=cislo)
for hodnoceni in hodnoceni_do_cisla:
prob = hodnoceni.problem
nadproblem = hlavni_problem(prob)
if nadproblem.id in temata:
nadproblem_slovnik = body_slovnik[nadproblem.id]
else:
nadproblem_slovnik = body_slovnik[-1]
problem_slovnik = nadproblem_slovnik[prob.id]
body = hodnoceni.body
# a mít více řešitelů
for resitel in hodnoceni.reseni.resitele.all():
if resitel not in aktivni_resitele:
print("Skipping {}".format(resitel.id))
continue
pricti_body(problem_slovnik, resitel, body)
return body_slovnik
# TODELETE
class FixedIterator:
def next(self):
return self.niter.__next__()
def __init__(self, niter):
self.niter = niter
# TODELETE
def data_vysledkovky_cisla(cislo):
problemy = problemy_cisla(cislo)
hlavni_problemy = hlavni_problemy_f(problemy)
## TODO možná chytřeji vybírat aktivní řešitele
# aktivní řešitelé - chceme letos něco poslal, TODO později vyfiltrujeme ty, kdo mají
# u alespoň jedné hodnoty něco jiného než NULL
aktivni_resitele = list(resi_v_rocniku(cislo.rocnik))
def body_resitelu(
za: Union[m.Cislo, m.Rocnik, None] = None,
do: m.Deadline = None,
od: m.Deadline = None,
jen_verejne: bool = True,
resitele=None,
null=0 # Výchozí hodnota, pokud pro daného řešitele nejsou body
) -> dict[int, int]:
filtr = Q()
if jen_verejne:
filtr &= Q(reseni__hodnoceni__deadline_body__verejna_vysledkovka=True)
# Zjistíme, typ objektu v parametru "za"
if isinstance(za, m.Rocnik):
filtr &= Q(reseni__hodnoceni__deadline_body__cislo__rocnik=za)
elif isinstance(za, m.Cislo):
filtr &= Q(reseni__hodnoceni__deadline_body__cislo=za)
# získáme body za číslo
hlavni_problemy_slovnik, cislobody = secti_body_za_cislo(cislo, aktivni_resitele, hlavni_problemy)
if do:
filtr &= Q(reseni__hodnoceni__deadline_body__deadline__lte=do.deadline)
# získáme body za ročník, seznam obsahuje dvojice (řešitel_id, body) setřízené sestupně
resitel_rocnikbody_sezn = secti_body_za_rocnik(cislo, aktivni_resitele, jen_verejne=True)
if od:
filtr &= Q(reseni__hodnoceni__deadline_body__deadline__gte=od.deadline)
# získáme body odjakživa
resitel_odjakzivabody_slov = body_resitelu(aktivni_resitele, cislo, jen_verejne=True)
resiteleQuery = m.Resitel.objects.all()
# řešitelé setřídění podle bodů za číslo sestupně
setrizeni_resitele_id = [dvojice[0] for dvojice in resitel_rocnikbody_sezn]
# spočítáme pořadí řešitelů
setrizeni_resitele_body = [dvojice[1] for dvojice in resitel_rocnikbody_sezn]
poradi = sloupec_s_poradim(setrizeni_resitele_body)
if resitele is not None:
resitele_id = [r.id for r in resitele]
resiteleQuery = resiteleQuery.filter(id__in=resitele_id)
# vytvoříme jednotlivé sloupce výsledkovky
radky_vysledkovky = []
i = 0
# Přidáme ke každému řešiteli údaj ".body" se součtem jejich bodů
resitele_s_body = resiteleQuery.annotate(
body=Sum('reseni__hodnoceni__body', filter=filtr))
# Teď jen z QuerySetu řešitelů anotovaných body vygenerujeme slovník
# indexovaný řešitelským id obsahující body.
# Pokud jsou body None, nahradíme za 0.
slovnik = {
int(res.id): (res.body if res.body else null) for res in resitele_s_body
}
return slovnik
class Vysledkovka(abc.ABC):
jen_verejne: bool
rocnik: m.Rocnik
do_deadlinu: m.Deadline
@property
@abc.abstractmethod
def aktivni_resitele(self) -> list[m.Resitel]:
...
@cached_property
def resitele_s_body_za_rocnik_setrizeny_seznam(self) -> list[tuple[int, int]]:
# spočítáme všem řešitelům jejich body za ročník
resitel_body_za_rocnik_slovnik = body_resitelu(
resitele=self.aktivni_resitele,
za=self.rocnik,
jen_verejne=self.jen_verejne,
do=self.do_deadlinu
)
# zeptáme se na dvojice (řešitel, body) za ročník a setřídíme sestupně
resitele_s_body_za_rocnik_setrizeny_seznam = sorted(
resitel_body_za_rocnik_slovnik.items(),
key=lambda x: x[1], reverse=True
)
return resitele_s_body_za_rocnik_setrizeny_seznam
@cached_property
def body_za_rocnik_seznamy(self) -> tuple[list[int], list[int]]:
if len(self.resitele_s_body_za_rocnik_setrizeny_seznam) == 0:
return [], []
return tuple(zip(*self.resitele_s_body_za_rocnik_setrizeny_seznam))
@property
def setrizeni_resitele_id(self) -> list[int]:
return self.body_za_rocnik_seznamy[0]
@property
def setrizene_body(self) -> list[int]:
return self.body_za_rocnik_seznamy[1]
@cached_property
def resitel_body_odjakziva_slovnik(self) -> dict[int, int]:
return body_resitelu(jen_verejne=self.jen_verejne, do=self.do_deadlinu)
@cached_property
def poradi(self):
# ze seznamu obsahujícího setřízené body spočítáme sloupec s pořadím
aktualni_poradi = 1
sloupec_s_poradim = []
# seskupíme seznam všech bodů podle hodnot
for index in range(0, len(self.setrizene_body)):
# pokud je pořadí větší než číslo řádku, tak jsme vypsali větší rozsah
# a chceme vypsat už jen prázdné místo, než dojdeme na správný řádek
if (index + 1) < aktualni_poradi:
sloupec_s_poradim.append("")
continue
velikost_skupiny = 0
# zjistíme počet po sobě jdoucích stejných hodnot
while self.setrizene_body[index] == self.setrizene_body[
index + velikost_skupiny]:
velikost_skupiny += 1
# na konci musíme ošetřit přetečení seznamu
if (index + velikost_skupiny) > len(self.setrizene_body) - 1:
break
# pokud je velikost skupiny 1, vypíšu pořadí
if velikost_skupiny == 1:
sloupec_s_poradim.append(f"{aktualni_poradi}.")
# pokud je skupina větší, vypíšu rozsah
else:
sloupec_s_poradim.append(
f"{aktualni_poradi}.–{aktualni_poradi + velikost_skupiny - 1}."
)
# zvětšíme aktuální pořadí o tolik, kolik pozic bylo přeskočeno
aktualni_poradi += velikost_skupiny
return sloupec_s_poradim
class VysledkovkaRocniku(Vysledkovka):
def __init__(self, rocnik: m.Rocnik, jen_verejne: bool = True):
self.rocnik = rocnik
self.jen_verejne = jen_verejne
self.do_deadlinu = m.Deadline.objects.filter(cislo__rocnik=rocnik).last()
@cached_property
def aktivni_resitele(self) -> list[m.Resitel]:
return list(resi_v_rocniku(self.rocnik))
@cached_property
def cisla_rocniku(self) -> list[m.Cislo]:
""" Vrátí všechna čísla daného ročníku. """
if self.jen_verejne:
return self.rocnik.verejne_vysledkovky_cisla()
else:
return self.rocnik.cisla.all().order_by('poradi')
@cached_property
def body_za_cisla_slovnik(self) -> dict[int, dict[int, int]]: # Výstup: m.Cislo.id → ( m.Resitel.id → body )
# TODO: Body jsou decimal!
body_cisla_slovnik = dict()
for cislo in self.cisla_rocniku:
# získáme body za číslo
body_za_cislo = body_resitelu(
za=cislo,
resitele=self.aktivni_resitele,
jen_verejne=self.jen_verejne,
null=""
)
body_cisla_slovnik[cislo.id] = body_za_cislo
return body_cisla_slovnik
class RadekVysledkovkyRocniku:
# TODO: přepsat na dataclass
""" Obsahuje věci, které se hodí vědět při konstruování výsledkovky.
Umožňuje snazší práci v templatu (lepší, než seznam)."""
def __init__(self, poradi, resitel, body_cisla_seznam, body_rocnik, body_odjakziva, rok):
self.poradi = poradi
self.resitel = resitel
self.rocnik_resitele = resitel.rocnik(rok)
self.body_rocnik = body_rocnik
self.body_celkem_odjakziva = body_odjakziva
self.body_cisla_seznam = body_cisla_seznam
self.titul = resitel.get_titul(body_odjakziva)
@cached_property
def radky_vysledkovky(self) -> list[RadekVysledkovkyRocniku]:
radky_vysledkovky = []
setrizeni_resitele_dict = dict()
for r in m.Resitel.objects.filter(
id__in=self.setrizeni_resitele_id
).select_related('osoba'):
setrizeni_resitele_dict[r.id] = r
for i, ar_id in enumerate(self.setrizeni_resitele_id):
if self.setrizene_body[i] > 0:
# seznam počtu bodů daného řešitele pro jednotlivá čísla
body_cisla_seznam = []
for cislo in self.cisla_rocniku:
body_cisla_seznam.append(self.body_za_cisla_slovnik[cislo.id][ar_id])
# Pokud řešitel dostal nějaké body
if self.resitele_s_body_za_rocnik_setrizeny_seznam[i] != 0:
# vytáhneme informace pro daného řešitele
radek = self.RadekVysledkovkyRocniku(
poradi=self.poradi[i],
resitel=setrizeni_resitele_dict[ar_id],
body_cisla_seznam=body_cisla_seznam,
body_rocnik=self.setrizene_body[i],
body_odjakziva=self.resitel_body_odjakziva_slovnik[ar_id],
rok=self.rocnik) # ročník semináře pro získání ročníku řešitele
radky_vysledkovky.append(radek)
return radky_vysledkovky
class VysledkovkaCisla(Vysledkovka):
def __init__(
self,
cislo: m.Cislo,
jen_verejne: bool = True,
do_deadlinu: m.Deadline = None
):
self.cislo = cislo
self.rocnik = cislo.rocnik
self.jen_verejne = jen_verejne
if do_deadlinu is None:
do_deadlinu = m.Deadline.objects.filter(cislo=cislo).last()
self.do_deadlinu = do_deadlinu
@cached_property
def aktivni_resitele(self) -> list[m.Resitel]:
# TODO možná chytřeji vybírat aktivní řešitele
return list(resi_v_rocniku(self.rocnik))
@cached_property
def problemy(self) -> list[m.Problem]:
""" Vrátí seznam všech problémů s body v daném čísle. """
return m.Problem.objects.filter(
hodnoceni__in=m.Hodnoceni.objects.filter(deadline_body__cislo=self.cislo)
).distinct().non_polymorphic().select_related('nadproblem').select_related('nadproblem__nadproblem')
@cached_property
def hlavni_problemy(self) -> list[m.Problem]:
""" Vrátí seznam všech problémů, které již nemají nadproblém. """
# hlavní problémy čísla
# (mají vlastní sloupeček ve výsledkovce, nemají nadproblém)
hlavni_problemy = set()
for p in self.problemy:
hlavni_problemy.add(p.hlavni_problem)
# zunikátnění
hlavni_problemy = list(hlavni_problemy)
hlavni_problemy.sort(
key=lambda k: k.kod_v_rocniku) # setřídit podle t1, t2, c3, ...
return hlavni_problemy
# Není cached, protože si myslím, že queryset lze použít ve for jen jednou.
@property
def hodnoceni_do_cisla(self):
hodnoceni = m.Hodnoceni.objects.prefetch_related('reseni__resitele').select_related('problem', 'reseni')
if self.jen_verejne:
hodnoceni = hodnoceni.filter(deadline_body__verejna_vysledkovka=True)
return hodnoceni.filter(
deadline_body__cislo=self.cislo,
deadline_body__deadline__lte=self.do_deadlinu.deadline,
body__isnull=False,
)
@cached_property
def sectene_body(self):
"""
Sečte body za číslo, hlavní problémy a podproblémy.
Problém s ID '-1' znamená problémy bez nadproblémů, jež nejsou témata, tj. články, úlohy, konfery,
"""
# Body za číslo
body_za_cislo = {ar.id: "" for ar in self.aktivni_resitele}
# Body za hlavní problémy
body_za_temata = {
hp.id: {ar.id: "" for ar in self.aktivni_resitele}
for hp in self.temata_a_spol
}
# Ostatní body
body_za_temata[-1] = {ar.id: "" for ar in self.aktivni_resitele}
# Body za podproblémy
body_za_problemy = {
tema.id: {
problem.id: {ar.id: "" for ar in self.aktivni_resitele}
for problem in self.podproblemy[tema.id]
}
for tema in self.temata_a_spol
}
# Ostatní body
body_za_problemy[-1] = {
problem.id: {ar.id: "" for ar in self.aktivni_resitele}
for problem in self.podproblemy[-1]
}
# Sečteme hodnocení
for hodnoceni in self.hodnoceni_do_cisla:
prob = hodnoceni.problem
nadproblem = prob.hlavni_problem.id
# Když nadproblém není "téma", pak je "Ostatní"
if nadproblem not in body_za_temata:
nadproblem = -1
problem_slovnik = body_za_problemy[nadproblem][prob.id]
nadproblem_slovnik = body_za_temata[nadproblem]
body = hodnoceni.body
# Může mít více řešitelů
for resitel in hodnoceni.reseni.resitele.all():
if resitel not in self.aktivni_resitele:
continue
self.pricti_body(body_za_cislo, resitel, body)
self.pricti_body(nadproblem_slovnik, resitel, body)
self.pricti_body(problem_slovnik, resitel, body)
return body_za_cislo, body_za_temata, body_za_problemy
@cached_property
def body_za_temata(self) -> dict[int, dict[int, str]]:
return self.sectene_body[1]
@cached_property
def body_za_cislo(self) -> dict[int, str]:
return self.sectene_body[0]
@cached_property
def problemy_slovnik(self):
return self.sectene_body[2]
@cached_property
def temata_a_spol(self) -> list[m.Problem]:
if self.rocnik.rocnik < ROCNIK_ZRUSENI_TEMAT:
return self.hlavni_problemy
else:
return list(filter(self.ne_clanek_ne_konfera, self.hlavni_problemy))
@cached_property
def je_nejake_ostatni(self):
return len(self.hlavni_problemy) - len(self.temata_a_spol) > 0
@cached_property
def podproblemy(self) -> dict[int, list[m.Problem]]:
podproblemy = {hp.id: [] for hp in self.temata_a_spol}
temata_a_spol = set(self.temata_a_spol)
podproblemy[-1] = []
for problem in self.problemy:
h_problem = problem.hlavni_problem
if h_problem in temata_a_spol:
podproblemy[h_problem.id].append(problem)
else:
podproblemy[-1].append(problem)
for podproblem in podproblemy.keys():
def int_or_zero(p):
try:
return int(p.kod)
except ValueError:
return 0
podproblemy[podproblem] = sorted(podproblemy[podproblem], key=int_or_zero)
return podproblemy
@cached_property
def podproblemy_seznam(self) -> list[list[m.Problem]]:
return [self.podproblemy[it.id] for it in self.temata_a_spol] + [self.podproblemy[-1]]
@cached_property
def podproblemy_iter(self) -> FixedIterator:
return FixedIterator(self.podproblemy_seznam.__iter__())
class RadekVysledkovkyCisla(object):
# TODO: Přepsat na dataclass
"""Obsahuje věci, které se hodí vědět při konstruování výsledkovky.
Umožňuje snazší práci v templatu (lepší, než seznam)."""
def __init__(self, poradi, resitel, temata_seznamk, body_cislo, body_rocnik, body_odjakziva, rok, body_podproblemy, body_podproblemy_iter):
self.resitel = resitel
self.rocnik_resitele = resitel.rocnik(rok)
self.body_cislo = body_cislo
self.body_rocnik = body_rocnik
self.body_celkem_odjakziva = body_odjakziva
self.poradi = poradi
self.body_za_temata_seznam = temata_seznamk
self.titul = resitel.get_titul(body_odjakziva)
self.body_podproblemy = body_podproblemy
self.body_podproblemy_iter = body_podproblemy_iter
@cached_property
def radky_vysledkovky(self) -> list[RadekVysledkovkyCisla]:
# vytvoříme jednotlivé sloupce výsledkovky
radky_vysledkovky = []
setrizeni_resitele_slovnik = {}
setrizeni_resitele = m.Resitel.objects.filter(id__in=self.setrizeni_resitele_id).select_related('osoba')
for r in setrizeni_resitele:
setrizeni_resitele_slovnik[r.id] = r
for i, ar_id in enumerate(self.setrizeni_resitele_id):
if self.setrizene_body[i] > 0:
# získáme seznam bodů za problémy pro daného řešitele
body_problemy = []
body_podproblemy = []
for hp in self.temata_a_spol:
body_problemy.append(self.body_za_temata[hp.id][ar_id])
body_podproblemy.append([
self.problemy_slovnik[hp.id][it.id][ar_id]
for it in self.podproblemy[hp.id]
])
if self.je_nejake_ostatni:
body_problemy.append(self.body_za_temata[-1][ar_id])
body_podproblemy.append(
[self.problemy_slovnik[-1][it.id][ar_id] for it in self.podproblemy[-1]])
# vytáhneme informace pro daného řešitele
radek = self.RadekVysledkovkyCisla(
poradi=self.poradi[i],
resitel=setrizeni_resitele_slovnik[ar_id],
temata_seznamk=body_problemy,
body_cislo=self.body_za_cislo[ar_id],
body_rocnik=self.setrizene_body[i],
body_odjakziva=self.resitel_body_odjakziva_slovnik[ar_id],
rok=self.rocnik,
body_podproblemy=body_podproblemy, # body všech podproblémů
body_podproblemy_iter=FixedIterator(body_podproblemy.__iter__())
) # ročník semináře pro zjištění ročníku řešitele
radky_vysledkovky.append(radek)
return radky_vysledkovky
@staticmethod
def pricti_body(slovnik, resitel, body):
""" Přiřazuje danému řešiteli body do slovníku. """
# testujeme na None (""), pokud je to první řešení
# daného řešitele, předěláme na 0
# (v dalším kroku přičteme reálný počet bodů),
# rozlišujeme tím mezi 0 a neodevzdaným řešením
if slovnik[resitel.id] == "":
slovnik[resitel.id] = 0
slovnik[resitel.id] += body
@staticmethod
def ne_clanek_ne_konfera(problem):
return not(isinstance(problem.get_real_instance(), m.Clanek) or isinstance(problem.get_real_instance(), m.Konfera))
if cislo.rocnik.rocnik < ROCNIK_ZRUSENI_TEMAT:
temata_a_spol = hlavni_problemy
else:
temata_a_spol = list(filter(ne_clanek_ne_konfera, hlavni_problemy))
# získáme body u jednotlivých témat
podproblemy = podproblemy_v_cislu(cislo, problemy, temata_a_spol)
problemy_slovnik = secti_body_za_cislo_podle_temat(cislo, aktivni_resitele, podproblemy, temata_a_spol)
# def not_empty(value):
# return value != ''
#
# je_nejake_ostatni = any(filter(not_empty, hlavni_problemy_slovnik[-1].values())) > 0
je_nejake_ostatni = len(hlavni_problemy) - len(temata_a_spol) > 0
setrizeni_resitele_slovnik = {}
setrizeni_resitele = m.Resitel.objects.filter(id__in=setrizeni_resitele_id).select_related('osoba')
for r in setrizeni_resitele:
setrizeni_resitele_slovnik[r.id] = r
for ar_id in setrizeni_resitele_id:
# získáme seznam bodů za problémy pro daného řešitele
body_problemy = []
body_podproblemy = []
for hp in temata_a_spol:
body_problemy.append(hlavni_problemy_slovnik[hp.id][ar_id])
body_podproblemy.append([problemy_slovnik[hp.id][it.id][ar_id] for it in podproblemy[hp.id]])
if je_nejake_ostatni:
body_problemy.append(hlavni_problemy_slovnik[-1][ar_id])
body_podproblemy.append([problemy_slovnik[-1][it.id][ar_id] for it in podproblemy[-1]])
# vytáhneme informace pro daného řešitele
radek = RadekVysledkovkyCisla(
poradi[i], # pořadí
setrizeni_resitele_slovnik[ar_id], # řešitel (z id)
body_problemy, # seznam bodů za hlavní problémy čísla
cislobody[ar_id], # body za číslo
setrizeni_resitele_body[i], # body za ročník (spočítané výše s pořadím)
resitel_odjakzivabody_slov[ar_id], # body odjakživa
cislo.rocnik,
body_podproblemy, # body všech podproblémů
FixedIterator(body_podproblemy.__iter__()) # TODELETE
) # ročník semináře pro zjištění ročníku řešitele
radky_vysledkovky.append(radek)
i += 1
# vytahané informace předáváme do kontextu
pt = [podproblemy[it.id] for it in temata_a_spol]+[podproblemy[-1]]
radky_vysledkovky = [radek for radek in radky_vysledkovky if radek.body_rocnik > 0]
return (
radky_vysledkovky,
temata_a_spol,
je_nejake_ostatni,
pt,
FixedIterator(pt.__iter__())
)
inst = problem.get_real_instance()
return not (isinstance(inst, m.Clanek) or isinstance(inst, m.Konfera))
class VysledkovkaDoTeXu(VysledkovkaCisla):
def __init__(
self,
nejake_cislo: m.Cislo,
od_vyjma: m.Deadline,
do_vcetne: m.Deadline
):
super().__init__(nejake_cislo, False, do_vcetne)
self.od_deadlinu = od_vyjma
@cached_property
def problemy(self) -> list[m.Problem]:
return m.Problem.objects.filter(hodnoceni__in=m.Hodnoceni.objects.filter(
deadline_body__deadline__gt=self.od_deadlinu.deadline,
deadline_body__deadline__lte=self.do_deadlinu.deadline,
)).distinct().non_polymorphic().select_related('nadproblem').select_related('nadproblem__nadproblem')
@property
def hodnoceni_do_cisla(self):
hodnoceni = m.Hodnoceni.objects.prefetch_related(
'problem', 'reseni', 'reseni__resitele')
if self.jen_verejne:
hodnoceni = hodnoceni.filter(deadline_body__verejna_vysledkovka=True)
return hodnoceni.filter(
deadline_body__deadline__gt=self.od_deadlinu.deadline,
deadline_body__deadline__lte=self.do_deadlinu.deadline,
body__isnull=False,
)

37
vysledkovky/views.py

@ -1,37 +0,0 @@
from .utils import data_vysledkovky_cisla, \
data_vysledkovky_rocniku
def vysledkovka_cisla(cislo, context=None):
if context is None:
context = {}
context['cislo'] = cislo
(
context['radky_vysledkovky'],
context['problemy'],
context['ostatni'],
context['podproblemy'],
context['podproblemy_iter']
) = data_vysledkovky_cisla(cislo)
return context
def vysledkovka_rocniku(rocnik, context=None, request=None, sneverejnou=False):
if context is None:
context = {}
(
context['radky_vysledkovky'],
context['cisla']
) = data_vysledkovky_rocniku(rocnik)
context['vysledkovka'] = len(context['cisla']) != 0
if sneverejnou and request and request.user.je_org:
(
context['radky_vysledkovky_s_neverejnymi'],
context['cisla_s_neverejnymi']
) = data_vysledkovky_rocniku(rocnik, jen_verejne=False)
return context
Loading…
Cancel
Save