from django.shortcuts import render from django.urls import reverse from django.views import generic from django.db.models import Q, Count 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 from django.contrib.auth.mixins import LoginRequiredMixin from django.db import transaction from django.http import HttpResponse 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 seminar.views import formularOKView from various.autentizace.views import LoginView from various.autentizace.utils import posli_reset_hesla from django.forms.models import model_to_dict 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(cislo_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(count=Count('problem')).order_by() context["pocty_neopravenych_reseni"] = [(it['problem__nazev'], it['count']) 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.pohlavi_muz = fcd['pohlavi_muz'] 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.skola = fcd['skola'] resitel_edit.rok_maturity = fcd['rok_maturity'] resitel_edit.zasilat = fcd['zasilat'] resitel_edit.zasilat_cislo_emailem = fcd['zasilat_cislo_emailem'] 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=f'Údaje byly úspěšně uloženy. Vrátit se zpět na profil.') return render(request, 'personalni/udaje/edit.html', {'form': form}) @sensitive_post_parameters('jmeno', 'prijmeni', 'email', 'telefon', 'datum_narozeni', 'ulice', 'mesto', 'psc', 'skola') 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'], pohlavi_muz = fcd['pohlavi_muz'], 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',''), 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', 'pohlavi_muz', '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 = s.Resitel( rok_maturity = fcd['rok_maturity'], zasilat = fcd['zasilat'], zasilat_cislo_emailem = fcd['zasilat_cislo_emailem'] ) 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'] 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 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__pohlavi_muz', 'osoba__ulice', 'osoba__mesto', 'osoba__psc', 'osoba__stat', 'skola', #FIXME: dává jen ID 'poznamka', 'osoba__poznamka', 'rok_maturity', 'zasilat', 'zasilat_cislo_emailem', '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__pohlavi_muz': 'pohlavi_muz', '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