diff --git a/seminar/forms.py b/seminar/forms.py index 2306ffac..5d1ae871 100644 --- a/seminar/forms.py +++ b/seminar/forms.py @@ -1,5 +1,6 @@ from django import forms from dal import autocomplete +from django.contrib.auth.forms import PasswordResetForm from django.core.exceptions import ObjectDoesNotExist from django.contrib.auth.models import User from django.forms import formset_factory @@ -37,21 +38,11 @@ class LoginForm(forms.Form): widget=forms.PasswordInput()) -class PrihlaskaForm(forms.Form): +class PrihlaskaForm(PasswordResetForm): username = forms.CharField(label='Přihlašovací jméno', max_length=256, required=True, help_text='Tímto jménem se následně budeš přihlašovat pro odevzdání řešení a další činnosti v semináři') - password = forms.CharField( - label='Heslo', - max_length=256, - required=True, - widget=forms.PasswordInput()) - password_check = forms.CharField( - label='Ověření hesla', - max_length=256, - required=True, - widget=forms.PasswordInput()) jmeno = forms.CharField(label='Jméno', max_length=256, required=True) prijmeni = forms.CharField(label='Příjmení', max_length=256, required=True) diff --git a/seminar/templates/seminar/profil/prihlaska.html b/seminar/templates/seminar/profil/prihlaska.html index 6074e24a..840cf642 100644 --- a/seminar/templates/seminar/profil/prihlaska.html +++ b/seminar/templates/seminar/profil/prihlaska.html @@ -33,8 +33,8 @@ {% include "seminar/profil/prihlaska_field.html" with field=form.username %} - {% include "seminar/profil/prihlaska_field.html" with field=form.password %} - {% include "seminar/profil/prihlaska_field.html" with field=form.password_check %} +{# {% include "seminar/profil/prihlaska_field.html" with field=form.password %}#} +{# {% include "seminar/profil/prihlaska_field.html" with field=form.password_check %}#}

diff --git a/seminar/views/views_all.py b/seminar/views/views_all.py index ec10b173..004995aa 100644 --- a/seminar/views/views_all.py +++ b/seminar/views/views_all.py @@ -1,10 +1,12 @@ - - +from django.contrib.auth.tokens import PasswordResetTokenGenerator +from django.contrib.sites.shortcuts import get_current_site from django.shortcuts import get_object_or_404, render, redirect from django.http import HttpResponse, HttpResponseRedirect, HttpResponseForbidden, JsonResponse from django.urls import reverse,reverse_lazy from django.core.exceptions import PermissionDenied, ObjectDoesNotExist from django.core.mail import send_mail +from django.utils.encoding import force_bytes +from django.utils.http import urlsafe_base64_encode from django.views import generic from django.utils.translation import ugettext as _ from django.http import Http404,HttpResponseBadRequest,HttpResponseRedirect @@ -79,7 +81,7 @@ class ObalkovaniView(generic.ListView): def get_context_data(self, **kwargs): context = super(ObalkovaniView, self).get_context_data(**kwargs) print(self.cislo) - context['cislo'] = self.cislo + context['cislo'] = self.cislo return context class TNLData(object): @@ -97,7 +99,7 @@ class TNLData(object): self.tema_in_path = True def add_edit_options(self): - self.deletable = tnltt.deletable(self) + self.deletable = tnltt.deletable(self) self.editable_siblings = tnltt.editableSiblings(self) self.editable_children = tnltt.editableChildren(self) self.text_only_subtree = tnltt.textOnlySubtree(self) @@ -117,7 +119,7 @@ class TNLData(object): while True: rocnik = isinstance(parent, s.RocnikNode) cislo = isinstance(parent, s.CisloNode) - uloha = (isinstance(parent, s.UlohaVzorakNode) or + uloha = (isinstance(parent, s.UlohaVzorakNode) or isinstance(parent, s.UlohaZadaniNode)) tema = isinstance(parent, s.TemaVCisleNode) @@ -137,7 +139,7 @@ class TNLData(object): print("Existuje TreeNode, který není pod číslem, ročníkem, úlohou" "ani tématem. {}".format(anode)) return False - + @classmethod def all_public_children(cls, anode): for ch in treelib.all_children(anode): @@ -156,14 +158,14 @@ class TNLData(object): if user.has_perm('auth.org'): enum_children = enumerate(treelib.all_children(anode)) else: - enum_children = enumerate(TNLData.all_public_children(anode)) - + enum_children = enumerate(TNLData.all_public_children(anode)) + for (idx,ch) in enum_children: outitem = cls.from_treenode(ch, user, out, idx) out.children.append(outitem) out.add_edit_options() return out - + @classmethod def from_tnldata_list(cls, tnllist): """Vyrobíme virtuální TNL, který nemá obsah, ale má za potomky všechna zadaná TNLData""" @@ -192,7 +194,7 @@ class TNLData(object): for tnl in result: found.append(tnl) return found - + def to_json(self): #self.node = anode #self.children = [] @@ -264,7 +266,7 @@ class TreeNodePridatView(generic.View): if kam not in ('pred','syn','za'): raise ValidationError('Přidat lze pouze před nebo za node nebo jako syna') - + if co == m.TextNode: new_obj = m.Text() new_obj.save() @@ -303,7 +305,7 @@ class TreeNodePridatView(generic.View): node = treelib.create_node_after(node,typ) return redirect(node.get_admin_url()) - + class TreeNodeSmazatView(generic.base.View): def post(self, request, *args, **kwargs): @@ -343,7 +345,7 @@ class TreeNodeProhoditView(generic.base.View): class SirotcinecView(generic.ListView): model = s.TreeNode template_name = 'seminar/orphanage.html' - + def get_queryset(self): return s.TreeNode.objects.not_instance_of(s.RocnikNode).filter(root=None,prev=None,succ=None,father_of_first=None) @@ -548,7 +550,7 @@ class TitulniStranaView(generic.ListView): def get_context_data(self, **kwargs): context = super(TitulniStranaView, self).get_context_data(**kwargs) nastaveni = get_object_or_404(Nastaveni) - + deadline_soustredeni = (nastaveni.aktualni_cislo.datum_deadline_soustredeni, "soustredeni") preddeadline = (nastaveni.aktualni_cislo.datum_preddeadline, "preddeadline") deadline = (nastaveni.aktualni_cislo.datum_deadline, "deadline") @@ -564,7 +566,7 @@ class TitulniStranaView(generic.ListView): context['nejblizsi_deadline'] = datetime.combine(nejblizsi_deadline[0], datetime.max.time()) else: context['nejblizsi_deadline'] = None - + context['typ_deadline'] = nejblizsi_deadline[1] # Aktuální témata @@ -637,10 +639,10 @@ class ArchivView(generic.ListView): urls[c.rocnik] = c.titulka_nahled.url context["object_list"] = urls - + return context - + @@ -762,7 +764,7 @@ class OdmenyView(generic.TemplateView): outlist.append({'jmeno': resitel.osoba.plne_jmeno(), 'ftitul': ftitul, 'ttitul': ttitul}) context['zmeny'] = outlist return context - + @@ -852,7 +854,7 @@ class OrgoRozcestnikView(TemplateView): os = s.Osoba.objects.get(user=u) organizator = s.Organizator.objects.get(osoba=os) #FIXME: přidat stav='STAV_ZADANY' - temata = s.Tema.objects.filter(Q(garant=organizator) | Q(autor=organizator) | Q(opravovatele__in=[organizator]), + 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() @@ -877,7 +879,7 @@ def TitulyView(request, rocnik, cislo): cislo_obj = Cislo.objects.get(rocnik = rocnik_obj, poradi = cislo) asciijmena = [] - jmenovci = False # detekuje, zda jsou dva řešitelé jmenovci (modulo nabodeníčka), + jmenovci = False # detekuje, zda jsou dva řešitelé jmenovci (modulo nabodeníčka), # pokud ano, vrátí se jako true slovnik_s_body = body_resitelu(resitele, rocnik_obj) @@ -893,7 +895,7 @@ def TitulyView(request, rocnik, cislo): asciijmena.append(resitel.ascii) else: jmenovci = True - + return render(request, 'seminar/archiv/tituly.tex', {'resitele': resitele,'jmenovci':jmenovci},content_type="text/plain") @@ -966,7 +968,7 @@ def group_by_rocnik(clanky): rocnik = clanek.cislo.rocnik.rocnik skupiny_clanku.append(skupina) return skupiny_clanku - + # FIXME: clanky jsou vsechny, pokud budou i neresitelske, tak se take zobrazi # FIXME: Původně tu byl kód přímo v těle třídy, což rozbíjelo migrace. Opravil jsem, ale vůbec nevím, jestli to funguje. @@ -1055,9 +1057,9 @@ class NahrajReseniView(LoginRequiredMixin, CreateView): def get_context_data(self,**kwargs): data = super().get_context_data(**kwargs) if self.request.POST: - data['prilohy'] = f.ReseniSPrilohamiFormSet(self.request.POST,self.request.FILES) + data['prilohy'] = f.ReseniSPrilohamiFormSet(self.request.POST,self.request.FILES) else: - data['prilohy'] = f.ReseniSPrilohamiFormSet() + data['prilohy'] = f.ReseniSPrilohamiFormSet() return data # FIXME prepsat tak, aby form_valid se volalo jen tehdy, kdyz je form i formset validni @@ -1070,13 +1072,13 @@ class NahrajReseniView(LoginRequiredMixin, CreateView): with transaction.atomic(): self.object = form.save() self.object.resitele.add(Resitel.objects.get(osoba__user = self.request.user)) - self.object.cas_doruceni = timezone.now() + self.object.cas_doruceni = timezone.now() self.object.forma = s.Reseni.FORMA_UPLOAD self.object.save() prilohy.instance = self.object prilohy.save() - + # Pošleme mail opravovatelům a garantovi # FIXME: Nechat spočítat databázi? Je to pár dotazů (pravděpodobně), takže to za to možná nestojí prijemci = set() @@ -1099,7 +1101,7 @@ class NahrajReseniView(LoginRequiredMixin, CreateView): 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)) + gdpr_logger.warn(msg+", form:{}".format(form_data)) from django.forms.models import model_to_dict def resitelEditView(request): @@ -1185,11 +1187,10 @@ def prihlaskaView(request): 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'], - password=fcd['password'], email = fcd['email']) u.save() resitel_perm = Permission.objects.filter(codename__exact='resitel').first() @@ -1229,7 +1230,7 @@ def prihlaskaView(request): zasilat = fcd['zasilat'], zasilat_cislo_emailem = fcd['zasilat_cislo_emailem'] ) - + r.save() r.osoba = o if fcd.get('skola'): @@ -1240,11 +1241,19 @@ def prihlaskaView(request): err_logger.warn(msg + str(form_hash)) r.save() - send_mail( + uid = urlsafe_base64_encode(force_bytes(u.pk)) + token = PasswordResetTokenGenerator().make_token(u) + url = "https://%s%s" % ( + str(get_current_site(request)), + str(reverse_lazy("password_reset_confirm", args=[uid, token])) + ) + + u.email_user( subject="Registrace na M&M", - message=f"Tento e-mail byl právě zaregistrován na mam.matfyz.cz.", # TODO: Dovymyslet text. + message=f"Tento e-mail byl právě zaregistrován na mam.matfyz.cz. Nové heslo si nastavíš na: " + url, + # TODO: templates/seminar/registrace a django/contrib/auth/forms.py říkají, jak na to lépe + # TODO: Dovymyslet text. from_email="registrace@mam.mff.cuni.cz", # FIXME: Chceme to mít radši tady, nebo v nastavení? - recipient_list=list([fcd['email']]), ) return formularOKView(request) @@ -1319,7 +1328,7 @@ class NahrajObrazekKTreeNoduView(LoginRequiredMixin, CreateView): return JsonResponse({"url":self.object.na_web.url}) - + # Jen hloupé rozhazovátko def profilView(request): @@ -1345,10 +1354,10 @@ def formularOKView(request): return render(request, template_name, context) #------------------ Jak řešit - možná má být udělané úplně jinak - + class JakResitView(generic.ListView): template_name = 'seminar/jak-resit.html' - + def get_queryset(self): return None