Browse Source

Merge branch 'data_migrations' of gimli.ms.mff.cuni.cz:/akce/mam/git/mamweb into data_migrations

export_seznamu_prednasek
Anet 5 years ago
parent
commit
0425e368ed
  1. 2
      mamweb/settings_local.py
  2. 1
      requirements.txt
  3. 58
      seminar/admin.py
  4. 89
      seminar/forms.py
  5. 23
      seminar/migrations/0072_auto_20191204_2257.py
  6. 22
      seminar/migrations/0073_copy_osoba_email_to_user_email.py
  7. 119
      seminar/models.py
  8. 78
      seminar/templates/seminar/edit.html
  9. 13
      seminar/templates/seminar/login.html
  10. 18
      seminar/templates/seminar/logout.html
  11. 30
      seminar/templates/seminar/org/obalkovani.html
  12. 14
      seminar/templates/seminar/tematka/rozcestnik.html
  13. 1
      seminar/templates/seminar/tematka/toaletak.html
  14. 3
      seminar/testutils.py
  15. 19
      seminar/urls.py
  16. 9
      seminar/utils.py
  17. 244
      seminar/views.py

2
mamweb/settings_local.py

@ -87,3 +87,5 @@ LOGGING = {
# set to 'DEBUG' for EXTRA verbose output # set to 'DEBUG' for EXTRA verbose output
# LOGGING['handlers']['console']['level'] = 'INFO' # LOGGING['handlers']['console']['level'] = 'INFO'
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'

1
requirements.txt

@ -27,6 +27,7 @@ django-crispy-forms
django-imagekit django-imagekit
django-polymorphic django-polymorphic
django-sitetree django-sitetree
django_reverse_admin
# Comments # Comments
akismet==1.0.1 akismet==1.0.1

58
seminar/admin.py

@ -1,20 +1,33 @@
from django.contrib import admin from django.contrib import admin
from polymorphic.admin import PolymorphicParentModelAdmin, PolymorphicChildModelAdmin, PolymorphicChildModelFilter from polymorphic.admin import PolymorphicParentModelAdmin, PolymorphicChildModelAdmin, PolymorphicChildModelFilter
from reversion.admin import VersionAdmin
from django_reverse_admin import ReverseModelAdmin
# Todo: reversion # Todo: reversion
import seminar.models as m import seminar.models as m
admin.site.register(m.Osoba)
admin.site.register(m.Skola) admin.site.register(m.Skola)
admin.site.register(m.Prijemce) admin.site.register(m.Prijemce)
admin.site.register(m.Resitel)
admin.site.register(m.Rocnik) admin.site.register(m.Rocnik)
admin.site.register(m.Cislo) admin.site.register(m.Cislo)
admin.site.register(m.Organizator) admin.site.register(m.Organizator)
admin.site.register(m.Soustredeni) 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) @admin.register(m.Problem)
class ProblemAdmin(PolymorphicParentModelAdmin): class ProblemAdmin(PolymorphicParentModelAdmin):
base_model = m.Problem base_model = m.Problem
@ -39,11 +52,35 @@ class UlohaAdmin(PolymorphicChildModelAdmin):
base_model = m.Uloha base_model = m.Uloha
show_in_index = True 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.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) 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.Pohadka)
admin.site.register(m.Konfera) admin.site.register(m.Konfera)
admin.site.register(m.Obrazek) admin.site.register(m.Obrazek)
@ -68,6 +105,17 @@ class TreeNodeAdmin(PolymorphicParentModelAdmin):
m.TextNode, 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) @admin.register(m.RocnikNode)
class RocnikNodeAdmin(PolymorphicChildModelAdmin): class RocnikNodeAdmin(PolymorphicChildModelAdmin):
base_model = m.RocnikNode base_model = m.RocnikNode

89
seminar/forms.py

@ -121,3 +121,92 @@ class PrihlaskaForm(forms.Form):
self.add_error('skola_nazev',forms.ValidationError('Je nutné vyplnit název školy')) self.add_error('skola_nazev',forms.ValidationError('Je nutné vyplnit název školy'))
elif data.get('skola_adresa')=='': elif data.get('skola_adresa')=='':
self.add_error('skola_adresa',forms.ValidationError('Je nutné vyplnit adresu školy')) 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)
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)
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'))

23
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é'),
),
]

22
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)
]

119
seminar/models.py

@ -21,9 +21,9 @@ from taggit.managers import TaggableManager
from reversion import revisions as reversion 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 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 from polymorphic.models import PolymorphicModel
@ -130,6 +130,17 @@ class Osoba(SeminarModelBase):
def __str__(self): def __str__(self):
return self.plne_jmeno() 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 # Mělo by být částečně vytaženo z Aesopa
# viz https://ovvp.mff.cuni.cz/wiki/aesop/export-skol. # viz https://ovvp.mff.cuni.cz/wiki/aesop/export-skol.
@ -620,7 +631,7 @@ class Problem(SeminarModelBase,PolymorphicModel):
id = models.AutoField(primary_key = True) id = models.AutoField(primary_key = True)
# Název # 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 # Problém má podproblémy
nadproblem = models.ForeignKey('self', verbose_name='nadřazený problém', nadproblem = models.ForeignKey('self', verbose_name='nadřazený problém',
@ -785,8 +796,11 @@ class Text(SeminarModelBase):
for tn in self.textnode_set.all(): for tn in self.textnode_set.all():
tn.save() tn.save()
def __str__(self):
parser = FirstTagParser()
parser.feed(str(self.na_web))
return parser.firstTag
class Uloha(Problem): class Uloha(Problem):
class Meta: class Meta:
db_table = 'seminar_ulohy' db_table = 'seminar_ulohy'
@ -885,7 +899,7 @@ class Reseni(SeminarModelBase):
# Konfera # Konfera
def __str__(self): 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) # NOTE: Potenciální DB HOG (bez select_related)
## Pravdepodobne uz nebude potreba: ## Pravdepodobne uz nebude potreba:
@ -1239,8 +1253,15 @@ class TreeNode(PolymorphicModel):
on_delete=models.SET_NULL, on_delete=models.SET_NULL,
verbose_name="další element na stejné úrovni") verbose_name="další element na stejné úrovni")
nazev = models.TextField("název tohoto node", 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", help_text = "Tento název se zobrazuje v nabídkách pro výběr vhodného TreeNode",
blank=False, null=True) blank=False,
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")
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): def print_tree(self,indent=0):
print("{}TreeNode({})".format(" "*indent,self.id)) print("{}TreeNode({})".format(" "*indent,self.id))
@ -1248,7 +1269,27 @@ class TreeNode(PolymorphicModel):
self.first_child.print_tree(indent=indent+2) self.first_child.print_tree(indent=indent+2)
if self.succ: if self.succ:
self.succ.print_tree(indent=indent) self.succ.print_tree(indent=indent)
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): def __str__(self):
if self.nazev: if self.nazev:
return self.nazev return self.nazev
@ -1260,6 +1301,9 @@ class TreeNode(PolymorphicModel):
self.aktualizuj_nazev() self.aktualizuj_nazev()
super().save(*args, **kwargs) 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 RocnikNode(TreeNode):
class Meta: class Meta:
db_table = 'seminar_nodes_rocnik' db_table = 'seminar_nodes_rocnik'
@ -1284,6 +1328,9 @@ class CisloNode(TreeNode):
def aktualizuj_nazev(self): def aktualizuj_nazev(self):
self.nazev = "CisloNode: "+str(self.cislo) self.nazev = "CisloNode: "+str(self.cislo)
def getOdkazStr(self):
return "Číslo " + str(self.cislo)
class MezicisloNode(TreeNode): class MezicisloNode(TreeNode):
class Meta: class Meta:
db_table = 'seminar_nodes_mezicislo' db_table = 'seminar_nodes_mezicislo'
@ -1305,6 +1352,8 @@ class MezicisloNode(TreeNode):
else: else:
print("!!!!! Nějaké neidentifikované mezičíslo !!!!!") print("!!!!! Nějaké neidentifikované mezičíslo !!!!!")
self.nazev = "MezicisloNode: Neidentifikovatelné mezičíslo!" self.nazev = "MezicisloNode: Neidentifikovatelné mezičíslo!"
def getOdkazStr(self):
return "Obsah dostupný pouze na webu"
class TemaVCisleNode(TreeNode): class TemaVCisleNode(TreeNode):
""" Obsahuje příspěvky k tématu v daném čísle """ """ Obsahuje příspěvky k tématu v daném čísle """
@ -1319,6 +1368,9 @@ class TemaVCisleNode(TreeNode):
def aktualizuj_nazev(self): def aktualizuj_nazev(self):
self.nazev = "TemaVCisleNode: "+str(self.tema) self.nazev = "TemaVCisleNode: "+str(self.tema)
def getOdkazStr(self):
return str(self.tema)
class KonferaNode(TreeNode): class KonferaNode(TreeNode):
class Meta: class Meta:
db_table = 'seminar_nodes_konfera' db_table = 'seminar_nodes_konfera'
@ -1347,6 +1399,10 @@ class ClanekNode(TreeNode):
def aktualizuj_nazev(self): def aktualizuj_nazev(self):
self.nazev = "ClanekNode: "+str(self.clanek) self.nazev = "ClanekNode: "+str(self.clanek)
def getOdkazStr(self):
return str(self.clanek)
class UlohaZadaniNode(TreeNode): class UlohaZadaniNode(TreeNode):
class Meta: class Meta:
db_table = 'seminar_nodes_uloha_zadani' db_table = 'seminar_nodes_uloha_zadani'
@ -1361,6 +1417,10 @@ class UlohaZadaniNode(TreeNode):
def aktualizuj_nazev(self): def aktualizuj_nazev(self):
self.nazev = "UlohaZadaniNode: "+str(self.uloha) self.nazev = "UlohaZadaniNode: "+str(self.uloha)
def getOdkazStr(self):
return str(self.uloha)
class PohadkaNode(TreeNode): class PohadkaNode(TreeNode):
class Meta: class Meta:
db_table = 'seminar_nodes_pohadka' db_table = 'seminar_nodes_pohadka'
@ -1388,6 +1448,10 @@ class UlohaVzorakNode(TreeNode):
def aktualizuj_nazev(self): def aktualizuj_nazev(self):
self.nazev = "UlohaVzorakNode: "+str(self.uloha) self.nazev = "UlohaVzorakNode: "+str(self.uloha)
def getOdkazStr(self):
return str(self.uloha)
class TextNode(TreeNode): class TextNode(TreeNode):
class Meta: class Meta:
db_table = 'seminar_nodes_obsah' db_table = 'seminar_nodes_obsah'
@ -1400,6 +1464,10 @@ class TextNode(TreeNode):
def aktualizuj_nazev(self): def aktualizuj_nazev(self):
self.nazev = "TextNode: "+str(self.text) self.nazev = "TextNode: "+str(self.text)
def getOdkazStr(self):
return str(self.text)
## FIXME: Logiku přesunout do views. ## FIXME: Logiku přesunout do views.
#class VysledkyBase(SeminarModelBase): #class VysledkyBase(SeminarModelBase):
# #
@ -1491,6 +1559,7 @@ class Nastaveni(SingletonModel):
aktualni_cislo = models.ForeignKey(Cislo, verbose_name='poslední vydané číslo', aktualni_cislo = models.ForeignKey(Cislo, verbose_name='poslední vydané číslo',
null=False, on_delete=models.PROTECT) null=False, on_delete=models.PROTECT)
@property
def aktualni_rocnik(self): def aktualni_rocnik(self):
return self.aktualni_cislo.rocnik return self.aktualni_cislo.rocnik
@ -1534,3 +1603,35 @@ class Novinky(models.Model):
return '[' + str(self.datum) + '] ' + self.text[0:50] return '[' + str(self.datum) + '] ' + self.text[0:50]
else: else:
return '[' + str(self.datum) + '] ' 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

78
seminar/templates/seminar/edit.html

@ -0,0 +1,78 @@
{% extends "seminar/zadani/base.html" %}
{% load staticfiles %}
{% block script %}
<!--script type="text/javascript" src="{% static 'admin/js/vendor/jquery/jquery.js' %}"></script!-->
{{form.media}}
<script src="{% static 'seminar/prihlaska.js' %}"></script>
{% endblock %}
{% block content %}
<h1>
{% block nadpis1a %}{% block nadpis1b %}
Změna osobních údajů
{% endblock %}{% endblock %}
</h1>
<form action="{% url 'seminar_resitel_edit' %}" method="post">
{% csrf_token %}
{{form.non_field_errors}}
<ul class="form">
<li>
Přihlašovací údaje
</li><li>
{% include "seminar/prihlaska_field.html" with field=form.username %}
</li><li>
Osobní údaje
</li><li>
{% include "seminar/prihlaska_field.html" with field=form.jmeno %}
</li><li>
{% include "seminar/prihlaska_field.html" with field=form.prijmeni %}
</li><li>
{% include "seminar/prihlaska_field.html" with field=form.pohlavi_muz%}
</li><li>
{% include "seminar/prihlaska_field.html" with field=form.email %}
</li><li>
{% include "seminar/prihlaska_field.html" with field=form.telefon %}
</li><li>
{% include "seminar/prihlaska_field.html" with field=form.datum_narozeni %}
</li><li>
<hr>
Bydliště
</li><li>
{% include "seminar/prihlaska_field.html" with field=form.ulice %}
</li><li>
{% include "seminar/prihlaska_field.html" with field=form.mesto %}
</li><li>
{% include "seminar/prihlaska_field.html" with field=form.psc %}
</li><li>
{% include "seminar/prihlaska_field.html" with field=form.stat %}
</li>
<li id="id_li_stat_text">
{% include "seminar/prihlaska_field.html" with field=form.stat_text %}
</li><li>
<hr>
{% include "seminar/prihlaska_field.html" with field=form.skola %}
</li><li>
<button id="id_skola_text_button" type="button">Škola není v seznamu</button>
</li>
<li id="id_li_skola_nazev">
Vyplň prosím celý název a adresu školy.<br>
{% include "seminar/prihlaska_field.html" with field=form.skola_nazev %}
</li>
<li id="id_li_skola_adresa">
{% include "seminar/prihlaska_field.html" with field=form.skola_adresa %}
</li><li>
{% include "seminar/prihlaska_field.html" with field=form.rok_maturity %}
</li><li>
{% include "seminar/prihlaska_field.html" with field=form.zasilat %}
</li><li>
{% include "seminar/prihlaska_field.html" with field=form.spam %}
</li>
</ul>
<input type="submit" value="Změnit">
</form>
<script>
$("#id_stat").on("change",addrCountryChanged);
$("#id_skola_text_button").on("click",schoolNotInList);
</script>
{% endblock %}

13
seminar/templates/seminar/login.html

@ -8,20 +8,13 @@
Přihlášení Přihlášení
{% endblock %}{% endblock %} {% endblock %}{% endblock %}
</h1> </h1>
{% if login_error %}
<span class="field_error">{{login_error}}<span>
{% endif %}
<form action="{% url 'login' %}" method="post"> <form action="{% url 'login' %}" method="post">
{% csrf_token %} {% csrf_token %}
{{form.non_field_errors}}
<ul class="form"> <ul class="form">
<li> {{ form.as_ul }}
{% include "seminar/prihlaska_field.html" with field=form.username %}
</li>
<li>
{% include "seminar/prihlaska_field.html" with field=form.password %}
</li>
</ul> </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... #}
<input type="hidden" name='next' value="{{ next }}">
<input type="submit" value="Přihlásit"> <input type="submit" value="Přihlásit">
</form> </form>

18
seminar/templates/seminar/logout.html

@ -0,0 +1,18 @@
{% extends "seminar/zadani/base.html" %}
{% load staticfiles %}
{% block content %}
<h1>
{% block nadpis1a %}{% block nadpis1b %}
Odhlášení
{% endblock %}{% endblock %}
</h1>
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 %}

30
seminar/templates/seminar/org/obalkovani.html

@ -0,0 +1,30 @@
{% extends "base.html" %}
{% block content %}
<h1>
{% block nadpis1a %}{% block nadpis1b %}
Obálkování {{ cislo }}
{% endblock %}{% endblock %}
</h1>
<ul>
{% for reseni in object_list %}
{% ifchanged reseni.resitele %}
{% if not forloop.first %}
</ul>
{% endif %}
<h4>{% for resitel in reseni.resitele.all %}{{resitel.osoba}},{% endfor %}</h4>
<ul>
{% endifchanged %}
<li>Celkem {{reseni.hodnoceni__body__sum}} bodů z {{reseni.hodnoceni__count}} hodnocení
<ul>
{% for h in reseni.hodnoceni_set.all %}
<li> {{ h.problem }}: {{ h.body }}b </li>
{% endfor %}
</ul>
</li>
{% endfor %}
</ul>
{% endblock content %}

14
seminar/templates/seminar/tematka/rozcestnik.html

@ -0,0 +1,14 @@
{% for tematko in tematka %}
<h1>{{tematko.nazev}}</h1>
<p>{{tematko.abstrakt}}</p>
<ul>
{% for cislo in tematko.cisla %}
<li><a href="/{{rocnik}}/t{{tematko.kod}}/#{{cislo.0.1}}">{{cislo.0.0}}</a></li>
<ul>
{% for odkaz in cislo.1 %}
<li><a href="/{{rocnik}}/t{{tematko.kod}}/#{{odkaz.1}}">{{odkaz.0}}</a></li>
{% endfor %}
</ul>
{% endfor %}
</ul>
{% endfor %}

1
seminar/templates/seminar/tematka/toaletak.html

@ -0,0 +1 @@
Stránká témátka

3
seminar/testutils.py

@ -380,7 +380,8 @@ def gen_temata(rnd, rocniky, rocnik_cisla, organizatori):
kod=str(n), kod=str(n),
# atributy třídy Téma # atributy třídy Téma
tema_typ=rnd.choice(Tema.TEMA_CHOICES)[0], 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)) konec_tematu = min(rnd.randint(ci, 7), len(cisla))
for i in range(ci, konec_tematu+1): for i in range(ci, konec_tematu+1):

19
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) staff_member_required = user_passes_test(lambda u: u.is_staff)
urlpatterns = [ urlpatterns = [
path('aktualni/temata/', views.TemataRozcestnikView),
path('<int:rocnik>/t<int:tematko>/', views.TematkoView),
# REDIRECTy # REDIRECTy
path('jak-resit/', RedirectView.as_view(url='/co-je-MaM/jak-resit/')), path('jak-resit/', RedirectView.as_view(url='/co-je-MaM/jak-resit/')),
@ -86,24 +89,30 @@ urlpatterns = [
path('stav', path('stav',
staff_member_required(views.StavDatabazeView), name='stav_databaze'), staff_member_required(views.StavDatabazeView), name='stav_databaze'),
path('cislo/<int:rocnik>.<int:cislo>/obalkovani', path('cislo/<int:rocnik>.<int: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/<int:rocnik>.<int:cislo>/tex-download.json', path('cislo/<int:rocnik>.<int:cislo>/tex-download.json',
staff_member_required(views.texDownloadView), name='seminar_tex_download'), staff_member_required(views.texDownloadView), name='seminar_tex_download'),
path('soustredeni/<int:soustredeni>/obalky.pdf', path('soustredeni/<int:soustredeni>/obalky.pdf',
staff_member_required(views.soustredeniObalkyView), name='seminar_soustredeni_obalky'), 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( path(
'tex-upload/', 'tex-upload/',
staff_member_required(views.texUploadView), staff_member_required(views.texUploadView),
name='seminar_tex_upload' name='seminar_tex_upload'
), ),
path('org/vloz_body/<int:tema>/',
staff_member_required(views.VlozBodyView.as_view()),name='seminar_org_vlozbody'),
path('auth/prihlaska/',views.prihlaskaView, name='seminar_prihlaska'), 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/logout/', views.LogoutView.as_view(), name='logout'),
path('auth/resitel/', views.ResitelView.as_view(), name='seminar_resitel'), path('auth/resitel/', views.ResitelView.as_view(), name='seminar_resitel'),
path('autocomplete/skola/',views.SkolaAutocomplete.as_view(), name='autocomplete_skola'), 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/<uidb64>/<token>/', 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('auth/resitel_edit', views.resitelEditView, name='seminar_resitel_edit'),
path('', views.TitulniStranaView.as_view(), name='titulni_strana'), path('', views.TitulniStranaView.as_view(), name='titulni_strana'),

9
seminar/utils.py

@ -2,9 +2,18 @@
import datetime import datetime
from django.contrib.auth.decorators import user_passes_test 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) 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): def histogram(seznam):
d = {} d = {}
for i in seznam: for i in seznam:

244
seminar/views.py

@ -2,24 +2,26 @@
from django.shortcuts import get_object_or_404, render from django.shortcuts import get_object_or_404, render
from django.http import HttpResponse, HttpResponseRedirect, HttpResponseForbidden, JsonResponse 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.core.exceptions import PermissionDenied, ObjectDoesNotExist
from django.views import generic from django.views import generic
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from django.http import Http404,HttpResponseBadRequest,HttpResponseRedirect 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.views.decorators.csrf import ensure_csrf_cookie
from django.contrib.auth import authenticate, login, get_user_model, logout 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.models import User
from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.auth.mixins import LoginRequiredMixin
from django.db import transaction from django.db import transaction
from dal import autocomplete 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 .models import VysledkyZaCislo, VysledkyKCisluZaRocnik, VysledkyKCisluOdjakziva
from . import utils from . import utils
from .unicodecsv import UnicodeWriter from .unicodecsv import UnicodeWriter
from .forms import PrihlaskaForm, LoginForm from .forms import PrihlaskaForm, LoginForm, EditForm
from datetime import timedelta, date, datetime from datetime import timedelta, date, datetime
from django.utils import timezone from django.utils import timezone
@ -43,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') 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): def AktualniZadaniView(request):
nastaveni = get_object_or_404(Nastaveni) nastaveni = get_object_or_404(Nastaveni)
@ -73,6 +114,99 @@ def ZadaniTemataView(request):
} }
) )
# 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 = []
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 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("=============================================")
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) #Odkazy jsou tuply (node, depth) v listu
print(odkazy)
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].getOdkazStr(), odkaz[0].getOdkaz())
vcisle = []
else:
print(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
})
return render(request, 'seminar/tematka/rozcestnik.html', {"tematka": tematka, "rocnik" : nastaveni.aktualni_rocnik().rocnik})
#def ZadaniAktualniVysledkovkaView(request): #def ZadaniAktualniVysledkovkaView(request):
# nastaveni = get_object_or_404(Nastaveni) # nastaveni = get_object_or_404(Nastaveni)
@ -672,7 +806,7 @@ def obalkyView(request,resitele):
return response return response
def obalkovaniView(request, rocnik, cislo): def oldObalkovaniView(request, rocnik, cislo):
rocnik = Rocnik.objects.get(rocnik=rocnik) rocnik = Rocnik.objects.get(rocnik=rocnik)
cislo = Cislo.objects.get(rocnik=rocnik, cislo=cislo) cislo = Cislo.objects.get(rocnik=rocnik, cislo=cislo)
@ -809,7 +943,7 @@ def StavDatabazeView(request):
@ensure_csrf_cookie @ensure_csrf_cookie
def LoginView(request): def TeXUploadLoginView(request):
"""Pro přihlášení při nahrávání z texu""" """Pro přihlášení při nahrávání z texu"""
q = request.POST q = request.POST
# nastavení cookie csrftoken # nastavení cookie csrftoken
@ -1016,8 +1150,6 @@ class ResitelView(LoginRequiredMixin,generic.DetailView):
return Resitel.objects.get(osoba__user=self.request.user) return Resitel.objects.get(osoba__user=self.request.user)
## Formulare ## Formulare
def resitelEditView(request):
pass
def resetPasswordView(request): def resetPasswordView(request):
pass pass
@ -1054,6 +1186,59 @@ def prihlaska_log_gdpr_safe(logger, gdpr_logger, msg, form_data):
logger.warn(msg) 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):
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(user_edit)
prefill_2=model_to_dict(resitel_edit)
prefill_3=model_to_dict(osoba_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():
## 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:
## Stránka před odeslaním formuláře = předvyplněný formulář
return render(request, 'seminar/edit.html', {'form': form})
def prihlaskaView(request): def prihlaskaView(request):
generic_logger = logging.getLogger('seminar.prihlaska') generic_logger = logging.getLogger('seminar.prihlaska')
@ -1159,3 +1344,46 @@ class SkolaAutocomplete(autocomplete.Select2QuerySetView):
# Q(user__last_name__isstartswith=query)) # Q(user__last_name__isstartswith=query))
# #
# return qs # 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
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')
# "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
success_url = reverse_lazy('reset_password_done')
from_email = 'login@mam.mff.cuni.cz'
# "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
class PasswordChangeView(auth_views.PasswordChangeView):
#template_name = 'seminar/password_change.html'
success_url = reverse_lazy('titulni_strana')

Loading…
Cancel
Save