Browse Source

Merge branch 'data_migrations' of gimli.ms.mff.cuni.cz:/akce/mam/git/mamweb into data_migrations

export_seznamu_prednasek
Kateřina Č 4 years ago
parent
commit
f960853239
  1. 34
      data/sitetree.json
  2. 2
      mamweb/settings_common.py
  3. 42
      mamweb/templates/base.html
  4. 22
      seminar/forms.py
  5. 37
      seminar/migrations/0001_squashed_0067_auto_20190814_0805.py
  6. 13
      seminar/migrations/0051_resitel_to_osoba.py
  7. 46
      seminar/migrations/0052_user_to_organizator.py
  8. 19
      seminar/models.py
  9. 5
      seminar/templates/seminar/odevzdavatko/detail.html
  10. 38
      seminar/templates/seminar/odevzdavatko/resitel_prehled.html
  11. 6
      seminar/templates/seminar/odevzdavatko/seznam.html
  12. 2
      seminar/templates/seminar/profil/edit.html
  13. 1
      seminar/templates/seminar/profil/resitel.html
  14. 35
      seminar/templatetags/deadliny.py
  15. 1
      seminar/urls.py
  16. 14
      seminar/utils.py
  17. 37
      seminar/views/odevzdavatko.py
  18. 17
      seminar/views/views_all.py
  19. 0
      various/__init__.py
  20. 3
      various/admin.py
  21. 5
      various/apps.py
  22. 14
      various/context_processors.py
  23. 0
      various/migrations/__init__.py
  24. 3
      various/models.py
  25. 3
      various/tests.py
  26. 3
      various/views.py

34
data/sitetree.json

@ -695,13 +695,13 @@
"alias": null, "alias": null,
"description": "", "description": "",
"hidden": false, "hidden": false,
"hint": "", "hint": "To, co ŘEŠITELÉ poslali",
"inbreadcrumbs": true, "inbreadcrumbs": true,
"inmenu": true, "inmenu": true,
"insitetree": true, "insitetree": true,
"parent": 21, "parent": 21,
"sort_order": 37, "sort_order": 38,
"title": "Odevzdaná řešení", "title": "Došlá řešení",
"tree": 1, "tree": 1,
"url": "odevzdavatko_tabulka", "url": "odevzdavatko_tabulka",
"urlaspattern": true "urlaspattern": true
@ -724,7 +724,7 @@
"inmenu": true, "inmenu": true,
"insitetree": true, "insitetree": true,
"parent": 21, "parent": 21,
"sort_order": 38, "sort_order": 42,
"title": "Odhlásit se", "title": "Odhlásit se",
"tree": 1, "tree": 1,
"url": "logout", "url": "logout",
@ -806,5 +806,31 @@
}, },
"model": "sitetree.treeitem", "model": "sitetree.treeitem",
"pk": 41 "pk": 41
},
{
"fields": {
"access_guest": false,
"access_loggedin": false,
"access_perm_type": 1,
"access_permissions": [
2
],
"access_restricted": true,
"alias": null,
"description": "",
"hidden": false,
"hint": "To, co jsem JÁ odevzdal",
"inbreadcrumbs": true,
"inmenu": true,
"insitetree": true,
"parent": 21,
"sort_order": 37,
"title": "Odevzdaná řešení",
"tree": 1,
"url": "seminar_resitel_odevzdana_reseni",
"urlaspattern": true
},
"model": "sitetree.treeitem",
"pk": 42
} }
] ]

2
mamweb/settings_common.py

@ -78,6 +78,7 @@ TEMPLATES = [
'django.contrib.messages.context_processors.messages', 'django.contrib.messages.context_processors.messages',
'sekizai.context_processors.sekizai', 'sekizai.context_processors.sekizai',
'header_fotky.context_processors.vzhled', 'header_fotky.context_processors.vzhled',
'various.context_processors.april',
) )
}, },
}, },
@ -132,6 +133,7 @@ INSTALLED_APPS = (
'korektury', 'korektury',
'prednasky', 'prednasky',
'header_fotky', 'header_fotky',
'various',
# Admin upravy: # Admin upravy:

42
mamweb/templates/base.html

@ -131,6 +131,48 @@
$("a[rel^='gallery-image']").prettyPhoto(prettyparams); $("a[rel^='gallery-image']").prettyPhoto(prettyparams);
}); });
</script> </script>
{% if april == 2021 %}
<script type="text/javascript" charset="utf-8">
function rotace(vektor, uhel_deg) {
var uhel = uhel_deg *(Math.PI / 180);
var x = vektor[0];
var y = vektor[1];
return [x*Math.cos(uhel) - y*Math.sin(uhel), x*Math.sin(uhel) + y*Math.cos(uhel)];
}
function rotace_a_posun(obj, uhel) {
var ow = obj.width();
var oh = obj.height();
var rohy = [[0,0], [0,oh], [ow, 0], [ow, oh]];
var minx = 0;
var miny = 0;
for (var roh of rohy) {
var otoceny = rotace(roh, uhel);
if (otoceny[0] < minx) {
minx = otoceny[0];
}
if (otoceny[1] < miny) {
miny = otoceny[1];
}
}
miny *= -1;
minx *= -1;
var transf_str = "translateX("+minx+"px) translateY("+miny+"px) rotate("+uhel+"deg)";
obj.css('transform-origin', 'top left');
obj.css('transform', transf_str);
}
function randomUhel() {
return Math.floor(360*Math.random());
}
$('.container').css('margin', 0);
rotace_a_posun($('.container'), randomUhel());
</script>
{% endif %}
{% render_block "js" %} {% render_block "js" %}
</body> </body>
</html> </html>

22
seminar/forms.py

@ -142,6 +142,28 @@ class PrihlaskaForm(forms.Form):
elif data.get('skola_adresa')=='': elif data.get('skola_adresa')=='':
self.add_error('skola_adresa',forms.ValidationError('Je nutné vyplnit adresu školy')) self.add_error('skola_adresa',forms.ValidationError('Je nutné vyplnit adresu školy'))
# Editační formulář bez řešitele.
class ProfileEditFormPoMaturite(forms.Form):
username = forms.CharField(label='Přihlašovací jméno',
max_length=256,
required=True)
jmeno = forms.CharField(label='Jméno', max_length=256, required=True)
prijmeni = forms.CharField(label='Příjmení', max_length=256, required=True)
pohlavi_muz = forms.ChoiceField(label='Pohlaví',
choices = ((True,'muž'),(False,'žena')), required=True)
email = forms.EmailField(label='E-mail',max_length=256, required=True)
telefon = forms.CharField(widget=TelInput(),label='Telefon',max_length=256, required=False)
datum_narozeni = forms.DateField(widget=DateInput(),label='Datum narození', required=False)
ulice = forms.CharField(label='Ulice', max_length=256, required=False)
mesto = forms.CharField(label='Město', max_length=256, required=False)
psc = forms.CharField(label='PSČ', max_length=32, required=False)
stat = forms.ChoiceField(label='Stát',
choices = (('CZ', 'Česká Republika'),
('SK', 'Slovenská Republika'),
('other', 'Jiné')),
required=False)
stat_text = forms.CharField(label='Stát', max_length=256, required=False)
class ProfileEditForm(forms.Form): class ProfileEditForm(forms.Form):
username = forms.CharField(label='Přihlašovací jméno', username = forms.CharField(label='Přihlašovací jméno',

37
seminar/migrations/0001_squashed_0067_auto_20190814_0805.py

@ -107,15 +107,41 @@ def spoj_k_organizatorum_osoby(apps, scema_editor):
for org in Organizator.objects.all(): for org in Organizator.objects.all():
# Spárování organizátora s osobou # Spárování organizátora s osobou
# Myšlenka: Když najdeme řešitele pro daného uživatele, tak se vezme Osoba příslušná uživateli,
# Pokud nenajdeme uživatele, tak ještě zkusíme dohledat Osobu podle e-mailu
user = org.user user = org.user
if user is None:
logger.error(f'Org {org} nemá uživatele!')
# Je to podezřelé, ale prostě vyrobíme novou osobu.
osoba = Osoba(user=user)
# Téhle osobě nejdou nastavit detaily, protože žádné nemáme.
else:
logger.info(f'Org {org.user.email}(ID: {org.id}) má uživatele {user}')
# 💢💢💢 Python nemá goto, ale prý má výjimky… 💢💢💢
class EndException(Exception): pass
try:
# Hledáme podle uživatele
resitele = Resitel.objects.filter(user=user) resitele = Resitel.objects.filter(user=user)
if resitele.count() != 0: if resitele.count() != 0 and user is not None:
osoba = resitele.first().osoba osoba = resitele.first().osoba
else: logger.info(f'Našel jsem řešitele {resitele.first().email} podle uživatele, používám jeho Osobu')
raise EndException
# Hledáme podle e-mailu
osoby = Osoba.objects.filter(email__iexact=user.email)
if osoby.count() != 0 and user.email != '':
osoba = osoby.first()
if osoba.user is None:
osoba.user = user
logger.info(f'Našel jsem Osobu {osoby.first().email} podle e-mailu')
raise EndException
# Fallback
logger.warning(f'Org neměl řešitele, zakládám novou Osobu.')
osoba = Osoba(user=user) osoba = Osoba(user=user)
# Přesun informací z usera do osoby # Přesun informací z usera do osoby
# pro řešitele již v minule migraci # pro osoby z řešitelů (jediné dosud existující osoby) již v minule migraci
osoba.jmeno = user.first_name osoba.jmeno = user.first_name
osoba.prijmeni = user.last_name osoba.prijmeni = user.last_name
osoba.email = user.email osoba.email = user.email
@ -124,6 +150,8 @@ def spoj_k_organizatorum_osoby(apps, scema_editor):
user.email = "Použij osobu!" user.email = "Použij osobu!"
user.save() user.save()
except EndException: pass
# Přesun informací z organizátora do jeho osoby # Přesun informací z organizátora do jeho osoby
osoba.prezdivka = org.prezdivka if org.prezdivka is not None else '' osoba.prezdivka = org.prezdivka if org.prezdivka is not None else ''
osoba.foto = org.foto osoba.foto = org.foto
@ -142,7 +170,10 @@ def fix_problem(apps, schema_editor):
else: else:
pr.autor = None pr.autor = None
if pr.opravovatel is not None: if pr.opravovatel is not None:
if Organizator.objects.filter(osoba__user=pr.opravovatel).first() is not None:
pr.opravovatele.add(Organizator.objects.filter(osoba__user=pr.opravovatel).first()) pr.opravovatele.add(Organizator.objects.filter(osoba__user=pr.opravovatel).first())
else:
logger.error(f'WTF, nespárovaný opravovatel {pr.opravovatel} problému {pr}')
pr.save() pr.save()
def fix_pohadka(apps, schema_editor): def fix_pohadka(apps, schema_editor):

13
seminar/migrations/0051_resitel_to_osoba.py

@ -28,26 +28,23 @@ def resitel_to_osoba(apps,schema_editor):
if u.first_name: if u.first_name:
if not o.jmeno: if not o.jmeno:
o.jmeno = u.first_name o.jmeno = u.first_name
u.first_name = 'Použij osobu!' u.first_name += ' (Uživatel!)'
elif o.jmeno == u.first_name: elif o.jmeno == u.first_name:
u.first_name = 'Použij osobu!' u.first_name += ' (Uživatel!)'
else: else:
raise ValueError('jmeno a first_name rozdílné: "{}" vs. "{}"'.format(o.jmeno, u.first_name)) raise ValueError('jmeno a first_name rozdílné: "{}" vs. "{}"'.format(o.jmeno, u.first_name))
if u.last_name: if u.last_name:
if not o.prijmeni: if not o.prijmeni:
o.prijmeni = u.last_name o.prijmeni = u.last_name
u.last_name = 'Použij osobu!' u.last_name += ' (Uživatel!)'
elif o.prijmeni == u.last_name: elif o.prijmeni == u.last_name:
u.last_name = 'Použij osobu!' u.last_name += ' (Uživatel!)'
else: else:
raise ValueError('prijmeni a last_name rozdílné: "{}" vs. "{}"'.format(o.prijmeni, u.last_name)) raise ValueError('prijmeni a last_name rozdílné: "{}" vs. "{}"'.format(o.prijmeni, u.last_name))
if u.email: if u.email:
if not o.email: if not o.email:
o.email = u.email o.email = u.email
u.email = 'Použij osobu!' elif o.email != u.email:
elif o.email == u.email:
u.email = 'Použij osobu!'
else:
raise ValueError('o.email a u.email rozdílné: "{}" vs. "{}"'.format(o.email, u.email)) raise ValueError('o.email a u.email rozdílné: "{}" vs. "{}"'.format(o.email, u.email))
u.save() u.save()

46
seminar/migrations/0052_user_to_organizator.py

@ -4,6 +4,10 @@ from __future__ import unicode_literals
from django.db import migrations from django.db import migrations
import logging
logger = logging.getLogger(__name__)
def spoj_k_organizatorum_osoby(apps, scema_editor): def spoj_k_organizatorum_osoby(apps, scema_editor):
Organizator = apps.get_model('seminar', 'Organizator') Organizator = apps.get_model('seminar', 'Organizator')
Resitel = apps.get_model('seminar', 'Resitel') Resitel = apps.get_model('seminar', 'Resitel')
@ -11,23 +15,50 @@ def spoj_k_organizatorum_osoby(apps, scema_editor):
for org in Organizator.objects.all(): for org in Organizator.objects.all():
# Spárování organizátora s osobou # Spárování organizátora s osobou
# Myšlenka: Když najdeme řešitele pro daného uživatele, tak se vezme Osoba příslušná uživateli,
# Pokud nenajdeme uživatele, tak ještě zkusíme dohledat Osobu podle e-mailu
user = org.user user = org.user
if user is None:
logger.error(f'Org {org} nemá uživatele!')
# Je to podezřelé, ale prostě vyrobíme novou osobu.
osoba = Osoba(user=user)
# Téhle osobě nejdou nastavit detaily, protože žádné nemáme.
else:
logger.info(f'Org {org.user.email}(ID: {org.id}) má uživatele {user}')
# 💢💢💢 Python nemá goto, ale prý má výjimky… 💢💢💢
class EndException(Exception): pass
try:
# Hledáme podle uživatele
resitele = Resitel.objects.filter(user=user) resitele = Resitel.objects.filter(user=user)
if resitele.count() != 0: if resitele.count() != 0 and user is not None:
osoba = resitele.first().osoba osoba = resitele.first().osoba
else: logger.info(f'Našel jsem řešitele {resitele.first().email} podle uživatele, používám jeho Osobu')
raise EndException
# Hledáme podle e-mailu
osoby = Osoba.objects.filter(email__iexact=user.email)
if osoby.count() != 0 and user.email != '':
osoba = osoby.first()
if osoba.user is None:
osoba.user = user
logger.info(f'Našel jsem Osobu {osoby.first().email} podle e-mailu')
raise EndException
# Fallback
logger.warning(f'Org neměl řešitele, zakládám novou Osobu.')
osoba = Osoba(user=user) osoba = Osoba(user=user)
# Přesun informací z usera do osoby # Přesun informací z usera do osoby
# pro řešitele již v minule migraci # pro osoby z řešitelů (jediné dosud existující osoby) již v minule migraci
osoba.jmeno = user.first_name osoba.jmeno = user.first_name
osoba.prijmeni = user.last_name osoba.prijmeni = user.last_name
osoba.email = user.email osoba.email = user.email
user.jmeno = "Použij osobu!" user.jmeno += " (Uživatel!)"
user.prijmeni = "Použij osobu!" user.prijmeni += " (Uživatel!)"
user.email = "Použij osobu!"
user.save() user.save()
except EndException: pass
# Přesun informací z organizátora do jeho osoby # Přesun informací z organizátora do jeho osoby
osoba.prezdivka = org.prezdivka if org.prezdivka is not None else '' osoba.prezdivka = org.prezdivka if org.prezdivka is not None else ''
osoba.foto = org.foto osoba.foto = org.foto
@ -46,7 +77,10 @@ def fix_problem(apps, schema_editor):
else: else:
pr.autor = None pr.autor = None
if pr.opravovatel is not None: if pr.opravovatel is not None:
if Organizator.objects.filter(osoba__user=pr.opravovatel).first() is not None:
pr.opravovatele.add(Organizator.objects.filter(osoba__user=pr.opravovatel).first()) pr.opravovatele.add(Organizator.objects.filter(osoba__user=pr.opravovatel).first())
else:
logger.error(f'WTF, nespárovaný opravovatel {pr.opravovatel} problému {pr}')
pr.save() pr.save()
def fix_pohadka(apps, schema_editor): def fix_pohadka(apps, schema_editor):

19
seminar/models.py

@ -602,13 +602,18 @@ class Cislo(SeminarModelBase):
png_filename = pathlib.Path(tempfile.mkdtemp(), 'nahled.png') png_filename = pathlib.Path(tempfile.mkdtemp(), 'nahled.png')
subprocess.run([ subprocess.run([
"convert", "gs",
"-density", "300x300", "-sstdout=%stderr",
"-geometry", "{}x{}".format(VYSKA, sirka), "-dSAFER",
"-background", "white", "-dNOPAUSE",
"-flatten", "-dBATCH",
"{}[0]".format(self.pdf.path), # titulní strana "-dNOPROMPT",
png_filename "-sDEVICE=pngalpha",
"-r{}x{}".format(VYSKA, sirka),
"-dFirstPage=1d",
"-dLastPage=1d",
"-sOutputFile=" + str(png_filename),
"-f%s" % self.pdf.path
], ],
check=True, check=True,
capture_output=True capture_output=True

5
seminar/templates/seminar/odevzdavatko/detail.html

@ -1,5 +1,6 @@
{% extends "base.html" %} {% extends "base.html" %}
{% load static %} {% load static %}
{% load deadliny %}
{% block content %} {% block content %}
@ -62,7 +63,9 @@ $(document).ready(function(){
<p>Řešitelé: {{ object.resitele.all | join:", " }}</p> <p>Řešitelé: {{ object.resitele.all | join:", " }}</p>
{# https://docs.djangoproject.com/en/3.1/ref/models/instances/#django.db.models.Model.get_FOO_display #} {# https://docs.djangoproject.com/en/3.1/ref/models/instances/#django.db.models.Model.get_FOO_display #}
<p>Forma: {{ object.get_forma_display }}, doručeno {{ object.cas_doruceni }}</p> <p>Forma: {{ object.get_forma_display }}</p>
<p>Doručeno {{ object.cas_doruceni }}, deadline: {{object.cas_doruceni | deadline_html }}</p>
{# Soubory: #} {# Soubory: #}
<h3>Přílohy:</h3> <h3>Přílohy:</h3>

38
seminar/templates/seminar/odevzdavatko/resitel_prehled.html

@ -0,0 +1,38 @@
{% extends "base.html" %}
{% load static %}
{% load deadliny %}
{% block custom_css %}
<style type=text/css>
.dosla_reseni tr th {
text-align: center;
}
.dosla_reseni tr th, .dosla_reseni tr td {
border: 1px solid black;
padding: 1px 10px 1px 10px;
border-collapse: collapse;
}
</style>
{% endblock custom_css %}
{% block content %}
{% for rocnik, hodnoceni in podle_rocniku %}
<h1>Ročník {{ rocnik }}</h1>
<table class="dosla_reseni">
<tr>
<th>Doručeno</th>
<th>Problém</th>
<th>Body</th>
<th>Deadline</th>
</tr>
{% for hodn in hodnoceni %}
<tr>
<td>{{ hodn.reseni.cas_doruceni }}</td>
<td>{{ hodn.problem }}</td>
<td>{{ hodn.body|default_if_none:"---" }}</td>
<td>{{ hodn.reseni.cas_doruceni | deadline_html }}</td>
</tr>
{% endfor %}
</table>
{% endfor %}
{% endblock %}

6
seminar/templates/seminar/odevzdavatko/seznam.html

@ -1,11 +1,15 @@
{% extends "base.html" %} {% extends "base.html" %}
{% load deadliny %}
{% block content %} {% block content %}
{% for dl, mnozina_reseni in reseni_podle_deadlinu.items %}
<h1>{{ dl.2 | deadline_html }}</h1>
<ul> <ul>
{% for obj in object_list %} {% for obj in mnozina_reseni %}
<li><a href="{% url 'odevzdavatko_detail_reseni' pk=obj.id %}">{{ obj }}</a> ({{ obj.get_forma_display }} {{ obj.cas_doruceni }}) <li><a href="{% url 'odevzdavatko_detail_reseni' pk=obj.id %}">{{ obj }}</a> ({{ obj.get_forma_display }} {{ obj.cas_doruceni }})
{% endfor %} {% endfor %}
</ul> </ul>
{% endfor %}
{% endblock %} {% endblock %}

2
seminar/templates/seminar/profil/edit.html

@ -61,6 +61,7 @@
{% include "seminar/profil/prihlaska_field.html" with field=form.stat_text id="id_li_stat_text"%} {% include "seminar/profil/prihlaska_field.html" with field=form.stat_text id="id_li_stat_text"%}
</table> </table>
{% if not po_maturite %} {# Vysloužilým účastníkům skrýt editaci školy apod. #}
<hr> <hr>
<h4> <h4>
@ -95,6 +96,7 @@
</table> </table>
<hr> <hr>
{% endif %}
<input type="submit" value="Změnit"> <input type="submit" value="Změnit">
</form> </form>

1
seminar/templates/seminar/profil/resitel.html

@ -11,6 +11,7 @@
<a href="{% url 'logout' %}">Odhlásit se</a><br> <a href="{% url 'logout' %}">Odhlásit se</a><br>
<a href="{% url 'seminar_resitel_edit' %}">Upravit údaje</a><br> <a href="{% url 'seminar_resitel_edit' %}">Upravit údaje</a><br>
<a href="{% url 'seminar_resitel_odevzdana_reseni' %}">Odevzdaná řešení</a><br>
{% endblock %} {% endblock %}

35
seminar/templatetags/deadliny.py

@ -0,0 +1,35 @@
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):
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]
@register.filter(name='deadline_kratseji')
def deadline_kratsi_text(datum):
typ, cislo, dl = deadline(datum)
strings = {
TypDeadline.PredDeadline: f"1. deadline {cislo}",
TypDeadline.SousDeadline: f"Soustřeďkový deadline {cislo}",
TypDeadline.FinalDeadline: f"Finální deadline {cislo}",
}
return strings[typ]
@register.filter(name='deadline_html')
def deadline_html(datum):
typ, _, _ = deadline(datum)
text = deadline_kratsi_text(datum)
classes = {
TypDeadline.PredDeadline: 'preddeadline',
TypDeadline.SousDeadline: 'sous_deadline',
TypDeadline.FinalDeadline: 'final_deadline',
}
return mark_safe(f'<span class="{classes[typ]}">{text}</span>')

1
seminar/urls.py

@ -135,6 +135,7 @@ urlpatterns = [
path('prihlasit/', views.LoginView.as_view(), name='login'), path('prihlasit/', views.LoginView.as_view(), name='login'),
path('odhlasit/', views.LogoutView.as_view(), name='logout'), path('odhlasit/', views.LogoutView.as_view(), name='logout'),
path('resitel/', resitel_required(views.ResitelView.as_view()), name='seminar_resitel'), path('resitel/', resitel_required(views.ResitelView.as_view()), name='seminar_resitel'),
path('resitel/odevzdana_reseni/', resitel_required(views.PrehledOdevzdanychReseni.as_view()), name='seminar_resitel_odevzdana_reseni'),
path('reset-hesla/', views.PasswordResetView.as_view(), name='reset_password'), path('reset-hesla/', views.PasswordResetView.as_view(), name='reset_password'),
path('zmena-hesla/', views.PasswordChangeView.as_view(), name='change_password'), path('zmena-hesla/', views.PasswordChangeView.as_view(), name='change_password'),
path('reset-hesla/2/', views.PasswordResetDoneView.as_view(), name='reset_password_done'), path('reset-hesla/2/', views.PasswordResetDoneView.as_view(), name='reset_password_done'),

14
seminar/utils.py

@ -309,7 +309,7 @@ def deadline_v_rocniku(datum, rocnik):
deadliny.append((TypDeadline.FinalDeadline, c, c.datum_deadline)) deadliny.append((TypDeadline.FinalDeadline, c, c.datum_deadline))
deadliny = sorted(deadliny, key=lambda x: x[2]) # podle data deadliny = sorted(deadliny, key=lambda x: x[2]) # podle data
for dl in deadliny: for dl in deadliny:
if datum <= dl: if datum <= dl[2]:
# První takový deadline je ten nejtěsnější # První takový deadline je ten nejtěsnější
return dl return dl
@ -319,20 +319,22 @@ def deadline(datum):
Vrací trojici (TypDeadline, Cislo, datumDeadline: date). Vrací trojici (TypDeadline, Cislo, datumDeadline: date).
""" """
if isinstance(datum, datetime.datetime):
datum = datum.date()
rok = datum.year rok = datum.year
# Dva ročníky podezřelé z obsahování dat # Dva ročníky podezřelé z obsahování dat
pozdejsi_rocnik = m.Rocnik.filter(prvni_rok=rok) pozdejsi_rocnik = m.Rocnik.objects.filter(prvni_rok=rok)
drivejsi_rocnik = m.Rocnik.filter(druhy_rok=rok) drivejsi_rocnik = m.Rocnik.objects.filter(prvni_rok=rok-1)
if any( if any([
pozdejsi_rocnik.count() > 1, pozdejsi_rocnik.count() > 1,
drivejsi_rocnik.count() > 1, drivejsi_rocnik.count() > 1,
): ]):
raise ValueError(f"Více ročníků začíná/končí stejným rokem: {rok}") raise ValueError(f"Více ročníků začíná/končí stejným rokem: {rok}")
pozdejsi_rocnik = pozdejsi_rocnik.first() if pozdejsi_rocnik.count() > 0 else None pozdejsi_rocnik = pozdejsi_rocnik.first() if pozdejsi_rocnik.count() > 0 else None
drivejsi_rocnik = drivejsi_rocnik.first() if drivejsi_rocnik.count() > 0 else None drivejsi_rocnik = drivejsi_rocnik.first() if drivejsi_rocnik.count() > 0 else None
# Předpokládáme, že neexistuje číslo, které má deadline ale nemá finální deadline. # Předpokládáme, že neexistuje číslo, které má deadline ale nemá finální deadline.
posledni_deadline_drivejsiho_rocniku = m.Cislo.objects.get(rocnik=drivejsi_rocnik, datum_deadline__isnull=False).datum_deadline posledni_deadline_drivejsiho_rocniku = m.Cislo.objects.filter(rocnik=drivejsi_rocnik, datum_deadline__isnull=False).last().datum_deadline
if datum <= posledni_deadline_drivejsiho_rocniku: if datum <= posledni_deadline_drivejsiho_rocniku:
return deadline_v_rocniku(datum, drivejsi_rocnik) return deadline_v_rocniku(datum, drivejsi_rocnik)

37
seminar/views/odevzdavatko.py

@ -8,12 +8,13 @@ from django.db import transaction
from dataclasses import dataclass from dataclasses import dataclass
import datetime import datetime
from itertools import groupby
import logging import logging
import seminar.models as m import seminar.models as m
import seminar.forms as f import seminar.forms as f
from seminar.forms import OdevzdavatkoTabulkaFiltrForm as FiltrForm from seminar.forms import OdevzdavatkoTabulkaFiltrForm as FiltrForm
from seminar.utils import aktivniResitele, resi_v_rocniku from seminar.utils import aktivniResitele, resi_v_rocniku, deadline
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -24,6 +25,8 @@ logger = logging.getLogger(__name__)
# - ReseniProblemuView # - ReseniProblemuView
# - Detail konkrétního řešení -- všechny soubory, datum, ... # - Detail konkrétního řešení -- všechny soubory, datum, ...
# - DetailReseniView # - DetailReseniView
# - Pro řešitele: přehled jejich odevzdaných řešení
# - PrehledOdevzdanychReseni
# #
# Taky se může hodit: # Taky se může hodit:
# - Tabulka všech řešitelů x všech problémů? # - Tabulka všech řešitelů x všech problémů?
@ -169,7 +172,14 @@ class ReseniProblemuView(MultipleObjectTemplateResponseMixin, MultipleObjectMixi
return redirect(reverse("odevzdavatko_detail_reseni", kwargs={"pk": jedine_reseni.id})) return redirect(reverse("odevzdavatko_detail_reseni", kwargs={"pk": jedine_reseni.id}))
context = self.get_context_data() context = self.get_context_data()
return self.render_to_response(context) return self.render_to_response(context)
# Kontext automaticky?
def get_context_data(self, *args, **kwargs):
ctx = super().get_context_data(*args, **kwargs)
# 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))}
return ctx
## XXX: https://docs.djangoproject.com/en/3.1/topics/class-based-views/mixins/#avoid-anything-more-complex ## XXX: https://docs.djangoproject.com/en/3.1/topics/class-based-views/mixins/#avoid-anything-more-complex
class DetailReseniView(DetailView): class DetailReseniView(DetailView):
@ -230,6 +240,29 @@ def hodnoceniReseniView(request, pk, *args, **kwargs):
return redirect(success_url) return redirect(success_url)
class PrehledOdevzdanychReseni(ListView):
model = m.Hodnoceni
template_name = 'seminar/odevzdavatko/resitel_prehled.html'
def get_queryset(self):
if not self.request.user.is_authenticated:
raise RuntimeError("Uživatel měl být přihlášený!")
resitel = m.Resitel.objects.get(osoba__user=self.request.user)
qs = super().get_queryset()
qs = qs.filter(reseni__resitele__in=[resitel])
return qs
def get_context_data(self, *args, **kwargs):
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 :-/
podle_rocniku = []
for rocnik, hodnoceni in groupby(ctx['object_list'], lambda ho: deadline(ho.reseni.cas_doruceni)[1].rocnik):
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í
return ctx
# Přehled všech řešení kvůli debugování # Přehled všech řešení kvůli debugování
class SeznamReseniView(ListView): class SeznamReseniView(ListView):

17
seminar/views/views_all.py

@ -26,7 +26,7 @@ import seminar.models as m
from seminar.models import Problem, Cislo, Reseni, Nastaveni, Rocnik, Soustredeni, Organizator, Resitel, Novinky, Soustredeni_Ucastnici, Pohadka, Tema, Clanek, Osoba, Skola # Tohle je stare a chceme se toho zbavit. Pouzivejte s.ToCoChci from seminar.models import Problem, Cislo, Reseni, Nastaveni, Rocnik, Soustredeni, Organizator, Resitel, Novinky, Soustredeni_Ucastnici, Pohadka, Tema, Clanek, Osoba, Skola # Tohle je stare a chceme se toho zbavit. Pouzivejte s.ToCoChci
#from .models import VysledkyZaCislo, VysledkyKCisluZaRocnik, VysledkyKCisluOdjakziva #from .models import VysledkyZaCislo, VysledkyKCisluZaRocnik, VysledkyKCisluOdjakziva
from seminar import utils, treelib from seminar import utils, treelib
from seminar.forms import PrihlaskaForm, LoginForm, ProfileEditForm from seminar.forms import PrihlaskaForm, LoginForm, ProfileEditForm, ProfileEditFormPoMaturite
import seminar.forms as f import seminar.forms as f
import seminar.templatetags.treenodes as tnltt import seminar.templatetags.treenodes as tnltt
import seminar.views.views_rest as vr import seminar.views.views_rest as vr
@ -1095,14 +1095,22 @@ def resitelEditView(request):
user_edit = osoba_edit.user user_edit = osoba_edit.user
## Vytvoření slovníku, kterým předvyplním formulář ## Vytvoření slovníku, kterým předvyplním formulář
prefill_1=model_to_dict(user_edit) prefill_1=model_to_dict(user_edit)
if resitel_edit: if resitel_edit and resitel_edit.rok_maturity >= date.today().year:
prefill_2=model_to_dict(resitel_edit) prefill_2=model_to_dict(resitel_edit)
prefill_1.update(prefill_2) prefill_1.update(prefill_2)
prefill_3=model_to_dict(osoba_edit) prefill_3=model_to_dict(osoba_edit)
prefill_1.update(prefill_3) prefill_1.update(prefill_3)
if 'datum_narozeni' in prefill_1:
prefill_1['datum_narozeni'] = str(prefill_1['datum_narozeni'])
if resitel_edit and resitel_edit.rok_maturity < date.today().year:
form = ProfileEditFormPoMaturite(initial=prefill_1)
else:
form = ProfileEditForm(initial=prefill_1) form = ProfileEditForm(initial=prefill_1)
## Změna údajů a jejich uložení ## Změna údajů a jejich uložení
if request.method == 'POST': if request.method == 'POST':
if resitel_edit and resitel_edit.rok_maturity < date.today().year:
form = ProfileEditFormPoMaturite(request.POST)
else:
form = ProfileEditForm(request.POST) form = ProfileEditForm(request.POST)
if form.is_valid(): if form.is_valid():
## Změny v osobě ## Změny v osobě
@ -1115,6 +1123,7 @@ def resitelEditView(request):
osoba_edit.ulice = fcd['ulice'] osoba_edit.ulice = fcd['ulice']
osoba_edit.mesto = fcd['mesto'] osoba_edit.mesto = fcd['mesto']
osoba_edit.psc = fcd['psc'] osoba_edit.psc = fcd['psc']
osoba_edit.datum_narozeni = fcd['datum_narozeni']
## Změny v osobě s podmínkami ## Změny v osobě s podmínkami
if fcd.get('spam',False): if fcd.get('spam',False):
osoba_edit.datum_souhlasu_zasilani = date.today() osoba_edit.datum_souhlasu_zasilani = date.today()
@ -1124,7 +1133,7 @@ def resitelEditView(request):
## Neznámá země ## Neznámá země
msg = "Unknown country {}".format(fcd['stat_text']) msg = "Unknown country {}".format(fcd['stat_text'])
if resitel_edit: if resitel_edit and resitel_edit.rok_maturity >= date.today().year:
## Změny v řešiteli ## Změny v řešiteli
resitel_edit.skola = fcd['skola'] resitel_edit.skola = fcd['skola']
resitel_edit.rok_maturity = fcd['rok_maturity'] resitel_edit.rok_maturity = fcd['rok_maturity']
@ -1140,7 +1149,7 @@ def resitelEditView(request):
return formularOKView(request) return formularOKView(request)
else: else:
## Stránka před odeslaním formuláře = předvyplněný formulář ## Stránka před odeslaním formuláře = předvyplněný formulář
return render(request, 'seminar/profil/edit.html', {'form': form}) return render(request, 'seminar/profil/edit.html', {'form': form, 'po_maturite': resitel_edit and resitel_edit.rok_maturity < date.today().year})
def prihlaskaView(request): def prihlaskaView(request):
generic_logger = logging.getLogger('seminar.prihlaska') generic_logger = logging.getLogger('seminar.prihlaska')

0
various/__init__.py

3
various/admin.py

@ -0,0 +1,3 @@
from django.contrib import admin
# Register your models here.

5
various/apps.py

@ -0,0 +1,5 @@
from django.apps import AppConfig
class VariousConfig(AppConfig):
name = 'various'

14
various/context_processors.py

@ -0,0 +1,14 @@
def april(req):
if 'X-April' in req.headers:
try:
year = int(req.headers['X-April'])
return {'april': year}
except:
pass # Fall-back to regular behaviour
import datetime
today = datetime.date.today()
if today.day == 1 and today.month == 4:
return {'april': today.year}
return {}

0
various/migrations/__init__.py

3
various/models.py

@ -0,0 +1,3 @@
from django.db import models
# Create your models here.

3
various/tests.py

@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

3
various/views.py

@ -0,0 +1,3 @@
from django.shortcuts import render
# Create your views here.
Loading…
Cancel
Save