diff --git a/.gitignore b/.gitignore index 826b07e2..36b0b565 100644 --- a/.gitignore +++ b/.gitignore @@ -10,7 +10,7 @@ # aux files *.pyc -*.swp +*.sw[mnop] # secrets /django.secret diff --git a/README.md b/README.md index 08842604..4c69c825 100644 --- a/README.md +++ b/README.md @@ -13,11 +13,13 @@ Use git :-) Quickstart ---------- - +Run the following commands: make install_venv . env/bin/activate make install_web +After finishing development, run "deactivate". + Make commands ------------- @@ -56,6 +58,8 @@ Make commands * `./manage.py test` - run the tests. +* `./manage.py shell` - run commands, list elemements of database, check syntax + by importing files, etc. Configurations -------------- diff --git a/galerie/admin.py b/galerie/admin.py index 98c83ea9..f0ac6e28 100644 --- a/galerie/admin.py +++ b/galerie/admin.py @@ -5,7 +5,6 @@ from django.contrib import admin from django.http import HttpResponseRedirect from django import forms from django.db import models -from autocomplete_light import shortcuts as autocomplete_light # akction @@ -39,11 +38,12 @@ class GalerieInline(admin.TabularInline): class ObrazekAdmin(admin.ModelAdmin): list_display = ('obrazek_velky', 'nazev', 'popis', 'obrazek_maly_tag') + search_fields = ['nazev','popis'] class GalerieAdmin(admin.ModelAdmin): - form = autocomplete_light.modelform_factory(Galerie, autocomplete_fields=['titulni_obrazek'], fields=['titulni_obrazek']) model = Galerie fields = ('zobrazit', 'nazev', 'titulni_obrazek', 'popis', 'galerie_up', 'soustredeni', 'poradi') + autocomplete_fields = ['titulni_obrazek'] list_display = ('nazev', 'soustredeni', 'galerie_up', 'poradi', 'zobrazit', 'datum_zmeny') inlines = [GalerieInline] actions = [zverejnit_fotogalerii, prepnout_fotogalerii_do_org_rezimu] diff --git a/galerie/autocomplete_light_registry.py b/galerie/autocomplete_light_registry.py.old similarity index 100% rename from galerie/autocomplete_light_registry.py rename to galerie/autocomplete_light_registry.py.old diff --git a/mamweb/settings_common.py b/mamweb/settings_common.py index a3e41b11..62cd7fe7 100644 --- a/mamweb/settings_common.py +++ b/mamweb/settings_common.py @@ -53,7 +53,7 @@ AUTHENTICATION_BACKENDS = ( MIDDLEWARE = ( - 'reversion.middleware.RevisionMiddleware', +# 'reversion.middleware.RevisionMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.common.CommonMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', @@ -98,13 +98,14 @@ INSTALLED_APPS = ( # Utilities 'sekizai', - 'reversion', +# 'reversion', 'django_countries', 'solo', 'ckeditor', 'ckeditor_uploader', 'taggit', - 'autocomplete_light', + 'dal', + 'dal_select2', 'fluent_comments', 'crispy_forms', @@ -118,6 +119,8 @@ INSTALLED_APPS = ( 'imagekit', + 'polymorphic', + # MaMweb 'mamweb', 'seminar', @@ -215,6 +218,14 @@ LOGGING = { 'handlers': ['console'], 'level': 'DEBUG', }, + 'seminar.prihlaska.form':{ + 'handlers': ['console','registration_logfile'], + 'level': 'INFO' + }, + 'seminar.prihlaska.problem':{ + 'handlers': ['console','mail_registration','registration_error_log'], + 'level': 'INFO' + }, # Catch-all logger '': { @@ -237,6 +248,24 @@ LOGGING = { 'class': 'django.utils.log.AdminEmailHandler', 'formatter': 'verbose', }, + 'mail_registraion': { + 'level': 'WARN', + 'class': 'django.utils.log.AdminEmailHandler', + 'formatter': 'verbose', + }, + 'registration_logfile':{ + 'level': 'INFO', + 'class': 'logging.FileHandler', + # filename declared in specific configuration files + 'formatter': 'verbose', + }, + 'registration_error_log':{ + 'level': 'INFO', + 'class': 'logging.FileHandler', + # filename declared in specific configuration files + 'formatter': 'verbose', + }, + }, } diff --git a/mamweb/settings_debug.py b/mamweb/settings_debug.py new file mode 100644 index 00000000..7dd34fae --- /dev/null +++ b/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, +} diff --git a/mamweb/settings_local.py b/mamweb/settings_local.py index e7709461..1e62009f 100644 --- a/mamweb/settings_local.py +++ b/mamweb/settings_local.py @@ -78,6 +78,11 @@ LOGGING = { # 'handlers': ['console'], # 'propagate': False, #}, + 'werkzeug': { + 'handlers': ['console'], + 'level': 'DEBUG', + 'propagate': True, + }, '': { 'handlers': ['console'], 'level': 'DEBUG', @@ -88,3 +93,5 @@ LOGGING = { # set to 'DEBUG' for EXTRA verbose output # LOGGING['handlers']['console']['level'] = 'INFO' + +EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend' diff --git a/mamweb/settings_prod.py b/mamweb/settings_prod.py index 918fd4b2..1c223842 100644 --- a/mamweb/settings_prod.py +++ b/mamweb/settings_prod.py @@ -61,6 +61,8 @@ CSRF_COOKIE_SECURE = True LOGGING['loggers']['']['handlers'] = ['console', 'mail_admins'] LOGGING['loggers']['django']['handlers'] = ['console', 'mail_admins'] +LOGGING['handlers']['registration_logfile']['filename'] = '/home/mam-web/logs/prod/registration.log' +LOGGING['handlers']['registration_error_log']['filename'] = '/home/mam-web/logs/prod/registration_errors.log' # E-MAIL NOTIFICATIONS diff --git a/mamweb/settings_test.py b/mamweb/settings_test.py index 9f971f45..2d85f2f5 100644 --- a/mamweb/settings_test.py +++ b/mamweb/settings_test.py @@ -65,3 +65,5 @@ CSRF_COOKIE_SECURE = True LOGGING['loggers']['']['handlers'] = ['console', 'mail_admins'] LOGGING['loggers']['django']['handlers'] = ['console', 'mail_admins'] +LOGGING['handlers']['registration_logfile']['filename'] = '/home/mam-web/logs/test/registration.log' +LOGGING['handlers']['registration_error_log']['filename'] = '/home/mam-web/logs/test/registration_errors.log' diff --git a/mamweb/static/css/mamweb.css b/mamweb/static/css/mamweb.css index fae1752c..082d5d36 100644 --- a/mamweb/static/css/mamweb.css +++ b/mamweb/static/css/mamweb.css @@ -862,3 +862,27 @@ div.nahledy_cisel { div.nahledy_cisel div, div.nahledy_cisel img { position: absolute; } +ul.form { + list-style-type: none; + padding-left: 0px; +} +label.field-label { + font-weight: normal; +} +label.field-required { + font-weight: bold; +} +.field-error { + font-size: 14px; + color: red; +} +ul.form li{ + margin-bottom: 3px; +} +p.gdpr { + font-size: 6pt; + margin-bottom: .66em; +} +div.gdpr { + font-size: 6pt; +} diff --git a/mamweb/templates/admin/base_site.html b/mamweb/templates/admin/base_site.html index 2b364bca..7e8707ef 100644 --- a/mamweb/templates/admin/base_site.html +++ b/mamweb/templates/admin/base_site.html @@ -4,7 +4,6 @@ {% block extrahead %} -{% include 'autocomplete_light/static.html' %} {% endblock %} {% block branding %} diff --git a/mamweb/templates/base.html b/mamweb/templates/base.html index 773dbba9..de0b416b 100644 --- a/mamweb/templates/base.html +++ b/mamweb/templates/base.html @@ -27,8 +27,8 @@ } }); - {# script specifický pro stránku #} @@ -97,7 +97,27 @@
-
+ {% sitetree_menu from "main_menu" include "trunk" %} + + {# + {% for item in menu_top %} +
  • + {{ item.name }} +
  • + {% if item.submenu %} + + {% endif %} + {% endfor %} + #} + + +
    {% block content %} {% endblock content %}
    diff --git a/mamweb/urls.py b/mamweb/urls.py index 6294fac4..0c8877a2 100644 --- a/mamweb/urls.py +++ b/mamweb/urls.py @@ -11,7 +11,6 @@ urlpatterns = [ # Admin a nastroje path('admin/', admin.site.urls), # NOQA path('ckeditor/', include('ckeditor_uploader.urls')), - path('autocomplete/', include('autocomplete_light.urls')), # Seminarova aplikace (ma vlastni podadresare) path('', include('seminar.urls')), diff --git a/requirements.txt b/requirements.txt index 8dcb57b6..22f8e43c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -22,11 +22,12 @@ django-solo django-ckeditor django-flat-theme django-taggit -django-autocomplete-light==2.3.6 +django-autocomplete-light django-crispy-forms django-imagekit django-polymorphic django-sitetree +django_reverse_admin # Comments akismet==1.0.1 diff --git a/seminar/admin.py b/seminar/admin.py index d02b4db7..6a9dd815 100644 --- a/seminar/admin.py +++ b/seminar/admin.py @@ -1,36 +1,171 @@ from django.contrib import admin +from polymorphic.admin import PolymorphicParentModelAdmin, PolymorphicChildModelAdmin, PolymorphicChildModelFilter +from reversion.admin import VersionAdmin +from django_reverse_admin import ReverseModelAdmin + +# Todo: reversion + import seminar.models as m -admin.site.register(m.Osoba) admin.site.register(m.Skola) admin.site.register(m.Prijemce) -admin.site.register(m.Resitel) admin.site.register(m.Rocnik) admin.site.register(m.Cislo) admin.site.register(m.Organizator) admin.site.register(m.Soustredeni) -admin.site.register(m.Problem) -admin.site.register(m.Tema) -admin.site.register(m.Clanek) + +@admin.register(m.Osoba) +class OsobaAdmin(admin.ModelAdmin): + actions = ['synchronizuj_maily'] + + def synchronizuj_maily(self, request, queryset): + for o in queryset: + if o.user is not None: + u = o.user + u.email = o.email + u.save() + self.message_user(request, "E-maily synchronizovány.") + synchronizuj_maily.short_description = "Synchronizuj vybraným osobám e-maily do uživatelů" + +@admin.register(m.Problem) +class ProblemAdmin(PolymorphicParentModelAdmin): + base_model = m.Problem + child_models = [ + m.Tema, + m.Clanek, + m.Uloha, + ] + +@admin.register(m.Tema) +class TemaAdmin(PolymorphicChildModelAdmin): + base_model = m.Tema + show_in_index = True + +@admin.register(m.Clanek) +class ClanekAdmin(PolymorphicChildModelAdmin): + base_model = m.Clanek + show_in_index = True + +@admin.register(m.Uloha) +class UlohaAdmin(PolymorphicChildModelAdmin): + base_model = m.Uloha + show_in_index = True + +class TextAdminInline(admin.TabularInline): + model = m.Text + exclude = ['text_zkraceny_set','text_zkraceny'] admin.site.register(m.Text) -admin.site.register(m.Uloha) -admin.site.register(m.Reseni) -admin.site.register(m.Hodnoceni) + +class ResitelInline(admin.TabularInline): + model = m.Resitel + extra = 1 +admin.site.register(m.Resitel) + +class PrilohaReseniInline(admin.TabularInline): + model = m.PrilohaReseni + extra = 1 admin.site.register(m.PrilohaReseni) + +class Reseni_ResiteleInline(admin.TabularInline): + model = m.Reseni_Resitele + +@admin.register(m.Reseni) +class ReseniAdmin(ReverseModelAdmin): + base_model = m.Reseni + inline_type = 'tabular' + inline_reverse = ['text_cely','resitele'] + exclude = ['text_zkraceny', 'text_zkraceny_set'] + inlines = [PrilohaReseniInline] +# FAIL in template +# inlines = [PrilohaReseniInline,Reseni_ResiteleInline] + +admin.site.register(m.Hodnoceni) admin.site.register(m.Pohadka) admin.site.register(m.Konfera) admin.site.register(m.Obrazek) -admin.site.register(m.TreeNode) -admin.site.register(m.RocnikNode) -admin.site.register(m.CisloNode) -admin.site.register(m.MezicisloNode) -admin.site.register(m.TemaVCisleNode) -admin.site.register(m.KonferaNode) -admin.site.register(m.ClanekNode) -admin.site.register(m.UlohaZadaniNode) -admin.site.register(m.PohadkaNode) -admin.site.register(m.UlohaVzorakNode) -admin.site.register(m.TextNode) + + +# Polymorfismus pro stromy +# TODO: Inlines podle https://django-polymorphic.readthedocs.io/en/stable/admin.html + +@admin.register(m.TreeNode) +class TreeNodeAdmin(PolymorphicParentModelAdmin): + base_model = m.TreeNode + child_models = [ + m.RocnikNode, + m.CisloNode, + m.MezicisloNode, + m.TemaVCisleNode, + m.KonferaNode, + m.ClanekNode, + m.UlohaZadaniNode, + m.PohadkaNode, + m.UlohaVzorakNode, + m.TextNode, + ] + + actions = ['aktualizuj_nazvy'] + + # XXX: nejspíš je to totální DB HOG, nechcete to použít moc často. + def aktualizuj_nazvy(self, request, queryset): + newqs = queryset.get_real_instances() + for tn in newqs: + tn.aktualizuj_nazev() + tn.save() + self.message_user(request, "Názvy aktualizovány.") + aktualizuj_nazvy.short_description = "Aktualizuj vybraným TreeNodům názvy" + +@admin.register(m.RocnikNode) +class RocnikNodeAdmin(PolymorphicChildModelAdmin): + base_model = m.RocnikNode + show_in_index = True + +@admin.register(m.CisloNode) +class CisloNodeAdmin(PolymorphicChildModelAdmin): + base_model = m.CisloNode + show_in_index = True + +@admin.register(m.MezicisloNode) +class MezicisloNodeAdmin(PolymorphicChildModelAdmin): + base_model = m.MezicisloNode + show_in_index = True + +@admin.register(m.TemaVCisleNode) +class TemaVCisleNodeAdmin(PolymorphicChildModelAdmin): + base_model = m.TemaVCisleNode + show_in_index = True + +@admin.register(m.KonferaNode) +class KonferaNodeAdmin(PolymorphicChildModelAdmin): + base_model = m.KonferaNode + show_in_index = True + +@admin.register(m.ClanekNode) +class ClanekNodeAdmin(PolymorphicChildModelAdmin): + base_model = m.ClanekNode + show_in_index = True + +@admin.register(m.UlohaZadaniNode) +class UlohaZadaniNodeAdmin(PolymorphicChildModelAdmin): + base_model = m.UlohaZadaniNode + show_in_index = True + +@admin.register(m.PohadkaNode) +class PohadkaNodeAdmin(PolymorphicChildModelAdmin): + base_model = m.PohadkaNode + show_in_index = True + +@admin.register(m.UlohaVzorakNode) +class UlohaVzorakNodeAdmin(PolymorphicChildModelAdmin): + base_model = m.UlohaVzorakNode + show_in_index = True + +@admin.register(m.TextNode) +class TextNodeAdmin(PolymorphicChildModelAdmin): + base_model = m.TextNode + show_in_index = True + + admin.site.register(m.Nastaveni) admin.site.register(m.Novinky) diff --git a/seminar/autocomplete_light_registry.py b/seminar/autocomplete_light_registry.py.old similarity index 100% rename from seminar/autocomplete_light_registry.py rename to seminar/autocomplete_light_registry.py.old diff --git a/seminar/forms.py b/seminar/forms.py index 693e36df..6a0e7911 100644 --- a/seminar/forms.py +++ b/seminar/forms.py @@ -1,6 +1,257 @@ from django import forms +from dal import autocomplete +from django.core.exceptions import ObjectDoesNotExist +from django.contrib.auth.models import User + +from .models import Skola, Resitel, Osoba, Problem +import seminar.models as m + +from datetime import date +import logging + +class LoginForm(forms.Form): + username = forms.CharField(label='Přihlašovací jméno', + max_length=256, + required=True) + password = forms.CharField( + label='Heslo', + max_length=256, + required=True, + widget=forms.PasswordInput()) + + +class PrihlaskaForm(forms.Form): + username = forms.CharField(label='Přihlašovací jméno', + max_length=256, + required=True, + help_text='Tímto jménem se následně budeš přihlašovat pro odevzdání řešení a další činnosti v semináři') + password = forms.CharField( + label='Heslo', + max_length=256, + required=True, + widget=forms.PasswordInput()) + password_check = forms.CharField( + label='Ověření hesla', + max_length=256, + required=True, + widget=forms.PasswordInput()) + + jmeno = forms.CharField(label='Jméno', max_length=256, required=True) + prijmeni = forms.CharField(label='Příjmení', max_length=256, required=True) + pohlavi_muz = forms.ChoiceField(label='Pohlaví', + choices = ((True,'muž'),(False,'žena')), required=True) + email = forms.EmailField(label='E-mail',max_length=256, required=True) + telefon = forms.CharField(label='Telefon',max_length=256, required=False) + datum_narozeni = forms.DateField(label='Datum narození', required=False) + ulice = forms.CharField(label='Ulice', max_length=256, required=False) + mesto = forms.CharField(label='Město', max_length=256, required=False) + psc = forms.CharField(label='PSČ', max_length=32, required=False) + stat = forms.ChoiceField(label='Stát', + choices = (('CZ', 'Česká Republika'), + ('SK', 'Slovenská Republika'), + ('other', 'Jiné')), + required=False) + stat_text = forms.CharField(label='Stát', max_length=256, required=False) + + skola = forms.ModelChoiceField(label="Škola", + queryset=Skola.objects.all(), + widget=autocomplete.ModelSelect2( + url='autocomplete_skola', + attrs = {'data-placeholder--id': '-1', + 'data-placeholder--text' : '---', + 'data-allow-clear': 'true'}) + ,required=False) + + skola_nazev = forms.CharField(label='Název školy', max_length=256, required=False) + skola_adresa = forms.CharField(label='Adresa školy', max_length=256, required=False) + +# trida = forms.CharField(label='Třída',max_length=10, required=True) + + rok_maturity = forms.IntegerField( + label='Rok maturity', + min_value=date.today().year, + max_value=date.today().year+8, + required=True) + zasilat = forms.ChoiceField(label='Kam zasílat čísla a řešení',choices = Resitel.ZASILAT_CHOICES, required=True) + gdpr = forms.BooleanField(label='Souhlasím se zpracováním osobních údajů', required=True) + spam = forms.BooleanField(label='Souhlasím se zasíláním materiálů od MFF UK', required=False) + + def clean_username(self): + err_logger = logging.getLogger('seminar.prihlaska.problem') + username = self.cleaned_data.get('username') + try: + User.objects.get(username=username) + msg = "Username {} exists".format(username) + err_logger.info(msg) + raise forms.ValidationError('Přihlašovací jméno je již použito') + + except ObjectDoesNotExist: + pass + return username + + def clean_email(self): + err_logger = logging.getLogger('seminar.prihlaska.problem') + email = self.cleaned_data.get('email') + try: + Osoba.objects.get(email=email) + msg = "Email {} exists".format(email) + err_logger.info(msg) + raise forms.ValidationError('Email je již použit') + + except ObjectDoesNotExist: + pass + return email + + + def clean(self): + super().clean() + + err_logger = logging.getLogger('seminar.prihlaska.problem') + + data = self.cleaned_data + if data.get('password') != data.get('password_check'): + self.add_error('password_check',forms.ValidationError('Hesla se neshodují')) + if data.get('stat') != '' and data.get('stat_text') != '': + self.add_error('stat',forms.ValidationError('Nelze mít vybraný stát z menu a zároven zapsaný textem')) + if data.get('skola') and (data.get('skola_nazev') or data.get('skola_adresa')): + self.add_error('skola',forms.ValidationError('Pokud je škola v seznamu, nevypisujte ji ručně, pokud není, zrušte výběr ze seznamu (křížek vpravo)')) + if not data.get('skola'): + if data.get('skola_nazev')=='' and data.get('skola_adresa')=='': + self.add_error('skola',forms.ValidationError('Je nutné vyplnit školu')) + elif data.get('skola_nazev')=='': + self.add_error('skola_nazev',forms.ValidationError('Je nutné vyplnit název školy')) + elif data.get('skola_adresa')=='': + self.add_error('skola_adresa',forms.ValidationError('Je nutné vyplnit adresu školy')) + + +class ProfileEditForm(forms.Form): + username = forms.CharField(label='Přihlašovací jméno', + max_length=256, + required=True) + + jmeno = forms.CharField(label='Jméno', max_length=256, required=True) + prijmeni = forms.CharField(label='Příjmení', max_length=256, required=True) + pohlavi_muz = forms.ChoiceField(label='Pohlaví', + choices = ((True,'muž'),(False,'žena')), required=True) + email = forms.EmailField(label='E-mail',max_length=256, required=True) + telefon = forms.CharField(label='Telefon',max_length=256, required=False) + datum_narozeni = forms.DateField(label='Datum narození', required=False) + ulice = forms.CharField(label='Ulice', max_length=256, required=False) + mesto = forms.CharField(label='Město', max_length=256, required=False) + psc = forms.CharField(label='PSČ', max_length=32, required=False) + stat = forms.ChoiceField(label='Stát', + choices = (('CZ', 'Česká Republika'), + ('SK', 'Slovenská Republika'), + ('other', 'Jiné')), + required=False) + stat_text = forms.CharField(label='Stát', max_length=256, required=False) + + skola = forms.ModelChoiceField(label="Škola", + queryset=Skola.objects.all(), + widget=autocomplete.ModelSelect2( + url='autocomplete_skola', + attrs = {'data-placeholder--id': '-1', + 'data-placeholder--text' : '---', + 'data-allow-clear': 'true'}) + ,required=False) + + skola_nazev = forms.CharField(label='Název školy', max_length=256, required=False) + skola_adresa = forms.CharField(label='Adresa školy', max_length=256, required=False) + +# trida = forms.CharField(label='Třída',max_length=10, required=True) + + rok_maturity = forms.IntegerField( + label='Rok maturity', + min_value=date.today().year, + max_value=date.today().year+8, + required=True) + zasilat = forms.ChoiceField(label='Kam zasílat čísla a řešení',choices = Resitel.ZASILAT_CHOICES, required=True) + spam = forms.BooleanField(label='Souhlasím se zasíláním materiálů od MFF UK', required=False) +# def clean_username(self): +# err_logger = logging.getLogger('seminar.prihlaska.problem') +# username = self.cleaned_data.get('username') +# try: +# User.objects.get(username=username) +# msg = "Username {} exists".format(username) +# err_logger.info(msg) +# raise forms.ValidationError('Přihlašovací jméno je již použito') +# +# except ObjectDoesNotExist: +# pass +# return username +# +# def clean_email(self): +# err_logger = logging.getLogger('seminar.prihlaska.problem') +# email = self.cleaned_data.get('email') +# try: +# Osoba.objects.get(email=email) +# msg = "Email {} exists".format(email) +# err_logger.info(msg) +# raise forms.ValidationError('Email je již použit') +# +# except ObjectDoesNotExist: +# pass +# return email + #def clean(self): + # super().clean() + # + # err_logger = logging.getLogger('seminar.prihlaska.problem') + + # data = self.cleaned_data + # if data.get('password') != data.get('password_check'): + # self.add_error('password_check',forms.ValidationError('Hesla se neshodují')) + # if data.get('stat') != '' and data.get('stat_text') != '': + # self.add_error('stat',forms.ValidationError('Nelze mít vybraný stát z menu a zároven zapsaný textem')) + # if data.get('skola') and (data.get('skola_nazev') or data.get('skola_adresa')): + # self.add_error('skola',forms.ValidationError('Pokud je škola v seznamu, nevypisujte ji ručně, pokud není, zrušte výběr ze seznamu (křížek vpravo)')) + # if not data.get('skola'): + # if data.get('skola_nazev')=='' and data.get('skola_adresa')=='': + # self.add_error('skola',forms.ValidationError('Je nutné vyplnit školu')) + # elif data.get('skola_nazev')=='': + # self.add_error('skola_nazev',forms.ValidationError('Je nutné vyplnit název školy')) + # elif data.get('skola_adresa')=='': + # self.add_error('skola_adresa',forms.ValidationError('Je nutné vyplnit adresu školy')) + +class VlozReseniForm(forms.Form): + #FIXME jen podproblémy daného problému + problem = forms.ModelChoiceField(label='Problém',queryset=m.Problem.objects.all()) + # to_field_name + #problem = models.ManyToManyField(Problem, verbose_name='problém', help_text='Problém', + # through='Hodnoceni') + + # FIXME pridat vice resitelu + resitel = forms.ModelChoiceField(label="Řešitel", + queryset=Resitel.objects.all(), + widget=autocomplete.ModelSelect2( + url='autocomplete_resitel', + attrs = {'data-placeholder--id': '-1', + 'data-placeholder--text' : '---', + 'data-allow-clear': 'true'}) + ) + + + #resitele = models.ManyToManyField(Resitel, verbose_name='autoři řešení', + # help_text='Seznam autorů řešení', through='Reseni_Resitele') + + cas_doruceni = forms.DateField(label="Čas doručení") + + #cas_doruceni = models.DateTimeField('čas_doručení', default=timezone.now, blank=True) + + forma = forms.ChoiceField(label="Forma řešení",choices = m.Reseni.FORMA_CHOICES) + #forma = models.CharField('forma řešení', max_length=16, choices=FORMA_CHOICES, blank=False, + # default=FORMA_EMAIL) + + poznamka = forms.CharField(label='Neveřejná poznámka') + #poznamka = models.TextField('neveřejná poznámka', blank=True, + # help_text='Neveřejná poznámka k řešení (plain text)') + + #TODO body do cisla + #TODO prilohy + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + #self.fields['favorite_color'] = forms.ChoiceField(choices=[(color.id, color.name) for color in Resitel.objects.all()]) + -class NameForm(forms.Form): - your_name = forms.CharField(label='Your name', max_length=100) diff --git a/seminar/management/commands/nukedb.py b/seminar/management/commands/nukedb.py new file mode 100644 index 00000000..f253841a --- /dev/null +++ b/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') diff --git a/seminar/management/commands/testdata.py b/seminar/management/commands/testdata.py index 6a7fb30c..d7c65367 100644 --- a/seminar/management/commands/testdata.py +++ b/seminar/management/commands/testdata.py @@ -15,21 +15,34 @@ User = django.contrib.auth.get_user_model() class Command(BaseCommand): - help = "Clear database and load testing data." - - def handle(self, *args, **options): - assert settings.DEBUG == True - dbfile = settings.DATABASES['default']['NAME'] - if os.path.exists(dbfile): - os.rename(dbfile, dbfile + '.old') - self.stderr.write('Stara databaze prejmenovana na "%s"' % (dbfile + '.old')) - call_command('migrate', no_input=True) - self.stdout.write('Vytvarim uzivatele "admin" (heslo "admin") a pseudo-nahodna data ...') - create_test_data(size=8) - self.stdout.write('Vytvoreno {} uzivatelu, {} skol, {} resitelu, {} rocniku, {} cisel,' - ' {} problemu, {} reseni.'.format(User.objects.count(), Skola.objects.count(), - Resitel.objects.count(), Rocnik.objects.count(), Cislo.objects.count(), - Problem.objects.count(), Reseni.objects.count())) + help = "Clear database and load testing data." + + def add_arguments(self, parser): + parser.add_argument( + '--no-clean', + action='store_true', + help='Změny se provedou v aktuální DB, ne v čisté. Aktuální DB se nezachová. (jen k debugování)', + ) + parser.add_argument( + '--no-migrate', + action='store_true', + help='Neprovádět migrace před generováním testovacích dat (jen k debugování)', + ) + + def handle(self, *args, **options): + assert settings.DEBUG == True + dbfile = settings.DATABASES['default']['NAME'] + if os.path.exists(dbfile) and not options['no_clean']: + os.rename(dbfile, dbfile + '.old') + self.stderr.write('Stara databaze prejmenovana na "%s"' % (dbfile + '.old')) + if not options['no_migrate']: + call_command('migrate', no_input=True) + self.stdout.write('Vytvarim uzivatele "admin" (heslo "admin") a pseudo-nahodna data ...') + create_test_data(size=8) + self.stdout.write('Vytvoreno {} uzivatelu, {} skol, {} resitelu, {} rocniku, {} cisel,' + ' {} problemu, {} reseni.'.format(User.objects.count(), Skola.objects.count(), + Resitel.objects.count(), Rocnik.objects.count(), Cislo.objects.count(), + Problem.objects.count(), Reseni.objects.count())) diff --git a/seminar/migrations/0001_squashed_0067_auto_20190814_0805.py b/seminar/migrations/0001_squashed_0067_auto_20190814_0805.py new file mode 100644 index 00000000..05d5f265 --- /dev/null +++ b/seminar/migrations/0001_squashed_0067_auto_20190814_0805.py @@ -0,0 +1,2164 @@ +# Generated by Django 2.2.6 on 2019-10-30 20:20 + +from django.conf import settings +from django.db import migrations, models +import django.db.migrations.operations.special +import django.db.models.deletion +import django.utils.timezone +import django_countries.fields +import imagekit.models.fields +import seminar.models +import taggit.managers + +# migr 0053 +import datetime as dt + +# migr 0058 +from django.db.models import Q + + +# Functions from the following migrations need manual copying. +# Move them and any dependencies into this file, then update the +# RunPython operations to refer to the local versions: + +# seminar.migrations.0051_resitel_to_osoba + +def resitel_to_osoba(apps,schema_editor): + Resitel = apps.get_model('seminar','Resitel') + Osoba = apps.get_model('seminar','Osoba') + for r in Resitel.objects.all(): + o = Osoba() + o.datum_narozeni = r.datum_narozeni + o.datum_registrace = r.datum_prihlaseni + o.datum_souhlasu_udaje = r.datum_souhlasu_udaje + o.datum_souhlasu_zasilani = r.datum_souhlasu_zasilani + o.email = r.email + o.jmeno = r.jmeno + o.mesto = r.mesto + o.pohlavi_muz = r.pohlavi_muz + o.prijmeni = r.prijmeni + o.psc = r.psc + o.stat = r.stat + o.telefon = r.telefon + o.ulice = r.ulice + o.user = r.user + if o.user: + u = o.user + if u.first_name: + if not o.jmeno: + o.jmeno = u.first_name + u.first_name = 'Použij osobu!' + elif o.jmeno == u.first_name: + u.first_name = 'Použij osobu!' + else: + raise ValueError('jmeno a first_name rozdílné: "{}" vs. "{}"'.format(o.jmeno, u.first_name)) + if u.last_name: + if not o.prijmeni: + o.prijmeni = u.last_name + u.last_name = 'Použij osobu!' + elif o.prijmeni == u.last_name: + u.last_name = 'Použij osobu!' + else: + raise ValueError('prijmeni a last_name rozdílné: "{}" vs. "{}"'.format(o.prijmeni, u.last_name)) + if u.email: + if not o.email: + o.email = u.email + u.email = 'Použij osobu!' + elif o.email == u.email: + u.email = 'Použij osobu!' + else: + raise ValueError('o.email a u.email rozdílné: "{}" vs. "{}"'.format(o.email, u.email)) + u.save() + + + o.save() + r.osoba = o + r.save() + +def osoba_to_resitel(apps, schema_editor): + Resitel = apps.get_model('seminar','Resitel') + Osoba = apps.get_model('seminar','Osoba') + for r in Resitel.objects.all(): + o = r.osoba + r.datum_narozeni = o.datum_narozeni + r.datum_prihlaseni = o.datum_registrace + r.datum_souhlasu_udaje = o.datum_souhlasu_udaje + r.datum_souhlasu_zasilani = o.datum_souhlasu_zasilani + r.email = o.email + r.jmeno = o.jmeno + r.mesto = o.mesto + r.pohlavi_muz = o.pohlavi_muz + r.prijmeni = o.prijmeni + r.psc = o.psc + r.stat = o.stat + r.telefon = o.telefon + r.ulice = o.ulice + r.user = o.user + r.save() + o.delete() + + +# seminar.migrations.0052_user_to_organizator + +def spoj_k_organizatorum_osoby(apps, scema_editor): + Organizator = apps.get_model('seminar', 'Organizator') + Resitel = apps.get_model('seminar', 'Resitel') + Osoba = apps.get_model('seminar', 'Osoba') + for org in Organizator.objects.all(): + + # Spárování organizátora s osobou + user = org.user + resitele = Resitel.objects.filter(user=user) + if resitele.count() != 0: + osoba = resitele.first().osoba + else: + osoba = Osoba(user=user) + + # Přesun informací z usera do osoby + # pro řešitele již v minule migraci + osoba.jmeno = user.first_name + osoba.prijmeni = user.last_name + osoba.email = user.email + user.jmeno = "Použij osobu!" + user.prijmeni = "Použij osobu!" + user.email = "Použij osobu!" + user.save() + + # Přesun informací z organizátora do jeho osoby + osoba.prezdivka = org.prezdivka if org.prezdivka is not None else '' + osoba.foto = org.foto + + # Všechno uložit + osoba.save() + org.osoba = osoba + org.save() + +def fix_problem(apps, schema_editor): + Problem = apps.get_model('seminar', 'Problem') + Organizator = apps.get_model('seminar', 'Organizator') + for pr in Problem.objects.all(): + if pr.autor_old is not None: + pr.autor = Organizator.objects.filter(osoba__user=pr.autor_old).first() + else: + pr.autor = None + if pr.opravovatel is not None: + pr.opravovatele.add(Organizator.objects.filter(osoba__user=pr.opravovatel).first()) + pr.save() + +def fix_pohadka(apps, schema_editor): + Pohadka = apps.get_model('seminar', 'Pohadka') + Organizator = apps.get_model('seminar', 'Organizator') + for poh in Pohadka.objects.all(): + if poh.autor_old is not None: + poh.autor = Organizator.objects.filter(osoba__user=poh.autor_old).first() + else: + poh.autor = None + poh.save() + +def fix_novinka(apps, schema_editor): + Novinky = apps.get_model('seminar', 'Novinky') + Organizator = apps.get_model('seminar', 'Organizator') + for nov in Novinky.objects.all(): + nov.autor = Organizator.objects.filter(osoba__user=nov.autor_old).first() + nov.save() + +# seminar.migrations.0053_organizator_organizuje_od_do + +def rok_to_datetime(apps,schema_editor): + Organizator = apps.get_model('seminar','Organizator') + for o in Organizator.objects.all(): + rok = o.organizuje_od_roku + if rok: + o.organizuje_od = dt.datetime(rok,1,1) + rok = o.organizuje_do_roku + if rok: + o.organizuje_do = dt.datetime(rok,12,31) + o.save() + +def datetime_to_rok(apps,schema_editor): + Organizator = apps.get_model('seminar','Organizator') + for o in Organizator.objects.all(): + o.organizuje_od_roku = o.organizuje_od.year + o.organizuje_do_roku = o.organizuje_do.year + o.save() + +# seminar.migrations.0056_vrcholy_pro_rocniky_a_cisla + +def generuj_RocnikNody_a_CisloNody(apps,schema_editor): + Rocnik = apps.get_model('seminar', 'Rocnik') + RocnikNode = apps.get_model('seminar', 'RocnikNode') + Cislo = apps.get_model('seminar', 'Cislo') + CisloNode = apps.get_model('seminar', 'CisloNode') + + last_rn = None # last_* slouží k navázání následníků + for r in Rocnik.objects.all(): + rn = RocnikNode.objects.create(rocnik=r) + rn.save() + rn.root = rn + rn.save() + if last_rn: + last_rn.succ = rn + last_rn.save() + last_rn = rn + + last_cn = None + for c in Cislo.objects.filter(rocnik=r): + cn = CisloNode.objects.create(cislo=c, root=rn) + cn.save() + if last_cn: # Jsme něčí následník + last_cn.succ = cn + last_cn.save() + else: # Jsme první v řadě, takže se musíme přidat jako first_child RočníkNodu + rn.first_child = cn + rn.save() + last_cn = cn + +# seminar.migrations.0057_reseni_to_reseni_hodnoceni + +def reseni_to_Reseni(apps, schema_editor): + Reseni = apps.get_model('seminar','Reseni') + Reseni_Resitele = apps.get_model('seminar','Reseni_Resitele') + Hodnoceni = apps.get_model('seminar','Hodnoceni') + + for r in Reseni.objects.all(): + rr = Reseni_Resitele.objects.create(resitele = r.resitel, reseni=r) + if r.body == None: + print("!!!!!!!!!!!!!!!") + print(r.id,r) + print("!!!!!!!!!!!!!!!") + else: + h = Hodnoceni.objects.create( + body=r.body, + cislo_body = r.cislo_body, + problem = r.problem_old, + reseni = r) + +# seminar.migrations.0058_problem_to_uloha_tema_clanek + +def poskladej_strom(apps, rodic, *texty): + Text = apps.get_model('seminar', 'Text') + TextNode = apps.get_model('seminar', 'TextNode') + if not rodic: + raise ValueError("Rodič musí být definovaný") + + uz_ma_deti = False + tn = None + for txt in texty: + if not txt: + continue + # Přidej do stromu: + textobj = Text.objects.create(na_web = txt) + textobj.save() + textnode = TextNode.objects.create(text = textobj) + textnode.save() + if not uz_ma_deti: + rodic.first_child = textnode + rodic.save() + tn = rodic.first_child + uz_ma_deti = True + else: + tn.succ = textnode + tn.save() + tn = tn.succ + +def uloha_to_Uloha(apps,schema_editor): + Problem = apps.get_model('seminar', 'Problem') + Uloha = apps.get_model('seminar', 'Uloha') + Text = apps.get_model('seminar', 'Text') + UlohaZadaniNode = apps.get_model('seminar', 'UlohaZadaniNode') + UlohaVzorakNode = apps.get_model('seminar', 'UlohaVzorakNode') + TextNode = apps.get_model('seminar', 'TextNode') + + ulohy = Problem.objects.filter(typ = 'uloha') + for uold in ulohy: + unew = Uloha.objects.create( + problem_ptr = uold, + # Zakomentované fieldy by se už měly nacházet v příslušném problému + #nazev = uold.nazev, + #stav = uold.stav, + #zamereni = uold.zamereni, + #poznamka = uold.poznamka, + #autor = uold.autor, + #kod = uold.kod, + cislo_zadani = uold.cislo_zadani_old, + cislo_reseni = uold.cislo_reseni_old, + max_body = uold.body, + #vytvoreno = uold.vytvoreno, + ) +# unew.opravovatele.add(*uold.opravovatele.all()) + unew.save() + + # Nody: + zadani_node = UlohaZadaniNode.objects.create(uloha = unew) + poskladej_strom(apps, zadani_node, uold.text_zadani) + zadani_node.save() + vzorak_node = UlohaVzorakNode.objects.create(uloha = unew) + poskladej_strom(apps, vzorak_node, uold.text_reseni) + vzorak_node.save() + +def konfery_rucne(apps, schema_editor): + # Tohle dělat nebudu, máme aktuálně celou jednu. Ale "Errors should never pass silently" + Problem = apps.get_model('seminar', 'Problem') + pocet_konfer = Problem.objects.filter(typ = 'konfera').count() + if pocet_konfer > 0: + raise NotImplementedError("Zkonvertuj {} konfer na objekt Konfera ručně, prosím".format(pocet_konfer)) + +def clanek_to_Clanek(apps,schema_editor): + Problem = apps.get_model('seminar', 'Problem') + Clanek = apps.get_model('seminar', 'Clanek') + ClanekNode = apps.get_model('seminar', 'ClanekNode') + Text = apps.get_model('seminar', 'Text') + TextNode = apps.get_model('seminar', 'TextNode') + + clanky = Problem.objects.filter(Q(typ='org-clanek') | Q(typ='res-clanek')) + for cl in clanky: + # Vybereme vhodné číslo pro článek z čísla zadání a čísla řešení: + if cl.cislo_zadani_old is None: + cislo = cl.cislo_reseni_old + elif cl.cislo_reseni_old is None: + cislo = cl.cislo_zadani_old + elif cl.cislo_reseni_old == cl.cislo_zadani_old: + cislo = cl.cislo_zadani_old + else: + raise ValueError("Různá čísla zadání a řešení u článku! (Článek: {})".format(cl.nazev)) + + clnew = Clanek.objects.create( + problem_ptr = cl, + # Problém by nemělo být potřeba upravovat + cislo = cislo, + # Body ignorujeme, protože už jsou v hodnocení + ) + clnew.save() + + # Aktuálně nemáme v modelu informaci o tom, jestli je to org-článek + # nebo řešitelský článek. Aby se neztratila informace, poznamenám to do + # poznámky. + cl.poznamka += "\nTyp:\t{}".format(cl.typ) + cl.save() + + # Vyrobíme nody: + clnode = ClanekNode(clanek = clnew) + poskladej_strom(apps, clnode, cl.text_zadani, cl.text_reseni) + clnode.save() + +def tema_to_Tema(apps, schema_editor): + Problem = apps.get_model('seminar', 'Problem') + Tema = apps.get_model('seminar', 'Tema') + TemaVCisleNode = apps.get_model('seminar', 'TemaVCisleNode') + Text = apps.get_model('seminar', 'Text') + TextNode = apps.get_model('seminar', 'TextNode') + + temata = Problem.objects.filter(Q(typ = 'tema') | Q(typ='serial')) + for t in temata: + # Vymyslíme správně ročník: + if t.cislo_zadani_old is None and t.cislo_reseni_old is None: + rocnik = None + elif t.cislo_zadani_old is None: + rocnik = t.cislo_reseni_old.rocnik + elif t.cislo_reseni_old is None: + rocnik = t.cislo_zadani_old.rocnik + elif t.cislo_reseni_old.rocnik == t.cislo_zadani_old.rocnik: + rocnik = t.cislo_zadani_old.rocnik + else: + raise ValueError("Nelze mít téma přes více ročníků! (Téma: {}".format(t.nazev)) + + tnew = Tema.objects.create( + problem_ptr = t, + tema_typ = t.typ, + rocnik = rocnik, + ) + tnew.save() + + # Nody: + tnode = TemaVCisleNode(tema = tnew) + poskladej_strom(apps, tnode, t.text_zadani, t.text_reseni) + tnode.save() + +# seminar.migrations.0059_vytvorit_pohadkanode + +def vytvor_pohadkanode(apps, schema_editor): + Pohadka = apps.get_model('seminar', 'Pohadka') + PohadkaNode = apps.get_model('seminar', 'PohadkaNode') + Text = apps.get_model('seminar', 'Text') + TextNode = apps.get_model('seminar', 'TextNode') + + for p in Pohadka.objects.all(): + t = Text.objects.create(na_web = p.text) + t.save() + tn = TextNode.objects.create(text = t) + tn.save() + pn = PohadkaNode.objects.create(pohadka = p, first_child = tn) + pn.save() + +# seminar.migrations.0060_spoj_stromy + +def pridej_potomka(rodic, potomek): + # Daný vrchol bude posledním potomkem rodiče + uz_ma_deti = False + posledni = None + + # Přidávaný potomek by neměl mít následovníka -- přidáváme potomka, ne podles. + if potomek.succ: + raise ValueError("Potomek má následovníka, to je velmi podezřelé!") + + # Najdeme aktuálně posledního potomka: + if rodic.first_child: + uz_ma_deti = True + posledni = rodic.first_child + while posledni.succ: + posledni = posledni.succ + + # Nastavíme kořen: + potomek.root = rodic.root + potomek.save() + + # Připojíme vrchol: + if uz_ma_deti: + posledni.succ = potomek + posledni.save() + else: + rodic.first_child = potomek + rodic.save() + +def pokacej_les(apps, schema_editor): + # Teď je potřeba všechny TreeNody příslušející k zadaným problémům připojit + # do hlavního stromu + # Tohle je jednoduchá verze: nejdřív témátka a seriály, pak úložky a pohádky, + # pak články a konfery, pak vzoráky, všechno setříděné podle kódu (FIXME?) + + # Kopírování je častým zdrojem chyb! + Cislo = apps.get_model('seminar', 'Cislo') + Tema = apps.get_model('seminar', 'Tema') + Konfera = apps.get_model('seminar', 'Konfera') + Clanek = apps.get_model('seminar', 'Clanek') + Uloha = apps.get_model('seminar', 'Uloha') + Problem = apps.get_model('seminar', 'Problem') + Pohadka = apps.get_model('seminar', 'Pohadka') + + for c in Cislo.objects.all().reverse(): + cnode = c.cislonode + + # Témata a seriály: + relevantni_temata = Tema.objects.filter(Q(cislo_zadani_old = c) | Q(cislo_reseni_old = c)).order_by('kod') + # Téma dáme do prvního čísla, kde se vyskytne + for t in relevantni_temata: + tnode = t.temavcislenode + if t.cislo_zadani_old and t.cislo_reseni_old: + assert(t.cislo_zadani_old <= t.cislo_reseni_old) + if t.cislo_reseni_old == c: + # Už by mělo být přidané do čísla zadání + continue + else: + # Patří sem (buď je to jediné číslo, nebo je to číslo zadání) + pridej_potomka(cnode, tnode) + + # Úložky (zadání) a pohádky + for u in Uloha.objects.filter(cislo_zadani = c).order_by('kod'): + unode = u.ulohazadaninode + pohadky_pred = Pohadka.objects.filter(uloha_old = u.problem_ptr, pred = True) + pohadky_po = Pohadka.objects.filter(uloha_old = u.problem_ptr, pred = False) + for p in pohadky_pred: + pnode = p.pohadkanode + pridej_potomka(cnode, pnode) + pridej_potomka(cnode, unode) + for p in pohadky_po: + pnode = p.pohadkanode + pridej_potomka(cnode, pnode) + + # Pohádky, které nejsou u úlohy jsou špatně: + if Pohadka.objects.exclude(uloha_old__typ='uloha').count(): + raise ValueError("Existuje pohádka, která není u úlohy") + + # Články + for cl in Clanek.objects.filter(cislo = c).order_by('kod'): + clnode = cl.claneknode + pridej_potomka(cnode, clnode) + + # Konfery + for k in Konfera.objects.all(): + knode = k.konferanode + if k.reseni and knode.root is None: + # Takováhle konfera nejspíš neexistuje + raise NotImplementedError("Konfery neumím zapojit do stromu") + + # Vzoráky + for u in Uloha.objects.filter(cislo_reseni = c).order_by('kod'): + unode = u.ulohavzoraknode + pridej_potomka(cnode, unode) + +# seminar.migrations.0065_treenode_polymorphic_ctype + +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) + +# seminar.migrations.0066_problem_polymorphic_ctype + +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): + atomic = False + replaces = [('seminar', '0001_initial'), ('seminar', '0002_add_body_views'), ('seminar', '0003_add_skola_zs_ss'), ('seminar', '0004_add_old_dakos_id'), ('seminar', '0005_alter_problem_autor'), ('seminar', '0006_problem_add_timestamp'), ('seminar', '0007_problem_zamereni'), ('seminar', '0008_reseni_forma'), ('seminar', '0009_rename_imported_IDs'), ('seminar', '0010_alter_rok_maturity'), ('seminar', '0011_alter_timestamp_def'), ('seminar', '0012_remove_soustredeni_ucastnici'), ('seminar', '0013_soustredeni_ucastnici_through_model'), ('seminar', '0014_uprava_poznamek'), ('seminar', '0015_soustredeni_text'), ('seminar', '0016_texty_problemu'), ('seminar', '0017_texty_problemu_minor'), ('seminar', '0018_problemnavrh_problemzadany'), ('seminar', '0019_rocnik_ciselne'), ('seminar', '0020_indexy_a_razeni'), ('seminar', '0021_cislo_verejna_vysledkovka'), ('seminar', '0022_decimal_body'), ('seminar', '0023_add_novinky'), ('seminar', '0024_add_organizator'), ('seminar', '0025_zmena_cesty_nahravani_obrazku'), ('seminar', '0026_soustredeni_typ'), ('seminar', '0027_export_flag_a_typ_akce'), ('seminar', '0028_add_body_celkem_views'), ('seminar', '0029_fix_body_celkem_views'), ('seminar', '0030_add_vysledky'), ('seminar', '0031_cislo_pdf'), ('seminar', '0032_cislo_pdf_blank_typos'), ('seminar', '0033_organizator_studuje_popisek'), ('seminar', '0034_reseni_forma_default_email'), ('seminar', '0035_django_imagekit'), ('seminar', '0036_add_org_to_soustredeni'), ('seminar', '0037_prispevek'), ('seminar', '0038_change_meta_prispevek'), ('seminar', '0039_pohadka'), ('seminar', '0040_pohadka_nepovinny_autor'), ('seminar', '0041_konfery'), ('seminar', '0042_cislo_faze'), ('seminar', '0043_uprava_faze'), ('seminar', '0044_uprava_faze'), ('seminar', '0045_cislo_pridani_faze_nahrano'), ('seminar', '0042_auto_20161005_0847'), ('seminar', '0046_merge'), ('seminar', '0047_auto_20170120_2118'), ('seminar', '0048_add_cislo_datum_deadline_soustredeni'), ('seminar', '0049_auto_20190430_2354'), ('seminar', '0050_auto_20190510_2228'), ('seminar', '0051_resitel_to_osoba'), ('seminar', '0052_user_to_organizator'), ('seminar', '0053_organizator_organizuje_od_do'), ('seminar', '0055_smazat_nemigrovane_zastarale_veci'), ('seminar', '0056_vrcholy_pro_rocniky_a_cisla'), ('seminar', '0057_reseni_to_reseni_hodnoceni'), ('seminar', '0058_problem_to_uloha_tema_clanek'), ('seminar', '0059_vytvorit_pohadkanode'), ('seminar', '0060_spoj_stromy'), ('seminar', '0061_kill_frankenstein'), ('seminar', '0062_redukce_modelu_pohadky'), ('seminar', '0063_procisteni_migraci'), ('seminar', '0064_auto_20190610_2358'), ('seminar', '0065_treenode_polymorphic_ctype'), ('seminar', '0066_problem_polymorphic_ctype'), ('seminar', '0067_auto_20190814_0805')] + + initial = True + + dependencies = [ + ('contenttypes', '0002_remove_content_type_name'), + ('taggit', '0001_initial'), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name='Cislo', + fields=[ + ('id', models.AutoField(primary_key=True, serialize=False)), + ('cislo', models.CharField(help_text='Většinou jen "1", vyjímečně "7-8", lexikograficky určije pořadí v ročníku!', max_length=32, verbose_name='název čísla')), + ('datum_vydani', models.DateField(blank=True, help_text='Datum vydání finální verze', null=True, verbose_name='datum vydání')), + ('datum_deadline', models.DateField(blank=True, help_text='Datum pro příjem řešení úloh zadaných v tomto čísle', null=True, verbose_name='datum deadline')), + ('verejne_db', models.BooleanField(db_column='verejne', default=False, verbose_name='číslo zveřejněno')), + ], + options={ + 'ordering': ['rocnik__rocnik', 'cislo'], + 'db_table': 'seminar_cisla', + 'verbose_name': 'Číslo', + 'verbose_name_plural': 'Čísla', + }, + ), + migrations.CreateModel( + name='Problem', + fields=[ + ('id', models.AutoField(primary_key=True, serialize=False)), + ('nazev', models.CharField(max_length=256, verbose_name='název')), + ('typ', models.CharField(choices=[(b'uloha', 'Úloha'), (b'tema', 'Téma'), (b'serial', 'Seriál'), (b'org-clanek', 'Organizátorský článek'), (b'res-clanek', 'Řesitelský článek')], default=b'uloha', max_length=32, verbose_name='typ problému')), + ('stav', models.CharField(choices=[(b'navrh', 'Návrh'), (b'zadany', 'Zadaný'), (b'smazany', 'Smazaný')], default=b'navrh', max_length=32, verbose_name='stav problému')), + ('text_problemu_org', models.TextField(blank=True, verbose_name='organizátorský (neveřejný) text')), + ('text_problemu', models.TextField(blank=True, verbose_name='veřejný text zadání a řešení')), + ('kod', models.CharField(blank=True, default=b'', help_text='Číslo/kód úlohy v čísle nebo kód tématu/článku/seriálu v ročníku', max_length=32, verbose_name='lokální kód')), + ('body', models.IntegerField(blank=True, null=True, verbose_name='maximum bodů')), + ('autor', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='autor_uloh', to=settings.AUTH_USER_MODEL, verbose_name='autor problému')), + ('cislo_reseni', models.ForeignKey(blank=True, help_text='Číslo s řešením úlohy, jen pro úlohy', null=True, on_delete=django.db.models.deletion.CASCADE, related_name='resene_problemy', to='seminar.Cislo', verbose_name='číslo řešení')), + ('cislo_zadani', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='zadane_problemy', to='seminar.Cislo', verbose_name='číslo zadání')), + ('opravovatel', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='opravovatel_uloh', to=settings.AUTH_USER_MODEL, verbose_name='opravovatel')), + ], + options={ + 'db_table': 'seminar_problemy', + 'verbose_name': 'Problém', + 'verbose_name_plural': 'Problémy', + }, + ), + migrations.CreateModel( + name='Resitel', + fields=[ + ('id', models.AutoField(primary_key=True, serialize=False)), + ('jmeno', models.CharField(max_length=256, verbose_name='jméno')), + ('prijmeni', models.CharField(max_length=256, verbose_name='příjmení')), + ('pohlavi_muz', models.BooleanField(default=False, verbose_name='pohlaví (muž)')), + ('rok_maturity', models.IntegerField(verbose_name='rok maturity')), + ('email', models.EmailField(blank=True, default=b'', max_length=256, verbose_name='e-mail')), + ('telefon', models.CharField(blank=True, default=b'', max_length=256, verbose_name='telefon')), + ('datum_narozeni', models.DateField(blank=True, null=True, verbose_name='datum narození')), + ('datum_souhlasu_udaje', models.DateField(blank=True, help_text='Datum souhlasu se zpracováním osobních údajů', null=True, verbose_name='datum souhlasu (údaje)')), + ('datum_souhlasu_zasilani', models.DateField(blank=True, help_text='Datum souhlasu se zasíláním MFF materiálů', null=True, verbose_name='datum souhlasu (spam)')), + ('datum_prihlaseni', models.DateField(default=django.utils.timezone.now, verbose_name='datum přihlášení')), + ('zasilat', models.CharField(choices=[(b'domu', 'Domů'), (b'do_skoly', 'Do školy'), (b'nikam', 'Nikam')], default=b'domu', max_length=32, verbose_name='kam zasílat')), + ('ulice', models.CharField(blank=True, default=b'', max_length=256, verbose_name='ulice')), + ('mesto', models.CharField(blank=True, default=b'', max_length=256, verbose_name='město')), + ('psc', models.CharField(blank=True, default=b'', max_length=32, verbose_name='PSČ')), + ('stat', django_countries.fields.CountryField(default=b'CZ', help_text='ISO 3166-1 kód země velkými písmeny (CZ, SK, ...)', max_length=2, verbose_name='stát')), + ('poznamka', models.TextField(blank=True, help_text='Neveřejná poznámka k řešiteli (plain text)', verbose_name='neveřejná poznámka')), + ], + options={ + 'ordering': ['prijmeni', 'jmeno'], + 'db_table': 'seminar_resitele', + 'verbose_name': 'Řešitel', + 'verbose_name_plural': 'Řešitelé', + }, + ), + migrations.CreateModel( + name='Rocnik', + fields=[ + ('id', models.AutoField(primary_key=True, serialize=False)), + ('prvni_rok', models.IntegerField(verbose_name='první rok')), + ('rocnik', models.CharField(max_length=16, verbose_name='číslo ročníku')), + ], + options={ + 'ordering': ['rocnik'], + 'db_table': 'seminar_rocniky', + 'verbose_name': 'Ročník', + 'verbose_name_plural': 'Ročníky', + }, + ), + migrations.CreateModel( + name='Skola', + fields=[ + ('id', models.AutoField(primary_key=True, serialize=False)), + ('aesop_id', models.CharField(blank=True, default=b'', help_text='Aesopi ID typu "izo:..." nebo "aesop:..."', max_length=32, verbose_name='Aesop ID')), + ('izo', models.CharField(blank=True, help_text='IZO školy (jen české školy)', max_length=32, verbose_name='IZO')), + ('nazev', models.CharField(help_text='Celý název školy', max_length=256, verbose_name='název')), + ('kratky_nazev', models.CharField(blank=True, help_text=b'Zkr\xc3\xa1cen\xc3\xbd n\xc3\xa1zev pro zobrazen\xc3\xad ve v\xc3\xbdsledkovce', max_length=256, verbose_name='zkrácený název')), + ('ulice', models.CharField(max_length=256, verbose_name='ulice')), + ('mesto', models.CharField(max_length=256, verbose_name='město')), + ('psc', models.CharField(max_length=32, verbose_name='PSČ')), + ('stat', django_countries.fields.CountryField(default=b'CZ', help_text='ISO 3166-1 kód zeme velkými písmeny (CZ, SK, ...)', max_length=2, verbose_name='stát')), + ('poznamka', models.TextField(blank=True, help_text='Neveřejná poznámka ke škole (plain text)', verbose_name='neveřejná poznámka')), + ], + options={ + 'db_table': 'seminar_skoly', + 'verbose_name': 'Škola', + 'verbose_name_plural': 'Školy', + }, + ), + migrations.CreateModel( + name='Soustredeni', + fields=[ + ('id', models.AutoField(primary_key=True, serialize=False)), + ('datum_zacatku', models.DateField(blank=True, help_text='První den soustředění', null=True, verbose_name='datum začátku')), + ('datum_konce', models.DateField(blank=True, help_text='Poslední den soustředění', null=True, verbose_name='datum konce')), + ('verejne_db', models.BooleanField(db_column='verejne', default=False, verbose_name='soustředění zveřejněno')), + ('misto', models.CharField(blank=True, default=b'', help_text='Místo (název obce, volitelně též objektu', max_length=256, verbose_name='místo soustředění')), + ('rocnik', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='soustredeni', to='seminar.Rocnik', verbose_name='ročník')), + ('ucastnici', models.ManyToManyField(db_table='seminar_soustredeni_ucastnici', help_text='Seznam účastníků soustředění', to='seminar.Resitel', verbose_name='účastníci soustředění')), + ], + options={ + 'ordering': ['rocnik__rocnik', 'datum_zacatku'], + 'db_table': 'seminar_soustredeni', + 'verbose_name': 'Soustředění', + 'verbose_name_plural': 'Soustředění', + }, + ), + migrations.AddField( + model_name='resitel', + name='skola', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='seminar.Skola', verbose_name='škola'), + ), + migrations.AddField( + model_name='resitel', + name='user', + field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='uživatel'), + ), + migrations.CreateModel( + name='Reseni', + fields=[ + ('id', models.AutoField(primary_key=True, serialize=False)), + ('body', models.IntegerField(blank=True, null=True, verbose_name='body')), + ('timestamp', models.DateTimeField(auto_now=True, verbose_name='vytvořeno')), + ('poznamka', models.TextField(blank=True, help_text='Neveřejná poznámka k řešení (plain text)', verbose_name='neveřejná poznámka')), + ('cislo_body', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='bodovana_reseni', to='seminar.Cislo', verbose_name='číslo pro body')), + ('problem', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='reseni', to='seminar.Problem', verbose_name='problém')), + ('resitel', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='reseni', to='seminar.Resitel', verbose_name='řešitel')), + ], + options={ + 'ordering': ['problem', 'resitel'], + 'db_table': 'seminar_reseni', + 'verbose_name': 'Řešení', + 'verbose_name_plural': 'Řešení', + }, + ), + migrations.CreateModel( + name='PrilohaReseni', + fields=[ + ('id', models.AutoField(primary_key=True, serialize=False)), + ('timestamp', models.DateTimeField(auto_now=True, verbose_name='vytvořeno')), + ('soubor', models.FileField(upload_to=seminar.models.generate_filename, verbose_name='soubor')), + ('poznamka', models.TextField(blank=True, help_text='Neveřejná poznámka k příloze řešení (plain text), např. o původu', verbose_name='neveřejná poznámka')), + ('reseni', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='prilohy', to='seminar.Reseni', verbose_name='řešení')), + ], + options={ + 'ordering': ['reseni', 'timestamp'], + 'db_table': 'seminar_priloha_reseni', + 'verbose_name': 'Příloha řešení', + 'verbose_name_plural': 'Přílohy řešení', + }, + ), + migrations.CreateModel( + name='Nastaveni', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('aktualni_cislo', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='seminar.Cislo', verbose_name='poslední vydané číslo')), + ('aktualni_rocnik', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='seminar.Rocnik', verbose_name='aktuální ročník')), + ], + options={ + 'db_table': 'seminar_nastaveni', + 'verbose_name': 'Nastavení semináře', + }, + ), + migrations.AddField( + model_name='cislo', + name='rocnik', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='cisla', to='seminar.Rocnik', verbose_name='ročník'), + ), + migrations.AlterField( + model_name='reseni', + name='poznamka', + field=models.TextField(blank=True, help_text='Neveřejná poznámka k řešení (plain text, editace v detailu řešení)', verbose_name='neveřejná poznámka'), + ), + migrations.AddField( + model_name='skola', + name='je_ss', + field=models.BooleanField(default=True, verbose_name='střední stupeň'), + ), + migrations.AddField( + model_name='skola', + name='je_zs', + field=models.BooleanField(default=True, verbose_name='základní stupeň'), + ), + migrations.AlterField( + model_name='problem', + name='autor', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='autor_uloh', to=settings.AUTH_USER_MODEL, verbose_name='autor problému'), + ), + migrations.AddField( + model_name='problem', + name='zamereni', + field=taggit.managers.TaggableManager(blank=True, help_text=b'Zam\xc4\x9b\xc5\x99en\xc3\xad M/F/I/O probl\xc3\xa9mu, p\xc5\x99\xc3\xadp. dal\xc5\xa1\xc3\xad tagy', through='taggit.TaggedItem', to='taggit.Tag', verbose_name='zaměření'), + ), + migrations.AddField( + model_name='reseni', + name='forma', + field=models.CharField(choices=[(b'papir', 'Papírové řešení'), (b'email', 'Emailem'), (b'upload', 'Upload přes web')], default=b'papir', max_length=16, verbose_name='forma řešení'), + ), + migrations.AddField( + model_name='resitel', + name='import_mamoper_id', + field=models.CharField(blank=True, default=b'', help_text='MAMOPER.MM_RIESITELIA.ID z DAKOS importu, jen historický význam', max_length=32, verbose_name='importované MM_RIESITELIA.ID'), + ), + migrations.AddField( + model_name='skola', + name='import_dakos_id', + field=models.CharField(blank=True, default=b'', help_text='DKSROOT.V_SKOLA.ID z DAKOS importu, jen historický význam', max_length=32, verbose_name='importované DKSROOT.V_SKOLA.ID'), + ), + migrations.AlterField( + model_name='resitel', + name='rok_maturity', + field=models.IntegerField(blank=True, null=True, verbose_name='rok maturity'), + ), + migrations.AlterField( + model_name='prilohareseni', + name='timestamp', + field=models.DateTimeField(blank=True, default=django.utils.timezone.now, editable=False, verbose_name='vytvořeno'), + ), + migrations.AddField( + model_name='problem', + name='import_dakos_id', + field=models.CharField(blank=True, default=b'', help_text='ID z importu z DAKOSU s prefixem podle původu: "AZAD:xxx (MAMOPER.MM_AZAD), ""DOZ:xxx" (MAMOPER.MM_DOZ), "ZAD:rocnik.cislo.uloha.typ" (MAMOPER.MM_ZADANIA), "ULOHA:xxx" (MAMOPER.MM_ULOHY)', max_length=32, verbose_name='importované ID s typem'), + ), + migrations.AddField( + model_name='problem', + name='timestamp', + field=models.DateTimeField(blank=True, default=django.utils.timezone.now, editable=False, verbose_name='vytvořeno'), + ), + migrations.AlterField( + model_name='reseni', + name='timestamp', + field=models.DateTimeField(blank=True, default=django.utils.timezone.now, editable=False, verbose_name='vytvořeno'), + ), + migrations.RemoveField( + model_name='soustredeni', + name='ucastnici', + ), + migrations.CreateModel( + name='Soustredeni_Ucastnici', + fields=[ + ('id', models.AutoField(primary_key=True, serialize=False)), + ('poznamka', models.TextField(blank=True, help_text='Neveřejná poznámka k účasti (plain text)', verbose_name='neveřejná poznámka')), + ('resitel', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='seminar.Resitel', verbose_name='řešitel')), + ('soustredeni', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='seminar.Soustredeni', verbose_name='soustředění')), + ], + options={ + 'ordering': ['soustredeni', 'resitel'], + 'db_table': 'seminar_soustredeni_ucastnici', + 'verbose_name': 'Účast na soustředění', + 'verbose_name_plural': 'Účasti na soustředění', + }, + ), + migrations.AddField( + model_name='soustredeni', + name='ucastnici', + field=models.ManyToManyField(help_text='Seznam účastníků soustředění', through='seminar.Soustredeni_Ucastnici', to='seminar.Resitel', verbose_name='účastníci soustředění'), + ), + migrations.AlterModelOptions( + name='problem', + options={'ordering': ['nazev'], 'verbose_name': 'Problém', 'verbose_name_plural': 'Problémy'}, + ), + migrations.AlterModelOptions( + name='reseni', + options={'ordering': ['problem_id', 'resitel_id'], 'verbose_name': 'Řešení', 'verbose_name_plural': 'Řešení'}, + ), + migrations.AlterModelOptions( + name='skola', + options={'ordering': ['mesto', 'nazev'], 'verbose_name': 'Škola', 'verbose_name_plural': 'Školy'}, + ), + migrations.AddField( + model_name='cislo', + name='poznamka', + field=models.TextField(blank=True, help_text='Neveřejná poznámka k číslu (plain text)', verbose_name='neveřejná poznámka'), + ), + migrations.AlterField( + model_name='reseni', + name='poznamka', + field=models.TextField(blank=True, help_text='Neveřejná poznámka k řešení (plain text)', verbose_name='neveřejná poznámka'), + ), + migrations.AddField( + model_name='soustredeni', + name='text', + field=models.TextField(blank=True, default=b'', verbose_name='text k soustředění (HTML)'), + ), + migrations.RenameField( + model_name='problem', + old_name='text_problemu_org', + new_name='text_org', + ), + migrations.AlterField( + model_name='problem', + name='text_org', + field=models.TextField(blank=True, verbose_name='neveřejné zadání a organizátorské a poznámky'), + ), + migrations.RenameField( + model_name='problem', + old_name='text_problemu', + new_name='text_zadani', + ), + migrations.AlterField( + model_name='problem', + name='text_org', + field=models.TextField(blank=True, help_text='Neveřejný návrh úlohy, návrh řešení, text zadání, poznámky ...', verbose_name='org poznámky (HTML)'), + ), + migrations.AlterField( + model_name='problem', + name='text_zadani', + field=models.TextField(blank=True, help_text='Veřejný text zadání (HTML)', verbose_name='veřejné zadání (HTML)'), + ), + migrations.AddField( + model_name='problem', + name='text_reseni', + field=models.TextField(blank=True, help_text='Veřejný text řešení (HTML, u témat i příspěvky a komentáře)', verbose_name='veřejné řešení (HTML)'), + ), + migrations.CreateModel( + name='ProblemNavrh', + fields=[ + ], + options={ + 'verbose_name': 'Problém (návrh)', + 'proxy': True, + 'verbose_name_plural': 'Problémy (návrhy)', + }, + bases=('seminar.problem',), + ), + migrations.CreateModel( + name='ProblemZadany', + fields=[ + ], + options={ + 'verbose_name': 'Problém (zadaný)', + 'proxy': True, + 'verbose_name_plural': 'Problémy (zadané)', + }, + bases=('seminar.problem',), + ), + migrations.AddField( + model_name='rocnik', + name='rocnik_n', + field=models.IntegerField(default=0, verbose_name='číslo ročníku'), + preserve_default=False, + ), + migrations.RunSQL( + sql='update seminar_rocniky set rocnik_n = cast (rocnik as integer)', + ), + migrations.RemoveField( + model_name='rocnik', + name='rocnik', + ), + migrations.RenameField( + model_name='rocnik', + old_name='rocnik_n', + new_name='rocnik', + ), + migrations.AlterModelOptions( + name='cislo', + options={'ordering': ['-rocnik__rocnik', '-cislo'], 'verbose_name': 'Číslo', 'verbose_name_plural': 'Čísla'}, + ), + migrations.AlterModelOptions( + name='reseni', + options={'ordering': ['problem_id', 'resitel__prijmeni', 'resitel__jmeno'], 'verbose_name': 'Řešení', 'verbose_name_plural': 'Řešení'}, + ), + migrations.AlterModelOptions( + name='rocnik', + options={'ordering': ['-rocnik'], 'verbose_name': 'Ročník', 'verbose_name_plural': 'Ročníky'}, + ), + migrations.AlterField( + model_name='cislo', + name='cislo', + field=models.CharField(db_index=True, help_text='Většinou jen "1", vyjímečně "7-8", lexikograficky určije pořadí v ročníku!', max_length=32, verbose_name='název čísla'), + ), + migrations.AlterField( + model_name='rocnik', + name='prvni_rok', + field=models.IntegerField(db_index=True, unique=True, verbose_name='první rok'), + ), + migrations.AlterField( + model_name='rocnik', + name='rocnik', + field=models.IntegerField(db_index=True, unique=True, verbose_name='číslo ročníku'), + ), + migrations.AddField( + model_name='cislo', + name='verejna_vysledkovka', + field=models.BooleanField(default=False, help_text='Je-li false u veřejného čísla, není výsledkovka zatím veřejná.', verbose_name='zveřejněna výsledkovka'), + ), + migrations.AlterField( + model_name='problem', + name='body', + field=models.DecimalField(blank=True, decimal_places=1, max_digits=8, null=True, verbose_name='maximum bodů'), + ), + migrations.AlterField( + model_name='reseni', + name='body', + field=models.DecimalField(blank=True, decimal_places=1, max_digits=8, null=True, verbose_name='body'), + ), + migrations.CreateModel( + name='Novinky', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('datum', models.DateField(auto_now_add=True)), + ('text', models.TextField(blank=True, null=True, verbose_name=b'Text novinky')), + ('obrazek', models.ImageField(blank=True, null=True, upload_to=b'image_novinky/%Y/%m/%d/', verbose_name=b'Obr\xc3\xa1zek')), + ('zverejneno', models.BooleanField(default=False, verbose_name=b'Zve\xc5\x99ejn\xc4\x9bno')), + ('autor', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name=b'Autor novinky')), + ], + options={ + 'verbose_name': 'Novinka', + 'verbose_name_plural': 'Novinky', + }, + ), + migrations.CreateModel( + name='Organizator', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('prezdivka', models.CharField(blank=True, max_length=32, null=True, verbose_name=b'P\xc5\x99ezd\xc3\xadvka')), + ('organizuje_od_roku', models.IntegerField(blank=True, null=True, verbose_name=b'Organizuje od roku')), + ('organizuje_do_roku', models.IntegerField(blank=True, null=True, verbose_name=b'Organizuje do roku')), + ('studuje', models.CharField(blank=True, max_length=256, null=True, verbose_name=b'Studuje')), + ('strucny_popis_organizatora', models.TextField(blank=True, null=True, verbose_name=b'Stru\xc4\x8dn\xc3\xbd popis organiz\xc3\xa1tora')), + ('foto', models.ImageField(blank=True, help_text=b'Vlo\xc5\xbe fotografii organiz\xc3\xa1tora o libovon\xc3\xa9 velikosti', null=True, upload_to=b'image_organizatori/velke/%Y/', verbose_name=b'Fotografie organiz\xc3\xa1tora')), + ('foto_male', models.ImageField(blank=True, editable=False, null=True, upload_to=b'image_organizatori/male/%Y/')), + ('user', models.OneToOneField(help_text=b'Vyber \xc3\xba\xc4\x8det sp\xc5\x99a\xc5\xbeen\xc3\xbd s organiz\xc3\xa1torem.', on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name=b'Osoba')), + ], + options={ + 'verbose_name': 'Organizátor', + 'verbose_name_plural': 'Organizátoři', + }, + ), + migrations.AddField( + model_name='rocnik', + name='exportovat', + field=models.BooleanField(db_column='exportovat', default=False, help_text='Exportuje se jen podle tohoto flagu (ne veřejnosti), a to jen čísla s veřejnou výsledkovkou', verbose_name='export do AESOPa'), + ), + migrations.AddField( + model_name='soustredeni', + name='exportovat', + field=models.BooleanField(db_column='exportovat', default=False, help_text='Exportuje se jen podle tohoto flagu (ne veřejnosti)', verbose_name='export do AESOPa'), + ), + migrations.AddField( + model_name='soustredeni', + name='typ', + field=models.CharField(choices=[(b'jarni', 'Jarní soustředění'), (b'podzimni', 'Podzimní soustředění'), (b'vikend', 'Víkendový sraz')], default=b'podzimni', max_length=16, verbose_name='typ akce'), + ), + migrations.AlterField( + model_name='organizator', + name='foto', + field=models.ImageField(blank=True, help_text=b'Vlo\xc5\xbe fotografii organiz\xc3\xa1tora o libovoln\xc3\xa9 velikosti', null=True, upload_to=b'image_organizatori/velke/%Y/', verbose_name=b'Fotografie organiz\xc3\xa1tora'), + ), + migrations.AlterModelOptions( + name='soustredeni', + options={'ordering': ['-rocnik__rocnik', '-datum_zacatku'], 'verbose_name': 'Soustředění', 'verbose_name_plural': 'Soustředění'}, + ), + migrations.AlterField( + model_name='cislo', + name='cislo', + field=models.CharField(db_index=True, help_text='Většinou jen "1", vyjímečně "7-8", lexikograficky určuje pořadí v ročníku!', max_length=32, verbose_name='název čísla'), + ), + migrations.AddField( + model_name='cislo', + name='pdf', + field=models.FileField(blank=True, help_text='Pdf čísla, které si mohou řešitelé stáhnout', null=True, upload_to=seminar.models.cislo_pdf_filename, verbose_name='pdf'), + ), + migrations.AlterField( + model_name='problem', + name='typ', + field=models.CharField(choices=[(b'uloha', 'Úloha'), (b'tema', 'Téma'), (b'serial', 'Seriál'), (b'org-clanek', 'Organizátorský článek'), (b'res-clanek', 'Řešitelský článek')], default=b'uloha', max_length=32, verbose_name='typ problému'), + ), + migrations.AlterField( + model_name='skola', + name='stat', + field=django_countries.fields.CountryField(default=b'CZ', help_text='ISO 3166-1 kód země velkými písmeny (CZ, SK, ...)', max_length=2, verbose_name='stát'), + ), + migrations.AlterField( + model_name='organizator', + name='studuje', + field=models.CharField(blank=True, help_text=b"Nap\xc5\x99. 'Studuje Obecnou fyziku (Bc.), 3. ro\xc4\x8dn\xc3\xadk', 'Vystudovala Diskr\xc3\xa9tn\xc3\xad modely a algoritmy (Mgr.)' nebo 'P\xc5\x99edn\xc3\xa1\xc5\xa1\xc3\xad na MFF'", max_length=256, null=True, verbose_name=b'Studium aj.'), + ), + migrations.AlterField( + model_name='reseni', + name='forma', + field=models.CharField(choices=[(b'papir', 'Papírové řešení'), (b'email', 'Emailem'), (b'upload', 'Upload přes web')], default=b'email', max_length=16, verbose_name='forma řešení'), + ), + migrations.RemoveField( + model_name='organizator', + name='foto_male', + ), + migrations.AlterField( + model_name='organizator', + name='foto', + field=imagekit.models.fields.ProcessedImageField(blank=True, help_text=b'Vlo\xc5\xbe fotografii organiz\xc3\xa1tora o libovoln\xc3\xa9 velikosti', null=True, upload_to=b'image_organizatori/velke/%Y/', verbose_name=b'Fotografie organiz\xc3\xa1tora'), + ), + migrations.CreateModel( + name='Soustredeni_Organizatori', + fields=[ + ('id', models.AutoField(primary_key=True, serialize=False)), + ('poznamka', models.TextField(blank=True, help_text='Neveřejná poznámka k účasti organizátora (plain text)', verbose_name='neveřejná poznámka')), + ('organizator', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='seminar.Organizator', verbose_name='organizátor')), + ('soustredeni', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='seminar.Soustredeni', verbose_name='soustředění')), + ], + options={ + 'ordering': ['soustredeni', 'organizator'], + 'db_table': 'seminar_soustredeni_organizatori', + 'verbose_name': 'Účast organizátorů na soustředění', + 'verbose_name_plural': 'Účasti organizátorů na soustředění', + }, + ), + migrations.AddField( + model_name='soustredeni', + name='organizatori', + field=models.ManyToManyField(help_text='Seznam organizátorů soustředění', through='seminar.Soustredeni_Organizatori', to='seminar.Organizator', verbose_name='Organizátoři soustředění'), + ), + migrations.CreateModel( + name='Prispevek', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('nazev', models.CharField(max_length=200, verbose_name=b'N\xc3\xa1zev')), + ('text_org', models.TextField(blank=True, null=True, verbose_name=b'Orgovsk\xc3\xbd text')), + ('text_resitel', models.TextField(blank=True, null=True, verbose_name=b'\xc5\x98e\xc5\xa1itelsk\xc3\xbd text')), + ('zverejnit', models.BooleanField(verbose_name=b'Zve\xc5\x99ejnit?')), + ('problem', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='seminar.Problem', verbose_name=b'Probl\xc3\xa9m')), + ('reseni', models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='seminar.Reseni', verbose_name=b'\xc5\x98e\xc5\xa1en\xc3\xad')), + ], + options={ + 'abstract': False, + 'verbose_name': 'Příspěvek k problému', + 'verbose_name_plural': 'Příspěvky k problémům', + }, + ), + migrations.CreateModel( + name='Konfera', + fields=[ + ('id', models.AutoField(primary_key=True, serialize=False)), + ('nazev', models.CharField(help_text='Název konfery', max_length=40, verbose_name='název konfery')), + ('popis', models.TextField(blank=True, help_text='Popis konfery k zobrazení na webu', verbose_name='popis konfery')), + ('abstrakt', models.TextField(blank=True, help_text='Abstrakt konfery tak, jak byl uveden ve sborníku', verbose_name='abstrakt')), + ('org_poznamka', models.TextField(blank=True, help_text='Neveřejná poznámka ke konfeře(plain text)', verbose_name='neveřejná poznámka')), + ('typ_prezentace', models.CharField(choices=[(b'veletrh', 'Veletrh (postery)'), (b'prezentace', 'Prezentace (přednáška)')], default=b'veletrh', max_length=16, verbose_name='typ prezentace')), + ('prezentace', models.FileField(help_text='Prezentace nebo fotka posteru', upload_to=seminar.models.generate_filename_konfera, verbose_name='prezentace')), + ('materialy', models.FileField(help_text='Další materiály ke konfeře zabalené do jednoho souboru', upload_to=seminar.models.generate_filename_konfera, verbose_name='materialy')), + ('organizator', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='konfery', to='seminar.Organizator', verbose_name='organizátor')), + ('soustredeni', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='konfery', to='seminar.Soustredeni', verbose_name='soustředění')), + ], + options={ + 'db_table': 'seminar_konfera', + 'verbose_name': 'Konfera', + 'verbose_name_plural': 'Konfery', + }, + ), + migrations.CreateModel( + name='Konfery_Ucastnici', + fields=[ + ('id', models.AutoField(primary_key=True, serialize=False)), + ('poznamka', models.TextField(blank=True, help_text='Neveřejná poznámka k účasti (plain text)', verbose_name='neveřejná poznámka')), + ('konfera', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='seminar.Konfera', verbose_name='konfera')), + ('resitel', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='seminar.Resitel', verbose_name='řešitel')), + ], + options={ + 'ordering': ['konfera', 'resitel'], + 'db_table': 'seminar_konfery_ucastnici', + 'verbose_name': 'Účast na konfeře', + 'verbose_name_plural': 'Účasti na konfeře', + }, + ), + migrations.AlterField( + model_name='problem', + name='typ', + field=models.CharField(choices=[(b'uloha', 'Úloha'), (b'tema', 'Téma'), (b'serial', 'Seriál'), (b'konfera', 'Konfera'), (b'org-clanek', 'Organizátorský článek'), (b'res-clanek', 'Řešitelský článek')], default=b'uloha', max_length=32, verbose_name='typ problému'), + ), + migrations.AddField( + model_name='konfera', + name='ucastnici', + field=models.ManyToManyField(help_text='Seznam účastníků konfery', through='seminar.Konfery_Ucastnici', to='seminar.Resitel', verbose_name='účastníci konfery'), + ), + migrations.AlterField( + model_name='konfera', + name='materialy', + field=models.FileField(blank=True, help_text='Další materiály ke konfeře zabalené do jednoho souboru', upload_to=seminar.models.generate_filename_konfera, verbose_name='materialy'), + ), + migrations.AlterField( + model_name='konfera', + name='prezentace', + field=models.FileField(blank=True, help_text='Prezentace nebo fotka posteru', upload_to=seminar.models.generate_filename_konfera, verbose_name='prezentace'), + ), + migrations.AddField( + model_name='konfera', + name='prispevek', + field=models.ForeignKey(blank=True, help_text='Účastnický přípěvek o konfeře', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='konfery', to='seminar.Problem', verbose_name='příspěvek do čísla'), + ), + migrations.AddField( + model_name='cislo', + name='faze', + field=models.CharField(choices=[('admin', 'Úpravy na webu'), ('tex', 'Úpravy v TeXu'), ('nahrano', 'Nahráno na web')], default='admin', help_text='Během fáze "Úpravy na webu" se obsah čísla vytváří (a případně komentuje) ve webovém rozhraní. Během fáze "Úpravy v TeXu" už obsah ve webovém rozhraní editovat nelze a návrhy na úpravy se píší do korekturovátka a zanášejí do gitu. Z něj se pak vygeneruje verze pro web a číslo se přepne do fáze "Nahráno na web", což jen znamená, že už nejde automaticky stáhnout obsah pro založení čísla v TeXu.', max_length=32, verbose_name='Fáze vytváření obsahu'), + ), + migrations.AddField( + model_name='cislo', + name='datum_deadline_soustredeni', + field=models.DateField(blank=True, help_text='Datum pro příjem řešení pro účast na soustředění', null=True, verbose_name='datum deadline soustředění'), + ), + migrations.CreateModel( + name='Osoba', + fields=[ + ('id', models.AutoField(primary_key=True, serialize=False)), + ('jmeno', models.CharField(max_length=256, verbose_name='jméno')), + ('prijmeni', models.CharField(max_length=256, verbose_name='příjmení')), + ('prezdivka', models.CharField(max_length=256, verbose_name='přezdívka')), + ('pohlavi_muz', models.BooleanField(default=False, verbose_name='pohlaví (muž)')), + ('email', models.EmailField(blank=True, default='', max_length=256, verbose_name='e-mail')), + ('telefon', models.CharField(blank=True, default='', max_length=256, verbose_name='telefon')), + ('datum_narozeni', models.DateField(blank=True, null=True, verbose_name='datum narození')), + ('datum_souhlasu_udaje', models.DateField(blank=True, help_text='Datum souhlasu se zpracováním osobních údajů', null=True, verbose_name='datum souhlasu (údaje)')), + ('datum_souhlasu_zasilani', models.DateField(blank=True, help_text='Datum souhlasu se zasíláním MFF materiálů', null=True, verbose_name='datum souhlasu (spam)')), + ('datum_registrace', models.DateField(default=django.utils.timezone.now, verbose_name='datum registrace do semináře')), + ('ulice', models.CharField(blank=True, default='', max_length=256, verbose_name='ulice')), + ('mesto', models.CharField(blank=True, default='', max_length=256, verbose_name='město')), + ('psc', models.CharField(blank=True, default='', max_length=32, verbose_name='PSČ')), + ('stat', django_countries.fields.CountryField(default='CZ', help_text='ISO 3166-1 kód země velkými písmeny (CZ, SK, ...)', max_length=2, verbose_name='stát')), + ('poznamka', models.TextField(blank=True, help_text='Neveřejná poznámka k osobě (plain text)', verbose_name='neveřejná poznámka')), + ('foto', imagekit.models.fields.ProcessedImageField(blank=True, help_text='Vlož fotografii osoby o libovolné velikosti', null=True, upload_to='image_osoby/velke/%Y/', verbose_name='Fotografie osoby')), + ('user', models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='uživatel')), + ], + options={ + 'verbose_name': 'Osoba', + 'verbose_name_plural': 'Osoby', + 'db_table': 'seminar_osoby', + 'ordering': ['prijmeni', 'jmeno'], + }, + ), + migrations.CreateModel( + name='Prijemce', + fields=[ + ('id', models.AutoField(primary_key=True, serialize=False)), + ('poznamka', models.TextField(blank=True, help_text='Neveřejná poznámka k příemci čísel (plain text)', verbose_name='neveřejná poznámka')), + ('osoba', models.ForeignKey(help_text='Které osobě či na jakou adresu se mají zasílat čísla', on_delete=django.db.models.deletion.CASCADE, to='seminar.Osoba', verbose_name='komu')), + ], + options={ + 'verbose_name': 'příjemce', + 'verbose_name_plural': 'příjemce', + 'db_table': 'seminar_prijemce', + }, + ), + migrations.CreateModel( + name='Text', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('na_web', models.TextField(blank=True, help_text='Text ke zveřejnění na webu', verbose_name='text na web')), + ('do_cisla', models.TextField(blank=True, help_text='Text ke zveřejnění v čísle', verbose_name='text do čísla')), + ], + options={ + 'verbose_name': 'text', + 'verbose_name_plural': 'texty', + 'db_table': 'seminar_texty', + }, + ), + migrations.CreateModel( + name='Uloha', + fields=[ + ('problem_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='seminar.Problem')), + ('max_body', models.DecimalField(blank=True, decimal_places=1, max_digits=8, null=True, verbose_name='maximum bodů')), + ], + options={ + 'verbose_name': 'Úloha', + 'verbose_name_plural': 'Úlohy', + 'db_table': 'seminar_ulohy', + }, + bases=('seminar.problem',), + ), + migrations.AlterModelOptions( + name='novinky', + options={'ordering': ['-datum'], 'verbose_name': 'Novinka', 'verbose_name_plural': 'Novinky'}, + ), + migrations.AlterModelOptions( + name='prilohareseni', + options={'ordering': ['reseni', 'vytvoreno'], 'verbose_name': 'Příloha řešení', 'verbose_name_plural': 'Přílohy řešení'}, + ), + migrations.AlterModelOptions( + name='reseni', + options={'ordering': ['-cas_doruceni'], 'verbose_name': 'Řešení', 'verbose_name_plural': 'Řešení'}, + ), + migrations.AlterModelOptions( + name='resitel', + options={'ordering': ['osoba'], 'verbose_name': 'Řešitel', 'verbose_name_plural': 'Řešitelé'}, + ), + migrations.RenameField( + model_name='konfera', + old_name='org_poznamka', + new_name='poznamka', + ), + migrations.RenameField( + model_name='reseni', + old_name='timestamp', + new_name='cas_doruceni', + ), + migrations.RenameField( + model_name='prilohareseni', + old_name='timestamp', + new_name='vytvoreno', + ), + migrations.RenameField( + model_name='problem', + old_name='text_org', + new_name='poznamka', + ), + migrations.RenameField( + model_name='problem', + old_name='timestamp', + new_name='vytvoreno', + ), + migrations.RenameField( + model_name='problem', + old_name='cislo_zadani', + new_name='cislo_zadani_old', + ), + migrations.RenameField( + model_name='problem', + old_name='cislo_reseni', + new_name='cislo_reseni_old', + ), + migrations.AddField( + model_name='konfera', + name='anotace', + field=models.TextField(blank=True, help_text='Popis, o čem bude konfera.', verbose_name='anotace'), + ), + migrations.AddField( + model_name='organizator', + name='organizuje_do', + field=models.DateTimeField(blank=True, null=True, verbose_name='Organizuje do'), + ), + migrations.AddField( + model_name='organizator', + name='organizuje_od', + field=models.DateTimeField(blank=True, null=True, verbose_name='Organizuje od'), + ), + migrations.AddField( + model_name='organizator', + name='skola', + field=models.CharField(blank=True, help_text='Škola, např. MFF, VŠCHT, VUT, ... prostě aby se nemuselo psát do studuješkolu, ale jen obor, možnost zobrazit zvlášť', max_length=256, null=True, verbose_name='Škola, kterou studuje'), + ), + migrations.AddField( + model_name='organizator', + name='vytvoreno', + field=models.DateTimeField(blank=True, default=django.utils.timezone.now, editable=False, verbose_name='Vytvořeno'), + ), + migrations.AddField( + model_name='problem', + name='garant', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='garant_problemu_problem', to='seminar.Organizator', verbose_name='garant zadaného problému'), + ), + migrations.AddField( + model_name='problem', + name='nadproblem', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='nadproblem_problem', to='seminar.Problem', verbose_name='nadřazený problém'), + ), + migrations.AddField( + model_name='problem', + name='opravovatele', + field=models.ManyToManyField(blank=True, related_name='opravovatele_problem', to='seminar.Organizator', verbose_name='opravovatelé'), + ), + migrations.AddField( + model_name='reseni', + name='zverejneno', + field=models.BooleanField(default=False, help_text='Udává, zda je řešení zveřejněno', verbose_name='řešení zveřejněno'), + ), + migrations.AlterField( + model_name='cislo', + name='verejna_vysledkovka', + field=models.BooleanField(default=False, help_text='Je-li false u veřejného čísla,\t\t\t\t není výsledkovka zatím veřejná.', verbose_name='zveřejněna výsledkovka'), + ), + migrations.AlterField( + model_name='cislo', + name='verejne_db', + field=models.BooleanField(db_column='verejne', default=False, verbose_name='číslo zveřejněno'), + ), + migrations.AlterField( + model_name='konfera', + name='typ_prezentace', + field=models.CharField(choices=[('veletrh', 'Veletrh (postery)'), ('prezentace', 'Prezentace (přednáška)')], default='veletrh', max_length=16, verbose_name='typ prezentace'), + ), + migrations.RenameField( + model_name='novinky', + old_name='autor', + new_name='autor_old', + ), + migrations.AddField( + model_name='novinky', + name='autor', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='seminar.Organizator', verbose_name='Autor novinky'), + ), + migrations.AlterField( + model_name='novinky', + name='obrazek', + field=models.ImageField(blank=True, null=True, upload_to='image_novinky/%Y/%m/%d/', verbose_name='Obrázek'), + ), + migrations.AlterField( + model_name='novinky', + name='text', + field=models.TextField(blank=True, null=True, verbose_name='Text novinky'), + ), + migrations.AlterField( + model_name='novinky', + name='zverejneno', + field=models.BooleanField(default=False, verbose_name='Zveřejněno'), + ), + migrations.AlterField( + model_name='organizator', + name='strucny_popis_organizatora', + field=models.TextField(blank=True, null=True, verbose_name='Stručný popis organizátora'), + ), + migrations.AlterField( + model_name='organizator', + name='studuje', + field=models.CharField(blank=True, help_text="Např. 'Studuje Obecnou fyziku (Bc.), 3. ročník', 'Vystudovala Diskrétní modely a algoritmy (Mgr.)' nebo 'Přednáší na MFF'", max_length=256, null=True, verbose_name='Studium aj.'), + ), + migrations.CreateModel( + name='Pohadka', + fields=[ + ('id', models.AutoField(primary_key=True, serialize=False)), + ('text', models.TextField(verbose_name='Text pohádky')), + ('pred', models.BooleanField(default=True, verbose_name='Před úlohou')), + ('vytvoreno', models.DateTimeField(blank=True, default=django.utils.timezone.now, editable=False, verbose_name='Vytvořeno')), + ('autor_old', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name=b'Autor poh\xc3\xa1dky')), + ('uloha_old', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='pohadky', to='seminar.Problem', verbose_name='Úloha')), + ('autor', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='seminar.Organizator', verbose_name='Autor pohádky')), + ('uloha', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='pohadky', to='seminar.Uloha', verbose_name='Úloha')), + ], + options={ + 'ordering': ['uloha__cislo_zadani', 'uloha__kod', '-pred'], + 'db_table': 'seminar_pohadky', + 'verbose_name': 'Pohádka', + 'verbose_name_plural': 'Pohádky', + }, + ), + migrations.RenameField( + model_name='problem', + old_name='autor', + new_name='autor_old', + ), + migrations.AddField( + model_name='problem', + name='autor', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='autor_problemu_problem', to='seminar.Organizator', verbose_name='autor problému'), + ), + migrations.AlterField( + model_name='problem', + name='kod', + field=models.CharField(blank=True, default='', help_text='Číslo/kód úlohy v čísle nebo kód tématu/článku/seriálu v ročníku', max_length=32, verbose_name='lokální kód'), + ), + migrations.AlterField( + model_name='problem', + name='stav', + field=models.CharField(choices=[('navrh', 'Návrh'), ('zadany', 'Zadaný'), ('vyreseny', 'Vyřešený'), ('smazany', 'Smazaný')], default='navrh', max_length=32, verbose_name='stav problému'), + ), + migrations.AlterField( + model_name='problem', + name='zamereni', + field=taggit.managers.TaggableManager(blank=True, help_text='Zaměření M/F/I/O problému, příp. další tagy', through='taggit.TaggedItem', to='taggit.Tag', verbose_name='zaměření'), + ), + migrations.AlterField( + model_name='reseni', + name='forma', + field=models.CharField(choices=[('papir', 'Papírové řešení'), ('email', 'Emailem'), ('upload', 'Upload přes web')], default='email', max_length=16, verbose_name='forma řešení'), + ), + migrations.RenameField( + model_name='reseni', + old_name='problem', + new_name='problem_old', + ), + migrations.AlterField( + model_name='resitel', + name='zasilat', + field=models.CharField(choices=[('domu', 'Domů'), ('do_skoly', 'Do školy'), ('nikam', 'Nikam')], default='domu', max_length=32, verbose_name='kam zasílat'), + ), + migrations.AlterField( + model_name='rocnik', + name='exportovat', + field=models.BooleanField(db_column='exportovat', default=False, help_text='Exportuje se jen podle tohoto flagu (ne veřejnosti), a to jen čísla s veřejnou výsledkovkou', verbose_name='export do AESOPa'), + ), + migrations.AlterField( + model_name='skola', + name='aesop_id', + field=models.CharField(blank=True, default='', help_text='Aesopi ID typu "izo:..." nebo "aesop:..."', max_length=32, verbose_name='Aesop ID'), + ), + migrations.AlterField( + model_name='skola', + name='kratky_nazev', + field=models.CharField(blank=True, help_text='Zkrácený název pro zobrazení ve výsledkovce', max_length=256, verbose_name='zkrácený název'), + ), + migrations.AlterField( + model_name='skola', + name='stat', + field=django_countries.fields.CountryField(default='CZ', help_text='ISO 3166-1 kód země velkými písmeny (CZ, SK, ...)', max_length=2, verbose_name='stát'), + ), + migrations.AlterField( + model_name='soustredeni', + name='exportovat', + field=models.BooleanField(db_column='exportovat', default=False, help_text='Exportuje se jen podle tohoto flagu (ne veřejnosti)', verbose_name='export do AESOPa'), + ), + migrations.AlterField( + model_name='soustredeni', + name='misto', + field=models.CharField(blank=True, default='', help_text='Místo (název obce, volitelně též objektu', max_length=256, verbose_name='místo soustředění'), + ), + migrations.AlterField( + model_name='soustredeni', + name='text', + field=models.TextField(blank=True, default='', verbose_name='text k soustředění (HTML)'), + ), + migrations.AlterField( + model_name='soustredeni', + name='typ', + field=models.CharField(choices=[('jarni', 'Jarní soustředění'), ('podzimni', 'Podzimní soustředění'), ('vikend', 'Víkendový sraz')], default='podzimni', max_length=16, verbose_name='typ akce'), + ), + migrations.AlterField( + model_name='soustredeni', + name='verejne_db', + field=models.BooleanField(db_column='verejne', default=False, verbose_name='soustředění zveřejněno'), + ), + migrations.AlterModelTable( + name='problem', + table='seminar_problemy', + ), + migrations.AddField( + model_name='uloha', + name='cislo_deadline', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='deadlinove_ulohy', to='seminar.Cislo', verbose_name='číslo deadlinu'), + ), + migrations.AddField( + model_name='uloha', + name='cislo_reseni', + field=models.ForeignKey(blank=True, help_text='Číslo s řešením úlohy, jen pro úlohy', null=True, on_delete=django.db.models.deletion.CASCADE, related_name='resene_ulohy', to='seminar.Cislo', verbose_name='číslo řešení'), + ), + migrations.AddField( + model_name='uloha', + name='cislo_zadani', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='zadane_ulohy', to='seminar.Cislo', verbose_name='číslo zadání'), + ), + migrations.CreateModel( + name='Tema', + fields=[ + ('problem_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='seminar.Problem')), + ('tema_typ', models.CharField(choices=[('tema', 'Téma'), ('serial', 'Seriál')], default='tema', max_length=16, verbose_name='Typ tématu')), + ('rocnik', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='seminar.Rocnik', verbose_name='ročník')), + ], + options={ + 'verbose_name': 'Téma', + 'verbose_name_plural': 'Témata', + 'db_table': 'seminar_temata', + }, + bases=('seminar.problem',), + ), + migrations.CreateModel( + name='Reseni_Resitele', + fields=[ + ('id', models.AutoField(primary_key=True, serialize=False)), + ('reseni', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='seminar.Reseni', verbose_name='řešení')), + ('resitele', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='seminar.Resitel', verbose_name='řešitel')), + ], + options={ + 'verbose_name': 'Řešení řešitelů', + 'verbose_name_plural': 'Řešení řešitelů', + 'db_table': 'seminar_reseni_resitele', + 'ordering': ['reseni', 'resitele'], + }, + ), + migrations.CreateModel( + name='Obrazek', + fields=[ + ('id', models.AutoField(primary_key=True, serialize=False)), + ('na_web', models.ImageField(blank=True, null=True, upload_to='obrazky/%Y/%m/%d/', verbose_name='obrázek na web')), + ('do_cisla_barevny', models.FileField(blank=True, help_text='Barevná verze obrázku do čísla', null=True, upload_to='obrazky/%Y/%m/%d/', verbose_name='barevný obrázek do čísla')), + ('do_cisla_cernobily', models.FileField(blank=True, help_text='Černobílá verze obrázku do čísla', null=True, upload_to='obrazky/%Y/%m/%d/', verbose_name='černobílý obrázek do čísla')), + ('text', models.ForeignKey(help_text='text, ve kterém se obrázek vyskytuje', on_delete=django.db.models.deletion.CASCADE, to='seminar.Text', verbose_name='text')), + ], + options={ + 'verbose_name': 'obrázek', + 'verbose_name_plural': 'obrázky', + 'db_table': 'seminar_obrazky', + }, + ), + migrations.CreateModel( + name='Hodnoceni', + fields=[ + ('id', models.AutoField(primary_key=True, serialize=False)), + ('body', models.DecimalField(decimal_places=1, max_digits=8, verbose_name='body')), + ('cislo_body', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='hodnoceni', to='seminar.Cislo', verbose_name='číslo pro body')), + ('problem', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='seminar.Problem', verbose_name='problém')), + ('reseni', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='seminar.Reseni', verbose_name='řešení')), + ], + options={ + 'verbose_name': 'Hodnocení', + 'verbose_name_plural': 'Hodnocení', + 'db_table': 'seminar_hodnoceni', + }, + ), + migrations.CreateModel( + name='Clanek', + fields=[ + ('problem_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='seminar.Problem')), + ('cislo', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='seminar.Cislo', verbose_name='číslo')), + ], + options={ + 'verbose_name': 'Článek', + 'verbose_name_plural': 'Články', + 'db_table': 'seminar_clanky', + }, + bases=('seminar.problem',), + ), + migrations.AddField( + model_name='reseni', + name='resitele', + field=models.ManyToManyField(help_text='Seznam autorů řešení', through='seminar.Reseni_Resitele', to='seminar.Resitel', verbose_name='autoři řešení'), + ), + migrations.AddField( + model_name='reseni', + name='text_cely', + field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='reseni_cely_set', to='seminar.Text', verbose_name='Plná verze textu řešení'), + ), + migrations.AddField( + model_name='reseni', + name='text_zkraceny', + field=models.ManyToManyField(help_text='Seznam úryvků z řešení', related_name='reseni_zkraceny_set', to='seminar.Text', verbose_name='zkrácené verze řešení'), + ), + migrations.AddField( + model_name='skola', + name='kontaktni_osoba', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='seminar.Osoba', verbose_name='Kontaktní osoba'), + ), + migrations.AddField( + model_name='reseni', + name='problem', + field=models.ManyToManyField(help_text='Problém', through='seminar.Hodnoceni', to='seminar.Problem', verbose_name='problém'), + ), + migrations.AddField( + model_name='konfera', + name='reseni', + field=models.OneToOneField(blank=True, help_text='Účastnický přípěvek o konfeře', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='konfery', to='seminar.Reseni', verbose_name='článek ke konfeře'), + ), + migrations.AddField( + model_name='organizator', + name='osoba', + field=models.OneToOneField(help_text='osobní údaje organizátora', null=True, on_delete=django.db.models.deletion.CASCADE, related_name='org', to='seminar.Osoba', verbose_name='osoba'), + ), + migrations.AddField( + model_name='resitel', + name='osoba', + field=models.OneToOneField(null=True, on_delete=django.db.models.deletion.CASCADE, to='seminar.Osoba', verbose_name='osoba'), + ), + migrations.CreateModel( + name='TreeNode', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('first_child', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='seminar.TreeNode', verbose_name='první potomek')), + ('root', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='potomci_set', to='seminar.TreeNode', verbose_name='kořen stromu')), + ('succ', models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='prev', to='seminar.TreeNode', verbose_name='další element na stejné úrovni')), + ], + options={ + 'verbose_name': 'TreeNode', + 'verbose_name_plural': 'TreeNody', + 'db_table': 'seminar_nodes_treenode', + }, + ), + migrations.CreateModel( + name='CisloNode', + fields=[ + ('treenode_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='seminar.TreeNode')), + ('cislo', models.OneToOneField(on_delete=django.db.models.deletion.PROTECT, to='seminar.Cislo', verbose_name='číslo')), + ], + options={ + 'verbose_name': 'Číslo (Node)', + 'verbose_name_plural': 'Čísla (Node)', + 'db_table': 'seminar_nodes_cislo', + }, + bases=('seminar.treenode',), + ), + migrations.CreateModel( + name='ClanekNode', + fields=[ + ('treenode_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='seminar.TreeNode')), + ('clanek', models.OneToOneField(null=True, on_delete=django.db.models.deletion.PROTECT, to='seminar.Clanek', verbose_name='článek')), + ], + options={ + 'verbose_name': 'Článek (Node)', + 'verbose_name_plural': 'Články (Node)', + 'db_table': 'seminar_nodes_clanek', + }, + bases=('seminar.treenode',), + ), + migrations.CreateModel( + name='KonferaNode', + fields=[ + ('treenode_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='seminar.TreeNode')), + ('konfera', models.OneToOneField(null=True, on_delete=django.db.models.deletion.PROTECT, to='seminar.Konfera', verbose_name='konfera')), + ], + options={ + 'verbose_name': 'Konfera (Node)', + 'verbose_name_plural': 'Konfery (Node)', + 'db_table': 'seminar_nodes_konfera', + }, + bases=('seminar.treenode',), + ), + migrations.CreateModel( + name='MezicisloNode', + fields=[ + ('treenode_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='seminar.TreeNode')), + ], + options={ + 'verbose_name': 'Mezičíslo (Node)', + 'verbose_name_plural': 'Mezičísla (Node)', + 'db_table': 'seminar_nodes_mezicislo', + }, + bases=('seminar.treenode',), + ), + migrations.CreateModel( + name='RocnikNode', + fields=[ + ('treenode_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='seminar.TreeNode')), + ('rocnik', models.OneToOneField(on_delete=django.db.models.deletion.PROTECT, to='seminar.Rocnik', verbose_name='ročník')), + ], + options={ + 'verbose_name': 'Ročník (Node)', + 'verbose_name_plural': 'Ročníky (Node)', + 'db_table': 'seminar_nodes_rocnik', + }, + bases=('seminar.treenode',), + ), + migrations.CreateModel( + name='TemaVCisleNode', + fields=[ + ('treenode_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='seminar.TreeNode')), + ('tema', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='seminar.Tema', verbose_name='téma v čísle')), + ], + options={ + 'verbose_name': 'Téma v čísle (Node)', + 'verbose_name_plural': 'Témata v čísle (Node)', + 'db_table': 'seminar_nodes_temavcisle', + }, + bases=('seminar.treenode',), + ), + migrations.CreateModel( + name='TextNode', + fields=[ + ('treenode_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='seminar.TreeNode')), + ('text', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='seminar.Text', verbose_name='text')), + ], + options={ + 'verbose_name': 'Text (Node)', + 'verbose_name_plural': 'Text (Node)', + 'db_table': 'seminar_nodes_obsah', + }, + bases=('seminar.treenode',), + ), + migrations.CreateModel( + name='UlohaVzorakNode', + fields=[ + ('treenode_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='seminar.TreeNode')), + ('uloha', models.OneToOneField(null=True, on_delete=django.db.models.deletion.PROTECT, to='seminar.Uloha', verbose_name='úloha')), + ], + options={ + 'verbose_name': 'Vzorák úlohy (Node)', + 'verbose_name_plural': 'Vzoráky úloh (Node)', + 'db_table': 'seminar_nodes_uloha_vzorak', + }, + bases=('seminar.treenode',), + ), + migrations.CreateModel( + name='UlohaZadaniNode', + fields=[ + ('treenode_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='seminar.TreeNode')), + ('uloha', models.OneToOneField(null=True, on_delete=django.db.models.deletion.PROTECT, to='seminar.Uloha', verbose_name='úloha')), + ], + options={ + 'verbose_name': 'Zadání úlohy (Node)', + 'verbose_name_plural': 'Zadání úloh (Node)', + 'db_table': 'seminar_nodes_uloha_zadani', + }, + bases=('seminar.treenode',), + ), + migrations.CreateModel( + name='PohadkaNode', + fields=[ + ('treenode_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='seminar.TreeNode')), + ('pohadka', models.OneToOneField(on_delete=django.db.models.deletion.PROTECT, to='seminar.Pohadka', verbose_name='pohádka')), + ], + options={ + 'verbose_name': 'Pohádka (Node)', + 'verbose_name_plural': 'Pohádky (Node)', + 'db_table': 'seminar_nodes_pohadka', + }, + bases=('seminar.treenode',), + ), + + # migr 0051 + migrations.RunPython( + resitel_to_osoba, + reverse_code=osoba_to_resitel, + ), + + # migr 0052 + migrations.RunPython( + spoj_k_organizatorum_osoby, + ), + migrations.RunPython( + fix_problem, + ), + migrations.RunPython( + fix_pohadka, + ), + migrations.RunPython( + fix_novinka, + ), + + # migr 0053 + migrations.RunPython( + rok_to_datetime, + reverse_code=datetime_to_rok, + ), + + migrations.RemoveField( + model_name='prispevek', + name='problem', + ), + migrations.RemoveField( + model_name='prispevek', + name='reseni', + ), + migrations.DeleteModel( + name='ProblemNavrh', + ), + migrations.DeleteModel( + name='ProblemZadany', + ), + migrations.RemoveField( + model_name='cislo', + name='faze', + ), + migrations.RemoveField( + model_name='konfera', + name='popis', + ), + migrations.RemoveField( + model_name='konfera', + name='prispevek', + ), + migrations.RemoveField( + model_name='problem', + name='import_dakos_id', + ), + migrations.RemoveField( + model_name='resitel', + name='import_mamoper_id', + ), + migrations.RemoveField( + model_name='skola', + name='import_dakos_id', + ), + migrations.DeleteModel( + name='Prispevek', + ), + + # migr 0056 + migrations.RunPython( + generuj_RocnikNody_a_CisloNody, + ), + + # migr 0057 + migrations.RunPython( + reseni_to_Reseni, + ), + + # migr 0058 + migrations.RunPython( + uloha_to_Uloha, + ), + migrations.RunPython( + tema_to_Tema, + ), + migrations.RunPython( + clanek_to_Clanek, + ), + migrations.RunPython( + konfery_rucne, + ), + + # migr 0059 + migrations.RunPython( + vytvor_pohadkanode, + ), + + # migr 0060 + migrations.RunPython( + pokacej_les, + ), + + migrations.RemoveField( + model_name='novinky', + name='autor_old', + ), + migrations.RemoveField( + model_name='organizator', + name='foto', + ), + migrations.RemoveField( + model_name='organizator', + name='organizuje_do_roku', + ), + migrations.RemoveField( + model_name='organizator', + name='organizuje_od_roku', + ), + migrations.RemoveField( + model_name='organizator', + name='prezdivka', + ), + migrations.RemoveField( + model_name='organizator', + name='user', + ), + migrations.RemoveField( + model_name='pohadka', + name='autor_old', + ), + migrations.RemoveField( + model_name='pohadka', + name='uloha_old', + ), + migrations.RemoveField( + model_name='problem', + name='autor_old', + ), + migrations.RemoveField( + model_name='problem', + name='body', + ), + migrations.RemoveField( + model_name='problem', + name='cislo_reseni_old', + ), + migrations.RemoveField( + model_name='problem', + name='cislo_zadani_old', + ), + migrations.RemoveField( + model_name='problem', + name='opravovatel', + ), + migrations.RemoveField( + model_name='problem', + name='text_reseni', + ), + migrations.RemoveField( + model_name='problem', + name='text_zadani', + ), + migrations.RemoveField( + model_name='problem', + name='typ', + ), + migrations.RemoveField( + model_name='reseni', + name='body', + ), + migrations.RemoveField( + model_name='reseni', + name='cislo_body', + ), + migrations.RemoveField( + model_name='reseni', + name='problem_old', + ), + migrations.RemoveField( + model_name='reseni', + name='resitel', + ), + migrations.RemoveField( + model_name='resitel', + name='datum_narozeni', + ), + migrations.RemoveField( + model_name='resitel', + name='datum_prihlaseni', + ), + migrations.RemoveField( + model_name='resitel', + name='datum_souhlasu_udaje', + ), + migrations.RemoveField( + model_name='resitel', + name='datum_souhlasu_zasilani', + ), + migrations.RemoveField( + model_name='resitel', + name='email', + ), + migrations.RemoveField( + model_name='resitel', + name='jmeno', + ), + migrations.RemoveField( + model_name='resitel', + name='mesto', + ), + migrations.RemoveField( + model_name='resitel', + name='pohlavi_muz', + ), + migrations.RemoveField( + model_name='resitel', + name='prijmeni', + ), + migrations.RemoveField( + model_name='resitel', + name='psc', + ), + migrations.RemoveField( + model_name='resitel', + name='stat', + ), + migrations.RemoveField( + model_name='resitel', + name='telefon', + ), + migrations.RemoveField( + model_name='resitel', + name='ulice', + ), + migrations.RemoveField( + model_name='resitel', + name='user', + ), + migrations.AlterModelOptions( + name='pohadka', + options={'ordering': ['vytvoreno'], 'verbose_name': 'Pohádka', 'verbose_name_plural': 'Pohádky'}, + ), + migrations.RemoveField( + model_name='pohadka', + name='pred', + ), + migrations.RemoveField( + model_name='pohadka', + name='text', + ), + migrations.RemoveField( + model_name='pohadka', + name='uloha', + ), + migrations.AlterField( + model_name='cislo', + name='verejna_vysledkovka', + field=models.BooleanField(default=False, help_text='Je-li false u veřejného čísla, není výsledkovka zatím veřejná.', verbose_name='zveřejněna výsledkovka'), + ), + migrations.AlterField( + model_name='prijemce', + name='osoba', + field=models.OneToOneField(help_text='Které osobě či na jakou adresu se mají zasílat čísla', on_delete=django.db.models.deletion.CASCADE, to='seminar.Osoba', verbose_name='komu'), + ), + migrations.AlterField( + model_name='reseni', + name='cas_doruceni', + field=models.DateTimeField(blank=True, default=django.utils.timezone.now, verbose_name='čas_doručení'), + ), + migrations.AlterField( + model_name='cislo', + name='rocnik', + field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='cisla', to='seminar.Rocnik', verbose_name='ročník'), + ), + migrations.AlterField( + model_name='clanek', + name='cislo', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='seminar.Cislo', verbose_name='číslo'), + ), + migrations.AlterField( + model_name='hodnoceni', + name='cislo_body', + field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='hodnoceni', to='seminar.Cislo', verbose_name='číslo pro body'), + ), + migrations.AlterField( + model_name='hodnoceni', + name='problem', + field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='seminar.Problem', verbose_name='problém'), + ), + migrations.AlterField( + model_name='konfery_ucastnici', + name='konfera', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='seminar.Konfera', verbose_name='konfera'), + ), + migrations.AlterField( + model_name='konfery_ucastnici', + name='resitel', + field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='seminar.Resitel', verbose_name='řešitel'), + ), + migrations.AlterField( + model_name='nastaveni', + name='aktualni_cislo', + field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='seminar.Cislo', verbose_name='poslední vydané číslo'), + ), + migrations.AlterField( + model_name='nastaveni', + name='aktualni_rocnik', + field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='seminar.Rocnik', verbose_name='aktuální ročník'), + ), + migrations.AlterField( + model_name='novinky', + name='autor', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='seminar.Organizator', verbose_name='Autor novinky'), + ), + migrations.AlterField( + model_name='organizator', + name='osoba', + field=models.OneToOneField(help_text='osobní údaje organizátora', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='org', to='seminar.Osoba', verbose_name='osoba'), + ), + migrations.AlterField( + model_name='osoba', + name='user', + field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.DO_NOTHING, to=settings.AUTH_USER_MODEL, verbose_name='uživatel'), + ), + migrations.AlterField( + model_name='pohadka', + name='autor', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='seminar.Organizator', verbose_name='Autor pohádky'), + ), + migrations.AlterField( + model_name='problem', + name='autor', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='autor_problemu_problem', to='seminar.Organizator', verbose_name='autor problému'), + ), + migrations.AlterField( + model_name='problem', + name='garant', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='garant_problemu_problem', to='seminar.Organizator', verbose_name='garant zadaného problému'), + ), + migrations.AlterField( + model_name='problem', + name='nadproblem', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='nadproblem_problem', to='seminar.Problem', verbose_name='nadřazený problém'), + ), + migrations.AlterField( + model_name='reseni', + name='text_cely', + field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='reseni_cely_set', to='seminar.Text', verbose_name='Plná verze textu řešení'), + ), + migrations.AlterField( + model_name='reseni_resitele', + name='resitele', + field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='seminar.Resitel', verbose_name='řešitel'), + ), + migrations.AlterField( + model_name='resitel', + name='osoba', + field=models.OneToOneField(null=True, on_delete=django.db.models.deletion.SET_NULL, to='seminar.Osoba', verbose_name='osoba'), + ), + migrations.AlterField( + model_name='resitel', + name='skola', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='seminar.Skola', verbose_name='škola'), + ), + migrations.AlterField( + model_name='skola', + name='kontaktni_osoba', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='seminar.Osoba', verbose_name='Kontaktní osoba'), + ), + migrations.AlterField( + model_name='soustredeni', + name='rocnik', + field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='soustredeni', to='seminar.Rocnik', verbose_name='ročník'), + ), + migrations.AlterField( + model_name='soustredeni_organizatori', + name='organizator', + field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='seminar.Organizator', verbose_name='organizátor'), + ), + migrations.AlterField( + model_name='soustredeni_organizatori', + name='soustredeni', + field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='seminar.Soustredeni', verbose_name='soustředění'), + ), + migrations.AlterField( + model_name='soustredeni_ucastnici', + name='resitel', + field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='seminar.Resitel', verbose_name='řešitel'), + ), + migrations.AlterField( + model_name='soustredeni_ucastnici', + name='soustredeni', + field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='seminar.Soustredeni', verbose_name='soustředění'), + ), + migrations.AlterField( + model_name='tema', + name='rocnik', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='seminar.Rocnik', verbose_name='ročník'), + ), + migrations.AlterField( + model_name='uloha', + name='cislo_deadline', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='deadlinove_ulohy', to='seminar.Cislo', verbose_name='číslo deadlinu'), + ), + migrations.AlterField( + model_name='uloha', + name='cislo_reseni', + field=models.ForeignKey(blank=True, help_text='Číslo s řešením úlohy, jen pro úlohy', null=True, on_delete=django.db.models.deletion.PROTECT, related_name='resene_ulohy', to='seminar.Cislo', verbose_name='číslo řešení'), + ), + migrations.AlterField( + model_name='uloha', + name='cislo_zadani', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='zadane_ulohy', to='seminar.Cislo', verbose_name='číslo zadání'), + ), + 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, + reverse_code=migrations.RunPython.noop, + ), + 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, + reverse_code=migrations.RunPython.noop, + ), + migrations.AlterField( + model_name='konfera', + name='nazev', + field=models.CharField(help_text='Název konfery', max_length=100, verbose_name='název konfery'), + ), + ] diff --git a/seminar/migrations/0065_treenode_polymorphic_ctype.py b/seminar/migrations/0065_treenode_polymorphic_ctype.py new file mode 100644 index 00000000..71eef262 --- /dev/null +++ b/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), + ] diff --git a/seminar/migrations/0066_problem_polymorphic_ctype.py b/seminar/migrations/0066_problem_polymorphic_ctype.py new file mode 100644 index 00000000..f956217e --- /dev/null +++ b/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), + ] diff --git a/seminar/migrations/0067_auto_20190814_0805.py b/seminar/migrations/0067_auto_20190814_0805.py new file mode 100644 index 00000000..8a72a659 --- /dev/null +++ b/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'), + ), + ] diff --git a/seminar/migrations/0068_treenode_nazev.py b/seminar/migrations/0068_treenode_nazev.py new file mode 100644 index 00000000..bead85d3 --- /dev/null +++ b/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), + ] diff --git a/seminar/migrations/0069_auto_20191120_2115.py b/seminar/migrations/0069_auto_20191120_2115.py new file mode 100644 index 00000000..04c6d1dc --- /dev/null +++ b/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'), + ), + ] diff --git a/seminar/migrations/0070_auto_20191120_2357.py b/seminar/migrations/0070_auto_20191120_2357.py new file mode 100644 index 00000000..3bd5466f --- /dev/null +++ b/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'), + ), + ] diff --git a/seminar/migrations/0071_remove_nastaveni_aktualni_rocnik.py b/seminar/migrations/0071_remove_nastaveni_aktualni_rocnik.py new file mode 100644 index 00000000..d20c1501 --- /dev/null +++ b/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', + ), + ] diff --git a/seminar/migrations/0072_auto_20191204_2257.py b/seminar/migrations/0072_auto_20191204_2257.py new file mode 100644 index 00000000..f96b670a --- /dev/null +++ b/seminar/migrations/0072_auto_20191204_2257.py @@ -0,0 +1,23 @@ +# Generated by Django 2.2.7 on 2019-12-04 21:57 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('seminar', '0071_remove_nastaveni_aktualni_rocnik'), + ] + + operations = [ + migrations.AddField( + model_name='treenode', + name='srolovatelne', + field=models.BooleanField(blank=True, help_text='Bude na stránce témátka možnost tuto položku skrýt', null=True, verbose_name='Srolovatelné'), + ), + migrations.AddField( + model_name='treenode', + name='zajimave', + field=models.BooleanField(default=False, help_text='Zobrazí se daná věc na rozcestníku témátek', verbose_name='Zajímavé'), + ), + ] diff --git a/seminar/migrations/0073_copy_osoba_email_to_user_email.py b/seminar/migrations/0073_copy_osoba_email_to_user_email.py new file mode 100644 index 00000000..3b280209 --- /dev/null +++ b/seminar/migrations/0073_copy_osoba_email_to_user_email.py @@ -0,0 +1,22 @@ +# Generated by Django 2.2.9 on 2020-01-15 21:28 + +from django.db import migrations + +def copy_mails(apps, schema_editor): + Osoba = apps.get_model('seminar', 'Osoba') + + for o in Osoba.objects.all(): + if o.user is not None: + u = o.user + u.email = o.email + u.save() + +class Migration(migrations.Migration): + + dependencies = [ + ('seminar', '0072_auto_20191204_2257'), + ] + + operations = [ + migrations.RunPython(copy_mails, migrations.RunPython.noop) + ] diff --git a/seminar/models.py b/seminar/models.py index e87aa325..f8c41ed0 100644 --- a/seminar/models.py +++ b/seminar/models.py @@ -21,10 +21,11 @@ from taggit.managers import TaggableManager from reversion import revisions as reversion -from seminar.utils import roman +from seminar.utils import roman, FirstTagParser # Pro získání úryvku z TextNode -from unidecode import unidecode +from unidecode import unidecode # Používám pro získání ID odkazu (ještě je to někde po někom zakomentované) +from polymorphic.models import PolymorphicModel class SeminarModelBase(models.Model): @@ -129,6 +130,17 @@ class Osoba(SeminarModelBase): def __str__(self): return self.plne_jmeno() + # Overridujeme save Osoby, aby když si změní e-mail, aby se projevil i v + # Userovi (a tak se dal poslat mail s resetem hesla) + def save(self, *args, **kwargs): + if self.user is not None: + u = self.user + # U svatého tučňáka, prosím ať tohle funguje. + # (Takhle se kódit asi nemá...) + u.email = self.email + u.save() + super().save() + # # Mělo by být částečně vytaženo z Aesopa # viz https://ovvp.mff.cuni.cz/wiki/aesop/export-skol. @@ -352,7 +364,7 @@ class Rocnik(SeminarModelBase): def verejna_cisla(self): vc = [c for c in self.cisla.all() if c.verejne()] - vc.sort(key=lambda c: c.cislo) + vc.sort(key=lambda c: c.poradi) return vc def posledni_verejne_cislo(self): @@ -361,7 +373,7 @@ class Rocnik(SeminarModelBase): def verejne_vysledkovky_cisla(self): vc = list(self.cisla.filter(verejna_vysledkovka=True)) - vc.sort(key=lambda c: c.cislo) + vc.sort(key=lambda c: c.poradi) return vc def posledni_zverejnena_vysledkovka_cislo(self): @@ -383,10 +395,18 @@ class Rocnik(SeminarModelBase): cache.set(name, c, 300) return c + def save(self, *args, **kwargs): + super().save(*args, **kwargs) + # *Node.save() aktualizuje název *Nodu. + try: + self.rocniknode.save() + except ObjectDoesNotExist: + # Neexistující *Node nemá smysl aktualizovat. + pass def cislo_pdf_filename(self, filename): rocnik = str(self.rocnik.rocnik) - return os.path.join('cislo', 'pdf', rocnik, '{}-{}.pdf'.format(rocnik, self.cislo)) + return os.path.join('cislo', 'pdf', rocnik, '{}-{}.pdf'.format(rocnik, self.poradi)) @reversion.register(ignore_duplicates=True) class Cislo(SeminarModelBase): @@ -395,7 +415,7 @@ class Cislo(SeminarModelBase): db_table = 'seminar_cisla' verbose_name = 'Číslo' verbose_name_plural = 'Čísla' - ordering = ['-rocnik__rocnik', '-cislo'] + ordering = ['-rocnik__rocnik', '-poradi'] # Interní ID id = models.AutoField(primary_key = True) @@ -403,7 +423,7 @@ class Cislo(SeminarModelBase): rocnik = models.ForeignKey(Rocnik, verbose_name='ročník', related_name='cisla', db_index=True,on_delete=models.PROTECT) - cislo = models.CharField('název čísla', max_length=32, db_index=True, + poradi = models.CharField('název čísla', max_length=32, db_index=True, help_text='Většinou jen "1", vyjímečně "7-8", lexikograficky určuje pořadí v ročníku!') datum_vydani = models.DateField('datum vydání', blank=True, null=True, @@ -436,20 +456,20 @@ class Cislo(SeminarModelBase): # CisloNode def kod(self): - return '%s.%s' % (self.rocnik.rocnik, self.cislo) + return '%s.%s' % (self.rocnik.rocnik, self.poradi) kod.short_description = 'Kód čísla' def __str__(self): # Potenciální DB HOG, pokud by se ročník necachoval r = Rocnik.cached_rocnik(self.rocnik_id) - return '{}.{}'.format(r.rocnik, self.cislo) + return '{}.{}'.format(r.rocnik, self.poradi) def verejne(self): return self.verejne_db verejne.boolean = True def verejne_url(self): - return reverse('seminar_cislo', kwargs={'rocnik': self.rocnik.rocnik, 'cislo': self.cislo}) + return reverse('seminar_cislo', kwargs={'rocnik': self.rocnik.rocnik, 'cislo': self.poradi}) def nasledujici(self): "Vrací None, pokud je toto poslední" @@ -471,11 +491,20 @@ class Cislo(SeminarModelBase): def get(cls, rocnik, cislo): try: r = Rocnik.objects.get(rocnik=rocnik) - c = r.cisla.get(cislo=cislo) + c = r.cisla.get(poradi=cislo) except ObjectDoesNotExist: return None return c + def save(self, *args, **kwargs): + super().save(*args, **kwargs) + # *Node.save() aktualizuje název *Nodu. + try: + self.cislonode.save() + except ObjectDoesNotExist: + # Neexistující *Node nemá smysl aktualizovat. + pass + @reversion.register(ignore_duplicates=True) class Organizator(SeminarModelBase): # zmena dedicnosti z models.Model na SeminarModelBase, potencialni vznik bugu @@ -583,7 +612,8 @@ class Soustredeni(SeminarModelBase): @reversion.register(ignore_duplicates=True) -class Problem(SeminarModelBase): +# Pozor na následující řádek. *Nekrmit, asi kouše!* +class Problem(SeminarModelBase,PolymorphicModel): class Meta: # Není abstraktní, protože se na něj jinak nedají dělat ForeignKeys. @@ -601,11 +631,11 @@ class Problem(SeminarModelBase): id = models.AutoField(primary_key = True) # Název - nazev = models.CharField('název', max_length=256) + nazev = models.CharField('název', max_length=256) # Zveřejnitelný na stránky # Problém má podproblémy nadproblem = models.ForeignKey('self', verbose_name='nadřazený problém', - related_name='nadproblem_%(class)s', null=True, blank=True, + related_name='podproblem', null=True, blank=True, on_delete=models.SET_NULL) STAV_NAVRH = 'navrh' @@ -698,6 +728,9 @@ class Tema(Problem): rocnik = models.ForeignKey(Rocnik, verbose_name='ročník',blank=True, null=True, on_delete=models.PROTECT) + abstrakt = models.TextField('Abstrakt na rozcestník', blank=True) + obrazek = models.ImageField('Obrázek na rozcestník', null=True) + def kod_v_rocniku(self): if self.stav == 'zadany': if self.nadproblem: @@ -705,6 +738,12 @@ class Tema(Problem): return "t{}".format(self.kod) return '' + def save(self, *args, **kwargs): + super().save(*args, **kwargs) + # *Node.save() aktualizuje název *Nodu. + for tvcn in self.temavcislenode_set.all(): + tvcn.save() + class Clanek(Problem): class Meta: db_table = 'seminar_clanky' @@ -725,6 +764,15 @@ class Clanek(Problem): return "c{}".format(self.kod) return '' + def save(self, *args, **kwargs): + super().save(*args, **kwargs) + # *Node.save() aktualizuje název *Nodu. + try: + self.claneknode.save() + except ObjectDoesNotExist: + # Neexistující *Node nemá smysl aktualizovat. + pass + class Text(SeminarModelBase): class Meta: db_table = 'seminar_texty' @@ -742,8 +790,17 @@ class Text(SeminarModelBase): # obrázky mají návaznost opačným směrem (vazba z druhé strany) + def save(self, *args, **kwargs): + super().save(*args, **kwargs) + # *Node.save() aktualizuje název *Nodu. + for tn in self.textnode_set.all(): + tn.save() - + def __str__(self): + parser = FirstTagParser() + parser.feed(str(self.na_web)) + return parser.firstTag + class Uloha(Problem): class Meta: db_table = 'seminar_ulohy' @@ -770,12 +827,26 @@ class Uloha(Problem): def kod_v_rocniku(self): if self.stav == 'zadany': - name="{}.u{}".format(self.cislo_zadani.cislo,self.kod) + name="{}.u{}".format(self.cislo_zadani.poradi,self.kod) if self.nadproblem: return self.nadproblem.kod_v_rocniku()+name return name return '' + def save(self, *args, **kwargs): + super().save(*args, **kwargs) + # *Node.save() aktualizuje název *Nodu. + try: + self.ulohazadaninode.save() + except ObjectDoesNotExist: + # Neexistující *Node nemá smysl aktualizovat. + pass + try: + self.ulohavzoraknode.save() + except ObjectDoesNotExist: + # Neexistující *Node nemá smysl aktualizovat. + pass + @reversion.register(ignore_duplicates=True) class Reseni(SeminarModelBase): @@ -828,7 +899,7 @@ class Reseni(SeminarModelBase): # Konfera def __str__(self): - return "{}: {}".format(self.resitel.osoba.plne_jmeno(), self.problem.nazev) + return "{}({}): {}({})".format(self.resitele.first(),len(self.resitele.all()), self.problem.first() ,len(self.problem.all())) # NOTE: Potenciální DB HOG (bez select_related) ## Pravdepodobne uz nebude potreba: @@ -856,7 +927,8 @@ class Hodnoceni(SeminarModelBase): reseni = models.ForeignKey(Reseni, verbose_name='řešení', on_delete=models.CASCADE) - problem = models.ForeignKey(Problem, verbose_name='problém', on_delete=models.PROTECT) + problem = models.ForeignKey(Problem, verbose_name='problém', + related_name='hodnoceni', on_delete=models.PROTECT) def __str__(self): return "{}, {}, {}".format(self.problem, self.reseni, self.body) @@ -957,6 +1029,14 @@ class Pohadka(SeminarModelBase): uryvek = self.text if len(self.text) < 50 else self.text[:(50-3)]+"..." return uryvek + def save(self, *args, **kwargs): + super().save(*args, **kwargs) + # *Node.save() aktualizuje název *Nodu. + try: + self.pohadkanode.save() + except ObjectDoesNotExist: + # Neexistující *Node nemá smysl aktualizovat. + pass @reversion.register(ignore_duplicates=True) class Soustredeni_Ucastnici(SeminarModelBase): @@ -1020,7 +1100,7 @@ class Konfera(models.Model): # Interní ID id = models.AutoField(primary_key = True) - nazev = models.CharField('název konfery', max_length=40, help_text = 'Název konfery') + nazev = models.CharField('název konfery', max_length=100, help_text = 'Název konfery') anotace = models.TextField('anotace', blank=True, help_text='Popis, o čem bude konfera.') @@ -1067,6 +1147,15 @@ class Konfera(models.Model): def __str__(self): return "{}: ({})".format(self.nazev, self.soustredeni) + def save(self, *args, **kwargs): + super().save(*args, **kwargs) + # *Node.save() aktualizuje název *Nodu. + try: + self.konferanode.save() + except ObjectDoesNotExist: + # Neexistující *Node nemá smysl aktualizovat. + pass + # Vazebna tabulka. Mozna se generuje automaticky. @reversion.register(ignore_duplicates=True) @@ -1139,12 +1228,13 @@ class Obrazek(SeminarModelBase): help_text = 'Černobílá verze obrázku do čísla', upload_to = 'obrazky/%Y/%m/%d/', blank=True, null=True) -class TreeNode(models.Model): +class TreeNode(PolymorphicModel): class Meta: db_table = "seminar_nodes_treenode" verbose_name = "TreeNode" verbose_name_plural = "TreeNody" + # TODO: Nechceme radši jako root vyžadovat přímo RocnikNode? root = models.ForeignKey('TreeNode', related_name="potomci_set", null = True, @@ -1162,14 +1252,59 @@ class TreeNode(models.Model): blank = True, on_delete=models.SET_NULL, verbose_name="další element na stejné úrovni") + nazev = models.TextField("název tohoto node", + help_text = "Tento název se zobrazuje v nabídkách pro výběr vhodného TreeNode", + blank=False, + null=True) # Nezveřejnitelný název na stránky - pouze do adminu + zajimave = models.BooleanField(default = False, + verbose_name = "Zajímavé", + help_text = "Zobrazí se daná věc na rozcestníku témátek") + srolovatelne = models.BooleanField(null = True, blank = True, + verbose_name = "Srolovatelné", + help_text = "Bude na stránce témátka možnost tuto položku skrýt") + # Slouží k debugování pro rychlé získání představy o podobě podstromu pod tímto TreeNode. def print_tree(self,indent=0): - print("{}TreeNode({})".format(" "*indent,self.id)) + # FIXME: Tady se spoléháme na to, že nedeklarovaný primární klíč se jmenuje by default 'id', což není úplně správně + print("{}{} (id: {})".format(" "*indent,self, self.id)) if self.first_child: self.first_child.print_tree(indent=indent+2) if self.succ: self.succ.print_tree(indent=indent) - + + def getOdkazStr(self): # String na rozcestník + return self.first_child.getOdkazStr() + + def getOdkaz(self): # ID HTML tagu, na který se bude scrollovat #{{self.getOdkaz}} + # Jsem si vědom, že tu potenciálně vznikají kolize. + # Přijdou mi natolik nepravděpodobné, že je neřeším + # Chtěl jsem ale hezké odkazy + string = unidecode(self.getOdkazStr()) + returnVal = "" + i = 0 + while len(returnVal) < 16: # Max 15 znaků + if i == len(string): + break + if string[i] == " ": + returnVal += "-" + if string[i].isalnum(): + returnVal += string[i].lower() + i += 1 + return returnVal + + def __str__(self): + if self.nazev: + return self.nazev + else: + #TODO: logování + return "Nepojmenovaný Treenode" + + def save(self, *args, **kwargs): + self.aktualizuj_nazev() + super().save(*args, **kwargs) + + def aktualizuj_nazev(self): + raise NotImplementedError("Pokus o aktualizaci názvu obecného TreeNode místo konkrétní instance") class RocnikNode(TreeNode): class Meta: @@ -1180,6 +1315,9 @@ class RocnikNode(TreeNode): on_delete = models.PROTECT, # Pokud chci mazat ročník, musím si Node pořešit ručně verbose_name = "ročník") + def aktualizuj_nazev(self): + self.nazev = "RocnikNode: "+str(self.rocnik) + class CisloNode(TreeNode): class Meta: db_table = 'seminar_nodes_cislo' @@ -1189,12 +1327,36 @@ class CisloNode(TreeNode): on_delete = models.PROTECT, # Pokud chci mazat číslo, musím si Node pořešit ručně verbose_name = "číslo") + def aktualizuj_nazev(self): + self.nazev = "CisloNode: "+str(self.cislo) + + def getOdkazStr(self): + return "Číslo " + str(self.cislo) + class MezicisloNode(TreeNode): class Meta: db_table = 'seminar_nodes_mezicislo' verbose_name = 'Mezičíslo (Node)' verbose_name_plural = 'Mezičísla (Node)' + def aktualizuj_nazev(self): + if self.prev: + if (self.prev.get_real_instance_class() != CisloNode and + self.prev.get_real_instance_class() != MezicisloNode): + raise ValueError("Předchůdce není číslo!") + posledni = self.prev.cislo + self.nazev = "MezicisloNode: Mezičíslo po čísle"+str(posledni) + elif self.root: + if self.root.get_real_instance_class() != RocnikNode: + raise ValueError("Kořen stromu není ročník!") + rocnik = self.root.rocnik + self.nazev = "MezicisloNode: První mezičíslo ročníku "+str(rocnik) + else: + print("!!!!! Nějaké neidentifikované mezičíslo !!!!!") + self.nazev = "MezicisloNode: Neidentifikovatelné mezičíslo!" + def getOdkazStr(self): + return "Obsah dostupný pouze na webu" + class TemaVCisleNode(TreeNode): """ Obsahuje příspěvky k tématu v daném čísle """ class Meta: @@ -1205,6 +1367,12 @@ class TemaVCisleNode(TreeNode): on_delete=models.PROTECT, # Pokud chci mazat téma, musím si Node pořešit ručně verbose_name = "téma v čísle") + def aktualizuj_nazev(self): + self.nazev = "TemaVCisleNode: "+str(self.tema) + + def getOdkazStr(self): + return str(self.tema) + class KonferaNode(TreeNode): class Meta: db_table = 'seminar_nodes_konfera' @@ -1216,6 +1384,9 @@ class KonferaNode(TreeNode): null=True, blank=False) + def aktualizuj_nazev(self): + self.nazev = "KonferaNode: "+str(self.konfera) + class ClanekNode(TreeNode): class Meta: db_table = 'seminar_nodes_clanek' @@ -1227,6 +1398,13 @@ class ClanekNode(TreeNode): null=True, blank=False) + def aktualizuj_nazev(self): + self.nazev = "ClanekNode: "+str(self.clanek) + + def getOdkazStr(self): + return str(self.clanek) + + class UlohaZadaniNode(TreeNode): class Meta: db_table = 'seminar_nodes_uloha_zadani' @@ -1238,6 +1416,13 @@ class UlohaZadaniNode(TreeNode): null=True, blank=False) + def aktualizuj_nazev(self): + self.nazev = "UlohaZadaniNode: "+str(self.uloha) + + def getOdkazStr(self): + return str(self.uloha) + + class PohadkaNode(TreeNode): class Meta: db_table = 'seminar_nodes_pohadka' @@ -1248,6 +1433,9 @@ class PohadkaNode(TreeNode): verbose_name = "pohádka", ) + def aktualizuj_nazev(self): + self.nazev = "PohadkaNode: "+str(self.pohadka) + class UlohaVzorakNode(TreeNode): class Meta: db_table = 'seminar_nodes_uloha_vzorak' @@ -1259,6 +1447,13 @@ class UlohaVzorakNode(TreeNode): null=True, blank=False) + def aktualizuj_nazev(self): + self.nazev = "UlohaVzorakNode: "+str(self.uloha) + + def getOdkazStr(self): + return str(self.uloha) + + class TextNode(TreeNode): class Meta: db_table = 'seminar_nodes_obsah' @@ -1267,6 +1462,13 @@ class TextNode(TreeNode): text = models.ForeignKey(Text, on_delete=models.PROTECT, verbose_name = 'text') + + def aktualizuj_nazev(self): + self.nazev = "TextNode: "+str(self.text) + + def getOdkazStr(self): + return str(self.text) + ## FIXME: Logiku přesunout do views. #class VysledkyBase(SeminarModelBase): @@ -1292,7 +1494,7 @@ class TextNode(TreeNode): # # def __str__(self): # return "%s: %sb (%s)".format(self.resitel.plne_jmeno(), self.body, -# str(self.cislo)) +# str(self.poradi)) # # NOTE: DB zatez pri vypisu (ale nepouzivany) @@ -1342,7 +1544,7 @@ class TextNode(TreeNode): # # def __str__(self): # # NOTE: DB HOG (ale nepouzivany) -# return "%s: %sb / %sb (do %s)" % (self.resitel.plne_jmeno(), self.body, self.body_celkem, str(self.cislo)) +# return "%s: %sb / %sb (do %s)" % (self.resitel.plne_jmeno(), self.body, self.body_celkem, str(self.poradi)) ##mozna potreba upravit @@ -1353,12 +1555,16 @@ class Nastaveni(SingletonModel): db_table = 'seminar_nastaveni' verbose_name = 'Nastavení semináře' - aktualni_rocnik = models.ForeignKey(Rocnik, verbose_name='aktuální ročník', - null=False, on_delete=models.PROTECT) +# aktualni_rocnik = models.ForeignKey(Rocnik, verbose_name='aktuální ročník', +# null=False, on_delete=models.PROTECT) aktualni_cislo = models.ForeignKey(Cislo, verbose_name='poslední vydané číslo', null=False, on_delete=models.PROTECT) + @property + def aktualni_rocnik(self): + return self.aktualni_cislo.rocnik + def __str__(self): return 'Nastavení semináře' @@ -1399,3 +1605,35 @@ class Novinky(models.Model): return '[' + str(self.datum) + '] ' + self.text[0:50] else: return '[' + str(self.datum) + '] ' + + + + +# FIXME: Tohle nepatří do aplikace 'seminar' +# Nefunkční alternativa vestavěného Usera, který má jméno a mail v přidružené Osobě +# from django.contrib.auth.models import User as Django_User +# +# class Uzivatel(Django_User): +# class Meta: +# proxy = True +# +# @property +# def first_name(self): +# osoby = Osoba.objects.filter(user=self) +# if len(osoby) == 0: +# return None +# return osoby.first().krestni_jmeno +# +# @property +# def last_name(self): +# osoby = Osoba.objects.filter(user=self) +# if len(osoby) == 0: +# return None +# return osoby.first().prijmeni +# +# @property +# def email(self): +# osoby = Osoba.objects.filter(user=self) +# if len(osoby) == 0: +# return None +# return osoby.first().email diff --git a/seminar/static/seminar/lisak.eps b/seminar/static/seminar/lisak.eps deleted file mode 100644 index 3a136212..00000000 --- a/seminar/static/seminar/lisak.eps +++ /dev/null @@ -1,1603 +0,0 @@ -%%BeginDocument: ./Eps/lisak.eps -%!PS-Adobe-3.0 EPSF-3.0 -%%BoundingBox: -191 -97 769 946 -%%Creator: CorelDRAW 8 -%%Title: L:\Prace\MaM\Obrazky\Lisak.eps -%%CreationDate: Tue May 11 17:28:49 1999 -%%For: Halef -%%DocumentProcessColors: Black -%%DocumentSuppliedResources: (atend) -%%EndComments -%%BeginProlog -/AutoFlatness false def -/AutoSteps 0 def -/CMYKMarks true def -/UseLevel 1 def -%Color profile: PROFILES.CCM - Obecn profil tiskrny CMYK - d. -%%BeginResource: procset wCorel8Dict 8.0 0 -/wCorel8Dict 300 dict def wCorel8Dict begin -% Copyright (c)1992-97 Corel Corporation -% All rights reserved. v8.0 r0.7 -/bd{bind def}bind def/ld{load def}bd/xd{exch def}bd/_ null def/rp{{pop}repeat} -bd/@cp/closepath ld/@gs/gsave ld/@gr/grestore ld/@np/newpath ld/Tl/translate ld -/$sv 0 def/@sv{/$sv save def}bd/@rs{$sv restore}bd/spg/showpage ld/showpage{} -bd currentscreen/@dsp xd/$dsp/@dsp def/$dsa xd/$dsf xd/$sdf false def/$SDF -false def/$Scra 0 def/SetScr/setscreen ld/setscreen{pop pop pop}bd/@ss{2 index -0 eq{$dsf 3 1 roll 4 -1 roll pop}if exch $Scra add exch load SetScr}bd -/SepMode_5 where{pop}{/SepMode_5 0 def}ifelse/CurrentInkName_5 where{pop} -{/CurrentInkName_5(Composite)def}ifelse/$ink_5 where{pop}{/$ink_5 -1 def} -ifelse/$c 0 def/$m 0 def/$y 0 def/$k 0 def/$t 1 def/$n _ def/$o 0 def/$fil 0 -def/$C 0 def/$M 0 def/$Y 0 def/$K 0 def/$T 1 def/$N _ def/$O 0 def/$PF false -def/s1c 0 def/s1m 0 def/s1y 0 def/s1k 0 def/s1t 0 def/s1n _ def/$bkg false def -/SK 0 def/SM 0 def/SY 0 def/SC 0 def/$op false def matrix currentmatrix/$ctm xd -/$ptm matrix def/$ttm matrix def/$stm matrix def/$ffpnt true def -/CorelDrawReencodeVect[16#0/grave 16#5/breve 16#6/dotaccent 16#8/ring -16#A/hungarumlaut 16#B/ogonek 16#C/caron 16#D/dotlessi 16#27/quotesingle -16#60/grave 16#7C/bar -16#82/quotesinglbase/florin/quotedblbase/ellipsis/dagger/daggerdbl -16#88/circumflex/perthousand/Scaron/guilsinglleft/OE -16#91/quoteleft/quoteright/quotedblleft/quotedblright/bullet/endash/emdash -16#98/tilde/trademark/scaron/guilsinglright/oe 16#9F/Ydieresis -16#A1/exclamdown/cent/sterling/currency/yen/brokenbar/section -16#a8/dieresis/copyright/ordfeminine/guillemotleft/logicalnot/minus/registered/macron -16#b0/degree/plusminus/twosuperior/threesuperior/acute/mu/paragraph/periodcentered -16#b8/cedilla/onesuperior/ordmasculine/guillemotright/onequarter/onehalf/threequarters/questiondown -16#c0/Agrave/Aacute/Acircumflex/Atilde/Adieresis/Aring/AE/Ccedilla -16#c8/Egrave/Eacute/Ecircumflex/Edieresis/Igrave/Iacute/Icircumflex/Idieresis -16#d0/Eth/Ntilde/Ograve/Oacute/Ocircumflex/Otilde/Odieresis/multiply -16#d8/Oslash/Ugrave/Uacute/Ucircumflex/Udieresis/Yacute/Thorn/germandbls -16#e0/agrave/aacute/acircumflex/atilde/adieresis/aring/ae/ccedilla -16#e8/egrave/eacute/ecircumflex/edieresis/igrave/iacute/icircumflex/idieresis -16#f0/eth/ntilde/ograve/oacute/ocircumflex/otilde/odieresis/divide -16#f8/oslash/ugrave/uacute/ucircumflex/udieresis/yacute/thorn/ydieresis]def -/L2?/languagelevel where{pop languagelevel 2 ge}{false}ifelse def -/@BeginSysCorelDict{systemdict/Corel30Dict known{systemdict/Corel30Dict get -exec}if systemdict/CorelLexDict known{1 systemdict/CorelLexDict get exec}if}bd -/@EndSysCorelDict{systemdict/Corel30Dict known{end}if/EndCorelLexDict where -{pop EndCorelLexDict}if}bd AutoFlatness{/@ifl{dup currentflat exch sub 10 gt{ -([Error: PathTooComplex; OffendingCommand: AnyPaintingOperator]\n)print flush -@np exit}{currentflat 2 add setflat}ifelse}bd/@fill/fill ld/fill{currentflat{ -{@fill}stopped{@ifl}{exit}ifelse}bind loop setflat}bd/@eofill/eofill ld/eofill -{currentflat{{@eofill}stopped{@ifl}{exit}ifelse}bind loop setflat}bd/@clip -/clip ld/clip{currentflat{{@clip}stopped{@ifl}{exit}ifelse}bind loop setflat} -bd/@eoclip/eoclip ld/eoclip{currentflat{{@eoclip}stopped{@ifl}{exit}ifelse} -bind loop setflat}bd/@stroke/stroke ld/stroke{currentflat{{@stroke}stopped -{@ifl}{exit}ifelse}bind loop setflat}bd}if L2?{/@ssa{true setstrokeadjust}bd}{ -/@ssa{}bd}ifelse/d/setdash ld/j/setlinejoin ld/J/setlinecap ld/M/setmiterlimit -ld/w/setlinewidth ld/O{/$o xd}bd/R{/$O xd}bd/W/eoclip ld/c/curveto ld/C/c ld/l -/lineto ld/L/l ld/rl/rlineto ld/m/moveto ld/n/newpath ld/N/newpath ld/P{11 rp} -bd/u{}bd/U{}bd/A{pop}bd/q/@gs ld/Q/@gr ld/&{}bd/@j{@sv @np}bd/@J{@rs}bd/g{1 -exch sub/$k xd/$c 0 def/$m 0 def/$y 0 def/$t 1 def/$n _ def/$fil 0 def}bd/G{1 -sub neg/$K xd _ 1 0 0 0/$C xd/$M xd/$Y xd/$T xd/$N xd}bd/k{1 index type -/stringtype eq{/$t xd/$n xd}{/$t 0 def/$n _ def}ifelse/$k xd/$y xd/$m xd/$c xd -/$fil 0 def}bd/K{1 index type/stringtype eq{/$T xd/$N xd}{/$T 0 def/$N _ def} -ifelse/$K xd/$Y xd/$M xd/$C xd}bd/x/k ld/X/K ld/sf{1 index type/stringtype eq{ -/s1t xd/s1n xd}{/s1t 0 def/s1n _ def}ifelse/s1k xd/s1y xd/s1m xd/s1c xd}bd/i{ -dup 0 ne{setflat}{pop}ifelse}bd/v{4 -2 roll 2 copy 6 -2 roll c}bd/V/v ld/y{2 -copy c}bd/Y/y ld/@w{matrix rotate/$ptm xd matrix scale $ptm dup concatmatrix -/$ptm xd 1 eq{$ptm exch dup concatmatrix/$ptm xd}if 1 w}bd/@g{1 eq dup/$sdf xd -{/$scp xd/$sca xd/$scf xd}if}bd/@G{1 eq dup/$SDF xd{/$SCP xd/$SCA xd/$SCF xd} -if}bd/@D{2 index 0 eq{$dsf 3 1 roll 4 -1 roll pop}if 3 copy exch $Scra add exch -load SetScr/$dsp xd/$dsa xd/$dsf xd}bd/$ngx{$SDF{$SCF SepMode_5 0 eq{$SCA} -{$dsa}ifelse $SCP @ss}if}bd/p{/$pm xd 7 rp/$pyf xd/$pxf xd/$pn xd/$fil 1 def} -bd/@MN{2 copy le{pop}{exch pop}ifelse}bd/@MX{2 copy ge{pop}{exch pop}ifelse}bd -/InRange{3 -1 roll @MN @MX}bd/@sqr{dup 0 rl dup 0 exch rl neg 0 rl @cp}bd -/currentscale{1 0 dtransform matrix defaultmatrix idtransform dup mul exch dup -mul add sqrt 0 1 dtransform matrix defaultmatrix idtransform dup mul exch dup -mul add sqrt}bd/@unscale{}bd/wDstChck{2 1 roll dup 3 -1 roll eq{1 add}if}bd -/@dot{dup mul exch dup mul add 1 exch sub}bd/@lin{exch pop abs 1 exch sub}bd -/cmyk2rgb{3{dup 5 -1 roll add 1 exch sub dup 0 lt{pop 0}if exch}repeat pop}bd -/rgb2cmyk{3{1 exch sub 3 1 roll}repeat 3 copy @MN @MN 3{dup 5 -1 roll sub neg -exch}repeat}bd/rgb2g{2 index .299 mul 2 index .587 mul add 1 index .114 mul add -4 1 roll pop pop pop}bd/WaldoColor_5 where{pop}{/SetRgb/setrgbcolor ld/GetRgb -/currentrgbcolor ld/SetGry/setgray ld/GetGry/currentgray ld/SetRgb2 systemdict -/setrgbcolor get def/GetRgb2 systemdict/currentrgbcolor get def/SetHsb -systemdict/sethsbcolor get def/GetHsb systemdict/currenthsbcolor get def -/rgb2hsb{SetRgb2 GetHsb}bd/hsb2rgb{3 -1 roll dup floor sub 3 1 roll SetHsb -GetRgb2}bd/setcmykcolor where{pop/SetCmyk_5/setcmykcolor ld}{/SetCmyk_5{ -cmyk2rgb SetRgb}bd}ifelse/currentcmykcolor where{pop/GetCmyk/currentcmykcolor -ld}{/GetCmyk{GetRgb rgb2cmyk}bd}ifelse/setoverprint where{pop}{/setoverprint{ -/$op xd}bd}ifelse/currentoverprint where{pop}{/currentoverprint{$op}bd}ifelse -/@tc_5{5 -1 roll dup 1 ge{pop}{4{dup 6 -1 roll mul exch}repeat pop}ifelse}bd -/@trp{exch pop 5 1 roll @tc_5}bd/setprocesscolor_5{SepMode_5 0 eq{SetCmyk_5}{0 -4 $ink_5 sub index exch pop 5 1 roll pop pop pop pop SepsColor true eq{$ink_5 3 -gt{1 sub neg SetGry}{0 0 0 4 $ink_5 roll SetCmyk_5}ifelse}{1 sub neg SetGry} -ifelse}ifelse}bd/findcmykcustomcolor where{pop}{/findcmykcustomcolor{5 array -astore}bd}ifelse/setcustomcolor where{pop}{/setcustomcolor{exch aload pop -SepMode_5 0 eq{pop @tc_5 setprocesscolor_5}{CurrentInkName_5 eq{4 index}{0} -ifelse 6 1 roll 5 rp 1 sub neg SetGry}ifelse}bd}ifelse/@scc_5{dup type -/booleantype eq{setoverprint}{1 eq setoverprint}ifelse dup _ eq{pop -setprocesscolor_5 pop}{findcmykcustomcolor exch setcustomcolor}ifelse SepMode_5 -0 eq{true}{GetGry 1 eq currentoverprint and not}ifelse}bd/colorimage where{pop -/ColorImage{colorimage}def}{/ColorImage{/ncolors xd pop/dataaq xd{dataaq -ncolors dup 3 eq{/$dat xd 0 1 $dat length 3 div 1 sub{dup 3 mul $dat 1 index -get 255 div $dat 2 index 1 add get 255 div $dat 3 index 2 add get 255 div rgb2g -255 mul cvi exch pop $dat 3 1 roll put}for $dat 0 $dat length 3 idiv -getinterval pop}{4 eq{/$dat xd 0 1 $dat length 4 div 1 sub{dup 4 mul $dat 1 -index get 255 div $dat 2 index 1 add get 255 div $dat 3 index 2 add get 255 div -$dat 4 index 3 add get 255 div cmyk2rgb rgb2g 255 mul cvi exch pop $dat 3 1 -roll put}for $dat 0 $dat length ncolors idiv getinterval}if}ifelse}image}bd -}ifelse/setcmykcolor{1 5 1 roll _ currentoverprint @scc_5/$ffpnt xd}bd -/currentcmykcolor{0 0 0 0}bd/setrgbcolor{rgb2cmyk setcmykcolor}bd -/currentrgbcolor{currentcmykcolor cmyk2rgb}bd/sethsbcolor{hsb2rgb setrgbcolor} -bd/currenthsbcolor{currentrgbcolor rgb2hsb}bd/setgray{dup dup setrgbcolor}bd -/currentgray{currentrgbcolor rgb2g}bd/InsideDCS false def/IMAGE systemdict -/image get def/image{InsideDCS{IMAGE}{/EPSDict where{pop SepMode_5 0 eq{IMAGE} -{dup type/dicttype eq{dup/ImageType get 1 ne{IMAGE}{dup dup/BitsPerComponent -get 8 eq exch/BitsPerComponent get 1 eq or currentcolorspace 0 get/DeviceGray -eq and{CurrentInkName_5(Black)eq{IMAGE}{dup/DataSource get/TCC xd/Height get -abs{TCC pop}repeat}ifelse}{IMAGE}ifelse}ifelse}{2 index 1 ne{CurrentInkName_5 -(Black)eq{IMAGE}{/TCC xd pop pop exch pop abs{TCC pop}repeat}ifelse}{IMAGE} -ifelse}ifelse}ifelse}{IMAGE}ifelse}ifelse}bd}ifelse/WaldoColor_5 true def/@sft -{$tllx $pxf add dup $tllx gt{$pwid sub}if/$tx xd $tury $pyf sub dup $tury lt -{$phei add}if/$ty xd}bd/@stb{pathbbox/$ury xd/$urx xd/$lly xd/$llx xd}bd/@ep{{ -cvx exec}forall}bd/@tp{@sv/$in true def 2 copy dup $lly le{/$in false def}if -$phei sub $ury ge{/$in false def}if dup $urx ge{/$in false def}if $pwid add -$llx le{/$in false def}if $in{@np 2 copy m $pwid 0 rl 0 $phei neg rl $pwid neg -0 rl 0 $phei rl clip @np $pn cvlit load aload pop 7 -1 roll 5 index sub 7 -1 -roll 3 index sub Tl matrix currentmatrix/$ctm xd @ep pop pop pop pop}{pop pop -}ifelse @rs}bd/@th{@sft 0 1 $tly 1 sub{dup $psx mul $tx add{dup $llx gt{$pwid -sub}{exit}ifelse}loop exch $phei mul $ty exch sub 0 1 $tlx 1 sub{$pwid mul 3 -copy 3 -1 roll add exch @tp pop}for pop pop}for}bd/@tv{@sft 0 1 $tlx 1 sub{dup -$pwid mul $tx add exch $psy mul $ty exch sub{dup $ury lt{$phei add}{exit} -ifelse}loop 0 1 $tly 1 sub{$phei mul 3 copy sub @tp pop}for pop pop}for}bd/$fm -0 def/wfill{1 $fm eq{fill}{eofill}ifelse}bd/wclip{1 $fm eq{clip}{eoclip}ifelse -}bd/@pf{@gs $ctm setmatrix $pm concat @stb wclip @sv Bburx Bbury $pm itransform -/$tury xd/$turx xd Bbllx Bblly $pm itransform/$tlly xd/$tllx xd newpath $tllx -$tlly m $tllx $tury l $turx $tury l $turx $tlly l $tllx $tlly m @cp pathbbox -@rs/$tury xd/$turx xd/$tlly xd/$tllx xd/$wid $turx $tllx sub def/$hei $tury -$tlly sub def @gs $vectpat{1 0 0 0 0 _ $o @scc_5{wfill}if}{$t $c $m $y $k $n $o -@scc_5{SepMode_5 0 eq $pfrg or{$tllx $tlly Tl $wid $hei scale <00> 8 1 false[8 -0 0 1 0 0]{}imagemask}{/$bkg true def}ifelse}if}ifelse @gr $wid 0 gt $hei 0 gt -and{$pn cvlit load aload pop/$pd xd 3 -1 roll sub/$phei xd exch sub/$pwid xd -$wid $pwid div ceiling 1 add/$tlx xd $hei $phei div ceiling 1 add/$tly xd $psx -0 eq{@tv}{@th}ifelse}if @gr @np/$bkg false def}bd/@Pf{@sv SepMode_5 0 eq $Psc 0 -ne or $ink_5 3 eq or{0 J 0 j[]0 d $t $c $m $y $k $n $o @scc_5 pop $ctm -setmatrix 72 1000 div dup matrix scale dup concat dup Bburx exch Bbury exch -itransform ceiling cvi/Bbury xd ceiling cvi/Bburx xd Bbllx exch Bblly exch -itransform floor cvi/Bblly xd floor cvi/Bbllx xd $Prm aload pop $Psn load exec -}{1 SetGry wfill}ifelse @rs @np}bd/F{matrix currentmatrix $sdf{$scf $sca $scp -@ss}if $fil 1 eq{@pf}{$fil 2 eq{@ff}{$fil 3 eq{@Pf}{$t $c $m $y $k $n $o @scc_5 -{wfill}{@np}ifelse}ifelse}ifelse}ifelse $sdf{$dsf $dsa $dsp @ss}if setmatrix} -bd/f{@cp F}bd/S{matrix currentmatrix $ctm setmatrix $SDF{$SCF $SCA $SCP @ss}if -$T $C $M $Y $K $N $O @scc_5{matrix currentmatrix $ptm concat stroke setmatrix} -{@np}ifelse $SDF{$dsf $dsa $dsp @ss}if setmatrix}bd/s{@cp S}bd/B{@gs F @gr S} -bd/b{@cp B}bd/_E{5 array astore exch cvlit xd}bd/@cc{currentfile $dat -readhexstring pop}bd/@sm{/$ctm $ctm currentmatrix def}bd/@E{/Bbury xd/Bburx xd -/Bblly xd/Bbllx xd}bd/@c{@cp}bd/@p{/$fil 1 def 1 eq dup/$vectpat xd{/$pfrg true -def}{@gs $t $c $m $y $k $n $o @scc_5/$pfrg xd @gr}ifelse/$pm xd/$psy xd/$psx xd -/$pyf xd/$pxf xd/$pn xd}bd/@P{/$fil 3 def/$Psn xd/$Psc xd array astore/$Prm xd -}bd/@ii{concat 3 index 3 index m 3 index 1 index l 2 copy l 1 index 3 index l 3 -index 3 index l clip pop pop pop pop}bd/tcc{@cc}def/@i{@sm @gs @ii 6 index 1 ne -{/$frg true def pop pop}{1 eq{s1t s1c s1m s1y s1k s1n $O @scc_5/$frg xd}{/$frg -false def}ifelse 1 eq{@gs $ctm setmatrix F @gr}if}ifelse @np/$ury xd/$urx xd -/$lly xd/$llx xd/$bts xd/$hei xd/$wid xd/$dat $wid $bts mul 8 div ceiling cvi -string def $bkg $frg or{$SDF{$SCF $SCA $SCP @ss}if $llx $lly Tl $urx $llx sub -$ury $lly sub scale $bkg{$t $c $m $y $k $n $o @scc_5 pop}if $wid $hei abs $bts -1 eq{$bkg}{$bts}ifelse[$wid 0 0 $hei neg 0 $hei 0 gt{$hei}{0}ifelse]/tcc load -$bts 1 eq{imagemask}{image}ifelse $SDF{$dsf $dsa $dsp @ss}if}{$hei abs{tcc pop} -repeat}ifelse @gr $ctm setmatrix}bd/@I{@sm @gs @ii @np/$ury xd/$urx xd/$lly xd -/$llx xd/$ncl xd/$bts xd/$hei xd/$wid xd/$dat $wid $bts mul $ncl mul 8 div -ceiling cvi string def $ngx $llx $lly Tl $urx $llx sub $ury $lly sub scale $wid -$hei abs $bts[$wid 0 0 $hei neg 0 $hei 0 gt{$hei}{0}ifelse]/@cc load false $ncl -ColorImage $SDF{$dsf $dsa $dsp @ss}if @gr $ctm setmatrix}bd/COMP 0 def -/MaskedImage false def L2?{/@I_2{@sm @gs @ii @np/$ury xd/$urx xd/$lly xd/$llx -xd/$ncl xd/$bts xd/$hei xd/$wid xd/$dat $wid $bts mul $ncl mul 8 div ceiling -cvi string def $ngx $ncl 1 eq{/DeviceGray}{$ncl 3 eq{/DeviceRGB}{/DeviceCMYK} -ifelse}ifelse setcolorspace $llx $lly Tl $urx $llx sub $ury $lly sub scale 8 -dict begin/ImageType 1 def/Width $wid def/Height $hei abs def/BitsPerComponent -$bts def/Decode $ncl 1 eq{[0 1]}{$ncl 3 eq{[0 1 0 1 0 1]}{[0 1 0 1 0 1 0 1]} -ifelse}ifelse def/ImageMatrix[$wid 0 0 $hei neg 0 $hei 0 gt{$hei}{0}ifelse]def -/DataSource currentfile/ASCII85Decode filter COMP 1 eq{/DCTDecode filter}{COMP -2 eq{/RunLengthDecode filter}if}ifelse def currentdict end image $SDF{$dsf $dsa -$dsp @ss}if @gr $ctm setmatrix}bd}{/@I_2{}bd}ifelse/@I_3{@sm @gs @ii @np/$ury -xd/$urx xd/$lly xd/$llx xd/$ncl xd/$bts xd/$hei xd/$wid xd/$dat $wid $bts mul -$ncl mul 8 div ceiling cvi string def $ngx $ncl 1 eq{/DeviceGray}{$ncl 3 eq -{/DeviceRGB}{/DeviceCMYK}ifelse}ifelse setcolorspace $llx $lly Tl $urx $llx sub -$ury $lly sub scale/ImageDataDict 8 dict def ImageDataDict begin/ImageType 1 -def/Width $wid def/Height $hei abs def/BitsPerComponent $bts def/Decode $ncl 1 -eq{[0 1]}{$ncl 3 eq{[0 1 0 1 0 1]}{[0 1 0 1 0 1 0 1]}ifelse}ifelse def -/ImageMatrix[$wid 0 0 $hei neg 0 $hei 0 gt{$hei}{0}ifelse]def/DataSource -currentfile/ASCII85Decode filter COMP 1 eq{/DCTDecode filter}{COMP 2 eq{ -/RunLengthDecode filter}if}ifelse def end/MaskedImageDict 7 dict def -MaskedImageDict begin/ImageType 3 def/InterleaveType 3 def/MaskDict -ImageMaskDict def/DataDict ImageDataDict def end MaskedImageDict image $SDF -{$dsf $dsa $dsp @ss}if @gr $ctm setmatrix}bd/@SetMask{/$mbts xd/$mhei xd/$mwid -xd/ImageMaskDict 8 dict def ImageMaskDict begin/ImageType 1 def/Width $mwid def -/Height $mhei abs def/BitsPerComponent $mbts def/DataSource maskstream def -/ImageMatrix[$mwid 0 0 $mhei neg 0 $mhei 0 gt{$mhei}{0}ifelse]def/Decode[1 0] -def end}bd/@B{@gs S @gr F}bd/@b{@cp @B}bd/@sep{CurrentInkName_5(Composite)eq -{/$ink_5 -1 def}{CurrentInkName_5(Cyan)eq{/$ink_5 0 def}{CurrentInkName_5 -(Magenta)eq{/$ink_5 1 def}{CurrentInkName_5(Yellow)eq{/$ink_5 2 def}{ -CurrentInkName_5(Black)eq{/$ink_5 3 def}{/$ink_5 4 def}ifelse}ifelse}ifelse} -ifelse}ifelse}bd/@whi{@gs -72000 dup m -72000 72000 l 72000 dup l 72000 -72000 -l @cp 1 SetGry fill @gr}bd/@neg{[{1 exch sub}/exec cvx currenttransfer/exec -cvx]cvx settransfer @whi}bd/deflevel 0 def/@sax{/deflevel deflevel 1 add def} -bd/@eax{/deflevel deflevel dup 0 gt{1 sub}if def deflevel 0 gt{/eax load}{eax} -ifelse}bd/eax{{exec}forall}bd/@rax{deflevel 0 eq{@rs @sv}if}bd/@daq{dup type -/arraytype eq{{}forall}if}bd/@BMP{/@cc xd UseLevel 3 eq MaskedImage true eq and -{7 -2 roll pop pop @I_3}{12 index 1 gt UseLevel 2 eq UseLevel 3 eq or and{7 -2 -roll pop pop @I_2}{11 index 1 eq{12 -1 roll pop @i}{7 -2 roll pop pop @I} -ifelse}ifelse}ifelse}bd systemdict/pdfmark known not{/pdfmark/cleartomark ld} -if -end -%%EndResource -%%EndProlog -%%BeginSetup -wCorel8Dict begin -@BeginSysCorelDict -2.6131 setmiterlimit -1.00 setflat -/$fst 128 def -%%EndSetup - -%%Page: 1 1 -%LogicalPage: 1 -%%BeginPageSetup -@sv -@sm -@sv -%%EndPageSetup -@rax %Note: Object -482.82180 -96.51912 768.51780 183.48888 @E - 1 O 0 @g -0.00 0.00 0.00 1.00 k -/$fm 0 def -614.79780 -96.51912 m -610.54980 -96.23112 606.30180 -96.01512 602.05380 -95.79912 C -594.49380 -90.75912 581.53380 -79.52712 580.23780 -71.31912 C -573.54180 -72.90312 569.86980 -76.71912 564.46980 -80.75112 C -552.15780 -82.69512 539.12580 -86.94312 527.60580 -86.36712 C -523.57380 -78.30312 530.26980 -71.10312 536.96580 -68.29512 C -538.76580 -62.10312 543.30180 -61.16712 548.62980 -57.71112 C -548.05380 -50.00712 537.32580 -44.82312 535.88580 -36.61512 C -535.38180 -36.61512 534.87780 -36.61512 534.37380 -36.61512 C -532.50180 -26.82312 531.70980 -26.10312 531.34980 -23.79912 C -533.65380 -22.64712 533.65380 -22.64712 534.37380 -20.77512 C -547.33380 -17.96712 552.58980 -17.03112 564.82980 -26.10312 C -565.04580 -28.04712 576.56580 -35.39112 580.23780 -35.89512 C -582.54180 -11.48712 580.59780 7.59288 565.98180 27.10488 C -565.18980 27.24888 564.39780 27.39288 563.67780 27.46488 C -562.23780 18.17688 557.84580 10.32888 552.01380 2.55288 C -540.92580 -2.05512 538.54980 13.64088 536.60580 19.90488 C -535.81380 19.90488 535.09380 19.90488 534.37380 19.90488 C -529.40580 9.89688 520.69380 -0.83112 508.81380 -0.83112 C -494.26980 8.88888 501.90180 18.60888 506.94180 32.00088 C -510.03780 38.04888 513.13380 44.09688 516.30180 50.07288 C -502.62180 47.69688 487.28580 43.37688 482.82180 60.58488 C -482.82180 75.92088 495.99780 80.81688 508.81380 83.62488 C -509.17380 86.21688 509.17380 86.21688 507.66180 90.03288 C -511.62180 108.96888 509.53380 106.66488 525.73380 108.82488 C -525.80580 109.83288 525.94980 110.84088 526.09380 111.84888 C -531.06180 111.56088 536.31780 105.94488 538.11780 100.54488 C -543.08580 102.27288 544.45380 108.17688 546.75780 112.64088 C -552.80580 120.12888 556.18980 122.79288 556.90980 124.66488 C -559.21380 123.58488 559.21380 123.58488 561.80580 123.94488 C -562.16580 125.09688 562.52580 126.24888 562.95780 127.32888 C -566.34180 126.68088 572.67780 117.10488 574.54980 113.36088 C -574.54980 105.87288 573.68580 99.60888 571.95780 91.54488 C -577.50180 91.83288 580.09380 94.92888 587.00580 94.92888 C -589.95780 92.91288 592.98180 90.89688 596.00580 88.88088 C -606.15780 73.32888 587.65380 57.34488 575.70180 48.20088 C -568.93380 45.17688 568.93380 45.17688 565.54980 42.51288 C -565.40580 41.28888 565.26180 40.06488 565.18980 38.76888 C -565.69380 38.76888 566.19780 38.76888 566.70180 38.76888 C -575.12580 25.16088 585.85380 9.10488 587.72580 -7.95912 C -592.83780 -6.08712 598.74180 27.75288 600.90180 34.23288 C -603.99780 38.76888 605.72580 44.45688 606.87780 49.28088 C -618.97380 73.40088 624.94980 83.19288 625.30980 85.49688 C -605.86980 84.77688 614.79780 105.15288 618.18180 116.38488 C -622.42980 122.72088 625.74180 126.39288 631.35780 131.14488 C -631.21380 151.08888 626.38980 166.28088 648.63780 171.39288 C -648.34980 172.40088 648.13380 173.40888 647.91780 174.41688 C -658.42980 174.05688 660.15780 170.45688 661.45380 158.57688 C -667.93380 160.16088 680.46180 184.42488 687.01380 177.87288 C -688.74180 178.37688 690.46980 178.88088 692.26980 179.38488 C -692.62980 181.25688 692.62980 181.25688 691.47780 183.48888 C -700.90980 182.98488 702.42180 175.13688 702.42180 166.13688 C -697.09380 155.62488 697.09380 155.62488 696.73380 153.32088 C -709.47780 152.88888 712.14180 142.30488 712.14180 129.99288 C -706.95780 120.63288 695.50980 108.96888 684.70980 106.95288 C -681.90180 104.57688 680.74980 103.35288 677.22180 102.84888 C -677.72580 90.24888 664.33380 71.67288 652.38180 67.06488 C -639.49380 70.16088 648.34980 87.36888 644.17380 94.92888 C -643.38180 95.07288 642.58980 95.21688 641.86980 95.28888 C -641.14980 93.41688 637.76580 90.75288 625.66980 72.68088 C -611.91780 47.40888 601.26180 22.64088 597.51780 -6.44712 C -612.63780 -1.04712 612.49380 24.29688 632.07780 22.92888 C -632.07780 22.42488 632.07780 21.92088 632.07780 21.41688 C -632.58180 21.41688 633.08580 21.41688 633.58980 21.41688 C -635.10180 12.99288 632.29380 6.72888 629.84580 -1.98312 C -638.62980 3.99288 643.23780 15.36888 654.68580 18.03288 C -660.01380 23.64888 665.41380 25.95288 672.32580 29.69688 C -673.26180 48.12888 680.02980 58.78488 697.09380 66.63288 C -695.43780 84.92088 694.28580 92.19288 710.26980 103.20888 C -714.08580 103.20888 714.80580 102.48888 721.21380 102.84888 C -721.71780 103.85688 722.22180 104.86488 722.72580 105.87288 C -723.58980 106.01688 724.45380 106.16088 725.31780 106.23288 C -729.06180 102.48888 731.94180 99.75288 732.87780 93.05688 C -740.29380 95.21688 745.04580 103.20888 753.90180 103.20888 C -754.69380 102.48888 754.69380 102.48888 757.64580 102.48888 C -762.03780 106.88088 763.98180 104.50488 768.51780 100.54488 C -767.65380 91.25688 763.33380 83.84088 757.64580 76.06488 C -753.32580 75.05688 749.07780 67.13688 747.92580 62.88888 C -749.36580 62.67288 750.87780 62.38488 752.38980 62.09688 C -754.47780 57.05688 758.79780 53.60088 758.79780 46.25688 C -751.30980 29.62488 738.06180 20.26488 720.06180 20.26488 C -715.45380 15.00888 703.64580 1.47288 695.65380 6.36888 C -691.11780 13.13688 694.57380 20.84088 695.29380 27.46488 C -675.78180 25.01688 656.34180 16.16088 642.66180 2.19288 C -640.35780 -1.55112 638.48580 -2.70312 638.12580 -5.00712 C -643.38180 -5.36712 643.38180 -5.36712 645.25380 -6.44712 C -645.25380 -20.84712 643.74180 -31.14312 637.76580 -44.89512 C -636.25380 -46.40712 636.25380 -46.40712 635.89380 -48.71112 C -648.99780 -49.07112 665.12580 -49.79112 678.73380 -51.73512 C -678.73380 -52.23912 678.73380 -52.74312 678.73380 -53.24712 C -663.39780 -63.61512 643.09380 -80.31912 623.86980 -81.11112 C -625.81380 -93.99912 627.97380 -95.00712 614.79780 -96.51912 C -@c -F - -@rax %Note: Object -587.00580 -92.77512 618.15345 -70.52712 @E - 1 O 0 @g -0.00 0.00 0.00 0.00 k -/$fm 0 def -613.28580 -92.77512 m -609.68580 -86.58312 600.61380 -91.40712 596.79780 -82.62312 C -596.29380 -82.62312 595.78980 -82.62312 595.28580 -82.62312 C -591.61380 -77.36712 589.16580 -75.85512 587.00580 -70.52712 C -594.49380 -70.88712 598.88580 -71.03112 603.92580 -78.08712 C -609.54180 -80.75112 609.54180 -80.75112 617.10180 -82.62312 C -618.39780 -87.15912 619.69380 -92.77512 613.28580 -92.77512 C -@c -F - -@rax %Note: Object -534.37380 -79.95912 573.46980 -64.09020 @E - 1 O 0 @g -0.00 0.00 0.00 0.00 k -/$fm 0 def -535.88580 -79.95912 m -535.38180 -79.81512 534.87780 -79.67112 534.37380 -79.59912 C -539.77380 -60.80712 558.34980 -63.54312 573.46980 -65.27112 C -567.13380 -78.01512 550.14180 -74.19912 539.62980 -78.44712 C -538.11780 -79.95912 538.11780 -79.95912 535.88580 -79.95912 C -@c -F - -@rax %Note: Object -612.20580 -75.42312 656.91780 -55.75946 @E - 1 O 0 @g -0.00 0.00 0.00 0.00 k -/$fm 0 def -620.05380 -75.42312 m -616.88580 -74.63112 612.63780 -68.79912 612.20580 -66.06312 C -630.34980 -54.75912 634.95780 -54.03912 656.91780 -57.71112 C -651.44580 -68.15112 631.64580 -75.42312 620.05380 -75.42312 C -@c -F - -@rax %Note: Object --104.09528 -70.16712 339.97380 514.40088 @E - 1 O 0 @g -0.00 0.00 0.00 1.00 k -/$fm 0 def -226.14180 -70.16712 m -220.81380 -64.83912 220.23780 -58.86312 212.60580 -56.99112 C -211.66980 -58.21512 210.80580 -59.43912 209.94180 -60.73512 C -201.08580 -61.02312 198.63780 -59.36712 198.63780 -50.94312 C -196.04580 -47.41512 195.90180 -47.19912 197.55780 -44.17512 C -195.54180 -44.17512 193.52580 -44.17512 191.50980 -44.17512 C -185.82180 -54.83112 185.96580 -59.22312 174.22980 -61.09512 C -169.18980 -57.71112 169.62180 -56.55912 168.61380 -51.73512 C -167.67780 -51.59112 166.81380 -51.44712 165.94980 -51.30312 C -153.34980 -67.43112 148.02180 -71.17512 140.38980 -51.73512 C -133.62180 -54.32712 133.62180 -54.32712 127.64580 -58.07112 C -116.48580 -58.07112 114.75780 -54.18312 114.10980 -44.89512 C -106.04580 -45.47112 99.63780 -55.19112 92.29380 -59.58312 C -89.26980 -59.58312 86.24580 -59.58312 83.29380 -59.58312 C -78.46980 -54.75912 78.32580 -52.81512 78.75780 -47.19912 C -72.06180 -48.49512 70.47780 -55.11912 62.19780 -55.11912 C -57.66180 -49.35912 58.38180 -47.05512 57.73380 -40.43112 C -53.91780 -41.51112 53.91780 -41.51112 47.94180 -44.53512 C -24.54180 -44.53512 29.36580 -27.11112 40.45380 -14.36712 C -48.66180 -9.83112 47.79780 -10.62312 56.94180 -10.62312 C -77.24580 9.68088 33.90180 36.53688 22.38180 45.17688 C -2.36580 55.18488 -17.72220 68.07288 -33.99420 82.11288 C --37.66620 83.91288 -45.44220 89.60088 -47.53020 90.03288 C --47.53020 90.53688 -47.53020 91.04088 -47.53020 91.54488 C --60.56220 102.63288 -68.84220 110.04888 -77.98620 122.43288 C --79.49820 126.96888 -82.52220 130.71288 -91.52220 146.55288 C --107.50620 182.40888 -113.69820 219.12888 -77.62620 243.46488 C --59.26620 249.65688 -50.26620 247.64088 -30.25020 244.90488 C --29.31420 243.96888 -8.21820 236.12088 -4.69020 235.47288 C --4.47420 234.89688 -4.25820 234.24888 -3.97020 233.60088 C -4.23780 232.23288 8.19780 227.40888 16.33380 224.96088 C -16.33380 224.45688 16.33380 223.95288 16.33380 223.44888 C -19.06980 221.93688 21.80580 220.42488 24.61380 218.91288 C -24.61380 218.40888 24.61380 217.90488 24.61380 217.40088 C -34.26180 213.44088 44.84580 189.68088 49.45380 178.95288 C -57.08580 182.84088 50.60580 193.78488 52.04580 201.56088 C -52.04580 207.82488 50.82180 221.00088 54.27780 226.47288 C -51.61380 269.67288 66.66180 315.53688 81.34980 355.35288 C -84.37380 359.09688 84.37380 359.09688 93.37380 377.96088 C -109.71780 400.35288 123.39780 430.23288 135.49380 454.85688 C -141.97380 473.57688 150.68580 493.88088 154.28580 512.88888 C -155.43780 514.04088 155.43780 514.04088 159.97380 514.40088 C -154.71780 483.22488 139.81380 451.47288 126.13380 422.81688 C -125.62980 422.74488 125.12580 422.60088 124.62180 422.45688 C -120.37380 404.38488 103.74180 389.55288 97.90980 371.91288 C -95.10180 366.44088 92.36580 360.89688 89.62980 355.35288 C -85.74180 353.91288 78.18180 327.84888 76.88580 320.64888 C -67.23780 302.07288 65.14980 271.11288 61.04580 249.80088 C -61.04580 236.98488 58.81380 218.91288 58.09380 200.84088 C -60.32580 164.62488 60.32580 164.62488 60.32580 155.62488 C -58.74180 153.96888 56.79780 151.88088 54.70980 151.44888 C -51.18180 157.92888 51.54180 163.25688 47.50980 168.44088 C -36.99780 191.04888 34.04580 195.51288 32.53380 199.32888 C -8.77380 229.06488 -34.28220 244.61688 -70.85820 238.92888 C --76.76220 236.55288 -83.81820 233.74488 -86.26620 227.19288 C --102.17820 211.28088 -97.06620 190.32888 -93.82620 169.95288 C --88.28220 156.12888 -74.09820 119.55288 -59.55420 113.36088 C --56.60220 108.10488 -39.97020 91.18488 -32.48220 90.03288 C --32.48220 89.52888 -32.48220 89.02488 -32.48220 88.52088 C --29.38620 86.79288 -26.29020 84.99288 -23.12220 83.19288 C --23.05020 82.76088 -22.90620 82.25688 -22.76220 81.75288 C --12.17820 77.00088 -1.16220 63.75288 10.35780 61.01688 C -12.51780 58.85688 23.53380 52.52088 27.63780 51.58488 C -30.73380 43.16088 45.27780 42.15288 49.45380 32.72088 C -57.15780 29.98488 64.71780 19.11288 67.81380 10.83288 C -67.81380 2.33688 69.61380 -5.36712 63.70980 -12.85512 C -51.18180 -16.23912 38.50980 -18.90312 38.50980 -34.38312 C -40.02180 -35.89512 40.02180 -35.89512 40.45380 -37.40712 C -44.70180 -36.75912 48.94980 -36.11112 53.19780 -35.46312 C -55.71780 -31.14312 61.33380 -27.83112 66.37380 -28.33512 C -66.37380 -34.31112 64.93380 -40.14312 65.22180 -47.19912 C -77.38980 -43.45512 72.70980 -30.92712 84.80580 -29.84712 C -85.23780 -36.90312 83.86980 -43.88712 85.52580 -51.73512 C -98.26980 -50.72712 104.67780 -40.64712 115.62180 -34.74312 C -119.43780 -25.23912 121.30980 -18.18312 132.90180 -16.31112 C -132.90180 -17.03112 132.90180 -17.75112 132.90180 -18.54312 C -145.64580 -14.65512 139.59780 -8.46312 143.77380 0.32088 C -143.77380 10.11288 143.77380 19.90488 143.77380 29.69688 C -140.89380 49.49688 138.66180 68.86488 134.77380 87.72888 C -134.77380 126.68088 107.70180 164.26488 107.70180 203.07288 C -106.18980 206.88888 103.16580 233.24088 102.44580 233.96088 C -96.32580 258.51288 101.07780 285.94488 102.44580 310.13688 C -103.16580 313.16088 104.67780 314.67288 105.10980 319.20888 C -106.69380 318.99288 108.27780 318.70488 109.93380 318.41688 C -107.70180 289.76088 107.70180 289.76088 106.90980 289.04088 C -106.90980 256.85688 110.65380 226.04088 114.46980 193.28088 C -117.99780 181.11288 123.18180 165.92088 124.98180 152.60088 C -132.97380 144.60888 141.75780 91.54488 143.77380 80.96088 C -145.28580 64.40088 146.79780 59.07288 150.54180 24.44088 C -150.25380 12.63288 150.03780 0.82488 149.82180 -10.98312 C -142.69380 -25.31112 131.67780 -18.75912 123.46980 -31.35912 C -122.31780 -36.47112 119.14980 -44.46312 122.38980 -49.79112 C -132.46980 -55.04712 136.21380 -39.35112 146.43780 -38.48712 C -146.94180 -45.25512 146.07780 -50.58312 150.18180 -56.55912 C -153.20580 -56.55912 153.20580 -56.55912 158.02980 -54.75912 C -161.05380 -47.91912 167.10180 -38.12712 169.76580 -32.43912 C -171.49380 -32.58312 173.22180 -32.72712 175.02180 -32.87112 C -175.38180 -47.19912 175.38180 -47.19912 176.53380 -52.09512 C -185.67780 -52.09512 184.38180 -46.40712 187.40580 -38.91912 C -188.91780 -21.71112 190.71780 -3.85512 184.38180 12.34488 C -183.22980 23.36088 182.43780 30.12888 184.02180 40.28088 C -183.51780 40.28088 183.01380 40.28088 182.50980 40.28088 C -182.07780 54.39288 183.30180 68.14488 184.38180 81.75288 C -180.13380 90.10488 179.41380 99.39288 177.97380 107.74488 C -177.46980 107.88888 176.96580 108.03288 176.53380 108.10488 C -175.38180 114.87288 176.10180 115.66488 175.02180 124.66488 C -165.66180 122.50488 166.02180 122.57688 163.35780 131.50488 C -163.35780 140.50488 164.07780 141.29688 164.79780 148.06488 C -164.07780 148.78488 164.79780 167.64888 164.07780 193.28088 C -163.06980 201.63288 162.06180 209.91288 161.05380 218.19288 C -155.72580 218.62488 156.51780 215.52888 151.33380 219.70488 C -145.57380 239.93688 152.98980 260.88888 153.92580 280.76088 C -151.69380 280.40088 149.46180 278.09688 147.94980 278.09688 C -138.22980 287.81688 162.34980 335.91288 163.71780 344.84088 C -157.59780 344.26488 152.19780 327.63288 146.79780 332.74488 C -146.79780 337.28088 148.30980 340.30488 149.02980 344.04888 C -154.21380 353.62488 179.55780 393.36888 177.97380 404.02488 C -177.18180 404.02488 176.46180 404.02488 175.74180 404.02488 C -172.50180 400.13688 170.77380 397.18488 165.94980 397.18488 C -161.48580 401.64888 168.97380 410.14488 171.56580 414.17688 C -179.26980 431.45688 190.14180 449.60088 194.17380 467.67288 C -198.34980 473.50488 197.91780 485.24088 205.04580 486.53688 C -205.90980 467.38488 189.92580 438.80088 181.35780 421.66488 C -178.83780 419.14488 179.19780 419.72088 178.76580 415.68888 C -181.71780 416.33688 184.74180 416.98488 187.76580 417.56088 C -188.48580 417.05688 189.20580 416.55288 189.99780 416.04888 C -191.29380 396.46488 165.15780 373.42488 160.69380 352.32888 C -165.30180 354.99288 168.03780 359.52888 173.50980 359.52888 C -175.02180 358.01688 175.02180 358.01688 175.38180 356.14488 C -166.45380 336.92088 159.68580 321.44088 155.43780 300.34488 C -160.90980 303.08088 156.58980 308.91288 165.58980 307.83288 C -165.58980 305.60088 165.58980 303.36888 165.58980 301.06488 C -159.75780 280.61688 156.73380 260.09688 156.22980 238.49688 C -158.60580 239.36088 158.53380 240.94488 159.18180 243.03288 C -161.41380 243.03288 163.64580 243.03288 165.94980 243.03288 C -167.53380 211.42488 170.62980 179.60088 171.99780 147.27288 C -172.57380 147.56088 173.22180 147.84888 173.86980 148.06488 C -175.38180 156.34488 175.38180 156.34488 177.97380 163.11288 C -179.70180 163.11288 181.50180 163.11288 183.30180 163.11288 C -183.44580 155.55288 183.22980 150.65688 181.35780 142.80888 C -182.14980 125.45688 182.86980 124.66488 182.50980 114.87288 C -183.01380 114.87288 183.51780 114.87288 184.02180 114.87288 C -183.58980 104.00088 184.02180 99.75288 188.55780 89.24088 C -194.53380 90.68088 208.35780 129.05688 211.45380 136.76088 C -212.17380 144.03288 211.81380 152.16888 219.73380 145.76088 C -220.30980 140.50488 220.95780 135.24888 221.60580 129.99288 C -222.46980 130.28088 223.33380 130.49688 224.19780 130.71288 C -227.22180 138.27288 230.24580 143.52888 236.22180 160.08888 C -240.54180 168.58488 241.83780 180.75288 248.31780 187.23288 C -248.17380 189.75288 248.02980 192.27288 247.95780 194.79288 C -251.70180 195.15288 251.70180 195.15288 254.72580 194.07288 C -257.02980 188.81688 259.40580 183.56088 261.78180 178.23288 C -262.42980 178.16088 263.07780 178.01688 263.72580 177.87288 C -270.13380 197.96088 269.34180 223.44888 269.34180 243.82488 C -270.85380 247.56888 270.85380 252.10488 272.00580 253.97688 C -279.99780 252.46488 282.73380 238.78488 284.74980 231.00888 C -286.62180 231.72888 286.62180 231.72888 287.41380 233.24088 C -288.34980 262.04088 284.53380 290.98488 285.90180 319.20888 C -287.41380 320.64888 287.41380 320.64888 289.28580 321.08088 C -292.52580 318.48888 296.05380 311.43288 300.51780 310.49688 C -301.95780 318.27288 294.10980 335.98488 291.87780 343.32888 C -288.85380 349.44888 278.77380 368.96088 282.08580 374.21688 C -287.12580 374.57688 290.29380 369.39288 294.90180 370.40088 C -292.66980 377.96088 292.66980 377.96088 289.64580 384.72888 C -280.64580 397.54488 267.10980 413.38488 266.74980 414.89688 C -246.51780 429.87288 232.18980 452.55288 221.24580 473.72088 C -218.22180 477.03288 215.19780 480.27288 212.17380 483.51288 C -210.66180 488.76888 210.66180 488.76888 209.22180 490.28088 C -211.09380 510.51288 215.34180 492.22488 219.73380 488.76888 C -221.10180 480.63288 230.31780 470.04888 233.98980 460.90488 C -249.03780 440.52888 249.03780 440.52888 258.03780 430.73688 C -260.98980 429.65688 276.03780 412.37688 276.46980 410.36088 C -276.97380 410.36088 277.47780 410.36088 277.98180 410.36088 C -277.98180 409.85688 277.98180 409.35288 277.98180 408.84888 C -278.48580 408.84888 278.98980 408.84888 279.49380 408.84888 C -279.56580 408.12888 279.70980 407.40888 279.85380 406.61688 C -290.94180 394.23288 299.29380 382.49688 303.54180 365.14488 C -304.04580 365.14488 304.54980 365.14488 305.05380 365.14488 C -307.21380 360.82488 305.41380 356.50488 300.51780 356.50488 C -297.78180 360.10488 295.26180 362.26488 291.51780 362.91288 C -295.04580 340.80888 308.43780 322.30488 308.43780 297.32088 C -307.93380 296.60088 307.42980 295.88088 306.92580 295.08888 C -306.06180 295.01688 305.19780 294.87288 304.33380 294.72888 C -300.15780 298.83288 297.13380 307.11288 292.30980 307.83288 C -291.87780 298.04088 294.18180 258.08088 294.18180 236.98488 C -293.82180 231.08088 295.76580 212.72088 287.77380 210.99288 C -285.10980 215.16888 285.10980 215.16888 283.23780 220.42488 C -283.81380 220.71288 284.46180 220.92888 285.10980 221.14488 C -284.96580 221.79288 284.82180 222.44088 284.74980 223.08888 C -279.20580 225.39288 278.91780 232.88088 277.98180 237.77688 C -277.18980 237.77688 276.46980 237.77688 275.74980 237.77688 C -274.95780 215.02488 274.38180 188.52888 268.54980 165.41688 C -265.88580 162.60888 265.59780 162.39288 262.21380 162.39288 C -261.78180 165.92088 261.42180 169.44888 261.06180 172.90488 C -255.37380 175.56888 255.80580 176.93688 253.93380 181.61688 C -252.92580 181.76088 251.91780 181.90488 250.90980 181.97688 C -241.54980 157.85688 233.91780 137.69688 230.24580 111.84888 C -224.41380 90.17688 215.55780 70.01688 207.70980 48.56088 C -204.68580 32.00088 204.68580 32.00088 198.63780 6.36888 C -198.34980 -8.96712 198.06180 -23.22312 199.42980 -38.91912 C -202.45380 -46.40712 202.45380 -46.40712 203.53380 -51.30312 C -204.75780 -51.30312 206.05380 -51.30312 207.34980 -51.30312 C -210.44580 -47.48712 209.86980 -46.40712 209.58180 -41.94312 C -212.67780 -39.99912 212.38980 -39.99912 216.70980 -40.43112 C -220.02180 -46.98312 220.45380 -54.18312 225.34980 -61.88712 C -226.42980 -61.74312 227.58180 -61.59912 228.73380 -61.52712 C -231.54180 -54.68712 230.46180 -45.90312 230.60580 -38.91912 C -239.53380 -41.51112 238.95780 -53.60712 247.16580 -53.60712 C -255.94980 -42.66312 244.14180 -32.29512 236.22180 -26.10312 C -229.30980 -11.55912 231.68580 13.49688 240.75780 25.16088 C -253.06980 55.32888 270.13380 81.17688 285.90180 108.82488 C -309.94980 168.44088 309.94980 168.44088 314.41380 181.97688 C -323.34180 215.45688 327.30180 248.64888 330.97380 282.27288 C -335.36580 286.23288 330.39780 325.25688 334.35780 332.38488 C -343.35780 332.38488 338.10180 314.16888 339.97380 306.39288 C -337.02180 261.89688 337.02180 261.89688 334.71780 245.26488 C -329.67780 231.72888 328.23780 216.46488 326.50980 201.56088 C -318.51780 172.68888 307.14180 144.53688 296.41380 116.38488 C -281.36580 88.52088 278.34180 84.70488 269.70180 67.78488 C -269.19780 67.71288 268.69380 67.56888 268.18980 67.42488 C -253.06980 37.47288 235.93380 15.65688 240.75780 -18.54312 C -245.22180 -24.23112 254.14980 -28.83912 256.16580 -37.40712 C -257.89380 -37.26312 259.62180 -37.11912 261.42180 -36.97512 C -264.94980 -41.15112 275.67780 -57.85512 284.38980 -56.19912 C -284.24580 -52.88712 284.10180 -49.64712 284.02980 -46.40712 C -289.57380 -47.41512 293.53380 -48.35112 294.54180 -55.47912 C -295.04580 -55.47912 295.54980 -55.47912 296.05380 -55.47912 C -293.74980 -61.59912 290.86980 -64.04712 285.54180 -68.65512 C -274.09380 -65.34312 266.67780 -52.81512 254.72580 -52.81512 C -250.18980 -60.66312 250.33380 -63.03912 241.18980 -60.73512 C -241.18980 -60.23112 241.18980 -59.72712 241.18980 -59.22312 C -239.67780 -58.71912 238.16580 -58.21512 236.65380 -57.71112 C -234.27780 -63.32712 233.34180 -70.16712 226.14180 -70.16712 C -@c -F - -@rax %Note: Object -298.07433 -67.14312 601.62180 853.66488 @E - 1 O 0 @g -0.00 0.00 0.00 1.00 k -/$fm 0 def -303.54180 -67.14312 m -302.02980 -66.63912 300.51780 -66.13512 299.07780 -65.63112 C -297.92580 -63.03912 297.92580 -63.03912 298.28580 -58.86312 C -300.01380 -58.86312 301.74180 -58.86312 303.54180 -58.86312 C -307.50180 -54.90312 306.70980 -44.53512 306.20580 -39.63912 C -311.10180 -39.27912 311.10180 -39.27912 314.05380 -40.43112 C -320.82180 -51.30312 322.69380 -52.45512 323.84580 -55.11912 C -327.30180 -54.97512 330.82980 -54.83112 334.35780 -54.75912 C -334.35780 -53.75112 334.35780 -52.74312 334.35780 -51.73512 C -334.86180 -51.73512 335.36580 -51.73512 335.86980 -51.73512 C -337.02180 -47.19912 337.02180 -47.19912 337.02180 -44.17512 C -327.80580 -25.67112 327.44580 -15.66312 330.25380 3.34488 C -332.34180 8.96088 337.16580 16.08888 338.17380 21.41688 C -338.74980 21.70488 339.32580 21.92088 339.97380 22.13688 C -346.38180 37.68888 373.66980 51.36888 389.29380 57.20088 C -406.57380 69.87288 430.47780 82.11288 444.51780 98.31288 C -454.38180 105.65688 464.31780 113.14488 470.43780 123.94488 C -483.39780 133.95288 486.99780 162.24888 488.86980 178.23288 C -482.38980 195.80088 481.30980 201.92088 463.66980 206.52888 C -442.64580 206.52888 442.64580 206.52888 441.92580 205.73688 C -427.74180 200.55288 413.19780 196.66488 399.80580 190.68888 C -390.22980 183.20088 380.72580 175.64088 371.22180 168.08088 C -364.52580 159.51288 354.22980 131.28888 347.89380 130.35288 C -345.30180 132.94488 346.23780 134.60088 346.02180 137.48088 C -354.87780 167.43288 364.52580 198.24888 369.34980 228.70488 C -381.44580 270.10488 388.86180 308.91288 390.37380 351.60888 C -388.86180 373.42488 388.86180 373.42488 384.39780 409.64088 C -383.60580 411.15288 382.88580 417.92088 381.37380 420.94488 C -380.58180 425.48088 379.86180 426.20088 373.81380 456.36888 C -370.86180 464.64888 364.81380 475.95288 358.83780 489.56088 C -357.03780 501.72888 349.69380 514.11288 345.66180 524.98488 C -344.65380 524.98488 343.64580 524.98488 342.63780 524.98488 C -342.27780 528.00888 340.76580 529.52088 339.25380 533.26488 C -338.60580 536.79288 337.95780 540.32088 337.38180 543.84888 C -336.87780 543.84888 336.37380 543.84888 335.86980 543.84888 C -332.77380 559.97688 327.80580 575.81688 324.99780 591.29688 C -321.32580 630.10488 322.76580 667.40088 323.48580 705.92088 C -327.94980 740.55288 327.94980 740.55288 329.46180 744.36888 C -331.33380 745.16088 333.20580 745.88088 335.14980 746.60088 C -336.44580 742.85688 334.28580 731.98488 332.48580 728.52888 C -328.95780 684.89688 327.44580 639.46488 331.76580 595.11288 C -335.36580 576.75288 339.75780 557.74488 345.66180 539.31288 C -346.23780 539.09688 346.88580 538.80888 347.53380 538.52088 C -349.40580 530.09688 353.86980 523.11288 357.32580 514.40088 C -358.18980 514.18488 359.05380 513.96888 359.91780 513.68088 C -365.89380 545.43288 375.18180 574.88088 382.88580 605.62488 C -401.53380 651.27288 424.42980 692.74488 448.26180 736.01688 C -458.12580 763.37688 472.38180 786.70488 476.84580 815.93688 C -476.84580 828.60888 479.58180 841.92888 482.46180 853.66488 C -484.47780 853.66488 486.49380 853.66488 488.50980 853.66488 C -488.43780 834.94488 486.13380 815.36088 490.38180 796.35288 C -493.26180 789.72888 496.14180 783.10488 499.02180 776.40888 C -507.44580 777.41688 514.14180 809.96088 526.09380 807.29688 C -527.24580 805.42488 527.24580 805.42488 528.68580 798.58488 C -528.25380 776.55288 526.81380 759.12888 533.65380 737.52888 C -534.58980 737.67288 535.59780 737.81688 536.60580 737.96088 C -542.22180 746.60088 542.22180 746.60088 552.80580 766.18488 C -555.39780 767.33688 555.39780 767.33688 558.42180 767.33688 C -560.29380 765.46488 560.43780 756.53688 558.78180 753.36888 C -560.14980 737.67288 562.88580 723.27288 565.98180 707.43288 C -566.41380 707.43288 566.91780 707.43288 567.42180 707.43288 C -567.78180 704.40888 568.57380 703.61688 572.31780 692.31288 C -577.57380 664.44888 579.08580 660.63288 582.10980 642.56088 C -585.13380 631.25688 588.08580 615.41688 589.59780 611.67288 C -596.79780 564.80088 599.96580 516.05688 601.62180 467.67288 C -598.74180 428.57688 594.06180 394.52088 585.85380 356.14488 C -581.31780 341.81688 579.87780 340.30488 573.82980 321.44088 C -555.82980 286.30488 540.78180 254.48088 516.66180 222.65688 C -508.52580 217.97688 501.32580 198.68088 493.04580 196.30488 C -492.61380 194.28888 492.25380 192.27288 491.89380 190.25688 C -492.90180 188.02488 493.90980 185.79288 494.91780 183.48888 C -496.28580 148.49688 481.45380 117.60888 452.43780 96.44088 C -426.80580 70.80888 395.05380 55.11288 364.81380 36.46488 C -364.45380 35.88888 364.09380 35.24088 363.73380 34.59288 C -342.70980 24.87288 336.66180 4.78488 335.50980 -17.03112 C -341.55780 -32.94312 352.06980 -51.66312 333.63780 -64.11912 C -322.83780 -64.11912 320.60580 -52.88712 314.05380 -51.73512 C -313.26180 -57.99912 312.10980 -67.14312 303.54180 -67.14312 C -@c -F - -@rax %Note: Object -541.14180 -56.55912 585.85380 -26.96088 @E - 1 O 0 @g -0.00 0.00 0.00 0.00 k -/$fm 0 def -570.44580 -56.55912 m -563.46180 -53.24712 556.11780 -54.68712 552.44580 -47.91912 C -551.94180 -47.91912 551.43780 -47.91912 550.93380 -47.91912 C -547.26180 -39.20712 540.42180 -37.55112 541.14180 -27.18312 C -559.35780 -25.95912 558.78180 -29.70312 574.18980 -40.43112 C -577.35780 -44.96712 579.73380 -50.07912 585.49380 -52.81512 C -585.56580 -53.46312 585.70980 -54.11112 585.85380 -54.75912 C -583.26180 -55.11912 580.23780 -53.60712 574.18980 -53.96712 C -573.90180 -54.83112 573.68580 -55.69512 573.46980 -56.55912 C -572.46180 -56.55912 571.45380 -56.55912 570.44580 -56.55912 C -@c -F - -@rax %Note: Object -598.30980 -56.55912 637.40580 -21.56712 @E - 1 O 0 @g -0.00 0.00 0.00 0.00 k -/$fm 0 def -602.77380 -56.55912 m -601.26180 -56.41512 599.74980 -56.27112 598.30980 -56.19912 C -599.82180 -48.35112 607.95780 -48.63912 614.07780 -44.53512 C -618.90180 -36.90312 629.98980 -23.79912 637.40580 -21.56712 C -635.82180 -34.02312 633.87780 -42.37512 624.58980 -50.58312 C -612.56580 -53.60712 605.79780 -56.55912 602.77380 -56.55912 C -@c -F - -@rax %Note: Object --189.56041 -47.56365 -38.17020 169.95288 @E - 1 O 0 @g -0.00 0.00 0.00 1.00 k -/$fm 0 def --111.46620 -47.55912 m --128.09820 -39.13512 -147.17820 -16.16712 -147.17820 2.55288 C --140.19420 11.33688 -142.42620 15.65688 -129.46620 11.98488 C --126.51420 9.96888 -106.42620 -5.72712 -103.90620 -5.72712 C --103.90620 -6.44712 -103.90620 -7.16712 -103.90620 -7.95912 C --98.93820 -11.48712 -95.91420 -15.59112 -91.88220 -21.13512 C --91.01820 -20.99112 -90.15420 -20.84712 -89.29020 -20.77512 C --90.73020 17.67288 -95.98620 41.64888 -120.10620 71.88888 C --120.75420 72.17688 -121.40220 72.46488 -121.97820 72.68088 C --121.97820 73.18488 -121.97820 73.68888 -121.97820 74.19288 C --122.98620 74.19288 -123.99420 74.19288 -125.00220 74.19288 C --128.31420 62.02488 -131.12220 48.20088 -146.02620 48.20088 C --155.53020 54.17688 -156.82620 67.49688 -155.02620 77.21688 C --164.09820 76.35288 -177.56220 44.96088 -189.29820 60.58488 C --190.30620 82.25688 -189.44220 95.07288 -172.37820 109.97688 C --172.30620 110.91288 -172.16220 111.77688 -172.01820 112.64088 C --188.43420 129.48888 -190.16220 134.16888 -186.63420 157.13688 C --185.91420 157.49688 -185.19420 157.85688 -184.40220 158.21688 C --179.79420 172.11288 -168.70620 168.87288 -158.05020 163.54488 C --157.33020 161.60088 -150.92220 156.34488 -150.56220 154.83288 C --149.55420 154.97688 -148.54620 155.12088 -147.53820 155.26488 C --142.85820 161.88888 -136.01820 162.17688 -133.28220 169.95288 C --112.76220 169.01688 -117.44220 150.00888 -120.89820 135.24888 C --120.75420 133.73688 -120.61020 132.22488 -120.46620 130.71288 C --87.05820 139.13688 -95.77020 106.80888 -106.57020 87.72888 C --110.53020 84.27288 -112.69020 83.26488 -117.44220 81.75288 C --115.42620 71.88888 -107.36220 66.48888 -102.39420 56.84088 C --92.89020 53.31288 -84.17820 2.62488 -82.52220 -6.44712 C --81.65820 -6.51912 -80.79420 -6.66312 -79.85820 -6.80712 C --69.99420 3.05688 -64.23420 16.66488 -52.06620 24.80088 C --42.34620 34.52088 -41.98620 32.50488 -38.17020 19.90488 C --38.17020 -14.36712 -48.53820 -36.83112 -85.11420 -42.30312 C --95.12220 -47.91912 -100.81020 -47.55912 -111.46620 -47.55912 C -@c -F - -@rax %Note: Object -588.87780 -47.55912 627.61380 15.36888 @E - 1 O 0 @g -0.00 0.00 0.00 0.00 k -/$fm 0 def -590.74980 -47.55912 m -590.10180 -47.12712 589.45380 -46.76712 588.87780 -46.40712 C -588.87780 -37.62312 595.42980 -24.01512 599.38980 -17.03112 C -599.53380 -16.81512 619.54980 13.92888 627.61380 15.36888 C -626.38980 4.42488 602.70180 -47.55912 590.74980 -47.55912 C -@c -F - -@rax %Note: Object --139.86539 -40.79112 -93.03420 6.00888 @E - 1 O 0 @g -0.00 0.00 0.00 0.00 k -/$fm 0 def --106.21020 -40.79112 m --121.61820 -36.83112 -128.67420 -23.43912 -137.38620 -11.77512 C --138.68220 -4.14312 -143.29020 0.39288 -135.51420 6.00888 C --126.94620 6.00888 -99.94620 -15.08712 -98.65020 -24.59112 C --93.82620 -27.83112 -94.40220 -31.28712 -93.03420 -37.40712 C --94.18620 -38.48712 -99.44220 -39.99912 -106.21020 -40.79112 C -@c -F - -@rax %Note: Object --81.37020 -33.23112 -44.62016 20.62488 @E - 1 O 0 @g -0.00 0.00 0.00 0.00 k -/$fm 0 def --77.62620 -33.23112 m --78.92220 -32.72712 -80.14620 -32.22312 -81.37020 -31.71912 C --83.60220 -18.25512 -57.89820 18.39288 -45.29820 20.62488 C --41.84220 -1.04712 -51.41820 -33.23112 -77.62620 -33.23112 C -@c -F - -@rax %Note: Object -629.12580 -17.39112 637.40580 -7.95912 @E - 1 O 0 @g -0.00 0.00 0.00 0.00 k -/$fm 0 def -630.63780 -17.39112 m -629.48580 -16.31112 629.48580 -16.31112 629.12580 -14.00712 C -632.94180 -12.56712 632.79780 -8.75112 637.40580 -7.95912 C -635.38980 -11.99112 633.80580 -14.22312 630.63780 -17.39112 C -@c -F - -@rax %Note: Object -506.97326 6.00888 539.26980 55.32888 @E - 1 O 0 @g -0.00 0.00 0.00 0.00 k -/$fm 0 def -509.53380 6.00888 m -498.73380 12.70488 521.55780 53.60088 533.65380 55.32888 C -534.73380 50.79288 536.31780 48.56088 539.26980 44.02488 C -537.68580 32.57688 522.99780 6.00888 509.53380 6.00888 C -@c -F - -@rax %Note: Object -543.60794 8.96088 557.79506 45.17688 @E - 1 O 0 @g -0.00 0.00 0.00 0.00 k -/$fm 0 def -547.18980 8.96088 m -543.80580 18.10488 540.78180 38.04888 547.90980 45.17688 C -549.49380 45.03288 551.14980 44.88888 552.80580 44.74488 C -552.87780 44.31288 553.02180 43.80888 553.16580 43.30488 C -554.17380 43.08888 555.18180 42.80088 556.18980 42.51288 C -560.22180 31.20888 556.26180 18.68088 549.42180 8.96088 C -548.62980 8.96088 547.90980 8.96088 547.18980 8.96088 C -@c -F - -@rax %Note: Object -188.91780 12.34488 223.83780 123.15288 @E - 1 O 0 @g -0.00 0.00 0.00 0.00 k -/$fm 0 def -191.50980 12.34488 m -189.42180 32.00088 188.91780 50.28888 188.91780 69.65688 C -193.81380 75.56088 193.81380 77.28888 194.89380 84.70488 C -201.37380 96.80088 206.41380 110.98488 212.96580 122.43288 C -213.82980 122.72088 214.69380 122.93688 215.62980 123.15288 C -216.42180 121.06488 216.49380 121.35288 219.37380 120.12888 C -218.22180 117.89688 218.22180 117.89688 218.22180 113.36088 C -220.09380 111.48888 220.38180 110.76888 223.83780 110.33688 C -214.26180 85.35288 203.24580 59.36088 198.63780 32.72088 C -195.39780 29.48088 195.68580 21.27288 195.68580 16.16088 C -195.03780 15.94488 194.38980 15.65688 193.81380 15.36888 C -193.52580 14.50488 193.23780 13.64088 193.02180 12.77688 C -192.51780 12.63288 192.01380 12.48888 191.50980 12.34488 C -@c -F - -@rax %Note: Object -699.67219 12.77688 716.67780 40.64088 @E - 1 O 0 @g -0.00 0.00 0.00 0.00 k -/$fm 0 def -700.54980 12.77688 m -697.38180 15.72888 703.35780 27.96888 704.65380 32.00088 C -708.39780 36.17688 709.83780 39.92088 715.59780 40.64088 C -715.95780 35.52888 716.31780 30.34488 716.67780 25.16088 C -711.78180 20.55288 706.95780 14.14488 700.54980 12.77688 C -@c -F - -@rax %Note: Object -724.59780 27.82488 753.30454 57.27969 @E - 1 O 0 @g -0.00 0.00 0.00 0.00 k -/$fm 0 def -728.34180 27.82488 m -727.04580 28.25688 725.82180 28.61688 724.59780 28.97688 C -727.18980 37.97688 727.98180 38.76888 728.34180 41.79288 C -727.69380 42.08088 727.04580 42.29688 726.46980 42.51288 C -726.46980 43.30488 726.46980 44.02488 726.46980 44.74488 C -736.69380 52.23288 737.55780 60.94488 753.18180 55.76088 C -755.55780 42.58488 740.72580 27.82488 728.34180 27.82488 C -@c -F - -@rax %Note: Object -678.83386 34.59288 711.42180 59.81074 @E - 1 O 0 @g -0.00 0.00 0.00 0.00 k -/$fm 0 def -680.24580 34.59288 m -674.98980 39.84888 685.14180 50.14488 687.73380 54.24888 C -699.68580 60.94488 699.03780 62.02488 711.42180 55.32888 C -711.42180 42.51288 689.67780 34.59288 680.24580 34.59288 C -@c -F - -@rax %Note: Object -719.07591 48.20088 727.83128 59.07288 @E - 1 O 0 @g -0.00 0.00 0.00 0.00 k -/$fm 0 def -719.34180 48.20088 m -718.90980 53.81688 718.90980 53.81688 720.06180 59.07288 C -727.62180 58.71288 730.28580 58.92888 725.31780 51.22488 C -720.85380 48.20088 720.85380 48.20088 719.34180 48.20088 C -@c -F - -@rax %Note: Object -557.36759 50.79288 593.94898 88.52088 @E - 1 O 0 @g -0.00 0.00 0.00 0.00 k -/$fm 0 def -558.42180 50.79288 m -555.54180 56.62488 559.35780 65.84088 560.29380 71.16888 C -570.22980 81.10488 576.06180 89.38488 591.54180 88.52088 C -601.98180 73.76088 578.36580 56.62488 565.98180 51.22488 C -563.46180 51.08088 560.94180 50.93688 558.42180 50.79288 C -@c -F - -@rax %Note: Object -540.92665 51.58488 551.95087 69.33402 @E - 1 O 0 @g -0.00 0.00 0.00 0.00 k -/$fm 0 def -541.86180 51.58488 m -540.78180 56.84088 540.78180 56.84088 541.14180 59.50488 C -541.71780 59.86488 542.36580 60.22488 543.01380 60.58488 C -543.22980 62.88888 543.44580 65.19288 543.73380 67.42488 C -555.61380 76.28088 554.38980 52.73688 541.86180 51.58488 C -@c -F - -@rax %Note: Object -488.86980 54.24888 525.73380 75.31569 @E - 1 O 0 @g -0.00 0.00 0.00 0.00 k -/$fm 0 def -496.78980 54.24888 m -492.75780 56.12088 490.81380 56.98488 488.86980 59.86488 C -488.86980 72.96888 502.11780 78.15288 513.34980 73.83288 C -517.16580 68.93688 519.25380 65.33688 525.73380 62.88888 C -524.94180 58.71288 501.68580 54.24888 496.78980 54.24888 C -@c -F - -@rax %Note: Object --148.92803 55.76088 -131.77020 92.92195 @E - 1 O 0 @g -0.00 0.00 0.00 0.00 k -/$fm 0 def --145.30620 55.76088 m --145.81020 55.90488 -146.31420 56.04888 -146.81820 56.12088 C --150.41820 68.28888 -149.05020 78.72888 -145.66620 90.03288 C --141.63420 92.55288 -138.03420 95.72088 -137.02620 88.52088 C --136.52220 88.52088 -136.01820 88.52088 -135.51420 88.52088 C --130.25820 81.03288 -129.53820 60.58488 -139.25820 55.76088 C --141.27420 55.76088 -143.29020 55.76088 -145.30620 55.76088 C -@c -F - -@rax %Note: Object --184.04220 63.24888 -154.66620 105.08088 @E - 1 O 0 @g -0.00 0.00 0.00 0.00 k -/$fm 0 def --182.89020 63.24888 m --183.32220 63.89688 -183.68220 64.54488 -184.04220 65.12088 C --184.04220 83.04888 -181.16220 106.30488 -158.48220 105.08088 C --157.69020 100.54488 -156.17820 98.31288 -154.66620 90.03288 C --160.06620 82.11288 -173.89020 63.24888 -182.89020 63.24888 C -@c -F - -@rax %Note: Object -726.10980 64.04088 761.46180 97.16088 @E - 1 O 0 @g -0.00 0.00 0.00 0.00 k -/$fm 0 def -731.36580 64.04088 m -729.85380 65.55288 729.85380 65.55288 726.10980 66.63288 C -734.53380 86.00088 738.70980 95.00088 761.46180 97.16088 C -762.39780 85.92888 740.36580 64.04088 731.36580 64.04088 C -@c -F - -@rax %Note: Object -701.93367 66.63288 726.32353 97.52088 @E - 1 O 0 @g -0.00 0.00 0.00 0.00 k -/$fm 0 def -714.80580 66.63288 m -714.80580 67.13688 714.80580 67.64088 714.80580 68.14488 C -711.99780 68.07288 709.26180 67.92888 706.52580 67.78488 C -694.86180 84.92088 706.81380 98.45688 724.95780 97.52088 C -728.12580 89.81688 725.74980 83.26488 719.70180 77.21688 C -718.98180 73.68888 718.62180 68.28888 714.80580 66.63288 C -@c -F - -@rax %Note: Object -514.93266 70.01688 535.14000 103.06035 @E - 1 O 0 @g -0.00 0.00 0.00 0.00 k -/$fm 0 def -533.65380 70.01688 m -516.73380 80.31288 510.75780 83.19288 517.81380 102.05688 C -521.55780 103.20888 521.55780 103.20888 530.19780 102.84888 C -536.10180 92.40888 535.38180 82.97688 534.73380 70.37688 C -534.37380 70.30488 534.01380 70.16088 533.65380 70.01688 C -@c -F - -@rax %Note: Object -651.61899 74.55288 671.53380 110.33688 @E - 1 O 0 @g -0.00 0.00 0.00 0.00 k -/$fm 0 def -654.68580 74.55288 m -653.67780 74.69688 652.66980 74.84088 651.66180 74.91288 C -651.51780 89.88888 651.30180 97.80888 658.42980 110.33688 C -659.65380 110.33688 660.87780 110.33688 662.17380 110.33688 C -662.46180 105.51288 666.63780 104.36088 671.53380 102.05688 C -670.45380 89.31288 664.69380 84.77688 656.19780 74.55288 C -655.69380 74.55288 655.18980 74.55288 654.68580 74.55288 C -@c -F - -@rax %Note: Object -544.45153 76.85688 568.13386 119.04888 @E - 1 O 0 @g -0.00 0.00 0.00 0.00 k -/$fm 0 def -550.14180 76.85688 m -549.63780 77.36088 549.13380 77.86488 548.62980 78.36888 C -547.33380 78.22488 546.10980 78.08088 544.88580 77.93688 C -543.08580 96.22488 546.10980 107.31288 561.44580 119.04888 C -562.45380 119.04888 563.46180 119.04888 564.46980 119.04888 C -574.54980 108.96888 561.87780 76.85688 550.14180 76.85688 C -@c -F - -@rax %Note: Object --133.33408 89.67288 -102.20400 126.60888 @E - 1 O 0 @g -0.00 0.00 0.00 0.00 k -/$fm 0 def --119.74620 89.67288 m --134.07420 91.25688 -135.15420 102.99288 -131.77020 115.30488 C --122.12220 122.14488 -117.80220 126.60888 -105.41820 126.60888 C --97.57020 116.16888 -105.56220 101.26488 -110.31420 90.75288 C --113.69820 89.67288 -113.69820 89.67288 -119.74620 89.67288 C -@c -F - -@rax %Note: Object -580.23780 90.75288 584.77380 93.05688 @E - 1 O 0 @g -0.00 0.00 0.00 0.00 k -/$fm 0 def -580.23780 90.75288 m -580.23780 91.54488 580.23780 92.33688 580.23780 93.05688 C -581.74980 92.98488 583.26180 92.84088 584.77380 92.69688 C -583.26180 92.04888 581.74980 91.40088 580.23780 90.75288 C -@c -F - -@rax %Note: Object -619.22239 92.69688 651.30180 125.09688 @E - 1 O 0 @g -0.00 0.00 0.00 0.00 k -/$fm 0 def -620.84580 92.69688 m -614.72580 102.56088 626.24580 119.33688 633.58980 125.09688 C -638.84580 125.09688 639.63780 124.30488 650.94180 123.94488 C -650.50980 117.17688 651.30180 115.66488 651.30180 113.36088 C -642.87780 105.65688 632.00580 94.06488 620.84580 92.69688 C -@c -F - -@rax %Note: Object --152.07420 97.95288 -138.89820 120.81515 @E - 1 O 0 @g -0.00 0.00 0.00 0.00 k -/$fm 0 def --148.25820 97.95288 m --149.05020 98.31288 -149.84220 98.67288 -150.56220 99.03288 C --150.56220 99.53688 -150.56220 100.04088 -150.56220 100.54488 C --151.06620 100.54488 -151.57020 100.54488 -152.07420 100.54488 C --152.00220 106.01688 -150.41820 129.41688 -138.89820 117.89688 C --138.89820 111.84888 -139.97820 97.95288 -148.25820 97.95288 C -@c -F - -@rax %Note: Object -668.58180 110.76888 708.03780 145.07943 @E - 1 O 0 @g -0.00 0.00 0.00 0.00 k -/$fm 0 def -672.68580 110.76888 m -671.31780 111.63288 669.94980 112.49688 668.58180 113.36088 C -668.58180 118.54488 672.10980 120.41688 675.34980 123.94488 C -678.15780 136.40088 691.04580 146.55288 704.29380 145.04088 C -705.08580 142.66488 704.86980 142.16088 708.03780 140.50488 C -704.50980 120.41688 691.98180 113.93688 672.68580 110.76888 C -@c -F - -@rax %Note: Object --183.02854 116.81688 -152.93820 161.29474 @E - 1 O 0 @g -0.00 0.00 0.00 0.00 k -/$fm 0 def --167.12220 116.81688 m --180.44220 127.32888 -186.49020 138.92088 -181.01820 154.83288 C --174.03420 163.47288 -170.21820 162.17688 -160.35420 158.57688 C --160.35420 158.14488 -160.35420 157.64088 -160.35420 157.13688 C --148.83420 149.00088 -153.73020 135.89688 -155.45820 123.15288 C --159.77820 119.69688 -161.72220 116.81688 -167.12220 116.81688 C -@c -F - -@rax %Note: Object -659.14980 119.04888 668.26687 128.48088 @E - 1 O 0 @g -0.00 0.00 0.00 0.00 k -/$fm 0 def -659.14980 119.04888 m -658.64580 125.24088 662.82180 127.54488 668.22180 128.48088 C -668.43780 124.23288 667.93380 123.72888 665.55780 119.40888 C -663.39780 119.33688 661.23780 119.19288 659.14980 119.04888 C -@c -F - -@rax %Note: Object --144.71291 122.79288 -122.98989 162.75288 @E - 1 O 0 @g -0.00 0.00 0.00 0.00 k -/$fm 0 def --134.72220 122.79288 m --138.10620 125.60088 -140.62620 127.25688 -144.51420 126.17688 C --144.15420 144.39288 -147.97020 150.65688 -134.00220 162.75288 C --114.05820 162.75288 -126.08220 134.38488 -130.97820 123.58488 C --132.49020 122.79288 -132.49020 122.79288 -134.72220 122.79288 C -@c -F - -@rax %Note: Object -665.14394 133.73688 696.37380 175.20888 @E - 1 O 0 @g -0.00 0.00 0.00 0.00 k -/$fm 0 def -665.91780 133.73688 m -665.48580 143.16888 662.89380 150.65688 670.09380 157.85688 C -671.31780 163.11288 683.91780 172.11288 688.52580 174.84888 C -690.97380 174.99288 693.49380 175.13688 696.01380 175.20888 C -696.08580 172.47288 696.22980 169.73688 696.37380 166.92888 C -692.62980 159.36888 674.98980 135.96888 665.91780 133.73688 C -@c -F - -@rax %Note: Object -636.69175 135.60888 655.88287 167.28888 @E - 1 O 0 @g -0.00 0.00 0.00 0.00 k -/$fm 0 def -638.84580 135.60888 m -634.02180 140.43288 638.41380 156.77688 640.35780 162.39288 C -644.96580 164.04888 645.25380 167.28888 652.38180 167.28888 C -656.91780 162.75288 657.06180 142.59288 652.74180 135.96888 C -648.06180 135.89688 643.45380 135.75288 638.84580 135.60888 C -@c -F - -@rax %Note: Object -694.14180 147.70488 699.03780 151.80888 @E - 1 O 0 @g -0.00 0.00 0.00 0.00 k -/$fm 0 def -694.50180 147.70488 m -694.35780 148.85688 694.21380 150.00888 694.14180 151.08888 C -695.22180 151.37688 696.37380 151.59288 697.52580 151.80888 C -697.52580 151.30488 697.52580 150.80088 697.52580 150.29688 C -698.02980 150.29688 698.53380 150.29688 699.03780 150.29688 C -698.74980 149.72088 698.46180 149.07288 698.24580 148.42488 C -696.94980 148.20888 695.72580 147.99288 694.50180 147.70488 C -@c -F - -@rax %Note: Object -363.73380 173.69688 594.13380 799.37688 @E - 1 O 0 @g -0.00 0.00 0.00 0.00 k -/$fm 0 def -363.73380 173.69688 m -364.81380 180.46488 370.86180 203.07288 376.83780 231.00888 C -377.62980 231.72888 380.58180 242.31288 381.37380 243.03288 C -388.42980 277.59288 394.98180 310.78488 397.93380 345.56088 C -397.93380 357.65688 397.14180 359.09688 397.14180 367.44888 C -391.16580 413.38488 391.16580 413.38488 390.37380 414.17688 C -387.27780 428.14488 383.38980 442.18488 381.73380 455.64888 C -381.22980 455.64888 380.72580 455.64888 380.22180 455.64888 C -379.06980 462.41688 376.11780 469.18488 372.73380 482.79288 C -372.22980 482.79288 371.72580 482.79288 371.22180 482.79288 C -369.78180 488.33688 368.41380 493.88088 367.04580 499.35288 C -365.74980 522.24888 371.22180 547.44888 378.70980 568.32888 C -379.28580 568.25688 379.93380 568.11288 380.58180 567.96888 C -382.52580 555.94488 384.25380 532.25688 398.29380 526.85688 C -416.29380 526.85688 428.74980 537.72888 443.36580 547.95288 C -444.37380 548.09688 445.38180 548.24088 446.38980 548.31288 C -446.74980 547.59288 447.10980 546.87288 447.54180 546.08088 C -448.18980 527.43288 444.30180 464.28888 473.46180 464.28888 C -484.40580 472.35288 489.80580 482.21688 499.02180 491.43288 C -499.88580 491.36088 500.74980 491.21688 501.68580 491.07288 C -501.68580 479.26488 501.68580 467.45688 501.68580 455.64888 C -503.62980 446.21688 505.14180 430.52088 514.78980 424.32888 C -533.72580 424.32888 535.09380 444.48888 550.14180 446.57688 C -552.80580 426.20088 552.80580 426.20088 553.52580 412.66488 C -558.06180 400.20888 568.28580 389.19288 582.10980 399.84888 C -582.10980 405.60888 575.12580 403.44888 571.23780 403.59288 C -570.80580 404.38488 570.44580 405.17688 570.08580 405.89688 C -567.70980 406.18488 565.33380 406.40088 562.95780 406.61688 C -558.42180 421.08888 558.13380 435.20088 556.54980 449.60088 C -553.88580 452.98488 552.44580 453.34488 552.44580 454.85688 C -541.35780 452.62488 536.46180 446.28888 529.47780 438.29688 C -525.51780 435.27288 521.77380 431.16888 517.09380 431.16888 C -501.97380 449.96088 511.54980 482.93688 506.58180 504.60888 C -489.51780 503.96088 486.27780 471.84888 466.69380 471.84888 C -457.62180 480.92088 455.89380 512.24088 455.02980 523.47288 C -455.02980 533.26488 455.24580 549.03288 451.64580 557.38488 C -450.13380 557.52888 448.62180 557.67288 447.18180 557.74488 C -433.71780 551.33688 410.60580 528.22488 396.78180 535.13688 C -386.98980 553.20888 386.62980 568.25688 386.62980 588.27288 C -398.36580 638.45688 425.07780 681.44088 449.77380 726.22488 C -452.00580 732.27288 455.02980 736.80888 467.05380 762.44088 C -471.44580 774.75288 475.83780 787.06488 480.22980 799.37688 C -480.94980 799.37688 481.66980 799.37688 482.46180 799.37688 C -491.10180 775.97688 491.10180 775.97688 495.63780 766.18488 C -496.78980 765.10488 496.78980 765.10488 500.17380 765.46488 C -506.50980 775.54488 514.35780 784.90488 519.32580 795.63288 C -520.33380 795.63288 521.34180 795.63288 522.34980 795.63288 C -522.27780 785.91288 517.59780 728.88888 536.60580 728.88888 C -539.62980 731.91288 539.62980 731.91288 549.42180 748.47288 C -550.28580 748.40088 551.14980 748.25688 552.01380 748.11288 C -552.80580 746.60088 552.80580 746.60088 554.24580 728.52888 C -562.09380 698.00088 570.66180 668.62488 576.06180 636.51288 C -588.37380 596.69688 589.59780 550.11288 594.13380 508.35288 C -593.34180 506.12088 593.34180 506.12088 594.13380 462.41688 C -589.16580 405.24888 584.19780 356.21688 557.26980 304.88088 C -549.56580 283.92888 537.10980 267.87288 527.24580 247.56888 C -526.30980 247.06488 525.44580 246.56088 524.58180 246.05688 C -523.93380 236.48088 501.54180 217.68888 494.55780 206.52888 C -493.54980 206.16888 492.54180 205.80888 491.53380 205.37688 C -491.24580 204.29688 490.95780 203.14488 490.74180 201.99288 C -489.22980 201.20088 489.22980 201.20088 485.48580 201.56088 C -473.38980 217.90488 461.94180 215.45688 443.36580 214.01688 C -434.29380 211.42488 427.88580 209.69688 420.10980 205.01688 C -396.27780 205.01688 384.46980 179.60088 363.73380 173.69688 C -@c -F - -@rax %Note: Object --27.22620 483.15288 195.36180 602.63660 @E - 1 O 0 @g -0.00 0.00 0.00 1.00 k -/$fm 0 def -41.17380 483.15288 m -28.42980 484.80888 15.75780 488.33688 5.46180 494.81688 C -5.17380 497.26488 -3.32220 500.57688 -5.41020 502.01688 C --8.14620 507.20088 -12.17820 508.56888 -13.69020 512.88888 C --14.69820 513.32088 -15.70620 513.68088 -16.71420 514.04088 C --18.51420 521.31288 -28.16220 522.68088 -27.22620 533.62488 C --24.27420 535.92888 -24.27420 535.92888 -22.76220 535.92888 C --19.09020 532.25688 -16.64220 520.80888 -9.94620 518.93688 C --7.42620 513.68088 5.60580 506.04888 6.61380 502.37688 C -11.86980 499.71288 11.86980 499.71288 22.38180 496.32888 C -22.38180 495.82488 22.38180 495.32088 22.38180 494.81688 C -42.68580 491.43288 42.68580 491.43288 49.45380 491.43288 C -50.96580 492.22488 69.75780 494.45688 93.80580 498.99288 C -97.04580 501.29688 100.28580 503.52888 103.59780 505.76088 C -113.24580 507.41688 132.90180 522.96888 137.79780 530.24088 C -149.24580 539.38488 155.79780 556.44888 160.33380 569.48088 C -160.18980 575.24088 160.04580 581.00088 159.97380 586.76088 C -160.54980 587.04888 161.19780 587.33688 161.84580 587.55288 C -161.91780 588.56088 162.06180 589.56888 162.20580 590.57688 C -161.41380 590.57688 160.69380 590.57688 159.97380 590.57688 C -158.31780 595.32888 157.09380 597.41688 158.02980 601.88088 C -159.18180 602.02488 160.33380 602.16888 161.48580 602.24088 C -171.63780 596.26488 182.58180 604.47288 194.53380 602.24088 C -195.61380 599.57688 195.54180 598.56888 194.89380 595.11288 C -185.24580 594.03288 175.38180 593.67288 166.74180 590.57688 C -167.17380 560.98488 161.62980 547.16088 141.54180 523.47288 C -141.39780 522.75288 141.25380 521.96088 141.18180 521.16888 C -157.23780 523.47288 169.69380 532.11288 180.99780 543.48888 C -183.01380 543.63288 185.02980 543.77688 187.04580 543.84888 C -187.04580 543.34488 187.04580 542.84088 187.04580 542.33688 C -187.54980 542.33688 188.05380 542.33688 188.55780 542.33688 C -186.97380 530.81688 155.58180 517.78488 146.43780 513.32088 C -143.98980 513.46488 141.61380 513.60888 139.23780 513.68088 C -138.87780 515.48088 138.51780 517.20888 138.15780 518.93688 C -130.59780 516.70488 129.37380 509.86488 121.59780 507.27288 C -98.70180 490.35288 68.89380 483.15288 41.17380 483.15288 C -@c -F - -@rax %Note: Object --72.73020 540.46488 248.32205 910.38784 @E - 1 O 0 @g -0.00 0.00 0.00 1.00 k -/$fm 0 def --45.29820 540.46488 m --47.60220 540.96888 -49.83420 541.47288 -52.06620 541.97688 C --66.61020 549.10488 -69.77820 554.50488 -72.73020 569.48088 C --69.85020 596.98488 -42.77820 613.40088 -26.14620 585.24888 C --25.06620 582.00888 -23.91420 578.76888 -22.76220 575.45688 C --8.65020 576.46488 6.90180 589.35288 18.63780 596.98488 C -30.87780 608.64888 58.81380 634.71288 58.81380 653.14488 C -56.50980 655.95288 54.27780 658.68888 52.04580 661.42488 C -50.24580 677.76888 50.96580 688.49688 54.27780 703.61688 C -57.01380 707.72088 57.94980 708.87288 56.22180 712.68888 C -55.71780 712.68888 55.21380 712.68888 54.70980 712.68888 C -53.34180 720.75288 49.66980 727.52088 55.42980 733.42488 C -56.94180 733.06488 58.45380 732.70488 59.96580 732.27288 C -61.62180 739.47288 62.12580 740.26488 67.81380 744.36888 C -66.66180 752.07288 62.62980 751.85688 58.45380 756.03288 C -54.78180 761.14488 53.12580 763.73688 48.66180 766.61688 C -33.32580 785.33688 13.45380 808.37688 5.46180 830.26488 C -3.94980 838.54488 3.15780 850.64088 0.20580 866.48088 C -1.28580 881.31288 6.03780 894.99288 7.69380 908.67288 C -11.29380 912.27288 14.38980 909.39288 18.63780 907.59288 C -29.94180 905.79288 40.16580 903.63288 51.68580 900.03288 C -52.69380 897.00888 74.43780 889.37688 79.54980 888.29688 C -79.54980 887.79288 79.54980 887.28888 79.54980 886.78488 C -80.26980 886.78488 80.98980 886.78488 81.78180 886.78488 C -81.78180 886.28088 81.78180 885.77688 81.78180 885.27288 C -84.66180 884.19288 90.70980 879.08088 90.06180 874.40088 C -84.01380 875.40888 84.08580 878.79288 80.26980 881.16888 C -68.74980 882.60888 58.74180 890.02488 47.94180 892.83288 C -47.94180 893.33688 47.94180 893.84088 47.94180 894.34488 C -36.27780 897.15288 25.04580 900.46488 13.74180 899.60088 C -12.22980 894.34488 11.43780 893.62488 11.07780 891.32088 C -17.70180 889.08888 31.38180 881.74488 33.68580 873.96888 C -34.54980 873.75288 35.41380 873.53688 36.27780 873.24888 C -36.34980 871.95288 43.47780 862.30488 46.06980 861.15288 C -46.14180 860.43288 46.28580 859.71288 46.42980 858.92088 C -47.00580 858.70488 47.65380 858.48888 48.30180 858.20088 C -62.19780 838.76088 69.10980 816.51288 78.03780 793.76088 C -78.54180 793.61688 79.04580 793.47288 79.54980 793.32888 C -83.00580 809.38488 85.45380 817.01688 93.37380 831.05688 C -100.93380 840.05688 100.93380 840.05688 101.29380 841.56888 C -87.39780 840.92088 80.62980 835.59288 80.62980 850.64088 C -92.65380 864.96888 92.65380 864.96888 98.70180 870.94488 C -113.60580 881.96088 125.12580 885.63288 143.41380 889.08888 C -143.41380 889.88088 143.41380 890.60088 143.41380 891.32088 C -142.62180 891.32088 141.90180 891.32088 141.18180 891.32088 C -141.18180 891.82488 141.18180 892.32888 141.18180 892.83288 C -140.53380 893.12088 139.88580 893.40888 139.23780 893.62488 C -139.45380 894.92088 139.74180 896.14488 140.02980 897.36888 C -166.59780 907.08888 193.88580 913.92888 222.39780 905.28888 C -229.09380 899.24088 229.09380 899.24088 230.60580 898.88088 C -231.82980 895.92888 231.68580 894.34488 235.14180 892.47288 C -235.50180 890.60088 235.86180 888.72888 236.22180 886.78488 C -236.86980 886.56888 237.51780 886.35288 238.16580 886.06488 C -238.02180 884.55288 237.87780 883.04088 237.73380 881.52888 C -247.59780 848.48088 253.78980 819.03288 241.54980 784.25688 C -234.56580 771.51288 237.44580 776.26488 226.86180 777.48888 C -225.56580 769.06488 213.10980 758.26488 206.55780 753.00888 C -193.38180 753.00888 195.25380 765.24888 194.53380 774.46488 C -182.36580 773.60088 175.66980 746.67288 159.97380 743.21688 C -157.95780 744.72888 155.94180 746.24088 153.92580 747.75288 C -152.70180 757.76088 153.99780 764.96088 155.43780 774.46488 C -142.26180 769.78488 132.03780 733.78488 115.62180 744.72888 C -108.13380 761.00088 114.46980 783.39288 119.36580 799.37688 C -105.97380 797.21688 99.27780 777.84888 83.29380 776.76888 C -82.93380 773.52888 82.42980 770.43288 78.03780 769.64088 C -75.08580 772.44888 76.09380 773.38488 72.78180 774.46488 C -73.14180 769.20888 78.32580 766.25688 79.90980 760.13688 C -79.40580 759.41688 78.90180 758.69688 78.39780 757.90488 C -76.95780 757.68888 75.58980 757.47288 74.22180 757.18488 C -74.43780 754.95288 74.72580 752.64888 75.01380 750.34488 C -76.23780 750.12888 77.46180 749.91288 78.75780 749.62488 C -80.12580 745.95288 84.22980 748.25688 83.29380 742.49688 C -81.78180 741.70488 81.78180 741.70488 69.75780 742.85688 C -69.82980 741.34488 69.97380 739.83288 70.11780 738.32088 C -65.07780 733.28088 68.24580 727.37688 69.75780 720.96888 C -74.94180 720.53688 79.47780 721.90488 85.52580 720.60888 C -88.62180 733.92888 91.42980 749.84088 92.29380 763.16088 C -92.79780 763.16088 93.30180 763.16088 93.80580 763.16088 C -93.80580 760.71288 93.80580 758.19288 93.80580 755.67288 C -94.52580 755.31288 95.24580 754.95288 96.03780 754.52088 C -96.10980 748.40088 96.25380 742.20888 96.39780 736.01688 C -90.85380 716.64888 92.72580 698.21688 88.90980 677.98488 C -86.24580 671.28888 76.81380 653.28888 69.75780 651.63288 C -67.16580 642.99288 66.51780 646.52088 61.47780 641.48088 C -51.46980 613.97688 24.75780 593.45688 1.35780 577.40088 C --6.13020 573.58488 -12.89820 567.75288 -21.25020 567.17688 C --24.85020 552.56088 -28.52220 540.46488 -45.29820 540.46488 C -@c -F - -@rax %Note: Object --53.21820 559.25688 -38.27197 582.29688 @E - 1 O 0 @g -0.00 0.00 0.00 0.00 k -/$fm 0 def --46.01820 559.25688 m --49.83420 562.28088 -49.83420 562.28088 -51.27420 562.64088 C --51.27420 563.64888 -51.27420 564.65688 -51.27420 565.66488 C --51.92220 565.95288 -52.57020 566.24088 -53.21820 566.45688 C --53.21820 576.96888 -53.21820 576.96888 -52.06620 579.63288 C --50.19420 580.78488 -50.19420 580.78488 -49.83420 582.29688 C --36.73020 581.86488 -35.14620 568.97688 -43.06620 559.25688 C --44.07420 559.25688 -45.08220 559.25688 -46.01820 559.25688 C -@c -F - -@rax %Note: Object -147.81487 656.52888 195.99846 740.98488 @E - 1 O 0 @g -0.00 0.00 0.00 1.00 k -/$fm 0 def -156.94980 656.52888 m -150.18180 661.06488 150.18180 661.06488 149.02980 662.93688 C -147.08580 679.35288 146.14980 700.08888 156.58980 713.40888 C -156.66180 714.92088 156.80580 716.43288 156.94980 717.94488 C -160.26180 718.59288 161.91780 718.80888 164.43780 721.32888 C -179.77380 725.14488 180.20580 727.73688 187.76580 740.98488 C -189.34980 740.84088 191.00580 740.69688 192.66180 740.55288 C -192.66180 732.27288 182.86980 724.28088 184.02180 719.09688 C -184.52580 719.09688 185.02980 719.09688 185.53380 719.09688 C -188.12580 722.55288 191.07780 726.72888 195.32580 727.37688 C -198.42180 721.97688 190.50180 712.47288 185.89380 708.87288 C -185.89380 699.36888 184.45380 671.50488 172.71780 668.19288 C -171.42180 662.86488 161.98980 657.75288 156.94980 656.52888 C -@c -F - -@rax %Note: Object -56.77965 660.27288 85.64995 708.51288 @E - 1 O 0 @g -0.00 0.00 0.00 0.00 k -/$fm 0 def -61.47780 660.27288 m -52.33380 671.64888 56.79780 704.91288 71.98980 708.51288 C -85.16580 708.51288 86.10180 707.14488 85.52580 692.74488 C -85.02180 692.74488 84.51780 692.74488 84.01380 692.74488 C -80.70180 697.71288 78.25380 697.13688 73.50180 698.72088 C -55.71780 692.38488 60.54180 675.96888 62.98980 661.06488 C -62.48580 660.84888 61.98180 660.56088 61.47780 660.27288 C -@c -F - -@rax %Note: Object -153.92580 682.52088 180.26476 710.53682 @E - 1 O 0 @g -0.00 0.00 0.00 0.00 k -/$fm 0 def -153.92580 682.52088 m -155.79780 698.57688 159.03780 718.66488 178.76580 707.43288 C -179.55780 702.89688 181.50180 699.36888 179.12580 694.61688 C -175.02180 700.44888 173.65380 700.23288 167.46180 700.23288 C -163.86180 696.27288 157.30980 684.03288 153.92580 682.52088 C -@c -F - -@rax %Note: Object -86.24580 748.83288 242.26980 902.10132 @E - 1 O 0 @g -0.00 0.00 0.00 0.00 k -/$fm 0 def -120.08580 748.83288 m -114.46980 772.44888 126.13380 790.95288 129.51780 812.19288 C -122.10180 819.60888 98.62980 786.20088 86.24580 785.04888 C -86.60580 791.60088 86.96580 798.15288 87.39780 804.63288 C -95.67780 826.16088 102.30180 835.16088 120.87780 847.97688 C -122.24580 850.20888 123.61380 851.72088 123.10980 854.02488 C -120.08580 854.81688 120.08580 854.81688 115.62180 854.02488 C -106.40580 849.41688 96.82980 847.32888 87.03780 846.82488 C -92.65380 870.44088 128.00580 880.59288 148.66980 883.40088 C -157.66980 883.68888 166.66980 883.97688 175.74180 884.19288 C -177.97380 886.42488 178.69380 886.92888 179.12580 889.80888 C -175.52580 893.40888 159.32580 892.68888 153.20580 893.62488 C -153.20580 894.41688 153.20580 895.13688 153.20580 895.85688 C -175.88580 901.47288 197.26980 905.21688 220.88580 898.52088 C -227.22180 892.83288 227.22180 892.83288 230.96580 887.57688 C -230.96580 878.28888 233.98980 863.02488 238.88580 854.02488 C -239.38980 853.95288 239.89380 853.80888 240.39780 853.66488 C -238.88580 843.72888 241.83780 828.96888 242.26980 817.44888 C -241.54980 809.16888 241.54980 809.16888 235.86180 785.40888 C -234.85380 785.33688 233.84580 785.19288 232.90980 785.04888 C -232.47780 791.81688 232.62180 791.96088 227.65380 793.32888 C -224.12580 784.40088 213.90180 759.27288 202.09380 760.92888 C -201.80580 770.72088 201.51780 780.51288 201.30180 790.30488 C -199.50180 790.30488 197.77380 790.30488 196.04580 790.30488 C -190.57380 781.66488 169.62180 752.14488 159.97380 751.13688 C -159.46980 764.45688 161.77380 774.24888 165.58980 786.56088 C -165.44580 789.00888 165.30180 791.38488 165.22980 793.76088 C -149.89380 793.76088 138.58980 751.42488 120.08580 748.83288 C -@c -F - -@rax %Note: Object -252.06180 759.77688 393.39780 945.96888 @E - 1 O 0 @g -0.00 0.00 0.00 1.00 k -/$fm 0 def -332.12580 759.77688 m -331.33380 759.92088 330.61380 760.06488 329.89380 760.13688 C -329.60580 762.65688 329.31780 765.17688 329.10180 767.69688 C -333.42180 768.34488 338.02980 768.27288 338.89380 773.74488 C -350.91780 778.85688 365.10180 789.36888 373.81380 799.37688 C -395.05380 838.90488 384.97380 892.04088 376.47780 933.51288 C -369.42180 930.99288 368.70180 928.83288 367.04580 922.20888 C -344.43780 914.79288 332.77380 895.06488 319.74180 876.27288 C -308.58180 858.20088 288.92580 830.12088 292.30980 806.93688 C -303.61380 812.55288 310.09380 821.84088 320.10180 829.90488 C -322.33380 830.04888 324.56580 830.19288 326.86980 830.26488 C -326.50980 823.06488 319.45380 813.56088 315.92580 806.93688 C -315.27780 802.18488 314.62980 797.43288 314.05380 792.60888 C -322.40580 793.04088 323.26980 798.29688 334.35780 795.63288 C -336.73380 790.52088 337.30980 778.20888 333.99780 772.95288 C -333.13380 772.88088 332.26980 772.73688 331.40580 772.59288 C -328.23780 775.76088 329.89380 785.55288 329.89380 789.58488 C -327.37380 789.22488 324.85380 788.86488 322.33380 788.43288 C -300.15780 777.12888 307.21380 792.75288 310.30980 809.88888 C -301.52580 809.31288 299.22180 798.22488 289.28580 798.22488 C -280.71780 811.32888 286.90980 824.07288 291.15780 837.82488 C -303.18180 861.15288 303.18180 861.15288 315.20580 880.80888 C -320.24580 887.14488 330.18180 898.37688 332.91780 905.64888 C -307.64580 899.67288 288.27780 875.33688 265.23780 864.17688 C -262.64580 859.13688 259.54980 855.53688 253.93380 855.53688 C -251.70180 857.76888 252.27780 858.70488 252.06180 861.15288 C -257.24580 863.52888 258.46980 864.75288 261.78180 869.50488 C -288.27780 887.79288 315.70980 905.07288 344.14980 920.40888 C -355.52580 927.89688 369.56580 934.80888 377.98980 945.96888 C -385.83780 945.96888 383.31780 942.00888 383.60580 933.51288 C -386.05380 923.21688 390.44580 908.24088 389.29380 897.36888 C -389.79780 897.36888 390.30180 897.36888 390.80580 897.36888 C -392.60580 872.45688 393.39780 870.94488 393.39780 855.89688 C -390.44580 849.92088 390.73380 829.47288 390.37380 825.00888 C -384.75780 798.51288 374.89380 788.43288 351.27780 772.95288 C -347.60580 766.25688 339.68580 759.77688 332.12580 759.77688 C -@c -F - -@rax %Note: Object -6.60359 765.82488 72.78180 884.91288 @E - 1 O 0 @g -0.00 0.00 0.00 0.00 k -/$fm 0 def -62.19780 765.82488 m -57.30180 769.56888 50.67780 774.96888 47.94180 779.79288 C -47.43780 779.79288 46.93380 779.79288 46.42980 779.79288 C -34.33380 794.33688 21.66180 812.19288 13.74180 828.03288 C -9.63780 842.14488 2.36580 872.38488 9.56580 884.91288 C -18.56580 883.32888 33.54180 869.57688 37.42980 860.43288 C -37.93380 860.43288 38.43780 860.43288 38.94180 860.43288 C -44.19780 852.51288 44.19780 852.51288 46.06980 851.36088 C -53.05380 838.90488 74.94180 795.41688 72.78180 785.40888 C -70.47780 785.40888 68.96580 786.20088 64.50180 786.20088 C -63.34980 785.04888 63.34980 785.04888 62.98980 781.30488 C -63.56580 781.08888 64.21380 780.80088 64.86180 780.51288 C -64.86180 779.00088 64.06980 778.28088 63.70980 772.95288 C -66.51780 771.65688 67.23780 771.00888 66.73380 767.33688 C -64.50180 765.82488 64.50180 765.82488 62.19780 765.82488 C -@c -F - -%%PageTrailer -@rs -@rs -%%Trailer -@EndSysCorelDict -end -%%DocumentSuppliedResources: procset wCorel8Dict -%%EOF - -%%EndDocument - diff --git a/seminar/static/seminar/lisak.pdf b/seminar/static/seminar/lisak.pdf new file mode 100644 index 00000000..f90b2784 Binary files /dev/null and b/seminar/static/seminar/lisak.pdf differ diff --git a/seminar/static/seminar/prihlaska.js b/seminar/static/seminar/prihlaska.js new file mode 100644 index 00000000..81f91d28 --- /dev/null +++ b/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(); +}); diff --git a/seminar/templates/seminar/archiv/obalky.tex b/seminar/templates/seminar/archiv/obalky.tex index ea20f63d..5f2d8e07 100644 --- a/seminar/templates/seminar/archiv/obalky.tex +++ b/seminar/templates/seminar/archiv/obalky.tex @@ -86,7 +86,8 @@ % Tohle makro vysází samotnou obálku \def\obalka#1#2#3#4#5#6#7{ % Horní a pravý okraj je zároveň okraj stránky, resetujeme odsazení -\includegraphics[height=2.55cm]{lisak.eps}\hskip 1 em\vbox{% +\includegraphics[height=2.55cm]{lisak.pdf} +\vbox{% \adresaMaM} \vskip 7.3 cm % Od oka \hskip\toskip% diff --git a/seminar/templates/seminar/edit.html b/seminar/templates/seminar/edit.html new file mode 100644 index 00000000..3f3e0d99 --- /dev/null +++ b/seminar/templates/seminar/edit.html @@ -0,0 +1,78 @@ +{% extends "seminar/zadani/base.html" %} +{% load staticfiles %} + +{% block script %} + + {{form.media}} + +{% endblock %} +{% block content %} +

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

    +
    + {% csrf_token %} + {{form.non_field_errors}} +
      +
    • + Přihlašovací údaje +
    • + {% include "seminar/prihlaska_field.html" with field=form.username %} +
    • + Osobní údaje +
    • + {% include "seminar/prihlaska_field.html" with field=form.jmeno %} +
    • + {% include "seminar/prihlaska_field.html" with field=form.prijmeni %} +
    • + {% include "seminar/prihlaska_field.html" with field=form.pohlavi_muz%} +
    • + {% include "seminar/prihlaska_field.html" with field=form.email %} +
    • + {% include "seminar/prihlaska_field.html" with field=form.telefon %} +
    • + {% include "seminar/prihlaska_field.html" with field=form.datum_narozeni %} +
    • +
      + Bydliště +
    • + {% include "seminar/prihlaska_field.html" with field=form.ulice %} +
    • + {% include "seminar/prihlaska_field.html" with field=form.mesto %} +
    • + {% include "seminar/prihlaska_field.html" with field=form.psc %} +
    • + {% include "seminar/prihlaska_field.html" with field=form.stat %} +
    • +
    • + {% include "seminar/prihlaska_field.html" with field=form.stat_text %} +
    • +
      + {% include "seminar/prihlaska_field.html" with field=form.skola %} +
    • + +
    • +
    • + Vyplň prosím celý název a adresu školy.
      + {% include "seminar/prihlaska_field.html" with field=form.skola_nazev %} +
    • +
    • + {% include "seminar/prihlaska_field.html" with field=form.skola_adresa %} +
    • + {% include "seminar/prihlaska_field.html" with field=form.rok_maturity %} +
    • + {% include "seminar/prihlaska_field.html" with field=form.zasilat %} +
    • + {% include "seminar/prihlaska_field.html" with field=form.spam %} +
    • +
    + +
    + +{% endblock %} + diff --git a/seminar/templates/seminar/gdpr.html b/seminar/templates/seminar/gdpr.html new file mode 100644 index 00000000..74e253f5 --- /dev/null +++ b/seminar/templates/seminar/gdpr.html @@ -0,0 +1,49 @@ +

    +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. +

    +
    +

    +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í. +

    +

    +Slibujeme Ti, že Tvá osobní data nezneužijeme k ničemu, co by nesouviselo s M&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. +

    +

    +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. +

    +

    +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. +

    +

    +Souhlas se zpracováním osobních údajů pro potřeby chodu semináře +

    +

    +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&M a Matematicko-fyzikální fakulty UK (dále jen M&M a MFF UK). +

    +

    +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.). +

    +

    +MFF UK tyto údaje zpracovává za účelem evidence řešitelů a účastníků M&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. +

    +

    +Údaje nebudou předány třetí osobě ani využívány k jiným účelům, než ke kterým byly poskytnuty. +

    +

    +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. +

    +

    +Dále máte právo: +

      +
    • požádat o informaci, jaké osobní údaje jsou o vás zpracovávány, +
    • požadovat opravu osobních údajů, pokud jsou neplatné nebo zastaralé, +
    • 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ů, +
    • požadovat, aby byly vaše osobní údaje předány jinému správci, +
    • podat stížnost u dozorového úřadu. +

      +

      +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. +

      +
    diff --git a/seminar/templates/seminar/login.html b/seminar/templates/seminar/login.html new file mode 100644 index 00000000..6319ecc0 --- /dev/null +++ b/seminar/templates/seminar/login.html @@ -0,0 +1,26 @@ +{% extends "seminar/zadani/base.html" %} +{% load staticfiles %} + + +{% block content %} +

    + {% block nadpis1a %}{% block nadpis1b %} + Přihlášení + {% endblock %}{% endblock %} +

    +
    + {% csrf_token %} +
      + {{ form.as_ul }} +
    + {# Django si posílá jméno další stránky jako obsah formuláře a výchozí hodnota (mi přišlo, že) nejde změnit... #} + + +
    + +Zapomněl jsem heslo
    +Zaregistrovat
    + + +{% endblock %} + diff --git a/seminar/templates/seminar/logout.html b/seminar/templates/seminar/logout.html new file mode 100644 index 00000000..ab41a8c8 --- /dev/null +++ b/seminar/templates/seminar/logout.html @@ -0,0 +1,18 @@ +{% extends "seminar/zadani/base.html" %} +{% load staticfiles %} + + +{% block content %} +

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

    + +Byl jsi úspěšně odhlášen +{# Tohle by se asi mělo udělat přes kontext (title), ale kašlu na to, stejně je to jen jednojazyčná stránka #} + +{# TODO: odkaz na znovupřihlášení? #} + +{% endblock %} + diff --git a/seminar/templates/seminar/org/obalkovani.html b/seminar/templates/seminar/org/obalkovani.html new file mode 100644 index 00000000..fa130bc7 --- /dev/null +++ b/seminar/templates/seminar/org/obalkovani.html @@ -0,0 +1,30 @@ +{% extends "base.html" %} + +{% block content %} +

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

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

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

    +
      + {% endifchanged %} + +
    • Celkem {{reseni.hodnoceni__body__sum}} bodů z {{reseni.hodnoceni__count}} hodnocení +
        + {% for h in reseni.hodnoceni_set.all %} +
      • {{ h.problem }}: {{ h.body }}b
      • + {% endfor %} +
      +
    • + {% endfor %} +
    + + +{% endblock content %} diff --git a/seminar/templates/seminar/org/vloz_reseni.html b/seminar/templates/seminar/org/vloz_reseni.html new file mode 100644 index 00000000..bfe4f6f6 --- /dev/null +++ b/seminar/templates/seminar/org/vloz_reseni.html @@ -0,0 +1,21 @@ +{% extends "seminar/zadani/base.html" %} +{% load staticfiles %} +{% block script %} + + {{form.media}} + +{% endblock %} + +{% block content %} +

    + {% block nadpis1a %}{% block nadpis1b %} + Vložit řešení + {% endblock %}{% endblock %} +

    +
    + {% csrf_token %} +{{form.as_p}} + +
    + +{% endblock %} diff --git a/seminar/templates/seminar/prihlaska.html b/seminar/templates/seminar/prihlaska.html index 891bdb6d..8b17c0bd 100644 --- a/seminar/templates/seminar/prihlaska.html +++ b/seminar/templates/seminar/prihlaska.html @@ -1,5 +1,112 @@ -
    - {% csrf_token %} - {{ form }} - +{% extends "seminar/zadani/base.html" %} +{% load staticfiles %} + +{% block script %} + + {{form.media}} + +{% endblock %} + +{% block content %} +

    + {% block nadpis1a %}{% block nadpis1b %} + Přihláška do semináře + {% endblock %}{% endblock %} +

    + + + + {% csrf_token %} + {{form.non_field_errors}} +
      +
    • + Přihlašovací údaje +
    • +
    • + {% include "seminar/prihlaska_field.html" with field=form.username %} +
    • +
    • + {% include "seminar/prihlaska_field.html" with field=form.password %} +
    • +
    • + {% include "seminar/prihlaska_field.html" with field=form.password_check %} +
    • +
    • + Osobní údaje +
    • +
    • + {% include "seminar/prihlaska_field.html" with field=form.jmeno %} +
    • +
    • + {% include "seminar/prihlaska_field.html" with field=form.prijmeni %} +
    • +
    • + {% include "seminar/prihlaska_field.html" with field=form.pohlavi_muz%} +
    • +
    • + {% include "seminar/prihlaska_field.html" with field=form.email %} +
    • +
    • + {% include "seminar/prihlaska_field.html" with field=form.telefon %} +
    • +
    • + {% include "seminar/prihlaska_field.html" with field=form.datum_narozeni %} +
    • +
    • +
      + Bydliště +
    • +
    • + {% include "seminar/prihlaska_field.html" with field=form.ulice %} +
    • +
    • + {% include "seminar/prihlaska_field.html" with field=form.mesto %} +
    • +
    • + {% include "seminar/prihlaska_field.html" with field=form.psc %} +
    • +
    • + {% include "seminar/prihlaska_field.html" with field=form.stat %} +
    • +
    • + {% include "seminar/prihlaska_field.html" with field=form.stat_text %} +
    • + +
    • +
      + {% include "seminar/prihlaska_field.html" with field=form.skola %} +
    • +
    • + +
    • +
    • + Vyplň prosím celý název a adresu školy.
      + {% include "seminar/prihlaska_field.html" with field=form.skola_nazev %} +
    • +
    • + {% include "seminar/prihlaska_field.html" with field=form.skola_adresa %} +
    • +
    • + {% include "seminar/prihlaska_field.html" with field=form.rok_maturity %} +
    • +
    • + {% include "seminar/prihlaska_field.html" with field=form.zasilat %} +
    • +
    • + {% include "seminar/gdpr.html" %} + {% include "seminar/prihlaska_field.html" with field=form.gdpr %} +
    • +
    • + {% include "seminar/prihlaska_field.html" with field=form.spam %} +
    • +
    +
    + + + +{% endblock %} + diff --git a/seminar/templates/seminar/prihlaska_field.html b/seminar/templates/seminar/prihlaska_field.html new file mode 100644 index 00000000..e37549ed --- /dev/null +++ b/seminar/templates/seminar/prihlaska_field.html @@ -0,0 +1,4 @@ + + {{field}} + {% if field.help_text %}{{ field.help_text|safe }}{% endif %} + {% if field.errors %}{{ field.errors }}{% endif %} diff --git a/seminar/templates/seminar/resitel.html b/seminar/templates/seminar/resitel.html new file mode 100644 index 00000000..3a38a085 --- /dev/null +++ b/seminar/templates/seminar/resitel.html @@ -0,0 +1,17 @@ +{% extends "seminar/zadani/base.html" %} +{% load staticfiles %} + + +{% block content %} +

    + {% block nadpis1a %}{% block nadpis1b %} + Stránka řešitele - {{ object.osoba.jmeno }} {{ object.osoba.prijmeni }} + {% endblock %}{% endblock %} +

    + +Odhlásit se
    +Upravit údaje
    + + +{% endblock %} + diff --git a/seminar/templates/seminar/tematka/rozcestnik.html b/seminar/templates/seminar/tematka/rozcestnik.html new file mode 100644 index 00000000..b13d6075 --- /dev/null +++ b/seminar/templates/seminar/tematka/rozcestnik.html @@ -0,0 +1,14 @@ +{% for tematko in tematka %} +

    {{tematko.nazev}}

    +

    {{tematko.abstrakt}}

    +
      + {% for cislo in tematko.cisla %} +
    • {{cislo.0.0}}
    • +
        + {% for odkaz in cislo.1 %} +
      • {{odkaz.0}}
      • + {% endfor %} +
      + {% endfor %} +
    +{% endfor %} diff --git a/seminar/templates/seminar/tematka/toaletak.html b/seminar/templates/seminar/tematka/toaletak.html new file mode 100644 index 00000000..8b556c6c --- /dev/null +++ b/seminar/templates/seminar/tematka/toaletak.html @@ -0,0 +1 @@ +Stránká témátka diff --git a/seminar/testutils.py b/seminar/testutils.py index 8a1581a0..4c13fd94 100644 --- a/seminar/testutils.py +++ b/seminar/testutils.py @@ -1,11 +1,13 @@ # -*- coding: utf-8 -*- import datetime +from pytz import timezone import random import lorem import django.contrib.auth from django.db import transaction import unidecode +import logging from seminar.models import Skola, Resitel, Rocnik, Cislo, Problem, Reseni, PrilohaReseni, Nastaveni, Soustredeni, Soustredeni_Ucastnici, Soustredeni_Organizatori, Osoba, Organizator, Prijemce, Tema, Uloha, Konfera, KonferaNode, TextNode, UlohaVzorakNode, RocnikNode, CisloNode, TemaVCisleNode, Text, Hodnoceni, UlohaZadaniNode, Novinky @@ -15,7 +17,11 @@ from django.contrib.sites.models import Site User = django.contrib.auth.get_user_model() zlinska = None # tohle bude speciální škola, které později dodáme kontaktní osobu +logger = logging.getLogger(__name__) + def gen_osoby(rnd, size): + logger.info('Generuji osoby (size={})...'.format(size)) + jmena_m = ['Aleš', 'Tomáš', 'Martin', 'Jakub', 'Petr', 'Lukáš', 'Cyril', 'Pavel Karel'] jmena_f = ['Eva', 'Karolína', 'Zuzana', 'Sylvie', 'Iva', 'Jana', 'Marie', 'Marta Iva', 'Shu Shan'] @@ -82,6 +88,8 @@ def gen_osoby(rnd, size): def gen_skoly(): #TODO někdy to přepsat, aby jich bylo více + logger.info('Generuji školy...') + skoly = [] prvnizs = Skola.objects.create(mesto='Praha', stat='CZ', psc='101 00', ulice='Krátká 5', nazev='První ZŠ', je_zs=True, je_ss=False) @@ -105,30 +113,43 @@ def gen_skoly(): #TODO někdy to přepsat, aby jich bylo více return skoly def gen_resitele(rnd, osoby, skoly): + logger.info('Generuji řešitele...') + resitele = [] for os in osoby: rand = rnd.randint(0, 8) if not (rand % 8 == 0): resitele.append(Resitel.objects.create(osoba=os, skola=rnd.choice(skoly), rok_maturity=rnd.randint(2019, 2029), - zasilat=rnd.choice(Resitel.ZASILAT_CHOICES))) + zasilat=rnd.choice(Resitel.ZASILAT_CHOICES)[0])) return resitele def gen_prijemci(rnd, osoby, kolik=10): + logger.info('Generuji příjemce (kolik={})...'.format(kolik)) prijemci = [] for i in rnd.sample(osoby, kolik): prijemci.append(Prijemce.objects.create(osoba=i)) return prijemci def gen_organizatori(rnd, osoby, last_rocnik, users): + logger.info('Generuji organizátory...') organizatori = [] for os in osoby: rand = rnd.randint(0, 8) if (rand % 8 == 0): pusobnost = rnd.randint(1, last_rocnik) - od = datetime.date(1993 + pusobnost, rnd.randint(1, 12), rnd.randint(1, 28)) - do = datetime.date(od.year + rnd.randint(1, 6), rnd.randint(1, 12), - rnd.randint(1, 28)) + od = datetime.datetime( + year=1993 + pusobnost, + month=rnd.randint(1, 12), + day=rnd.randint(1, 28), + tzinfo=timezone('CET'), + ) + do = datetime.datetime( + year=od.year + rnd.randint(1, 6), + month=rnd.randint(1, 12), + day=rnd.randint(1, 28), + tzinfo=timezone('CET'), + ) #aktualni organizatori jeste nemaji vyplnene organizuje_do if do.year > datetime.datetime.now().year: do = None @@ -137,6 +158,8 @@ def gen_organizatori(rnd, osoby, last_rocnik, users): return organizatori def gen_ulohy_do_cisla(rnd, organizatori, resitele, rocnik_cisla, rocniky, size): + logger.info('Generuji úlohy do čísla (size={})...'.format(size)) + # ulohy resene v cisle jaka = ["Šachová", "Černá", "Větrná", "Dlouhá", "Křehká", "Rychlá", "Zákeřná", "Fyzikální"] @@ -159,7 +182,14 @@ def gen_ulohy_do_cisla(rnd, organizatori, resitele, rocnik_cisla, rocniky, size) for rocnik in rocniky: k+=1 cisla = rocnik_cisla[k-1] - for ci in range(3, len(cisla)+1): + for ci in range(3, len(cisla)+1): # pro všechna čísla + resitele_size = round(7/8 * 30 * size) # očekáváný celkový počet řešitelů + poc_res = rnd.randint(round(resitele_size/8), round(3*resitele_size/4)) + # dané číslo řeší něco mezi osminou a tříčtvrtinou všech řešitelů + # (náhodná hausnumera, možno změnit) + # účelem je, aby se řešení generovala z menší množiny řešitelů a tedy + # bylo více řešení od jednoho řešitele daného čísla + resitele_cisla = rnd.sample(resitele, poc_res) for pi in range(1, ((size + 1) // 2) + 1): poc_op = rnd.randint(1, 4) # počet opravovatelů @@ -200,6 +230,7 @@ def gen_ulohy_do_cisla(rnd, organizatori, resitele, rocnik_cisla, rocniky, size) p.ulohazadaninode = uloha_zadani otec_syn(cisla[ci-2-1].cislonode, uloha_zadani) + # generování vzorového textu text_vzoraku = Text.objects.create( na_web = rnd.choice(reseni), do_cisla = rnd.choice(reseni) @@ -212,17 +243,18 @@ def gen_ulohy_do_cisla(rnd, organizatori, resitele, rocnik_cisla, rocniky, size) p.opravovatele.set(rnd.sample(organizatori,poc_op)) p.save() - # reseni ulohy + # generování řešení poc_reseni = rnd.randint(size // 2, size * 2) - #poc_resitel = rnd.randint(1, 3) <- k čemu je himbajs tahle proměnná? - # vybereme nahodny vzorek resitelu o delce poctu reseni - # (nebo skoro vsechny resitele, pokud jich je mene nez pocet reseni) + # generujeme náhodný počet řešení for ri in range(poc_reseni): - res_vyber = rnd.sample(resitele, rnd.randint(1, 5)) + if rnd.randint(1, 10) == 6: + # cca desetina řešení od více řešitelů + res_vyber = rnd.sample(resitele_cisla, rnd.randint(2, 5)) + else: + res_vyber = rnd.sample(resitele_cisla, 1) + res = Reseni.objects.create(forma=rnd.choice(Reseni.FORMA_CHOICES)[0]) # problem a resitele přiřadíme později, ManyToManyField # se nedá vyplnit v create() - res = Reseni.objects.create(forma=rnd.choice(Reseni.FORMA_CHOICES)) - #res.save() <- asi smazat res.resitele.set(res_vyber) res.save() hod = Hodnoceni.objects.create( @@ -235,29 +267,33 @@ def gen_ulohy_do_cisla(rnd, organizatori, resitele, rocnik_cisla, rocniky, size) return def gen_soustredeni(rnd, resitele, organizatori): - soustredeni = [] - for _ in range(1, 10): #FIXME Tu range si změňte jak chcete, nevím, co přesně znamená size (asi Anet?) - datum_zacatku=datetime.date(rnd.randint(2000, 2020), rnd.randint(1, 12), rnd.randint(1, 28)) - working_sous = Soustredeni.objects.create( - rocnik=Rocnik.objects.order_by('?').first(), - verejne_db=rnd.choice([True, False]), - misto=rnd.choice(['Kremrolovice', 'Indiánov', 'U zmzliny', 'Vafláreň', 'Větrník', 'Horní Rakvička', 'Dolní cheesecake']), - typ=rnd.choice(['jarni', 'podzimni', 'vikend']), - datum_zacatku=datum_zacatku, - datum_konce=datum_zacatku + datetime.timedelta(days=7)) - ucastnici = rnd.sample(resitele, min(len(resitele), 20)) - working_sous.ucastnici.set(ucastnici) - #for res in rnd.sample(resitele, min(len(resitele), 20)): - # Soustredeni_Ucastnici.objects.create(resitel=res, soutredeni=working_sous) - orgove_vyber = rnd.sample(organizatori, min(len(organizatori), 20)) - working_sous.organizatori.set(orgove_vyber) - #for org in rnd.sample(organizatori, min(len(organizatori), 20)): - # Soustredeni_Organizatori.objects.create(organizator=org, soutredeni=working_sous) - working_sous.save() - soustredeni.append(working_sous) - return soustredeni + logger.info('Generuji soustředění...') + + soustredeni = [] + for _ in range(1, 10): #FIXME Tu range si změňte jak chcete, nevím, co přesně znamená size (asi Anet?) + datum_zacatku=datetime.date(rnd.randint(2000, 2020), rnd.randint(1, 12), rnd.randint(1, 28)) + working_sous = Soustredeni.objects.create( + rocnik=Rocnik.objects.order_by('?').first(), + verejne_db=rnd.choice([True, False]), + misto=rnd.choice(['Kremrolovice', 'Indiánov', 'U zmzliny', 'Vafláreň', 'Větrník', 'Horní Rakvička', 'Dolní cheesecake']), + typ=rnd.choice(['jarni', 'podzimni', 'vikend']), + datum_zacatku=datum_zacatku, + datum_konce=datum_zacatku + datetime.timedelta(days=7)) + ucastnici = rnd.sample(resitele, min(len(resitele), 20)) + working_sous.ucastnici.set(ucastnici) + #for res in rnd.sample(resitele, min(len(resitele), 20)): + # Soustredeni_Ucastnici.objects.create(resitel=res, soutredeni=working_sous) + orgove_vyber = rnd.sample(organizatori, min(len(organizatori), 20)) + working_sous.organizatori.set(orgove_vyber) + #for org in rnd.sample(organizatori, min(len(organizatori), 20)): + # Soustredeni_Organizatori.objects.create(organizator=org, soutredeni=working_sous) + working_sous.save() + soustredeni.append(working_sous) + return soustredeni def gen_rocniky(last_rocnik, size): + logger.info('Generuji ročníky (size={})...'.format(size)) + rocniky = [] node = None for ri in range(min(last_rocnik - size, 1), last_rocnik + 1): @@ -269,6 +305,8 @@ def gen_rocniky(last_rocnik, size): return rocniky def gen_konfery(size, rnd, organizatori, resitele, soustredeni): + logger.info('Generuji konfery (size={})...'.format(size)) + konfery = [] for _ in range(1, size): #FIXME Tu range si změňte jak chcete, nevím, co přesně znamená size (asi Anet?) # Anet: size je parametr udávající velikost testovacích dat a dá se pomocí ní škálovat, @@ -293,6 +331,8 @@ def gen_konfery(size, rnd, organizatori, resitele, soustredeni): return konfery def gen_cisla(rnd, rocniky): + logger.info('Generuji čísla...') + rocnik_cisla = [] for rocnik in rocniky: otec = True @@ -318,7 +358,7 @@ def gen_cisla(rnd, rocniky): cislo = Cislo.objects.create( rocnik = rocnik, - cislo = str(ci), + poradi = str(ci), datum_vydani=vydano, datum_deadline=deadline, verejne_db=True @@ -336,6 +376,8 @@ def gen_cisla(rnd, rocniky): return rocnik_cisla def gen_temata(rnd, rocniky, rocnik_cisla, organizatori): + logger.info('Generuji témata...') + jake = ["Hravé", "Fyzikální", "Nejlepší", "Totálně masakrální", "Šokující", "Magnetické", "Modré", "Překvapivé", "Plasmatické", "Novoroční"] @@ -362,8 +404,9 @@ def gen_temata(rnd, rocniky, rocnik_cisla, organizatori): garant=rnd.choice(organizatori), kod=str(n), # atributy třídy Téma - tema_typ=rnd.choice(Tema.TEMA_CHOICES), - rocnik=rocnik + tema_typ=rnd.choice(Tema.TEMA_CHOICES)[0], + rocnik=rocnik, + abstrakt = "Abstrakt tematka {}".format(n) ) konec_tematu = min(rnd.randint(ci, 7), len(cisla)) for i in range(ci, konec_tematu+1): @@ -379,6 +422,8 @@ def gen_temata(rnd, rocniky, rocnik_cisla, organizatori): def gen_ulohy_k_tematum(rnd, rocniky, rocnik_cisla, rocnik_temata, organizatori): + logger.info('Generuji úlohy k tématům...') + # ulohy resene v cisle jaka = ["Šachová", "Černá", "Větrná", "Dlouhá", "Křehká", "Rychlá", "Zákeřná", "Fyzikální"] @@ -475,6 +520,8 @@ def gen_ulohy_k_tematum(rnd, rocniky, rocnik_cisla, rocnik_temata, organizatori) return def gen_novinky(rnd, organizatori): + logger.info('Generuji novinky...') + jake = ["zábavné", "veselé", "dobrodružné", "skvělé"] co = ["soustředění", "Fyziklání", "víkendové setkání"] @@ -496,6 +543,8 @@ def otec_syn(otec, syn): @transaction.atomic def create_test_data(size = 6, rnd = None): + logger.info('Vyrábím testovací data (size={})...'.format(size)) + assert size >= 1 # pevna pseudo-nahodnost rnd = rnd or random.Random(x=42) @@ -576,5 +625,5 @@ def create_test_data(size = 6, rnd = None): # obecné nastavení semináře, musí být už přidané ročníky a čísla, jinak se nastaví divně - nastaveni = Nastaveni.objects.create(aktualni_rocnik = Rocnik.objects.last(), + nastaveni = Nastaveni.objects.create( aktualni_cislo = Cislo.objects.all()[1]) diff --git a/seminar/urls.py b/seminar/urls.py index 67d0b526..bc1c89a8 100644 --- a/seminar/urls.py +++ b/seminar/urls.py @@ -3,10 +3,14 @@ from django.contrib.auth.decorators import user_passes_test from . import views, export from .utils import staff_member_required from django.views.generic.base import RedirectView +from django.contrib.auth import views as auth_views staff_member_required = user_passes_test(lambda u: u.is_staff) urlpatterns = [ + path('aktualni/temata/', views.TemataRozcestnikView), + path('/t/', views.TematkoView), + # REDIRECTy path('jak-resit/', RedirectView.as_view(url='/co-je-MaM/jak-resit/')), @@ -85,19 +89,36 @@ urlpatterns = [ path('stav', staff_member_required(views.StavDatabazeView), name='stav_databaze'), path('cislo/./obalkovani', - staff_member_required(views.obalkovaniView), name='seminar_cislo_resitel_obalkovani'), + staff_member_required(views.ObalkovaniView.as_view()), name='seminar_cislo_resitel_obalkovani'), path('cislo/./tex-download.json', staff_member_required(views.texDownloadView), name='seminar_tex_download'), path('soustredeni//obalky.pdf', staff_member_required(views.soustredeniObalkyView), name='seminar_soustredeni_obalky'), - path('tex-upload/login/', views.LoginView, name='seminar_login'), + path('tex-upload/login/', views.TeXUploadLoginView, name='seminar_login'), path( 'tex-upload/', staff_member_required(views.texUploadView), name='seminar_tex_upload' ), - path('prihlaska/',views.get_name), + path('org/vloz_body//', + 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///', views.PasswordResetConfirmView.as_view(), name='password_reset_confirm'), + path('auth/reset_password_complete/', views.PasswordResetCompleteView.as_view(), name='reset_password_complete'), + path('auth/resitel_edit', views.resitelEditView, name='seminar_resitel_edit'), + + + path('temp/add_solution', views.AddSolutionView.as_view(),name='seminar_vloz_reseni'), + path('', views.TitulniStranaView.as_view(), name='titulni_strana'), # Ceka na autocomplete v3 diff --git a/seminar/utils.py b/seminar/utils.py index 75092384..d910a5b6 100644 --- a/seminar/utils.py +++ b/seminar/utils.py @@ -2,9 +2,18 @@ import datetime from django.contrib.auth.decorators import user_passes_test +from html.parser import HTMLParser staff_member_required = user_passes_test(lambda u: u.is_staff) +class FirstTagParser(HTMLParser): + def __init__(self, *args, **kwargs): + self.firstTag = None + super().__init__(*args, **kwargs) + def handle_data(self, data): + if self.firstTag == None: + self.firstTag = data + def histogram(seznam): d = {} for i in seznam: diff --git a/seminar/views.py b/seminar/views.py index 57c85b65..e174ab28 100644 --- a/seminar/views.py +++ b/seminar/views.py @@ -2,22 +2,31 @@ from django.shortcuts import get_object_or_404, render from django.http import HttpResponse, HttpResponseRedirect, HttpResponseForbidden, JsonResponse -from django.urls import reverse +from django.urls import reverse,reverse_lazy from django.core.exceptions import PermissionDenied, ObjectDoesNotExist from django.views import generic from django.utils.translation import ugettext as _ from django.http import Http404,HttpResponseBadRequest,HttpResponseRedirect -from django.db.models import Q +from django.db.models import Q, Sum, Count from django.views.decorators.csrf import ensure_csrf_cookie -from django.contrib.auth import authenticate, login - -from .models import Problem, Cislo, Reseni, Nastaveni, Rocnik, Soustredeni, Organizator, Resitel, Novinky, Soustredeni_Ucastnici, Pohadka, Tema, Clanek +from django.views.generic.edit import FormView +from django.contrib.auth import authenticate, login, get_user_model, logout +from django.contrib.auth import views as auth_views +from django.contrib.auth.models import User +from django.contrib.auth.mixins import LoginRequiredMixin +from django.db import transaction +from dal import autocomplete + +import seminar.models as s +from .models import Problem, Cislo, Reseni, Nastaveni, Rocnik, Soustredeni, Organizator, Resitel, Novinky, Soustredeni_Ucastnici, Pohadka, Tema, Clanek, Osoba, Skola # Tohle je stare a chceme se toho zbavit. Pouzivejte s.ToCoChci #from .models import VysledkyZaCislo, VysledkyKCisluZaRocnik, VysledkyKCisluOdjakziva from . import utils from .unicodecsv import UnicodeWriter -from .forms import NameForm +from .forms import PrihlaskaForm, LoginForm, ProfileEditForm +import seminar.forms as f from datetime import timedelta, date, datetime +from django.utils import timezone from itertools import groupby import tempfile import subprocess @@ -30,6 +39,7 @@ import json import traceback import sys import csv +import logging def verejna_temata(rocnik): @@ -37,6 +47,45 @@ def verejna_temata(rocnik): """ return Problem.objects.filter(typ=Problem.TYP_TEMA, cislo_zadani__rocnik=rocnik, cislo_zadani__verejne_db=True).order_by('kod') +def temata_v_rocniku(rocnik): + return Problem.objects.filter(typ=Problem.TYP_TEMA, rocnik=rocnik) + +def get_problemy_k_tematu(tema): + return Problemy.objects.filter(nadproblem = tema) + + +class VlozBodyView(generic.ListView): + template_name = 'seminar/org/vloz_body.html' + + def get_queryset(self): + self.tema = get_object_or_404(Problem,id=self.kwargs['tema']) + print(self.tema) + self.problemy = Problem.objects.filter(nadproblem = self.tema) + print(self.problemy) + self.reseni = Reseni.objects.filter(problem__in=self.problemy) + print(self.reseni) + return self.reseni + + +class ObalkovaniView(generic.ListView): + template_name = 'seminar/org/obalkovani.html' + + def get_queryset(self): + rocnik = get_object_or_404(Rocnik,rocnik=self.kwargs['rocnik']) + cislo = get_object_or_404(Cislo,rocnik=rocnik,poradi=self.kwargs['cislo']) + self.cislo = cislo + self.hodnoceni = s.Hodnoceni.objects.filter(cislo_body=cislo) + self.reseni = Reseni.objects.filter(hodnoceni__in = self.hodnoceni).annotate(Sum('hodnoceni__body')).annotate(Count('hodnoceni')).order_by('resitele__osoba') + return self.reseni + + def get_context_data(self, **kwargs): + context = super(ObalkovaniView, self).get_context_data(**kwargs) + print(self.cislo) + context['cislo'] = self.cislo + return context + + + def AktualniZadaniView(request): nastaveni = get_object_or_404(Nastaveni) @@ -67,6 +116,99 @@ def ZadaniTemataView(request): } ) +# TODO Napsat tuto funkci znovu rekurzivně podle Jethrorad. Potom se podívat, jak lehce se dá modifikovat pro Rozcestník. Pokud lehce, rozšířit ji. Pokud složitě - použít tuhle +def vytahniZLesaSeznam(tematko, koren, pouze_zajimave=False): + returnVal = [] + + stack = [] + stack.append((koren.first_child, 0, False)) #Tuple of node, depth and relevance + + while len(stack) > 0: + wn, wd, wr = stack.pop() + + if wn.succ != None: + stack.append((wn.succ, wd, wr)) + if isinstance(wn, s.TemaVCisleNode): + print("TEMA") + print(wn.tema.id) + print(tematko.id) + if wn.tema.id == tematko.id: + returnVal.append((posledni_cislo, 0)) + print("PRIDANO") + wr = True + wd = 1 + + if wn.srolovatelne: + tagOpen = s.Text(na_web = "Otevírací srolovací tag") + tagOpenNode = s.TextNode(text = tagOpen) + tagClose = s.Text(na_web = "Zavírací srolovací tag") + tagCloseNode = s.TextNode(text = tagClose) + stack.append((tagCloseNode, wd, True)) + + if wn.first_child != None: + stack.append((wn.first_child, wd + 1, wr)) + + if isinstance(wn, s.CisloNode): + posledni_cislo = wn + print(wn) + + if wr: + print("ZAJIMAVE") + if pouze_zajimave: + if not wn.zajimave: + continue + returnVal.append((wn, wd)) + return returnVal + +def TematkoView(request, rocnik, tematko): + nastaveni = s.Nastaveni.objects.first() + rocnik_object = s.Rocnik.objects.filter(rocnik=rocnik) + tematko_object = s.Tema.objects.filter(rocnik=rocnik_object[0], kod=tematko) + seznam = vytahniZLesaSeznam(tematko_object[0], nastaveni.aktualni_rocnik().rocniknode) + for node, depth in seznam: + if node.isinstance(node, s.KonferaNode): + raise Exception("Not implemented yet") + if node.isinstance(node, s.PohadkaNode): # Mohu ignorovat, má pod sebou + pass + + return render(request, 'seminar/tematka/toaletak.html', {}) + + +def TemataRozcestnikView(request): + print("=============================================") + nastaveni = s.Nastaveni.objects.first() + tematka_objects = s.Tema.objects.filter(rocnik=nastaveni.aktualni_rocnik()) + tematka = [] #List tematka obsahuje pro kazde tematko object a list vsech TemaVCisleNodu - implementované pomocí slovníku + for tematko_object in tematka_objects: + print("AKTUALNI TEMATKO") + print(tematko_object.id) + odkazy = vytahniZLesaSeznam(tematko_object, nastaveni.aktualni_rocnik().rocniknode, pouze_zajimave = True) #Odkazy jsou tuply (node, depth) v listu + print(odkazy) + cisla = [] # List tuplů (nazev cisla, list odkazů) + vcisle = [] + cislo = None + for odkaz in odkazy: + if odkaz[1] == 0: + if cislo != None: + cisla.append((cislo, vcisle)) + cislo = (odkaz[0].getOdkazStr(), odkaz[0].getOdkaz()) + vcisle = [] + else: + print(odkaz[0].getOdkaz()) + vcisle.append((odkaz[0].getOdkazStr(), odkaz[0].getOdkaz())) + if cislo != None: + cisla.append((cislo, vcisle)) + + print(cisla) + tematka.append({ + "kod" : tematko_object.kod, + "nazev" : tematko_object.nazev, + "abstrakt" : tematko_object.abstrakt, + "obrazek": tematko_object.obrazek, + "cisla" : cisla + }) + return render(request, 'seminar/tematka/rozcestnik.html', {"tematka": tematka, "rocnik" : nastaveni.aktualni_rocnik().rocnik}) + #def ZadaniAktualniVysledkovkaView(request): # nastaveni = get_object_or_404(Nastaveni) @@ -143,7 +285,7 @@ class StareNovinkyView(generic.ListView): # Organizatori -def aktivniOrganizatori(datum=date.today()): +def aktivniOrganizatori(datum=timezone.now()): return Organizator.objects.exclude( organizuje_do__isnull=False, organizuje_do__lt=datum @@ -252,26 +394,115 @@ class ArchivView(generic.ListView): context["nahledy"] = "\n".join(tags) return context - -def sloupec_s_poradim(vysledky): - # počet řešitelů ve výsledkovce nad aktuálním - lepsich_resitelu = 0 - - poradi_l = [] - # projdeme skupiny řešitelů se stejným počtem bodů - for skupina in (list(x) for _, x in groupby(vysledky, lambda x: x.body)): - - # připravíme si obsahy buněk ve sloupci pořadí pro skupinu - if len(skupina) == 1: - poradi_l += ["{}.".format(lepsich_resitelu + 1)] - # je-li účastníků se stejným počtem bodů víc, pořadí (rozsah X.-Y.) je jen u prvního +### Výsledky + +# ze setřízeného(!) seznamu všech bodů vytvoří seznam s pořadími (včetně 3.-5. a pak 2 volná místa atp.) +def sloupec_s_poradim(seznam_s_body): + aktualni_poradi = 1 + sloupec_s_poradim = [] + + # seskupíme seznam všech bodů podle hodnot + for index in range(0, len(seznam_s_body)): + # pokud je pořadí větší než číslo řádku, tak jsme vypsali větší rozsah a chceme + # vypsat už jen prázdné místo, než dojdeme na správný řádek + if (index + 1) < aktualni_poradi: + sloupec_s_poradim.append("") + continue + velikost_skupiny = 0 + # zjistíme počet po sobě jdoucích stejných hodnot + while seznam_s_body[index] == seznam_s_body[index + velikost_skupiny]: + velikost_skupiny = velikost_skupiny + 1 + # na konci musíme ošetřit přetečení seznamu + if (index + velikost_skupiny) > len(seznam_s_body) - 1: + break + # pokud je velikost skupiny 1, vypíšu pořadí + if velikost_skupiny == 1: + sloupec_s_poradim.append("{}.".format(aktualni_poradi)) + # pokud je skupina větší, vypíšu rozsah else: - poradi_l += [u"{}.–{}.".format(lepsich_resitelu + 1, lepsich_resitelu + len(skupina))] + [""] * (len(skupina)-1) - lepsich_resitelu += len(skupina) - #pomlcka je opravdu pomlcka v unicode!!dulezite pro vysledkovku v TeXu - - return poradi_l - + sloupec_s_poradim.append("{}.–{}.".format(aktualni_poradi, + aktualni_poradi+velikost_skupiny-1)) + # zvětšíme aktuální pořadí o tolik, kolik pozic bylo přeskočeno + aktualni_poradi = aktualni_poradi + velikost_skupiny + return sloupec_s_poradim + +# spočítá součet bodů získaných daným řešitelem za zadaný problém a všechny jeho podproblémy +def __soucet_resitele_problemu(problem, resitel, cislo, soucet): + # sečteme body za daný problém přes všechna řešení daného problému + # od daného řešitele + reseni_resitele = problem.hodnoceni_set.filter(reseni__resitele=resitel, + cislo_body=cislo) + # XXX chyba na řádku výše - řešení může mít více řešitelů, asi chceme contains + # nebo in + for r in reseni_resitele: + soucet += r.body + + # a přičteme k tomu hodnocení všech podproblémů + for p in problem.podproblem.all(): + # i přes jméno by to měla být množina jeho podproblémů + soucet += __soucet_resitele_problemu(p, resitel, soucet) + return soucet + +# spočítá součet všech bodů ze všech podproblémů daného problému daného řešitele +def body_resitele_problemu_v_cisle(problem, resitel, cislo): + # probably FIXED: nezohledňuje číslo, do kterého se body počítají + return __soucet_resitele_problemu(problem, resitel, cislo, 0) + +# vrátí list všech problémů s body v daném čísle, které již nemají nadproblém +def hlavni_problemy_cisla(cislo): + hodnoceni = cislo.hodnoceni.select_related('problem', 'reseni').all() # hodnocení, která se vážou k danému číslu + + reseni = [h.reseni for h in hodnoceni] + problemy = [h.problem for h in hodnoceni] + problemy_set = set(problemy) # chceme každý problém unikátně, + problemy = (list(problemy_set)) # převedení na množinu a zpět to zaručí + + # hlavní problémy čísla + # (mají vlastní sloupeček ve výsledkovce, nemají nadproblém) + hlavni_problemy = [] + for p in problemy: + while not(p.nadproblem == None): + p = p.nadproblem + hlavni_problemy.append(p) + + # zunikátnění + hlavni_problemy_set = set(hlavni_problemy) + hlavni_problemy = list(hlavni_problemy_set) + hlavni_problemy.sort(key=lambda k: k.kod_v_rocniku()) # setřídit podle t1, t2, c3, ... + + return hlavni_problemy + +def body_resitele_odjakziva(resitel): + body = 0 + resitelova_hodnoceni = Hodnoceni.objects.select_related('body').all().filter(reseni_resitele=resitel) + # TODO: v radku nahore chceme _in nebo _contains + for hodnoceni in resitelova_hodnoceni: + body = body + hodnoceni.body + return body + +# spočítá součet všech bodů řešitele za dané číslo +def body_resitele_v_cisle(resitel, cislo): + hlavni_problemy = hlavni_problemy_cisla(cislo) + body_resitele = 0 + for h in hlavni_problemy: + body_resitele = body_resitele + body_resitele_problemu_v_cisle(h, resitel, cislo) + # TODO: je rozdíl mezi odevzdanou úlohou za 0 a tím, když řešitel nic neodevzdal + # řešit přes kontrolu velikosti množiny řešení daného problému do daného čísla? + # Tady to ale nevadí, tady se počítá součet za číslo. + return body_resitele + +# spočítá součet všech bodů řešitele za daný rok (nebo jen do daného čísla včetně) +def body_resitele_v_rocniku(resitel, rocnik, do_cisla=None): + # pokud do_cisla=None, tak do posledního čísla v ročníku + # do_cisla je objekt Cislo + cisla = rocnik.cisla.all() # funkce vrátí pole objektů + # Cislo už lexikograficky setřízené, viz models + body = 0 + for cislo in cisla: + if cislo.poradi == do_cisla.poradi: break + # druhá část zaručuje, že máme výsledky do daného čísla včetně + body = body + body_resitele_v_cisle(resitel, cislo) + return body #def vysledkovka_rocniku(rocnik, jen_verejne=True): # """Přebírá ročník (např. context["rocnik"]) a vrací výsledkovou listinu ve @@ -288,7 +519,7 @@ def sloupec_s_poradim(vysledky): # return None # # #vybere vsechny vysledky z posledniho (verejneho) cisla a setridi sestupne dle bodu -# vysledky = list(cisla_v_rocniku.filter(cislo = cisla_v_rocniku[0].cislo).order_by('-body', 'resitel__prijmeni', 'resitel__jmeno').select_related('resitel')) +# vysledky = list(cisla_v_rocniku.filter(cislo = cisla_v_rocniku[0].poradi).order_by('-body', 'resitel__prijmeni', 'resitel__jmeno').select_related('resitel')) # # class Vysledkovka: # def __init__(self): @@ -304,7 +535,7 @@ def sloupec_s_poradim(vysledky): # v.poradi = poradi # v.resitel.rocnik = v.resitel.rocnik(rocnik) # -# verejne_vysl_odjakziva = VysledkyKCisluOdjakziva.objects.filter(cislo__rocnik=rocnik, cislo=cisla_v_rocniku[0].cislo) +# verejne_vysl_odjakziva = VysledkyKCisluOdjakziva.objects.filter(cislo__rocnik=rocnik, cislo=cisla_v_rocniku[0].poradi) # if jen_verejne: # verejne_vysl_odjakziva = verejne_vysl_odjakziva.filter(cislo__verejna_vysledkovka=True) # @@ -380,16 +611,17 @@ class ProblemView(generic.DetailView): class VysledkyResitele(object): """Pro daného řešitele ukládá počet bodů za jednotlivé úlohy a celkový - počet bodů za číslo.""" - - def __init__(self, jmeno, prijmeni): - resitel_jmeno = jmeno - resitel_prijmeni = prijmeni - body = {} - body_cislo = 0 - - def body_za_cislo(self): - return sum(body.values()) + počet bodů za konkrétní ročník do daného čísla a za dané číslo.""" + + def __init__(self, resitel, cislo, rocnik): + self.resitel = resitel + self.cislo = cislo + self.body_cislo = body_resitele_v_cisle(resitel, cislo) + self.body = [] + self.rocnik = rocnik + self.body_rocnik = body_resitele_v_rocniku(resitel, rocnik, cislo) + self.body_celkem_odjakziva = resitel.vsechny_body() + self.poradi = 0 class CisloView(generic.DetailView): model = Cislo @@ -400,8 +632,8 @@ class CisloView(generic.DetailView): if queryset is None: queryset = self.get_queryset() rocnik_arg = self.kwargs.get('rocnik') - cislo_arg = self.kwargs.get('cislo') - queryset = queryset.filter(rocnik__rocnik=rocnik_arg, cislo=cislo_arg) + poradi_arg = self.kwargs.get('cislo') + queryset = queryset.filter(rocnik__rocnik=rocnik_arg, poradi=poradi_arg) try: obj = queryset.get() @@ -410,88 +642,53 @@ class CisloView(generic.DetailView): {'verbose_name': queryset.model._meta.verbose_name}) return obj - # spočítá součet bodů získaných daným řešitelem za zadaný problém a všechny jeho podproblémy - def __soucet_resitele_problemu(problem, resitel, soucet): - # FIXME: správně je nadproblem_(typ problemu), ale to by bylo potřeba nějak - # zjistit, jaký typ nodu to vlastně je a aplikovat to ve volání funkce - - # sečteme body za daný problém přes všechna řešení daného problému - # od daného řešitele - reseni_resitele = problem.hodnoceni_set.filter(reseni_resitele__contains=resitel) - for r in reseni_resitele: - soucet += r.body - - for p in problem.nadproblem_set: - # i přes jméno by to měla být množina jeho podproblémů - soucet += __soucet_resitele_problemu(p, resitel, soucet) - return soucet - - - def vysledky_resitele_problemu(problem, resitel, cislo): - return __soucet_resitele_problemu(problem, resitel, 0) - - def get_context_data(self, **kwargs): context = super(CisloView, self).get_context_data(**kwargs) ## TODO upravit dle nového modelu cislo = context['cislo'] - hodnoceni = cislo.hodnoceni_set # hodnocení, která se vážou k danému číslu - - reseni = [h.reseni for h in hodnoceni] - problemy = [h.problem for h in hodnoceni] - problemy_set = set(problemy) # chceme každý problém unikátně, - problemy = (list(problemy_set)) # převedení na množinu a zpět to zaručí - - # hlavní problémy čísla - # (mají vlastní sloupeček ve výsledkovce, nemají nadproblém) - hlavni_problemy = [] - for p in problemy: - while not(p.nadproblem == None): - p = nadproblem - hlavni_problemy.append(p) - - # zunikátnění - hlavni_problemy_set = set(hlavni_problemy) - hlavni_problemy = list(hlavni_problemy_set) + hlavni_problemy = hlavni_problemy_cisla(cislo) ## TODO dostat pro tyto problémy součet v daném čísle pro daného řešitele ## TODO možná chytřeji vybírat aktivní řešitele ## chceme letos něco poslal aktivni_resitele = Resitel.objects.filter( - rok_maturity__gte=context['rocnik'].druhy_rok()) + rok_maturity__gte=cislo.rocnik.druhy_rok()) + # TODO: zkusit hodnoceni__rocnik... #.filter(hodnoceni_set__rocnik__eq=cislo_rocnik) radky_vysledkovky = [] for ar in aktivni_resitele: - vr = VysledkyResitele(ar.jmeno, ar.prijmeni) - for h in hlavni_problemy: - body = vysledky_resitele_problemu(h, ar, cislo) - vr.body[h.kod_v_rocniku] = body - vr.body_cislo = vr.body_cislo + body + # získáme výsledky řešitele - součty přes číslo a ročník + vr = VysledkyResitele(ar, cislo, cislo.rocnik) + for hp in hlavni_problemy: + vr.body.append( + body_resitele_problemu_v_cisle(hp, ar, cislo)) radky_vysledkovky.append(vr) - ## TODO: spočítat počet bodů řešitele v daném ročníku a seřadit je podle toho - ## TODO: možná použít tyto funkce i v RocnikVysledkovkaView (a umístit sem nebo tam)? - + # setřídíme řádky výsledkovky/objekty VysledkyResitele podle bodů + radky_vysledkovky.sort(key=lambda vr: vr.body_rocnik, reverse=True) -# vysledky = VysledkyKCisluZaRocnik.objects.filter(cislo = context['cislo']).\ -# order_by('-body', 'resitel__prijmeni', 'resitel__jmeno') -# reseni = Reseni.objects.filter(cislo_body = context['cislo']).select_related("resitel") + # 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) + + # každému řádku výsledkovky přidáme jeho pořadí + i = 0 + for rv in radky_vysledkovky: + rv.poradi = sloupec_poradi[i] + i = i + 1 + + # vytahané informace předáváme do kontextu + context['cislo'] = cislo + context['radky_vysledkovky'] = radky_vysledkovky + context['problemy'] = hlavni_problemy +# context['v_cisle_zadane'] = TODO +# context['resene_problemy'] = resene_problemy + #XXX testovat + #XXX opravit to, že se nezobrazují body za jednotlivé úlohy - # typy úloh, které se mají zobrazovat u čísla, tj. těch, které byly - # v čísle skutečně zadány -# typy_skutecne_zadanych = [Problem.TYP_ULOHA, Problem.TYP_SERIAL, Problem.TYP_ORG_CLANEK] -# v_cisle_zadane = Problem.objects.filter(cislo_zadani=context['cislo']).filter(typ__in=typy_skutecne_zadanych).order_by('kod') + return context -# resene_problemy = Problem.objects.filter(cislo_reseni=context['cislo']).filter(typ__in=typy_skutecne_zadanych).order_by('cislo_zadani__cislo', 'kod') -# -# poradi_typu = { -# Problem.TYP_ULOHA: 1, -# Problem.TYP_SERIAL: 2, -# Problem.TYP_ORG_CLANEK: 3, -# Problem.TYP_TEMA: 4, -# Problem.TYP_RES_CLANEK: 5 -# } # problemy = sorted(set(r.problem for r in reseni), key=lambda x:(poradi_typu[x.typ], x.kod_v_rocniku())) # #setridi problemy podle typu a poradi zadani # problem_index = {} @@ -529,7 +726,6 @@ class CisloView(generic.DetailView): # context['problemy'] = problemy # context['v_cisle_zadane'] = v_cisle_zadane # context['resene_problemy'] = resene_problemy -# return context class ArchivTemataView(generic.ListView): model = Problem @@ -601,9 +797,9 @@ def obalkyView(request,resitele): tex = render(request,'seminar/archiv/obalky.tex', {'resitele': resitele}).content tempdir = tempfile.mkdtemp() - with open(tempdir+"/obalky.tex","wb") as texfile: + with open(tempdir+"/obalky.tex","w") as texfile: texfile.write(tex) - shutil.copy(os.path.join(settings.STATIC_ROOT, 'seminar/lisak.eps'),tempdir) + shutil.copy(os.path.join(settings.STATIC_ROOT, 'seminar/lisak.pdf'),tempdir) subprocess.call(["pdflatex","obalky.tex"],cwd = tempdir) with open(tempdir+"/obalky.pdf","rb") as pdffile: @@ -612,7 +808,7 @@ def obalkyView(request,resitele): return response -def obalkovaniView(request, rocnik, cislo): +def oldObalkovaniView(request, rocnik, cislo): rocnik = Rocnik.objects.get(rocnik=rocnik) cislo = Cislo.objects.get(rocnik=rocnik, cislo=cislo) @@ -749,7 +945,7 @@ def StavDatabazeView(request): @ensure_csrf_cookie -def LoginView(request): +def TeXUploadLoginView(request): """Pro přihlášení při nahrávání z texu""" q = request.POST # nastavení cookie csrftoken @@ -938,7 +1134,7 @@ def texDownloadView(request, rocnik, cislo): "body": p.body, "zadani": p.text_zadani, "reseni": p.text_reseni, - "cislo_zadani": p.cislo_zadani.cislo, + "cislo_zadani": p.cislo_zadani.poradi, } for p in resene ], } @@ -947,26 +1143,213 @@ def texDownloadView(request, rocnik, cislo): cislo.save() return JsonResponse(response) +class ResitelView(LoginRequiredMixin,generic.DetailView): + model = Resitel + template_name = 'seminar/resitel.html' + + def get_object(self, queryset=None): + print(self.request.user) + return Resitel.objects.get(osoba__user=self.request.user) + ## Formulare +class AddSolutionView(LoginRequiredMixin, FormView): + template_name = 'seminar/org/vloz_reseni.html' + form_class = f.VlozReseniForm + success_url = '/' + +def resetPasswordView(request): + pass -def get_name(request): - # if this is a POST request we need to process the form data +def loginView(request): if request.method == 'POST': - # create a form instance and populate it with data from the request: - form = NameForm(request.POST) - # check whether it's valid: + form = LoginForm(request.POST) if form.is_valid(): - # process the data in form.cleaned_data as required - # ... - # redirect to a new URL: + user = authenticate(request, + username=form.cleaned_data['username'], + password=form.cleaned_data['password']) + print(form.cleaned_data) + if user is not None: + login(request,user) + return HttpResponseRedirect('/') + else: + return render(request, + 'seminar/login.html', + {'form': form, 'login_error': 'Neplatné jméno nebo heslo'}) + + else: + form = LoginForm() + return render(request, 'seminar/login.html', {'form': form}) + +def logoutView(request): + form = LoginForm() + if request.user.is_authenticated: + logout(request) + return render(request, 'seminar/login.html', {'form': form, 'login_error': 'Byli jste úspěšně odhlášeni'}) + return render(request, 'seminar/login.html', {'form': form}) + + +def prihlaska_log_gdpr_safe(logger, gdpr_logger, msg, form_data): + msg = "{}, form_hash:{}".format(msg,hash(form_data)) + logger.warn(msg) + gdpr_logger.warn(msg+", form:{}".format(form_data)) + +from django.forms.models import model_to_dict +def resitelEditView(request): + err_logger = logging.getLogger('seminar.prihlaska.problem') + ## Načtení objektu Osoba a Resitel, patrici k aktuálně přihlášenému uživately + u = request.user + osoba_edit = Osoba.objects.get(user=u) + resitel_edit = osoba_edit.resitel + user_edit = osoba_edit.user + ## Vytvoření slovníku, kterým předvyplním formulář + prefill_1=model_to_dict(user_edit) + prefill_2=model_to_dict(resitel_edit) + prefill_3=model_to_dict(osoba_edit) + prefill_1.update(prefill_2) + prefill_1.update(prefill_3) + form = ProfileEditForm(initial=prefill_1) + ## Změna údajů a jejich uložení + if request.method == 'POST': + form = ProfileEditForm(request.POST) + if form.is_valid(): + ## Změny v osobě + fcd = form.cleaned_data + osoba_edit.jmeno = fcd['jmeno'] + osoba_edit.prijmeni = fcd['prijmeni'] + osoba_edit.pohlavi_muz = fcd['pohlavi_muz'] + osoba_edit.email = fcd['email'] + osoba_edit.telefon = fcd['telefon'] + osoba_edit.ulice = fcd['ulice'] + osoba_edit.mesto = fcd['mesto'] + osoba_edit.psc = fcd['psc'] + ## Změny v osobě s podmínkami + if fcd.get('spam',False): + osoba_edit.datum_souhlasu_zasilani = date.today() + if fcd.get('stat','') in ('CZ','SK'): + osoba_edit.stat = fcd['stat'] + else: + ## Neznámá země + msg = "Unknown country {}".format(fcd['stat_text']) + + ## Změny v řešiteli + resitel_edit.skola = fcd['skola'] + resitel_edit.rok_maturity = fcd['rok_maturity'] + resitel_edit.zasilat = fcd['zasilat'] + if fcd.get('skola'): + resitel_edit.skola = fcd['skola'] + else: + # Unknown school - log it + msg = "Unknown school {}, {}".format(fcd['skola_nazev'],fcd['skola_adresa']) + resitel_edit.save() + osoba_edit.save() + return HttpResponseRedirect('/thanks/') + else: + ## Stránka před odeslaním formuláře = předvyplněný formulář + return render(request, 'seminar/edit.html', {'form': form}) + +def prihlaskaView(request): + generic_logger = logging.getLogger('seminar.prihlaska') + err_logger = logging.getLogger('seminar.prihlaska.problem') + form_logger = logging.getLogger('seminar.prihlaska.form') + if request.method == 'POST': + form = PrihlaskaForm(request.POST) + # TODO vyresit, co se bude v jakych situacich zobrazovat + if form.is_valid(): + generic_logger.info("Form valid") + fcd = form.cleaned_data + form_hash = hash(fcd) + form_logger.info(fcd,form_hash=form_hash) + + with transaction.atomic(): + u = User.objects.create_user( + username=fcd['username'], + password=fcd['password'], + email = fcd['email']) + u.save() + + o = Osoba( + jmeno = fcd['jmeno'], + prijmeni = fcd['prijmeni'], + pohlavi_muz = fcd['pohlavi_muz'], + email = fcd['email'], + telefon = fcd.get('telefon',''), + datum_narozeni = fcd.get('datum_narozeni',None), + datum_souhlasu_udaje = date.today(), + datum_registrace = date.today(), + ulice = fcd.get('ulice',''), + mesto = fcd.get('mesto',''), + psc = fcd.get('psc',''), + poznamka = str(fcd) + ) + if fcd.get('spam',False): + o.datum_souhlasu_zasilani = date.today() + if fcd.get('stat','') in ('CZ','SK'): + o.stat = fcd['stat'] + else: + # Unknown country - log it + msg = "Unknown country {}".format(fcd['stat_text']) + err_logger.warn(msg,form_hash=form_hash) + + o.save() + o.user = u + o.save() + + r = Resitel( + rok_maturity = fcd['rok_maturity'], + zasilat = fcd['zasilat'] + ) + + r.save() + r.osoba = o + if fcd.get('skola'): + r.skola = fcd['skola'] + else: + # Unknown school - log it + msg = "Unknown school {}, {}".format(fcd['skola_nazev'],fcd['skola_adresa']) + err_logger.warn(msg,form_hash=form_hash) + r.save() + + return HttpResponseRedirect('/thanks/') # if a GET (or any other method) we'll create a blank form else: - form = NameForm() + form = PrihlaskaForm() return render(request, 'seminar/prihlaska.html', {'form': form}) +class SkolaAutocomplete(autocomplete.Select2QuerySetView): + def get_queryset(self): + # Don't forget to filter out results depending on the visitor ! + qs = Skola.objects.all() + if self.q: + qs = qs.filter( + Q(nazev__istartswith=self.q)| + Q(kratky_nazev__istartswith=self.q)| + Q(ulice__istartswith=self.q)| + Q(mesto__istartswith=self.q)) + + return qs + +class LoginRequiredAjaxMixin(object): + def dispatch(self, request, *args, **kwargs): + #if request.is_ajax() and not request.user.is_authenticated: # Pokud to otevřu jako stránku, tak se omezení neuplatní, takže to asi nechceme + if not request.user.is_authenticated: + return JsonResponse(data={'results': [], 'pagination': {}}, status=401) + return super(LoginRequiredAjaxMixin, self).dispatch(request, *args, **kwargs) + +class ResitelAutocomplete(LoginRequiredAjaxMixin,autocomplete.Select2QuerySetView): + def get_queryset(self): + qs = Resitel.objects.all() + if self.q: + qs = qs.filter( + Q(osoba__jmeno__startswith=self.q)| + Q(osoba__prijmeni__startswith=self.q)| + Q(osoba__prezdivka__startswith=self.q) + ) + return qs + + # Ceka na autocomplete v3 # class OrganizatorAutocomplete(autocomplete.Select2QuerySetView): # def get_queryset(self): @@ -987,3 +1370,46 @@ def get_name(request): # Q(user__last_name__isstartswith=query)) # # return qs + +# FIXME: Tohle asi vlastně vůbec nepatří do aplikace 'seminar' +class LoginView(auth_views.LoginView): + # Jen vezmeme vestavěný a dáme mu vhodný template a přesměrovací URL + template_name = 'seminar/login.html' + + # Přesměrovací URL má být v kontextu: + def get_context_data(self, **kwargs): + ctx = super().get_context_data(**kwargs) + ctx['next'] = reverse('titulni_strana') + return ctx + +class LogoutView(auth_views.LogoutView): + # Jen vezmeme vestavěný a dáme mu vhodný template a přesměrovací URL + template_name = 'seminar/logout.html' + # Pavel: Vůbec nevím, proč to s _lazy funguje, ale bez toho to bylo rozbité. + next_page = reverse_lazy('titulni_strana') + +# "Chci resetovat heslo" +class PasswordResetView(auth_views.PasswordResetView): + #template_name = 'seminar/password_reset.html' + # TODO: vlastní email_template_name a subject_template_name a html_email_template_name + success_url = reverse_lazy('reset_password_done') + from_email = 'login@mam.mff.cuni.cz' + +# "Poslali jsme e-mail (pokud bylo kam))" +class PasswordResetDoneView(auth_views.PasswordResetDoneView): + #template_name = 'seminar/password_reset_done.html' + pass + +# "Vymysli si heslo" +class PasswordResetConfirmView(auth_views.PasswordResetConfirmView): + #template_name = 'seminar/password_confirm_done.html' + success_url = reverse_lazy('reset_password_complete') + +# "Heslo se asi změnilo." +class PasswordResetCompleteView(auth_views.PasswordResetCompleteView): + #template_name = 'seminar/password_complete_done.html' + pass + +class PasswordChangeView(auth_views.PasswordChangeView): + #template_name = 'seminar/password_change.html' + success_url = reverse_lazy('titulni_strana')