import tempfile
import subprocess
import shutil
import http
from django . shortcuts import render
from django . urls import reverse
from django . views import generic
from django . db . models import Q , Count , Min
from django . views . decorators . debug import sensitive_post_parameters
from django . views . generic . base import TemplateView
from django . contrib . auth . models import User , Permission , Group , AnonymousUser
from django . contrib . auth . mixins import LoginRequiredMixin
from django . contrib . staticfiles . finders import find
from django . db import transaction
from django . http import HttpResponse
from django . utils import timezone
import seminar . models as s
import seminar . models as m
from . forms import PrihlaskaForm , ProfileEditForm , PoMaturiteProfileEditForm
from datetime import date
import logging
import csv
from various . views . pomocne import formularOKView
from various . autentizace . views import LoginView
from various . autentizace . utils import posli_reset_hesla
from django . forms . models import model_to_dict
from . models import Organizator
def aktivniOrganizatori ( datum = timezone . now ( ) ) :
return Organizator . objects . exclude (
organizuje_do__isnull = False ,
organizuje_do__lt = datum
) . order_by ( ' osoba__jmeno ' )
class CojemamOrganizatoriView ( generic . ListView ) :
model = Organizator
template_name = ' personalni/organizatori.html '
queryset = aktivniOrganizatori ( )
def get_context_data ( self , * * kwargs ) :
context = super ( CojemamOrganizatoriView , self ) . get_context_data ( * * kwargs )
context [ ' aktivni ' ] = True
return context
class CojemamOrganizatoriStariView ( generic . ListView ) :
model = Organizator
template_name = ' personalni/organizatori.html '
queryset = Organizator . objects . exclude (
id__in = aktivniOrganizatori ( )
) . order_by ( ' -organizuje_do ' )
def obalkyView ( request , resitele ) :
if len ( resitele ) == 0 :
return HttpResponse (
render ( request , ' universal.html ' , {
' title ' : ' Není pro koho vyrobit obálky. ' ,
' text ' : ' Právě ses pokusil/a vygenerovat obálky pro prázdnou množinu lidí. Můžeš to zkusit změnit, případně se zeptej webařů :-) ' ,
} ) ,
status = http . HTTPStatus . NOT_FOUND ,
)
tex = render ( request , ' personalni/obalky.tex ' , {
' resitele ' : resitele
} ) . content
with tempfile . TemporaryDirectory ( ) as tempdir :
with open ( tempdir + " /obalky.tex " , " w " ) as texfile :
texfile . write ( tex . decode ( ) )
shutil . copy ( find ( ' personalni/lisak.pdf ' ) , tempdir )
subprocess . call ( [ " pdflatex " , " obalky.tex " ] , cwd = tempdir )
with open ( tempdir + " /obalky.pdf " , " rb " ) as pdffile :
response = HttpResponse ( pdffile . read ( ) , content_type = ' application/pdf ' )
return response
class OrgoRozcestnikView ( TemplateView ) :
""" Zobrazí organizátorský rozcestník. """
template_name = ' personalni/profil/orgorozcestnik.html '
def get_context_data ( self , * * kwargs ) :
context = super ( ) . get_context_data ( * * kwargs )
context [ ' posledni_soustredeni ' ] = s . Soustredeni . objects . order_by ( ' -datum_konce ' ) . first ( )
nastaveni = s . Nastaveni . objects . first ( )
aktualni_rocnik = nastaveni . aktualni_rocnik
context [ ' posledni_cislo_url ' ] = nastaveni . aktualni_cislo . verejne_url ( )
# TODO možná chceme odkazovat na právě rozpracované číslo, a ne to poslední vydané
# pokud nechceme haluzit kód (= poradi) dalšího čísla, bude asi potřeba jít
# 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 ( deadline_body__isnull = True )
context [ ' pocet_neobodovanych_reseni ' ] = neobodovana_reseni . count ( )
context [ ' pocet_reseni_mimo_cislo ' ] = reseni_mimo_cislo . count ( )
u = self . request . user
os = s . Osoba . objects . get ( user = u )
organizator = s . Organizator . objects . get ( osoba = os )
context [ ' muj_pocet_neobodovanych_reseni ' ] = neobodovana_reseni . filter ( Q ( problem__garant = organizator ) | Q ( problem__autor = organizator ) | Q ( problem__opravovatele__in = [ organizator ] ) ) . distinct ( ) . count ( )
context [ ' muj_pocet_reseni_mimo_cislo ' ] = reseni_mimo_cislo . filter ( Q ( problem__garant = organizator ) | Q ( problem__autor = organizator ) | Q ( problem__opravovatele__in = [ organizator ] ) ) . count ( )
# Termínářský přehled
pocty_neopravenych_reseni = neobodovana_reseni . select_related ( ' problem ' ) . values ( ' problem__nazev ' ) . annotate ( cas = Min ( ' reseni__cas_doruceni ' ) ) . order_by ( ' cas ' )
context [ " pocty_neopravenych_reseni " ] = [ ( it [ ' problem__nazev ' ] , it [ ' cas ' ] . date ) for it in pocty_neopravenych_reseni . all ( ) ]
#FIXME: přidat stav='STAV_ZADANY'
temata = s . Tema . objects . filter ( Q ( garant = organizator ) | Q ( autor = organizator ) | Q ( opravovatele__in = [ organizator ] ) ,
rocnik = aktualni_rocnik ) . distinct ( )
ulohy = s . Uloha . objects . filter ( Q ( garant = organizator ) | Q ( autor = organizator ) | Q ( opravovatele__in = [ organizator ] ) ,
cislo_zadani__rocnik = aktualni_rocnik ) . distinct ( )
clanky = s . Clanek . objects . filter ( Q ( garant = organizator ) | Q ( autor = organizator ) | Q ( opravovatele__in = [ organizator ] ) ,
cislo__rocnik = aktualni_rocnik ) . distinct ( )
context [ ' temata ' ] = temata
context [ ' ulohy ' ] = ulohy
context [ ' clanky ' ] = clanky
context [ ' organizator ' ] = organizator
return context
#content_type = 'text/plain; charset=UTF8'
#XXX
class ResitelView ( LoginRequiredMixin , generic . DetailView ) :
model = s . Resitel
template_name = ' personalni/profil/resitel.html '
def get_object ( self , queryset = None ) :
print ( self . request . user )
return s . Resitel . objects . get ( osoba__user = self . request . user )
### Formulare
# pro přidání políčka do formuláře je potřeba
# - mít v modelu tu položku, kterou chci upravovat
# - přidat do views (prihlaskaView, resitelEditView)
# - přidat do forms
# - includovat do html
def prihlaska_log_gdpr_safe ( logger , gdpr_logger , msg , form_data ) :
msg = " {} , form_hash: {} " . format ( msg , hash ( frozenset ( form_data . items ) ) )
logger . warn ( msg )
gdpr_logger . warn ( msg + " , form: {} " . format ( form_data ) )
@sensitive_post_parameters ( ' jmeno ' , ' prijmeni ' , ' email ' , ' telefon ' , ' datum_narozeni ' , ' ulice ' , ' mesto ' , ' psc ' , ' skola ' )
def resitelEditView ( request ) :
err_logger = logging . getLogger ( ' seminar.prihlaska.problem ' )
## Načtení objektů Osoba a Resitel patřících k aktuálně přihlášenému uživateli
u = request . user
osoba_edit = s . Osoba . objects . get ( user = u )
if hasattr ( osoba_edit , ' resitel ' ) :
resitel_edit = osoba_edit . resitel
else :
resitel_edit = None
user_edit = osoba_edit . user
## Vytvoření slovníku, kterým předvyplním formulář
prefill_1 = model_to_dict ( user_edit )
if resitel_edit :
prefill_2 = model_to_dict ( resitel_edit )
prefill_1 . update ( prefill_2 )
prefill_3 = model_to_dict ( osoba_edit )
prefill_1 . update ( prefill_3 )
if ' datum_narozeni ' in prefill_1 :
prefill_1 [ ' datum_narozeni ' ] = str ( prefill_1 [ ' datum_narozeni ' ] )
if ' datum_souhlasu_udaje ' in prefill_1 :
prefill_1 [ ' spam ' ] = bool ( prefill_1 [ ' datum_souhlasu_zasilani ' ] )
if ' rok_maturity ' not in prefill_1 or prefill_1 [ ' rok_maturity ' ] < date . today ( ) . year :
form = PoMaturiteProfileEditForm ( initial = prefill_1 )
else :
form = ProfileEditForm ( initial = prefill_1 )
## Změna údajů a jejich uložení
if request . method == ' POST ' :
POST = request . POST . copy ( )
POST [ " username " ] = osoba_edit . user . username
if ' rok_maturity ' not in prefill_1 or prefill_1 [ ' rok_maturity ' ] < date . today ( ) . year :
form = PoMaturiteProfileEditForm ( POST )
else :
form = ProfileEditForm ( POST )
form . username = user_edit . username
if form . is_valid ( ) :
## Změny v osobě
fcd = form . cleaned_data
form_hash = hash ( frozenset ( fcd . items ( ) ) )
form_logger = logging . getLogger ( ' seminar.prihlaska.form ' )
form_logger . info ( " EDIT: " + str ( fcd ) + str ( form_hash ) ) # TODO možná logovat jinak
osoba_edit . jmeno = fcd [ ' jmeno ' ]
osoba_edit . prijmeni = fcd [ ' prijmeni ' ]
osoba_edit . osloveni = fcd [ ' osloveni ' ]
osoba_edit . email = fcd [ ' email ' ]
osoba_edit . telefon = fcd [ ' telefon ' ]
osoba_edit . ulice = fcd [ ' ulice ' ]
osoba_edit . mesto = fcd [ ' mesto ' ]
osoba_edit . psc = fcd [ ' psc ' ]
osoba_edit . datum_narozeni = fcd [ ' datum_narozeni ' ]
## Změny v osobě s podmínkami
if fcd . get ( ' spam ' , False ) :
if not osoba_edit . datum_souhlasu_zasilani :
osoba_edit . datum_souhlasu_zasilani = date . today ( )
else :
osoba_edit . datum_souhlasu_zasilani = None
if fcd . get ( ' stat ' , ' ' ) in ( ' CZ ' , ' SK ' ) :
osoba_edit . stat = fcd [ ' stat ' ]
else :
## Neznámá země
msg = " Unknown country {} " . format ( fcd [ ' stat_text ' ] )
if resitel_edit :
## Změny v řešiteli
resitel_edit . prezdivka_resitele = fcd [ ' prezdivka_resitele ' ] if fcd [ ' prezdivka_resitele ' ] != ' ' else None
resitel_edit . skola = fcd [ ' skola ' ]
resitel_edit . rok_maturity = fcd [ ' rok_maturity ' ]
resitel_edit . zasilat = fcd [ ' zasilat ' ]
resitel_edit . zasilat_cislo_emailem = fcd [ ' zasilat_cislo_emailem ' ]
resitel_edit . zasilat_cislo_papirove = fcd [ ' zasilat_cislo_papirove ' ]
if fcd . get ( ' skola ' ) :
resitel_edit . skola = fcd [ ' skola ' ]
else :
# Unknown school - log it
msg = " Unknown school {} , {} " . format ( fcd [ ' skola_nazev ' ] , fcd [ ' skola_adresa ' ] )
resitel_edit . save ( )
osoba_edit . save ( )
return formularOKView (
request ,
text = ' Údaje byly úspěšně uloženy. ' ,
dalsi_odkazy = [ ( " Vrátit se zpět na profil " , reverse ( " profil " ) ) ] ,
)
return render ( request , ' personalni/udaje/edit.html ' , { ' form ' : form } )
@sensitive_post_parameters ( ' jmeno ' , ' prijmeni ' , ' email ' , ' telefon ' , ' datum_narozeni ' , ' ulice ' , ' mesto ' , ' psc ' , ' skola ' , ' jak_se_dozvedeli ' )
def prihlaskaView ( request ) :
generic_logger = logging . getLogger ( ' seminar.prihlaska ' )
err_logger = logging . getLogger ( ' seminar.prihlaska.problem ' )
form_logger = logging . getLogger ( ' seminar.prihlaska.form ' )
if request . method == ' POST ' :
form = PrihlaskaForm ( request . POST )
# TODO vyresit, co se bude v jakych situacich zobrazovat
if form . is_valid ( ) :
generic_logger . info ( " Form valid " )
fcd = form . cleaned_data
form_hash = hash ( frozenset ( fcd . items ( ) ) )
form_logger . info ( str ( fcd ) + str ( form_hash ) ) # TODO možná logovat jinak
with transaction . atomic ( ) :
u = User . objects . create_user (
username = fcd [ ' username ' ] ,
email = fcd [ ' email ' ] )
u . save ( )
resitel_perm = Permission . objects . filter ( codename__exact = ' resitel ' ) . first ( )
u . user_permissions . add ( resitel_perm )
resitel_grp = Group . objects . filter ( name__exact = ' resitel ' ) . first ( )
u . groups . add ( resitel_grp )
o = s . Osoba (
jmeno = fcd [ ' jmeno ' ] ,
prijmeni = fcd [ ' prijmeni ' ] ,
osloveni = fcd [ ' osloveni ' ] ,
email = fcd [ ' email ' ] ,
telefon = fcd . get ( ' telefon ' , ' ' ) ,
datum_narozeni = fcd . get ( ' datum_narozeni ' , None ) ,
datum_souhlasu_udaje = date . today ( ) ,
datum_registrace = date . today ( ) ,
ulice = fcd . get ( ' ulice ' , ' ' ) ,
mesto = fcd . get ( ' mesto ' , ' ' ) ,
psc = fcd . get ( ' psc ' , ' ' ) ,
jak_se_dozvedeli = fcd . get ( ' jak_se_dozvedeli ' , ' ' ) ,
poznamka = str ( fcd )
)
if fcd . get ( ' spam ' , False ) :
o . datum_souhlasu_zasilani = date . today ( )
if fcd . get ( ' stat ' , ' ' ) in ( ' CZ ' , ' SK ' ) :
o . stat = fcd [ ' stat ' ]
else :
# Unknown country - log it
msg = " Unknown country {} " . format ( fcd [ ' stat_text ' ] )
err_logger . warn ( msg + str ( form_hash ) )
# Dovolujeme doregistraci uživatele pro existující mail, takže naopak chceme doplnit/aktualizovat údaje do stávajícího objektu
try :
orig_osoba = m . Osoba . objects . get ( email = fcd [ ' email ' ] )
orig_osoba . poznamka + = ' \n DOREGISTRACE K EXISTUJÍCÍMU E-MAILU, diff níže. '
except m . Osoba . DoesNotExist :
# Trik: Budeme aktualizovat údaje nové osoby, takže se asi nic nezmění, ale fungovat to bude.
orig_osoba = o
# Porovnání údajů
assert orig_osoba . user is None , " Právě-registrující-se osoba už má Uživatele! "
osoba_attrs = [ ' jmeno ' , ' prijmeni ' , ' osloveni ' , ' email ' , ' telefon ' , ' datum_narozeni ' , ' ulice ' , ' mesto ' , ' psc ' , ' stat ' , ' datum_souhlasu_udaje ' , ' datum_souhlasu_zasilani ' , ' datum_registrace ' ]
diffattrs = [ ]
for attr in osoba_attrs :
new = getattr ( o , attr )
old = getattr ( orig_osoba , attr )
if new != old :
orig_osoba . poznamka + = f ' \n Rozdíl v { attr } : Původní { old } , nový { new } '
diffattrs . append ( f ' Osoba. { attr } ' )
setattr ( orig_osoba , attr , new )
# Datum registrace chceme původní / nižší:
orig_osoba . datum_registrace = min ( orig_osoba . datum_registrace , o . datum_registrace )
# Od této chvíle dál je správná osoba ta "původní", novou podle formuláře si ale zachováme
o , o_form = orig_osoba , o
o . save ( )
o . user = u
o . save ( )
# Jednoduchá kvazi-kontrola duplicitních Osob
kolize = m . Osoba . objects . filter ( jmeno = o . jmeno , prijmeni = o . prijmeni )
if kolize . count ( ) > 1 : # Jednu z nich jsme právě uložili
err_logger . warning ( f ' Zaregistrovala se osoba s kolizním jménem. ID osob: { [ o . id for o in kolize ] } ' )
r = s . Resitel (
prezdivka_resitele = fcd [ ' prezdivka_resitele ' ] if fcd [ ' prezdivka_resitele ' ] != " " else None ,
rok_maturity = fcd [ ' rok_maturity ' ] ,
zasilat = fcd [ ' zasilat ' ] ,
zasilat_cislo_emailem = fcd [ ' zasilat_cislo_emailem ' ] ,
zasilat_cislo_papirove = fcd [ ' zasilat_cislo_papirove ' ] ,
)
if fcd . get ( ' skola ' ) :
r . skola = fcd [ ' skola ' ]
else :
# Unknown school - log it
msg = " Unknown school {} , {} " . format ( fcd [ ' skola_nazev ' ] , fcd [ ' skola_adresa ' ] )
err_logger . warn ( msg + str ( form_hash ) )
# Porovnání údajů u řešitele
try :
orig_resitel = o . resitel
orig_resitel . poznamka + = ' \n DOREGISTRACE ŘEŠITELE, diff: '
except m . Resitel . DoesNotExist :
# Stejný trik:
orig_resitel = r
resitel_attrs = [ ' skola ' , ' rok_maturity ' , ' zasilat ' , ' zasilat_cislo_emailem ' , ' zasilat_cislo_papirove ' ]
for attr in resitel_attrs :
new = getattr ( r , attr )
old = getattr ( orig_resitel , attr )
if new != old :
orig_resitel . poznamka + = f ' \n Rozdíl v { attr } : Původní { old } , nový { new } '
diffattrs . append ( f ' Resitel. { attr } ' )
setattr ( orig_resitel , attr , new )
r , r_form = orig_resitel , r
r . osoba = o # Tohle by mělo být bezpečné…
r . save ( )
if diffattrs : err_logger . warning ( f ' Different fields when matching Řešitel id { r . id } or Osoba id { o . id } : { diffattrs } ' )
posli_reset_hesla ( u , request )
return formularOKView ( request , text = ' Na tvůj e-mail jsme právě poslali odkaz pro nastavení hesla. ' )
# if a GET (or any other method) we'll create a blank form
else :
form = PrihlaskaForm ( )
return render ( request , ' personalni/udaje/prihlaska.html ' , { ' form ' : form } )
# Jen hloupé rozhazovátko
def profilView ( request ) :
user = request . user
if not isinstance ( user , AnonymousUser ) and m . Osoba . objects . filter ( user = user ) . count ( ) != 1 :
# m.Osoba.objects.get() v ostatních views selže
return render ( request , " universal.html " , {
' title ' : ' Krize identity. ' ,
' raw_html ' : r ' <blockquote>Zvláštní pocit, že jo?<br>[…]<br>Co to znamená?<br>— Že ti MaMweb neumí říct, kdo jsi.<br>A <a href= " /admin " >Admin</a> ano?<br>— V tom je rozdíl.</blockquote> — Matrix (1999), parafrázováno ' ,
} )
if user . has_perm ( ' auth.org ' ) :
return OrgoRozcestnikView . as_view ( ) ( request )
if user . has_perm ( ' auth.resitel ' ) :
return ResitelView . as_view ( ) ( request )
else :
return LoginView . as_view ( ) ( request )
def dataResiteluCsvResponse ( queryset , columns = None , with_header = True ) :
""" Pomocná funkce pro vracení dat řešitelů jako CSV. Musí dostat správný QuerySet, který dává Řešitele """
# TODO: Možná nějak zobecnit i na Osoby?
# TODO: Nemá to spíš být class-based? Tohle je efektivně metoda "get", které ale chybí "get_queryset"…
default_columns = (
' id ' ,
' osoba__jmeno ' ,
' osoba__prijmeni ' ,
' osoba__prezdivka ' ,
' osoba__email ' ,
' osoba__telefon ' ,
' osoba__user__username ' ,
' osoba__datum_narozeni ' ,
' osoba__osloveni ' ,
' osoba__ulice ' ,
' osoba__mesto ' ,
' osoba__psc ' ,
' osoba__stat ' ,
' skola ' , #FIXME: dává jen ID
' osoba__jak_se_dozvedeli ' ,
' poznamka ' ,
' osoba__poznamka ' ,
' rok_maturity ' ,
' zasilat ' ,
' zasilat_cislo_emailem ' ,
' zasilat_cislo_papirove ' ,
' osoba__datum_registrace ' ,
' osoba__datum_souhlasu_udaje ' ,
' osoba__datum_souhlasu_zasilani ' ,
)
if columns is None : columns = default_columns
field_name_overrides = {
# Zrušení prefixu "osoba__"
' osoba__jmeno ' : ' jmeno ' ,
' osoba__prijmeni ' : ' prijmeni ' ,
' osoba__prezdivka ' : ' prezdivka ' ,
' osoba__email ' : ' email ' ,
' osoba__telefon ' : ' telefon ' ,
' osoba__user__username ' : ' user ' ,
' osoba__datum_narozeni ' : ' datum_narozeni ' ,
' osoba__osloveni ' : ' osloveni ' ,
' osoba__ulice ' : ' ulice ' ,
' osoba__mesto ' : ' mesto ' ,
' osoba__psc ' : ' psc ' ,
' osoba__stat ' : ' stat ' ,
' osoba__datum_registrace ' : ' datum_registrace ' ,
' osoba__datum_souhlasu_udaje ' : ' datum_souhlasu_udaje ' ,
' osoba__datum_souhlasu_zasilani ' : ' datum_souhlasu_zasilani ' ,
}
def get_field_name ( column_name ) :
if column_name in field_name_overrides :
return field_name_overrides [ 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