Merge branch 'master' into korekturovatko
This commit is contained in:
commit
8ec06d0c90
18 changed files with 313 additions and 93 deletions
|
@ -42,7 +42,11 @@
|
||||||
|
|
||||||
window.addEventListener("load", _ => {
|
window.addEventListener("load", _ => {
|
||||||
aktualizuj_vse({}, true, () => {
|
aktualizuj_vse({}, true, () => {
|
||||||
if (location.hash !== "") location.hash = location.hash; // Po rozházení korektur sescrollujeme na kotvu v URL
|
if (location.hash !== "") { // Po rozházení korektur sescrollujeme na kotvu v URL
|
||||||
|
const h = location.hash.substring(1);
|
||||||
|
location.hash = "HACK";
|
||||||
|
location.hash = h;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -26,9 +26,10 @@
|
||||||
|
|
||||||
.tres {
|
.tres {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
|
text-align: end;
|
||||||
}
|
}
|
||||||
|
|
||||||
.grey {
|
.half-opacity {
|
||||||
opacity: 0.5;
|
opacity: 0.5;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,7 +18,7 @@
|
||||||
{% for osoba in object_list %}
|
{% for osoba in object_list %}
|
||||||
<div class="osoba">
|
<div class="osoba">
|
||||||
<div class="uno">{{ osoba.jmeno }} {{ osoba.prijmeni }}</div>
|
<div class="uno">{{ osoba.jmeno }} {{ osoba.prijmeni }}</div>
|
||||||
<div class="dos {% if not osoba.jak_se_dozvedeli %}grey{% endif %}">{% if osoba.jak_se_dozvedeli %} {{osoba.jak_se_dozvedeli}} {% else %} NEZADÁNO {% endif %}</div>
|
<div class="dos {% if not osoba.jak_se_dozvedeli %}half-opacity{% endif %}">{% if osoba.jak_se_dozvedeli %} {{osoba.jak_se_dozvedeli}} {% else %} NEZADÁNO {% endif %}</div>
|
||||||
<div class="tres">{{ osoba.datum_registrace }}</div>
|
<div class="tres">{{ osoba.datum_registrace }}</div>
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
88
personalni/templates/personalni/profil/export_lidi.html
Normal file
88
personalni/templates/personalni/profil/export_lidi.html
Normal file
|
@ -0,0 +1,88 @@
|
||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
|
||||||
|
<h2><strong>Export lidí</strong></h2>
|
||||||
|
|
||||||
|
<select name="select-one" id="select-one">
|
||||||
|
<option value="0">---</option>
|
||||||
|
<option value="1">Řešitelé čísla</option>
|
||||||
|
<option value="2">Řešitelé ročníku</option>
|
||||||
|
<option value="3">Všichni řešitelé, kteří ještě neodmaturovali</option>
|
||||||
|
<option value="4">Organizátoři soustředění</option>
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<select name="select-two" id="select-two">
|
||||||
|
<!-- will be filled with ajax -->
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<button id="download-button">Stáhnout</button>
|
||||||
|
|
||||||
|
<script defer>
|
||||||
|
const select_one = document.getElementById("select-one")
|
||||||
|
const select_two = document.getElementById("select-two")
|
||||||
|
const download_button = document.getElementById("download-button")
|
||||||
|
|
||||||
|
download_button.style.display = 'none'
|
||||||
|
select_two.style.display = 'none'
|
||||||
|
|
||||||
|
const fetch_dict_string = '{{ typy_exportu|safe }}'
|
||||||
|
const fetch_dict = JSON.parse(fetch_dict_string)
|
||||||
|
|
||||||
|
|
||||||
|
select_one.addEventListener('change', (e) => {
|
||||||
|
value = e.target.value
|
||||||
|
select_two.style.display = 'none'
|
||||||
|
select_two.innerHTML = ''
|
||||||
|
// puvodni stav
|
||||||
|
if (value == 0) {
|
||||||
|
download_button.style.display = 'none'
|
||||||
|
select_two.style.display = 'none'
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// v tomto pripade muzeme rovnou stahnout
|
||||||
|
if (!(value in fetch_dict)) {
|
||||||
|
download_button.style.display = 'block'
|
||||||
|
select_two.style.display = 'none'
|
||||||
|
return
|
||||||
|
}
|
||||||
|
download_button.style.display = 'none'
|
||||||
|
fetch("/profil/exporty_lidi/get/" + value)
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
const option = document.createElement('option')
|
||||||
|
option.value = 0
|
||||||
|
option.text = '---'
|
||||||
|
select_two.appendChild(option)
|
||||||
|
for (const [key, value] of Object.entries(data)) {
|
||||||
|
const option = document.createElement('option')
|
||||||
|
option.value = value["id"]
|
||||||
|
option.text = value["display"]
|
||||||
|
select_two.appendChild(option)
|
||||||
|
}
|
||||||
|
select_two.style.display = 'block'
|
||||||
|
})
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
select_two.addEventListener('change', (e) => {
|
||||||
|
value = e.target.value
|
||||||
|
if (value == 0) {
|
||||||
|
download_button.style.display = 'none'
|
||||||
|
return
|
||||||
|
}
|
||||||
|
download_button.style.display = 'block'
|
||||||
|
})
|
||||||
|
|
||||||
|
download_button.addEventListener('click', (e) => {
|
||||||
|
if (select_two.innerHTML == '') {
|
||||||
|
window.location.href = "/profil/exporty_lidi/get_csv_only_one_step/" + select_one.value
|
||||||
|
} else {
|
||||||
|
window.location.href = "/profil/exporty_lidi/get_csv/" + select_one.value + "/" + select_two.value
|
||||||
|
}
|
||||||
|
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{% endblock %}
|
|
@ -107,6 +107,13 @@
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
|
<hr />
|
||||||
|
<h2><strong>Exporty dat lidí v semináří</strong></h2>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li><a href="{% url 'exporty_lidi' %}">dostupné exporty</a></li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
<hr />
|
<hr />
|
||||||
<p>Nemůžeš najít, co hledáš? Může to být v <a href="{% url 'admin:index' %}">administračním rozhraní webu</a>.</p>
|
<p>Nemůžeš najít, co hledáš? Může to být v <a href="{% url 'admin:index' %}">administračním rozhraní webu</a>.</p>
|
||||||
{% endblock content %}
|
{% endblock content %}
|
||||||
|
|
|
@ -38,6 +38,28 @@ urlpatterns = [
|
||||||
'org/propagace/jak-se-dozvedeli/',
|
'org/propagace/jak-se-dozvedeli/',
|
||||||
org_required(views.JakSeDozvedeliView.as_view()),
|
org_required(views.JakSeDozvedeliView.as_view()),
|
||||||
name='jak_se_dozvedeli'
|
name='jak_se_dozvedeli'
|
||||||
|
),
|
||||||
|
|
||||||
|
# export dat o řešitelích
|
||||||
|
path(
|
||||||
|
'profil/exporty_lidi',
|
||||||
|
org_required(views.ExportLidiView.as_view()),
|
||||||
|
name='exporty_lidi',
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
'profil/exporty_lidi/get/<int:type>',
|
||||||
|
org_required(views.get_export_options),
|
||||||
|
name='exporty_lidi_options',
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
'profil/exporty_lidi/get_csv_only_one_step/<int:type>',
|
||||||
|
org_required(views.download_export_csv_only_first_step),
|
||||||
|
name='exporty_lidi_data',
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
'profil/exporty_lidi/get_csv/<int:type>/<int:id>',
|
||||||
|
org_required(views.download_export_csv),
|
||||||
|
name='exporty_lidi_download',
|
||||||
)
|
)
|
||||||
|
|
||||||
]
|
]
|
||||||
|
|
|
@ -20,13 +20,16 @@ from django.utils import timezone
|
||||||
import personalni.models as m
|
import personalni.models as m
|
||||||
from soustredeni.models import Soustredeni
|
from soustredeni.models import Soustredeni
|
||||||
from odevzdavatko.models import Hodnoceni
|
from odevzdavatko.models import Hodnoceni
|
||||||
from tvorba.models import Clanek, Uloha, Tema
|
from tvorba.models import Clanek, Uloha, Tema, Cislo, Rocnik
|
||||||
|
import tvorba.utils as tvorba_utils
|
||||||
from various.models import Nastaveni
|
from various.models import Nastaveni
|
||||||
from .forms import PrihlaskaForm, ProfileEditForm, PoMaturiteProfileEditForm
|
from .forms import PrihlaskaForm, ProfileEditForm, PoMaturiteProfileEditForm
|
||||||
|
|
||||||
from datetime import date
|
from datetime import date
|
||||||
import logging
|
import logging
|
||||||
import csv
|
import csv
|
||||||
|
from enum import Enum
|
||||||
|
import json
|
||||||
|
|
||||||
from various.views.pomocne import formularOKView
|
from various.views.pomocne import formularOKView
|
||||||
from various.autentizace.views import LoginView
|
from various.autentizace.views import LoginView
|
||||||
|
@ -140,7 +143,55 @@ class OrgoRozcestnikView(TemplateView):
|
||||||
|
|
||||||
#content_type = 'text/plain; charset=UTF8'
|
#content_type = 'text/plain; charset=UTF8'
|
||||||
#XXX
|
#XXX
|
||||||
|
|
||||||
|
class PrvniTypExportu(Enum):
|
||||||
|
CISLA = 1
|
||||||
|
ROCNIKU = 2
|
||||||
|
SOUSTREDENI = 4
|
||||||
|
|
||||||
|
class ExportLidiView(TemplateView):
|
||||||
|
template_name = 'personalni/profil/export_lidi.html'
|
||||||
|
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
context = super().get_context_data(**kwargs)
|
||||||
|
context['typy_exportu'] = json.dumps({member.value: member.name.lower().capitalize() for member in PrvniTypExportu})
|
||||||
|
return context
|
||||||
|
|
||||||
|
|
||||||
|
def get_export_options(request, type):
|
||||||
|
if type == PrvniTypExportu.CISLA.value:
|
||||||
|
data = [{"id": c.id, "display": str(c)} for c in Cislo.objects.all()]
|
||||||
|
if type == PrvniTypExportu.ROCNIKU.value:
|
||||||
|
data = [{"id": r.id, "display": str(r)} for r in Rocnik.objects.all()]
|
||||||
|
if type == PrvniTypExportu.SOUSTREDENI.value:
|
||||||
|
data = [{"id": s.id, "display": str(s)} for s in Soustredeni.objects.all()]
|
||||||
|
return HttpResponse(json.dumps(data), content_type='application/json')
|
||||||
|
|
||||||
|
def download_export_csv_only_first_step(request, type):
|
||||||
|
if type == 3:
|
||||||
|
response = dataResiteluCsvResponse(tvorba_utils.resitele_co_neodmaturovali())
|
||||||
|
response['Content-Disposition'] = 'attachment; filename="resitele_co_neodmaturovali.csv"'
|
||||||
|
return response
|
||||||
|
|
||||||
|
def download_export_csv(request, type, id):
|
||||||
|
if type == PrvniTypExportu.CISLA.value:
|
||||||
|
response = dataResiteluCsvResponse(tvorba_utils.resi_cislo(Cislo.objects.get(id=id)))
|
||||||
|
name = str(Cislo.objects.get(id=id)).replace(" ", "_") + "_resitele_cisla.csv"
|
||||||
|
response['Content-Disposition'] = 'attachment; filename="' + name + '"'
|
||||||
|
return response
|
||||||
|
if type == PrvniTypExportu.ROCNIKU.value:
|
||||||
|
response = dataResiteluCsvResponse(tvorba_utils.resi_v_rocniku(Rocnik.objects.get(id=id)))
|
||||||
|
name = str(Rocnik.objects.get(id=id)).replace(" ", "_") + "_resitele_rocniku.csv"
|
||||||
|
response['Content-Disposition'] = 'attachment; filename="' + name + '"'
|
||||||
|
return response
|
||||||
|
if type == PrvniTypExportu.SOUSTREDENI.value:
|
||||||
|
soustredeni = Soustredeni.objects.get(id=id)
|
||||||
|
organizatori = soustredeni.organizatori.all()
|
||||||
|
organizatoriOsoby = Osoba.objects.filter(org__in=organizatori)
|
||||||
|
response = dataOsobCsvResponse(organizatoriOsoby, columns=("jmeno", "prijmeni", "email", "telefon",))
|
||||||
|
name = str(soustredeni).replace(" ", "_") + "_organizatori_soustredeni.csv"
|
||||||
|
response['Content-Disposition'] = 'attachment; filename="' + name + '"'
|
||||||
|
return response
|
||||||
|
|
||||||
class ResitelView(LoginRequiredMixin,generic.DetailView):
|
class ResitelView(LoginRequiredMixin,generic.DetailView):
|
||||||
model = m.Resitel
|
model = m.Resitel
|
||||||
|
@ -470,3 +521,46 @@ def dataResiteluCsvResponse(queryset, columns=None, with_header=True):
|
||||||
writer.writerows(queryset_list)
|
writer.writerows(queryset_list)
|
||||||
|
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
def dataOsobCsvResponse(queryset, columns=None, with_header=True):
|
||||||
|
"""Pomocná funkce pro vracení dat osob jako CSV. Musí dostat správný QuerySet, který dává Ososby"""
|
||||||
|
|
||||||
|
default_columns = (
|
||||||
|
'id',
|
||||||
|
'jmeno',
|
||||||
|
'prijmeni',
|
||||||
|
'prezdivka',
|
||||||
|
'email',
|
||||||
|
'telefon',
|
||||||
|
'datum_narozeni',
|
||||||
|
'osloveni',
|
||||||
|
'ulice',
|
||||||
|
'mesto',
|
||||||
|
'psc',
|
||||||
|
'stat',
|
||||||
|
'jak_se_dozvedeli',
|
||||||
|
'poznamka',
|
||||||
|
'datum_registrace',
|
||||||
|
'datum_souhlasu_udaje',
|
||||||
|
'datum_souhlasu_zasilani',
|
||||||
|
)
|
||||||
|
|
||||||
|
if columns is None: columns = default_columns
|
||||||
|
|
||||||
|
def get_field_name(column_name):
|
||||||
|
return column_name
|
||||||
|
|
||||||
|
response = HttpResponse(content_type='text/csv')
|
||||||
|
writer = csv.writer(response)
|
||||||
|
|
||||||
|
# První řádek je záhlaví
|
||||||
|
if with_header:
|
||||||
|
writer.writerow(map(get_field_name, columns))
|
||||||
|
|
||||||
|
# Data:
|
||||||
|
queryset_list = queryset.values_list(*columns)
|
||||||
|
writer.writerows(queryset_list)
|
||||||
|
|
||||||
|
return response
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -10,7 +10,7 @@ from soustredeni.models import Soustredeni
|
||||||
|
|
||||||
class Seznam_PrednaskaInline(admin.TabularInline):
|
class Seznam_PrednaskaInline(admin.TabularInline):
|
||||||
"""
|
"""
|
||||||
Pomůcka pro :py:class:`prednasky.admin.SeznamAdmin` zobrazující hezky :py:class:`Přednášky <prednasky.models.Prednaska>`
|
:py:class:`Inline <django.contrib.admin.TabularInline>` pro :py:class:`prednasky.admin.SeznamAdmin` zobrazující :py:class:`Přednášky <prednasky.models.Prednaska>`
|
||||||
v adminu :py:class:`Seznamu <prednasky.models.Seznam>`.
|
v adminu :py:class:`Seznamu <prednasky.models.Seznam>`.
|
||||||
"""
|
"""
|
||||||
model = Prednaska.seznamy.through
|
model = Prednaska.seznamy.through
|
||||||
|
@ -60,7 +60,7 @@ class Seznam_PrednaskaInline(admin.TabularInline):
|
||||||
|
|
||||||
class Seznam_ZnalostInline(admin.TabularInline):
|
class Seznam_ZnalostInline(admin.TabularInline):
|
||||||
"""
|
"""
|
||||||
Pomůcka pro :py:class:`prednasky.admin.SeznamAdmin` zobrazující hezky :py:class:`Znalosti <prednasky.models.Znalost>`
|
:py:class:`Inline <django.contrib.admin.TabularInline>` pro :py:class:`prednasky.admin.SeznamAdmin` zobrazující :py:class:`Znalosti <prednasky.models.Znalost>`
|
||||||
v adminu :py:class:`Seznamu <prednasky.models.Seznam>`.
|
v adminu :py:class:`Seznamu <prednasky.models.Seznam>`.
|
||||||
"""
|
"""
|
||||||
model = Znalost.seznamy.through
|
model = Znalost.seznamy.through
|
||||||
|
@ -97,7 +97,7 @@ admin.site.register(Seznam, SeznamAdmin)
|
||||||
|
|
||||||
|
|
||||||
class PrednaskaAdmin(VersionAdmin):
|
class PrednaskaAdmin(VersionAdmin):
|
||||||
""" Admin pro :py:class:`Přednášku <prednasky.models.Prednaska> """
|
""" Admin pro :py:class:`Přednášku <prednasky.models.Prednaska>` """
|
||||||
list_display = ['nazev', 'org', 'obor']
|
list_display = ['nazev', 'org', 'obor']
|
||||||
list_filter = ['org', 'obor']
|
list_filter = ['org', 'obor']
|
||||||
search_fields = ['nazev']
|
search_fields = ['nazev']
|
||||||
|
@ -138,7 +138,7 @@ admin.site.register(Prednaska, PrednaskaAdmin)
|
||||||
|
|
||||||
class ZnalostAdmin(PrednaskaAdmin): # Trochu hack, ať nemusím vypisovat všechno znovu
|
class ZnalostAdmin(PrednaskaAdmin): # Trochu hack, ať nemusím vypisovat všechno znovu
|
||||||
"""
|
"""
|
||||||
Admin pro :py:class:`Znalost <prednasky.models.Znalost>
|
Admin pro :py:class:`Znalost <prednasky.models.Znalost>`
|
||||||
TODO předělat, aby nedědila z :py:class:`prednasky.admin.PrednaskaAdmin`, ale společné věci byly zvlášť
|
TODO předělat, aby nedědila z :py:class:`prednasky.admin.PrednaskaAdmin`, ale společné věci byly zvlášť
|
||||||
"""
|
"""
|
||||||
list_display = ("__str__",)
|
list_display = ("__str__",)
|
||||||
|
|
|
@ -3,7 +3,7 @@ from django import forms
|
||||||
from .models import Hlasovani, HlasovaniOZnalostech
|
from .models import Hlasovani, HlasovaniOZnalostech
|
||||||
|
|
||||||
class HlasovaniPrednaskaForm(forms.Form):
|
class HlasovaniPrednaskaForm(forms.Form):
|
||||||
""" :py:class:`Formulář <django.forms.Form>` pro pro :py:class:`Hlasování <prednasky.models.Hlasovani>` o jedné :py:class:`Přednášce <prednasky.models.Prednaska>`
|
""" :py:class:`Formulář <django.forms.Form>` pro :py:class:`Hlasování <prednasky.models.Hlasovani>` o jedné :py:class:`Přednášce <prednasky.models.Prednaska>`
|
||||||
(neobsahuje téměř nic, většina se musí doplnit jiným způsobem)
|
(neobsahuje téměř nic, většina se musí doplnit jiným způsobem)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
@ -17,7 +17,7 @@ class HlasovaniPrednaskaForm(forms.Form):
|
||||||
HlasovaniPrednaskaFormSet = forms.formset_factory(HlasovaniPrednaskaForm, extra=0)
|
HlasovaniPrednaskaFormSet = forms.formset_factory(HlasovaniPrednaskaForm, extra=0)
|
||||||
|
|
||||||
class HlasovaniZnalostiForm(forms.Form):
|
class HlasovaniZnalostiForm(forms.Form):
|
||||||
""" :py:class:`Formulář <django.forms.Form>` pro pro :py:class:`HlasováníOZnalostech <prednasky.models.HlasovaniOZnalostech>` o jedné :py:class:`Znalosti <prednasky.models.Znalost>`
|
""" :py:class:`Formulář <django.forms.Form>` pro :py:class:`HlasováníOZnalostech <prednasky.models.HlasovaniOZnalostech>` o jedné :py:class:`Znalosti <prednasky.models.Znalost>`
|
||||||
(neobsahuje téměř nic, většina se musí doplnit jiným způsobem)
|
(neobsahuje téměř nic, většina se musí doplnit jiným způsobem)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
20
prednasky/migrations/0023_hlasovani_ucastnik_osoba.py
Normal file
20
prednasky/migrations/0023_hlasovani_ucastnik_osoba.py
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
# Generated by Django 4.2.16 on 2025-02-19 17:31
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('personalni', '0019_rename_upozorneni_resitel_upozornovat_na_opravy_reseni'),
|
||||||
|
('prednasky', '0022_preklep_u_odpovedi_hlasovanioznalostech'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='hlasovani',
|
||||||
|
name='ucastnik_osoba',
|
||||||
|
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='personalni.osoba'),
|
||||||
|
),
|
||||||
|
]
|
|
@ -6,7 +6,7 @@ from personalni.models import Organizator, Osoba
|
||||||
|
|
||||||
class Seznam(models.Model):
|
class Seznam(models.Model):
|
||||||
"""
|
"""
|
||||||
Spojuje :py:class:`Přednášky <prednasky.models.Prednaska>`
|
Spojuje :py:class:`Přednášky <prednasky.models.Prednaska>` a :py:class:`Znalosti <prednasky.models.Znalost>
|
||||||
se :py:class:`Soustředěními <soustredeni.models.Soustredeni>`,
|
se :py:class:`Soustředěními <soustredeni.models.Soustredeni>`,
|
||||||
kde by mohly zaznít, nebo zazní/zazněly.
|
kde by mohly zaznít, nebo zazní/zazněly.
|
||||||
"""
|
"""
|
||||||
|
@ -19,8 +19,8 @@ class Seznam(models.Model):
|
||||||
|
|
||||||
class Stav(models.IntegerChoices):
|
class Stav(models.IntegerChoices):
|
||||||
""" Stav seznamu přednášek (NAVRH se používá k hlasování viz :py:func:`daný view <prednasky.views.newPrednaska>`). """
|
""" Stav seznamu přednášek (NAVRH se používá k hlasování viz :py:func:`daný view <prednasky.views.newPrednaska>`). """
|
||||||
NAVRH = 1, "Návrh"
|
NAVRH = 1, "Návrh" #: odpovídá před-soustřeďkové představě o tom, jaké přednášky dělat (dá se o nich třeba hlasovat ap.)
|
||||||
BUDE = 2, "Bude"
|
BUDE = 2, "Bude" #: odpovídá definitivní představě o tom, co bude/bylo a dá se porovnávat s novými návrhy
|
||||||
|
|
||||||
id = models.AutoField(primary_key=True)
|
id = models.AutoField(primary_key=True)
|
||||||
soustredeni = models.ForeignKey(Soustredeni, null=True, default=None, on_delete=models.PROTECT)
|
soustredeni = models.ForeignKey(Soustredeni, null=True, default=None, on_delete=models.PROTECT)
|
||||||
|
@ -88,6 +88,7 @@ class Hlasovani(models.Model):
|
||||||
#: že všechna předchozí hlasování zde mají náhodný string…)
|
#: že všechna předchozí hlasování zde mají náhodný string…)
|
||||||
#: TODO Změnit to na Osobu*
|
#: TODO Změnit to na Osobu*
|
||||||
ucastnik = models.CharField("Účastník", max_length=100)
|
ucastnik = models.CharField("Účastník", max_length=100)
|
||||||
|
ucastnik_osoba = models.ForeignKey(Osoba, on_delete=models.CASCADE, blank=False, null=True)
|
||||||
seznam = models.ForeignKey(Seznam, null=True, on_delete=models.SET_NULL)
|
seznam = models.ForeignKey(Seznam, null=True, on_delete=models.SET_NULL)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
|
@ -98,6 +99,9 @@ class Znalost(models.Model):
|
||||||
"""
|
"""
|
||||||
Reprezentuje znalost, na kterou se můžeme účastníka ptát (nechat je hlasovat).
|
Reprezentuje znalost, na kterou se můžeme účastníka ptát (nechat je hlasovat).
|
||||||
(Viz :py:class:`HlasováníOZnalostech <prednasky.models.HlasovaniOZnalostech>`.)
|
(Viz :py:class:`HlasováníOZnalostech <prednasky.models.HlasovaniOZnalostech>`.)
|
||||||
|
|
||||||
|
(V podstatě :py:class:`Přednáška <prednasky.models.Prednaska>, jen neobsahuje
|
||||||
|
tolik detailů a v hlasování má jiné odpovědi.)
|
||||||
"""
|
"""
|
||||||
class Meta:
|
class Meta:
|
||||||
db_table = "prednasky_znalost"
|
db_table = "prednasky_znalost"
|
||||||
|
@ -117,6 +121,9 @@ class HlasovaniOZnalostech(models.Model):
|
||||||
Reprezentuje hlasování jednoho účastníka
|
Reprezentuje hlasování jednoho účastníka
|
||||||
o jedné :py:class:`Znalosti <prednasky.models.Znalost>`
|
o jedné :py:class:`Znalosti <prednasky.models.Znalost>`
|
||||||
v jednom :py:class:`Seznamu <prednasky.models.Seznam>` (účastníkův pohled se totiž mezi sousy změnit)
|
v jednom :py:class:`Seznamu <prednasky.models.Seznam>` (účastníkův pohled se totiž mezi sousy změnit)
|
||||||
|
|
||||||
|
(V podstatě totéž, co :py:class:`Hlasování <prednasky.models.Hlasovani>`, jen má jiné komentáře
|
||||||
|
u odpovědí a místo přednášky odkazuje na znalost.)
|
||||||
"""
|
"""
|
||||||
class Odpoved(models.IntegerChoices):
|
class Odpoved(models.IntegerChoices):
|
||||||
""" Na kolik danou znalost účastník ovládá v daném Hlasování (větší číslo = víc zná) """
|
""" Na kolik danou znalost účastník ovládá v daném Hlasování (větší číslo = víc zná) """
|
||||||
|
|
|
@ -14,6 +14,7 @@
|
||||||
<p>Obtížnost 1 je nejlehčí, 3 nejtěžší.</p>
|
<p>Obtížnost 1 je nejlehčí, 3 nejtěžší.</p>
|
||||||
{{ form_set_prednasky.management_form }}
|
{{ form_set_prednasky.management_form }}
|
||||||
{% for f, p in formy_a_prednasky %}
|
{% for f, p in formy_a_prednasky %}
|
||||||
|
<div class="hlasovani-prednaska">
|
||||||
<h4>{{p.nazev}} ({{p.org}})</h4>
|
<h4>{{p.nazev}} ({{p.org}})</h4>
|
||||||
<p class="textprednasky">{{p.anotace | linebreaksbr}}</p>
|
<p class="textprednasky">{{p.anotace | linebreaksbr}}</p>
|
||||||
<label>Obor: </label> {{p.obor}}<br>
|
<label>Obor: </label> {{p.obor}}<br>
|
||||||
|
@ -22,17 +23,20 @@
|
||||||
<br>
|
<br>
|
||||||
{{ f }}
|
{{ f }}
|
||||||
<br>
|
<br>
|
||||||
|
</div>
|
||||||
{% empty %}
|
{% empty %}
|
||||||
Nejsou žádné přednášky o kterých by šlo hlasovat.
|
Nejsou žádné přednášky o kterých by šlo hlasovat.
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
||||||
{{ form_set_znalosti.management_form }}
|
{{ form_set_znalosti.management_form }}
|
||||||
{% for f, z in formy_a_znalosti %}
|
{% for f, z in formy_a_znalosti %}
|
||||||
|
<div class="hlasovani-znalost">
|
||||||
{% if forloop.first %}<hr/><h3>Jak moc znáš následující?</h3>{% endif %}
|
{% if forloop.first %}<hr/><h3>Jak moc znáš následující?</h3>{% endif %}
|
||||||
<h4>{{z.nazev}}</h4>
|
<h4>{{z.nazev}}</h4>
|
||||||
<p class="textznalosti">{{z.text | linebreaksbr}}</p>
|
<p class="textznalosti">{{z.text | linebreaksbr}}</p>
|
||||||
{{ f }}
|
{{ f }}
|
||||||
<br>
|
<br>
|
||||||
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
<input type="submit" value="Odeslat"/>
|
<input type="submit" value="Odeslat"/>
|
||||||
</form>
|
</form>
|
||||||
|
|
|
@ -2,19 +2,19 @@
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<h1>{% block nadpis1a %}
|
<h1>{% block nadpis1a %}
|
||||||
Hlasování o přednáškách
|
Výsledky hlasování o přednáškách
|
||||||
{% endblock %}</h1>
|
{% endblock %}</h1>
|
||||||
{# Projdi vsechny seznamy #}
|
{# Projdi vsechny seznamy #}
|
||||||
<div class="mam-org-only">
|
<div class="mam-org-only">
|
||||||
<ul>
|
<ul>
|
||||||
{% for seznam in object_list %}
|
{% for seznam in object_list %}
|
||||||
<li>
|
<li>
|
||||||
{% if seznam.stav == 1 %} {# STAV_NAHRH = 1 #}
|
{% if seznam.stav == seznam.Stav.NAVRH %}
|
||||||
<a href="/prednasky/seznam_prednasek/{{seznam.id}}">Návrh přednášek na soustředění {{seznam.soustredeni.misto}} </a>
|
Návrh přednášek na soustředění {{seznam.soustredeni.misto}}
|
||||||
{% else %}
|
{% else %}
|
||||||
<a href="/prednasky/seznam_prednasek/{{seznam.id}}">Seznam přednášek na soustředění {{seznam.soustredeni.misto}} </a>
|
Seznam přednášek na soustředění {{seznam.soustredeni.misto}}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<a href="/prednasky/seznam_prednasek/{{seznam.id}}/hlasovani.csv">Export</a>
|
(<a href='{% url "seznam-export-csv" seznam=seznam.id %}'>CSV</a>)
|
||||||
</li>
|
</li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
|
|
|
@ -12,19 +12,9 @@ urlpatterns = [
|
||||||
'prednasky/metaseznam_prednasek',
|
'prednasky/metaseznam_prednasek',
|
||||||
org_required(views.MetaSeznamListView.as_view()),
|
org_required(views.MetaSeznamListView.as_view()),
|
||||||
name='metaseznam-list'),
|
name='metaseznam-list'),
|
||||||
# path(
|
|
||||||
# 'prednasky/seznam_prednasek/<int:seznam>/export',
|
|
||||||
# org_required(views.SeznamExportView),
|
|
||||||
# name='seznam-export'
|
|
||||||
# ),
|
|
||||||
path(
|
path(
|
||||||
'prednasky/seznam_prednasek/<int:seznam>/hlasovani.csv',
|
'prednasky/seznam_prednasek/<int:seznam>/hlasovani.csv',
|
||||||
org_required(views.PrednaskyExportView),
|
org_required(views.PrednaskyExportView),
|
||||||
name='seznam-export-csv'
|
name='seznam-export-csv'
|
||||||
),
|
),
|
||||||
path(
|
|
||||||
'prednasky/seznam_prednasek/<int:seznam>/',
|
|
||||||
org_required(views.SeznamListView.as_view()),
|
|
||||||
name='seznam-list'
|
|
||||||
),
|
|
||||||
]
|
]
|
||||||
|
|
|
@ -64,6 +64,7 @@ def newPrednaska(request: HttpRequest) -> HttpResponse:
|
||||||
prednaska=prednaska,
|
prednaska=prednaska,
|
||||||
body=form.cleaned_data['body'],
|
body=form.cleaned_data['body'],
|
||||||
ucastnik=ucastnik,
|
ucastnik=ucastnik,
|
||||||
|
ucastnik_osoba=osoba,
|
||||||
seznam=seznam,
|
seznam=seznam,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -116,8 +117,8 @@ def newPrednaska(request: HttpRequest) -> HttpResponse:
|
||||||
'prednasky/base.html',
|
'prednasky/base.html',
|
||||||
{
|
{
|
||||||
'form_set_prednasky': form_set_prednasky, 'form_set_znalosti': form_set_znalosti,
|
'form_set_prednasky': form_set_prednasky, 'form_set_znalosti': form_set_znalosti,
|
||||||
'formy_a_prednasky': zip(form_set_prednasky, prednasky),
|
'formy_a_prednasky': list(zip(form_set_prednasky, prednasky)),
|
||||||
'formy_a_znalosti': zip(form_set_znalosti, znalosti),
|
'formy_a_znalosti': list(zip(form_set_znalosti, znalosti)),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -132,64 +133,6 @@ class MetaSeznamListView(generic.ListView):
|
||||||
template_name = 'prednasky/metaseznam_prednasek.html'
|
template_name = 'prednasky/metaseznam_prednasek.html'
|
||||||
|
|
||||||
|
|
||||||
class SeznamListView(generic.ListView):
|
|
||||||
"""
|
|
||||||
Náhled na to, kolik má která přednáška v :py:class:`Seznamu <prednasky.models.Seznam>` :py:class:`hlasů <prednasky.models.Hlasovani.Body>`.
|
|
||||||
(Je otázka, zda tento View vůbec chceme. Pokud ano, hodilo by se do něj přidat i znalosti.)
|
|
||||||
"""
|
|
||||||
template_name = 'prednasky/seznam_prednasek.html'
|
|
||||||
|
|
||||||
def get_queryset(self):
|
|
||||||
self.seznam = get_object_or_404(Seznam, id=self.kwargs["seznam"])
|
|
||||||
prednasky = Prednaska.objects.filter(seznamy=self.seznam).order_by(
|
|
||||||
'org__osoba__user__first_name', 'org__osoba__user__last_name'
|
|
||||||
)
|
|
||||||
return prednasky
|
|
||||||
|
|
||||||
# FIXME nahradit anotaci s filtrem po prechodu na Django 2.2
|
|
||||||
def get_context_data(self,**kwargs):
|
|
||||||
context = super(SeznamListView, self).get_context_data(**kwargs)
|
|
||||||
|
|
||||||
# hlasovani se vztahuje k nejnovejsimu soustredeni
|
|
||||||
sous = Soustredeni.objects.first()
|
|
||||||
seznam = Seznam.objects.filter(soustredeni = sous, stav=Seznam.Stav.NAVRH).first()
|
|
||||||
|
|
||||||
for obj in self.object_list:
|
|
||||||
hlasovani_set = obj.hlasovani_set.filter(seznam=seznam).only('body')
|
|
||||||
obj.body = sum(map(lambda x: x.body,hlasovani_set))
|
|
||||||
|
|
||||||
return context
|
|
||||||
|
|
||||||
|
|
||||||
# def SeznamExportView(request, seznam):
|
|
||||||
# """Vypíše výsledky hlasování ve formátu pro prologovský optimalizátor"""
|
|
||||||
# # TODO zřejmě se nepoužívá, časem vyřadit? nahradit tabulkou vhodnější pro
|
|
||||||
# # lidi?
|
|
||||||
# hlasovani = Hlasovani.objects.filter(seznam=seznam)
|
|
||||||
# prednasky = Prednaska.objects.filter(seznamy=seznam)
|
|
||||||
# orgove = set(p.org for p in prednasky)
|
|
||||||
# ucastnici = set(h.ucastnik for h in hlasovani)
|
|
||||||
#
|
|
||||||
# for p in prednasky:
|
|
||||||
# p.body = []
|
|
||||||
# for u in ucastnici:
|
|
||||||
# try:
|
|
||||||
# p.body.append(hlasovani.get(ucastnik=u, prednaska=p).body)
|
|
||||||
# except ObjectDoesNotExist:
|
|
||||||
# # účastník nehlasoval
|
|
||||||
# p.body.append("?")
|
|
||||||
#
|
|
||||||
# for h in hlasovani:
|
|
||||||
# h.ucastnik = hash(h.ucastnik)
|
|
||||||
#
|
|
||||||
# return render(
|
|
||||||
# request,
|
|
||||||
# 'prednasky/seznam_prednasek_export.txt',
|
|
||||||
# {"hlasovani": hlasovani, "prednasky": prednasky, "orgove": orgove},
|
|
||||||
# content_type="text/plain"
|
|
||||||
# )
|
|
||||||
|
|
||||||
|
|
||||||
def PrednaskyExportView(request: HttpRequest, seznam: int, **kwargs) -> HttpResponse:
|
def PrednaskyExportView(request: HttpRequest, seznam: int, **kwargs) -> HttpResponse:
|
||||||
"""
|
"""
|
||||||
Vrátí všechna :py:class:`Hlasování <prednasky.models.Hlasovani>`
|
Vrátí všechna :py:class:`Hlasování <prednasky.models.Hlasovani>`
|
||||||
|
@ -214,6 +157,8 @@ def PrednaskyExportView(request: HttpRequest, seznam: int, **kwargs) -> HttpResp
|
||||||
# A po inicializaci sloupců vyplníme tabulku
|
# A po inicializaci sloupců vyplníme tabulku
|
||||||
table: [str, list[str|Prednaska|Znalost,]] = {}
|
table: [str, list[str|Prednaska|Znalost,]] = {}
|
||||||
|
|
||||||
|
errors = []
|
||||||
|
|
||||||
for h in hlasovani:
|
for h in hlasovani:
|
||||||
if h.ucastnik not in table: # Pokud jsme účastníka ještě neviděli, předgenerujeme si jeho řádek
|
if h.ucastnik not in table: # Pokud jsme účastníka ještě neviděli, předgenerujeme si jeho řádek
|
||||||
table[h.ucastnik] = [h.ucastnik] + ([""] * width)
|
table[h.ucastnik] = [h.ucastnik] + ([""] * width)
|
||||||
|
@ -221,7 +166,7 @@ def PrednaskyExportView(request: HttpRequest, seznam: int, **kwargs) -> HttpResp
|
||||||
if h.prednaska.id in prednasky_map:
|
if h.prednaska.id in prednasky_map:
|
||||||
table[h.ucastnik][prednasky_map[h.prednaska.id]] = h.body
|
table[h.ucastnik][prednasky_map[h.prednaska.id]] = h.body
|
||||||
else:
|
else:
|
||||||
pass # TODO Padat hlasitě?
|
errors.append(f"Přednáška {h.prednaska.id} ({h.prednaska}) dostala od Účastníka {h.ucastnik} následující hodnocení: {h.body}")
|
||||||
|
|
||||||
for h in hlasovani_o_znalostech:
|
for h in hlasovani_o_znalostech:
|
||||||
ucastnik = str(h.ucastnik) + ' ' + str(h.ucastnik.id) # id, kvůli kolizi jmen
|
ucastnik = str(h.ucastnik) + ' ' + str(h.ucastnik.id) # id, kvůli kolizi jmen
|
||||||
|
@ -231,8 +176,10 @@ def PrednaskyExportView(request: HttpRequest, seznam: int, **kwargs) -> HttpResp
|
||||||
if h.znalost.id in znalosti_map:
|
if h.znalost.id in znalosti_map:
|
||||||
table[ucastnik][znalosti_map[h.znalost.id]] = h.odpoved
|
table[ucastnik][znalosti_map[h.znalost.id]] = h.odpoved
|
||||||
else:
|
else:
|
||||||
pass # TODO Padat hlasitě?
|
errors.append(f"Znalost {h.znalost.id} ({h.znalost}) dostala od Účastníka {h.ucastnik.id} následující odpověď: {h.odpoved}")
|
||||||
|
|
||||||
|
if len(errors) > 0:
|
||||||
|
logger.error("Při exportování hlasování o přednáškách a znalostech se neexportovali hodnocení a přednášky (pravděpodobně se od hlasování vyškrtla nějaká znalost/přednáška ze seznamu):\n" + "\n".join(errors))
|
||||||
|
|
||||||
response = HttpResponse(content_type="text/csv", charset="utf-8")
|
response = HttpResponse(content_type="text/csv", charset="utf-8")
|
||||||
response["Content-Disposition"] = 'attachment; filename="hlasovani.csv"'
|
response["Content-Disposition"] = 'attachment; filename="hlasovani.csv"'
|
||||||
|
|
|
@ -16,4 +16,11 @@
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
|
<h2>Seznam účastníků – červená znamená že jim nechodí fyzické číslo</h2>
|
||||||
|
<ul>
|
||||||
|
{% for resitel in resitele %}
|
||||||
|
<li {% if resitel.neposilame %}style="color: white; background-color: red;"{% endif %}>{{ resitel.jmeno }}: {% if resitel.bodydiff > 3 %}🧦{% endif %} {% if resitel.ttitul != resitel.ftitul %} {{resitel.ftitul}} → {{resitel.ttitul}} {% endif %}</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
|
||||||
{% endblock content %}
|
{% endblock content %}
|
||||||
|
|
|
@ -27,6 +27,30 @@ def resi_v_rocniku(rocnik, cislo=None):
|
||||||
reseni__hodnoceni__deadline_body__cislo__rocnik=rocnik,
|
reseni__hodnoceni__deadline_body__cislo__rocnik=rocnik,
|
||||||
reseni__hodnoceni__deadline_body__cislo__poradi__lte=cislo.poradi
|
reseni__hodnoceni__deadline_body__cislo__poradi__lte=cislo.poradi
|
||||||
).distinct()
|
).distinct()
|
||||||
|
|
||||||
|
def resi_cislo(cislo):
|
||||||
|
""" Vrátí seznam řešitelů, co vyřešili nějaký problém v daném čísle.
|
||||||
|
Parametry:
|
||||||
|
cislo (typu Cislo) číslo, ve kterém chci řešitele, co něco odevzdali
|
||||||
|
Výstup:
|
||||||
|
QuerySet objektů typu Resitel
|
||||||
|
"""
|
||||||
|
|
||||||
|
return personalni.models.Resitel.objects.filter(
|
||||||
|
reseni__hodnoceni__deadline_body__cislo=cislo
|
||||||
|
).distinct()
|
||||||
|
|
||||||
|
def resitele_co_neodmaturovali():
|
||||||
|
""" Vrátí seznam řešitelů, co ještě neodmaturovali.
|
||||||
|
Pokud ještě není srpen, tak zahrnuje i ty, kteří odmaturovali letos.
|
||||||
|
|
||||||
|
Výstup:
|
||||||
|
QuerySet objektů typu Resitel """
|
||||||
|
from datetime import datetime
|
||||||
|
current_year = datetime.now().year
|
||||||
|
if datetime.now().month < 8:
|
||||||
|
current_year -= 1
|
||||||
|
return personalni.models.Resitel.objects.filter(rok_maturity__gte=current_year)
|
||||||
|
|
||||||
|
|
||||||
def aktivniResitele(cislo, pouze_letosni=False):
|
def aktivniResitele(cislo, pouze_letosni=False):
|
||||||
|
|
|
@ -375,7 +375,8 @@ class OdmenyView(generic.TemplateView):
|
||||||
tocislo = get_object_or_404(Cislo, rocnik=self.kwargs.get('trocnik'), poradi=self.kwargs.get('tcislo'))
|
tocislo = get_object_or_404(Cislo, rocnik=self.kwargs.get('trocnik'), poradi=self.kwargs.get('tcislo'))
|
||||||
resitele = utils.aktivniResitele(tocislo)
|
resitele = utils.aktivniResitele(tocislo)
|
||||||
|
|
||||||
def get_diff(from_deadline: Deadline, to_deadline: Deadline):
|
def get_diff(from_deadline: Deadline, to_deadline: Deadline, probody=False):
|
||||||
|
"""Co je probody? pokud True, funkce vrací všechny rešitele a k nim potřebné informace, pokud False, vrací jen ty, kteří mají změnu v titulu."""
|
||||||
frombody = body_resitelu(resitele=resitele, jen_verejne=False, do=from_deadline)
|
frombody = body_resitelu(resitele=resitele, jen_verejne=False, do=from_deadline)
|
||||||
tobody = body_resitelu(resitele=resitele, jen_verejne=False, do=to_deadline)
|
tobody = body_resitelu(resitele=resitele, jen_verejne=False, do=to_deadline)
|
||||||
outlist = []
|
outlist = []
|
||||||
|
@ -384,8 +385,11 @@ class OdmenyView(generic.TemplateView):
|
||||||
tbody = tobody.get(resitel.id, 0)
|
tbody = tobody.get(resitel.id, 0)
|
||||||
ftitul = resitel.get_titul(fbody)
|
ftitul = resitel.get_titul(fbody)
|
||||||
ttitul = resitel.get_titul(tbody)
|
ttitul = resitel.get_titul(tbody)
|
||||||
if ftitul != ttitul:
|
if probody:
|
||||||
outlist.append({'jmeno': resitel.osoba.plne_jmeno(), 'ftitul': ftitul, 'ttitul': ttitul})
|
outlist.append({'jmeno': resitel.osoba.plne_jmeno(), 'fbody': fbody, 'tbody': tbody, 'ftitul': ftitul, 'ttitul': ttitul, 'bodydiff': tbody - fbody, "neposilame": not(resitel.zasilat_cislo_papirove)})
|
||||||
|
else:
|
||||||
|
if ftitul != ttitul:
|
||||||
|
outlist.append({'jmeno': resitel.osoba.plne_jmeno(), 'ftitul': ftitul, 'ttitul': ttitul})
|
||||||
return outlist
|
return outlist
|
||||||
|
|
||||||
def posledni_deadline_oprava(cislo: Cislo) -> Deadline:
|
def posledni_deadline_oprava(cislo: Cislo) -> Deadline:
|
||||||
|
@ -401,6 +405,7 @@ class OdmenyView(generic.TemplateView):
|
||||||
context["from_deadline"] = from_deadline
|
context["from_deadline"] = from_deadline
|
||||||
context["to_deadline"] = to_deadline
|
context["to_deadline"] = to_deadline
|
||||||
context["zmeny"] = get_diff(from_deadline, to_deadline)
|
context["zmeny"] = get_diff(from_deadline, to_deadline)
|
||||||
|
context["resitele"] = get_diff(from_deadline, to_deadline, probody=resitele.order_by("osoba__prijmeni"))
|
||||||
|
|
||||||
return context
|
return context
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue