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'] 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'
Zvláštní pocit, že jo?
[…]
Co to znamená?
— Že ti MaMweb neumí říct, kdo jsi.
A Admin ano?
— V tom je rozdíl.
— 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