Web M&M
https://mam.matfyz.cz
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
467 lines
17 KiB
467 lines
17 KiB
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 personalni.models as m
|
|
from soustredeni.models import Soustredeni
|
|
from odevzdavatko.models import Hodnoceni
|
|
from tvorba.models import Clanek, Uloha, Tema
|
|
from various.models import Nastaveni
|
|
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'] = Soustredeni.objects.order_by('-datum_konce').first()
|
|
nastaveni = 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 = Hodnoceni.objects.filter(body__isnull=True)
|
|
reseni_mimo_cislo = 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 = m.Osoba.objects.get(user=u)
|
|
organizator = m.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 = Tema.objects.filter(Q(garant=organizator) | Q(autor=organizator) | Q(opravovatele__in=[organizator]),
|
|
rocnik=aktualni_rocnik).distinct()
|
|
ulohy = Uloha.objects.filter(Q(garant=organizator) | Q(autor=organizator) | Q(opravovatele__in=[organizator]),
|
|
cislo_zadani__rocnik=aktualni_rocnik).distinct()
|
|
clanky = 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 = m.Resitel
|
|
template_name = 'personalni/profil/resitel.html'
|
|
|
|
def get_object(self, queryset=None):
|
|
print(self.request.user)
|
|
return m.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('personalni.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 = m.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('personalni.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']
|
|
resitel_edit.upozorneni = fcd['upozorneni']
|
|
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('personalni.prihlaska')
|
|
err_logger = logging.getLogger('personalni.prihlaska.problem')
|
|
form_logger = logging.getLogger('personalni.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 = m.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 += '\nDOREGISTRACE 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'\nRozdí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 = m.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 += '\nDOREGISTRACE Ř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'\nRozdí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
|
|
|