Browse Source

Merge remote-tracking branch 'origin/master' into stable

remotes/origin/temata v1.1
Bc. Petr Pecha 9 years ago
parent
commit
42993f688e
  1. 23
      Makefile
  2. 12
      mamweb/context_processors.py
  3. 84
      mamweb/middleware.py
  4. 2
      mamweb/settings_common.py
  5. 8
      mamweb/settings_prod.py
  6. 18
      mamweb/settings_test.py
  7. 20
      mamweb/static/css/mamweb.css
  8. BIN
      mamweb/static/images/header-bg-archiv-NOC.jpg
  9. BIN
      mamweb/static/images/header-bg-archiv.jpg
  10. BIN
      mamweb/static/images/header-bg-archiv.png
  11. BIN
      mamweb/static/images/header-bg-clanek-NOC.jpg
  12. BIN
      mamweb/static/images/header-bg-clanek.jpg
  13. BIN
      mamweb/static/images/header-bg-clanek.png
  14. BIN
      mamweb/static/images/header-bg-odevzdat-NOC.jpg
  15. BIN
      mamweb/static/images/header-bg-soustredeni-NOC.jpg
  16. BIN
      mamweb/static/images/header-bg-soustredeni.jpg
  17. BIN
      mamweb/static/images/header-bg-soustredeni.png
  18. BIN
      mamweb/static/images/header-bg-uvod-NOC.jpg
  19. BIN
      mamweb/static/images/header-bg-uvod.jpg
  20. BIN
      mamweb/static/images/header-bg-uvod.png
  21. BIN
      mamweb/static/images/header-bg-zadani-NOC.jpg
  22. BIN
      mamweb/static/images/header-bg-zadani.jpg
  23. BIN
      mamweb/static/images/header-bg-zadani.png
  24. BIN
      mamweb/static/images/header-bg.jpg
  25. BIN
      mamweb/static/images/header-bg.png
  26. 19
      mamweb/templates/400.html
  27. 19
      mamweb/templates/403.html
  28. 2
      mamweb/templates/base.html
  29. 1
      mamweb/templates/flatpages/default.html
  30. 2
      mamweb/urls.py
  31. 21
      mamweb/wsgi.py
  32. 26
      requirements.txt
  33. 38
      seminar/admin.py
  34. 8
      seminar/autocomplete_light_registry.py
  35. 20
      seminar/management/commands/auth.py
  36. 44
      seminar/migrations/0032_cislo_pdf_blank_typos.py
  37. 20
      seminar/migrations/0033_organizator_studuje_popisek.py
  38. 43
      seminar/models.py
  39. 41
      seminar/templates/seminar/archiv/rocnik.html
  40. 2
      seminar/templates/seminar/cojemam/organizatori.html
  41. 29
      seminar/templates/seminar/vysledkovka_rocnik.html
  42. 35
      seminar/templates/seminar/zadani/AktualniVysledkovka.html
  43. 2
      seminar/templates/seminar/zadani/AktualniZadani.html
  44. 1
      seminar/urls.py
  45. 126
      seminar/views.py

23
Makefile

@ -1,6 +1,6 @@
.PHONY: clean_env init_env clean_virtualenv install_packages clean install run all schema_seminar.pdf schema_all.pdf
PYTHON=python2.7
VE_VER=12.0.7
VE_VER=13.1.2
LOCAL_PYTHON=bin/python
all: install
@ -17,7 +17,7 @@ make_env: ${LOCAL_PYTHON}
# phony, but fast repeated execution
install_packages: make_env
bin/pip install -r requirements.txt
bin/pip install -r requirements.txt --upgrade
# phony
clean_env:
@ -35,12 +35,13 @@ ${LOCAL_PYTHON}: virtualenv
virtualenv:
curl -O https://pypi.python.org/packages/source/v/virtualenv/virtualenv-${VE_VER}.tar.gz
tar xvfz virtualenv-${VE_VER}.tar.gz
mv virtualenv-${VE_VER} virtualenv
mv -T virtualenv-${VE_VER} virtualenv
rm virtualenv-${VE_VER}.tar.gz
# phony
clean_virtualenv:
rm -rf virtualenv/
rm -rf virtualenv-*.tar.gz
run:
./manage.py runserver_plus
@ -60,21 +61,24 @@ schema_all.pdf:
# Deploy to current *mamweb-test* directory
deploy_test:
@if [[ `pwd` != "/akce/MaM/WWW/mamweb-test" ]]; then echo "Only possible in /akce/MaM/WWW/mamweb-test"; exit 1; fi
@if [ ${USER} != "www-mam" ]; then echo "Only possible by user www-mam"; exit 1; fi
@if [ `pwd` != "/akce/MaM/WWW/mamweb-test" ]; then echo "Only possible in /akce/MaM/WWW/mamweb-test"; exit 1; fi
@echo "Installing version from origin/master ..."
git pull origin master
git clean -f
make install
./manage.py migrate
./manage.py collectstatic --noinput
(chown -Rf :mam . || true )
(chmod -Rf g+w . || true )
(chown -R :mam . || true )
(chmod -R g+rX,go-w . || true )
@echo Notifying apache about the change ...
touch mamweb/wsgi.py
@echo Done.
# Deploy to current *mamweb-prod* directory
deploy_prod:
@if [[ `pwd` != "/akce/MaM/WWW/mamweb-prod" ]]; then echo "Only possible in /akce/MaM/WWW/mamweb-prod"; exit 1; fi
@if [ ${USER} != "www-mam" ]; then echo "Only possible by user www-mam"; exit 1; fi
@if [ `pwd` != "/akce/MaM/WWW/mamweb-prod" ]; then echo "Only possible in /akce/MaM/WWW/mamweb-prod"; exit 1; fi
@echo "Backing up production DB ..."
( cd .. && ./backup_prod_db.sh )
@echo "Installing version from origin/stable ..."
@ -83,8 +87,9 @@ deploy_prod:
make install
./manage.py migrate
./manage.py collectstatic --noinput
(chown -Rf :mam . || true )
(chmod -Rf g+w . || true )
(chown -R :mam . || true )
(chmod -R g+rX,go-w . || true )
@echo Notifying apache about the change ...
touch mamweb/wsgi.py
@echo Done.

12
mamweb/context_processors.py

@ -0,0 +1,12 @@
from datetime import datetime, date
def vzhled(request):
''' Podle casu prida do templatu, zdali je nebo neni noc '''
hodin = datetime.now().hour
if (hodin <= 6) or (hodin >= 20):
noc = True
else:
noc = False
return {'noc' : noc}

84
mamweb/middleware.py

@ -0,0 +1,84 @@
from datetime import datetime, date
from django.conf import settings
from django.http import HttpResponse, HttpResponseRedirect
class LoggedInHintCookieMiddleware(object):
"""Middleware to securely help with 'logged-in' detection for dual HTTP/HTTPS sites.
On insecure requests: Checks for a (non-secure) cookie settings.LOGGED_IN_HINT_COOKIE_NAME
and if present, redirects to HTTPS (same adress).
Note this usually breaks non-GET (POST) requests.
On secure requests: Updates cookie settings.LOGGED_IN_HINT_COOKIE_NAME to reflect
whether an user is logged in in the current session (cookie set to 'True' or cleared).
The cookie is set to expire at the same time as the sessionid cookie.
By default, LOGGED_IN_HINT_COOKIE_NAME = 'logged_in_hint'.
"""
def __init__(self):
if hasattr(settings, 'LOGGED_IN_HINT_COOKIE_NAME'):
self.cookie_name = settings.LOGGED_IN_HINT_COOKIE_NAME
else: self.cookie_name = 'logged_in_hint'
self.cookie_value = 'True'
def cookie_correct(self, request):
return self.cookie_name in request.COOKIES and request.COOKIES[self.cookie_name] == self.cookie_value
def process_request(self, request):
if not request.is_secure():
if self.cookie_correct(request):
# redirect insecure (assuming http) requests with hint cookie to https
url = HttpRequest.build_absolute_uri()
assert url[:5] == 'http:'
return HttpResponseRedirect('https:' + url[5:])
return None
def process_response(self, request, response):
if request.is_secure():
# assuming full session info (as the conn. is secure)
if request.user.is_authenticated():
if not self.cookie_correct(request):
expiry = None if request.session.get_expire_at_browser_close() else request.session.get_expiry_date()
response.set_cookie(self.cookie_name, value=self.cookie_value, expires=expiry, secure=False)
else:
if self.cookie_name in request.COOKIES:
response.delete_cookie(self.cookie_name)
return response
class vzhled:
def process_request(self, request):
return None
def process_view(self, request, view_func, view_args, view_kwargs):
#print "====== process_request ======"
#print view_func
#print view_args
#print view_kwargs
#print "============================="
return None
def process_template_response(self, request, response):
hodin = datetime.now().hour
if (hodin <= 6) or (hodin >= 14): # TODO 20
response.context_data['noc'] = True
else:
response.context_data['noc'] = False
return response
def process_response(self, request, response):
#hodin = datetime.now().hour
#if (hodin <= 6) or (hodin >= 14): # TODO 20
#response.context_data['noc'] = True
#else:
#response.context_data['noc'] = False
return response
##def process_exception(request, exception):
#pass

2
mamweb/settings_common.py

@ -60,6 +60,7 @@ MIDDLEWARE_CLASSES = (
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'mamweb.middleware.LoggedInHintCookieMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.auth.middleware.SessionAuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
@ -78,6 +79,7 @@ TEMPLATE_CONTEXT_PROCESSORS = (
'django.core.context_processors.tz',
'sekizai.context_processors.sekizai',
'django.core.context_processors.static',
'mamweb.context_processors.vzhled',
)
INSTALLED_APPS = (

8
mamweb/settings_prod.py

@ -47,12 +47,18 @@ import os
SERVER_EMAIL = 'mamweb-prod-errors@mam.mff.cuni.cz'
ADMINS = [
('Tomas Gavenciak', 'gavento@ucw.cz'),
('Petr Pecha', 'nejlepsitextovyeditorjevim@gmail.com'),
('Matěj Kocián', 'matej.kocian@gmail.com'),
]
# SECURITY: only send sensitive cookies via HTTPS
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True
# LOGGING = {
# 'version': 1,
# 'disable_existing_loggers': True,

18
mamweb/settings_test.py

@ -25,11 +25,11 @@ INSTALLED_APPS += (
SECRET_KEY = ')^u=i65*zmr_k53a*@f4q_+ji^o@!pgpef*5&8c7zzv9l+zo)n'
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True
DEBUG = False
TEMPLATE_DEBUG = True
TEMPLATE_DEBUG = False
ALLOWED_HOSTS = ['*.mam.mff.cuni.cz']
ALLOWED_HOSTS = ['*.mam.mff.cuni.cz', 'atrey.karlin.mff.cuni.cz', 'mam.mff.cuni.cz']
# Database
# https://docs.djangoproject.com/en/1.7/ref/settings/#databases
@ -45,6 +45,18 @@ DATABASES = {
},
}
import os
SERVER_EMAIL = 'mamweb-test-errors@mam.mff.cuni.cz'
ADMINS = [
('Petr Pecha', 'nejlepsitextovyeditorjevim@gmail.com'),
]
# SECURITY: only send sensitive cookies via HTTPS
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True

20
mamweb/static/css/mamweb.css

@ -58,16 +58,24 @@ h2 {
#header {
position: relative;
font-size: 250%;
background: url("../images/header-bg-uvod.png") no-repeat center top;
background: url("../images/header-bg-uvod.jpg") no-repeat center top;
height: 255px;
top: -1px;
}
#header.cojemam { background-image: url("../images/header-bg-uvod.png"); }
#header.soustredeni { background-image: url("../images/header-bg-soustredeni.png"); }
#header.zadani { background-image: url("../images/header-bg-zadani.png"); }
#header.clanky { background-image: url("../images/header-bg-clanek.png"); }
#header.archiv { background-image: url("../images/header-bg-archiv.png"); }
#header.cojemam { background-image: url("../images/header-bg-uvod.jpg"); }
#header.soustredeni { background-image: url("../images/header-bg-soustredeni.jpg"); }
#header.zadani { background-image: url("../images/header-bg-zadani.jpg"); }
#header.clanky { background-image: url("../images/header-bg-clanek.jpg"); }
#header.archiv { background-image: url("../images/header-bg-archiv.jpg"); }
#header.NOC {background-image: url("../images/header-bg-uvod-NOC.jpg"); }
#header.NOCcojemam { background-image: url("../images/header-bg-uvod-NOC.jpg"); }
#header.NOCsoustredeni { background-image: url("../images/header-bg-soustredeni-NOC.jpg"); }
#header.NOCzadani { background-image: url("../images/header-bg-zadani-NOC.jpg"); }
#header.NOCclanky { background-image: url("../images/header-bg-clanek-NOC.jpg"); }
#header.NOCarchiv { background-image: url("../images/header-bg-archiv-NOC.jpg"); }
#header img.logo {
position: absolute;

BIN
mamweb/static/images/header-bg-archiv-NOC.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

BIN
mamweb/static/images/header-bg-archiv.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

BIN
mamweb/static/images/header-bg-archiv.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 400 KiB

BIN
mamweb/static/images/header-bg-clanek-NOC.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

BIN
mamweb/static/images/header-bg-clanek.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

BIN
mamweb/static/images/header-bg-clanek.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 519 KiB

BIN
mamweb/static/images/header-bg-odevzdat-NOC.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

BIN
mamweb/static/images/header-bg-soustredeni-NOC.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

BIN
mamweb/static/images/header-bg-soustredeni.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 101 KiB

BIN
mamweb/static/images/header-bg-soustredeni.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 562 KiB

BIN
mamweb/static/images/header-bg-uvod-NOC.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

BIN
mamweb/static/images/header-bg-uvod.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

BIN
mamweb/static/images/header-bg-uvod.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 512 KiB

BIN
mamweb/static/images/header-bg-zadani-NOC.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

BIN
mamweb/static/images/header-bg-zadani.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

BIN
mamweb/static/images/header-bg-zadani.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 456 KiB

BIN
mamweb/static/images/header-bg.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 122 KiB

BIN
mamweb/static/images/header-bg.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 679 KiB

19
mamweb/templates/400.html

@ -0,0 +1,19 @@
{% extends "base.html" %}
{% load staticfiles %}
{% block content %}
<h2>
{% block nadpis1a %}{% block nadpis1b %}
O-jo-jo-jo-joj
{% endblock %}{% endblock %}
</h2>
<p>
Chybička se vloudila.
Zkuste přejít na <a href="/">titulní stránku</a>
nebo se podívat na <a href="/zadani/aktualni/">aktuální zadání</a>.
</p>
<img src="{% static '500.png' %}">
{% endblock %}

19
mamweb/templates/403.html

@ -0,0 +1,19 @@
{% extends "base.html" %}
{% load staticfiles %}
{% block content %}
<h2>
{% block nadpis1a %}{% block nadpis1b %}
Vrrrrrrrrr
{% endblock %}{% endblock %}
</h2>
<p>
Tady pravděpodobně nemáte co dělat.
Zkuste přejít na <a href="/">titulní stránku</a>
nebo se podívat na <a href="/zadani/aktualni/">aktuální zadání</a>.
</p>
<img src="{% static '500.png' %}">
{% endblock %}

2
mamweb/templates/base.html

@ -48,7 +48,7 @@
<div class='row'>
<div class='col-md-12'>
<a href='/'>
<div id="header" class="{% block header %}{% endblock %}">
<div id="header" class="{% if noc %}NOC{% endif %}{% block header %}{% endblock %}">
<img class="logo" src="{% static 'images/logo.png' %}" />
<!--<h1>{% block nadpis1b %}Nadpis 1. úrovně{% endblock %}</h1>-->
</div>

1
mamweb/templates/flatpages/default.html

@ -5,7 +5,6 @@
{% endblock %}{% endblock %}
{% block content %}
<div>
{{ flatpage.content }}
</div>

2
mamweb/urls.py

@ -19,8 +19,6 @@ urlpatterns = patterns('',
url(r'^comments_dj/', include('django_comments.urls')),
url(r'^comments_fl/', include('fluent_comments.urls')),
# Obsah - flatpages
url(r'^', include('django.contrib.flatpages.urls')), # Pozor: musi byt posledni
)
# This is only needed when using runserver.

21
mamweb/wsgi.py

@ -7,11 +7,22 @@ For more information on this file, see
https://docs.djangoproject.com/en/1.7/howto/deployment/wsgi/
"""
import sys, os, os.path
import sys, os, os.path, traceback, time, signal
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "mamweb.settings")
sys.path.append(os.path.join(os.path.dirname(__file__), '..'))
sys.path.append(os.path.join(os.path.dirname(__file__), '..', 'lib', 'python2.7', 'site-packages'))
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'lib', 'python2.7', 'site-packages')))
try:
from django.core.wsgi import get_wsgi_application
application = get_wsgi_application()
except Exception:
print 'handling WSGI exception'
# Error loading applications
if 'mod_wsgi' in sys.modules:
traceback.print_exc()
os.kill(os.getpid(), signal.SIGINT)
time.sleep(2)
from django.core.wsgi import get_wsgi_application
application = get_wsgi_application()

26
requirements.txt

@ -1,25 +1,27 @@
# basic libs
psycopg2==2.6
html5lib==0.999
ipython==3.0.0
Pillow==2.7.0
psycopg2==2.6.1
html5lib==0.9999999
ipython==4.0.0
Pillow==2.9.0
pytz==2014.10
six==1.9.0
pexpect==3.3
traitlets==4.0.0
# Django and modules
Django==1.7.8
Django==1.7.10 # Updatable to 1.8 (possibly incompatible)
django-bootstrap-sass==0.0.6a0
django-mptt==0.7.3
django-reversion==1.8.6
django-reversion==1.9.3
django-sekizai==0.8.1
django-countries==3.2
django-solo==1.1.0
django-ckeditor==4.4.7
django-ckeditor==4.4.7 # Updatable to 5.0 (some incompatible changes)
django-flat-theme==0.9.3
django-taggit==0.14.0
django-autocomplete-light==2.1.1
django-taggit==0.17
django-autocomplete-light==2.2.6
django-crispy-forms==1.4.0
# Comments
@ -30,9 +32,9 @@ django-contrib-comments==1.6.1
# debug tools/extensions
django-debug-toolbar==1.3.0
django-extensions==1.5.3
sqlparse==0.1.15
django-debug-toolbar==1.3.2
django-extensions==1.5.6
sqlparse==0.1.16
Werkzeug==0.10.4
# G+, FB authorisation

38
seminar/admin.py

@ -9,10 +9,26 @@ from ckeditor.widgets import CKEditorWidget
from django.db.models import Count
from django.db import models
from django.contrib.auth.models import User
from seminar.models import Skola, Resitel, Rocnik, Cislo, Problem, Reseni, PrilohaReseni, Nastaveni, Soustredeni, Soustredeni_Ucastnici, Novinky, Organizator
import autocomplete_light
class UserModelChoiceField(forms.ModelChoiceField):
u"""Vlastní ModelChoiceField pro uživatele. Zobrazí kromě loginu i jméno.
"""
def label_from_instance(self, obj):
return u"{} ({})".format(obj.get_full_name(), obj.username)
def get_form_predvypln_autora(self, request, obj=None, *args, **kwargs):
u"""get_form fce pro Adminy. Předvyplňí přihlášeného uživatele jako autora.
"""
form = super(self.__class__, self).get_form(request, *args, **kwargs)
form.base_fields['autor'].initial = request.user.id
return form
def make_set_action(atribut, hodnota, nazev):
u"""
Pomocnik pro rychle vytvareni hromadnych admin akci ktere jen nastavuji
@ -271,13 +287,14 @@ admin.site.register(Reseni, ReseniAdmin)
from autocomplete_light.contrib.taggit_field import TaggitField, TaggitWidget
#TODO: Autocomplete autor/opravovatel
class ProblemAdminForm(forms.ModelForm):
text_zadani = forms.CharField(widget=CKEditorWidget(), required=False, **field_labels(Problem, 'text_zadani'))
text_reseni = forms.CharField(widget=CKEditorWidget(), required=False, **field_labels(Problem, 'text_reseni'))
text_org = forms.CharField(widget=CKEditorWidget(), required=False, **field_labels(Problem, 'text_org'))
zamereni = TaggitField(widget=TaggitWidget('TagAutocomplete'), required=False)
autor = UserModelChoiceField(User.objects.filter(is_staff=True))
opravovatel = UserModelChoiceField(User.objects.filter(is_staff=True), required=False)
class Meta:
model = Problem
exclude = []
@ -302,13 +319,16 @@ class ProblemAdmin(reversion.VersionAdmin):
return obj.pocet_reseni
class ProblemNavrhAdmin(ProblemAdmin):
list_display = ['nazev', 'typ', 'stav', 'autor', 'timestamp']
list_filter = ['typ', 'stav', 'timestamp']
list_display = ['nazev', 'typ', 'zamereni', 'stav', 'autor', 'timestamp']
list_filter = ['typ', 'zamereni', 'timestamp', 'stav']
def get_queryset(self, request):
qs = super(ProblemNavrhAdmin, self).get_queryset(request)
return qs.filter(stav__in=[Problem.STAV_NAVRH, Problem.STAV_SMAZANY])
get_form = get_form_predvypln_autora
create_modeladmin(ProblemNavrhAdmin, Problem, 'ProblemNavrh', verbose_name=u'Problém (návrh)', verbose_name_plural=u'Problémy (návrhy)')
class ProblemZadanyAdmin(ProblemAdmin):
@ -320,6 +340,8 @@ class ProblemZadanyAdmin(ProblemAdmin):
qs = super(ProblemZadanyAdmin, self).get_queryset(request)
return qs.filter(stav=Problem.STAV_ZADANY).annotate(pocet_reseni=Count('reseni'))
get_form = get_form_predvypln_autora
create_modeladmin(ProblemZadanyAdmin, Problem, 'ProblemZadany', verbose_name=u'Problém (zadaný)', verbose_name_plural=u'Problémy (zadané)')
#admin.site.register(Problem, ProblemAdmin)
@ -356,6 +378,8 @@ admin.site.register(Soustredeni, SoustredeniAdmin)
class NovinkyAdminForm(forms.ModelForm):
text = forms.CharField(widget=CKEditorWidget(), required=False,
**field_labels(Novinky, 'text'))
autor = UserModelChoiceField(User.objects.filter(is_staff=True))
class Meta:
model = Novinky
exclude = []
@ -380,13 +404,7 @@ class NovinkyAdmin(admin.ModelAdmin):
list_display = ['datum', 'autor', 'text', 'zverejneno', 'obrazek']
actions = [zverejnit_novinky, zneverejnit_novinky]
# předvyplnění přihlášeného uživatele jako autora novinky
def formfield_for_foreignkey(self, db_field, request, **kwargs):
if db_field.name == 'autor':
kwargs['initial'] = request.user.id
return super(NovinkyAdmin, self).formfield_for_foreignkey(
db_field, request, **kwargs
)
get_form = get_form_predvypln_autora
admin.site.register(Novinky, NovinkyAdmin)

8
seminar/autocomplete_light_registry.py

@ -77,7 +77,13 @@ class ProblemAutocomplete(autocomplete_light.AutocompleteModelBase):
def choice_label(self, p):
if p.stav == Problem.STAV_ZADANY:
return u"%s (%s, %s.%s)" % (p.nazev, p.typ, p.cislo_zadani.rocnik.rocnik, p.kod_v_rocniku())
popisek = ""
try:
popisek = u"%s (%s, %s.%s)" % (p.nazev, p.typ, p.cislo_zadani.rocnik.rocnik, p.kod_v_rocniku())
except:
#popisek = u"%s (%s, %s.%s)" % (p.nazev, p.typ, p.stav)
popisek = "CHYBA"
return popisek
else:
return u"%s (%s, %s)" % (p.nazev, p.typ, p.stav)

20
seminar/management/commands/auth.py

@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
from django.core.management.base import NoArgsCommand
from django.contrib.sessions.models import Session
from django.contrib.auth.models import User
class Command(NoArgsCommand):
u"""Vypiš username přihlášeného orga s daným session_key.
Příkaz pro manage.py, který ze vstupu přečte session_key (tak, jak je
uložen v cookie sessionid) a pokud session existuje a příslušný přihlášený
uživatel právo přihlásit se do admina, vypíše jeho username.
"""
def handle_noargs(self, **options):
session_key = raw_input()
s = Session.objects.get(pk=session_key).get_decoded()
user_id = s['_auth_user_id']
user = User.objects.get(pk=user_id)
if user.is_staff:
print(user.username)

44
seminar/migrations/0032_cislo_pdf_blank_typos.py

@ -0,0 +1,44 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
import django_countries.fields
import seminar.models
class Migration(migrations.Migration):
dependencies = [
('seminar', '0031_cislo_pdf'),
]
operations = [
migrations.AlterModelOptions(
name='soustredeni',
options={'ordering': ['-rocnik__rocnik', '-datum_zacatku'], 'verbose_name': 'Soust\u0159ed\u011bn\xed', 'verbose_name_plural': 'Soust\u0159ed\u011bn\xed'},
),
migrations.AlterField(
model_name='cislo',
name='cislo',
field=models.CharField(help_text='V\u011bt\u0161inou jen "1", vyj\xedme\u010dn\u011b "7-8", lexikograficky ur\u010duje po\u0159ad\xed v ro\u010dn\xedku!', max_length=32, verbose_name='n\xe1zev \u010d\xedsla', db_index=True),
preserve_default=True,
),
migrations.AlterField(
model_name='cislo',
name='pdf',
field=models.FileField(help_text='Pdf \u010d\xedsla, kter\xe9 si mohou \u0159e\u0161itel\xe9 st\xe1hnout', upload_to=seminar.models.cislo_pdf_filename, null=True, verbose_name='pdf', blank=True),
preserve_default=True,
),
migrations.AlterField(
model_name='problem',
name='typ',
field=models.CharField(default=b'uloha', max_length=32, verbose_name='typ probl\xe9mu', choices=[(b'uloha', '\xdaloha'), (b'tema', 'T\xe9ma'), (b'serial', 'Seri\xe1l'), (b'org-clanek', 'Organiz\xe1torsk\xfd \u010dl\xe1nek'), (b'res-clanek', '\u0158e\u0161itelsk\xfd \u010dl\xe1nek')]),
preserve_default=True,
),
migrations.AlterField(
model_name='skola',
name='stat',
field=django_countries.fields.CountryField(default=b'CZ', help_text='ISO 3166-1 k\xf3d zem\u011b velk\xfdmi p\xedsmeny (CZ, SK, ...)', max_length=2, verbose_name='st\xe1t'),
preserve_default=True,
),
]

20
seminar/migrations/0033_organizator_studuje_popisek.py

@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
class Migration(migrations.Migration):
dependencies = [
('seminar', '0032_cislo_pdf_blank_typos'),
]
operations = [
migrations.AlterField(
model_name='organizator',
name='studuje',
field=models.CharField(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.', blank=True),
preserve_default=True,
),
]

43
seminar/models.py

@ -83,7 +83,7 @@ class Skola(SeminarModelBase):
kratky_nazev = models.CharField(u'zkrácený název', max_length=256, blank=True,
help_text="Zkrácený název pro zobrazení ve výsledkovce")
# Ulice může být jen číslo
# Ulice může být jen číslo
ulice = models.CharField(u'ulice', max_length=256)
mesto = models.CharField(u'město', max_length=256)
@ -93,7 +93,7 @@ class Skola(SeminarModelBase):
# ISO 3166-1 dvojznakovy kod zeme velkym pismem (CZ, SK)
# Ekvivalentní s CharField(max_length=2, default='CZ', ...)
stat = CountryField(u'stát', default='CZ',
help_text=u'ISO 3166-1 kód zeme velkými písmeny (CZ, SK, ...)')
help_text=u'ISO 3166-1 kód země velkými písmeny (CZ, SK, ...)')
# Jaké vzdělání škpla poskytuje?
je_zs = models.BooleanField(u'základní stupeň', default=True)
@ -161,7 +161,7 @@ class Resitel(SeminarModelBase):
]
zasilat = models.CharField(u'kam zasílat', max_length=32, choices=ZASILAT_CHOICES, blank=False, default=ZASILAT_DOMU)
# Ulice může být i jen číslo
# Ulice může být i jen číslo
ulice = models.CharField(u'ulice', max_length=256, blank=True, default='')
mesto = models.CharField(u'město', max_length=256, blank=True, default='')
@ -325,7 +325,7 @@ class Cislo(SeminarModelBase):
rocnik = models.ForeignKey(Rocnik, verbose_name=u'ročník', related_name='cisla', db_index=True)
cislo = models.CharField(u'název čísla', max_length=32, db_index=True,
help_text=u'Většinou jen "1", vyjímečně "7-8", lexikograficky určije pořadí v ročníku!')
help_text=u'Většinou jen "1", vyjímečně "7-8", lexikograficky určuje pořadí v ročníku!')
datum_vydani = models.DateField(u'datum vydání', blank=True, null=True,
help_text=u'Datum vydání finální verze')
@ -349,7 +349,7 @@ class Cislo(SeminarModelBase):
kod.short_description = u'Kód čísla'
def __str__(self):
# Potenciální DB HOG, pokud by se ročník neckešoval
# Potenciální DB HOG, pokud by se ročník necachoval
r = Rocnik.cached_rocnik(self.rocnik_id)
return force_unicode(u'%s.%s' % (r.rocnik, self.cislo, ))
@ -403,7 +403,7 @@ class Problem(SeminarModelBase):
(TYP_TEMA, u'Téma'),
(TYP_SERIAL, u'Seriál'),
(TYP_ORG_CLANEK, u'Organizátorský článek'),
(TYP_RES_CLANEK, u'Řesitelský článek'),
(TYP_RES_CLANEK, u'Řešitelský článek'),
]
typ = models.CharField(u'typ problému', max_length=32, choices=TYP_CHOICES, blank=False, default=TYP_ULOHA)
@ -447,18 +447,19 @@ class Problem(SeminarModelBase):
# Staré (do 2014) ID problému z DAKOSU -- jen u importovaných záznamů
import_dakos_id = models.CharField(u'importované ID s typem', max_length=32, blank=True, default='',
help_text=(u'ID z importu z DAKOSU s prefixem podle původu: "AZAD:xxx (MAMOPER.MM_AZAD), "' +
help_text=(u'ID z importu z DAKOSU s prefixem podle původu: "AZAD:xxx (MAMOPER.MM_AZAD), "' +
u'"DOZ:xxx" (MAMOPER.MM_DOZ), "ZAD:rocnik.cislo.uloha.typ" (MAMOPER.MM_ZADANIA), "ULOHA:xxx" (MAMOPER.MM_ULOHY)'))
def __str__(self):
return force_unicode(u'%s' % (self.nazev, ))
def kod_v_rocniku(self):
if self.typ == self.TYP_ULOHA:
return force_unicode(u"%s.u%s" % (self.cislo_zadani.cislo, self.kod,))
if self.typ == self.TYP_TEMA:
return force_unicode(u"t%s" % (self.kod,))
return ''
if self.stav == 'zadany':
if self.typ == self.TYP_ULOHA:
return force_unicode(u"%s.u%s" % (self.cislo_zadani.cislo, self.kod,))
if self.typ == self.TYP_TEMA:
return force_unicode(u"t%s" % (self.kod,))
return ' Není zadaný '
def nazev_typu(self):
return dict(self.TYP_CHOICES)[self.typ]
@ -771,8 +772,11 @@ class Organizator(models.Model):
null = True, blank = True)
organizuje_do_roku = models.IntegerField('Organizuje do roku',
null = True, blank = True)
studuje = models.CharField('Studuje', max_length = 256,
null = True, blank = True)
studuje = models.CharField('Studium aj.', max_length = 256,
null = True, 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'")
strucny_popis_organizatora = models.TextField('Stručný popis organizátora',
null = True, blank = True)
foto = models.ImageField('Fotografie organizátora',
@ -789,11 +793,14 @@ class Organizator(models.Model):
verbose_name_plural = 'Organizátoři'
def save(self):
if self.id is not None:
puvodni = Organizator.objects.get(id=self.id)
if self.foto:
original = Image.open(self.foto)
jmeno = os.path.basename(self.foto.file.name)
Organizator._vyrobMiniaturu(original, jmeno, 500, self.foto)
Organizator._vyrobMiniaturu(original, jmeno, 200, self.foto_male)
if not puvodni or puvodni.foto != self.foto:
original = Image.open(self.foto)
jmeno = os.path.basename(self.foto.file.name)
Organizator._vyrobMiniaturu(original, jmeno, 500, self.foto)
Organizator._vyrobMiniaturu(original, jmeno, 200, self.foto_male)
super(Organizator, self).save()
@staticmethod

41
seminar/templates/seminar/archiv/rocnik.html

@ -29,38 +29,17 @@
{% endif %}
{% if vysledkovka %}
<h3>Výsledkovka</h3>
<table class='vysledkovka'>
<tr class='border-b'>
<th class='border-r'>#
<th class='border-r'>Jméno
<th class='border-r'>R.
<th class='border-r'>Odjakživa
{% for c in rocnik.verejna_cisla %}
{% if c.verejna_vysledkovka %}
<th class='border-r'><a href="{{ c.verejne_url }}">
{{c.rocnik.rocnik}}.{{ c.cislo }}</a>
{% endif %}
{% endfor %}
<th class='border-r'>Celkem
<h3>Výsledková listina</h3>
{% include "seminar/vysledkovka_rocnik.html" %}
{% endif %}
{% for rv in vysledkovka %}
<tr>
<td class='border-r'>{% autoescape off %}{{ rv.poradi }}{% endautoescape %}
<th class='border-r'>
{% if rv.titul %}
{{ rv.titul }}<sup>MM</sup>
{% endif %}
{{ rv.resitel.plne_jmeno }}
<td class='border-r'>{{ rv.resitel.rocnik }}
<td class='border-r'>{{ rv.body_odjakziva }}
{% for b in rv.body_cisla %}
<td class='border-r'>{{ b }}
{% endfor %}
<td class='border-r'><b>{{ rv.body_rocnik }}</b>
</tr>
{% endfor %}
</table>
{% if user.is_staff and vysledkovka_s_neverejnymi %}
<div class='mam-org-only'>
<h3>Výsledková listina včetně neveřejných bodů</h3>
{% with vysledkovka_s_neverejnymi as vysledkovka %}
{% include "seminar/vysledkovka_rocnik.html" %}
{% endwith %}
</div>
{% endif %}
</div>

2
seminar/templates/seminar/cojemam/organizatori.html

@ -53,7 +53,7 @@
Aktivní v letech {{org.organizuje_od_roku | default:"?" }}&ndash;{{org.organizuje_do_roku | default:"?" }}
{% endif %}
{% if org.studuje %}
<li>Studuje: {{org.studuje}}
<li>{{org.studuje}}
{% endif %}
{% if org.user.email %}
<li>Pošta:

29
seminar/templates/seminar/vysledkovka_rocnik.html

@ -0,0 +1,29 @@
<table class='vysledkovka'>
<tr class='border-b'>
<th class='border-r'>#
<th class='border-r'>Jméno
<th class='border-r'>R.
<th class='border-r'>Odjakživa
{% for c in vysledkovka.cisla %}
<th class='border-r'><a href="{{ c.verejne_url }}">
{{c.rocnik.rocnik}}.{{ c.cislo }}</a>
{% endfor %}
<th class='border-r'>Celkem
{% for rv in vysledkovka.radky %}
<tr>
<td class='border-r'>{% autoescape off %}{{ rv.poradi }}{% endautoescape %}
<th class='border-r'>
{% if rv.titul %}
{{ rv.titul }}<sup>MM</sup>
{% endif %}
{{ rv.resitel.plne_jmeno }}
<td class='border-r'>{{ rv.resitel.rocnik }}
<td class='border-r'>{{ rv.body_odjakziva }}
{% for b in rv.body_cisla %}
<td class='border-r'>{{ b }}
{% endfor %}
<td class='border-r'><b>{{ rv.body_rocnik }}</b>
</tr>
{% endfor %}
</table>

35
seminar/templates/seminar/zadani/AktualniVysledkovka.html

@ -0,0 +1,35 @@
{% extends "seminar/zadani/base.html" %}
{% block submenu %}
{% with "vysledkova-listina" as selected %}
{% include 'seminar/zadani/submenu.html' %}
{% endwith %}
{% endblock submenu %}
{% block content %}
{% with nastaveni.aktualni_rocnik as rocnik %}
<h2>
{% block nadpis1a %}{% block nadpis1b %}
Výsledky
{% endblock %}{% endblock %}
</h2>
{% if vysledkovka %}
{% include "seminar/vysledkovka_rocnik.html" %}
{% else %}
V tomto ročníku zatím žádné výsledky nejsou
{% endif %}
{% if user.is_staff and vysledkovka_s_neverejnymi %}
<div class='mam-org-only'>
<h2>Výsledky včetně neveřejných</h2>
{% with vysledkovka_s_neverejnymi as vysledkovka %}
{% include "seminar/vysledkovka_rocnik.html" %}
{% endwith %}
</div>
{% endif %}
{% endwith %}
{% endblock content %}

2
seminar/templates/seminar/zadani/AktualniZadani.html

@ -18,8 +18,6 @@
{% if ac.zadane_problemy.all %}
<div class="zadani_azad_termin">
Termín odeslání {{ac.cislo}}. série: {{ac.datum_deadline}}
<br>
Termín odeslání 1. série pro účast na soustředění: 21. září 2015
</div>
{% endif %}
{#TODO a co speciální deadline pro účast na soustředění? #}

1
seminar/urls.py

@ -19,6 +19,7 @@ urlpatterns = patterns('',
url(r'^zadani/aktualni/$', views.AktualniZadaniView, name='seminar_aktualni_zadani'),
url(r'^zadani/temata/$', views.ZadaniTemataView, name='seminar_temata'),
url(r'^zadani/vysledkova-listina/$', views.ZadaniAktualniVysledkovkaView, name='seminar_vysledky'),
url(r'^$', views.TitulniStranaView.as_view(), name='titulni_strana'),
url(r'^stare-novinky/$', views.StareNovinkyView.as_view(), name='stare_novinky'),

126
seminar/views.py

@ -15,17 +15,22 @@ from datetime import timedelta, date, datetime
from itertools import groupby
def verejna_temata(rocnik):
"""Vrací queryset zveřejněných témat v daném ročníku.
"""
return Problem.objects.filter(typ=Problem.TYP_TEMA, cislo_zadani__rocnik=rocnik, cislo_zadani__verejne_db=True).order_by('kod')
def AktualniZadaniView(request):
nastaveni = get_object_or_404(Nastaveni)
problemy = Problem.objects.filter(cislo_zadani=nastaveni.aktualni_cislo).filter(stav = 'zadany')
ulohy = problemy.filter(typ = 'uloha').order_by('kod')
serialy = problemy.filter(typ = 'serial').order_by('kod')
temata = problemy.filter(typ = 'tema').order_by('kod')
jednorazove_problemy = [ulohy, serialy]
return render(request, 'seminar/zadani/AktualniZadani.html',
{'nastaveni': nastaveni,
'jednorazove_problemy': jednorazove_problemy,
'temata': temata,
'temata': verejna_temata(nastaveni.aktualni_rocnik),
},
)
@ -33,10 +38,23 @@ def ZadaniTemataView(request):
nastaveni = get_object_or_404(Nastaveni)
return render(request, 'seminar/zadani/Temata.html',
{
'temata': Problem.objects.filter(typ=Problem.TYP_TEMA, stav=Problem.STAV_ZADANY, cislo_zadani__rocnik=nastaveni.aktualni_rocnik).order_by('kod'),
'temata': verejna_temata(nastaveni.aktualni_rocnik)
}
)
def ZadaniAktualniVysledkovkaView(request):
nastaveni = get_object_or_404(Nastaveni)
vysledkovka = vysledkovka_rocniku(nastaveni.aktualni_rocnik)
vysledkovka_s_neverejnymi = vysledkovka_rocniku(nastaveni.aktualni_rocnik, jen_verejne=False)
return render(request, 'seminar/zadani/AktualniVysledkovka.html',
{
'nastaveni': nastaveni,
'vysledkovka': vysledkovka,
'vysledkovka_s_neverejnymi': vysledkovka_s_neverejnymi,
}
)
### Titulni strana
class TitulniStranaView(generic.ListView):
@ -110,6 +128,63 @@ def sloupec_s_poradim(vysledky):
return poradi_l
def vysledkovka_rocniku(rocnik, jen_verejne=True):
"""Přebírá ročník (např. context["rocnik"]) a vrací výsledkovou listinu ve
formě vhodné pro šablonu "seminar/vysledkovka_rocniku.html"
"""
#vyberu vsechny vysledky z rocniku
cisla_v_rocniku = VysledkyKCisluZaRocnik.objects.filter(cislo__rocnik=rocnik).order_by('cislo')
if jen_verejne:
cisla_v_rocniku = cisla_v_rocniku.filter(cislo__verejna_vysledkovka=True)
#pokud žádné nejsou, výsledkovka se nezobrazí
if not cisla_v_rocniku:
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'))
class Vysledkovka:
def __init__(self):
self.radky = []
self.cisla = []
vysledkovka = Vysledkovka()
vysledkovka.cisla = (rocnik.verejne_vysledkovky_cisla() if jen_verejne else rocnik.cisla.all().order_by('cislo'))
# doplníme některé údaje do řádků výsledkovky pro řešitele ve skupině
for poradi, v in zip(sloupec_s_poradim(vysledky), 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)
if jen_verejne:
verejne_vysl_odjakziva = verejne_vysl_odjakziva.filter(cislo__verejna_vysledkovka=True)
v.body_odjakziva = verejne_vysl_odjakziva.filter(resitel = v.resitel)[0].body
v.titul = v.resitel.get_titul(v.body_odjakziva)
v.body_rocnik = v.body
v.body_cisla = []
#pokud pro dany rok a cislo nema resitel vysledky, ma defaultne 0
for cis in vysledkovka.cisla:
if not jen_verejne or cis.verejna_vysledkovka:
#seznam vysledku se spravnym rocnikem a cislem pro resitele
#zobrazim jen je-li vysledkovka verejna
body_za_cislo = VysledkyZaCislo.objects.filter(cislo__rocnik=rocnik).filter(cislo = cis).filter(resitel = v.resitel)
if body_za_cislo:
#neprazdne vysledky by mely obsahovat prave jeden vysledek
v.body_cisla.append(body_za_cislo[0].body)
else:
#resitel nema za cislo body
v.body_cisla.append(0)
vysledkovka.radky.append(v)
return vysledkovka
class RocnikView(generic.DetailView):
model = Rocnik
template_name = 'seminar/archiv/rocnik.html'
@ -131,46 +206,9 @@ class RocnikView(generic.DetailView):
def get_context_data(self, **kwargs):
context = super(RocnikView, self).get_context_data(**kwargs)
cisla_v_rocniku = VysledkyKCisluZaRocnik.objects.filter(cislo__verejna_vysledkovka = True).filter(cislo__rocnik = context['rocnik']).order_by('cislo')
#vyberu vsechny verejne vysledky z rocniku
#pokud žádné nejsou, výsledkovka se nezobrazí
if cisla_v_rocniku:
vysledky = list(cisla_v_rocniku.filter(cislo = cisla_v_rocniku[0].cislo).order_by('-body', 'resitel__prijmeni', 'resitel__jmeno').select_related('resitel'))
#vybere vsechny vysledky z posledniho verejneho cisla a setridi sestupne dle bodu
vysledkovka = []
# doplníme některé údaje do řádků výsledkovky pro řešitele ve skupině
for poradi, v in zip(sloupec_s_poradim(vysledky), vysledky):
v.poradi = poradi
v.resitel.rocnik = v.resitel.rocnik(context['rocnik'])
verejne_vysl_odjakziva = VysledkyKCisluOdjakziva.objects.filter(cislo__verejna_vysledkovka = True).filter(cislo__rocnik = context['rocnik']).filter(cislo = cisla_v_rocniku[0].cislo)
v.body_odjakziva = verejne_vysl_odjakziva.filter(resitel = v.resitel)[0].body
v.titul = v.resitel.get_titul(v.body_odjakziva)
v.body_rocnik = v.body
v.body_cisla = []
#pokud pro dany rok a cislo nema resitel vysledky, ma defaultne 0
for cis in context['rocnik'].verejna_cisla():
if cis.verejna_vysledkovka:
body_za_cislo = VysledkyZaCislo.objects.filter(cislo__rocnik = context['rocnik']).filter(cislo = cis).filter(resitel = v.resitel)
#seznam vysledku se spravnym rocnikem a cislem pro resitele
#zobrazim jen je-li vysledkovka verejna
if body_za_cislo:
v.body_cisla.append(body_za_cislo[0].body)
#neprazdne vysledky by mely obsahovat prave jeden vysledek
else:
v.body_cisla.append(0)
#resitel nema za cislo body
vysledkovka.append(v)
context['vysledkovka'] = vysledkovka
temata_v_rocniku = Problem.objects.filter(typ=Problem.TYP_TEMA, cislo_zadani__rocnik=context['rocnik']).order_by('kod')
context['temata_v_rocniku'] = temata_v_rocniku
context['vysledkovka'] = vysledkovka_rocniku(context["rocnik"])
context['vysledkovka_s_neverejnymi'] = vysledkovka_rocniku(context["rocnik"], jen_verejne=False)
context['temata_v_rocniku'] = verejna_temata(context["rocnik"])
return context
@ -227,7 +265,7 @@ class CisloView(generic.DetailView):
resene_problemy = Problem.objects.filter(cislo_reseni=context['cislo']).filter(typ__in=typy_skutecne_zadanych).order_by('cislo_zadani__cislo', 'kod')
problemy = sorted(list(set([r.problem for r in reseni])), key=lambda x:(0 if x.typ==Problem.TYP_ULOHA else 1, x.kod_v_rocniku))
problemy = sorted(set(r.problem for r in reseni), key=lambda x:(0 if x.typ==Problem.TYP_ULOHA else 1, x.kod_v_rocniku()))
#setridi problemy podle typu a poradi zadani
problem_index = {}
for i in range(len(problemy)):

Loading…
Cancel
Save