Browse Source

Merge branch 'master' of atrey.karlin.mff.cuni.cz:/akce/MaM/MaMweb/mamweb

remotes/origin/vysl deploy-test-2015-07-05-21-52-xlfd
Jan Musílek 10 years ago
parent
commit
78f97835d1
  1. 2
      Makefile
  2. 12
      dakosdump/README.md
  3. 2
      mamweb/settings_common.py
  4. 2
      mamweb/templates/menu.html
  5. 2
      mamweb/urls.py
  6. 74
      seminar/admin.py
  7. 148
      seminar/export.py
  8. 32
      seminar/migrations/0019_rocnik_ciselne.py
  9. 44
      seminar/migrations/0020_indexy_a_razeni.py
  10. 20
      seminar/migrations/0021_cislo_verejna_vysledkovka.py
  11. 30
      seminar/migrations/0022_decimal_body.py
  12. 32
      seminar/migrations/0023_add_novinky.py
  13. 35
      seminar/migrations/0024_add_organizator.py
  14. 20
      seminar/migrations/0025_zmena_cesty_nahravani_obrazku.py
  15. 157
      seminar/models.py
  16. 15
      seminar/ovvpfile.py
  17. 10
      seminar/templates/seminar/archiv/cislo.html
  18. 36
      seminar/templates/seminar/cojemam/organizatori.html
  19. 2
      seminar/templates/seminar/export_index.csv
  20. 11
      seminar/templates/seminar/export_rocnik.csv
  21. 45
      seminar/templates/seminar/soustredeni/seznam_soustredeni.html
  22. 17
      seminar/tests.py
  23. 10
      seminar/testutils.py
  24. 10
      seminar/urls.py
  25. 63
      seminar/views.py

2
Makefile

@ -86,7 +86,7 @@ push_test:
(chown -Rf :mam . || true ) && \
(chmod -Rf g+w . || true ) && \
echo 'Reloading apache ... (You may have to start it manually on error!)' && \
~/etc/apache2/apache2ctl -k reload && \
~/etc/apache2/apache2ctl -k restart && \
echo Done."
@echo "Test pushed to ${TEST_SERVER}:${TEST_DIR} successfully."

12
dakosdump/README.md

File diff suppressed because one or more lines are too long

2
mamweb/settings_common.py

@ -25,7 +25,7 @@ APPEND_SLASH = False
LANGUAGE_CODE = 'cs'
TIME_ZONE = 'Europe/Prague'
USE_I18N = False
USE_I18N = True
USE_L10N = True
USE_TZ = True

2
mamweb/templates/menu.html

@ -3,7 +3,7 @@
<ul>
<li><a href="/co-je-MaM/uvod/">Co je M&amp;M</a>
<li><a href="/soustredeni/uvod/">Soustředění</a>
<li><a href="{% url 'seminar_seznam_soustredeni' %}">Soustředění</a>
<li><a href="/zadani/aktualni/">Zadání</a>
<li><a href="/clanky/uvod/">Články</a>
<li><a href="/archiv/cisla/">Archiv</a>

2
mamweb/urls.py

@ -5,7 +5,7 @@ from django.contrib import admin
from django.conf import settings
from django.views.generic.base import TemplateView
urlpatterns = i18n_patterns('',
urlpatterns = patterns('',
# Admin a nastroje
url(r'^admin/', include(admin.site.urls)), # NOQA

74
seminar/admin.py

@ -8,7 +8,7 @@ from ckeditor.widgets import CKEditorWidget
from django.db.models import Count
from django.db import models
from seminar.models import Skola, Resitel, Rocnik, Cislo, Problem, Reseni, PrilohaReseni, Nastaveni, Soustredeni, Soustredeni_Ucastnici
from seminar.models import Skola, Resitel, Rocnik, Cislo, Problem, Reseni, PrilohaReseni, Nastaveni, Soustredeni, Soustredeni_Ucastnici, Novinky, Organizator
import autocomplete_light
@ -177,10 +177,11 @@ admin.site.register(Skola, SkolaAdmin)
class CisloAdmin(reversion.VersionAdmin):
fieldsets = [
(None, {'fields': ['cislo', 'rocnik', 'verejne_db', 'poznamka']}),
(None, {'fields': ['cislo', 'rocnik', 'verejne_db', 'verejna_vysledkovka', 'poznamka']}),
(u'Data', {'fields': ['datum_vydani', 'datum_deadline']}),
]
list_display = ['kod', 'rocnik', 'cislo', 'datum_vydani', 'datum_deadline', 'verejne']
list_display = ['kod', 'rocnik', 'cislo', 'datum_vydani', 'datum_deadline', 'verejna_vysledkovka']
list_filter = ['rocnik']
view_on_site = Cislo.verejne_url
def get_queryset(self, request):
@ -284,7 +285,7 @@ create_modeladmin(ProblemNavrhAdmin, Problem, 'ProblemNavrh', verbose_name=u'Pro
class ProblemZadanyAdmin(ProblemAdmin):
list_display = ['nazev', 'typ', 'autor', 'opravovatel', 'kod', 'cislo_zadani', 'pocet_reseni', 'verejne']
list_filter = ['typ', 'cislo_zadani__rocnik']
list_filter = ['typ', 'cislo_zadani__cislo', 'cislo_zadani__rocnik']
inlines = [ReseniKProblemuInline]
def get_queryset(self, request):
@ -297,6 +298,18 @@ create_modeladmin(ProblemZadanyAdmin, Problem, 'ProblemZadany', verbose_name=u'P
### Soustredeni
def zverejnit_soustredeni(modeladmin, request, queryset):
for soustredeni in queryset:
soustredeni.verejne_db = True
soustredeni.save()
zverejnit_soustredeni.short_description = 'Zveřejnit soustředění'
def skryt_soustredeni(modeladmin, request, queryset):
for soustredeni in queryset:
soustredeni.verejne_db = False
soustredeni.save()
skryt_soustredeni.short_description = 'Skrýt soustředění (Zneveřjnit)'
class SoustredeniAdminForm(forms.ModelForm):
text = forms.CharField(widget=CKEditorWidget(), required=False, **field_labels(Soustredeni, 'text'))
class Meta:
@ -313,6 +326,61 @@ class SoustredeniAdmin(reversion.VersionAdmin):
inlines = [Soustredeni_UcastniciInline]
list_filter = ['rocnik']
view_on_site = Soustredeni.verejne_url
actions = [zverejnit_soustredeni, skryt_soustredeni, ]
admin.site.register(Soustredeni, SoustredeniAdmin)
### Novinky
class NovinkyAdminForm(forms.ModelForm):
text = forms.CharField(widget=CKEditorWidget(), required=False,
**field_labels(Novinky, 'text'))
class Meta:
model = Novinky
exclude = []
class NovinkyAdmin(admin.ModelAdmin):
form = NovinkyAdminForm
admin.site.register(Novinky, NovinkyAdmin)
### Organizator
def jmeno_organizatora(obj):
''' vraci jmeno organizatora '''
jmeno = obj.user.first_name
if obj.prezdivka:
jmeno = jmeno + ' "' + obj.prezdivka + '"'
jmeno = jmeno + ' ' + obj.user.last_name
if jmeno == ' ': # zobrazeni bezejmennych orgu
return 'org'
return jmeno
jmeno_organizatora.short_description = 'Jméno organizátora'
def je_organizator_aktivni(obj):
''' zjisti, zda-li je organizator aktivni '''
return obj.user.is_active
je_organizator_aktivni.short_description = 'Aktivní'
je_organizator_aktivni.boolean = True
def zaktivovat_organizatory(modeladmin, request, queryset):
''' vybrane organizatory oznaci jako aktivni '''
for org in queryset:
org.user.is_active = True
org.user.save()
zaktivovat_organizatory.short_description = 'Zaktivovat organizátory'
def deaktivovat_organizatory(modeladmin, request, queryset):
''' deaktivuje vybrane organizatory '''
for org in queryset:
org.user.is_active = False
org.user.save()
deaktivovat_organizatory.short_description = 'Deaktivovat organizátory'
@admin.register(Organizator)
class OrganizatorAdmin(admin.ModelAdmin):
list_filter = ['organizuje_do_roku']
list_display = [jmeno_organizatora, je_organizator_aktivni,]
actions = [zaktivovat_organizatory, deaktivovat_organizatory,]

148
seminar/export.py

@ -1,80 +1,92 @@
import datetime
import datetime, django
from django.shortcuts import get_object_or_404, render
from django.http import HttpResponseRedirect
from django.http import HttpResponse
from django.core.urlresolvers import reverse
from django.views import generic
from .models import Problem, Cislo, Reseni, VysledkyKCislu, Nastaveni, Rocnik
class ExportIndexView(generic.base.TemplateView):
template_name = 'seminar/export_index.csv'
content_type = 'text/plain; charset=utf-8'
def get_context_data(self, **kwargs):
context = super(ExportIndexView, self).get_context_data(**kwargs)
context['exports'] = []
from django.utils.encoding import force_text
from .models import Problem, Cislo, Reseni, VysledkyKCislu, Nastaveni, Rocnik, Soustredeni
from .ovvpfile import OvvpFile
class ExportIndexView(generic.View):
def get(self, request):
ls = []
for r in Rocnik.objects.all():
if r.verejna_cisla():
url = reverse('seminar_export_rocnik', kwargs={'prvni_rok': r.prvni_rok})
context['exports'].append(url.split('/')[-1])
return context
ls.append(url.split('/')[-1])
for s in Soustredeni.objects.all():
if s.ucastnici.count() >= 1 and s.verejne:
url = reverse('seminar_export_sous', kwargs={'datum_zacatku': s.datum_zacatku.isoformat()})
ls.append(url.split('/')[-1])
return HttpResponse('\n'.join(ls) + '\n', content_type='text/plain; charset=utf-8')
class ExportRocnikView(generic.DetailView):
slug_field = 'prvni_rok'
slug_url_kwarg = 'prvni_rok'
model = Rocnik
template_name = 'seminar/export_rocnik.csv'
content_type = 'text/plain; charset=utf-8'
def get_context_data(self, **kwargs):
context = super(ExportRocnikView, self).get_context_data(**kwargs)
rocnik = context['rocnik']
cislo = rocnik.posledni_verejne_cislo()
sloupce = ['id', 'name', 'surname',
'gender', 'born', 'email', 'end-year',
'street', 'town', 'postcode', 'country',
'spam-flag', 'spam-date', 'school', 'school-name',
'points', 'rank',]
radky = []
def default_ovvpfile(event, rocnik):
of = OvvpFile()
of.headers['version'] = '1'
of.headers['event'] = event
of.headers['year'] = force_text(rocnik.prvni_rok)
of.headers['date'] = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
of.headers['id-scope'] = 'mam'
of.headers['id-generation'] = '1'
return of
class ExportSousView(generic.View):
def get(self, request, datum_zacatku=None):
try:
dz = django.utils.dateparse.parse_date(datum_zacatku)
except:
dz = None
if dz is None:
raise django.http.Http404()
s = get_object_or_404(Soustredeni, datum_zacatku=dz)
of = default_ovvpfile('MaM.sous', s.rocnik)
of.headers['x-event-begin'] = s.datum_zacatku.isoformat()
of.headers['x-event-end'] = s.datum_konce.isoformat()
of.headers['x-event-location'] = s.misto
of.headers['comment'] = u'MaM-Web export ucastniku soustredeni v {x-event-location} od {x-event-begin} do {x-event-end}'.format(**of.headers)
of.columns = ['id', 'name', 'surname', 'gender', 'email', 'end-year', 'school', 'school-name']
for u in s.ucastnici.all():
of.rows.append(u.export_row())
return of.to_HttpResponse()
class ExportRocnikView(generic.View):
def get(self, request, prvni_rok=None):
try:
pr = int(prvni_rok)
except:
pr = None
if pr is None:
raise django.http.Http404()
rocnik = get_object_or_404(Rocnik, prvni_rok=pr)
cislo = rocnik.posledni_verejne_cislo()
vysledky = VysledkyKCislu.objects.filter(cislo = cislo).select_related("resitel").order_by('-body_celkem').all()
of = default_ovvpfile('MaM.rocnik', rocnik)
of.headers['comment'] = u'MaM-Web export aktivnich resitelu rocniku {rocnik} do cisla {cislo}'.format(
rocnik=rocnik, cislo=cislo)
of.columns = ['id', 'name', 'surname', 'gender', 'born', 'email', 'end-year',
'street', 'town', 'postcode', 'country', 'spam-flag', 'spam-date',
'school', 'school-name', 'points', 'rank',]
posledni_body = 100000
posledni_poradi = 0
for vi in range(len(vysledky)):
v = vysledky[vi]
rd = {}
rd['name'] = v.resitel.jmeno
rd['surname'] = v.resitel.prijmeni
rd['id'] = v.resitel.id
rd['gender'] = 'M' if v.resitel.pohlavi_muz else 'F'
if v.resitel.datum_narozeni:
rd['born'] = v.resitel.datum_narozeni.strftime("%Y-%m-%d")
else:
rd['born'] = ''
rd['email'] = v.resitel.email
rd['end-year'] = v.resitel.rok_maturity
# TODO(gavento): Adresa skoly, kdyz preferuje zasilani tam?
rd['street'] = v.resitel.ulice
rd['town'] = v.resitel.mesto
rd['postcode'] = v.resitel.psc
rd['country'] = v.resitel.stat
if v.resitel.datum_souhlasu_zasilani:
rd['spam-flag'] = 'Y'
rd['spam-date'] = v.resitel.datum_souhlasu_zasilani.strftime("%Y-%m-%d")
else:
rd['spam-flag'] = ''
rd['spam-date'] = ''
if v.resitel.skola:
rd['school'] = v.resitel.skola.aesop_id
rd['school-name'] = str(v.resitel.skola)
else:
rd['school'] = ''
rd['school-name'] = 'Skola neni znama'
rd = v.resitel.export_row()
if posledni_body > v.body_celkem:
posledni_body = v.body_celkem
@ -82,16 +94,8 @@ class ExportRocnikView(generic.DetailView):
rd['rank'] = posledni_poradi
rd['points'] = v.body_celkem
r = []
for c in sloupce:
r.append(rd.pop(c))
assert len(rd) == 0
radky.append(r)
context['date'] = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
context['cislo'] = cislo
context['sloupce'] = sloupce
context['radky'] = radky
return context
of.rows.append(rd)
return of.to_HttpResponse()

32
seminar/migrations/0019_rocnik_ciselne.py

@ -0,0 +1,32 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
class Migration(migrations.Migration):
dependencies = [
('seminar', '0018_problemnavrh_problemzadany'),
]
operations = [
migrations.AddField(
model_name='rocnik',
name='rocnik_n',
field=models.IntegerField(default=0, verbose_name='\u010d\xedslo ro\u010dn\xedku'),
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',
),
]

44
seminar/migrations/0020_indexy_a_razeni.py

@ -0,0 +1,44 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
class Migration(migrations.Migration):
dependencies = [
('seminar', '0019_rocnik_ciselne'),
]
operations = [
migrations.AlterModelOptions(
name='cislo',
options={'ordering': ['-rocnik__rocnik', '-cislo'], 'verbose_name': '\u010c\xedslo', 'verbose_name_plural': '\u010c\xedsla'},
),
migrations.AlterModelOptions(
name='reseni',
options={'ordering': ['problem_id', 'resitel__prijmeni', 'resitel__jmeno'], 'verbose_name': '\u0158e\u0161en\xed', 'verbose_name_plural': '\u0158e\u0161en\xed'},
),
migrations.AlterModelOptions(
name='rocnik',
options={'ordering': ['-rocnik'], 'verbose_name': 'Ro\u010dn\xedk', 'verbose_name_plural': 'Ro\u010dn\xedky'},
),
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\u010dije 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='rocnik',
name='prvni_rok',
field=models.IntegerField(unique=True, verbose_name='prvn\xed rok', db_index=True),
preserve_default=True,
),
migrations.AlterField(
model_name='rocnik',
name='rocnik',
field=models.IntegerField(unique=True, verbose_name='\u010d\xedslo ro\u010dn\xedku', db_index=True),
preserve_default=True,
),
]

20
seminar/migrations/0021_cislo_verejna_vysledkovka.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', '0020_indexy_a_razeni'),
]
operations = [
migrations.AddField(
model_name='cislo',
name='verejna_vysledkovka',
field=models.BooleanField(default=False, help_text='Je-li false u ve\u0159ejn\xe9ho \u010d\xedsla, nen\xed v\xfdsledkovka zat\xedm ve\u0159ejn\xe1.', verbose_name='zve\u0159ejn\u011bna v\xfdsledkovka'),
preserve_default=True,
),
]

30
seminar/migrations/0022_decimal_body.py

@ -0,0 +1,30 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
import importlib
from django.db import models, migrations
migration_0022 = importlib.import_module('seminar.migrations.0002_add_body_views')
print dir(migration_0022)
class Migration(migrations.Migration):
dependencies = [
('seminar', '0021_cislo_verejna_vysledkovka'),
]
operations = [
migrations.RunSQL(migration_0022.DROP_VIEWS),
migrations.AlterField(
model_name='problem',
name='body',
field=models.DecimalField(null=True, verbose_name='maximum bod\u016f', max_digits=8, decimal_places=1, blank=True),
preserve_default=True,
),
migrations.AlterField(
model_name='reseni',
name='body',
field=models.DecimalField(null=True, verbose_name='body', max_digits=8, decimal_places=1, blank=True),
preserve_default=True,
),
migrations.RunSQL(migration_0022.CREATE_VIEWS),
]

32
seminar/migrations/0023_add_novinky.py

@ -0,0 +1,32 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
from django.conf import settings
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('seminar', '0022_decimal_body'),
]
operations = [
migrations.CreateModel(
name='Novinky',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('datum', models.DateField(auto_now_add=True)),
('text', models.TextField(null=True, verbose_name=b'Text novinky', blank=True)),
('obrazek', models.ImageField(upload_to=b'image_novinky/%Y/%m/%d/', null=True, verbose_name=b'Obr\xc3\xa1zek', blank=True)),
('zverejneno', models.BooleanField(default=b'False', verbose_name=b'Zve\xc5\x99ejn\xc4\x9bno')),
('autor', models.ForeignKey(verbose_name=b'Autor novinky', to=settings.AUTH_USER_MODEL)),
],
options={
'verbose_name': 'Novinka',
'verbose_name_plural': 'Novinky',
},
bases=(models.Model,),
),
]

35
seminar/migrations/0024_add_organizator.py

@ -0,0 +1,35 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
from django.conf import settings
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('seminar', '0023_add_novinky'),
]
operations = [
migrations.CreateModel(
name='Organizator',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('prezdivka', models.CharField(max_length=32, null=True, verbose_name=b'P\xc5\x99ezd\xc3\xadvka', blank=True)),
('organizuje_od_roku', models.IntegerField(null=True, verbose_name=b'Organizuje od roku', blank=True)),
('organizuje_do_roku', models.IntegerField(null=True, verbose_name=b'Organizuje do roku', blank=True)),
('studuje', models.CharField(max_length=256, null=True, verbose_name=b'Studuje', blank=True)),
('strucny_popis_organizatora', models.TextField(null=True, verbose_name=b'Stru\xc4\x8dn\xc3\xbd popis organiz\xc3\xa1tora', blank=True)),
('foto', models.ImageField(help_text=b'Vlo\xc5\xbe fotografii organiz\xc3\xa1tora o libovon\xc3\xa9 velikosti', upload_to=b'image_organizatori/%Y/', null=True, verbose_name=b'Fotografie organiz\xc3\xa1tora', blank=True)),
('foto_male', models.ImageField(upload_to=b'image_organizatori/male/%Y/', null=True, editable=False, blank=True)),
('user', models.OneToOneField(verbose_name=b'Osoba', to=settings.AUTH_USER_MODEL, help_text=b'Vyber \xc3\xba\xc4\x8det sp\xc5\x99a\xc5\xbeen\xc3\xbd s organiz\xc3\xa1torem.')),
],
options={
'verbose_name': 'Organiz\xe1tor',
'verbose_name_plural': 'Organiz\xe1to\u0159i',
},
bases=(models.Model,),
),
]

20
seminar/migrations/0025_zmena_cesty_nahravani_obrazku.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', '0024_add_organizator'),
]
operations = [
migrations.AlterField(
model_name='organizator',
name='foto',
field=models.ImageField(help_text=b'Vlo\xc5\xbe fotografii organiz\xc3\xa1tora o libovon\xc3\xa9 velikosti', upload_to=b'image_organizatori/velke/%Y/', null=True, verbose_name=b'Fotografie organiz\xc3\xa1tora', blank=True),
preserve_default=True,
),
]

157
seminar/models.py

@ -12,6 +12,10 @@ from django.utils.text import slugify
from django.core.urlresolvers import reverse
from django.core.cache import cache
from PIL import Image
import os
from cStringIO import StringIO
from django.core.files.base import ContentFile
from django_countries.fields import CountryField
from solo.models import SingletonModel
@ -29,12 +33,6 @@ class SeminarModelBase(models.Model):
def verejne(self):
return False
# def public_url(self):
# if self.Meta.url_name:
# return reverse(self.Meta.url_name,
# kwargs={'id': self.id, 'pk': self.id})
# return None
def get_absolute_url(self):
return self.verejne_url() # TODO "absolute"
@ -188,6 +186,30 @@ class Resitel(SeminarModelBase):
def __str__(self):
return force_unicode(self.plne_jmeno())
def export_row(self):
"Slovnik pro pouziti v OVVP exportu"
return {
'id': self.id,
'name': self.jmeno,
'surname': self.prijmeni,
'gender': 'M' if self.pohlavi_muz else 'F',
'born': self.datum_narozeni.isoformat() if self.datum_narozeni else '',
'email': self.email,
'end-year': self.rok_maturity,
# TODO(gavento): Adresa skoly, kdyz preferuje zasilani tam?
'street': self.ulice,
'town': self.mesto,
'postcode': self.psc,
'country': self.stat,
'spam-flag': 'Y' if self.datum_souhlasu_zasilani else '',
'spam-date': self.datum_souhlasu_zasilani.isoformat() if self.datum_souhlasu_zasilani else '',
'school': self.skola.aesop_id if self.skola else '',
'school-name': str(self.skola) if self.skola else 'Skola neni znama',
}
@reversion.register(ignore_duplicate_revisions=True)
@python_2_unicode_compatible
@ -202,18 +224,15 @@ class Rocnik(SeminarModelBase):
# Interní ID
id = models.AutoField(primary_key = True)
prvni_rok = models.IntegerField(u'první rok')
prvni_rok = models.IntegerField(u'první rok', db_index=True, unique=True)
rocnik = models.CharField(u'číslo ročníku', max_length=16)
rocnik = models.IntegerField(u'číslo ročníku', db_index=True, unique=True)
def __str__(self):
return force_unicode(u'%s (%d/%d)' % (self.rocnik, self.prvni_rok, self.prvni_rok+1))
def roman(self):
if self.rocnik.isdigit():
return force_unicode(roman(int(self.rocnik)))
else:
return force_unicode(self.rocnik)
return force_unicode(roman(int(self.rocnik)))
def verejne(self):
return len(self.verejna_cisla()) > 0
@ -232,7 +251,7 @@ class Rocnik(SeminarModelBase):
return self.prvni_rok + 1
def verejne_url(self):
return reverse('seminar_rocnik', kwargs={'pk': self.id})
return reverse('seminar_rocnik', kwargs={'rocnik': self.rocnik})
@classmethod
def cached_rocnik(cls, r_id):
@ -258,9 +277,9 @@ class Cislo(SeminarModelBase):
# Interní ID
id = models.AutoField(primary_key = True)
rocnik = models.ForeignKey(Rocnik, verbose_name=u'ročník', related_name='cisla')
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,
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!')
datum_vydani = models.DateField(u'datum vydání', blank=True, null=True,
@ -271,6 +290,9 @@ class Cislo(SeminarModelBase):
verejne_db = models.BooleanField(u'číslo zveřejněno', db_column='verejne', default=False)
verejna_vysledkovka = models.BooleanField(u'zveřejněna výsledkovka', default=False,
help_text=u'Je-li false u veřejného čísla, není výsledkovka zatím veřejná.')
poznamka = models.TextField(u'neveřejná poznámka', blank=True,
help_text=u'Neveřejná poznámka k číslu (plain text)')
@ -288,7 +310,7 @@ class Cislo(SeminarModelBase):
verejne.boolean = True
def verejne_url(self):
return reverse('seminar_cislo', kwargs={'pk': self.id})
return reverse('seminar_cislo', kwargs={'rocnik': self.rocnik.rocnik, 'cislo': self.cislo})
def nasledujici(self):
u"Vrací None, pokud je toto poslední"
@ -370,7 +392,7 @@ class Problem(SeminarModelBase):
cislo_reseni = models.ForeignKey(Cislo, verbose_name=u'číslo řešení', blank=True, null=True, related_name=u'resene_problemy',
help_text=u'Číslo s řešením úlohy, jen pro úlohy')
body = models.IntegerField(u'maximum bodů', blank=True, null=True)
body = models.DecimalField(max_digits=8, decimal_places=1, verbose_name=u'maximum bodů', blank=True, null=True)
timestamp = models.DateTimeField(u'vytvořeno', default=timezone.now, blank=True, editable=False)
@ -411,7 +433,7 @@ class Reseni(SeminarModelBase):
db_table = 'seminar_reseni'
verbose_name = u'Řešení'
verbose_name_plural = u'Řešení'
ordering = ['problem_id', 'resitel_id']
ordering = ['problem_id', 'resitel__prijmeni', 'resitel__jmeno',]
# Interní ID
id = models.AutoField(primary_key = True)
@ -420,7 +442,7 @@ class Reseni(SeminarModelBase):
resitel = models.ForeignKey(Resitel, verbose_name=u'řešitel', related_name='reseni')
body = models.IntegerField(u'body', blank=True, null=True)
body = models.DecimalField(max_digits=8, decimal_places=1, verbose_name=u'body', blank=True, null=True)
cislo_body = models.ForeignKey(Cislo, verbose_name=u'číslo pro body', related_name='bodovana_reseni', blank=True, null=True)
@ -443,6 +465,12 @@ class Reseni(SeminarModelBase):
return force_unicode(u"%s: %s (%sb)" % (self.resitel.plne_jmeno(), self.problem.nazev, self.body))
# NOTE: Potenciální DB HOG (bez select_related)
def save(self, *args, **kwargs):
if ((self.cislo_body is None) and (self.problem.cislo_reseni) and
(self.problem.typ == Problem.TYP_ULOHA)):
self.cislo_body = self.problem.cislo_reseni
super(Reseni, self).save(*args, **kwargs)
# PrilohaReseni method
def generate_filename(self, filename):
@ -562,7 +590,8 @@ class VysledkyBase(SeminarModelBase):
resitel = models.ForeignKey(Resitel, verbose_name=u'řešitel', db_column='resitel_id', on_delete=models.DO_NOTHING)
body = models.IntegerField(u'body za číslo', db_column='body')
body = models.DecimalField(max_digits=8, decimal_places=1, db_column='body',
verbose_name=u'body za číslo')
def __str__(self):
return force_unicode(u"%s: %sb (%s)" % (self.resitel.plne_jmeno(), self.body, str(self.cislo)))
@ -589,7 +618,8 @@ class VysledkyKCislu(VysledkyBase):
abstract = False
managed = False
body_celkem = models.IntegerField(u'body celkem do čísla', db_column='body_celkem')
body_celkem = models.DecimalField(max_digits=8, decimal_places=1, db_column='body_celkem',
verbose_name=u'body celkem do čísla')
def __str__(self):
# NOTE: DB HOG (ale nepouzivany)
@ -616,3 +646,88 @@ class Nastaveni(SingletonModel):
def verejne(self):
return False
@python_2_unicode_compatible
class Novinky(models.Model):
datum = models.DateField(auto_now_add=True)
text = models.TextField('Text novinky', blank=True, null=True)
obrazek = models.ImageField('Obrázek', upload_to='image_novinky/%Y/%m/%d/',
null=True, blank=True)
autor = models.ForeignKey(settings.AUTH_USER_MODEL,
verbose_name='Autor novinky')
zverejneno = models.BooleanField('Zveřejněno', default="False")
def __str__(self):
return '[' + str(self.datum) + '] ' + self.text[0:50]
class Meta:
verbose_name = 'Novinka'
verbose_name_plural = 'Novinky'
@python_2_unicode_compatible
class Organizator(models.Model):
user = models.OneToOneField(settings.AUTH_USER_MODEL, verbose_name='Osoba',
help_text = 'Vyber účet spřažený s organizátorem.')
prezdivka = models.CharField('Přezdívka', max_length = 32,
null = True, blank = True)
organizuje_od_roku = models.IntegerField('Organizuje od roku',
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)
strucny_popis_organizatora = models.TextField('Stručný popis organizátora',
null = True, blank = True)
foto = models.ImageField('Fotografie organizátora',
upload_to='image_organizatori/velke/%Y/', null = True, blank = True,
help_text = 'Vlož fotografii organizátora o libovoné velikosti')
foto_male = models.ImageField(upload_to='image_organizatori/male/%Y/',
null = True, blank = True, editable = False)
def __str__(self):
return str(self.user)
class Meta:
verbose_name = 'Organizátor'
verbose_name_plural = 'Organizátoři'
def save(self):
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)
super(Organizator, self).save()
@staticmethod
def _vyrobMiniaturu(original, jmeno, maximum, field):
zmensenina = Organizator._zmensiObrazek(original, maximum)
f = StringIO()
try:
zmensenina.save(f, format=original.format)
data = ContentFile(f.getvalue())
finally:
f.close()
field.save(jmeno, data, save = False)
@staticmethod
def _zmensiObrazek(original, maximum):
"""Preskaluje obrazek tak, aby byl zachovan pomer stran
a zadny rozmer nepresahoval maxRozmer. Pokud zadny rozmer
nepresahuje maxRozmer, tak vrati puvodni obrazek
(tj. nedojde ke zvetseni obrazku)."""
novaVelikost = Organizator._zmensiVelikost(original.size, maximum)
return original.resize(novaVelikost, Image.ANTIALIAS)
@staticmethod
def _zmensiVelikost(velikost, maximum):
maximum = float(maximum)
w, h = velikost
soucasneMaximum = max(w, h)
if soucasneMaximum <= maximum:
return velikost
pomer = maximum/soucasneMaximum
return (int(w * pomer), int(h * pomer))

15
seminar/ovvpfile.py

@ -1,9 +1,19 @@
# -*- coding: utf-8 -*-
try:
from django.http import HttpResponse
from django.utils.encoding import force_text
except:
force_text = str
class OvvpFile(object):
def __init__(self):
# { header: value, ... }
self.headers = {}
# [ 'column-name', ... ]
self.columns = []
# [ { column: value, ...}, ...]
self.rows = []
def to_lines(self):
@ -15,11 +25,14 @@ class OvvpFile(object):
yield '\t'.join([c for c in self.columns]) + '\n'
# rows
for r in self.rows:
yield '\t'.join([r[c] for c in self.columns]) + '\n'
yield '\t'.join([force_text(r[c]) for c in self.columns]) + '\n'
def to_string(self):
return ''.join(self.to_lines())
def to_HttpResponse(self):
return HttpResponse(self.to_string(), content_type='text/plain; charset=utf-8')
def parse_from(self, source, with_headers=True):
"Parse data from file, string or line iterator, overwriting self"
if isinstance(source, str) or isinstance(source, unicode):

10
seminar/templates/seminar/archiv/cislo.html

@ -20,7 +20,12 @@
{% endfor %}
</ul>
{% if cislo.verejna_vysledkovka %}
<h3>Výsledkovka</h3>
{% else %}
<div class='mam-org-only'>
<h3>Výsledkovka (neveřejná)</h3>
{% endif %}
<table class='vysledkovka'>
<tr class='border-b'>
@ -44,6 +49,11 @@
{% endfor %}
</table>
{% if cislo.verejna_vysledkovka %}
{% else %}
</div>
{% endif %}
</div>
{% endblock content %}

36
seminar/templates/seminar/cojemam/organizatori.html

@ -4,6 +4,42 @@
<div>
<h2>Organizátoři</h2>
{% for org in object_list %}
{% if org.user.is_active %}
<b>Aktivní</b>
{% else %}
Není aktivní
{% endif %}
{{org.user.first_name}}
{% if org.prezdivka %}
"{{org.prezdivka}}"
{% endif %}
{{org.user.last_name}} <br>
<ul>
{% if org.organizuje_od_roku %}
<li>Organizuje od roku: {{org.organizuje_od_roku}}
{% endif %}
{% if org.organizuje_do_roku %}
<li>Organizuje do roku: {{org.organizuje_do_roku}}
{% endif %}
{% if org.studuje %}
<li>Studuje: {{org.studuje}}
{% endif %}
{% if org.user.email %}
<li>Pošta:
{# zobrazeni e-mailu (na jednom radku, aby nevznikali mezery navic) #}
{% for znak in org.user.email %}{% if znak == '@' %} &#60;zavináč&#62; {% elif znak == '.' %} &#60;tečka&#62; {% else %}{{znak}}{% endif %}{% endfor %}
{% endif %}
</ul>
{% if org.foto %}
<div class="foto_org">
<img src="{{org.foto.url}}" height="{{org.foto.height}}">
<img src="{{org.foto_male.url}}" height="{{org.foto_male.height}}">
</div>
{% endif %}
<hr>
{% endfor %}
</div>
{% endblock content %}

2
seminar/templates/seminar/export_index.csv

@ -1,2 +0,0 @@
{% for e in exports %}{{ e }}
{% endfor %}
1 {% for e in exports %}{{ e }}
2 {% endfor %}

11
seminar/templates/seminar/export_rocnik.csv

@ -1,11 +0,0 @@
version 1
event MaM.rocnik
year {{ rocnik.prvni_rok }}
date {{ date }}
id-scope mam
id-generation 1
comment MaM-Web export aktivnich resitelu rocniku {{ rocnik }} do cisla {{ cislo.kod }}
{% for c in sloupce %}{{ c }} {% endfor %}
{% for r in radky %}{% for c in r %}{{ c }} {% endfor %}
{% endfor %}
Can't render this file because it has a wrong number of fields in line 11.

45
seminar/templates/seminar/soustredeni/seznam_soustredeni.html

@ -0,0 +1,45 @@
{% extends "seminar/soustredeni/base.html" %}
{% block content %}
<h1> Soustředění </h1>
{# Projdi vsechna soustredeni #}
{% for soustredeni in object_list %}
{# Kdyz je verejne -> zobraz #}
{% if soustredeni.verejne_db or user.is_authenticated %}
{% if not soustredeni.verejne_db and user.is_authenticated %}
Groups of user: {{user.groups.all}} <br>
<!-- TODO pri prihlasovani ucastniku dodelat prava
jen na group org ve view -->
Toto soustředění není veřejné, vidíte ho jen proto,
že jste přihlášení. <br>
{% endif %}
{# misto soustredeni TODO upravit#}
{% if soustredeni.misto %}
<h2>
Soustředění v&nbsp;{{soustredeni.misto}}
od {{soustredeni.datum_zacatku}} do {{soustredeni.datum_konce}}
při {{soustredeni.rocnik.rocnik}}. ročníku M&M
</h2>
{% endif %}
{# popis soustredeni #}
{% if soustredeni.text %}
{% autoescape off %}{{soustredeni.text}}{% endautoescape %}
{% endif %}
{# Účastníci #}
<h3>Soustředění se zůčastnili tito účastníci:</h3>
<ul>
{% for i in soustredeni.soustredeni_ucastnici_set.all %}
<li>{{i.resitel}}
{% empty %}
<li>Nic!
{% endfor %}
</ul>
{% endif %}
{% empty %}
Žádná soustředění zatím neproběhla!
{% endfor %}
{% endblock %}

17
seminar/tests.py

@ -29,7 +29,7 @@ class SeminarBasicTests(TestCase):
def test_render_cislo_e2e(self):
cs = Cislo.objects.all()
for c in cs[:4]:
url = reverse('seminar_cislo', args=(c.id,))
url = c.verejne_url()
r = self.client.get(url)
assert r.status_code == 200
assert len(r.content) >= 100
@ -38,12 +38,25 @@ class SeminarBasicTests(TestCase):
def test_render_problem_e2e(self):
ps = Problem.objects.all()
for p in ps[:4]:
url = reverse('seminar_problem', args=(p.id,))
url = p.verejne_url()
r = self.client.get(url)
assert r.status_code == 200
assert len(r.content) >= 100
# TODO: Validate cntent as HTML
def test_export_e2e(self):
i_url = '/aesop-export/index.csv'
i_r = self.client.get(i_url)
assert i_r.status_code == 200
ls = i_r.content.strip().split('\n')
for u in [ls[0], ls[-1]]:
ex_r = self.client.get('/aesop-export/' + u)
assert ex_r.status_code == 200
assert len(ex_r.content) >= 100
o = ovvpfile.parse(ex_r.content)
assert o.headers['version'] == '1'
def test_admin_url(self):
for m in [Skola, Resitel, Rocnik, Cislo, Problem, Reseni, Nastaveni]:
o = m.objects.first()

10
seminar/testutils.py

@ -5,7 +5,7 @@ import random
import django.contrib.auth
from django.db import transaction
from seminar.models import Skola, Resitel, Rocnik, Cislo, Problem, Reseni, PrilohaReseni, Nastaveni
from seminar.models import Skola, Resitel, Rocnik, Cislo, Problem, Reseni, PrilohaReseni, Nastaveni, Soustredeni, Soustredeni_Ucastnici
from django.contrib.flatpages.models import FlatPage
from django.contrib.sites.models import Site
@ -70,7 +70,7 @@ def create_test_data(size = 6, rnd = None):
for ci in range(1, cisel + 1):
vydano = datetime.date(r.prvni_rok, ci + 6, 1)
deadline = datetime.date(r.prvni_rok, ci + 8, 1) if ci + 2 < cisel else None
c = Cislo.objects.create(rocnik = r, cislo = str(ci), datum_vydani=vydano, datum_deadline=deadline)
c = Cislo.objects.create(rocnik = r, cislo = str(ci), datum_vydani=vydano, datum_deadline=deadline, verejne_db=True)
cs[ci] = c
# problemy resene v ci
@ -91,6 +91,12 @@ def create_test_data(size = 6, rnd = None):
res = Reseni.objects.create(problem = p, resitel = resitel,
body = rnd.randint(0, p.body), cislo_body = cs[ci])
sous = Soustredeni.objects.create(rocnik=Rocnik.objects.first(), verejne_db=True, misto=u'Někde',
datum_zacatku=datetime.date(2000, 11, 23), datum_konce=datetime.date(2000, 11, 27))
for res in rnd.sample(resitele, 6):
Soustredeni_Ucastnici.objects.create(resitel=res, soustredeni=sous)
sous.save()
nastaveni = Nastaveni.objects.create(aktualni_rocnik = Rocnik.objects.last(),
aktualni_cislo = Cislo.objects.all()[1])

10
seminar/urls.py

@ -6,14 +6,18 @@ urlpatterns = patterns('',
url(r'^co-je-MaM/organizatori/$', views.CojemamOrganizatoriView.as_view()),
url(r'^archiv/cisla/$', views.CislaView.as_view()),
url(r'^archiv/cisla/rocnik/(?P<pk>\d+)/$', views.RocnikView.as_view(), name='seminar_rocnik'),
url(r'^archiv/cisla/cislo/(?P<pk>\d+)/$', views.CisloView.as_view(), name='seminar_cislo'),
url(r'^archiv/cisla/problem/(?P<pk>\d+)/$', views.ProblemView.as_view(), name='seminar_problem'),
url(r'^rocnik/(?P<rocnik>\d+)/$', views.RocnikView.as_view(), name='seminar_rocnik'),
url(r'^cislo/(?P<rocnik>\d+)\.(?P<cislo>\d+)/$', views.CisloView.as_view(), name='seminar_cislo'),
url(r'^problem/(?P<pk>\d+)/$', views.ProblemView.as_view(), name='seminar_problem'),
url(r'^soustredeni/$', views.SoustredeniListView.as_view(),
name = 'seminar_seznam_soustredeni'),
url(r'^soustredeni/(?P<pk>\d+)/$', views.SoustredeniView.as_view(), name='seminar_soustredeni'),
url(r'^zadani/aktualni/$', views.AktualniZadaniView, name='seminar_aktualni_zadani'),
url(r'^aesop-export/mam-rocnik-(?P<prvni_rok>\d+)\.csv$', export.ExportRocnikView.as_view(), name='seminar_export_rocnik'),
url(r'^aesop-export/mam-sous-(?P<datum_zacatku>[\d-]+)\.csv$', export.ExportSousView.as_view(), name='seminar_export_sous'),
url(r'^aesop-export/index.csv$', export.ExportIndexView.as_view(), name='seminar_export_index'),
)

63
seminar/views.py

@ -2,7 +2,10 @@ from django.shortcuts import get_object_or_404, render
from django.http import HttpResponseRedirect
from django.core.urlresolvers import reverse
from django.views import generic
from .models import Problem, Cislo, Reseni, VysledkyKCislu, Nastaveni, Rocnik, Soustredeni
from django.utils.translation import ugettext as _
from django.http import Http404
from .models import Problem, Cislo, Reseni, VysledkyKCislu, Nastaveni, Rocnik, Soustredeni, Organizator
def AktualniZadaniView(request):
@ -12,11 +15,14 @@ def AktualniZadaniView(request):
},
)
# Co je M&M
### Co je M&M
class CojemamOrganizatoriView(generic.TemplateView):
class CojemamOrganizatoriView(generic.ListView):
model = Organizator
template_name='seminar/cojemam/organizatori.html'
### Archiv
class CislaView(generic.ListView):
model = Rocnik
template_name='seminar/archiv/cisla.html'
@ -25,9 +31,19 @@ class RocnikView(generic.DetailView):
model = Rocnik
template_name = 'seminar/archiv/rocnik.html'
class SoustredeniView(generic.DetailView):
model = Soustredeni
template_name = 'seminar/archiv/soustredeni.html'
# Vlastni ziskavani objektu z databaze podle (Rocnik.rocnik)
def get_object(self, queryset=None):
if queryset is None:
queryset = self.get_queryset()
rocnik_arg = self.kwargs.get('rocnik')
queryset = queryset.filter(rocnik=rocnik_arg)
try:
obj = queryset.get()
except queryset.model.DoesNotExist:
raise Http404(_("No %(verbose_name)s found matching the query") %
{'verbose_name': queryset.model._meta.verbose_name})
return obj
class ProblemView(generic.DetailView):
model = Problem
@ -39,26 +55,50 @@ class RadekVysledkovky(object):
class CisloView(generic.DetailView):
model = Cislo
template_name = 'seminar/archiv/cislo.html'
# Vlastni ziskavani objektu z databaze podle (Rocnik.rocnik)
def get_object(self, queryset=None):
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)
try:
obj = queryset.get()
except queryset.model.DoesNotExist:
raise Http404(_("No %(verbose_name)s found matching the query") %
{'verbose_name': queryset.model._meta.verbose_name})
return obj
def get_context_data(self, **kwargs):
context = super(CisloView, self).get_context_data(**kwargs)
# Vysledky k cislu: seznam objektu typu (cislo, resitel, body [v tom cisle], body_celkem [od zac. rocniku])
vysledky = VysledkyKCislu.objects.filter(cislo = context['cislo']).order_by('-body_celkem').select_related("resitel")
reseni = Reseni.objects.filter(cislo_body = context['cislo']).select_related("resitel")
# seznam problemu s nejakymi body v tomto cisle
problemy = sorted(list(set([r.problem for r in reseni])), key=lambda x:(0 if x.typ==Problem.TYP_ULOHA else 1,x.kod))
# cislo sloupecku pro problem (inverzni funkce)
problem_index = {}
for i in range(len(problemy)):
problem_index[problemy[i].id] = i
# pomocna mapa Resitel: RadekVysledkovky
vysledky_resitele = {}
# radky vysledkovky, seznam objektu RadekVysledkovky
vysledkovka = []
# posledni pocet bodu, pro detekci stejnych mist
posledni_body = 100000
for vi in range(len(vysledky)):
v = vysledky[vi]
tv = RadekVysledkovky()
tv.resitel = v.resitel
tv.vysledek = v
tv.body = ['']*len(problemy)
tv.body = ['']*len(problemy) # misto pro body za jednotlive uloy
tv.poradi = ''
if posledni_body > v.body_celkem:
posledni_body = v.body_celkem
@ -66,6 +106,7 @@ class CisloView(generic.DetailView):
vysledky_resitele[v.resitel.id] = tv
vysledkovka.append(tv)
# doplneni bodu za jednotliva reseni lidi do RadekVysledkovky.body
for r in reseni:
vysledky_resitele[r.resitel.id].body[problem_index[r.problem.id]] = r.body
@ -73,4 +114,12 @@ class CisloView(generic.DetailView):
context['problemy'] = problemy
return context
### Soustredeni
class SoustredeniListView(generic.ListView):
model = Soustredeni
template_name = 'seminar/soustredeni/seznam_soustredeni.html'
class SoustredeniView(generic.DetailView):
model = Soustredeni
template_name = 'seminar/archiv/soustredeni.html'

Loading…
Cancel
Save