import csv import http import logging from django.http import HttpResponse, HttpRequest from django.shortcuts import render, get_object_or_404 from django.views import generic from django.shortcuts import HttpResponseRedirect from django.core.exceptions import ObjectDoesNotExist from django.db import transaction from various.views.pomocne import formularOKView from .forms import HlasovaniPrednaskaFormSet, HlasovaniZnalostiFormSet from various.models import Nastaveni from prednasky.models import Prednaska, Hlasovani, Znalost, HlasovaniOZnalostech, Seznam from soustredeni.models import Soustredeni from personalni.models import Osoba PREDNASKY_PREFIX = "prednasky" ZNALOSTI_PREFIX = "znalosti" logger = logging.getLogger(__name__) def newPrednaska(request: HttpRequest) -> HttpResponse: """ View zobrazující a ukládající účastnické hlasování (:py:class:`Hlasování ` a :py:class:`HlasováníOZnalostech `) o :py:class:`Přednáškách ` a :py:class:`Znalostech ` """ # hlasovani se vztahuje k nejnovejsimu soustredeni sous = Nastaveni.get_solo().aktualni_sous seznam = Seznam.objects.filter(soustredeni = sous, stav=Seznam.Stav.NAVRH).first() if sous is None or seznam is None: return render(request, 'universal.html', { 'title': "Nelze hlasovat", 'text': "Není žádný seznam přednášek, o kterém by se dalo hlasovat.", }, status=http.HTTPStatus.NOT_FOUND) osoba = Osoba.objects.filter(user=request.user).first() ucastnik = osoba.plne_jmeno() + ' ' + str(osoba.id) # id, kvůli kolizi jmen if request.method == 'POST': # Když to byl POST, tak ukládáme. # Načteme data do formsetů form_set_prednasky = HlasovaniPrednaskaFormSet(request.POST, prefix=PREDNASKY_PREFIX) form_set_znalosti = HlasovaniZnalostiFormSet(request.POST, prefix=ZNALOSTI_PREFIX) if form_set_prednasky.is_valid() and form_set_znalosti.is_valid(): with transaction.atomic(): # Místo updatování data prostě smažeme a vytvoříme nová seznam.hlasovani_set.filter(ucastnik=ucastnik).delete() seznam.hlasovanioznalostech_set.filter(ucastnik=osoba).delete() for form in form_set_prednasky: prednaska_id = form.cleaned_data['prednaska_id'] prednaska = Prednaska.objects.filter(id=prednaska_id).first() if prednaska is None: logger.error(f"Účastník {ucastnik} hodnotil neexistující přednášku {prednaska_id} číslem {form.cleaned_data['body']}") continue Hlasovani.objects.create( prednaska=prednaska, body=form.cleaned_data['body'], ucastnik=ucastnik, seznam=seznam, ) for form in form_set_znalosti: znalost_id = form.cleaned_data['znalost_id'] znalost = Znalost.objects.filter(id=znalost_id).first() if znalost is None: logger.error(f"Účastník {ucastnik} hodnotil neexistující znalost {znalost_id} číslem {form.cleaned_data['odpoved']}") continue HlasovaniOZnalostech.objects.create( odpoved=form.cleaned_data['odpoved'], znalost=znalost, ucastnik=osoba, seznam=seznam, ) return HttpResponseRedirect('./hotovo') else: # Pokud je nějaký formset nevalidní, vracíme je k přepracování prednasky = seznam.prednaska_set.all() znalosti = seznam.znalost_set.all() # FIXME Spadnout, pokud nesedí přednáška/znalost s formulářem. (Nějak se mi to nepovedlo.) # Může se totiž stát, že se mezitím změnily přednášky (nějaká byla přidána/odebrána) else: # Když to nebyl POST, tak inicializujeme (pokud už o přednášce/znalosti účastník hlasoval, předvyplníme mu to). def odpoved_prednasky(p: Prednaska) -> Hlasovani.Body: hlasovani = p.hlasovani_set.filter(ucastnik=ucastnik).first() return hlasovani.body if hlasovani else Hlasovani.Body.JEDNO def odpoved_znalosti(z: Znalost) -> HlasovaniOZnalostech.Odpoved: hlasovani = z.hlasovanioznalostech_set.filter(ucastnik=osoba).first() return hlasovani.odpoved if hlasovani else HlasovaniOZnalostech.Odpoved.CIRCA prednasky = seznam.prednaska_set.all() znalosti = seznam.znalost_set.all() form_set_prednasky = HlasovaniPrednaskaFormSet(initial=[ {"prednaska_id": p.id, "body": odpoved_prednasky(p)} for p in prednasky ], prefix=PREDNASKY_PREFIX) form_set_znalosti = HlasovaniZnalostiFormSet(initial=[ {"znalost_id": z.id, "odpoved": odpoved_znalosti(z)} for z in znalosti ], prefix=ZNALOSTI_PREFIX) # V případě nePOSTu nebo chyby při ukládání vracíme hlasování return render( request, 'prednasky/base.html', { 'form_set_prednasky': form_set_prednasky, 'form_set_znalosti': form_set_znalosti, 'formy_a_prednasky': zip(form_set_prednasky, prednasky), 'formy_a_znalosti': zip(form_set_znalosti, znalosti), } ) def Prednaska_hotovo(request: HttpRequest) -> HttpResponse: """ View po vyplnění :py:func:`hlasování ` """ return formularOKView(request, "Děkujeme za vyplnění hlasování o přednáškách a těšíme se na soustředění.") class MetaSeznamListView(generic.ListView): """ Seznam všech :py:class:`Seznamů ` s odkazy na exporty """ model = Seznam template_name = 'prednasky/metaseznam_prednasek.html' class SeznamListView(generic.ListView): """ Náhled na to, kolik má která přednáška v :py:class:`Seznamu ` :py:class:`hlasů `. (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: """ Vrátí všechna :py:class:`Hlasování ` i :py:class:`HlasováníOZnalostech ` v daném :py:class:`Seznamu ` jako csv soubor (řádky = účastníci, sloupce = přednášky&znalosti). :param seznam: ID daného :py:class:`Seznamu ` """ hlasovani = Hlasovani.objects.filter(seznam=seznam).select_related("prednaska") hlasovani_o_znalostech = HlasovaniOZnalostech.objects.filter(seznam=seznam).select_related('ucastnik', 'znalost') # Inicializujeme sloupce prednasky = list(Prednaska.objects.filter(seznamy=seznam)) znalosti = list(Znalost.objects.filter(seznamy=seznam)) prednasky_map: dict[int, int] = {p.id: i for i, p in enumerate(prednasky, 1)} offset = len(prednasky_map) znalosti_map: dict[int, int] = {z.id: i for i, z in enumerate(znalosti, offset + 1)} width = offset + len(znalosti_map) # A po inicializaci sloupců vyplníme tabulku table: [str, list[str|Prednaska|Znalost,]] = {} for h in hlasovani: 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) if h.prednaska.id in prednasky_map: table[h.ucastnik][prednasky_map[h.prednaska.id]] = h.body else: pass # TODO Padat hlasitě? for h in hlasovani_o_znalostech: ucastnik = str(h.ucastnik) + ' ' + str(h.ucastnik.id) # id, kvůli kolizi jmen if ucastnik not in table: # Pokud jsme účastníka ještě neviděli, předgenerujeme si jeho řádek table[ucastnik] = [ucastnik] + ([""] * width) if h.znalost.id in znalosti_map: table[ucastnik][znalosti_map[h.znalost.id]] = h.odpoved else: pass # TODO Padat hlasitě? response = HttpResponse(content_type="text/csv", charset="utf-8") response["Content-Disposition"] = 'attachment; filename="hlasovani.csv"' writer = csv.writer(response) writer.writerow(["jména \\ přednáška|znalost"] + list(map(str, prednasky + znalosti))) for row in table.values(): writer.writerow(list(map(str, row))) return response