From 08eb89d790e353f47836970bf821b8d2aa428f94 Mon Sep 17 00:00:00 2001 From: KubaR Date: Wed, 27 Nov 2019 23:45:45 +0100 Subject: [PATCH 01/17] resitelEditView(view.py), EditForm(forms.py), template.edit.html --- seminar/forms.py | 103 ++++++++++++++++++++++++++++ seminar/templates/seminar/edit.html | 85 +++++++++++++++++++++++ seminar/views.py | 28 +++++++- 3 files changed, 213 insertions(+), 3 deletions(-) create mode 100644 seminar/templates/seminar/edit.html diff --git a/seminar/forms.py b/seminar/forms.py index 42d3c2d7..29e6607e 100644 --- a/seminar/forms.py +++ b/seminar/forms.py @@ -121,3 +121,106 @@ class PrihlaskaForm(forms.Form): self.add_error('skola_nazev',forms.ValidationError('Je nutné vyplnit název školy')) elif data.get('skola_adresa')=='': self.add_error('skola_adresa',forms.ValidationError('Je nutné vyplnit adresu školy')) + + + + +class EditForm(forms.Form): + 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=False, + widget=forms.PasswordInput()) + password_check = forms.CharField( + label='Ověření hesla', + max_length=256, + required=False, + 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) + pohlavi_muz = forms.ChoiceField(label='Pohlaví', + choices = ((True,'muž'),(False,'žena')), required=True) + email = forms.EmailField(label='E-mail',max_length=256, required=True) + telefon = forms.CharField(label='Telefon',max_length=256, required=False) + datum_narozeni = forms.DateField(label='Datum narození', required=False) + ulice = forms.CharField(label='Ulice', max_length=256, required=False) + mesto = forms.CharField(label='Město', max_length=256, required=False) + psc = forms.CharField(label='PSČ', max_length=32, required=False) + stat = forms.ChoiceField(label='Stát', + choices = (('CZ', 'Česká Republika'), + ('SK', 'Slovenská Republika'), + ('other', 'Jiné')), + required=False) + stat_text = forms.CharField(label='Stát', max_length=256, required=False) + + skola = forms.ModelChoiceField(label="Škola", + queryset=Skola.objects.all(), + widget=autocomplete.ModelSelect2( + url='autocomplete_skola', + attrs = {'data-placeholder--id': '-1', + 'data-placeholder--text' : '---', + 'data-allow-clear': 'true'}) + ,required=False) + + skola_nazev = forms.CharField(label='Název školy', max_length=256, required=False) + skola_adresa = forms.CharField(label='Adresa školy', max_length=256, required=False) + +# trida = forms.CharField(label='Třída',max_length=10, required=True) + + rok_maturity = forms.IntegerField( + label='Rok maturity', + min_value=date.today().year, + max_value=date.today().year+8, + required=True) + zasilat = forms.ChoiceField(label='Kam zasílat čísla a řešení',choices = Resitel.ZASILAT_CHOICES, required=True) + gdpr = forms.BooleanField(label='Souhlasím se zpracováním osobních údajů', required=True) + spam = forms.BooleanField(label='Souhlasím se zasíláním materiálů od MFF UK', required=False) +# def clean_username(self): +# err_logger = logging.getLogger('seminar.prihlaska.problem') +# username = self.cleaned_data.get('username') +# try: +# User.objects.get(username=username) +# msg = "Username {} exists".format(username) +# err_logger.info(msg) +# raise forms.ValidationError('Přihlašovací jméno je již použito') +# +# except ObjectDoesNotExist: +# pass +# return username +# +# def clean_email(self): +# err_logger = logging.getLogger('seminar.prihlaska.problem') +# email = self.cleaned_data.get('email') +# try: +# Osoba.objects.get(email=email) +# msg = "Email {} exists".format(email) +# err_logger.info(msg) +# raise forms.ValidationError('Email je již použit') +# +# except ObjectDoesNotExist: +# pass +# return email + #def clean(self): + # super().clean() + # + # err_logger = logging.getLogger('seminar.prihlaska.problem') + + # data = self.cleaned_data + # if data.get('password') != data.get('password_check'): + # self.add_error('password_check',forms.ValidationError('Hesla se neshodují')) + # if data.get('stat') != '' and data.get('stat_text') != '': + # self.add_error('stat',forms.ValidationError('Nelze mít vybraný stát z menu a zároven zapsaný textem')) + # if data.get('skola') and (data.get('skola_nazev') or data.get('skola_adresa')): + # self.add_error('skola',forms.ValidationError('Pokud je škola v seznamu, nevypisujte ji ručně, pokud není, zrušte výběr ze seznamu (křížek vpravo)')) + # if not data.get('skola'): + # if data.get('skola_nazev')=='' and data.get('skola_adresa')=='': + # self.add_error('skola',forms.ValidationError('Je nutné vyplnit školu')) + # elif data.get('skola_nazev')=='': + # self.add_error('skola_nazev',forms.ValidationError('Je nutné vyplnit název školy')) + # elif data.get('skola_adresa')=='': + # self.add_error('skola_adresa',forms.ValidationError('Je nutné vyplnit adresu školy')) diff --git a/seminar/templates/seminar/edit.html b/seminar/templates/seminar/edit.html new file mode 100644 index 00000000..cc039301 --- /dev/null +++ b/seminar/templates/seminar/edit.html @@ -0,0 +1,85 @@ +{% extends "seminar/zadani/base.html" %} +{% load staticfiles %} + +{% block script %} + + {{form.media}} + +{% endblock %} +{% block content %} +

+ {% block nadpis1a %}{% block nadpis1b %} + Změna osobních údajů + {% endblock %}{% endblock %} +

+
+ {% csrf_token %} + {{form.non_field_errors}} + + +
+ +{% endblock %} + diff --git a/seminar/views.py b/seminar/views.py index f2906d90..39ee76af 100644 --- a/seminar/views.py +++ b/seminar/views.py @@ -19,7 +19,7 @@ from .models import Problem, Cislo, Reseni, Nastaveni, Rocnik, Soustredeni, Orga #from .models import VysledkyZaCislo, VysledkyKCisluZaRocnik, VysledkyKCisluOdjakziva from . import utils from .unicodecsv import UnicodeWriter -from .forms import PrihlaskaForm, LoginForm +from .forms import PrihlaskaForm, LoginForm, EditForm from datetime import timedelta, date, datetime from django.utils import timezone @@ -989,8 +989,6 @@ class ResitelView(LoginRequiredMixin,generic.DetailView): return Resitel.objects.get(osoba__user=self.request.user) ## Formulare -def resitelEditView(request): - pass def resetPasswordView(request): pass @@ -1027,6 +1025,30 @@ def prihlaska_log_gdpr_safe(logger, gdpr_logger, msg, form_data): logger.warn(msg) gdpr_logger.warn(msg+", form:{}".format(form_data)) +from django.forms.models import model_to_dict +def resitelEditView(request): + ## Načtení objektu Osoba a Resitel, patrici k aktuálně přihlášenému uživately + u = request.user + osoba_edit = Osoba.objects.get(user=u) + resitel_edit = osoba_edit.resitel + user_edit = osoba_edit.user + ## Vytvoření slovníku, kterým předvyplním formulář + prefill_1=model_to_dict(osoba_edit) + prefill_2=model_to_dict(resitel_edit) + prefill_3=model_to_dict(user_edit) + prefill_1.update(prefill_2) + prefill_1.update(prefill_3) + form = EditForm(initial=prefill_1) + ## Změna údajů a jejich uložení + if request.method == 'POST': + form = EditForm(request.POST) + if form.is_valid(): + osoba_edit.prijmeni = 'NOVOTA' + osoba_edit.save() + return HttpResponseRedirect('/thanks/') + else: + ## Stránka před odeslaním formuláře = předvyplněný formulář + return render(request, 'seminar/edit.html', {'form': form}) def prihlaskaView(request): generic_logger = logging.getLogger('seminar.prihlaska') From 5f66fd0b03a7bf84ff488706dc5aca8fcd62ef5c Mon Sep 17 00:00:00 2001 From: KubaR Date: Wed, 4 Dec 2019 22:33:20 +0100 Subject: [PATCH 02/17] EditView, EditFrom, Edit.html uprava --- seminar/forms.py | 16 +------------ seminar/templates/seminar/edit.html | 7 ------ seminar/views.py | 35 ++++++++++++++++++++++++++--- 3 files changed, 33 insertions(+), 25 deletions(-) diff --git a/seminar/forms.py b/seminar/forms.py index 29e6607e..b28beeb9 100644 --- a/seminar/forms.py +++ b/seminar/forms.py @@ -123,23 +123,10 @@ class PrihlaskaForm(forms.Form): self.add_error('skola_adresa',forms.ValidationError('Je nutné vyplnit adresu školy')) - - class EditForm(forms.Form): 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=False, - widget=forms.PasswordInput()) - password_check = forms.CharField( - label='Ověření hesla', - max_length=256, - required=False, - widget=forms.PasswordInput()) + required=True) jmeno = forms.CharField(label='Jméno', max_length=256, required=True) prijmeni = forms.CharField(label='Příjmení', max_length=256, required=True) @@ -178,7 +165,6 @@ class EditForm(forms.Form): max_value=date.today().year+8, required=True) zasilat = forms.ChoiceField(label='Kam zasílat čísla a řešení',choices = Resitel.ZASILAT_CHOICES, required=True) - gdpr = forms.BooleanField(label='Souhlasím se zpracováním osobních údajů', required=True) spam = forms.BooleanField(label='Souhlasím se zasíláním materiálů od MFF UK', required=False) # def clean_username(self): # err_logger = logging.getLogger('seminar.prihlaska.problem') diff --git a/seminar/templates/seminar/edit.html b/seminar/templates/seminar/edit.html index cc039301..3f3e0d99 100644 --- a/seminar/templates/seminar/edit.html +++ b/seminar/templates/seminar/edit.html @@ -20,10 +20,6 @@ Přihlašovací údaje
  • {% include "seminar/prihlaska_field.html" with field=form.username %} -
  • - {% include "seminar/prihlaska_field.html" with field=form.password %} -
  • - {% include "seminar/prihlaska_field.html" with field=form.password_check %}
  • Osobní údaje
  • @@ -68,9 +64,6 @@ {% include "seminar/prihlaska_field.html" with field=form.rok_maturity %}
  • {% include "seminar/prihlaska_field.html" with field=form.zasilat %} -
  • - {% include "seminar/gdpr.html" %} - {% include "seminar/prihlaska_field.html" with field=form.gdpr %}
  • {% include "seminar/prihlaska_field.html" with field=form.spam %}
  • diff --git a/seminar/views.py b/seminar/views.py index 39ee76af..27728f0c 100644 --- a/seminar/views.py +++ b/seminar/views.py @@ -1027,15 +1027,16 @@ def prihlaska_log_gdpr_safe(logger, gdpr_logger, msg, form_data): from django.forms.models import model_to_dict def resitelEditView(request): + err_logger = logging.getLogger('seminar.prihlaska.problem') ## Načtení objektu Osoba a Resitel, patrici k aktuálně přihlášenému uživately u = request.user osoba_edit = Osoba.objects.get(user=u) resitel_edit = osoba_edit.resitel user_edit = osoba_edit.user ## Vytvoření slovníku, kterým předvyplním formulář - prefill_1=model_to_dict(osoba_edit) + prefill_1=model_to_dict(user_edit) prefill_2=model_to_dict(resitel_edit) - prefill_3=model_to_dict(user_edit) + prefill_3=model_to_dict(osoba_edit) prefill_1.update(prefill_2) prefill_1.update(prefill_3) form = EditForm(initial=prefill_1) @@ -1043,7 +1044,35 @@ def resitelEditView(request): if request.method == 'POST': form = EditForm(request.POST) if form.is_valid(): - osoba_edit.prijmeni = 'NOVOTA' + ## Změny v osobě + fcd = form.cleaned_data + 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'] + ## Změny v osobě s podmínkami + if fcd.get('spam',False): + osoba_edit.datum_souhlasu_zasilani = date.today() + if fcd.get('stat','') in ('CZ','SK'): + osoba_edit.stat = fcd['stat'] + else: + ## Neznámá země + msg = "Unknown country {}".format(fcd['stat_text']) + + ## Změny v řešiteli + resitel_edit.skola = fcd['skola'] + resitel_edit.rok_maturity = fcd['rok_maturity'] + resitel_edit.zasilat = fcd['zasilat'] + 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 HttpResponseRedirect('/thanks/') else: From 19fa7361504839d2c9bde0aff7b63a6fb74752c4 Mon Sep 17 00:00:00 2001 From: Pavel 'LEdoian' Turinsky Date: Wed, 4 Dec 2019 23:03:12 +0100 Subject: [PATCH 03/17] =?UTF-8?q?TeXov=C3=BD=20upload=20p=C5=99ejmenov?= =?UTF-8?q?=C3=A1n,=20aby=20se=20za=20chv=C3=ADli=20nekryl=20n=C3=A1zev=20?= =?UTF-8?q?s=20vestav=C4=9Bn=C3=BDm=20LoginView?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- seminar/urls.py | 2 +- seminar/views.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/seminar/urls.py b/seminar/urls.py index c37d1357..9d2c158a 100644 --- a/seminar/urls.py +++ b/seminar/urls.py @@ -92,7 +92,7 @@ urlpatterns = [ path('soustredeni//obalky.pdf', staff_member_required(views.soustredeniObalkyView), name='seminar_soustredeni_obalky'), - path('tex-upload/login/', views.LoginView, name='seminar_login'), + path('tex-upload/login/', views.TeXUploadLoginView, name='seminar_login'), path( 'tex-upload/', staff_member_required(views.texUploadView), diff --git a/seminar/views.py b/seminar/views.py index 39ee76af..bb580c08 100644 --- a/seminar/views.py +++ b/seminar/views.py @@ -782,7 +782,7 @@ def StavDatabazeView(request): @ensure_csrf_cookie -def LoginView(request): +def TeXUploadLoginView(request): """Pro přihlášení při nahrávání z texu""" q = request.POST # nastavení cookie csrftoken From 1ff1d050280f389ffbcaf8d26bcc63e6bbc85e3f Mon Sep 17 00:00:00 2001 From: "Martin Z. (Zimamazim)" Date: Thu, 5 Dec 2019 00:50:04 +0100 Subject: [PATCH 04/17] Rozcestnik tematek view --- seminar/migrations/0072_auto_20191204_2257.py | 23 +++++ seminar/models.py | 49 +++++++++-- .../templates/seminar/tematka/rozcestnik.html | 14 ++++ seminar/testutils.py | 3 +- seminar/urls.py | 3 + seminar/utils.py | 9 ++ seminar/views.py | 84 ++++++++++++++++++- 7 files changed, 177 insertions(+), 8 deletions(-) create mode 100644 seminar/migrations/0072_auto_20191204_2257.py create mode 100644 seminar/templates/seminar/tematka/rozcestnik.html diff --git a/seminar/migrations/0072_auto_20191204_2257.py b/seminar/migrations/0072_auto_20191204_2257.py new file mode 100644 index 00000000..f96b670a --- /dev/null +++ b/seminar/migrations/0072_auto_20191204_2257.py @@ -0,0 +1,23 @@ +# Generated by Django 2.2.7 on 2019-12-04 21:57 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('seminar', '0071_remove_nastaveni_aktualni_rocnik'), + ] + + operations = [ + migrations.AddField( + model_name='treenode', + name='srolovatelne', + field=models.BooleanField(blank=True, help_text='Bude na stránce témátka možnost tuto položku skrýt', null=True, verbose_name='Srolovatelné'), + ), + migrations.AddField( + model_name='treenode', + name='zajimave', + field=models.BooleanField(default=False, help_text='Zobrazí se daná věc na rozcestníku témátek', verbose_name='Zajímavé'), + ), + ] diff --git a/seminar/models.py b/seminar/models.py index 691c04af..414f96aa 100644 --- a/seminar/models.py +++ b/seminar/models.py @@ -21,7 +21,7 @@ from taggit.managers import TaggableManager from reversion import revisions as reversion -from seminar.utils import roman +from seminar.utils import roman, FirstTagParser # Pro získání úryvku z TextNode from unidecode import unidecode @@ -785,8 +785,11 @@ class Text(SeminarModelBase): for tn in self.textnode_set.all(): tn.save() - - + def __str__(self): + parser = FirstTagParser() + parser.feed(str(self.na_web)) + return parser.firstTag + class Uloha(Problem): class Meta: db_table = 'seminar_ulohy' @@ -1238,8 +1241,15 @@ class TreeNode(PolymorphicModel): on_delete=models.SET_NULL, verbose_name="další element na stejné úrovni") nazev = models.TextField("název tohoto node", - help_text = "Tento název se zobrazuje v nabídkách pro výběr vhodného TreeNode", - blank=False, null=True) + help_text = "Tento název se zobrazuje v nabídkách pro výběr vhodného TreeNode", + blank=False, + null=True) + zajimave = models.BooleanField(default = False, + verbose_name = "Zajímavé", + help_text = "Zobrazí se daná věc na rozcestníku témátek") + srolovatelne = models.BooleanField(null = True, blank = True, + verbose_name = "Srolovatelné", + help_text = "Bude na stránce témátka možnost tuto položku skrýt") def print_tree(self,indent=0): print("{}TreeNode({})".format(" "*indent,self.id)) @@ -1247,7 +1257,10 @@ class TreeNode(PolymorphicModel): self.first_child.print_tree(indent=indent+2) if self.succ: self.succ.print_tree(indent=indent) - + + def getOdkaz(self): + return self.first_child.getOdkaz() + def __str__(self): if self.nazev: return self.nazev @@ -1283,6 +1296,9 @@ class CisloNode(TreeNode): def aktualizuj_nazev(self): self.nazev = "CisloNode: "+str(self.cislo) + def getOdkaz(self): + return "Číslo " + str(self.cislo) + class MezicisloNode(TreeNode): class Meta: db_table = 'seminar_nodes_mezicislo' @@ -1304,6 +1320,8 @@ class MezicisloNode(TreeNode): else: print("!!!!! Nějaké neidentifikované mezičíslo !!!!!") self.nazev = "MezicisloNode: Neidentifikovatelné mezičíslo!" + def getOdkaz(self): + return "Obsah dostupný pouze na webu" class TemaVCisleNode(TreeNode): """ Obsahuje příspěvky k tématu v daném čísle """ @@ -1318,6 +1336,9 @@ class TemaVCisleNode(TreeNode): def aktualizuj_nazev(self): self.nazev = "TemaVCisleNode: "+str(self.tema) + def getOdkaz(self): + return str(self.tema) + class KonferaNode(TreeNode): class Meta: db_table = 'seminar_nodes_konfera' @@ -1346,6 +1367,10 @@ class ClanekNode(TreeNode): def aktualizuj_nazev(self): self.nazev = "ClanekNode: "+str(self.clanek) + def getOdkaz(self): + return str(self.clanek) + + class UlohaZadaniNode(TreeNode): class Meta: db_table = 'seminar_nodes_uloha_zadani' @@ -1360,6 +1385,10 @@ class UlohaZadaniNode(TreeNode): def aktualizuj_nazev(self): self.nazev = "UlohaZadaniNode: "+str(self.uloha) + def getOdkaz(self): + return str(self.uloha) + + class PohadkaNode(TreeNode): class Meta: db_table = 'seminar_nodes_pohadka' @@ -1387,6 +1416,10 @@ class UlohaVzorakNode(TreeNode): def aktualizuj_nazev(self): self.nazev = "UlohaVzorakNode: "+str(self.uloha) + def getOdkaz(self): + return str(self.uloha) + + class TextNode(TreeNode): class Meta: db_table = 'seminar_nodes_obsah' @@ -1399,6 +1432,10 @@ class TextNode(TreeNode): def aktualizuj_nazev(self): self.nazev = "TextNode: "+str(self.text) + def getOdkaz(self): + return str(self.text) + + ## FIXME: Logiku přesunout do views. #class VysledkyBase(SeminarModelBase): # diff --git a/seminar/templates/seminar/tematka/rozcestnik.html b/seminar/templates/seminar/tematka/rozcestnik.html new file mode 100644 index 00000000..2afb0e38 --- /dev/null +++ b/seminar/templates/seminar/tematka/rozcestnik.html @@ -0,0 +1,14 @@ +{% for tematko in tematka %} +

    TEMA

    +

    {{tematko.abstrakt}}

    +
      + {% for cislo in tematko.cisla %} +
    • {{cislo.0}}
    • +
        + {% for odkaz in cislo.1 %} +
      • {{odkaz}}
      • + {% endfor %} +
      + {% endfor %} +
    +{% endfor %} diff --git a/seminar/testutils.py b/seminar/testutils.py index 204c0ea6..f378e725 100644 --- a/seminar/testutils.py +++ b/seminar/testutils.py @@ -380,7 +380,8 @@ def gen_temata(rnd, rocniky, rocnik_cisla, organizatori): kod=str(n), # atributy třídy Téma tema_typ=rnd.choice(Tema.TEMA_CHOICES)[0], - rocnik=rocnik + rocnik=rocnik, + abstrakt = "Abstrakt tematka {}".format(n) ) konec_tematu = min(rnd.randint(ci, 7), len(cisla)) for i in range(ci, konec_tematu+1): diff --git a/seminar/urls.py b/seminar/urls.py index c37d1357..67b1f865 100644 --- a/seminar/urls.py +++ b/seminar/urls.py @@ -8,6 +8,9 @@ from django.contrib.auth import views as auth_views staff_member_required = user_passes_test(lambda u: u.is_staff) urlpatterns = [ + # TEMP DEV + path('dev_tematka/', views.TemataRozcestnikView), + # REDIRECTy path('jak-resit/', RedirectView.as_view(url='/co-je-MaM/jak-resit/')), diff --git a/seminar/utils.py b/seminar/utils.py index 75092384..d910a5b6 100644 --- a/seminar/utils.py +++ b/seminar/utils.py @@ -2,9 +2,18 @@ import datetime from django.contrib.auth.decorators import user_passes_test +from html.parser import HTMLParser staff_member_required = user_passes_test(lambda u: u.is_staff) +class FirstTagParser(HTMLParser): + def __init__(self, *args, **kwargs): + self.firstTag = None + super().__init__(*args, **kwargs) + def handle_data(self, data): + if self.firstTag == None: + self.firstTag = data + def histogram(seznam): d = {} for i in seznam: diff --git a/seminar/views.py b/seminar/views.py index f2906d90..0e5f9cd7 100644 --- a/seminar/views.py +++ b/seminar/views.py @@ -15,7 +15,8 @@ from django.contrib.auth.mixins import LoginRequiredMixin from django.db import transaction from dal import autocomplete -from .models import Problem, Cislo, Reseni, Nastaveni, Rocnik, Soustredeni, Organizator, Resitel, Novinky, Soustredeni_Ucastnici, Pohadka, Tema, Clanek, Osoba, Skola +import seminar.models as s +from .models import Problem, Cislo, Reseni, Nastaveni, Rocnik, Soustredeni, Organizator, Resitel, Novinky, Soustredeni_Ucastnici, Pohadka, Tema, Clanek, Osoba, Skola # Tohle je stare a chceme se toho zbavit. Pouzivejte s.ToCoChci #from .models import VysledkyZaCislo, VysledkyKCisluZaRocnik, VysledkyKCisluOdjakziva from . import utils from .unicodecsv import UnicodeWriter @@ -73,6 +74,87 @@ def ZadaniTemataView(request): } ) +#TODO na příště - implementovat DFS, které vrátí seznam objektů, jejich hloubku a objekt, který chci zobrazit, +#TODO na příště - rozmyslet, jak zobrazovat objekty - u každého Nodu se objekt, na který ukazuje jmenuje jinak, zavést metodu, která se u každé subclassy bude jmenovat stejně? __str__ +#TODO na příště - v jaké formě předávat templatu? Jak řešit rozbalovací tagy? +#TODO na příště - implementace vpisování rozbalovacích tagů, vytvořit si nový objekt, který bude mít stejnou metodu jako objekty, které mají node, která bude vracet vhodný tag a prostě ji přidat do seznamu? +def vytahniZLesaSeznam(tematko, koren, pouze_zajimave=False): + returnVal = [] + + stack = [] + stack.append((koren.first_child, 0, False)) #Tuple of node, depth and relevance + + while len(stack) > 0: + wn, wd, wr = stack.pop() + + if wn.succ != None: + stack.append((wn.succ, wd, wr)) + if isinstance(wn, s.TemaVCisleNode): + print("TEMA") + print(wn.tema.id) + print(tematko.id) + if wn.tema.id == tematko.id: + returnVal.append((posledni_cislo, 0)) + print("PRIDANO") + wr = True + wd = 1 + + if wn.srolovatelne: + tagOpen = s.Text(na_web = "Otevírací srolovací tag") + tagOpenNode = s.TextNode(text = tagOpen) + tagClose = s.Text(na_web = "Zavírací srolovací tag") + tagCloseNode = s.TextNode(text = tagClose) + stack.append((tagCloseNode, wd, True)) + + if wn.first_child != None: + stack.append((wn.first_child, wd + 1, wr)) + + if isinstance(wn, s.CisloNode): + posledni_cislo = wn + print(wn) + + if wr: + print("ZAJIMAVE") + if pouze_zajimave: + if not wn.zajimave: + continue + returnVal.append((wn, wd)) + return returnVal + + +def TemataRozcestnikView(request): + print("=============================================") + nastaveni = s.Nastaveni.objects.first() + tematka_objects = s.Tema.objects.filter(rocnik=nastaveni.aktualni_rocnik()) + tematka = [] #List tematka obsahuje pro kazde tematko object a list vsech TemaVCisleNodu - implementované pomocí slovníku + for tematko_object in tematka_objects: + print("AKTUALNI TEMATKO") + print(tematko_object.id) + odkazy = vytahniZLesaSeznam(tematko_object, nastaveni.aktualni_rocnik().rocniknode, pouze_zajimave = True) + print(odkazy) + cisla = [] + vcisle = [] + cislo = None + for odkaz in odkazy: + if odkaz[1] == 0: + if cislo != None: + cisla.append((cislo, vcisle)) + cislo = odkaz[0].getOdkaz() + vcisle = [] + else: + print(odkaz[0].getOdkaz()) + vcisle.append(odkaz[0].getOdkaz()) + if cislo != None: + cisla.append((cislo, vcisle)) + + print(cisla) + tematka.append({ + "abstrakt" : tematko_object.abstrakt, + "obrazek": tematko_object.obrazek, + "cisla" : cisla + }) + return render(request, 'seminar/tematka/rozcestnik.html', {"tematka": tematka}) + #def ZadaniAktualniVysledkovkaView(request): # nastaveni = get_object_or_404(Nastaveni) From 9a0d229f9ac7777d49e7ac0bde3b5f71ad571358 Mon Sep 17 00:00:00 2001 From: "Martin Z. (Zimamazim)" Date: Wed, 11 Dec 2019 23:35:27 +0100 Subject: [PATCH 05/17] Rozcestnik tematek - odkazy --- seminar/models.py | 43 +++++++++++++------ .../templates/seminar/tematka/rozcestnik.html | 6 +-- seminar/urls.py | 4 +- seminar/views.py | 12 ++++-- 4 files changed, 43 insertions(+), 22 deletions(-) diff --git a/seminar/models.py b/seminar/models.py index 414f96aa..60bc361e 100644 --- a/seminar/models.py +++ b/seminar/models.py @@ -23,7 +23,7 @@ from reversion import revisions as reversion from seminar.utils import roman, FirstTagParser # Pro získání úryvku z TextNode -from unidecode import unidecode +from unidecode import unidecode # Používám pro získání ID odkazu (ještě je to někde po někom zakomentované) from polymorphic.models import PolymorphicModel @@ -620,7 +620,7 @@ class Problem(SeminarModelBase,PolymorphicModel): id = models.AutoField(primary_key = True) # Název - nazev = models.CharField('název', max_length=256) + nazev = models.CharField('název', max_length=256) # Zveřejnitelný na stránky # Problém má podproblémy nadproblem = models.ForeignKey('self', verbose_name='nadřazený problém', @@ -1243,7 +1243,7 @@ class TreeNode(PolymorphicModel): nazev = models.TextField("název tohoto node", help_text = "Tento název se zobrazuje v nabídkách pro výběr vhodného TreeNode", blank=False, - null=True) + null=True) # Nezveřejnitelný název na stránky - pouze do adminu zajimave = models.BooleanField(default = False, verbose_name = "Zajímavé", help_text = "Zobrazí se daná věc na rozcestníku témátek") @@ -1258,9 +1258,26 @@ class TreeNode(PolymorphicModel): if self.succ: self.succ.print_tree(indent=indent) - def getOdkaz(self): - return self.first_child.getOdkaz() - + def getOdkazStr(self): # String na rozcestník + return self.first_child.getOdkazStr() + + def getOdkaz(self): # ID HTML tagu, na který se bude scrollovat #{{self.getOdkaz}} + # Jsem si vědom, že tu potenciálně vznikají kolize. + # Přijdou mi natolik nepravděpodobné, že je neřeším + # Chtěl jsem ale hezké odkazy + string = unidecode(self.getOdkazStr()) + returnVal = "" + i = 0 + while len(returnVal) < 16: # Max 15 znaků + if i == len(string): + break + if string[i] == " ": + returnVal += "-" + if string[i].isalnum(): + returnVal += string[i].lower() + i += 1 + return returnVal + def __str__(self): if self.nazev: return self.nazev @@ -1296,7 +1313,7 @@ class CisloNode(TreeNode): def aktualizuj_nazev(self): self.nazev = "CisloNode: "+str(self.cislo) - def getOdkaz(self): + def getOdkazStr(self): return "Číslo " + str(self.cislo) class MezicisloNode(TreeNode): @@ -1320,7 +1337,7 @@ class MezicisloNode(TreeNode): else: print("!!!!! Nějaké neidentifikované mezičíslo !!!!!") self.nazev = "MezicisloNode: Neidentifikovatelné mezičíslo!" - def getOdkaz(self): + def getOdkazStr(self): return "Obsah dostupný pouze na webu" class TemaVCisleNode(TreeNode): @@ -1336,7 +1353,7 @@ class TemaVCisleNode(TreeNode): def aktualizuj_nazev(self): self.nazev = "TemaVCisleNode: "+str(self.tema) - def getOdkaz(self): + def getOdkazStr(self): return str(self.tema) class KonferaNode(TreeNode): @@ -1367,7 +1384,7 @@ class ClanekNode(TreeNode): def aktualizuj_nazev(self): self.nazev = "ClanekNode: "+str(self.clanek) - def getOdkaz(self): + def getOdkazStr(self): return str(self.clanek) @@ -1385,7 +1402,7 @@ class UlohaZadaniNode(TreeNode): def aktualizuj_nazev(self): self.nazev = "UlohaZadaniNode: "+str(self.uloha) - def getOdkaz(self): + def getOdkazStr(self): return str(self.uloha) @@ -1416,7 +1433,7 @@ class UlohaVzorakNode(TreeNode): def aktualizuj_nazev(self): self.nazev = "UlohaVzorakNode: "+str(self.uloha) - def getOdkaz(self): + def getOdkazStr(self): return str(self.uloha) @@ -1432,7 +1449,7 @@ class TextNode(TreeNode): def aktualizuj_nazev(self): self.nazev = "TextNode: "+str(self.text) - def getOdkaz(self): + def getOdkazStr(self): return str(self.text) diff --git a/seminar/templates/seminar/tematka/rozcestnik.html b/seminar/templates/seminar/tematka/rozcestnik.html index 2afb0e38..abec82dd 100644 --- a/seminar/templates/seminar/tematka/rozcestnik.html +++ b/seminar/templates/seminar/tematka/rozcestnik.html @@ -1,12 +1,12 @@ {% for tematko in tematka %} -

    TEMA

    +

    {{tematko.nazev}}

    {{tematko.abstrakt}}

      {% for cislo in tematko.cisla %} -
    • {{cislo.0}}
    • +
    • {{cislo.0.0}} -> /{{tematko.kod}}/#{{cislo.0.1}}
      • {% for odkaz in cislo.1 %} -
      • {{odkaz}}
      • +
      • {{odkaz.0}} -> /{{tematko.kod}}/#{{odkaz.1}}
      • {% endfor %}
      {% endfor %} diff --git a/seminar/urls.py b/seminar/urls.py index 67b1f865..347b3821 100644 --- a/seminar/urls.py +++ b/seminar/urls.py @@ -8,8 +8,8 @@ from django.contrib.auth import views as auth_views staff_member_required = user_passes_test(lambda u: u.is_staff) urlpatterns = [ - # TEMP DEV - path('dev_tematka/', views.TemataRozcestnikView), + path('tematka/', views.TemataRozcestnikView), + path('tematko//', views.TematkoView), # REDIRECTy path('jak-resit/', RedirectView.as_view(url='/co-je-MaM/jak-resit/')), diff --git a/seminar/views.py b/seminar/views.py index 3b8408b2..9f743d5d 100644 --- a/seminar/views.py +++ b/seminar/views.py @@ -121,6 +121,8 @@ def vytahniZLesaSeznam(tematko, koren, pouze_zajimave=False): returnVal.append((wn, wd)) return returnVal +def TematkoView(request): + neco def TemataRozcestnikView(request): print("=============================================") @@ -130,25 +132,27 @@ def TemataRozcestnikView(request): for tematko_object in tematka_objects: print("AKTUALNI TEMATKO") print(tematko_object.id) - odkazy = vytahniZLesaSeznam(tematko_object, nastaveni.aktualni_rocnik().rocniknode, pouze_zajimave = True) + odkazy = vytahniZLesaSeznam(tematko_object, nastaveni.aktualni_rocnik().rocniknode, pouze_zajimave = True) #Odkazy jsou tuply (node, depth) v listu print(odkazy) - cisla = [] + cisla = [] # List tuplů (nazev cisla, list odkazů) vcisle = [] cislo = None for odkaz in odkazy: if odkaz[1] == 0: if cislo != None: cisla.append((cislo, vcisle)) - cislo = odkaz[0].getOdkaz() + cislo = (odkaz[0].getOdkazStr(), odkaz[0].getOdkaz()) vcisle = [] else: print(odkaz[0].getOdkaz()) - vcisle.append(odkaz[0].getOdkaz()) + vcisle.append((odkaz[0].getOdkazStr(), odkaz[0].getOdkaz())) if cislo != None: cisla.append((cislo, vcisle)) print(cisla) tematka.append({ + "kod" : tematko_object.kod, + "nazev" : tematko_object.nazev, "abstrakt" : tematko_object.abstrakt, "obrazek": tematko_object.obrazek, "cisla" : cisla From 5dda5f662838f212d4638b56b805240a0ee573a2 Mon Sep 17 00:00:00 2001 From: Pavel 'LEdoian' Turinsky Date: Fri, 13 Dec 2019 16:38:56 +0100 Subject: [PATCH 06/17] =?UTF-8?q?Built-in=20login=20formul=C3=A1=C5=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- seminar/templates/seminar/login.html | 11 +---------- seminar/urls.py | 2 +- seminar/views.py | 12 ++++++++++++ 3 files changed, 14 insertions(+), 11 deletions(-) diff --git a/seminar/templates/seminar/login.html b/seminar/templates/seminar/login.html index 88cd364f..eee1303c 100644 --- a/seminar/templates/seminar/login.html +++ b/seminar/templates/seminar/login.html @@ -8,19 +8,10 @@ Přihlášení {% endblock %}{% endblock %} -{% if login_error %} -{{login_error}} -{% endif %}
      {% csrf_token %} - {{form.non_field_errors}}
        -
      • - {% include "seminar/prihlaska_field.html" with field=form.username %} -
      • -
      • - {% include "seminar/prihlaska_field.html" with field=form.password %} -
      • + {{ form.as_ul }}
      diff --git a/seminar/urls.py b/seminar/urls.py index 9d2c158a..5f534020 100644 --- a/seminar/urls.py +++ b/seminar/urls.py @@ -99,7 +99,7 @@ urlpatterns = [ name='seminar_tex_upload' ), path('auth/prihlaska/',views.prihlaskaView, name='seminar_prihlaska'), - path('auth/login/', views.loginView, name='login'), + path('auth/login/', views.LoginView.as_view(), name='login'), path('auth/logout/', views.logoutView, name='logout'), path('auth/resitel/', views.ResitelView.as_view(), name='seminar_resitel'), path('autocomplete/skola/',views.SkolaAutocomplete.as_view(), name='autocomplete_skola'), diff --git a/seminar/views.py b/seminar/views.py index bb580c08..46571927 100644 --- a/seminar/views.py +++ b/seminar/views.py @@ -10,6 +10,7 @@ from django.http import Http404,HttpResponseBadRequest,HttpResponseRedirect from django.db.models import Q from django.views.decorators.csrf import ensure_csrf_cookie from django.contrib.auth import authenticate, login, get_user_model, logout +from django.contrib.auth import views as auth_views from django.contrib.auth.models import User from django.contrib.auth.mixins import LoginRequiredMixin from django.db import transaction @@ -1154,3 +1155,14 @@ class SkolaAutocomplete(autocomplete.Select2QuerySetView): # Q(user__last_name__isstartswith=query)) # # return qs + +# FIXME: Tohle asi vlastně vůbec nepatří do aplikace 'seminar' +class LoginView(auth_views.LoginView): + # Jen vezmeme vestavěný a dáme mu vhodný template a přesměrovací URL + template_name = 'seminar/login.html' + + # Přesměrovací URL má být v kontextu: + def get_context_data(self, **kwargs): + ctx = super().get_context_data(**kwargs) + ctx['next'] = reverse('titulni_strana') + return ctx From ca3daf769f0bf0960cd3cdb3e9d94cb166f32516 Mon Sep 17 00:00:00 2001 From: "Martin Z. (Zimamazim)" Date: Wed, 18 Dec 2019 22:50:17 +0100 Subject: [PATCH 07/17] =?UTF-8?q?Drobn=C3=A9=20=C3=BApravy?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Dnes jsem se bavil s Jethrem, jak udělat toaleťák. Bude potřeba přepsat vytahniZLesaTematka, aby uměla otevírací a zavírací tagy pro každý podstrom. Poté uvidím, co použiju pro rozcestník. --- .../templates/seminar/tematka/rozcestnik.html | 4 ++-- .../templates/seminar/tematka/toaletak.html | 1 + seminar/urls.py | 4 ++-- seminar/views.py | 22 +++++++++++++------ 4 files changed, 20 insertions(+), 11 deletions(-) create mode 100644 seminar/templates/seminar/tematka/toaletak.html diff --git a/seminar/templates/seminar/tematka/rozcestnik.html b/seminar/templates/seminar/tematka/rozcestnik.html index abec82dd..b13d6075 100644 --- a/seminar/templates/seminar/tematka/rozcestnik.html +++ b/seminar/templates/seminar/tematka/rozcestnik.html @@ -3,10 +3,10 @@

      {{tematko.abstrakt}}

        {% for cislo in tematko.cisla %} -
      • {{cislo.0.0}} -> /{{tematko.kod}}/#{{cislo.0.1}}
      • +
      • {{cislo.0.0}}
        • {% for odkaz in cislo.1 %} -
        • {{odkaz.0}} -> /{{tematko.kod}}/#{{odkaz.1}}
        • +
        • {{odkaz.0}}
        • {% endfor %}
        {% endfor %} diff --git a/seminar/templates/seminar/tematka/toaletak.html b/seminar/templates/seminar/tematka/toaletak.html new file mode 100644 index 00000000..8b556c6c --- /dev/null +++ b/seminar/templates/seminar/tematka/toaletak.html @@ -0,0 +1 @@ +Stránká témátka diff --git a/seminar/urls.py b/seminar/urls.py index 213494f6..c09d32b0 100644 --- a/seminar/urls.py +++ b/seminar/urls.py @@ -8,8 +8,8 @@ from django.contrib.auth import views as auth_views staff_member_required = user_passes_test(lambda u: u.is_staff) urlpatterns = [ - path('tematka/', views.TemataRozcestnikView), - path('tematko//', views.TematkoView), + path('aktualni/temata/', views.TemataRozcestnikView), + path('/t/', views.TematkoView), # REDIRECTy path('jak-resit/', RedirectView.as_view(url='/co-je-MaM/jak-resit/')), diff --git a/seminar/views.py b/seminar/views.py index ccbb2d00..5838f48d 100644 --- a/seminar/views.py +++ b/seminar/views.py @@ -75,10 +75,7 @@ def ZadaniTemataView(request): } ) -#TODO na příště - implementovat DFS, které vrátí seznam objektů, jejich hloubku a objekt, který chci zobrazit, -#TODO na příště - rozmyslet, jak zobrazovat objekty - u každého Nodu se objekt, na který ukazuje jmenuje jinak, zavést metodu, která se u každé subclassy bude jmenovat stejně? __str__ -#TODO na příště - v jaké formě předávat templatu? Jak řešit rozbalovací tagy? -#TODO na příště - implementace vpisování rozbalovacích tagů, vytvořit si nový objekt, který bude mít stejnou metodu jako objekty, které mají node, která bude vracet vhodný tag a prostě ji přidat do seznamu? +# TODO Napsat tuto funkci znovu rekurzivně podle Jethrorad. Potom se podívat, jak lehce se dá modifikovat pro Rozcestník. Pokud lehce, rozšířit ji. Pokud složitě - použít tuhle def vytahniZLesaSeznam(tematko, koren, pouze_zajimave=False): returnVal = [] @@ -122,8 +119,19 @@ def vytahniZLesaSeznam(tematko, koren, pouze_zajimave=False): returnVal.append((wn, wd)) return returnVal -def TematkoView(request): - neco +def TematkoView(request, rocnik, tematko): + nastaveni = s.Nastaveni.objects.first() + rocnik_object = s.Rocnik.objects.filter(rocnik=rocnik) + tematko_object = s.Tema.objects.filter(rocnik=rocnik_object[0], kod=tematko) + seznam = vytahniZLesaSeznam(tematko_object[0], nastaveni.aktualni_rocnik().rocniknode) + for node, depth in seznam: + if node.isinstance(node, s.KonferaNode): + raise Exception("Not implemented yet") + if node.isinstance(node, s.PohadkaNode): # Mohu ignorovat, má pod sebou + pass + + return render(request, 'seminar/tematka/toaletak.html', {}) + def TemataRozcestnikView(request): print("=============================================") @@ -158,7 +166,7 @@ def TemataRozcestnikView(request): "obrazek": tematko_object.obrazek, "cisla" : cisla }) - return render(request, 'seminar/tematka/rozcestnik.html', {"tematka": tematka}) + return render(request, 'seminar/tematka/rozcestnik.html', {"tematka": tematka, "rocnik" : nastaveni.aktualni_rocnik().rocnik}) #def ZadaniAktualniVysledkovkaView(request): From 1f789f6ad0ef4646814505bc23666397009773c0 Mon Sep 17 00:00:00 2001 From: Pavel 'LEdoian' Turinsky Date: Thu, 19 Dec 2019 01:29:27 +0100 Subject: [PATCH 08/17] =?UTF-8?q?Models:=20ne=C3=BAsp=C4=9B=C5=A1n=C3=BD?= =?UTF-8?q?=20(zakomentovan=C3=BD)=20pokus=20o=20vlastn=C3=ADho=20Usera?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- seminar/models.py | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/seminar/models.py b/seminar/models.py index 60bc361e..bf68d015 100644 --- a/seminar/models.py +++ b/seminar/models.py @@ -1587,3 +1587,35 @@ class Novinky(models.Model): return '[' + str(self.datum) + '] ' + self.text[0:50] else: return '[' + str(self.datum) + '] ' + + + + +# FIXME: Tohle nepatří do aplikace 'seminar' +# Nefunkční alternativa vestavěného Usera, který má jméno a mail v přidružené Osobě +# from django.contrib.auth.models import User as Django_User +# +# class Uzivatel(Django_User): +# class Meta: +# proxy = True +# +# @property +# def first_name(self): +# osoby = Osoba.objects.filter(user=self) +# if len(osoby) == 0: +# return None +# return osoby.first().krestni_jmeno +# +# @property +# def last_name(self): +# osoby = Osoba.objects.filter(user=self) +# if len(osoby) == 0: +# return None +# return osoby.first().prijmeni +# +# @property +# def email(self): +# osoby = Osoba.objects.filter(user=self) +# if len(osoby) == 0: +# return None +# return osoby.first().email From 1004e785de9ff0641ac8e2c7cbc3b45cfff893b4 Mon Sep 17 00:00:00 2001 From: Pavel 'LEdoian' Turinsky Date: Thu, 19 Dec 2019 01:29:53 +0100 Subject: [PATCH 09/17] =?UTF-8?q?Models:=20Aktu=C3=A1ln=C3=AD=20ro=C4=8Dn?= =?UTF-8?q?=C3=ADk=20je=20property?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- seminar/models.py | 1 + 1 file changed, 1 insertion(+) diff --git a/seminar/models.py b/seminar/models.py index bf68d015..fefd72d3 100644 --- a/seminar/models.py +++ b/seminar/models.py @@ -1544,6 +1544,7 @@ class Nastaveni(SingletonModel): aktualni_cislo = models.ForeignKey(Cislo, verbose_name='poslední vydané číslo', null=False, on_delete=models.PROTECT) + @property def aktualni_rocnik(self): return self.aktualni_cislo.rocnik From 4f1828b7af2f40869cc3d78d3d66618d27108316 Mon Sep 17 00:00:00 2001 From: Pavel 'LEdoian' Turinsky Date: Thu, 19 Dec 2019 01:30:48 +0100 Subject: [PATCH 10/17] =?UTF-8?q?P=C5=99ihla=C5=A1ovac=C3=AD=20a=20odhla?= =?UTF-8?q?=C5=A1ovac=C3=AD=20a=20heslo-zapom=C3=ADnac=C3=AD=20views?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Templates většinou chybí. --- seminar/templates/seminar/login.html | 2 ++ seminar/templates/seminar/logout.html | 18 ++++++++++++++++ seminar/urls.py | 8 +++++-- seminar/views.py | 31 ++++++++++++++++++++++++++- 4 files changed, 56 insertions(+), 3 deletions(-) create mode 100644 seminar/templates/seminar/logout.html diff --git a/seminar/templates/seminar/login.html b/seminar/templates/seminar/login.html index eee1303c..6319ecc0 100644 --- a/seminar/templates/seminar/login.html +++ b/seminar/templates/seminar/login.html @@ -13,6 +13,8 @@
          {{ form.as_ul }}
        + {# Django si posílá jméno další stránky jako obsah formuláře a výchozí hodnota (mi přišlo, že) nejde změnit... #} + diff --git a/seminar/templates/seminar/logout.html b/seminar/templates/seminar/logout.html new file mode 100644 index 00000000..ab41a8c8 --- /dev/null +++ b/seminar/templates/seminar/logout.html @@ -0,0 +1,18 @@ +{% extends "seminar/zadani/base.html" %} +{% load staticfiles %} + + +{% block content %} +

        + {% block nadpis1a %}{% block nadpis1b %} + Odhlášení + {% endblock %}{% endblock %} +

        + +Byl jsi úspěšně odhlášen +{# Tohle by se asi mělo udělat přes kontext (title), ale kašlu na to, stejně je to jen jednojazyčná stránka #} + +{# TODO: odkaz na znovupřihlášení? #} + +{% endblock %} + diff --git a/seminar/urls.py b/seminar/urls.py index 213494f6..3d3bd1d5 100644 --- a/seminar/urls.py +++ b/seminar/urls.py @@ -103,10 +103,14 @@ urlpatterns = [ ), path('auth/prihlaska/',views.prihlaskaView, name='seminar_prihlaska'), path('auth/login/', views.LoginView.as_view(), name='login'), - path('auth/logout/', views.logoutView, name='logout'), + path('auth/logout/', views.LogoutView.as_view(), name='logout'), path('auth/resitel/', views.ResitelView.as_view(), name='seminar_resitel'), path('autocomplete/skola/',views.SkolaAutocomplete.as_view(), name='autocomplete_skola'), - path('auth/reset_password', views.resetPasswordView, name='reset_password'), + path('auth/reset_password/', views.PasswordResetView.as_view(), name='reset_password'), + path('auth/change_password/', views.PasswordChangeView.as_view(), name='change_password'), + path('auth/reset_password_done/', views.PasswordResetDoneView.as_view(), name='reset_password_done'), + path('auth/reset_password_confirm/', views.PasswordResetConfirmView.as_view(), name='reset_password_confirm'), + path('auth/reset_password_complete/', views.PasswordResetCompleteView.as_view(), name='reset_password_complete'), path('auth/resitel_edit', views.resitelEditView, name='seminar_resitel_edit'), path('', views.TitulniStranaView.as_view(), name='titulni_strana'), diff --git a/seminar/views.py b/seminar/views.py index ccbb2d00..66a65bbc 100644 --- a/seminar/views.py +++ b/seminar/views.py @@ -2,7 +2,7 @@ from django.shortcuts import get_object_or_404, render from django.http import HttpResponse, HttpResponseRedirect, HttpResponseForbidden, JsonResponse -from django.urls import reverse +from django.urls import reverse,reverse_lazy from django.core.exceptions import PermissionDenied, ObjectDoesNotExist from django.views import generic from django.utils.translation import ugettext as _ @@ -1281,3 +1281,32 @@ class LoginView(auth_views.LoginView): ctx = super().get_context_data(**kwargs) ctx['next'] = reverse('titulni_strana') return ctx + +class LogoutView(auth_views.LogoutView): + # Jen vezmeme vestavěný a dáme mu vhodný template a přesměrovací URL + template_name = 'seminar/logout.html' + # Pavel: Vůbec nevím, proč to s _lazy funguje, ale bez toho to bylo rozbité. + next_page = reverse_lazy('titulni_strana') + +class PasswordResetView(auth_views.PasswordResetView): + #template_name = 'seminar/password_reset.html' + # TODO: vlastní email_template_name a subject_template_name a html_email_template_name + success_url = reverse_lazy('reset_password_done') + from_email = 'login@mam.mff.cuni.cz' + # TODO: přepsat User-a :-( + +class PasswordResetDoneView(auth_views.PasswordResetDoneView): + #template_name = 'seminar/password_reset_done.html' + pass + +class PasswordResetConfirmView(auth_views.PasswordResetConfirmView): + #template_name = 'seminar/password_confirm_done.html' + success_url = reverse_lazy('reset_password_complete') + +class PasswordResetCompleteView(auth_views.PasswordResetCompleteView): + #template_name = 'seminar/password_complete_done.html' + pass + +class PasswordChangeView(auth_views.PasswordChangeView): + #template_name = 'seminar/password_change.html' + success_url = reverse_lazy('titulni_strana') From f1b8a9b5adfcd7d2df28b72d1fe36bb88b2baff9 Mon Sep 17 00:00:00 2001 From: "Tomas \"Jethro\" Pokorny" Date: Wed, 8 Jan 2020 21:44:04 +0100 Subject: [PATCH 11/17] Models: str pro reseni podle noveho modelu. --- seminar/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/seminar/models.py b/seminar/models.py index fefd72d3..7a16bc96 100644 --- a/seminar/models.py +++ b/seminar/models.py @@ -888,7 +888,7 @@ class Reseni(SeminarModelBase): # Konfera def __str__(self): - return "{}: {}".format(self.resitel.osoba.plne_jmeno(), self.problem.nazev) + return "{}({}): {}({})".format(self.resitele.first(),len(self.resitele.all()), self.problem.first() ,len(self.problem.all())) # NOTE: Potenciální DB HOG (bez select_related) ## Pravdepodobne uz nebude potreba: From 65a76935a6a1c56182ed632002d79f7debf75197 Mon Sep 17 00:00:00 2001 From: "Tomas \"Jethro\" Pokorny" Date: Wed, 8 Jan 2020 21:44:50 +0100 Subject: [PATCH 12/17] Admin: django-reverse-admin ukazka a fail s m2m. --- requirements.txt | 1 + seminar/admin.py | 33 +++++++++++++++++++++++++++++---- 2 files changed, 30 insertions(+), 4 deletions(-) diff --git a/requirements.txt b/requirements.txt index f2fd4306..22f8e43c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -27,6 +27,7 @@ django-crispy-forms django-imagekit django-polymorphic django-sitetree +django_reverse_admin # Comments akismet==1.0.1 diff --git a/seminar/admin.py b/seminar/admin.py index e524a19d..11944b56 100644 --- a/seminar/admin.py +++ b/seminar/admin.py @@ -1,6 +1,8 @@ from django.contrib import admin from polymorphic.admin import PolymorphicParentModelAdmin, PolymorphicChildModelAdmin, PolymorphicChildModelFilter +from reversion.admin import VersionAdmin +from django_reverse_admin import ReverseModelAdmin # Todo: reversion @@ -9,7 +11,6 @@ import seminar.models as m admin.site.register(m.Osoba) admin.site.register(m.Skola) admin.site.register(m.Prijemce) -admin.site.register(m.Resitel) admin.site.register(m.Rocnik) admin.site.register(m.Cislo) admin.site.register(m.Organizator) @@ -39,11 +40,35 @@ class UlohaAdmin(PolymorphicChildModelAdmin): base_model = m.Uloha show_in_index = True - +class TextAdminInline(admin.TabularInline): + model = m.Text + exclude = ['text_zkraceny_set','text_zkraceny'] admin.site.register(m.Text) -admin.site.register(m.Reseni) -admin.site.register(m.Hodnoceni) + +class ResitelInline(admin.TabularInline): + model = m.Resitel + extra = 1 +admin.site.register(m.Resitel) + +class PrilohaReseniInline(admin.TabularInline): + model = m.PrilohaReseni + extra = 1 admin.site.register(m.PrilohaReseni) + +class Reseni_ResiteleInline(admin.TabularInline): + model = m.Reseni_Resitele + +@admin.register(m.Reseni) +class ReseniAdmin(ReverseModelAdmin): + base_model = m.Reseni + inline_type = 'tabular' + inline_reverse = ['text_cely','resitele'] + exclude = ['text_zkraceny', 'text_zkraceny_set'] + inlines = [PrilohaReseniInline] +# FAIL in template +# inlines = [PrilohaReseniInline,Reseni_ResiteleInline] + +admin.site.register(m.Hodnoceni) admin.site.register(m.Pohadka) admin.site.register(m.Konfera) admin.site.register(m.Obrazek) From 83aa9a17587180de2f64a075e275da01308db26e Mon Sep 17 00:00:00 2001 From: Pavel 'LEdoian' Turinsky Date: Wed, 15 Jan 2020 22:33:15 +0100 Subject: [PATCH 13/17] =?UTF-8?q?Kop=C3=ADrujeme=20emaily=20z=20osoby=20do?= =?UTF-8?q?=20usera,=20aby=20jednodu=C5=A1e=20fungovalo=20resetov=C3=A1n?= =?UTF-8?q?=C3=AD=20hesla.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../0073_copy_osoba_email_to_user_email.py | 22 +++++++++++++++++++ seminar/models.py | 11 ++++++++++ seminar/views.py | 4 ++++ 3 files changed, 37 insertions(+) create mode 100644 seminar/migrations/0073_copy_osoba_email_to_user_email.py diff --git a/seminar/migrations/0073_copy_osoba_email_to_user_email.py b/seminar/migrations/0073_copy_osoba_email_to_user_email.py new file mode 100644 index 00000000..3b280209 --- /dev/null +++ b/seminar/migrations/0073_copy_osoba_email_to_user_email.py @@ -0,0 +1,22 @@ +# Generated by Django 2.2.9 on 2020-01-15 21:28 + +from django.db import migrations + +def copy_mails(apps, schema_editor): + Osoba = apps.get_model('seminar', 'Osoba') + + for o in Osoba.objects.all(): + if o.user is not None: + u = o.user + u.email = o.email + u.save() + +class Migration(migrations.Migration): + + dependencies = [ + ('seminar', '0072_auto_20191204_2257'), + ] + + operations = [ + migrations.RunPython(copy_mails, migrations.RunPython.noop) + ] diff --git a/seminar/models.py b/seminar/models.py index 7a16bc96..e65dcb67 100644 --- a/seminar/models.py +++ b/seminar/models.py @@ -130,6 +130,17 @@ class Osoba(SeminarModelBase): def __str__(self): return self.plne_jmeno() + # Overridujeme save Osoby, aby když si změní e-mail, aby se projevil i v + # Userovi (a tak se dal poslat mail s resetem hesla) + def save(self, *args, **kwargs): + if self.user is not None: + u = self.user + # U svatého tučňáka, prosím ať tohle funguje. + # (Takhle se kódit asi nemá...) + u.email = self.email + u.save() + super().save() + # # Mělo by být částečně vytaženo z Aesopa # viz https://ovvp.mff.cuni.cz/wiki/aesop/export-skol. diff --git a/seminar/views.py b/seminar/views.py index 065d6879..4a18739f 100644 --- a/seminar/views.py +++ b/seminar/views.py @@ -1296,6 +1296,7 @@ class LogoutView(auth_views.LogoutView): # Pavel: Vůbec nevím, proč to s _lazy funguje, ale bez toho to bylo rozbité. next_page = reverse_lazy('titulni_strana') +# "Chci resetovat heslo" class PasswordResetView(auth_views.PasswordResetView): #template_name = 'seminar/password_reset.html' # TODO: vlastní email_template_name a subject_template_name a html_email_template_name @@ -1303,14 +1304,17 @@ class PasswordResetView(auth_views.PasswordResetView): from_email = 'login@mam.mff.cuni.cz' # TODO: přepsat User-a :-( +# "Poslali jsme e-mail (pokud bylo kam))" class PasswordResetDoneView(auth_views.PasswordResetDoneView): #template_name = 'seminar/password_reset_done.html' pass +# "Vymysli si heslo" class PasswordResetConfirmView(auth_views.PasswordResetConfirmView): #template_name = 'seminar/password_confirm_done.html' success_url = reverse_lazy('reset_password_complete') +# "Heslo se asi změnilo." class PasswordResetCompleteView(auth_views.PasswordResetCompleteView): #template_name = 'seminar/password_complete_done.html' pass From a420a6bc98dc80307392998f7bc05e278c1b4b32 Mon Sep 17 00:00:00 2001 From: Pavel 'LEdoian' Turinsky Date: Wed, 15 Jan 2020 23:55:13 +0100 Subject: [PATCH 14/17] =?UTF-8?q?Zprovozn=C4=9Bn=20reset=20hesla?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- mamweb/settings_local.py | 2 ++ seminar/urls.py | 2 +- seminar/views.py | 1 - 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/mamweb/settings_local.py b/mamweb/settings_local.py index 517772ee..5a2aa969 100644 --- a/mamweb/settings_local.py +++ b/mamweb/settings_local.py @@ -87,3 +87,5 @@ LOGGING = { # set to 'DEBUG' for EXTRA verbose output # LOGGING['handlers']['console']['level'] = 'INFO' + +EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend' diff --git a/seminar/urls.py b/seminar/urls.py index d76e2e31..7ae2558c 100644 --- a/seminar/urls.py +++ b/seminar/urls.py @@ -109,7 +109,7 @@ urlpatterns = [ path('auth/reset_password/', views.PasswordResetView.as_view(), name='reset_password'), path('auth/change_password/', views.PasswordChangeView.as_view(), name='change_password'), path('auth/reset_password_done/', views.PasswordResetDoneView.as_view(), name='reset_password_done'), - path('auth/reset_password_confirm/', views.PasswordResetConfirmView.as_view(), name='reset_password_confirm'), + path('auth/reset_password_confirm///', views.PasswordResetConfirmView.as_view(), name='password_reset_confirm'), path('auth/reset_password_complete/', views.PasswordResetCompleteView.as_view(), name='reset_password_complete'), path('auth/resitel_edit', views.resitelEditView, name='seminar_resitel_edit'), path('', views.TitulniStranaView.as_view(), name='titulni_strana'), diff --git a/seminar/views.py b/seminar/views.py index 4a18739f..6f31a889 100644 --- a/seminar/views.py +++ b/seminar/views.py @@ -1302,7 +1302,6 @@ class PasswordResetView(auth_views.PasswordResetView): # TODO: vlastní email_template_name a subject_template_name a html_email_template_name success_url = reverse_lazy('reset_password_done') from_email = 'login@mam.mff.cuni.cz' - # TODO: přepsat User-a :-( # "Poslali jsme e-mail (pokud bylo kam))" class PasswordResetDoneView(auth_views.PasswordResetDoneView): From e86c090ab849faa0a64548a3dbd37168ab2dbb49 Mon Sep 17 00:00:00 2001 From: Pavel 'LEdoian' Turinsky Date: Thu, 16 Jan 2020 01:26:08 +0100 Subject: [PATCH 15/17] =?UTF-8?q?Admin:=20Akce=20na=20synchronizaci=20dupl?= =?UTF-8?q?icit=20informac=C3=AD=20v=20datab=C3=A1zi?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- seminar/admin.py | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/seminar/admin.py b/seminar/admin.py index 11944b56..6a9dd815 100644 --- a/seminar/admin.py +++ b/seminar/admin.py @@ -8,7 +8,6 @@ from django_reverse_admin import ReverseModelAdmin import seminar.models as m -admin.site.register(m.Osoba) admin.site.register(m.Skola) admin.site.register(m.Prijemce) admin.site.register(m.Rocnik) @@ -16,6 +15,19 @@ admin.site.register(m.Cislo) admin.site.register(m.Organizator) admin.site.register(m.Soustredeni) +@admin.register(m.Osoba) +class OsobaAdmin(admin.ModelAdmin): + actions = ['synchronizuj_maily'] + + def synchronizuj_maily(self, request, queryset): + for o in queryset: + if o.user is not None: + u = o.user + u.email = o.email + u.save() + self.message_user(request, "E-maily synchronizovány.") + synchronizuj_maily.short_description = "Synchronizuj vybraným osobám e-maily do uživatelů" + @admin.register(m.Problem) class ProblemAdmin(PolymorphicParentModelAdmin): base_model = m.Problem @@ -93,6 +105,17 @@ class TreeNodeAdmin(PolymorphicParentModelAdmin): m.TextNode, ] + actions = ['aktualizuj_nazvy'] + + # XXX: nejspíš je to totální DB HOG, nechcete to použít moc často. + def aktualizuj_nazvy(self, request, queryset): + newqs = queryset.get_real_instances() + for tn in newqs: + tn.aktualizuj_nazev() + tn.save() + self.message_user(request, "Názvy aktualizovány.") + aktualizuj_nazvy.short_description = "Aktualizuj vybraným TreeNodům názvy" + @admin.register(m.RocnikNode) class RocnikNodeAdmin(PolymorphicChildModelAdmin): base_model = m.RocnikNode From 33fe0452b34956ab84d4f30399cecff934435cc5 Mon Sep 17 00:00:00 2001 From: Pavel 'LEdoian' Turinsky Date: Thu, 16 Jan 2020 01:26:46 +0100 Subject: [PATCH 16/17] =?UTF-8?q?Models:=20sho=C4=8F=20web,=20pokud=20n?= =?UTF-8?q?=C4=9Bkdo=20aktualizuje=20n=C3=A1zev=20obecn=C3=A9mu=20TreeNode?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- seminar/models.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/seminar/models.py b/seminar/models.py index e65dcb67..95441384 100644 --- a/seminar/models.py +++ b/seminar/models.py @@ -1300,6 +1300,9 @@ class TreeNode(PolymorphicModel): self.aktualizuj_nazev() super().save(*args, **kwargs) + def aktualizuj_nazev(self): + raise NotImplementedError("Pokus o aktualizaci názvu obecného TreeNode místo konkrétní instance") + class RocnikNode(TreeNode): class Meta: db_table = 'seminar_nodes_rocnik' From aaee03497d1718869b5b078f65b843091cc93a00 Mon Sep 17 00:00:00 2001 From: "Tomas \"Jethro\" Pokorny" Date: Wed, 29 Jan 2020 22:22:54 +0100 Subject: [PATCH 17/17] =?UTF-8?q?Views:=20Aktualizov=C3=A1n=20view=20a=20t?= =?UTF-8?q?emplate=20na=20ob=C3=A1lkov=C3=A1n=C3=AD.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- seminar/templates/seminar/org/obalkovani.html | 30 +++++++++++++ seminar/urls.py | 4 +- seminar/views.py | 43 ++++++++++++++++++- 3 files changed, 74 insertions(+), 3 deletions(-) create mode 100644 seminar/templates/seminar/org/obalkovani.html diff --git a/seminar/templates/seminar/org/obalkovani.html b/seminar/templates/seminar/org/obalkovani.html new file mode 100644 index 00000000..fa130bc7 --- /dev/null +++ b/seminar/templates/seminar/org/obalkovani.html @@ -0,0 +1,30 @@ +{% extends "base.html" %} + +{% block content %} +

        + {% block nadpis1a %}{% block nadpis1b %} + Obálkování {{ cislo }} + {% endblock %}{% endblock %} +

        +
          + {% for reseni in object_list %} + {% ifchanged reseni.resitele %} + {% if not forloop.first %} +
        + {% endif %} +

        {% for resitel in reseni.resitele.all %}{{resitel.osoba}},{% endfor %}

        +
          + {% endifchanged %} + +
        • Celkem {{reseni.hodnoceni__body__sum}} bodů z {{reseni.hodnoceni__count}} hodnocení +
            + {% for h in reseni.hodnoceni_set.all %} +
          • {{ h.problem }}: {{ h.body }}b
          • + {% endfor %} +
          +
        • + {% endfor %} +
        + + +{% endblock content %} diff --git a/seminar/urls.py b/seminar/urls.py index 7ae2558c..57e447f3 100644 --- a/seminar/urls.py +++ b/seminar/urls.py @@ -89,7 +89,7 @@ urlpatterns = [ path('stav', staff_member_required(views.StavDatabazeView), name='stav_databaze'), path('cislo/./obalkovani', - staff_member_required(views.obalkovaniView), name='seminar_cislo_resitel_obalkovani'), + staff_member_required(views.ObalkovaniView.as_view()), name='seminar_cislo_resitel_obalkovani'), path('cislo/./tex-download.json', staff_member_required(views.texDownloadView), name='seminar_tex_download'), path('soustredeni//obalky.pdf', @@ -101,6 +101,8 @@ urlpatterns = [ staff_member_required(views.texUploadView), name='seminar_tex_upload' ), + path('org/vloz_body//', + staff_member_required(views.VlozBodyView.as_view()),name='seminar_org_vlozbody'), path('auth/prihlaska/',views.prihlaskaView, name='seminar_prihlaska'), path('auth/login/', views.LoginView.as_view(), name='login'), path('auth/logout/', views.LogoutView.as_view(), name='logout'), diff --git a/seminar/views.py b/seminar/views.py index 6f31a889..b4c710a5 100644 --- a/seminar/views.py +++ b/seminar/views.py @@ -7,7 +7,7 @@ from django.core.exceptions import PermissionDenied, ObjectDoesNotExist from django.views import generic from django.utils.translation import ugettext as _ from django.http import Http404,HttpResponseBadRequest,HttpResponseRedirect -from django.db.models import Q +from django.db.models import Q, Sum, Count from django.views.decorators.csrf import ensure_csrf_cookie from django.contrib.auth import authenticate, login, get_user_model, logout from django.contrib.auth import views as auth_views @@ -45,6 +45,45 @@ def verejna_temata(rocnik): """ return Problem.objects.filter(typ=Problem.TYP_TEMA, cislo_zadani__rocnik=rocnik, cislo_zadani__verejne_db=True).order_by('kod') +def temata_v_rocniku(rocnik): + return Problem.objects.filter(typ=Problem.TYP_TEMA, rocnik=rocnik) + +def get_problemy_k_tematu(tema): + return Problemy.objects.filter(nadproblem = tema) + + +class VlozBodyView(generic.ListView): + template_name = 'seminar/org/vloz_body.html' + + def get_queryset(self): + self.tema = get_object_or_404(Problem,id=self.kwargs['tema']) + print(self.tema) + self.problemy = Problem.objects.filter(nadproblem = self.tema) + print(self.problemy) + self.reseni = Reseni.objects.filter(problem__in=self.problemy) + print(self.reseni) + return self.reseni + + +class ObalkovaniView(generic.ListView): + template_name = 'seminar/org/obalkovani.html' + + def get_queryset(self): + rocnik = get_object_or_404(Rocnik,rocnik=self.kwargs['rocnik']) + cislo = get_object_or_404(Cislo,rocnik=rocnik,poradi=self.kwargs['cislo']) + self.cislo = cislo + self.hodnoceni = s.Hodnoceni.objects.filter(cislo_body=cislo) + self.reseni = Reseni.objects.filter(hodnoceni__in = self.hodnoceni).annotate(Sum('hodnoceni__body')).annotate(Count('hodnoceni')).order_by('resitele__osoba') + return self.reseni + + def get_context_data(self, **kwargs): + context = super(ObalkovaniView, self).get_context_data(**kwargs) + print(self.cislo) + context['cislo'] = self.cislo + return context + + + def AktualniZadaniView(request): nastaveni = get_object_or_404(Nastaveni) @@ -740,7 +779,7 @@ def obalkyView(request,resitele): return response -def obalkovaniView(request, rocnik, cislo): +def oldObalkovaniView(request, rocnik, cislo): rocnik = Rocnik.objects.get(rocnik=rocnik) cislo = Cislo.objects.get(rocnik=rocnik, cislo=cislo)