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 # aux files
*.pyc *.pyc
*.swp *.sw[mnop]
# secrets # secrets
/django.secret /django.secret

6
README.md

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

4
galerie/admin.py

@ -5,7 +5,6 @@ from django.contrib import admin
from django.http import HttpResponseRedirect from django.http import HttpResponseRedirect
from django import forms from django import forms
from django.db import models from django.db import models
from autocomplete_light import shortcuts as autocomplete_light
# akction # akction
@ -39,11 +38,12 @@ class GalerieInline(admin.TabularInline):
class ObrazekAdmin(admin.ModelAdmin): class ObrazekAdmin(admin.ModelAdmin):
list_display = ('obrazek_velky', 'nazev', 'popis', 'obrazek_maly_tag') list_display = ('obrazek_velky', 'nazev', 'popis', 'obrazek_maly_tag')
search_fields = ['nazev','popis']
class GalerieAdmin(admin.ModelAdmin): class GalerieAdmin(admin.ModelAdmin):
form = autocomplete_light.modelform_factory(Galerie, autocomplete_fields=['titulni_obrazek'], fields=['titulni_obrazek'])
model = Galerie model = Galerie
fields = ('zobrazit', 'nazev', 'titulni_obrazek', 'popis', 'galerie_up', 'soustredeni', 'poradi') fields = ('zobrazit', 'nazev', 'titulni_obrazek', 'popis', 'galerie_up', 'soustredeni', 'poradi')
autocomplete_fields = ['titulni_obrazek']
list_display = ('nazev', 'soustredeni', 'galerie_up', 'poradi', 'zobrazit', 'datum_zmeny') list_display = ('nazev', 'soustredeni', 'galerie_up', 'poradi', 'zobrazit', 'datum_zmeny')
inlines = [GalerieInline] inlines = [GalerieInline]
actions = [zverejnit_fotogalerii, prepnout_fotogalerii_do_org_rezimu] 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 = ( MIDDLEWARE = (
'reversion.middleware.RevisionMiddleware', # 'reversion.middleware.RevisionMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware', 'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware', 'django.middleware.csrf.CsrfViewMiddleware',
@ -98,13 +98,14 @@ INSTALLED_APPS = (
# Utilities # Utilities
'sekizai', 'sekizai',
'reversion', # 'reversion',
'django_countries', 'django_countries',
'solo', 'solo',
'ckeditor', 'ckeditor',
'ckeditor_uploader', 'ckeditor_uploader',
'taggit', 'taggit',
'autocomplete_light', 'dal',
'dal_select2',
'fluent_comments', 'fluent_comments',
'crispy_forms', 'crispy_forms',
@ -118,6 +119,8 @@ INSTALLED_APPS = (
'imagekit', 'imagekit',
'polymorphic',
# MaMweb # MaMweb
'mamweb', 'mamweb',
'seminar', 'seminar',
@ -215,6 +218,14 @@ LOGGING = {
'handlers': ['console'], 'handlers': ['console'],
'level': 'DEBUG', '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 # Catch-all logger
'': { '': {
@ -237,6 +248,24 @@ LOGGING = {
'class': 'django.utils.log.AdminEmailHandler', 'class': 'django.utils.log.AdminEmailHandler',
'formatter': 'verbose', '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'], # 'handlers': ['console'],
# 'propagate': False, # 'propagate': False,
#}, #},
'werkzeug': {
'handlers': ['console'],
'level': 'DEBUG',
'propagate': True,
},
'': { '': {
'handlers': ['console'], 'handlers': ['console'],
'level': 'DEBUG', 'level': 'DEBUG',
@ -88,3 +93,5 @@ LOGGING = {
# set to 'DEBUG' for EXTRA verbose output # set to 'DEBUG' for EXTRA verbose output
# LOGGING['handlers']['console']['level'] = 'INFO' # LOGGING['handlers']['console']['level'] = 'INFO'
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'

2
mamweb/settings_prod.py

@ -61,6 +61,8 @@ CSRF_COOKIE_SECURE = True
LOGGING['loggers']['']['handlers'] = ['console', 'mail_admins'] LOGGING['loggers']['']['handlers'] = ['console', 'mail_admins']
LOGGING['loggers']['django']['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 # E-MAIL NOTIFICATIONS

2
mamweb/settings_test.py

@ -65,3 +65,5 @@ CSRF_COOKIE_SECURE = True
LOGGING['loggers']['']['handlers'] = ['console', 'mail_admins'] LOGGING['loggers']['']['handlers'] = ['console', 'mail_admins']
LOGGING['loggers']['django']['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 { div.nahledy_cisel div, div.nahledy_cisel img {
position: absolute; 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 %} {% block extrahead %}
<link rel="shortcut icon" href="{% static 'favicon.ico' %}" type="image/x-icon"> <link rel="shortcut icon" href="{% static 'favicon.ico' %}" type="image/x-icon">
<script src="{% static 'js/jquery-1.11.1.js' %}"></script> <script src="{% static 'js/jquery-1.11.1.js' %}"></script>
{% include 'autocomplete_light/static.html' %}
{% endblock %} {% endblock %}
{% block branding %} {% block branding %}

24
mamweb/templates/base.html

@ -27,8 +27,8 @@
} }
}); });
</script> </script>
<script type="text/javascript" <script type="text/javascript" async
src="https://cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML"> src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.6/MathJax.js?config=TeX-AMS-MML_HTMLorMML">
</script> </script>
{# script specifický pro stránku #} {# script specifický pro stránku #}
@ -97,6 +97,26 @@
<div class='row content'> <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'> <div class='col-md-12'>
{% block content %} {% block content %}
{% endblock content %} {% endblock content %}

1
mamweb/urls.py

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

3
requirements.txt

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

173
seminar/admin.py

@ -1,36 +1,171 @@
from django.contrib import admin 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 import seminar.models as m
admin.site.register(m.Osoba)
admin.site.register(m.Skola) admin.site.register(m.Skola)
admin.site.register(m.Prijemce) admin.site.register(m.Prijemce)
admin.site.register(m.Resitel)
admin.site.register(m.Rocnik) admin.site.register(m.Rocnik)
admin.site.register(m.Cislo) admin.site.register(m.Cislo)
admin.site.register(m.Organizator) admin.site.register(m.Organizator)
admin.site.register(m.Soustredeni) admin.site.register(m.Soustredeni)
admin.site.register(m.Problem)
admin.site.register(m.Tema) @admin.register(m.Osoba)
admin.site.register(m.Clanek) 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.Text)
admin.site.register(m.Uloha)
admin.site.register(m.Reseni) class ResitelInline(admin.TabularInline):
admin.site.register(m.Hodnoceni) model = m.Resitel
extra = 1
admin.site.register(m.Resitel)
class PrilohaReseniInline(admin.TabularInline):
model = m.PrilohaReseni
extra = 1
admin.site.register(m.PrilohaReseni) admin.site.register(m.PrilohaReseni)
class Reseni_ResiteleInline(admin.TabularInline):
model = m.Reseni_Resitele
@admin.register(m.Reseni)
class ReseniAdmin(ReverseModelAdmin):
base_model = m.Reseni
inline_type = 'tabular'
inline_reverse = ['text_cely','resitele']
exclude = ['text_zkraceny', 'text_zkraceny_set']
inlines = [PrilohaReseniInline]
# FAIL in template
# inlines = [PrilohaReseniInline,Reseni_ResiteleInline]
admin.site.register(m.Hodnoceni)
admin.site.register(m.Pohadka) admin.site.register(m.Pohadka)
admin.site.register(m.Konfera) admin.site.register(m.Konfera)
admin.site.register(m.Obrazek) admin.site.register(m.Obrazek)
admin.site.register(m.TreeNode)
admin.site.register(m.RocnikNode)
admin.site.register(m.CisloNode) # Polymorfismus pro stromy
admin.site.register(m.MezicisloNode) # TODO: Inlines podle https://django-polymorphic.readthedocs.io/en/stable/admin.html
admin.site.register(m.TemaVCisleNode)
admin.site.register(m.KonferaNode) @admin.register(m.TreeNode)
admin.site.register(m.ClanekNode) class TreeNodeAdmin(PolymorphicParentModelAdmin):
admin.site.register(m.UlohaZadaniNode) base_model = m.TreeNode
admin.site.register(m.PohadkaNode) child_models = [
admin.site.register(m.UlohaVzorakNode) m.RocnikNode,
admin.site.register(m.TextNode) 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.Nastaveni)
admin.site.register(m.Novinky) 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 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): class Command(BaseCommand):
help = "Clear database and load testing data." 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): def handle(self, *args, **options):
assert settings.DEBUG == True assert settings.DEBUG == True
dbfile = settings.DATABASES['default']['NAME'] 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') os.rename(dbfile, dbfile + '.old')
self.stderr.write('Stara databaze prejmenovana na "%s"' % (dbfile + '.old')) self.stderr.write('Stara databaze prejmenovana na "%s"' % (dbfile + '.old'))
if not options['no_migrate']:
call_command('migrate', no_input=True) call_command('migrate', no_input=True)
self.stdout.write('Vytvarim uzivatele "admin" (heslo "admin") a pseudo-nahodna data ...') self.stdout.write('Vytvarim uzivatele "admin" (heslo "admin") a pseudo-nahodna data ...')
create_test_data(size=8) 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 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): class SeminarModelBase(models.Model):
@ -129,6 +130,17 @@ class Osoba(SeminarModelBase):
def __str__(self): def __str__(self):
return self.plne_jmeno() return self.plne_jmeno()
# Overridujeme save Osoby, aby když si změní e-mail, aby se projevil i v
# Userovi (a tak se dal poslat mail s resetem hesla)
def save(self, *args, **kwargs):
if self.user is not None:
u = self.user
# U svatého tučňáka, prosím ať tohle funguje.
# (Takhle se kódit asi nemá...)
u.email = self.email
u.save()
super().save()
# #
# Mělo by být částečně vytaženo z Aesopa # Mělo by být částečně vytaženo z Aesopa
# viz https://ovvp.mff.cuni.cz/wiki/aesop/export-skol. # viz https://ovvp.mff.cuni.cz/wiki/aesop/export-skol.
@ -352,7 +364,7 @@ class Rocnik(SeminarModelBase):
def verejna_cisla(self): def verejna_cisla(self):
vc = [c for c in self.cisla.all() if c.verejne()] 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 return vc
def posledni_verejne_cislo(self): def posledni_verejne_cislo(self):
@ -361,7 +373,7 @@ class Rocnik(SeminarModelBase):
def verejne_vysledkovky_cisla(self): def verejne_vysledkovky_cisla(self):
vc = list(self.cisla.filter(verejna_vysledkovka=True)) vc = list(self.cisla.filter(verejna_vysledkovka=True))
vc.sort(key=lambda c: c.cislo) vc.sort(key=lambda c: c.poradi)
return vc return vc
def posledni_zverejnena_vysledkovka_cislo(self): def posledni_zverejnena_vysledkovka_cislo(self):
@ -383,10 +395,18 @@ class Rocnik(SeminarModelBase):
cache.set(name, c, 300) cache.set(name, c, 300)
return c 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): def cislo_pdf_filename(self, filename):
rocnik = str(self.rocnik.rocnik) 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) @reversion.register(ignore_duplicates=True)
class Cislo(SeminarModelBase): class Cislo(SeminarModelBase):
@ -395,7 +415,7 @@ class Cislo(SeminarModelBase):
db_table = 'seminar_cisla' db_table = 'seminar_cisla'
verbose_name = 'Číslo' verbose_name = 'Číslo'
verbose_name_plural = 'Čísla' verbose_name_plural = 'Čísla'
ordering = ['-rocnik__rocnik', '-cislo'] ordering = ['-rocnik__rocnik', '-poradi']
# Interní ID # Interní ID
id = models.AutoField(primary_key = True) 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', rocnik = models.ForeignKey(Rocnik, verbose_name='ročník', related_name='cisla',
db_index=True,on_delete=models.PROTECT) 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!') 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, datum_vydani = models.DateField('datum vydání', blank=True, null=True,
@ -436,20 +456,20 @@ class Cislo(SeminarModelBase):
# CisloNode # CisloNode
def kod(self): 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' kod.short_description = 'Kód čísla'
def __str__(self): def __str__(self):
# Potenciální DB HOG, pokud by se ročník necachoval # Potenciální DB HOG, pokud by se ročník necachoval
r = Rocnik.cached_rocnik(self.rocnik_id) r = Rocnik.cached_rocnik(self.rocnik_id)
return '{}.{}'.format(r.rocnik, self.cislo) return '{}.{}'.format(r.rocnik, self.poradi)
def verejne(self): def verejne(self):
return self.verejne_db return self.verejne_db
verejne.boolean = True verejne.boolean = True
def verejne_url(self): 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): def nasledujici(self):
"Vrací None, pokud je toto poslední" "Vrací None, pokud je toto poslední"
@ -471,11 +491,20 @@ class Cislo(SeminarModelBase):
def get(cls, rocnik, cislo): def get(cls, rocnik, cislo):
try: try:
r = Rocnik.objects.get(rocnik=rocnik) r = Rocnik.objects.get(rocnik=rocnik)
c = r.cisla.get(cislo=cislo) c = r.cisla.get(poradi=cislo)
except ObjectDoesNotExist: except ObjectDoesNotExist:
return None return None
return c 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) @reversion.register(ignore_duplicates=True)
class Organizator(SeminarModelBase): class Organizator(SeminarModelBase):
# zmena dedicnosti z models.Model na SeminarModelBase, potencialni vznik bugu # zmena dedicnosti z models.Model na SeminarModelBase, potencialni vznik bugu
@ -583,7 +612,8 @@ class Soustredeni(SeminarModelBase):
@reversion.register(ignore_duplicates=True) @reversion.register(ignore_duplicates=True)
class Problem(SeminarModelBase): # Pozor na následující řádek. *Nekrmit, asi kouše!*
class Problem(SeminarModelBase,PolymorphicModel):
class Meta: class Meta:
# Není abstraktní, protože se na něj jinak nedají dělat ForeignKeys. # 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) id = models.AutoField(primary_key = True)
# Název # Název
nazev = models.CharField('název', max_length=256) nazev = models.CharField('název', max_length=256) # Zveřejnitelný na stránky
# Problém má podproblémy # Problém má podproblémy
nadproblem = models.ForeignKey('self', verbose_name='nadřazený problém', nadproblem = models.ForeignKey('self', verbose_name='nadřazený problém',
related_name='nadproblem_%(class)s', null=True, blank=True, related_name='podproblem', null=True, blank=True,
on_delete=models.SET_NULL) on_delete=models.SET_NULL)
STAV_NAVRH = 'navrh' STAV_NAVRH = 'navrh'
@ -698,6 +728,9 @@ class Tema(Problem):
rocnik = models.ForeignKey(Rocnik, verbose_name='ročník',blank=True, null=True, rocnik = models.ForeignKey(Rocnik, verbose_name='ročník',blank=True, null=True,
on_delete=models.PROTECT) 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): def kod_v_rocniku(self):
if self.stav == 'zadany': if self.stav == 'zadany':
if self.nadproblem: if self.nadproblem:
@ -705,6 +738,12 @@ class Tema(Problem):
return "t{}".format(self.kod) return "t{}".format(self.kod)
return '<Není zadaný>' 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 Clanek(Problem):
class Meta: class Meta:
db_table = 'seminar_clanky' db_table = 'seminar_clanky'
@ -725,6 +764,15 @@ class Clanek(Problem):
return "c{}".format(self.kod) return "c{}".format(self.kod)
return '<Není zadaný>' 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 Text(SeminarModelBase):
class Meta: class Meta:
db_table = 'seminar_texty' 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) # 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 Uloha(Problem):
class Meta: class Meta:
@ -770,12 +827,26 @@ class Uloha(Problem):
def kod_v_rocniku(self): def kod_v_rocniku(self):
if self.stav == 'zadany': 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: if self.nadproblem:
return self.nadproblem.kod_v_rocniku()+name return self.nadproblem.kod_v_rocniku()+name
return name return name
return '<Není zadaný>' 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) @reversion.register(ignore_duplicates=True)
class Reseni(SeminarModelBase): class Reseni(SeminarModelBase):
@ -828,7 +899,7 @@ class Reseni(SeminarModelBase):
# Konfera # Konfera
def __str__(self): def __str__(self):
return "{}: {}".format(self.resitel.osoba.plne_jmeno(), self.problem.nazev) return "{}({}): {}({})".format(self.resitele.first(),len(self.resitele.all()), self.problem.first() ,len(self.problem.all()))
# NOTE: Potenciální DB HOG (bez select_related) # NOTE: Potenciální DB HOG (bez select_related)
## Pravdepodobne uz nebude potreba: ## Pravdepodobne uz nebude potreba:
@ -856,7 +927,8 @@ class Hodnoceni(SeminarModelBase):
reseni = models.ForeignKey(Reseni, verbose_name='řešení', on_delete=models.CASCADE) 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): def __str__(self):
return "{}, {}, {}".format(self.problem, self.reseni, self.body) 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)]+"..." uryvek = self.text if len(self.text) < 50 else self.text[:(50-3)]+"..."
return uryvek 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) @reversion.register(ignore_duplicates=True)
class Soustredeni_Ucastnici(SeminarModelBase): class Soustredeni_Ucastnici(SeminarModelBase):
@ -1020,7 +1100,7 @@ class Konfera(models.Model):
# Interní ID # Interní ID
id = models.AutoField(primary_key = True) 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, anotace = models.TextField('anotace', blank=True,
help_text='Popis, o čem bude konfera.') help_text='Popis, o čem bude konfera.')
@ -1067,6 +1147,15 @@ class Konfera(models.Model):
def __str__(self): def __str__(self):
return "{}: ({})".format(self.nazev, self.soustredeni) 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. # Vazebna tabulka. Mozna se generuje automaticky.
@reversion.register(ignore_duplicates=True) @reversion.register(ignore_duplicates=True)
@ -1139,12 +1228,13 @@ class Obrazek(SeminarModelBase):
help_text = 'Černobílá verze obrázku do čísla', help_text = 'Černobílá verze obrázku do čísla',
upload_to = 'obrazky/%Y/%m/%d/', blank=True, null=True) upload_to = 'obrazky/%Y/%m/%d/', blank=True, null=True)
class TreeNode(models.Model): class TreeNode(PolymorphicModel):
class Meta: class Meta:
db_table = "seminar_nodes_treenode" db_table = "seminar_nodes_treenode"
verbose_name = "TreeNode" verbose_name = "TreeNode"
verbose_name_plural = "TreeNody" verbose_name_plural = "TreeNody"
# TODO: Nechceme radši jako root vyžadovat přímo RocnikNode?
root = models.ForeignKey('TreeNode', root = models.ForeignKey('TreeNode',
related_name="potomci_set", related_name="potomci_set",
null = True, null = True,
@ -1162,14 +1252,59 @@ class TreeNode(models.Model):
blank = True, blank = True,
on_delete=models.SET_NULL, on_delete=models.SET_NULL,
verbose_name="další element na stejné úrovni") verbose_name="další element na stejné úrovni")
nazev = models.TextField("název tohoto node",
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): 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: if self.first_child:
self.first_child.print_tree(indent=indent+2) self.first_child.print_tree(indent=indent+2)
if self.succ: if self.succ:
self.succ.print_tree(indent=indent) self.succ.print_tree(indent=indent)
def getOdkazStr(self): # String na rozcestník
return self.first_child.getOdkazStr()
def getOdkaz(self): # ID HTML tagu, na který se bude scrollovat #{{self.getOdkaz}}
# Jsem si vědom, že tu potenciálně vznikají kolize.
# Přijdou mi natolik nepravděpodobné, že je neřeším
# Chtěl jsem ale hezké odkazy
string = unidecode(self.getOdkazStr())
returnVal = ""
i = 0
while len(returnVal) < 16: # Max 15 znaků
if i == len(string):
break
if string[i] == " ":
returnVal += "-"
if string[i].isalnum():
returnVal += string[i].lower()
i += 1
return returnVal
def __str__(self):
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 RocnikNode(TreeNode):
class Meta: 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ě on_delete = models.PROTECT, # Pokud chci mazat ročník, musím si Node pořešit ručně
verbose_name = "ročník") verbose_name = "ročník")
def aktualizuj_nazev(self):
self.nazev = "RocnikNode: "+str(self.rocnik)
class CisloNode(TreeNode): class CisloNode(TreeNode):
class Meta: class Meta:
db_table = 'seminar_nodes_cislo' 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ě on_delete = models.PROTECT, # Pokud chci mazat číslo, musím si Node pořešit ručně
verbose_name = "číslo") 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 MezicisloNode(TreeNode):
class Meta: class Meta:
db_table = 'seminar_nodes_mezicislo' db_table = 'seminar_nodes_mezicislo'
verbose_name = 'Mezičíslo (Node)' verbose_name = 'Mezičíslo (Node)'
verbose_name_plural = 'Mezičísla (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): class TemaVCisleNode(TreeNode):
""" Obsahuje příspěvky k tématu v daném čísle """ """ Obsahuje příspěvky k tématu v daném čísle """
class Meta: 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ě on_delete=models.PROTECT, # Pokud chci mazat téma, musím si Node pořešit ručně
verbose_name = "téma v čísle") 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 KonferaNode(TreeNode):
class Meta: class Meta:
db_table = 'seminar_nodes_konfera' db_table = 'seminar_nodes_konfera'
@ -1216,6 +1384,9 @@ class KonferaNode(TreeNode):
null=True, null=True,
blank=False) blank=False)
def aktualizuj_nazev(self):
self.nazev = "KonferaNode: "+str(self.konfera)
class ClanekNode(TreeNode): class ClanekNode(TreeNode):
class Meta: class Meta:
db_table = 'seminar_nodes_clanek' db_table = 'seminar_nodes_clanek'
@ -1227,6 +1398,13 @@ class ClanekNode(TreeNode):
null=True, null=True,
blank=False) blank=False)
def aktualizuj_nazev(self):
self.nazev = "ClanekNode: "+str(self.clanek)
def getOdkazStr(self):
return str(self.clanek)
class UlohaZadaniNode(TreeNode): class UlohaZadaniNode(TreeNode):
class Meta: class Meta:
db_table = 'seminar_nodes_uloha_zadani' db_table = 'seminar_nodes_uloha_zadani'
@ -1238,6 +1416,13 @@ class UlohaZadaniNode(TreeNode):
null=True, null=True,
blank=False) blank=False)
def aktualizuj_nazev(self):
self.nazev = "UlohaZadaniNode: "+str(self.uloha)
def getOdkazStr(self):
return str(self.uloha)
class PohadkaNode(TreeNode): class PohadkaNode(TreeNode):
class Meta: class Meta:
db_table = 'seminar_nodes_pohadka' db_table = 'seminar_nodes_pohadka'
@ -1248,6 +1433,9 @@ class PohadkaNode(TreeNode):
verbose_name = "pohádka", verbose_name = "pohádka",
) )
def aktualizuj_nazev(self):
self.nazev = "PohadkaNode: "+str(self.pohadka)
class UlohaVzorakNode(TreeNode): class UlohaVzorakNode(TreeNode):
class Meta: class Meta:
db_table = 'seminar_nodes_uloha_vzorak' db_table = 'seminar_nodes_uloha_vzorak'
@ -1259,6 +1447,13 @@ class UlohaVzorakNode(TreeNode):
null=True, null=True,
blank=False) blank=False)
def aktualizuj_nazev(self):
self.nazev = "UlohaVzorakNode: "+str(self.uloha)
def getOdkazStr(self):
return str(self.uloha)
class TextNode(TreeNode): class TextNode(TreeNode):
class Meta: class Meta:
db_table = 'seminar_nodes_obsah' db_table = 'seminar_nodes_obsah'
@ -1268,6 +1463,13 @@ class TextNode(TreeNode):
on_delete=models.PROTECT, on_delete=models.PROTECT,
verbose_name = 'text') 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. ## FIXME: Logiku přesunout do views.
#class VysledkyBase(SeminarModelBase): #class VysledkyBase(SeminarModelBase):
# #
@ -1292,7 +1494,7 @@ class TextNode(TreeNode):
# #
# def __str__(self): # def __str__(self):
# return "%s: %sb (%s)".format(self.resitel.plne_jmeno(), self.body, # return "%s: %sb (%s)".format(self.resitel.plne_jmeno(), self.body,
# str(self.cislo)) # str(self.poradi))
# # NOTE: DB zatez pri vypisu (ale nepouzivany) # # NOTE: DB zatez pri vypisu (ale nepouzivany)
@ -1342,7 +1544,7 @@ class TextNode(TreeNode):
# #
# def __str__(self): # def __str__(self):
# # NOTE: DB HOG (ale nepouzivany) # # 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 ##mozna potreba upravit
@ -1353,12 +1555,16 @@ class Nastaveni(SingletonModel):
db_table = 'seminar_nastaveni' db_table = 'seminar_nastaveni'
verbose_name = 'Nastavení semináře' verbose_name = 'Nastavení semináře'
aktualni_rocnik = models.ForeignKey(Rocnik, verbose_name='aktuální ročník', # aktualni_rocnik = models.ForeignKey(Rocnik, verbose_name='aktuální ročník',
null=False, on_delete=models.PROTECT) # null=False, on_delete=models.PROTECT)
aktualni_cislo = models.ForeignKey(Cislo, verbose_name='poslední vydané číslo', aktualni_cislo = models.ForeignKey(Cislo, verbose_name='poslední vydané číslo',
null=False, on_delete=models.PROTECT) null=False, on_delete=models.PROTECT)
@property
def aktualni_rocnik(self):
return self.aktualni_cislo.rocnik
def __str__(self): def __str__(self):
return 'Nastavení semináře' return 'Nastavení semináře'
@ -1399,3 +1605,35 @@ class Novinky(models.Model):
return '[' + str(self.datum) + '] ' + self.text[0:50] return '[' + str(self.datum) + '] ' + self.text[0:50]
else: else:
return '[' + str(self.datum) + '] ' return '[' + str(self.datum) + '] '
# FIXME: Tohle nepatří do aplikace 'seminar'
# Nefunkční alternativa vestavěného Usera, který má jméno a mail v přidružené Osobě
# from django.contrib.auth.models import User as Django_User
#
# class Uzivatel(Django_User):
# class Meta:
# proxy = True
#
# @property
# def first_name(self):
# osoby = Osoba.objects.filter(user=self)
# if len(osoby) == 0:
# return None
# return osoby.first().krestni_jmeno
#
# @property
# def last_name(self):
# osoby = Osoba.objects.filter(user=self)
# if len(osoby) == 0:
# return None
# return osoby.first().prijmeni
#
# @property
# def email(self):
# osoby = Osoba.objects.filter(user=self)
# if len(osoby) == 0:
# return None
# return osoby.first().email

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 % Tohle makro vysází samotnou obálku
\def\obalka#1#2#3#4#5#6#7{ \def\obalka#1#2#3#4#5#6#7{
% Horní a pravý okraj je zároveň okraj stránky, resetujeme odsazení % 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} \adresaMaM}
\vskip 7.3 cm % Od oka \vskip 7.3 cm % Od oka
\hskip\toskip% \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 %} {% csrf_token %}
{{ form }} {{form.non_field_errors}}
<input type="submit" value="Submit"> <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> </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 -*- # -*- coding: utf-8 -*-
import datetime import datetime
from pytz import timezone
import random import random
import lorem import lorem
import django.contrib.auth import django.contrib.auth
from django.db import transaction from django.db import transaction
import unidecode 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 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() User = django.contrib.auth.get_user_model()
zlinska = None # tohle bude speciální škola, které později dodáme kontaktní osobu zlinska = None # tohle bude speciální škola, které později dodáme kontaktní osobu
logger = logging.getLogger(__name__)
def gen_osoby(rnd, size): def gen_osoby(rnd, size):
logger.info('Generuji osoby (size={})...'.format(size))
jmena_m = ['Aleš', 'Tomáš', 'Martin', 'Jakub', 'Petr', 'Lukáš', 'Cyril', 'Pavel Karel'] jmena_m = ['Aleš', 'Tomáš', 'Martin', 'Jakub', 'Petr', 'Lukáš', 'Cyril', 'Pavel Karel']
jmena_f = ['Eva', 'Karolína', 'Zuzana', 'Sylvie', 'Iva', 'Jana', 'Marie', jmena_f = ['Eva', 'Karolína', 'Zuzana', 'Sylvie', 'Iva', 'Jana', 'Marie',
'Marta Iva', 'Shu Shan'] '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 def gen_skoly(): #TODO někdy to přepsat, aby jich bylo více
logger.info('Generuji školy...')
skoly = [] skoly = []
prvnizs = Skola.objects.create(mesto='Praha', stat='CZ', psc='101 00', prvnizs = Skola.objects.create(mesto='Praha', stat='CZ', psc='101 00',
ulice='Krátká 5', nazev='První ZŠ', je_zs=True, je_ss=False) 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 return skoly
def gen_resitele(rnd, osoby, skoly): def gen_resitele(rnd, osoby, skoly):
logger.info('Generuji řešitele...')
resitele = [] resitele = []
for os in osoby: for os in osoby:
rand = rnd.randint(0, 8) rand = rnd.randint(0, 8)
if not (rand % 8 == 0): if not (rand % 8 == 0):
resitele.append(Resitel.objects.create(osoba=os, skola=rnd.choice(skoly), resitele.append(Resitel.objects.create(osoba=os, skola=rnd.choice(skoly),
rok_maturity=rnd.randint(2019, 2029), rok_maturity=rnd.randint(2019, 2029),
zasilat=rnd.choice(Resitel.ZASILAT_CHOICES))) zasilat=rnd.choice(Resitel.ZASILAT_CHOICES)[0]))
return resitele return resitele
def gen_prijemci(rnd, osoby, kolik=10): def gen_prijemci(rnd, osoby, kolik=10):
logger.info('Generuji příjemce (kolik={})...'.format(kolik))
prijemci = [] prijemci = []
for i in rnd.sample(osoby, kolik): for i in rnd.sample(osoby, kolik):
prijemci.append(Prijemce.objects.create(osoba=i)) prijemci.append(Prijemce.objects.create(osoba=i))
return prijemci return prijemci
def gen_organizatori(rnd, osoby, last_rocnik, users): def gen_organizatori(rnd, osoby, last_rocnik, users):
logger.info('Generuji organizátory...')
organizatori = [] organizatori = []
@ -118,9 +130,18 @@ def gen_organizatori(rnd, osoby, last_rocnik, users):
rand = rnd.randint(0, 8) rand = rnd.randint(0, 8)
if (rand % 8 == 0): if (rand % 8 == 0):
pusobnost = rnd.randint(1, last_rocnik) pusobnost = rnd.randint(1, last_rocnik)
od = datetime.date(1993 + pusobnost, rnd.randint(1, 12), rnd.randint(1, 28)) od = datetime.datetime(
do = datetime.date(od.year + rnd.randint(1, 6), rnd.randint(1, 12), year=1993 + pusobnost,
rnd.randint(1, 28)) 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 #aktualni organizatori jeste nemaji vyplnene organizuje_do
#popis orga #popis orga
@ -136,6 +157,8 @@ def gen_organizatori(rnd, osoby, last_rocnik, users):
return organizatori return organizatori
def gen_ulohy_do_cisla(rnd, organizatori, resitele, rocnik_cisla, rocniky, size): 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 # ulohy resene v cisle
jaka = ["Šachová", "Černá", "Větrná", "Dlouhá", "Křehká", "Rychlá", jaka = ["Šachová", "Černá", "Větrná", "Dlouhá", "Křehká", "Rychlá",
"Zákeřná", "Fyzikální"] "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: for rocnik in rocniky:
k+=1 k+=1
cisla = rocnik_cisla[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): for pi in range(1, ((size + 1) // 2) + 1):
poc_op = rnd.randint(1, 4) # počet opravovatelů 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 p.ulohazadaninode = uloha_zadani
otec_syn(cisla[ci-2-1].cislonode, uloha_zadani) otec_syn(cisla[ci-2-1].cislonode, uloha_zadani)
# generování vzorového textu
text_vzoraku = Text.objects.create( text_vzoraku = Text.objects.create(
na_web = rnd.choice(reseni), na_web = rnd.choice(reseni),
do_cisla = 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.opravovatele.set(rnd.sample(organizatori,poc_op))
p.save() p.save()
# reseni ulohy # generování řešení
poc_reseni = rnd.randint(size // 2, size * 2) poc_reseni = rnd.randint(size // 2, size * 2)
#poc_resitel = rnd.randint(1, 3) <- k čemu je himbajs tahle proměnná? # generujeme náhodný počet řešení
# vybereme nahodny vzorek resitelu o delce poctu reseni
# (nebo skoro vsechny resitele, pokud jich je mene nez pocet reseni)
for ri in range(poc_reseni): 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 # problem a resitele přiřadíme později, ManyToManyField
# se nedá vyplnit v create() # 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.resitele.set(res_vyber)
res.save() res.save()
hod = Hodnoceni.objects.create( hod = Hodnoceni.objects.create(
@ -234,6 +266,8 @@ def gen_ulohy_do_cisla(rnd, organizatori, resitele, rocnik_cisla, rocniky, size)
return return
def gen_soustredeni(rnd, resitele, organizatori): def gen_soustredeni(rnd, resitele, organizatori):
logger.info('Generuji soustředění...')
soustredeni = [] soustredeni = []
for _ in range(1, 10): #FIXME Tu range si změňte jak chcete, nevím, co přesně znamená size (asi Anet?) 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)) 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 return soustredeni
def gen_rocniky(last_rocnik, size): def gen_rocniky(last_rocnik, size):
logger.info('Generuji ročníky (size={})...'.format(size))
rocniky = [] rocniky = []
node = None node = None
for ri in range(min(last_rocnik - size, 1), last_rocnik + 1): for ri in range(min(last_rocnik - size, 1), last_rocnik + 1):
@ -268,6 +304,8 @@ def gen_rocniky(last_rocnik, size):
return rocniky return rocniky
def gen_konfery(size, rnd, organizatori, resitele, soustredeni): def gen_konfery(size, rnd, organizatori, resitele, soustredeni):
logger.info('Generuji konfery (size={})...'.format(size))
konfery = [] konfery = []
for _ in range(1, size): #FIXME Tu range si změňte jak chcete, nevím, co přesně znamená size (asi Anet?) 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, # 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 return konfery
def gen_cisla(rnd, rocniky): def gen_cisla(rnd, rocniky):
logger.info('Generuji čísla...')
rocnik_cisla = [] rocnik_cisla = []
for rocnik in rocniky: for rocnik in rocniky:
otec = True otec = True
@ -317,7 +357,7 @@ def gen_cisla(rnd, rocniky):
cislo = Cislo.objects.create( cislo = Cislo.objects.create(
rocnik = rocnik, rocnik = rocnik,
cislo = str(ci), poradi = str(ci),
datum_vydani=vydano, datum_vydani=vydano,
datum_deadline=deadline, datum_deadline=deadline,
verejne_db=True verejne_db=True
@ -335,6 +375,8 @@ def gen_cisla(rnd, rocniky):
return rocnik_cisla return rocnik_cisla
def gen_temata(rnd, rocniky, rocnik_cisla, organizatori): def gen_temata(rnd, rocniky, rocnik_cisla, organizatori):
logger.info('Generuji témata...')
jake = ["Hravé", "Fyzikální", "Nejlepší", "Totálně masakrální", jake = ["Hravé", "Fyzikální", "Nejlepší", "Totálně masakrální",
"Šokující", "Magnetické", "Modré", "Překvapivé", "Šokující", "Magnetické", "Modré", "Překvapivé",
"Plasmatické", "Novoroční"] "Plasmatické", "Novoroční"]
@ -361,8 +403,9 @@ def gen_temata(rnd, rocniky, rocnik_cisla, organizatori):
garant=rnd.choice(organizatori), garant=rnd.choice(organizatori),
kod=str(n), kod=str(n),
# atributy třídy Téma # atributy třídy Téma
tema_typ=rnd.choice(Tema.TEMA_CHOICES), tema_typ=rnd.choice(Tema.TEMA_CHOICES)[0],
rocnik=rocnik rocnik=rocnik,
abstrakt = "Abstrakt tematka {}".format(n)
) )
konec_tematu = min(rnd.randint(ci, 7), len(cisla)) konec_tematu = min(rnd.randint(ci, 7), len(cisla))
for i in range(ci, konec_tematu+1): for i in range(ci, konec_tematu+1):
@ -378,6 +421,8 @@ def gen_temata(rnd, rocniky, rocnik_cisla, organizatori):
def gen_ulohy_k_tematum(rnd, rocniky, rocnik_cisla, rocnik_temata, 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 # ulohy resene v cisle
jaka = ["Šachová", "Černá", "Větrná", "Dlouhá", "Křehká", "Rychlá", jaka = ["Šachová", "Černá", "Větrná", "Dlouhá", "Křehká", "Rychlá",
"Zákeřná", "Fyzikální"] "Zákeřná", "Fyzikální"]
@ -474,6 +519,8 @@ def gen_ulohy_k_tematum(rnd, rocniky, rocnik_cisla, rocnik_temata, organizatori)
return return
def gen_novinky(rnd, organizatori): def gen_novinky(rnd, organizatori):
logger.info('Generuji novinky...')
jake = ["zábavné", "veselé", "dobrodružné", "skvělé"] jake = ["zábavné", "veselé", "dobrodružné", "skvělé"]
co = ["soustředění", "Fyziklání", "víkendové setkání"] co = ["soustředění", "Fyziklání", "víkendové setkání"]
@ -495,6 +542,8 @@ def otec_syn(otec, syn):
@transaction.atomic @transaction.atomic
def create_test_data(size = 6, rnd = None): def create_test_data(size = 6, rnd = None):
logger.info('Vyrábím testovací data (size={})...'.format(size))
assert size >= 1 assert size >= 1
# pevna pseudo-nahodnost # pevna pseudo-nahodnost
rnd = rnd or random.Random(x=42) 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ě # 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]) 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 . import views, export
from .utils import staff_member_required from .utils import staff_member_required
from django.views.generic.base import RedirectView 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) staff_member_required = user_passes_test(lambda u: u.is_staff)
urlpatterns = [ urlpatterns = [
path('aktualni/temata/', views.TemataRozcestnikView),
path('<int:rocnik>/t<int:tematko>/', views.TematkoView),
# REDIRECTy # REDIRECTy
path('jak-resit/', RedirectView.as_view(url='/co-je-MaM/jak-resit/')), path('jak-resit/', RedirectView.as_view(url='/co-je-MaM/jak-resit/')),
@ -85,19 +89,36 @@ urlpatterns = [
path('stav', path('stav',
staff_member_required(views.StavDatabazeView), name='stav_databaze'), staff_member_required(views.StavDatabazeView), name='stav_databaze'),
path('cislo/<int:rocnik>.<int:cislo>/obalkovani', path('cislo/<int:rocnik>.<int:cislo>/obalkovani',
staff_member_required(views.obalkovaniView), name='seminar_cislo_resitel_obalkovani'), staff_member_required(views.ObalkovaniView.as_view()), name='seminar_cislo_resitel_obalkovani'),
path('cislo/<int:rocnik>.<int:cislo>/tex-download.json', path('cislo/<int:rocnik>.<int:cislo>/tex-download.json',
staff_member_required(views.texDownloadView), name='seminar_tex_download'), staff_member_required(views.texDownloadView), name='seminar_tex_download'),
path('soustredeni/<int:soustredeni>/obalky.pdf', path('soustredeni/<int:soustredeni>/obalky.pdf',
staff_member_required(views.soustredeniObalkyView), name='seminar_soustredeni_obalky'), staff_member_required(views.soustredeniObalkyView), name='seminar_soustredeni_obalky'),
path('tex-upload/login/', views.LoginView, name='seminar_login'), path('tex-upload/login/', views.TeXUploadLoginView, name='seminar_login'),
path( path(
'tex-upload/', 'tex-upload/',
staff_member_required(views.texUploadView), staff_member_required(views.texUploadView),
name='seminar_tex_upload' name='seminar_tex_upload'
), ),
path('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'), path('', views.TitulniStranaView.as_view(), name='titulni_strana'),
# Ceka na autocomplete v3 # Ceka na autocomplete v3

9
seminar/utils.py

@ -2,9 +2,18 @@
import datetime import datetime
from django.contrib.auth.decorators import user_passes_test from django.contrib.auth.decorators import user_passes_test
from html.parser import HTMLParser
staff_member_required = user_passes_test(lambda u: u.is_staff) staff_member_required = user_passes_test(lambda u: u.is_staff)
class FirstTagParser(HTMLParser):
def __init__(self, *args, **kwargs):
self.firstTag = None
super().__init__(*args, **kwargs)
def handle_data(self, data):
if self.firstTag == None:
self.firstTag = data
def histogram(seznam): def histogram(seznam):
d = {} d = {}
for i in seznam: for i in seznam:

654
seminar/views.py

@ -2,22 +2,31 @@
from django.shortcuts import get_object_or_404, render from django.shortcuts import get_object_or_404, render
from django.http import HttpResponse, HttpResponseRedirect, HttpResponseForbidden, JsonResponse from django.http import HttpResponse, HttpResponseRedirect, HttpResponseForbidden, JsonResponse
from django.urls import reverse from django.urls import reverse,reverse_lazy
from django.core.exceptions import PermissionDenied, ObjectDoesNotExist from django.core.exceptions import PermissionDenied, ObjectDoesNotExist
from django.views import generic from django.views import generic
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from django.http import Http404,HttpResponseBadRequest,HttpResponseRedirect from django.http import Http404,HttpResponseBadRequest,HttpResponseRedirect
from django.db.models import Q from django.db.models import Q, Sum, Count
from django.views.decorators.csrf import ensure_csrf_cookie from django.views.decorators.csrf import ensure_csrf_cookie
from django.contrib.auth import authenticate, login from django.views.generic.edit import FormView
from django.contrib.auth import authenticate, login, get_user_model, logout
from .models import Problem, Cislo, Reseni, Nastaveni, Rocnik, Soustredeni, Organizator, Resitel, Novinky, Soustredeni_Ucastnici, Pohadka, Tema, Clanek 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 .models import VysledkyZaCislo, VysledkyKCisluZaRocnik, VysledkyKCisluOdjakziva
from . import utils from . import utils
from .unicodecsv import UnicodeWriter 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 datetime import timedelta, date, datetime
from django.utils import timezone
from itertools import groupby from itertools import groupby
import tempfile import tempfile
import subprocess import subprocess
@ -30,6 +39,7 @@ import json
import traceback import traceback
import sys import sys
import csv import csv
import logging
def verejna_temata(rocnik): 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') return Problem.objects.filter(typ=Problem.TYP_TEMA, cislo_zadani__rocnik=rocnik, cislo_zadani__verejne_db=True).order_by('kod')
def temata_v_rocniku(rocnik):
return Problem.objects.filter(typ=Problem.TYP_TEMA, rocnik=rocnik)
def get_problemy_k_tematu(tema):
return Problemy.objects.filter(nadproblem = tema)
class VlozBodyView(generic.ListView):
template_name = 'seminar/org/vloz_body.html'
def get_queryset(self):
self.tema = get_object_or_404(Problem,id=self.kwargs['tema'])
print(self.tema)
self.problemy = Problem.objects.filter(nadproblem = self.tema)
print(self.problemy)
self.reseni = Reseni.objects.filter(problem__in=self.problemy)
print(self.reseni)
return self.reseni
class ObalkovaniView(generic.ListView):
template_name = 'seminar/org/obalkovani.html'
def get_queryset(self):
rocnik = get_object_or_404(Rocnik,rocnik=self.kwargs['rocnik'])
cislo = get_object_or_404(Cislo,rocnik=rocnik,poradi=self.kwargs['cislo'])
self.cislo = cislo
self.hodnoceni = s.Hodnoceni.objects.filter(cislo_body=cislo)
self.reseni = Reseni.objects.filter(hodnoceni__in = self.hodnoceni).annotate(Sum('hodnoceni__body')).annotate(Count('hodnoceni')).order_by('resitele__osoba')
return self.reseni
def get_context_data(self, **kwargs):
context = super(ObalkovaniView, self).get_context_data(**kwargs)
print(self.cislo)
context['cislo'] = self.cislo
return context
def AktualniZadaniView(request): def AktualniZadaniView(request):
nastaveni = get_object_or_404(Nastaveni) nastaveni = get_object_or_404(Nastaveni)
@ -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): #def ZadaniAktualniVysledkovkaView(request):
# nastaveni = get_object_or_404(Nastaveni) # nastaveni = get_object_or_404(Nastaveni)
@ -143,7 +285,7 @@ class StareNovinkyView(generic.ListView):
# Organizatori # Organizatori
def aktivniOrganizatori(datum=date.today()): def aktivniOrganizatori(datum=timezone.now()):
return Organizator.objects.exclude( return Organizator.objects.exclude(
organizuje_do__isnull=False, organizuje_do__isnull=False,
organizuje_do__lt=datum organizuje_do__lt=datum
@ -252,26 +394,115 @@ class ArchivView(generic.ListView):
context["nahledy"] = "\n".join(tags) context["nahledy"] = "\n".join(tags)
return context return context
### Výsledky
def sloupec_s_poradim(vysledky): # 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.)
# počet řešitelů ve výsledkovce nad aktuálním def sloupec_s_poradim(seznam_s_body):
lepsich_resitelu = 0 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 # seskupíme seznam všech bodů podle hodnot
if len(skupina) == 1: for index in range(0, len(seznam_s_body)):
poradi_l += ["{}.".format(lepsich_resitelu + 1)] # pokud je pořadí větší než číslo řádku, tak jsme vypsali větší rozsah a chceme
# je-li účastníků se stejným počtem bodů víc, pořadí (rozsah X.-Y.) je jen u prvního # 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: else:
poradi_l += [u"{}.–{}.".format(lepsich_resitelu + 1, lepsich_resitelu + len(skupina))] + [""] * (len(skupina)-1) sloupec_s_poradim.append("{}.–{}.".format(aktualni_poradi,
lepsich_resitelu += len(skupina) aktualni_poradi+velikost_skupiny-1))
#pomlcka je opravdu pomlcka v unicode!!dulezite pro vysledkovku v TeXu # 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
# 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
return poradi_l # 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): #def vysledkovka_rocniku(rocnik, jen_verejne=True):
# """Přebírá ročník (např. context["rocnik"]) a vrací výsledkovou listinu ve # """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 # return None
# #
# #vybere vsechny vysledky z posledniho (verejneho) cisla a setridi sestupne dle bodu # #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: # class Vysledkovka:
# def __init__(self): # def __init__(self):
@ -304,7 +535,7 @@ def sloupec_s_poradim(vysledky):
# v.poradi = poradi # v.poradi = poradi
# v.resitel.rocnik = v.resitel.rocnik(rocnik) # 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: # if jen_verejne:
# verejne_vysl_odjakziva = verejne_vysl_odjakziva.filter(cislo__verejna_vysledkovka=True) # verejne_vysl_odjakziva = verejne_vysl_odjakziva.filter(cislo__verejna_vysledkovka=True)
# #
@ -380,16 +611,17 @@ class ProblemView(generic.DetailView):
class VysledkyResitele(object): class VysledkyResitele(object):
"""Pro daného řešitele ukládá počet bodů za jednotlivé úlohy a celkový """Pro daného řešitele ukládá počet bodů za jednotlivé úlohy a celkový
počet bodů za číslo.""" počet bodů za konkrétní ročník do daného čísla a za dané číslo."""
def __init__(self, jmeno, prijmeni): def __init__(self, resitel, cislo, rocnik):
resitel_jmeno = jmeno self.resitel = resitel
resitel_prijmeni = prijmeni self.cislo = cislo
body = {} self.body_cislo = body_resitele_v_cisle(resitel, cislo)
body_cislo = 0 self.body = []
self.rocnik = rocnik
def body_za_cislo(self): self.body_rocnik = body_resitele_v_rocniku(resitel, rocnik, cislo)
return sum(body.values()) self.body_celkem_odjakziva = resitel.vsechny_body()
self.poradi = 0
class CisloView(generic.DetailView): class CisloView(generic.DetailView):
model = Cislo model = Cislo
@ -400,8 +632,8 @@ class CisloView(generic.DetailView):
if queryset is None: if queryset is None:
queryset = self.get_queryset() queryset = self.get_queryset()
rocnik_arg = self.kwargs.get('rocnik') rocnik_arg = self.kwargs.get('rocnik')
cislo_arg = self.kwargs.get('cislo') poradi_arg = self.kwargs.get('cislo')
queryset = queryset.filter(rocnik__rocnik=rocnik_arg, cislo=cislo_arg) queryset = queryset.filter(rocnik__rocnik=rocnik_arg, poradi=poradi_arg)
try: try:
obj = queryset.get() obj = queryset.get()
@ -410,88 +642,53 @@ class CisloView(generic.DetailView):
{'verbose_name': queryset.model._meta.verbose_name}) {'verbose_name': queryset.model._meta.verbose_name})
return obj 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): def get_context_data(self, **kwargs):
context = super(CisloView, self).get_context_data(**kwargs) context = super(CisloView, self).get_context_data(**kwargs)
## TODO upravit dle nového modelu ## TODO upravit dle nového modelu
cislo = context['cislo'] cislo = context['cislo']
hodnoceni = cislo.hodnoceni_set # hodnocení, která se vážou k danému číslu hlavni_problemy = hlavni_problemy_cisla(cislo)
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)
## TODO dostat pro tyto problémy součet v daném čísle pro daného řešitele ## 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 ## TODO možná chytřeji vybírat aktivní řešitele
## chceme letos něco poslal ## chceme letos něco poslal
aktivni_resitele = Resitel.objects.filter( 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) #.filter(hodnoceni_set__rocnik__eq=cislo_rocnik)
radky_vysledkovky = [] radky_vysledkovky = []
for ar in aktivni_resitele: for ar in aktivni_resitele:
vr = VysledkyResitele(ar.jmeno, ar.prijmeni) # získáme výsledky řešitele - součty přes číslo a ročník
for h in hlavni_problemy: vr = VysledkyResitele(ar, cislo, cislo.rocnik)
body = vysledky_resitele_problemu(h, ar, cislo) for hp in hlavni_problemy:
vr.body[h.kod_v_rocniku] = body vr.body.append(
vr.body_cislo = vr.body_cislo + body body_resitele_problemu_v_cisle(hp, ar, cislo))
radky_vysledkovky.append(vr) radky_vysledkovky.append(vr)
## TODO: spočítat počet bodů řešitele v daném ročníku a seřadit je podle toho # setřídíme řádky výsledkovky/objekty VysledkyResitele podle bodů
## TODO: možná použít tyto funkce i v RocnikVysledkovkaView (a umístit sem nebo tam)? 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']).\ # každému řádku výsledkovky přidáme jeho pořadí
# order_by('-body', 'resitel__prijmeni', 'resitel__jmeno') i = 0
# reseni = Reseni.objects.filter(cislo_body = context['cislo']).select_related("resitel") 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 # vytahané informace předáváme do kontextu
# v čísle skutečně zadány context['cislo'] = cislo
# typy_skutecne_zadanych = [Problem.TYP_ULOHA, Problem.TYP_SERIAL, Problem.TYP_ORG_CLANEK] context['radky_vysledkovky'] = radky_vysledkovky
# v_cisle_zadane = Problem.objects.filter(cislo_zadani=context['cislo']).filter(typ__in=typy_skutecne_zadanych).order_by('kod') 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())) # 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 # #setridi problemy podle typu a poradi zadani
# problem_index = {} # problem_index = {}
@ -529,7 +726,6 @@ class CisloView(generic.DetailView):
# context['problemy'] = problemy # context['problemy'] = problemy
# context['v_cisle_zadane'] = v_cisle_zadane # context['v_cisle_zadane'] = v_cisle_zadane
# context['resene_problemy'] = resene_problemy # context['resene_problemy'] = resene_problemy
# return context
class ArchivTemataView(generic.ListView): class ArchivTemataView(generic.ListView):
model = Problem model = Problem
@ -601,9 +797,9 @@ def obalkyView(request,resitele):
tex = render(request,'seminar/archiv/obalky.tex', {'resitele': resitele}).content tex = render(request,'seminar/archiv/obalky.tex', {'resitele': resitele}).content
tempdir = tempfile.mkdtemp() tempdir = tempfile.mkdtemp()
with open(tempdir+"/obalky.tex","wb") as texfile: with open(tempdir+"/obalky.tex","w") as texfile:
texfile.write(tex) 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) subprocess.call(["pdflatex","obalky.tex"],cwd = tempdir)
with open(tempdir+"/obalky.pdf","rb") as pdffile: with open(tempdir+"/obalky.pdf","rb") as pdffile:
@ -612,7 +808,7 @@ def obalkyView(request,resitele):
return response return response
def obalkovaniView(request, rocnik, cislo): def oldObalkovaniView(request, rocnik, cislo):
rocnik = Rocnik.objects.get(rocnik=rocnik) rocnik = Rocnik.objects.get(rocnik=rocnik)
cislo = Cislo.objects.get(rocnik=rocnik, cislo=cislo) cislo = Cislo.objects.get(rocnik=rocnik, cislo=cislo)
@ -749,7 +945,7 @@ def StavDatabazeView(request):
@ensure_csrf_cookie @ensure_csrf_cookie
def LoginView(request): def TeXUploadLoginView(request):
"""Pro přihlášení při nahrávání z texu""" """Pro přihlášení při nahrávání z texu"""
q = request.POST q = request.POST
# nastavení cookie csrftoken # nastavení cookie csrftoken
@ -938,7 +1134,7 @@ def texDownloadView(request, rocnik, cislo):
"body": p.body, "body": p.body,
"zadani": p.text_zadani, "zadani": p.text_zadani,
"reseni": p.text_reseni, "reseni": p.text_reseni,
"cislo_zadani": p.cislo_zadani.cislo, "cislo_zadani": p.cislo_zadani.poradi,
} for p in resene } for p in resene
], ],
} }
@ -947,26 +1143,213 @@ def texDownloadView(request, rocnik, cislo):
cislo.save() cislo.save()
return JsonResponse(response) 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 ## 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): def loginView(request):
# if this is a POST request we need to process the form data
if request.method == 'POST': if request.method == 'POST':
# create a form instance and populate it with data from the request: form = LoginForm(request.POST)
form = NameForm(request.POST)
# check whether it's valid:
if form.is_valid(): if form.is_valid():
# process the data in form.cleaned_data as required user = authenticate(request,
# ... username=form.cleaned_data['username'],
# redirect to a new URL: 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/') return HttpResponseRedirect('/thanks/')
# if a GET (or any other method) we'll create a blank form # if a GET (or any other method) we'll create a blank form
else: else:
form = NameForm() form = PrihlaskaForm()
return render(request, 'seminar/prihlaska.html', {'form': form}) 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 # Ceka na autocomplete v3
# class OrganizatorAutocomplete(autocomplete.Select2QuerySetView): # class OrganizatorAutocomplete(autocomplete.Select2QuerySetView):
# def get_queryset(self): # def get_queryset(self):
@ -987,3 +1370,46 @@ def get_name(request):
# Q(user__last_name__isstartswith=query)) # Q(user__last_name__isstartswith=query))
# #
# return qs # return qs
# FIXME: Tohle asi vlastně vůbec nepatří do aplikace 'seminar'
class LoginView(auth_views.LoginView):
# Jen vezmeme vestavěný a dáme mu vhodný template a přesměrovací URL
template_name = 'seminar/login.html'
# Přesměrovací URL má být v kontextu:
def get_context_data(self, **kwargs):
ctx = super().get_context_data(**kwargs)
ctx['next'] = reverse('titulni_strana')
return ctx
class LogoutView(auth_views.LogoutView):
# Jen vezmeme vestavěný a dáme mu vhodný template a přesměrovací URL
template_name = 'seminar/logout.html'
# Pavel: Vůbec nevím, proč to s _lazy funguje, ale bez toho to bylo rozbité.
next_page = reverse_lazy('titulni_strana')
# "Chci resetovat heslo"
class PasswordResetView(auth_views.PasswordResetView):
#template_name = 'seminar/password_reset.html'
# TODO: vlastní email_template_name a subject_template_name a html_email_template_name
success_url = reverse_lazy('reset_password_done')
from_email = 'login@mam.mff.cuni.cz'
# "Poslali jsme e-mail (pokud bylo kam))"
class PasswordResetDoneView(auth_views.PasswordResetDoneView):
#template_name = 'seminar/password_reset_done.html'
pass
# "Vymysli si heslo"
class PasswordResetConfirmView(auth_views.PasswordResetConfirmView):
#template_name = 'seminar/password_confirm_done.html'
success_url = reverse_lazy('reset_password_complete')
# "Heslo se asi změnilo."
class PasswordResetCompleteView(auth_views.PasswordResetCompleteView):
#template_name = 'seminar/password_complete_done.html'
pass
class PasswordChangeView(auth_views.PasswordChangeView):
#template_name = 'seminar/password_change.html'
success_url = reverse_lazy('titulni_strana')

Loading…
Cancel
Save