Browse Source

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

export_seznamu_prednasek
Kateřina Čížková 5 years ago
parent
commit
2e4610c76f
  1. 2
      .gitignore
  2. 6
      README.md
  3. 4
      galerie/admin.py
  4. 0
      galerie/autocomplete_light_registry.py.old
  5. 35
      mamweb/settings_common.py
  6. 13
      mamweb/settings_debug.py
  7. 7
      mamweb/settings_local.py
  8. 2
      mamweb/settings_prod.py
  9. 2
      mamweb/settings_test.py
  10. 24
      mamweb/static/css/mamweb.css
  11. 1
      mamweb/templates/admin/base_site.html
  12. 24
      mamweb/templates/base.html
  13. 1
      mamweb/urls.py
  14. 3
      requirements.txt
  15. 173
      seminar/admin.py
  16. 0
      seminar/autocomplete_light_registry.py.old
  17. 255
      seminar/forms.py
  18. 20
      seminar/management/commands/nukedb.py
  19. 15
      seminar/management/commands/testdata.py
  20. 31
      seminar/migrations/0065_treenode_polymorphic_ctype.py
  21. 29
      seminar/migrations/0066_problem_polymorphic_ctype.py
  22. 18
      seminar/migrations/0067_auto_20190814_0805.py
  23. 107
      seminar/migrations/0068_treenode_nazev.py
  24. 28
      seminar/migrations/0069_auto_20191120_2115.py
  25. 23
      seminar/migrations/0070_auto_20191120_2357.py
  26. 17
      seminar/migrations/0071_remove_nastaveni_aktualni_rocnik.py
  27. 23
      seminar/migrations/0072_auto_20191204_2257.py
  28. 22
      seminar/migrations/0073_copy_osoba_email_to_user_email.py
  29. 288
      seminar/models.py
  30. 1603
      seminar/static/seminar/lisak.eps
  31. BIN
      seminar/static/seminar/lisak.pdf
  32. 32
      seminar/static/seminar/prihlaska.js
  33. 3
      seminar/templates/seminar/archiv/obalky.tex
  34. 78
      seminar/templates/seminar/edit.html
  35. 49
      seminar/templates/seminar/gdpr.html
  36. 26
      seminar/templates/seminar/login.html
  37. 18
      seminar/templates/seminar/logout.html
  38. 30
      seminar/templates/seminar/org/obalkovani.html
  39. 21
      seminar/templates/seminar/org/vloz_reseni.html
  40. 113
      seminar/templates/seminar/prihlaska.html
  41. 4
      seminar/templates/seminar/prihlaska_field.html
  42. 17
      seminar/templates/seminar/resitel.html
  43. 14
      seminar/templates/seminar/tematka/rozcestnik.html
  44. 1
      seminar/templates/seminar/tematka/toaletak.html
  45. 81
      seminar/testutils.py
  46. 27
      seminar/urls.py
  47. 9
      seminar/utils.py
  48. 654
      seminar/views.py

2
.gitignore

@ -10,7 +10,7 @@
# aux files
*.pyc
*.swp
*.sw[mnop]
# secrets
/django.secret

6
README.md

@ -13,11 +13,13 @@ Use git :-)
Quickstart
----------
Run the following commands:
make install_venv
. env/bin/activate
make install_web
After finishing development, run "deactivate".
Make commands
-------------
@ -56,6 +58,8 @@ Make commands
* `./manage.py test` - run the tests.
* `./manage.py shell` - run commands, list elemements of database, check syntax
by importing files, etc.
Configurations
--------------

4
galerie/admin.py

@ -5,7 +5,6 @@ from django.contrib import admin
from django.http import HttpResponseRedirect
from django import forms
from django.db import models
from autocomplete_light import shortcuts as autocomplete_light
# akction
@ -39,11 +38,12 @@ class GalerieInline(admin.TabularInline):
class ObrazekAdmin(admin.ModelAdmin):
list_display = ('obrazek_velky', 'nazev', 'popis', 'obrazek_maly_tag')
search_fields = ['nazev','popis']
class GalerieAdmin(admin.ModelAdmin):
form = autocomplete_light.modelform_factory(Galerie, autocomplete_fields=['titulni_obrazek'], fields=['titulni_obrazek'])
model = Galerie
fields = ('zobrazit', 'nazev', 'titulni_obrazek', 'popis', 'galerie_up', 'soustredeni', 'poradi')
autocomplete_fields = ['titulni_obrazek']
list_display = ('nazev', 'soustredeni', 'galerie_up', 'poradi', 'zobrazit', 'datum_zmeny')
inlines = [GalerieInline]
actions = [zverejnit_fotogalerii, prepnout_fotogalerii_do_org_rezimu]

0
galerie/autocomplete_light_registry.py → galerie/autocomplete_light_registry.py.old

35
mamweb/settings_common.py

@ -53,7 +53,7 @@ AUTHENTICATION_BACKENDS = (
MIDDLEWARE = (
'reversion.middleware.RevisionMiddleware',
# 'reversion.middleware.RevisionMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
@ -98,13 +98,14 @@ INSTALLED_APPS = (
# Utilities
'sekizai',
'reversion',
# 'reversion',
'django_countries',
'solo',
'ckeditor',
'ckeditor_uploader',
'taggit',
'autocomplete_light',
'dal',
'dal_select2',
'fluent_comments',
'crispy_forms',
@ -118,6 +119,8 @@ INSTALLED_APPS = (
'imagekit',
'polymorphic',
# MaMweb
'mamweb',
'seminar',
@ -215,6 +218,14 @@ LOGGING = {
'handlers': ['console'],
'level': 'DEBUG',
},
'seminar.prihlaska.form':{
'handlers': ['console','registration_logfile'],
'level': 'INFO'
},
'seminar.prihlaska.problem':{
'handlers': ['console','mail_registration','registration_error_log'],
'level': 'INFO'
},
# Catch-all logger
'': {
@ -237,6 +248,24 @@ LOGGING = {
'class': 'django.utils.log.AdminEmailHandler',
'formatter': 'verbose',
},
'mail_registraion': {
'level': 'WARN',
'class': 'django.utils.log.AdminEmailHandler',
'formatter': 'verbose',
},
'registration_logfile':{
'level': 'INFO',
'class': 'logging.FileHandler',
# filename declared in specific configuration files
'formatter': 'verbose',
},
'registration_error_log':{
'level': 'INFO',
'class': 'logging.FileHandler',
# filename declared in specific configuration files
'formatter': 'verbose',
},
},
}

13
mamweb/settings_debug.py

@ -0,0 +1,13 @@
# Debugovaci nastaveni settings.py
# Pro vyber tohoto nastaveni muzete pouzit tez:
# DJANGO_SETTINGS_MODULE=mamweb.settings_debug ./manage.py ...
# Import local settings
from .settings_local import *
# Vypisovani databazovych dotazu do konzole
LOGGING['loggers']['django.db.backends'] = {
'level': 'DEBUG',
'handlers': ['console'],
'propagate': False,
}

7
mamweb/settings_local.py

@ -78,6 +78,11 @@ LOGGING = {
# 'handlers': ['console'],
# 'propagate': False,
#},
'werkzeug': {
'handlers': ['console'],
'level': 'DEBUG',
'propagate': True,
},
'': {
'handlers': ['console'],
'level': 'DEBUG',
@ -88,3 +93,5 @@ LOGGING = {
# set to 'DEBUG' for EXTRA verbose output
# LOGGING['handlers']['console']['level'] = 'INFO'
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'

2
mamweb/settings_prod.py

@ -61,6 +61,8 @@ CSRF_COOKIE_SECURE = True
LOGGING['loggers']['']['handlers'] = ['console', 'mail_admins']
LOGGING['loggers']['django']['handlers'] = ['console', 'mail_admins']
LOGGING['handlers']['registration_logfile']['filename'] = '/home/mam-web/logs/prod/registration.log'
LOGGING['handlers']['registration_error_log']['filename'] = '/home/mam-web/logs/prod/registration_errors.log'
# E-MAIL NOTIFICATIONS

2
mamweb/settings_test.py

@ -65,3 +65,5 @@ CSRF_COOKIE_SECURE = True
LOGGING['loggers']['']['handlers'] = ['console', 'mail_admins']
LOGGING['loggers']['django']['handlers'] = ['console', 'mail_admins']
LOGGING['handlers']['registration_logfile']['filename'] = '/home/mam-web/logs/test/registration.log'
LOGGING['handlers']['registration_error_log']['filename'] = '/home/mam-web/logs/test/registration_errors.log'

24
mamweb/static/css/mamweb.css

@ -862,3 +862,27 @@ div.nahledy_cisel {
div.nahledy_cisel div, div.nahledy_cisel img {
position: absolute;
}
ul.form {
list-style-type: none;
padding-left: 0px;
}
label.field-label {
font-weight: normal;
}
label.field-required {
font-weight: bold;
}
.field-error {
font-size: 14px;
color: red;
}
ul.form li{
margin-bottom: 3px;
}
p.gdpr {
font-size: 6pt;
margin-bottom: .66em;
}
div.gdpr {
font-size: 6pt;
}

1
mamweb/templates/admin/base_site.html

@ -4,7 +4,6 @@
{% block extrahead %}
<link rel="shortcut icon" href="{% static 'favicon.ico' %}" type="image/x-icon">
<script src="{% static 'js/jquery-1.11.1.js' %}"></script>
{% include 'autocomplete_light/static.html' %}
{% endblock %}
{% block branding %}

24
mamweb/templates/base.html

@ -27,8 +27,8 @@
}
});
</script>
<script type="text/javascript"
src="https://cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML">
<script type="text/javascript" async
src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.6/MathJax.js?config=TeX-AMS-MML_HTMLorMML">
</script>
{# script specifický pro stránku #}
@ -97,6 +97,26 @@
<div class='row content'>
{% sitetree_menu from "main_menu" include "trunk" %}
{#
{% for item in menu_top %}
<li class="{% if item.selected %} active {% endif %}">
<a href="{{ item.url }}"> <i class="{{ item.icon_class }}"></i> {{ item.name }}</a>
</li>
{% if item.submenu %}
<ul>
{% for menu in item.submenu %}
<li class="{% if menu.selected %} active {% endif %}">
<a href="{{ menu.url }}">{{ menu.name }}</a>
</li>
{% endfor %}
</ul>
{% endif %}
{% endfor %}
#}
<div class='col-md-12'>
{% block content %}
{% endblock content %}

1
mamweb/urls.py

@ -11,7 +11,6 @@ urlpatterns = [
# Admin a nastroje
path('admin/', admin.site.urls), # NOQA
path('ckeditor/', include('ckeditor_uploader.urls')),
path('autocomplete/', include('autocomplete_light.urls')),
# Seminarova aplikace (ma vlastni podadresare)
path('', include('seminar.urls')),

3
requirements.txt

@ -22,11 +22,12 @@ django-solo
django-ckeditor
django-flat-theme
django-taggit
django-autocomplete-light==2.3.6
django-autocomplete-light
django-crispy-forms
django-imagekit
django-polymorphic
django-sitetree
django_reverse_admin
# Comments
akismet==1.0.1

173
seminar/admin.py

@ -1,36 +1,171 @@
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
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)
admin.site.register(m.Soustredeni)
admin.site.register(m.Problem)
admin.site.register(m.Tema)
admin.site.register(m.Clanek)
@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
child_models = [
m.Tema,
m.Clanek,
m.Uloha,
]
@admin.register(m.Tema)
class TemaAdmin(PolymorphicChildModelAdmin):
base_model = m.Tema
show_in_index = True
@admin.register(m.Clanek)
class ClanekAdmin(PolymorphicChildModelAdmin):
base_model = m.Clanek
show_in_index = True
@admin.register(m.Uloha)
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.Uloha)
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)
admin.site.register(m.TreeNode)
admin.site.register(m.RocnikNode)
admin.site.register(m.CisloNode)
admin.site.register(m.MezicisloNode)
admin.site.register(m.TemaVCisleNode)
admin.site.register(m.KonferaNode)
admin.site.register(m.ClanekNode)
admin.site.register(m.UlohaZadaniNode)
admin.site.register(m.PohadkaNode)
admin.site.register(m.UlohaVzorakNode)
admin.site.register(m.TextNode)
# Polymorfismus pro stromy
# TODO: Inlines podle https://django-polymorphic.readthedocs.io/en/stable/admin.html
@admin.register(m.TreeNode)
class TreeNodeAdmin(PolymorphicParentModelAdmin):
base_model = m.TreeNode
child_models = [
m.RocnikNode,
m.CisloNode,
m.MezicisloNode,
m.TemaVCisleNode,
m.KonferaNode,
m.ClanekNode,
m.UlohaZadaniNode,
m.PohadkaNode,
m.UlohaVzorakNode,
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
show_in_index = True
@admin.register(m.CisloNode)
class CisloNodeAdmin(PolymorphicChildModelAdmin):
base_model = m.CisloNode
show_in_index = True
@admin.register(m.MezicisloNode)
class MezicisloNodeAdmin(PolymorphicChildModelAdmin):
base_model = m.MezicisloNode
show_in_index = True
@admin.register(m.TemaVCisleNode)
class TemaVCisleNodeAdmin(PolymorphicChildModelAdmin):
base_model = m.TemaVCisleNode
show_in_index = True
@admin.register(m.KonferaNode)
class KonferaNodeAdmin(PolymorphicChildModelAdmin):
base_model = m.KonferaNode
show_in_index = True
@admin.register(m.ClanekNode)
class ClanekNodeAdmin(PolymorphicChildModelAdmin):
base_model = m.ClanekNode
show_in_index = True
@admin.register(m.UlohaZadaniNode)
class UlohaZadaniNodeAdmin(PolymorphicChildModelAdmin):
base_model = m.UlohaZadaniNode
show_in_index = True
@admin.register(m.PohadkaNode)
class PohadkaNodeAdmin(PolymorphicChildModelAdmin):
base_model = m.PohadkaNode
show_in_index = True
@admin.register(m.UlohaVzorakNode)
class UlohaVzorakNodeAdmin(PolymorphicChildModelAdmin):
base_model = m.UlohaVzorakNode
show_in_index = True
@admin.register(m.TextNode)
class TextNodeAdmin(PolymorphicChildModelAdmin):
base_model = m.TextNode
show_in_index = True
admin.site.register(m.Nastaveni)
admin.site.register(m.Novinky)

0
seminar/autocomplete_light_registry.py → seminar/autocomplete_light_registry.py.old

255
seminar/forms.py

@ -1,6 +1,257 @@
from django import forms
from dal import autocomplete
from django.core.exceptions import ObjectDoesNotExist
from django.contrib.auth.models import User
from .models import Skola, Resitel, Osoba, Problem
import seminar.models as m
from datetime import date
import logging
class LoginForm(forms.Form):
username = forms.CharField(label='Přihlašovací jméno',
max_length=256,
required=True)
password = forms.CharField(
label='Heslo',
max_length=256,
required=True,
widget=forms.PasswordInput())
class PrihlaskaForm(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=True,
widget=forms.PasswordInput())
password_check = forms.CharField(
label='Ověření hesla',
max_length=256,
required=True,
widget=forms.PasswordInput())
jmeno = forms.CharField(label='Jméno', max_length=256, required=True)
prijmeni = forms.CharField(label='Příjmení', max_length=256, required=True)
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'))
class ProfileEditForm(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'))
class VlozReseniForm(forms.Form):
#FIXME jen podproblémy daného problému
problem = forms.ModelChoiceField(label='Problém',queryset=m.Problem.objects.all())
# to_field_name
#problem = models.ManyToManyField(Problem, verbose_name='problém', help_text='Problém',
# through='Hodnoceni')
# FIXME pridat vice resitelu
resitel = forms.ModelChoiceField(label="Řešitel",
queryset=Resitel.objects.all(),
widget=autocomplete.ModelSelect2(
url='autocomplete_resitel',
attrs = {'data-placeholder--id': '-1',
'data-placeholder--text' : '---',
'data-allow-clear': 'true'})
)
#resitele = models.ManyToManyField(Resitel, verbose_name='autoři řešení',
# help_text='Seznam autorů řešení', through='Reseni_Resitele')
cas_doruceni = forms.DateField(label="Čas doručení")
#cas_doruceni = models.DateTimeField('čas_doručení', default=timezone.now, blank=True)
forma = forms.ChoiceField(label="Forma řešení",choices = m.Reseni.FORMA_CHOICES)
#forma = models.CharField('forma řešení', max_length=16, choices=FORMA_CHOICES, blank=False,
# default=FORMA_EMAIL)
poznamka = forms.CharField(label='Neveřejná poznámka')
#poznamka = models.TextField('neveřejná poznámka', blank=True,
# help_text='Neveřejná poznámka k řešení (plain text)')
#TODO body do cisla
#TODO prilohy
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
#self.fields['favorite_color'] = forms.ChoiceField(choices=[(color.id, color.name) for color in Resitel.objects.all()])
class NameForm(forms.Form):
your_name = forms.CharField(label='Your name', max_length=100)

20
seminar/management/commands/nukedb.py

@ -0,0 +1,20 @@
from mamweb.settings import INSTALLED_APPS
from django.core.management.base import BaseCommand, CommandError
from django.core.management import call_command
class Command(BaseCommand):
help = "Odmigruje všechny moduly (i.e. smaže všechny tabulky, ale databázi nechá)"
def add_arguments(self, parser):
# TODO: --force (makat a neblábolit)
pass
def handle(self, *args, **options):
# TODO: zeptat se
for app in INSTALLED_APPS:
app = app.split('.')[-1]
try:
call_command('migrate', app, 'zero')
except CommandError:
# app nemá migrace (aspoň typicky)
pass
call_command('showmigrations')

15
seminar/management/commands/testdata.py

@ -17,12 +17,25 @@ User = django.contrib.auth.get_user_model()
class Command(BaseCommand):
help = "Clear database and load testing data."
def add_arguments(self, parser):
parser.add_argument(
'--no-clean',
action='store_true',
help='Změny se provedou v aktuální DB, ne v čisté. Aktuální DB se nezachová. (jen k debugování)',
)
parser.add_argument(
'--no-migrate',
action='store_true',
help='Neprovádět migrace před generováním testovacích dat (jen k debugování)',
)
def handle(self, *args, **options):
assert settings.DEBUG == True
dbfile = settings.DATABASES['default']['NAME']
if os.path.exists(dbfile):
if os.path.exists(dbfile) and not options['no_clean']:
os.rename(dbfile, dbfile + '.old')
self.stderr.write('Stara databaze prejmenovana na "%s"' % (dbfile + '.old'))
if not options['no_migrate']:
call_command('migrate', no_input=True)
self.stdout.write('Vytvarim uzivatele "admin" (heslo "admin") a pseudo-nahodna data ...')
create_test_data(size=8)

31
seminar/migrations/0065_treenode_polymorphic_ctype.py

@ -0,0 +1,31 @@
# Generated by Django 2.2.4 on 2019-08-13 19:36
from django.db import migrations, models
import django.db.models.deletion
def vyrob_treenodum_ctypes(apps, schema_editor):
# Kód zkopírovaný z dokumentace: https://django-polymorphic.readthedocs.io/en/stable/migrating.html
# XXX: Nevím, jestli se tohle náhodou nemělo spustit na všech childech (jen/i)
TreeNode = apps.get_model('seminar', 'TreeNode')
ContentType = apps.get_model('contenttypes', 'ContentType')
new_ct = ContentType.objects.get_for_model(TreeNode)
TreeNode.objects.filter(polymorphic_ctype__isnull=True).update(polymorphic_ctype=new_ct)
class Migration(migrations.Migration):
dependencies = [
('contenttypes', '0002_remove_content_type_name'),
('seminar', '0064_auto_20190610_2358'),
]
operations = [
migrations.AddField(
model_name='treenode',
name='polymorphic_ctype',
field=models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='polymorphic_seminar.treenode_set+', to='contenttypes.ContentType'),
),
migrations.RunPython(vyrob_treenodum_ctypes, migrations.RunPython.noop),
]

29
seminar/migrations/0066_problem_polymorphic_ctype.py

@ -0,0 +1,29 @@
# Generated by Django 2.2.4 on 2019-08-13 19:45
from django.db import migrations, models
import django.db.models.deletion
def vyrob_problemum_ctypes(apps, schema_editor):
# Kód zkopírovaný z dokumentace: https://django-polymorphic.readthedocs.io/en/stable/migrating.html
# XXX: Nevím, jestli se tohle náhodou nemělo spustit na všech childech (jen/i)
Problem = apps.get_model('seminar', 'Problem')
ContentType = apps.get_model('contenttypes', 'ContentType')
new_ct = ContentType.objects.get_for_model(Problem)
Problem.objects.filter(polymorphic_ctype__isnull=True).update(polymorphic_ctype=new_ct)
class Migration(migrations.Migration):
dependencies = [
('contenttypes', '0002_remove_content_type_name'),
('seminar', '0065_treenode_polymorphic_ctype'),
]
operations = [
migrations.AddField(
model_name='problem',
name='polymorphic_ctype',
field=models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='polymorphic_seminar.problem_set+', to='contenttypes.ContentType'),
),
migrations.RunPython(vyrob_problemum_ctypes, migrations.RunPython.noop),
]

18
seminar/migrations/0067_auto_20190814_0805.py

@ -0,0 +1,18 @@
# Generated by Django 2.2.4 on 2019-08-14 06:05
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('seminar', '0066_problem_polymorphic_ctype'),
]
operations = [
migrations.AlterField(
model_name='konfera',
name='nazev',
field=models.CharField(help_text='Název konfery', max_length=100, verbose_name='název konfery'),
),
]

107
seminar/migrations/0068_treenode_nazev.py

@ -0,0 +1,107 @@
# Generated by Django 2.2.5 on 2019-09-26 19:35
from django.db import migrations, models
# Migrace nejspíš neumí volat metody modelů:
# https://stackoverflow.com/questions/28777338/django-migrations-runpython-not-able-to-call-model-methods#37685925
def fix_RocnikNode_names(apps,schema_editor):
Objects = apps.get_model('seminar', 'RocnikNode')
for obj in Objects.objects.all():
obj.nazev = str(obj.rocnik)+" (RocnikNode)"
obj.save()
def fix_CisloNode_names(apps,schema_editor):
Objects = apps.get_model('seminar', 'CisloNode')
for obj in Objects.objects.all():
obj.nazev = str(obj.cislo)+" (CisloNode)"
obj.save()
def fix_MezicisloNode_names(apps,schema_editor):
Objects = apps.get_model('seminar', 'MezicisloNode')
for obj in Objects.objects.all():
if obj.prev:
if (obj.prev.get_real_instance_class() != CisloNode and
obj.prev.get_real_instance_class() != MezicisloNode):
raise ValueError("Předchůdce není číslo!")
posledni = obj.prev.cislo
obj.nazev = "Mezičíslo po čísle"+str(posledni)+" (MezicisloNode)"
elif obj.root:
if obj.root.get_real_instance_class() != RocnikNode:
raise ValueError("Kořen stromu není ročník!")
rocnik = obj.root.rocnik
obj.nazev = "První mezičíslo ročníku "+" (MezicisloNode)"
else:
print("!!!!! Nějaké neidentifikované mezičíslo !!!!!")
obj.nazev = "Neidentifikovatelné mezičíslo! (MezicisloNode)"
obj.save()
def fix_TemaVCisleNode_names(apps,schema_editor):
Objects = apps.get_model('seminar', 'TemaVCisleNode')
for obj in Objects.objects.all():
obj.nazev = str(obj.tema)+" (TemaVCisleNode)"
obj.save()
def fix_KonferaNode_names(apps,schema_editor):
Objects = apps.get_model('seminar', 'KonferaNode')
for obj in Objects.objects.all():
obj.nazev = str(obj.konfera)+" (KonferaNode)"
obj.save()
def fix_ClanekNode_names(apps,schema_editor):
Objects = apps.get_model('seminar', 'ClanekNode')
for obj in Objects.objects.all():
obj.nazev = str(obj.clanek)+" (ClanekNode)"
obj.save()
def fix_UlohaZadaniNode_names(apps,schema_editor):
Objects = apps.get_model('seminar', 'UlohaZadaniNode')
for obj in Objects.objects.all():
obj.nazev = str(obj.uloha)+" (UlohaZadaniNode)"
obj.save()
def fix_PohadkaNode_names(apps,schema_editor):
Objects = apps.get_model('seminar', 'PohadkaNode')
for obj in Objects.objects.all():
obj.nazev = str(obj.pohadka)+" (PohadkaNode)"
obj.save()
def fix_UlohaVzorakNode_names(apps,schema_editor):
Objects = apps.get_model('seminar', 'UlohaVzorakNode')
for obj in Objects.objects.all():
obj.nazev = str(obj.uloha)+" (UlohaVzorakNode)"
obj.save()
def fix_TextNode_names(apps,schema_editor):
Objects = apps.get_model('seminar', 'TextNode')
for obj in Objects.objects.all():
obj.nazev = str(obj.text)+" (TextNode)"
obj.save()
def fix_all_names(apps,schema_editor):
fix_RocnikNode_names(apps,schema_editor)
fix_CisloNode_names(apps,schema_editor)
fix_MezicisloNode_names(apps,schema_editor)
fix_TemaVCisleNode_names(apps,schema_editor)
fix_KonferaNode_names(apps,schema_editor)
fix_ClanekNode_names(apps,schema_editor)
fix_UlohaZadaniNode_names(apps,schema_editor)
fix_PohadkaNode_names(apps,schema_editor)
fix_UlohaVzorakNode_names(apps,schema_editor)
fix_TextNode_names(apps,schema_editor)
class Migration(migrations.Migration):
dependencies = [
('seminar', '0067_auto_20190814_0805'),
]
operations = [
migrations.AddField(
model_name='treenode',
name='nazev',
field=models.TextField(help_text='Tento název se zobrazuje v nabídkách pro výběr vhodného TreeNode', null=True, verbose_name='název tohoto node'),
),
migrations.RunPython(fix_all_names),
]

28
seminar/migrations/0069_auto_20191120_2115.py

@ -0,0 +1,28 @@
# Generated by Django 2.2.7 on 2019-11-20 20:15
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('seminar', '0068_treenode_nazev'),
]
operations = [
migrations.AlterModelOptions(
name='cislo',
options={'ordering': ['-rocnik__rocnik', '-poradi'], 'verbose_name': 'Číslo', 'verbose_name_plural': 'Čísla'},
),
migrations.RenameField(
model_name='cislo',
old_name='cislo',
new_name='poradi',
),
migrations.AlterField(
model_name='problem',
name='nadproblem',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='podproblem', to='seminar.Problem', verbose_name='nadřazený problém'),
),
]

23
seminar/migrations/0070_auto_20191120_2357.py

@ -0,0 +1,23 @@
# Generated by Django 2.2.7 on 2019-11-20 22:57
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('seminar', '0069_auto_20191120_2115'),
]
operations = [
migrations.AddField(
model_name='tema',
name='abstrakt',
field=models.TextField(blank=True, verbose_name='Abstrakt na rozcestník'),
),
migrations.AddField(
model_name='tema',
name='obrazek',
field=models.ImageField(null=True, upload_to='', verbose_name='Obrázek na rozcestník'),
),
]

17
seminar/migrations/0071_remove_nastaveni_aktualni_rocnik.py

@ -0,0 +1,17 @@
# Generated by Django 2.2.7 on 2019-11-21 17:38
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('seminar', '0070_auto_20191120_2357'),
]
operations = [
migrations.RemoveField(
model_name='nastaveni',
name='aktualni_rocnik',
),
]

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

288
seminar/models.py

@ -21,10 +21,11 @@ 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
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
class SeminarModelBase(models.Model):
@ -129,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.
@ -352,7 +364,7 @@ class Rocnik(SeminarModelBase):
def verejna_cisla(self):
vc = [c for c in self.cisla.all() if c.verejne()]
vc.sort(key=lambda c: c.cislo)
vc.sort(key=lambda c: c.poradi)
return vc
def posledni_verejne_cislo(self):
@ -361,7 +373,7 @@ class Rocnik(SeminarModelBase):
def verejne_vysledkovky_cisla(self):
vc = list(self.cisla.filter(verejna_vysledkovka=True))
vc.sort(key=lambda c: c.cislo)
vc.sort(key=lambda c: c.poradi)
return vc
def posledni_zverejnena_vysledkovka_cislo(self):
@ -383,10 +395,18 @@ class Rocnik(SeminarModelBase):
cache.set(name, c, 300)
return c
def save(self, *args, **kwargs):
super().save(*args, **kwargs)
# *Node.save() aktualizuje název *Nodu.
try:
self.rocniknode.save()
except ObjectDoesNotExist:
# Neexistující *Node nemá smysl aktualizovat.
pass
def cislo_pdf_filename(self, filename):
rocnik = str(self.rocnik.rocnik)
return os.path.join('cislo', 'pdf', rocnik, '{}-{}.pdf'.format(rocnik, self.cislo))
return os.path.join('cislo', 'pdf', rocnik, '{}-{}.pdf'.format(rocnik, self.poradi))
@reversion.register(ignore_duplicates=True)
class Cislo(SeminarModelBase):
@ -395,7 +415,7 @@ class Cislo(SeminarModelBase):
db_table = 'seminar_cisla'
verbose_name = 'Číslo'
verbose_name_plural = 'Čísla'
ordering = ['-rocnik__rocnik', '-cislo']
ordering = ['-rocnik__rocnik', '-poradi']
# Interní ID
id = models.AutoField(primary_key = True)
@ -403,7 +423,7 @@ class Cislo(SeminarModelBase):
rocnik = models.ForeignKey(Rocnik, verbose_name='ročník', related_name='cisla',
db_index=True,on_delete=models.PROTECT)
cislo = models.CharField('název čísla', max_length=32, db_index=True,
poradi = models.CharField('název čísla', max_length=32, db_index=True,
help_text='Většinou jen "1", vyjímečně "7-8", lexikograficky určuje pořadí v ročníku!')
datum_vydani = models.DateField('datum vydání', blank=True, null=True,
@ -436,20 +456,20 @@ class Cislo(SeminarModelBase):
# CisloNode
def kod(self):
return '%s.%s' % (self.rocnik.rocnik, self.cislo)
return '%s.%s' % (self.rocnik.rocnik, self.poradi)
kod.short_description = 'Kód čísla'
def __str__(self):
# Potenciální DB HOG, pokud by se ročník necachoval
r = Rocnik.cached_rocnik(self.rocnik_id)
return '{}.{}'.format(r.rocnik, self.cislo)
return '{}.{}'.format(r.rocnik, self.poradi)
def verejne(self):
return self.verejne_db
verejne.boolean = True
def verejne_url(self):
return reverse('seminar_cislo', kwargs={'rocnik': self.rocnik.rocnik, 'cislo': self.cislo})
return reverse('seminar_cislo', kwargs={'rocnik': self.rocnik.rocnik, 'cislo': self.poradi})
def nasledujici(self):
"Vrací None, pokud je toto poslední"
@ -471,11 +491,20 @@ class Cislo(SeminarModelBase):
def get(cls, rocnik, cislo):
try:
r = Rocnik.objects.get(rocnik=rocnik)
c = r.cisla.get(cislo=cislo)
c = r.cisla.get(poradi=cislo)
except ObjectDoesNotExist:
return None
return c
def save(self, *args, **kwargs):
super().save(*args, **kwargs)
# *Node.save() aktualizuje název *Nodu.
try:
self.cislonode.save()
except ObjectDoesNotExist:
# Neexistující *Node nemá smysl aktualizovat.
pass
@reversion.register(ignore_duplicates=True)
class Organizator(SeminarModelBase):
# zmena dedicnosti z models.Model na SeminarModelBase, potencialni vznik bugu
@ -583,7 +612,8 @@ class Soustredeni(SeminarModelBase):
@reversion.register(ignore_duplicates=True)
class Problem(SeminarModelBase):
# Pozor na následující řádek. *Nekrmit, asi kouše!*
class Problem(SeminarModelBase,PolymorphicModel):
class Meta:
# Není abstraktní, protože se na něj jinak nedají dělat ForeignKeys.
@ -601,11 +631,11 @@ class Problem(SeminarModelBase):
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',
related_name='nadproblem_%(class)s', null=True, blank=True,
related_name='podproblem', null=True, blank=True,
on_delete=models.SET_NULL)
STAV_NAVRH = 'navrh'
@ -698,6 +728,9 @@ class Tema(Problem):
rocnik = models.ForeignKey(Rocnik, verbose_name='ročník',blank=True, null=True,
on_delete=models.PROTECT)
abstrakt = models.TextField('Abstrakt na rozcestník', blank=True)
obrazek = models.ImageField('Obrázek na rozcestník', null=True)
def kod_v_rocniku(self):
if self.stav == 'zadany':
if self.nadproblem:
@ -705,6 +738,12 @@ class Tema(Problem):
return "t{}".format(self.kod)
return '<Není zadaný>'
def save(self, *args, **kwargs):
super().save(*args, **kwargs)
# *Node.save() aktualizuje název *Nodu.
for tvcn in self.temavcislenode_set.all():
tvcn.save()
class Clanek(Problem):
class Meta:
db_table = 'seminar_clanky'
@ -725,6 +764,15 @@ class Clanek(Problem):
return "c{}".format(self.kod)
return '<Není zadaný>'
def save(self, *args, **kwargs):
super().save(*args, **kwargs)
# *Node.save() aktualizuje název *Nodu.
try:
self.claneknode.save()
except ObjectDoesNotExist:
# Neexistující *Node nemá smysl aktualizovat.
pass
class Text(SeminarModelBase):
class Meta:
db_table = 'seminar_texty'
@ -742,7 +790,16 @@ class Text(SeminarModelBase):
# obrázky mají návaznost opačným směrem (vazba z druhé strany)
def save(self, *args, **kwargs):
super().save(*args, **kwargs)
# *Node.save() aktualizuje název *Nodu.
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:
@ -770,12 +827,26 @@ class Uloha(Problem):
def kod_v_rocniku(self):
if self.stav == 'zadany':
name="{}.u{}".format(self.cislo_zadani.cislo,self.kod)
name="{}.u{}".format(self.cislo_zadani.poradi,self.kod)
if self.nadproblem:
return self.nadproblem.kod_v_rocniku()+name
return name
return '<Není zadaný>'
def save(self, *args, **kwargs):
super().save(*args, **kwargs)
# *Node.save() aktualizuje název *Nodu.
try:
self.ulohazadaninode.save()
except ObjectDoesNotExist:
# Neexistující *Node nemá smysl aktualizovat.
pass
try:
self.ulohavzoraknode.save()
except ObjectDoesNotExist:
# Neexistující *Node nemá smysl aktualizovat.
pass
@reversion.register(ignore_duplicates=True)
class Reseni(SeminarModelBase):
@ -828,7 +899,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:
@ -856,7 +927,8 @@ class Hodnoceni(SeminarModelBase):
reseni = models.ForeignKey(Reseni, verbose_name='řešení', on_delete=models.CASCADE)
problem = models.ForeignKey(Problem, verbose_name='problém', on_delete=models.PROTECT)
problem = models.ForeignKey(Problem, verbose_name='problém',
related_name='hodnoceni', on_delete=models.PROTECT)
def __str__(self):
return "{}, {}, {}".format(self.problem, self.reseni, self.body)
@ -957,6 +1029,14 @@ class Pohadka(SeminarModelBase):
uryvek = self.text if len(self.text) < 50 else self.text[:(50-3)]+"..."
return uryvek
def save(self, *args, **kwargs):
super().save(*args, **kwargs)
# *Node.save() aktualizuje název *Nodu.
try:
self.pohadkanode.save()
except ObjectDoesNotExist:
# Neexistující *Node nemá smysl aktualizovat.
pass
@reversion.register(ignore_duplicates=True)
class Soustredeni_Ucastnici(SeminarModelBase):
@ -1020,7 +1100,7 @@ class Konfera(models.Model):
# Interní ID
id = models.AutoField(primary_key = True)
nazev = models.CharField('název konfery', max_length=40, help_text = 'Název konfery')
nazev = models.CharField('název konfery', max_length=100, help_text = 'Název konfery')
anotace = models.TextField('anotace', blank=True,
help_text='Popis, o čem bude konfera.')
@ -1067,6 +1147,15 @@ class Konfera(models.Model):
def __str__(self):
return "{}: ({})".format(self.nazev, self.soustredeni)
def save(self, *args, **kwargs):
super().save(*args, **kwargs)
# *Node.save() aktualizuje název *Nodu.
try:
self.konferanode.save()
except ObjectDoesNotExist:
# Neexistující *Node nemá smysl aktualizovat.
pass
# Vazebna tabulka. Mozna se generuje automaticky.
@reversion.register(ignore_duplicates=True)
@ -1139,12 +1228,13 @@ class Obrazek(SeminarModelBase):
help_text = 'Černobílá verze obrázku do čísla',
upload_to = 'obrazky/%Y/%m/%d/', blank=True, null=True)
class TreeNode(models.Model):
class TreeNode(PolymorphicModel):
class Meta:
db_table = "seminar_nodes_treenode"
verbose_name = "TreeNode"
verbose_name_plural = "TreeNody"
# TODO: Nechceme radši jako root vyžadovat přímo RocnikNode?
root = models.ForeignKey('TreeNode',
related_name="potomci_set",
null = True,
@ -1162,14 +1252,59 @@ class TreeNode(models.Model):
blank = True,
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) # 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")
# Slouží k debugování pro rychlé získání představy o podobě podstromu pod tímto TreeNode.
def print_tree(self,indent=0):
print("{}TreeNode({})".format(" "*indent,self.id))
# FIXME: Tady se spoléháme na to, že nedeklarovaný primární klíč se jmenuje by default 'id', což není úplně správně
print("{}{} (id: {})".format(" "*indent,self, self.id))
if self.first_child:
self.first_child.print_tree(indent=indent+2)
if self.succ:
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):
if self.nazev:
return self.nazev
else:
#TODO: logování
return "Nepojmenovaný Treenode"
def save(self, *args, **kwargs):
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:
@ -1180,6 +1315,9 @@ class RocnikNode(TreeNode):
on_delete = models.PROTECT, # Pokud chci mazat ročník, musím si Node pořešit ručně
verbose_name = "ročník")
def aktualizuj_nazev(self):
self.nazev = "RocnikNode: "+str(self.rocnik)
class CisloNode(TreeNode):
class Meta:
db_table = 'seminar_nodes_cislo'
@ -1189,12 +1327,36 @@ class CisloNode(TreeNode):
on_delete = models.PROTECT, # Pokud chci mazat číslo, musím si Node pořešit ručně
verbose_name = "číslo")
def aktualizuj_nazev(self):
self.nazev = "CisloNode: "+str(self.cislo)
def getOdkazStr(self):
return "Číslo " + str(self.cislo)
class MezicisloNode(TreeNode):
class Meta:
db_table = 'seminar_nodes_mezicislo'
verbose_name = 'Mezičíslo (Node)'
verbose_name_plural = 'Mezičísla (Node)'
def aktualizuj_nazev(self):
if self.prev:
if (self.prev.get_real_instance_class() != CisloNode and
self.prev.get_real_instance_class() != MezicisloNode):
raise ValueError("Předchůdce není číslo!")
posledni = self.prev.cislo
self.nazev = "MezicisloNode: Mezičíslo po čísle"+str(posledni)
elif self.root:
if self.root.get_real_instance_class() != RocnikNode:
raise ValueError("Kořen stromu není ročník!")
rocnik = self.root.rocnik
self.nazev = "MezicisloNode: První mezičíslo ročníku "+str(rocnik)
else:
print("!!!!! Nějaké neidentifikované mezičíslo !!!!!")
self.nazev = "MezicisloNode: Neidentifikovatelné mezičíslo!"
def getOdkazStr(self):
return "Obsah dostupný pouze na webu"
class TemaVCisleNode(TreeNode):
""" Obsahuje příspěvky k tématu v daném čísle """
class Meta:
@ -1205,6 +1367,12 @@ class TemaVCisleNode(TreeNode):
on_delete=models.PROTECT, # Pokud chci mazat téma, musím si Node pořešit ručně
verbose_name = "téma v čísle")
def aktualizuj_nazev(self):
self.nazev = "TemaVCisleNode: "+str(self.tema)
def getOdkazStr(self):
return str(self.tema)
class KonferaNode(TreeNode):
class Meta:
db_table = 'seminar_nodes_konfera'
@ -1216,6 +1384,9 @@ class KonferaNode(TreeNode):
null=True,
blank=False)
def aktualizuj_nazev(self):
self.nazev = "KonferaNode: "+str(self.konfera)
class ClanekNode(TreeNode):
class Meta:
db_table = 'seminar_nodes_clanek'
@ -1227,6 +1398,13 @@ class ClanekNode(TreeNode):
null=True,
blank=False)
def aktualizuj_nazev(self):
self.nazev = "ClanekNode: "+str(self.clanek)
def getOdkazStr(self):
return str(self.clanek)
class UlohaZadaniNode(TreeNode):
class Meta:
db_table = 'seminar_nodes_uloha_zadani'
@ -1238,6 +1416,13 @@ class UlohaZadaniNode(TreeNode):
null=True,
blank=False)
def aktualizuj_nazev(self):
self.nazev = "UlohaZadaniNode: "+str(self.uloha)
def getOdkazStr(self):
return str(self.uloha)
class PohadkaNode(TreeNode):
class Meta:
db_table = 'seminar_nodes_pohadka'
@ -1248,6 +1433,9 @@ class PohadkaNode(TreeNode):
verbose_name = "pohádka",
)
def aktualizuj_nazev(self):
self.nazev = "PohadkaNode: "+str(self.pohadka)
class UlohaVzorakNode(TreeNode):
class Meta:
db_table = 'seminar_nodes_uloha_vzorak'
@ -1259,6 +1447,13 @@ class UlohaVzorakNode(TreeNode):
null=True,
blank=False)
def aktualizuj_nazev(self):
self.nazev = "UlohaVzorakNode: "+str(self.uloha)
def getOdkazStr(self):
return str(self.uloha)
class TextNode(TreeNode):
class Meta:
db_table = 'seminar_nodes_obsah'
@ -1268,6 +1463,13 @@ class TextNode(TreeNode):
on_delete=models.PROTECT,
verbose_name = 'text')
def aktualizuj_nazev(self):
self.nazev = "TextNode: "+str(self.text)
def getOdkazStr(self):
return str(self.text)
## FIXME: Logiku přesunout do views.
#class VysledkyBase(SeminarModelBase):
#
@ -1292,7 +1494,7 @@ class TextNode(TreeNode):
#
# def __str__(self):
# return "%s: %sb (%s)".format(self.resitel.plne_jmeno(), self.body,
# str(self.cislo))
# str(self.poradi))
# # NOTE: DB zatez pri vypisu (ale nepouzivany)
@ -1342,7 +1544,7 @@ class TextNode(TreeNode):
#
# def __str__(self):
# # NOTE: DB HOG (ale nepouzivany)
# return "%s: %sb / %sb (do %s)" % (self.resitel.plne_jmeno(), self.body, self.body_celkem, str(self.cislo))
# return "%s: %sb / %sb (do %s)" % (self.resitel.plne_jmeno(), self.body, self.body_celkem, str(self.poradi))
##mozna potreba upravit
@ -1353,12 +1555,16 @@ class Nastaveni(SingletonModel):
db_table = 'seminar_nastaveni'
verbose_name = 'Nastavení semináře'
aktualni_rocnik = models.ForeignKey(Rocnik, verbose_name='aktuální ročník',
null=False, on_delete=models.PROTECT)
# aktualni_rocnik = models.ForeignKey(Rocnik, verbose_name='aktuální ročník',
# null=False, on_delete=models.PROTECT)
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
def __str__(self):
return 'Nastavení semináře'
@ -1399,3 +1605,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

1603
seminar/static/seminar/lisak.eps

File diff suppressed because it is too large

BIN
seminar/static/seminar/lisak.pdf

Binary file not shown.

32
seminar/static/seminar/prihlaska.js

@ -0,0 +1,32 @@
function addrCountryChanged(){
var stat_select = document.getElementById('id_stat');
var stat_text = document.getElementById('id_li_stat_text');
var stat = stat_select[stat_select.selectedIndex].value;
if (stat === "other"){
stat_text.style.display="block";
} else {
stat_text.style.display="none";
$('#id_stat_text').val("");
}
}
function hideSchoolTextfields(){
var skola_nazev = document.getElementById('id_li_skola_nazev');
var skola_adresa = document.getElementById('id_li_skola_adresa');
skola_nazev.style.display="none";
skola_adresa.style.display="none";
}
function schoolNotInList(){
var skola_nazev = document.getElementById('id_li_skola_nazev');
var skola_adresa = document.getElementById('id_li_skola_adresa');
// FIXME nefunguje a nevim proc (TypeError: $(...).select2 is not a function)
//var skola_select = $('#id_skola').select2();
//skola_select.val(null).trigger('change');
skola_nazev.style.display="block";
skola_adresa.style.display="block";
}
document.addEventListener("DOMContentLoaded", function(){
addrCountryChanged();
hideSchoolTextfields();
});

3
seminar/templates/seminar/archiv/obalky.tex

@ -86,7 +86,8 @@
% Tohle makro vysází samotnou obálku
\def\obalka#1#2#3#4#5#6#7{
% Horní a pravý okraj je zároveň okraj stránky, resetujeme odsazení
\includegraphics[height=2.55cm]{lisak.eps}\hskip 1 em\vbox{%
\includegraphics[height=2.55cm]{lisak.pdf}
\vbox{%
\adresaMaM}
\vskip 7.3 cm % Od oka
\hskip\toskip%

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 %}

49
seminar/templates/seminar/gdpr.html

@ -0,0 +1,49 @@
<p>
TL;DR:
K tomu, abychom mohli zpracovávat Tvá data (uložit si tvou adresu, zobrazit Tvé jméno ve výsledkové listině, opravit Tvá řešení) od Tebe potřebujeme souhlas.
Pokud se zpracováváním souhlasíš dle níže uvedených podmínek, zaškrtni políčko níže.
</p>
<div class="gdpr">
<p class="gdpr">
Získáváme od Tebe údaje vyplněné v přihlášce do semináře (jméno, příjmení, poštovní a e-mailovou adresu, školu, kterou navštěvuješ a rok maturity), případně v přihlášce na soustředění (navíc datum narození, telefonní číslo). Také uchováváme všechna řešení, která nám pošleš, a jejich hodnocení.
</p>
<p class="gdpr">
Slibujeme Ti, že Tvá osobní data nezneužijeme k ničemu, co by nesouviselo s M&amp;M nebo s dalšími aktivitami Matfyzu, a nikdy je nepředáme nikomu cizímu. Údaje využíváme k zajištění chodu semináře a také je sdílíme s ostatními propagačními akcemi Matfyzu, abychom mohli vyhodnocovat úspěšnost akcí. Pokud budeš mít zájem, budeme Ti také posílat zajímavé zprávy a novinky týkajíci se Matfyzu.
</p>
<p class="gdpr">
Veřejně vystavujeme pouze výsledkové listiny, které také uchováváme pro archivní účely. Pokud ale z nějakého důvodu nebudeš chtít mít své jméno či školu uvedené ve výsledkové listině, není problém to zařídit, napiš nám. Z tištěných materiálů samozřejmě údaje už odstranit nemůžeme.
</p>
<p class="gdpr">
Na soustředěních a dalších akcích semináře navíc pořizujeme fotografie a videozáznamy a používáme je ke zpravodajským a propagačním účelům. Pro propagační účely si od Tebe vyžádáme samostatný souhlas na začátku akce.
</p>
<p class="gdpr">
<i>Souhlas se zpracováním osobních údajů pro potřeby chodu semináře</i>
</p>
<p class="gdpr">
Tímto uděluji souhlas Univerzitě Karlově, se sídlem Ovocný trh 560/5, 116 36 Praha 1, IČO 00216208 (dále jen UK), která je správcem osobních údajů všech fakult a součástí UK, ke zpracování osobních údajů pro potřeby Korespondenčního semináře M&amp;M a Matematicko-fyzikální fakulty UK (dále jen M&amp;M a MFF UK).
</p>
<p class="gdpr">
Tento souhlas uděluji pro všechny výše uvedené osobní údaje, a to po dobu účasti v semináři a 10 let poté, a dále souhlasím s uchováváním potřebných dat pro archivní účely i po této lhůtě (vystavené výsledkové listiny aj.).
</p>
<p class="gdpr">
MFF UK tyto údaje zpracovává za účelem evidence řešitelů a účastníků M&amp;M, k zajištění celoročního fungování semináře, analýze účinnosti jednotlivých propagačních akcí MFF UK a zpravodajským účelům. Osobám, které o to projeví zájem v nastavení svého účtu, bude MFF UK také zasílat propagační materiály.
</p>
<p class="gdpr">
Údaje nebudou předány třetí osobě ani využívány k jiným účelům, než ke kterým byly poskytnuty.
</p>
<p class="gdpr">
Tento souhlas uděluji ze své vlastní a svobodné vůle a beru na vědomí, že jej mohu kdykoliv odvolat zasláním e-mailu na adresu mam@matfyz.cz. Stejně tak může být požadováno vymazání i z archivních údajů M&M, pokud to bude technicky možné. Beru na vědomí, že údaje z tištěných publikací není možné zpětně odstranit.
</p>
<p class="gdpr">
Dále máte právo:
<ul>
<li>požádat o informaci, jaké osobní údaje jsou o vás zpracovávány,
<li>požadovat opravu osobních údajů, pokud jsou neplatné nebo zastaralé,
<li>požadovat, aby nebyly vaše osobní údaje zpracovávány do doby, než bude vyřešena oprávněnost výše uvedených požadavků,
<li>požadovat, aby byly vaše osobní údaje předány jinému správci,
<li>podat stížnost u dozorového úřadu.
</p>
<p class="gdpr">
V případě jakéhokoliv dotazu nebo uplatnění svých práv můžete kontaktovat pověřence pro ochranu osobních údajů na e-mailové adrese gdpr@cuni.cz.
</p>
</div>

26
seminar/templates/seminar/login.html

@ -0,0 +1,26 @@
{% extends "seminar/zadani/base.html" %}
{% load staticfiles %}
{% block content %}
<h1>
{% block nadpis1a %}{% block nadpis1b %}
Přihlášení
{% endblock %}{% endblock %}
</h1>
<form action="{% url 'login' %}" method="post">
{% csrf_token %}
<ul class="form">
{{ form.as_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">
</form>
<a href="{% url 'reset_password' %}">Zapomněl jsem heslo</a><br>
<a href="{% url 'seminar_prihlaska' %}">Zaregistrovat</a><br>
{% endblock %}

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 %}

21
seminar/templates/seminar/org/vloz_reseni.html

@ -0,0 +1,21 @@
{% 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 %}
Vložit řešení
{% endblock %}{% endblock %}
</h1>
<form action="{% url 'seminar_vloz_reseni' %}" method="post">
{% csrf_token %}
{{form.as_p}}
<input type="submit" value="Vložit">
</form>
{% endblock %}

113
seminar/templates/seminar/prihlaska.html

@ -1,5 +1,112 @@
<form action="/prihlaska/" method="post">
{% 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 %}
Přihláška do semináře
{% endblock %}{% endblock %}
</h1>
<form action="{% url 'seminar_prihlaska' %}" method="post">
{% csrf_token %}
{{ form }}
<input type="submit" value="Submit">
{{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>
{% include "seminar/prihlaska_field.html" with field=form.password %}
</li>
<li>
{% include "seminar/prihlaska_field.html" with field=form.password_check %}
</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/gdpr.html" %}
{% include "seminar/prihlaska_field.html" with field=form.gdpr %}
</li>
<li>
{% include "seminar/prihlaska_field.html" with field=form.spam %}
</li>
</ul>
<input type="submit" value="Odeslat">
</form>
<script>
$("#id_stat").on("change",addrCountryChanged);
$("#id_skola_text_button").on("click",schoolNotInList);
</script>
{% endblock %}

4
seminar/templates/seminar/prihlaska_field.html

@ -0,0 +1,4 @@
<label class="field-label{% if field.field.required %} field-required{% endif %}" for="{{ field.id_for_label }}">{{ field.label }}:</label>
{{field}}
{% if field.help_text %}<span class="field-helptext">{{ field.help_text|safe }}</span>{% endif %}
{% if field.errors %}<span class="field-error">{{ field.errors }}</span>{% endif %}

17
seminar/templates/seminar/resitel.html

@ -0,0 +1,17 @@
{% extends "seminar/zadani/base.html" %}
{% load staticfiles %}
{% block content %}
<h1>
{% block nadpis1a %}{% block nadpis1b %}
Stránka řešitele - {{ object.osoba.jmeno }} {{ object.osoba.prijmeni }}
{% endblock %}{% endblock %}
</h1>
<a href="{% url 'logout' %}">Odhlásit se</a><br>
<a href="{% url 'seminar_resitel_edit' %}">Upravit údaje</a><br>
{% endblock %}

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

81
seminar/testutils.py

@ -1,11 +1,13 @@
# -*- coding: utf-8 -*-
import datetime
from pytz import timezone
import random
import lorem
import django.contrib.auth
from django.db import transaction
import unidecode
import logging
from seminar.models import Skola, Resitel, Rocnik, Cislo, Problem, Reseni, PrilohaReseni, Nastaveni, Soustredeni, Soustredeni_Ucastnici, Soustredeni_Organizatori, Osoba, Organizator, Prijemce, Tema, Uloha, Konfera, KonferaNode, TextNode, UlohaVzorakNode, RocnikNode, CisloNode, TemaVCisleNode, Text, Hodnoceni, UlohaZadaniNode, Novinky
@ -15,7 +17,11 @@ from django.contrib.sites.models import Site
User = django.contrib.auth.get_user_model()
zlinska = None # tohle bude speciální škola, které později dodáme kontaktní osobu
logger = logging.getLogger(__name__)
def gen_osoby(rnd, size):
logger.info('Generuji osoby (size={})...'.format(size))
jmena_m = ['Aleš', 'Tomáš', 'Martin', 'Jakub', 'Petr', 'Lukáš', 'Cyril', 'Pavel Karel']
jmena_f = ['Eva', 'Karolína', 'Zuzana', 'Sylvie', 'Iva', 'Jana', 'Marie',
'Marta Iva', 'Shu Shan']
@ -67,6 +73,8 @@ def gen_osoby(rnd, size):
def gen_skoly(): #TODO někdy to přepsat, aby jich bylo více
logger.info('Generuji školy...')
skoly = []
prvnizs = Skola.objects.create(mesto='Praha', stat='CZ', psc='101 00',
ulice='Krátká 5', nazev='První ZŠ', je_zs=True, je_ss=False)
@ -90,22 +98,26 @@ def gen_skoly(): #TODO někdy to přepsat, aby jich bylo více
return skoly
def gen_resitele(rnd, osoby, skoly):
logger.info('Generuji řešitele...')
resitele = []
for os in osoby:
rand = rnd.randint(0, 8)
if not (rand % 8 == 0):
resitele.append(Resitel.objects.create(osoba=os, skola=rnd.choice(skoly),
rok_maturity=rnd.randint(2019, 2029),
zasilat=rnd.choice(Resitel.ZASILAT_CHOICES)))
zasilat=rnd.choice(Resitel.ZASILAT_CHOICES)[0]))
return resitele
def gen_prijemci(rnd, osoby, kolik=10):
logger.info('Generuji příjemce (kolik={})...'.format(kolik))
prijemci = []
for i in rnd.sample(osoby, kolik):
prijemci.append(Prijemce.objects.create(osoba=i))
return prijemci
def gen_organizatori(rnd, osoby, last_rocnik, users):
logger.info('Generuji organizátory...')
organizatori = []
@ -118,9 +130,18 @@ def gen_organizatori(rnd, osoby, last_rocnik, users):
rand = rnd.randint(0, 8)
if (rand % 8 == 0):
pusobnost = rnd.randint(1, last_rocnik)
od = datetime.date(1993 + pusobnost, rnd.randint(1, 12), rnd.randint(1, 28))
do = datetime.date(od.year + rnd.randint(1, 6), rnd.randint(1, 12),
rnd.randint(1, 28))
od = datetime.datetime(
year=1993 + pusobnost,
month=rnd.randint(1, 12),
day=rnd.randint(1, 28),
tzinfo=timezone('CET'),
)
do = datetime.datetime(
year=od.year + rnd.randint(1, 6),
month=rnd.randint(1, 12),
day=rnd.randint(1, 28),
tzinfo=timezone('CET'),
)
#aktualni organizatori jeste nemaji vyplnene organizuje_do
#popis orga
@ -136,6 +157,8 @@ def gen_organizatori(rnd, osoby, last_rocnik, users):
return organizatori
def gen_ulohy_do_cisla(rnd, organizatori, resitele, rocnik_cisla, rocniky, size):
logger.info('Generuji úlohy do čísla (size={})...'.format(size))
# ulohy resene v cisle
jaka = ["Šachová", "Černá", "Větrná", "Dlouhá", "Křehká", "Rychlá",
"Zákeřná", "Fyzikální"]
@ -158,7 +181,14 @@ def gen_ulohy_do_cisla(rnd, organizatori, resitele, rocnik_cisla, rocniky, size)
for rocnik in rocniky:
k+=1
cisla = rocnik_cisla[k-1]
for ci in range(3, len(cisla)+1):
for ci in range(3, len(cisla)+1): # pro všechna čísla
resitele_size = round(7/8 * 30 * size) # očekáváný celkový počet řešitelů
poc_res = rnd.randint(round(resitele_size/8), round(3*resitele_size/4))
# dané číslo řeší něco mezi osminou a tříčtvrtinou všech řešitelů
# (náhodná hausnumera, možno změnit)
# účelem je, aby se řešení generovala z menší množiny řešitelů a tedy
# bylo více řešení od jednoho řešitele daného čísla
resitele_cisla = rnd.sample(resitele, poc_res)
for pi in range(1, ((size + 1) // 2) + 1):
poc_op = rnd.randint(1, 4) # počet opravovatelů
@ -199,6 +229,7 @@ def gen_ulohy_do_cisla(rnd, organizatori, resitele, rocnik_cisla, rocniky, size)
p.ulohazadaninode = uloha_zadani
otec_syn(cisla[ci-2-1].cislonode, uloha_zadani)
# generování vzorového textu
text_vzoraku = Text.objects.create(
na_web = rnd.choice(reseni),
do_cisla = rnd.choice(reseni)
@ -211,17 +242,18 @@ def gen_ulohy_do_cisla(rnd, organizatori, resitele, rocnik_cisla, rocniky, size)
p.opravovatele.set(rnd.sample(organizatori,poc_op))
p.save()
# reseni ulohy
# generování řešení
poc_reseni = rnd.randint(size // 2, size * 2)
#poc_resitel = rnd.randint(1, 3) <- k čemu je himbajs tahle proměnná?
# vybereme nahodny vzorek resitelu o delce poctu reseni
# (nebo skoro vsechny resitele, pokud jich je mene nez pocet reseni)
# generujeme náhodný počet řešení
for ri in range(poc_reseni):
res_vyber = rnd.sample(resitele, rnd.randint(1, 5))
if rnd.randint(1, 10) == 6:
# cca desetina řešení od více řešitelů
res_vyber = rnd.sample(resitele_cisla, rnd.randint(2, 5))
else:
res_vyber = rnd.sample(resitele_cisla, 1)
res = Reseni.objects.create(forma=rnd.choice(Reseni.FORMA_CHOICES)[0])
# problem a resitele přiřadíme později, ManyToManyField
# se nedá vyplnit v create()
res = Reseni.objects.create(forma=rnd.choice(Reseni.FORMA_CHOICES))
#res.save() <- asi smazat
res.resitele.set(res_vyber)
res.save()
hod = Hodnoceni.objects.create(
@ -234,6 +266,8 @@ def gen_ulohy_do_cisla(rnd, organizatori, resitele, rocnik_cisla, rocniky, size)
return
def gen_soustredeni(rnd, resitele, organizatori):
logger.info('Generuji soustředění...')
soustredeni = []
for _ in range(1, 10): #FIXME Tu range si změňte jak chcete, nevím, co přesně znamená size (asi Anet?)
datum_zacatku=datetime.date(rnd.randint(2000, 2020), rnd.randint(1, 12), rnd.randint(1, 28))
@ -257,6 +291,8 @@ def gen_soustredeni(rnd, resitele, organizatori):
return soustredeni
def gen_rocniky(last_rocnik, size):
logger.info('Generuji ročníky (size={})...'.format(size))
rocniky = []
node = None
for ri in range(min(last_rocnik - size, 1), last_rocnik + 1):
@ -268,6 +304,8 @@ def gen_rocniky(last_rocnik, size):
return rocniky
def gen_konfery(size, rnd, organizatori, resitele, soustredeni):
logger.info('Generuji konfery (size={})...'.format(size))
konfery = []
for _ in range(1, size): #FIXME Tu range si změňte jak chcete, nevím, co přesně znamená size (asi Anet?)
# Anet: size je parametr udávající velikost testovacích dat a dá se pomocí ní škálovat,
@ -292,6 +330,8 @@ def gen_konfery(size, rnd, organizatori, resitele, soustredeni):
return konfery
def gen_cisla(rnd, rocniky):
logger.info('Generuji čísla...')
rocnik_cisla = []
for rocnik in rocniky:
otec = True
@ -317,7 +357,7 @@ def gen_cisla(rnd, rocniky):
cislo = Cislo.objects.create(
rocnik = rocnik,
cislo = str(ci),
poradi = str(ci),
datum_vydani=vydano,
datum_deadline=deadline,
verejne_db=True
@ -335,6 +375,8 @@ def gen_cisla(rnd, rocniky):
return rocnik_cisla
def gen_temata(rnd, rocniky, rocnik_cisla, organizatori):
logger.info('Generuji témata...')
jake = ["Hravé", "Fyzikální", "Nejlepší", "Totálně masakrální",
"Šokující", "Magnetické", "Modré", "Překvapivé",
"Plasmatické", "Novoroční"]
@ -361,8 +403,9 @@ def gen_temata(rnd, rocniky, rocnik_cisla, organizatori):
garant=rnd.choice(organizatori),
kod=str(n),
# atributy třídy Téma
tema_typ=rnd.choice(Tema.TEMA_CHOICES),
rocnik=rocnik
tema_typ=rnd.choice(Tema.TEMA_CHOICES)[0],
rocnik=rocnik,
abstrakt = "Abstrakt tematka {}".format(n)
)
konec_tematu = min(rnd.randint(ci, 7), len(cisla))
for i in range(ci, konec_tematu+1):
@ -378,6 +421,8 @@ def gen_temata(rnd, rocniky, rocnik_cisla, organizatori):
def gen_ulohy_k_tematum(rnd, rocniky, rocnik_cisla, rocnik_temata, organizatori):
logger.info('Generuji úlohy k tématům...')
# ulohy resene v cisle
jaka = ["Šachová", "Černá", "Větrná", "Dlouhá", "Křehká", "Rychlá",
"Zákeřná", "Fyzikální"]
@ -474,6 +519,8 @@ def gen_ulohy_k_tematum(rnd, rocniky, rocnik_cisla, rocnik_temata, organizatori)
return
def gen_novinky(rnd, organizatori):
logger.info('Generuji novinky...')
jake = ["zábavné", "veselé", "dobrodružné", "skvělé"]
co = ["soustředění", "Fyziklání", "víkendové setkání"]
@ -495,6 +542,8 @@ def otec_syn(otec, syn):
@transaction.atomic
def create_test_data(size = 6, rnd = None):
logger.info('Vyrábím testovací data (size={})...'.format(size))
assert size >= 1
# pevna pseudo-nahodnost
rnd = rnd or random.Random(x=42)
@ -575,5 +624,5 @@ def create_test_data(size = 6, rnd = None):
# obecné nastavení semináře, musí být už přidané ročníky a čísla, jinak se nastaví divně
nastaveni = Nastaveni.objects.create(aktualni_rocnik = Rocnik.objects.last(),
nastaveni = Nastaveni.objects.create(
aktualni_cislo = Cislo.objects.all()[1])

27
seminar/urls.py

@ -3,10 +3,14 @@ from django.contrib.auth.decorators import user_passes_test
from . import views, export
from .utils import staff_member_required
from django.views.generic.base import RedirectView
from django.contrib.auth import views as auth_views
staff_member_required = user_passes_test(lambda u: u.is_staff)
urlpatterns = [
path('aktualni/temata/', views.TemataRozcestnikView),
path('<int:rocnik>/t<int:tematko>/', views.TematkoView),
# REDIRECTy
path('jak-resit/', RedirectView.as_view(url='/co-je-MaM/jak-resit/')),
@ -85,19 +89,36 @@ urlpatterns = [
path('stav',
staff_member_required(views.StavDatabazeView), name='stav_databaze'),
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',
staff_member_required(views.texDownloadView), name='seminar_tex_download'),
path('soustredeni/<int: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),
name='seminar_tex_upload'
),
path('prihlaska/',views.get_name),
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/login/', views.LoginView.as_view(), name='login'),
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('autocomplete/resitel/',views.ResitelAutocomplete.as_view(), name='autocomplete_resitel'),
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('temp/add_solution', views.AddSolutionView.as_view(),name='seminar_vloz_reseni'),
path('', views.TitulniStranaView.as_view(), name='titulni_strana'),
# Ceka na autocomplete v3

9
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:

654
seminar/views.py

@ -2,22 +2,31 @@
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 _
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
from .models import Problem, Cislo, Reseni, Nastaveni, Rocnik, Soustredeni, Organizator, Resitel, Novinky, Soustredeni_Ucastnici, Pohadka, Tema, Clanek
from django.views.generic.edit import FormView
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
from dal import autocomplete
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
from .forms import NameForm
from .forms import PrihlaskaForm, LoginForm, ProfileEditForm
import seminar.forms as f
from datetime import timedelta, date, datetime
from django.utils import timezone
from itertools import groupby
import tempfile
import subprocess
@ -30,6 +39,7 @@ import json
import traceback
import sys
import csv
import logging
def verejna_temata(rocnik):
@ -37,6 +47,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)
@ -67,6 +116,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):
# nastaveni = get_object_or_404(Nastaveni)
@ -143,7 +285,7 @@ class StareNovinkyView(generic.ListView):
# Organizatori
def aktivniOrganizatori(datum=date.today()):
def aktivniOrganizatori(datum=timezone.now()):
return Organizator.objects.exclude(
organizuje_do__isnull=False,
organizuje_do__lt=datum
@ -252,26 +394,115 @@ class ArchivView(generic.ListView):
context["nahledy"] = "\n".join(tags)
return context
### Výsledky
def sloupec_s_poradim(vysledky):
# počet řešitelů ve výsledkovce nad aktuálním
lepsich_resitelu = 0
# ze setřízeného(!) seznamu všech bodů vytvoří seznam s pořadími (včetně 3.-5. a pak 2 volná místa atp.)
def sloupec_s_poradim(seznam_s_body):
aktualni_poradi = 1
sloupec_s_poradim = []
poradi_l = []
# projdeme skupiny řešitelů se stejným počtem bodů
for skupina in (list(x) for _, x in groupby(vysledky, lambda x: x.body)):
# připravíme si obsahy buněk ve sloupci pořadí pro skupinu
if len(skupina) == 1:
poradi_l += ["{}.".format(lepsich_resitelu + 1)]
# je-li účastníků se stejným počtem bodů víc, pořadí (rozsah X.-Y.) je jen u prvního
# seskupíme seznam všech bodů podle hodnot
for index in range(0, len(seznam_s_body)):
# pokud je pořadí větší než číslo řádku, tak jsme vypsali větší rozsah a chceme
# vypsat už jen prázdné místo, než dojdeme na správný řádek
if (index + 1) < aktualni_poradi:
sloupec_s_poradim.append("")
continue
velikost_skupiny = 0
# zjistíme počet po sobě jdoucích stejných hodnot
while seznam_s_body[index] == seznam_s_body[index + velikost_skupiny]:
velikost_skupiny = velikost_skupiny + 1
# na konci musíme ošetřit přetečení seznamu
if (index + velikost_skupiny) > len(seznam_s_body) - 1:
break
# pokud je velikost skupiny 1, vypíšu pořadí
if velikost_skupiny == 1:
sloupec_s_poradim.append("{}.".format(aktualni_poradi))
# pokud je skupina větší, vypíšu rozsah
else:
poradi_l += [u"{}.–{}.".format(lepsich_resitelu + 1, lepsich_resitelu + len(skupina))] + [""] * (len(skupina)-1)
lepsich_resitelu += len(skupina)
#pomlcka je opravdu pomlcka v unicode!!dulezite pro vysledkovku v TeXu
sloupec_s_poradim.append("{}.–{}.".format(aktualni_poradi,
aktualni_poradi+velikost_skupiny-1))
# zvětšíme aktuální pořadí o tolik, kolik pozic bylo přeskočeno
aktualni_poradi = aktualni_poradi + velikost_skupiny
return sloupec_s_poradim
return poradi_l
# spočítá součet bodů získaných daným řešitelem za zadaný problém a všechny jeho podproblémy
def __soucet_resitele_problemu(problem, resitel, cislo, soucet):
# sečteme body za daný problém přes všechna řešení daného problému
# od daného řešitele
reseni_resitele = problem.hodnoceni_set.filter(reseni__resitele=resitel,
cislo_body=cislo)
# XXX chyba na řádku výše - řešení může mít více řešitelů, asi chceme contains
# nebo in
for r in reseni_resitele:
soucet += r.body
# a přičteme k tomu hodnocení všech podproblémů
for p in problem.podproblem.all():
# i přes jméno by to měla být množina jeho podproblémů
soucet += __soucet_resitele_problemu(p, resitel, soucet)
return soucet
# spočítá součet všech bodů ze všech podproblémů daného problému daného řešitele
def body_resitele_problemu_v_cisle(problem, resitel, cislo):
# probably FIXED: nezohledňuje číslo, do kterého se body počítají
return __soucet_resitele_problemu(problem, resitel, cislo, 0)
# vrátí list všech problémů s body v daném čísle, které již nemají nadproblém
def hlavni_problemy_cisla(cislo):
hodnoceni = cislo.hodnoceni.select_related('problem', 'reseni').all() # hodnocení, která se vážou k danému číslu
reseni = [h.reseni for h in hodnoceni]
problemy = [h.problem for h in hodnoceni]
problemy_set = set(problemy) # chceme každý problém unikátně,
problemy = (list(problemy_set)) # převedení na množinu a zpět to zaručí
# hlavní problémy čísla
# (mají vlastní sloupeček ve výsledkovce, nemají nadproblém)
hlavni_problemy = []
for p in problemy:
while not(p.nadproblem == None):
p = p.nadproblem
hlavni_problemy.append(p)
# zunikátnění
hlavni_problemy_set = set(hlavni_problemy)
hlavni_problemy = list(hlavni_problemy_set)
hlavni_problemy.sort(key=lambda k: k.kod_v_rocniku()) # setřídit podle t1, t2, c3, ...
return hlavni_problemy
def body_resitele_odjakziva(resitel):
body = 0
resitelova_hodnoceni = Hodnoceni.objects.select_related('body').all().filter(reseni_resitele=resitel)
# TODO: v radku nahore chceme _in nebo _contains
for hodnoceni in resitelova_hodnoceni:
body = body + hodnoceni.body
return body
# spočítá součet všech bodů řešitele za dané číslo
def body_resitele_v_cisle(resitel, cislo):
hlavni_problemy = hlavni_problemy_cisla(cislo)
body_resitele = 0
for h in hlavni_problemy:
body_resitele = body_resitele + body_resitele_problemu_v_cisle(h, resitel, cislo)
# TODO: je rozdíl mezi odevzdanou úlohou za 0 a tím, když řešitel nic neodevzdal
# řešit přes kontrolu velikosti množiny řešení daného problému do daného čísla?
# Tady to ale nevadí, tady se počítá součet za číslo.
return body_resitele
# spočítá součet všech bodů řešitele za daný rok (nebo jen do daného čísla včetně)
def body_resitele_v_rocniku(resitel, rocnik, do_cisla=None):
# pokud do_cisla=None, tak do posledního čísla v ročníku
# do_cisla je objekt Cislo
cisla = rocnik.cisla.all() # funkce vrátí pole objektů
# Cislo už lexikograficky setřízené, viz models
body = 0
for cislo in cisla:
if cislo.poradi == do_cisla.poradi: break
# druhá část zaručuje, že máme výsledky do daného čísla včetně
body = body + body_resitele_v_cisle(resitel, cislo)
return body
#def vysledkovka_rocniku(rocnik, jen_verejne=True):
# """Přebírá ročník (např. context["rocnik"]) a vrací výsledkovou listinu ve
@ -288,7 +519,7 @@ def sloupec_s_poradim(vysledky):
# return None
#
# #vybere vsechny vysledky z posledniho (verejneho) cisla a setridi sestupne dle bodu
# vysledky = list(cisla_v_rocniku.filter(cislo = cisla_v_rocniku[0].cislo).order_by('-body', 'resitel__prijmeni', 'resitel__jmeno').select_related('resitel'))
# vysledky = list(cisla_v_rocniku.filter(cislo = cisla_v_rocniku[0].poradi).order_by('-body', 'resitel__prijmeni', 'resitel__jmeno').select_related('resitel'))
#
# class Vysledkovka:
# def __init__(self):
@ -304,7 +535,7 @@ def sloupec_s_poradim(vysledky):
# v.poradi = poradi
# v.resitel.rocnik = v.resitel.rocnik(rocnik)
#
# verejne_vysl_odjakziva = VysledkyKCisluOdjakziva.objects.filter(cislo__rocnik=rocnik, cislo=cisla_v_rocniku[0].cislo)
# verejne_vysl_odjakziva = VysledkyKCisluOdjakziva.objects.filter(cislo__rocnik=rocnik, cislo=cisla_v_rocniku[0].poradi)
# if jen_verejne:
# verejne_vysl_odjakziva = verejne_vysl_odjakziva.filter(cislo__verejna_vysledkovka=True)
#
@ -380,16 +611,17 @@ class ProblemView(generic.DetailView):
class VysledkyResitele(object):
"""Pro daného řešitele ukládá počet bodů za jednotlivé úlohy a celkový
počet bodů za číslo."""
def __init__(self, jmeno, prijmeni):
resitel_jmeno = jmeno
resitel_prijmeni = prijmeni
body = {}
body_cislo = 0
def body_za_cislo(self):
return sum(body.values())
počet bodů za konkrétní ročník do daného čísla a za dané číslo."""
def __init__(self, resitel, cislo, rocnik):
self.resitel = resitel
self.cislo = cislo
self.body_cislo = body_resitele_v_cisle(resitel, cislo)
self.body = []
self.rocnik = rocnik
self.body_rocnik = body_resitele_v_rocniku(resitel, rocnik, cislo)
self.body_celkem_odjakziva = resitel.vsechny_body()
self.poradi = 0
class CisloView(generic.DetailView):
model = Cislo
@ -400,8 +632,8 @@ class CisloView(generic.DetailView):
if queryset is None:
queryset = self.get_queryset()
rocnik_arg = self.kwargs.get('rocnik')
cislo_arg = self.kwargs.get('cislo')
queryset = queryset.filter(rocnik__rocnik=rocnik_arg, cislo=cislo_arg)
poradi_arg = self.kwargs.get('cislo')
queryset = queryset.filter(rocnik__rocnik=rocnik_arg, poradi=poradi_arg)
try:
obj = queryset.get()
@ -410,88 +642,53 @@ class CisloView(generic.DetailView):
{'verbose_name': queryset.model._meta.verbose_name})
return obj
# spočítá součet bodů získaných daným řešitelem za zadaný problém a všechny jeho podproblémy
def __soucet_resitele_problemu(problem, resitel, soucet):
# FIXME: správně je nadproblem_(typ problemu), ale to by bylo potřeba nějak
# zjistit, jaký typ nodu to vlastně je a aplikovat to ve volání funkce
# sečteme body za daný problém přes všechna řešení daného problému
# od daného řešitele
reseni_resitele = problem.hodnoceni_set.filter(reseni_resitele__contains=resitel)
for r in reseni_resitele:
soucet += r.body
for p in problem.nadproblem_set:
# i přes jméno by to měla být množina jeho podproblémů
soucet += __soucet_resitele_problemu(p, resitel, soucet)
return soucet
def vysledky_resitele_problemu(problem, resitel, cislo):
return __soucet_resitele_problemu(problem, resitel, 0)
def get_context_data(self, **kwargs):
context = super(CisloView, self).get_context_data(**kwargs)
## TODO upravit dle nového modelu
cislo = context['cislo']
hodnoceni = cislo.hodnoceni_set # hodnocení, která se vážou k danému číslu
reseni = [h.reseni for h in hodnoceni]
problemy = [h.problem for h in hodnoceni]
problemy_set = set(problemy) # chceme každý problém unikátně,
problemy = (list(problemy_set)) # převedení na množinu a zpět to zaručí
# hlavní problémy čísla
# (mají vlastní sloupeček ve výsledkovce, nemají nadproblém)
hlavni_problemy = []
for p in problemy:
while not(p.nadproblem == None):
p = nadproblem
hlavni_problemy.append(p)
# zunikátnění
hlavni_problemy_set = set(hlavni_problemy)
hlavni_problemy = list(hlavni_problemy_set)
hlavni_problemy = hlavni_problemy_cisla(cislo)
## TODO dostat pro tyto problémy součet v daném čísle pro daného řešitele
## TODO možná chytřeji vybírat aktivní řešitele
## chceme letos něco poslal
aktivni_resitele = Resitel.objects.filter(
rok_maturity__gte=context['rocnik'].druhy_rok())
rok_maturity__gte=cislo.rocnik.druhy_rok())
# TODO: zkusit hodnoceni__rocnik...
#.filter(hodnoceni_set__rocnik__eq=cislo_rocnik)
radky_vysledkovky = []
for ar in aktivni_resitele:
vr = VysledkyResitele(ar.jmeno, ar.prijmeni)
for h in hlavni_problemy:
body = vysledky_resitele_problemu(h, ar, cislo)
vr.body[h.kod_v_rocniku] = body
vr.body_cislo = vr.body_cislo + body
# získáme výsledky řešitele - součty přes číslo a ročník
vr = VysledkyResitele(ar, cislo, cislo.rocnik)
for hp in hlavni_problemy:
vr.body.append(
body_resitele_problemu_v_cisle(hp, ar, cislo))
radky_vysledkovky.append(vr)
## TODO: spočítat počet bodů řešitele v daném ročníku a seřadit je podle toho
## TODO: možná použít tyto funkce i v RocnikVysledkovkaView (a umístit sem nebo tam)?
# setřídíme řádky výsledkovky/objekty VysledkyResitele podle bodů
radky_vysledkovky.sort(key=lambda vr: vr.body_rocnik, reverse=True)
# generujeme sloupec s pořadím pomocí stejně zvané funkce
pocty_bodu = [rv.body_rocnik for rv in radky_vysledkovky]
sloupec_poradi = sloupec_s_poradim(pocty_bodu)
# vysledky = VysledkyKCisluZaRocnik.objects.filter(cislo = context['cislo']).\
# order_by('-body', 'resitel__prijmeni', 'resitel__jmeno')
# reseni = Reseni.objects.filter(cislo_body = context['cislo']).select_related("resitel")
# každému řádku výsledkovky přidáme jeho pořadí
i = 0
for rv in radky_vysledkovky:
rv.poradi = sloupec_poradi[i]
i = i + 1
# typy úloh, které se mají zobrazovat u čísla, tj. těch, které byly
# v čísle skutečně zadány
# typy_skutecne_zadanych = [Problem.TYP_ULOHA, Problem.TYP_SERIAL, Problem.TYP_ORG_CLANEK]
# v_cisle_zadane = Problem.objects.filter(cislo_zadani=context['cislo']).filter(typ__in=typy_skutecne_zadanych).order_by('kod')
# vytahané informace předáváme do kontextu
context['cislo'] = cislo
context['radky_vysledkovky'] = radky_vysledkovky
context['problemy'] = hlavni_problemy
# context['v_cisle_zadane'] = TODO
# context['resene_problemy'] = resene_problemy
#XXX testovat
#XXX opravit to, že se nezobrazují body za jednotlivé úlohy
return context
# resene_problemy = Problem.objects.filter(cislo_reseni=context['cislo']).filter(typ__in=typy_skutecne_zadanych).order_by('cislo_zadani__cislo', 'kod')
#
# poradi_typu = {
# Problem.TYP_ULOHA: 1,
# Problem.TYP_SERIAL: 2,
# Problem.TYP_ORG_CLANEK: 3,
# Problem.TYP_TEMA: 4,
# Problem.TYP_RES_CLANEK: 5
# }
# problemy = sorted(set(r.problem for r in reseni), key=lambda x:(poradi_typu[x.typ], x.kod_v_rocniku()))
# #setridi problemy podle typu a poradi zadani
# problem_index = {}
@ -529,7 +726,6 @@ class CisloView(generic.DetailView):
# context['problemy'] = problemy
# context['v_cisle_zadane'] = v_cisle_zadane
# context['resene_problemy'] = resene_problemy
# return context
class ArchivTemataView(generic.ListView):
model = Problem
@ -601,9 +797,9 @@ def obalkyView(request,resitele):
tex = render(request,'seminar/archiv/obalky.tex', {'resitele': resitele}).content
tempdir = tempfile.mkdtemp()
with open(tempdir+"/obalky.tex","wb") as texfile:
with open(tempdir+"/obalky.tex","w") as texfile:
texfile.write(tex)
shutil.copy(os.path.join(settings.STATIC_ROOT, 'seminar/lisak.eps'),tempdir)
shutil.copy(os.path.join(settings.STATIC_ROOT, 'seminar/lisak.pdf'),tempdir)
subprocess.call(["pdflatex","obalky.tex"],cwd = tempdir)
with open(tempdir+"/obalky.pdf","rb") as pdffile:
@ -612,7 +808,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)
@ -749,7 +945,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
@ -938,7 +1134,7 @@ def texDownloadView(request, rocnik, cislo):
"body": p.body,
"zadani": p.text_zadani,
"reseni": p.text_reseni,
"cislo_zadani": p.cislo_zadani.cislo,
"cislo_zadani": p.cislo_zadani.poradi,
} for p in resene
],
}
@ -947,26 +1143,213 @@ def texDownloadView(request, rocnik, cislo):
cislo.save()
return JsonResponse(response)
class ResitelView(LoginRequiredMixin,generic.DetailView):
model = Resitel
template_name = 'seminar/resitel.html'
def get_object(self, queryset=None):
print(self.request.user)
return Resitel.objects.get(osoba__user=self.request.user)
## Formulare
class AddSolutionView(LoginRequiredMixin, FormView):
template_name = 'seminar/org/vloz_reseni.html'
form_class = f.VlozReseniForm
success_url = '/'
def resetPasswordView(request):
pass
def get_name(request):
# if this is a POST request we need to process the form data
def loginView(request):
if request.method == 'POST':
# create a form instance and populate it with data from the request:
form = NameForm(request.POST)
# check whether it's valid:
form = LoginForm(request.POST)
if form.is_valid():
# process the data in form.cleaned_data as required
# ...
# redirect to a new URL:
user = authenticate(request,
username=form.cleaned_data['username'],
password=form.cleaned_data['password'])
print(form.cleaned_data)
if user is not None:
login(request,user)
return HttpResponseRedirect('/')
else:
return render(request,
'seminar/login.html',
{'form': form, 'login_error': 'Neplatné jméno nebo heslo'})
else:
form = LoginForm()
return render(request, 'seminar/login.html', {'form': form})
def logoutView(request):
form = LoginForm()
if request.user.is_authenticated:
logout(request)
return render(request, 'seminar/login.html', {'form': form, 'login_error': 'Byli jste úspěšně odhlášeni'})
return render(request, 'seminar/login.html', {'form': form})
def prihlaska_log_gdpr_safe(logger, gdpr_logger, msg, form_data):
msg = "{}, form_hash:{}".format(msg,hash(form_data))
logger.warn(msg)
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 = ProfileEditForm(initial=prefill_1)
## Změna údajů a jejich uložení
if request.method == 'POST':
form = ProfileEditForm(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):
generic_logger = logging.getLogger('seminar.prihlaska')
err_logger = logging.getLogger('seminar.prihlaska.problem')
form_logger = logging.getLogger('seminar.prihlaska.form')
if request.method == 'POST':
form = PrihlaskaForm(request.POST)
# TODO vyresit, co se bude v jakych situacich zobrazovat
if form.is_valid():
generic_logger.info("Form valid")
fcd = form.cleaned_data
form_hash = hash(fcd)
form_logger.info(fcd,form_hash=form_hash)
with transaction.atomic():
u = User.objects.create_user(
username=fcd['username'],
password=fcd['password'],
email = fcd['email'])
u.save()
o = Osoba(
jmeno = fcd['jmeno'],
prijmeni = fcd['prijmeni'],
pohlavi_muz = fcd['pohlavi_muz'],
email = fcd['email'],
telefon = fcd.get('telefon',''),
datum_narozeni = fcd.get('datum_narozeni',None),
datum_souhlasu_udaje = date.today(),
datum_registrace = date.today(),
ulice = fcd.get('ulice',''),
mesto = fcd.get('mesto',''),
psc = fcd.get('psc',''),
poznamka = str(fcd)
)
if fcd.get('spam',False):
o.datum_souhlasu_zasilani = date.today()
if fcd.get('stat','') in ('CZ','SK'):
o.stat = fcd['stat']
else:
# Unknown country - log it
msg = "Unknown country {}".format(fcd['stat_text'])
err_logger.warn(msg,form_hash=form_hash)
o.save()
o.user = u
o.save()
r = Resitel(
rok_maturity = fcd['rok_maturity'],
zasilat = fcd['zasilat']
)
r.save()
r.osoba = o
if fcd.get('skola'):
r.skola = fcd['skola']
else:
# Unknown school - log it
msg = "Unknown school {}, {}".format(fcd['skola_nazev'],fcd['skola_adresa'])
err_logger.warn(msg,form_hash=form_hash)
r.save()
return HttpResponseRedirect('/thanks/')
# if a GET (or any other method) we'll create a blank form
else:
form = NameForm()
form = PrihlaskaForm()
return render(request, 'seminar/prihlaska.html', {'form': form})
class SkolaAutocomplete(autocomplete.Select2QuerySetView):
def get_queryset(self):
# Don't forget to filter out results depending on the visitor !
qs = Skola.objects.all()
if self.q:
qs = qs.filter(
Q(nazev__istartswith=self.q)|
Q(kratky_nazev__istartswith=self.q)|
Q(ulice__istartswith=self.q)|
Q(mesto__istartswith=self.q))
return qs
class LoginRequiredAjaxMixin(object):
def dispatch(self, request, *args, **kwargs):
#if request.is_ajax() and not request.user.is_authenticated: # Pokud to otevřu jako stránku, tak se omezení neuplatní, takže to asi nechceme
if not request.user.is_authenticated:
return JsonResponse(data={'results': [], 'pagination': {}}, status=401)
return super(LoginRequiredAjaxMixin, self).dispatch(request, *args, **kwargs)
class ResitelAutocomplete(LoginRequiredAjaxMixin,autocomplete.Select2QuerySetView):
def get_queryset(self):
qs = Resitel.objects.all()
if self.q:
qs = qs.filter(
Q(osoba__jmeno__startswith=self.q)|
Q(osoba__prijmeni__startswith=self.q)|
Q(osoba__prezdivka__startswith=self.q)
)
return qs
# Ceka na autocomplete v3
# class OrganizatorAutocomplete(autocomplete.Select2QuerySetView):
# def get_queryset(self):
@ -987,3 +1370,46 @@ def get_name(request):
# 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
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