Browse Source

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

export_seznamu_prednasek
Anet 4 years ago
parent
commit
d9558a750a
  1. BIN
      Schema_new.dia
  2. 332
      flat.json
  3. 24
      seminar/forms.py
  4. 24
      seminar/migrations/0074_auto_20200228_1401.py
  5. 18
      seminar/migrations/0075_auto_20200228_2010.py
  6. 19
      seminar/migrations/0076_auto_20200228_2013.py
  7. 32
      seminar/migrations/0077_auto_20200318_2146.py
  8. 51
      seminar/models.py
  9. 49
      seminar/static/seminar/dynamic_formsets.js
  10. 44
      seminar/templates/seminar/nahraj_reseni.html
  11. 165
      seminar/testutils.py
  12. 124
      seminar/treelib.py
  13. 2
      seminar/urls.py
  14. 16
      seminar/utils.py
  15. 2
      seminar/views/__init__.py
  16. 63
      seminar/views/autocomplete.py
  17. 8
      seminar/views/helpers.py
  18. 0
      seminar/views/unicodecsv.py
  19. 91
      seminar/views/utils.py
  20. 94
      seminar/views/views_all.py
  21. 1
      sitetree_new.json

BIN
Schema_new.dia

Binary file not shown.

332
flat.json

File diff suppressed because one or more lines are too long

24
seminar/forms.py

@ -2,6 +2,7 @@ from django import forms
from dal import autocomplete from dal import autocomplete
from django.core.exceptions import ObjectDoesNotExist from django.core.exceptions import ObjectDoesNotExist
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.forms.models import inlineformset_factory
from .models import Skola, Resitel, Osoba, Problem from .models import Skola, Resitel, Osoba, Problem
import seminar.models as m import seminar.models as m
@ -252,6 +253,25 @@ class VlozReseniForm(forms.Form):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
#self.fields['favorite_color'] = forms.ChoiceField(choices=[(color.id, color.name) for color in Resitel.objects.all()]) #self.fields['favorite_color'] = forms.ChoiceField(choices=[(color.id, color.name) for color in Resitel.objects.all()])
class NahrajReseniForm(forms.ModelForm):
class Meta:
model = m.Reseni
fields = ('problem',)
widgets = {'problem':
autocomplete.ModelSelect2Multiple(
url='autocomplete_problem_odevzdatelny',
attrs = {'data-placeholder--id': '-1',
'data-placeholder--text' : '---',
'data-allow-clear': 'true'},
)
}
ReseniSPrilohamiFormSet = inlineformset_factory(m.Reseni,m.PrilohaReseni,
form = NahrajReseniForm,
fields = ('soubor','res_poznamka'),
widgets = {'res_poznamka':forms.TextInput()},
extra = 1,
)

24
seminar/migrations/0074_auto_20200228_1401.py

@ -0,0 +1,24 @@
# Generated by Django 2.2.9 on 2020-02-28 13:01
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('seminar', '0073_copy_osoba_email_to_user_email'),
]
operations = [
migrations.AddField(
model_name='prilohareseni',
name='res_poznamka',
field=models.TextField(blank=True, help_text='Poznámka k příloze řešení, např. co daný soubor obsahuje', verbose_name='poznámka řešitele'),
),
migrations.AlterField(
model_name='hodnoceni',
name='problem',
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='hodnoceni', to='seminar.Problem', verbose_name='problém'),
),
]

18
seminar/migrations/0075_auto_20200228_2010.py

@ -0,0 +1,18 @@
# Generated by Django 2.2.9 on 2020-02-28 19:10
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('seminar', '0074_auto_20200228_1401'),
]
operations = [
migrations.AlterField(
model_name='hodnoceni',
name='body',
field=models.DecimalField(decimal_places=1, max_digits=8, null=True, verbose_name='body'),
),
]

19
seminar/migrations/0076_auto_20200228_2013.py

@ -0,0 +1,19 @@
# Generated by Django 2.2.9 on 2020-02-28 19:13
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('seminar', '0075_auto_20200228_2010'),
]
operations = [
migrations.AlterField(
model_name='hodnoceni',
name='cislo_body',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, related_name='hodnoceni', to='seminar.Cislo', verbose_name='číslo pro body'),
),
]

32
seminar/migrations/0077_auto_20200318_2146.py

@ -0,0 +1,32 @@
# Generated by Django 2.2.9 on 2020-03-18 20:46
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('seminar', '0076_auto_20200228_2013'),
]
operations = [
migrations.CreateModel(
name='CastNode',
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')),
('nadpis', models.CharField(help_text='Nadpis podvěšené části obsahu', max_length=100, verbose_name='Nadpis')),
],
options={
'verbose_name': 'Část (Node)',
'verbose_name_plural': 'Části (Node)',
'db_table': 'seminar_nodes_cast',
},
bases=('seminar.treenode',),
),
migrations.AlterField(
model_name='treenode',
name='first_child',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='father_of_first', to='seminar.TreeNode', verbose_name='první potomek'),
),
]

51
seminar/models.py

@ -920,10 +920,10 @@ class Hodnoceni(SeminarModelBase):
body = models.DecimalField(max_digits=8, decimal_places=1, verbose_name='body', body = models.DecimalField(max_digits=8, decimal_places=1, verbose_name='body',
blank=False, null=False) blank=False, null=True)
cislo_body = models.ForeignKey(Cislo, verbose_name='číslo pro body', cislo_body = models.ForeignKey(Cislo, verbose_name='číslo pro body',
related_name='hodnoceni', blank=False, null=False, on_delete=models.PROTECT) related_name='hodnoceni', blank=False, null=True, on_delete=models.PROTECT)
reseni = models.ForeignKey(Reseni, verbose_name='řešení', on_delete=models.CASCADE) reseni = models.ForeignKey(Reseni, verbose_name='řešení', on_delete=models.CASCADE)
@ -935,17 +935,16 @@ class Hodnoceni(SeminarModelBase):
## FIXME: Budeme řešit později, pokud to bude potřeba. def aux_generate_filename(self, filename):
#def aux_generate_filename(self, filename): """Pomocná funkce generující ošetřený název souboru v adresáři s datem"""
# """Pomocná funkce generující ošetřený název souboru v adresáři s datem""" clean = get_valid_filename(
# clean = get_valid_filename( unidecode(filename.replace('/', '-').replace('\0', ''))
# unidecode(filename.replace('/', '-').replace('\0', '')) )
# ) datedir = timezone.now().strftime('%Y-%m')
# datedir = timezone.now().strftime('%Y-%m') fname = "{}_{}".format(
# fname = "%s_%s" % ( timezone.now().strftime('%Y-%m-%d-%H:%M'),
# timezone.now().strftime('%Y-%m-%d-%H:%M'), clean)
# clean) return os.path.join(datedir, fname)
# return os.path.join(datedir, fname)
# Django neumí jednoduše serializovat partial nebo třídu s __call__ # Django neumí jednoduše serializovat partial nebo třídu s __call__
# (https://docs.djangoproject.com/en/1.8/topics/migrations/), # (https://docs.djangoproject.com/en/1.8/topics/migrations/),
@ -988,9 +987,12 @@ class PrilohaReseni(SeminarModelBase):
poznamka = models.TextField('neveřejná poznámka', blank=True, poznamka = models.TextField('neveřejná poznámka', blank=True,
help_text='Neveřejná poznámka k příloze řešení (plain text), např. o původu') help_text='Neveřejná poznámka k příloze řešení (plain text), např. o původu')
res_poznamka = models.TextField('poznámka řešitele', blank=True,
help_text='Poznámka k příloze řešení, např. co daný soubor obsahuje')
def __str__(self): def __str__(self):
return self.soubor return str(self.soubor)
class Pohadka(SeminarModelBase): class Pohadka(SeminarModelBase):
@ -1228,6 +1230,8 @@ class Obrazek(SeminarModelBase):
help_text = 'Černobílá verze obrázku do čísla', help_text = 'Černobílá verze obrázku do čísla',
upload_to = 'obrazky/%Y/%m/%d/', blank=True, null=True) upload_to = 'obrazky/%Y/%m/%d/', blank=True, null=True)
# TODO placement hint - chci ho tady / pred textem / za textem
class TreeNode(PolymorphicModel): class TreeNode(PolymorphicModel):
class Meta: class Meta:
db_table = "seminar_nodes_treenode" db_table = "seminar_nodes_treenode"
@ -1242,6 +1246,7 @@ class TreeNode(PolymorphicModel):
on_delete = models.SET_NULL, # Vrcholy s null kořenem jsou sirotci bez ročníku on_delete = models.SET_NULL, # Vrcholy s null kořenem jsou sirotci bez ročníku
verbose_name="kořen stromu") verbose_name="kořen stromu")
first_child = models.ForeignKey('TreeNode', first_child = models.ForeignKey('TreeNode',
related_name='father_of_first',
null = True, null = True,
blank = True, blank = True,
on_delete=models.SET_NULL, on_delete=models.SET_NULL,
@ -1262,15 +1267,6 @@ class TreeNode(PolymorphicModel):
srolovatelne = models.BooleanField(null = True, blank = True, srolovatelne = models.BooleanField(null = True, blank = True,
verbose_name = "Srolovatelné", verbose_name = "Srolovatelné",
help_text = "Bude na stránce témátka možnost tuto položku skrýt") 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):
# 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 def getOdkazStr(self): # String na rozcestník
return self.first_child.getOdkazStr() return self.first_child.getOdkazStr()
@ -1339,6 +1335,7 @@ class MezicisloNode(TreeNode):
verbose_name = 'Mezičíslo (Node)' verbose_name = 'Mezičíslo (Node)'
verbose_name_plural = 'Mezičísla (Node)' verbose_name_plural = 'Mezičísla (Node)'
# TODO: Využít TreeLib
def aktualizuj_nazev(self): def aktualizuj_nazev(self):
if self.prev: if self.prev:
if (self.prev.get_real_instance_class() != CisloNode and if (self.prev.get_real_instance_class() != CisloNode and
@ -1469,6 +1466,14 @@ class TextNode(TreeNode):
def getOdkazStr(self): def getOdkazStr(self):
return str(self.text) return str(self.text)
class CastNode(TreeNode):
class Meta:
db_table = 'seminar_nodes_cast'
verbose_name = 'Část (Node)'
verbose_name_plural = 'Části (Node)'
nadpis = models.CharField('Nadpis', max_length=100, help_text = 'Nadpis podvěšené části obsahu')
## FIXME: Logiku přesunout do views. ## FIXME: Logiku přesunout do views.
#class VysledkyBase(SeminarModelBase): #class VysledkyBase(SeminarModelBase):

49
seminar/static/seminar/dynamic_formsets.js

@ -0,0 +1,49 @@
// Credit https://medium.com/all-about-django/adding-forms-dynamically-to-a-django-formset-375f1090c2b0
function updateElementIndex(el, prefix, ndx) {
var id_regex = new RegExp('(' + prefix + '-\\d+)');
var replacement = prefix + '-' + ndx;
if ($(el).attr("for")) {
$(el).attr("for", $(el).attr("for").replace(id_regex, replacement));
}
if (el.id) {
el.id = el.id.replace(id_regex, replacement);
}
if (el.name) {
el.name = el.name.replace(id_regex, replacement);
}
}
// Credit https://medium.com/all-about-django/adding-forms-dynamically-to-a-django-formset-375f1090c2b0
function deleteForm(prefix, btn) {
var total = parseInt($('#id_' + prefix + '-TOTAL_FORMS').val());
if (total >= 1){
btn.closest('div').remove();
var forms = $('.attachment');
$('#id_' + prefix + '-TOTAL_FORMS').val(forms.length);
for (var i=0, formCount=forms.length; i<formCount; i++) {
$(forms.get(i)).find(':input').each(function() {
updateElementIndex(this, prefix, i);
});
}
}
return false;
}
// Credit: https://simpleit.rocks/python/django/dynamic-add-form-with-add-button-in-django-modelformset-template/
$(document).ready(function(){
$('#add_attachment').click(function() {
var form_idx = $('#id_prilohy-TOTAL_FORMS').val();
var new_form = $('#empty_form').html().replace(/__prefix__/g, form_idx);
$('#form_set').append(new_form);
// Newly created form has not the binding between remove button and remove function
// We need to add it manually
$('.remove_attachment').click(function(){
deleteForm("prilohy",this);
});
$('#id_prilohy-TOTAL_FORMS').val(parseInt(form_idx) + 1);
});
$('.remove_attachment').click(function(){
deleteForm("prilohy",this);
});
});

44
seminar/templates/seminar/nahraj_reseni.html

@ -0,0 +1,44 @@
{% extends "seminar/zadani/base.html" %}
{% load staticfiles %}
{% block script %}
<!--script type="text/javascript" src="{% static 'admin/js/vendor/jquery/jquery.js' %}"></script!-->
{{form.media}}
<script src="{% static 'seminar/dynamic_formsets.js' %}"></script>
{% endblock %}
{% block content %}
<h1>
{% block nadpis1a %}{% block nadpis1b %}
Vložit řešení
{% endblock %}{% endblock %}
</h1>
<form enctype="multipart/form-data" action="{% url 'seminar_nahraj_reseni' %}" method="post">
{% csrf_token %}
{{form}}
{{prilohy.management_form}}
<div id="form_set">
{% for form in prilohy.forms %}
<div class="attachment">
{{form.non_field_errors}}
{{form.errors}}
<table class='no_error'>
{{ form }}
</table>
<input type="button" value="Odebrat" class="remove_attachment" id="{{form.prefix}}-jsremove">
</div>
{% endfor %}
</div>
<input type="button" value="Přidat přílohu" id="add_attachment">
<div id="empty_form" style="display:none">
<div class="attachment">
<table class='no_error'>
{{ prilohy.empty_form }}
</table>
<input type="button" value="Odebrat" class="remove_attachment" id="id_prilohy-__prefix__-jsremove">
</div>
</div>
<input type="submit" value="Odevzdat">
</form>
{% endblock %}

165
seminar/testutils.py

@ -9,7 +9,7 @@ from django.db import transaction
import unidecode import unidecode
import logging import logging
from seminar.models import Skola, Resitel, Rocnik, Cislo, Problem, Reseni, PrilohaReseni, Nastaveni, Soustredeni, Soustredeni_Ucastnici, Soustredeni_Organizatori, Osoba, Organizator, Prijemce, Tema, Uloha, Konfera, KonferaNode, TextNode, UlohaVzorakNode, RocnikNode, CisloNode, TemaVCisleNode, Text, Hodnoceni, UlohaZadaniNode, Novinky from seminar.models import Skola, Resitel, Rocnik, Cislo, Problem, Reseni, PrilohaReseni, Nastaveni, Soustredeni, Soustredeni_Ucastnici, Soustredeni_Organizatori, Osoba, Organizator, Prijemce, Tema, Uloha, Konfera, KonferaNode, TextNode, UlohaVzorakNode, RocnikNode, CisloNode, TemaVCisleNode, Text, Hodnoceni, UlohaZadaniNode, Novinky, TreeNode
from django.contrib.flatpages.models import FlatPage from django.contrib.flatpages.models import FlatPage
from django.contrib.sites.models import Site from django.contrib.sites.models import Site
@ -390,17 +390,15 @@ def gen_temata(rnd, rocniky, rocnik_cisla, organizatori):
co = ["téma", "záření", "stavení", "jiskření", "jelito", co = ["téma", "záření", "stavení", "jiskření", "jelito",
"drama", "kuře", "moře", "klání", "proudění", "čekání"] "drama", "kuře", "moře", "klání", "proudění", "čekání"]
poc_oboru = rnd.randint(1, 2) poc_oboru = rnd.randint(1, 2)
poc_op = rnd.randint(1, 3)
rocnik_temata = [] rocnik_temata = []
k = 0 # Věříme, že rocnik_cisla je pole polí čísel podle ročníků, tak si necháme dát vždycky jeden ročník a k němu příslušná čísla.
for rocnik in rocniky: for rocnik, cisla in zip(rocniky, rocnik_cisla):
k+=1 kod = 1
n = 0 letosni_temata = []
temata = [] # Do každého ročníku vymyslíme tři (zatím) témata, v každém z prvních čísel jedno
cisla = rocnik_cisla[k-1] for zacatek_tematu in range(1, 3):
for ci in range(1, 3): # Vygenerujeme téma
n+=1
t = Tema.objects.create( t = Tema.objects.create(
# atributy třídy Problem # atributy třídy Problem
nazev=" ".join([rnd.choice(jake), rnd.choice(co)]), nazev=" ".join([rnd.choice(jake), rnd.choice(co)]),
@ -408,22 +406,32 @@ def gen_temata(rnd, rocniky, rocnik_cisla, organizatori):
zamereni=rnd.sample(["M", "F", "I", "O", "B"], poc_oboru), zamereni=rnd.sample(["M", "F", "I", "O", "B"], poc_oboru),
autor=rnd.choice(organizatori), autor=rnd.choice(organizatori),
garant=rnd.choice(organizatori), garant=rnd.choice(organizatori),
kod=str(n), kod=str(kod),
# atributy třídy Téma # atributy třídy Téma
tema_typ=rnd.choice(Tema.TEMA_CHOICES)[0], tema_typ=rnd.choice(Tema.TEMA_CHOICES)[0],
rocnik=rocnik, rocnik=rocnik,
abstrakt = "Abstrakt tematka {}".format(n) abstrakt = "Abstrakt tematka {}".format(kod)
) )
konec_tematu = min(rnd.randint(ci, 7), len(cisla)) kod += 1
for i in range(ci, konec_tematu+1):
# Vymyslíme, kdy skončí
konec_tematu = min(rnd.randint(zacatek_tematu, 7), len(cisla))
# Vyrobíme TemaVCisleNody pro obsah
for i in range(zacatek_tematu, konec_tematu+1):
node = TemaVCisleNode.objects.create(tema = t) node = TemaVCisleNode.objects.create(tema = t)
# FIXME: Není to off-by-one?
otec = cisla[i-1].cislonode otec = cisla[i-1].cislonode
otec_syn(otec, node) otec_syn(otec, node)
t.opravovatele.set(rnd.sample(organizatori, poc_op)) # Vymyslíme, kdo to bude opravovat
poc_opravovatelu = rnd.randint(1, 3)
t.opravovatele.set(rnd.sample(organizatori, poc_opravovatelu))
# Uložíme všechno
t.save() t.save()
temata.append((ci, konec_tematu, t)) letosni_temata.append((zacatek_tematu, konec_tematu, t))
rocnik_temata.append(temata) rocnik_temata.append(letosni_temata)
return rocnik_temata return rocnik_temata
@ -448,84 +456,103 @@ def gen_ulohy_k_tematum(rnd, rocniky, rocnik_cisla, rocnik_temata, organizatori)
"netriviální aplikace diferenciálních rovnic", "zadání je vnitřně" "netriviální aplikace diferenciálních rovnic", "zadání je vnitřně"
"sporné", "nepopsatelně jednoduché", "pokud jste na to nepřišli," "sporné", "nepopsatelně jednoduché", "pokud jste na to nepřišli,"
"tak jste fakt hloupí"] "tak jste fakt hloupí"]
k = 0 # Ke každému ročníku si vezmeme příslušná čísla a témata
for rocnik in rocniky: for rocnik, cisla, temata in zip(rocniky, rocnik_cisla, rocnik_temata):
k+=1 # Do každého čísla nagenerujeme ke každému témátku pár úložek
cisla = rocnik_cisla[k-1] for cislo in cisla:
temata = rocnik_temata[k-1] print("Generuji úložky do {}-tého čísla".format(cislo.poradi))
for ci in range(len(cisla)): # Vzorák bude o dvě čísla dál
print("Generuji {}-té číslo".format(ci)) cislo_se_vzorakem = Cislo.objects.filter(
cislo = cisla[ci-1] rocnik=rocnik,
mozna_tema_vcn = cislo.cislonode.first_child poradi=str(int(cislo.poradi) + 2),
# kdybyste nad tím někdo taky přemýšleli, tak vcn == VCisleNode :) )
while mozna_tema_vcn != None: # Pokud není číslo pro vzorák, tak se dá do posledního čísla (i kdyby tam mělo být zadání i řešení...)
if type(mozna_tema_vcn) != TemaVCisleNode: # Tohle sice umožňuje vygenerovat vzorák do čísla dávno po konci témátka, ale to nám pro jednoduchost nevadí.
mozna_tema_vcn = mozna_tema_vcn.succ if len(cislo_se_vzorakem) == 0:
cislo_se_vzorakem = cisla[-1]
else:
cislo_se_vzorakem = cislo_se_vzorakem.first()
# FIXME: Tenhle generátor dát asi někam jinam
def potomci(node):
if not isinstance(node, TreeNode):
raise ValueError("Typ {} nemá potomky", type(node))
current_child = node.first_child
while current_child is not None:
yield current_child
current_child = current_child.succ
for mozna_tema_node in potomci(cislo.cislonode):
if not isinstance(mozna_tema_node, TemaVCisleNode):
continue continue
else: tema_node = mozna_tema_node
tema = mozna_tema_vcn.tema tema = tema_node.tema
if not temata[int(tema.kod)-1][1] >= ci+2: # Pokud už témátko skončilo, žádné úložky negenerujeme
mozna_tema_vcn = mozna_tema_vcn.succ # FIXME: Bylo by hezčí, kdyby se čísla předávala jako Cislo a ne jako int v té trojici (start, konec, tema)
if not temata[int(tema.kod)-1][1] >= int(cislo_se_vzorakem.poradi):
continue continue
for i in range(1, rnd.randint(1, 4)): # Generujeme 1 až 4 úložky k tomuto témátku do tohoto čísla
poc_op = rnd.randint(1, 4) for kod in range(1, rnd.randint(1, 4)):
poc_oboru = rnd.randint(1, 2) u = Uloha.objects.create(
p = Uloha.objects.create(
nazev=": ".join([tema.nazev, nazev=": ".join([tema.nazev,
"úloha {}.".format(i)]), "úloha {}.".format(kod)]),
nadproblem=tema, nadproblem=tema,
stav=Problem.STAV_ZADANY, stav=Problem.STAV_ZADANY,
zamereni=tema.zamereni, zamereni=tema.zamereni,
autor=tema.autor, autor=tema.autor,
garant=tema.garant, garant=tema.garant,
kod=str(i), kod=str(kod),
cislo_zadani=cislo, cislo_zadani=cislo,
cislo_reseni=cisla[ci+2-1], cislo_reseni=cislo_se_vzorakem,
cislo_deadline=cisla[ci+2-1], cislo_deadline=cislo_se_vzorakem,
max_body = rnd.randint(1, 8) max_body = rnd.randint(1, 8)
) )
p.opravovatele.set(rnd.sample(organizatori, poc_op)) poc_opravovatelu = rnd.randint(1, 4)
u.opravovatele.set(rnd.sample(organizatori, poc_opravovatelu))
text_zadani = Text.objects.create( # Samotný obsah následně vzniklého Textu zadání
na_web = " ".join( obsah = " ".join(
[rnd.choice(sloveso),
rnd.choice(koho),
rnd.choice(ceho),
rnd.choice(jmeno),
rnd.choice(kde)]
),
do_cisla = " ".join(
[rnd.choice(sloveso), [rnd.choice(sloveso),
rnd.choice(koho), rnd.choice(koho),
rnd.choice(ceho), rnd.choice(ceho),
rnd.choice(jmeno), rnd.choice(jmeno),
rnd.choice(kde)] rnd.choice(kde)]
) )
) text_zadani = Text.objects.create(
na_web = obsah,
do_cisla = obsah,
)
zad = TextNode.objects.create(text = text_zadani) zad = TextNode.objects.create(text = text_zadani)
uloha_zadani = UlohaZadaniNode.objects.create(uloha=p, first_child = zad) uloha_zadani = UlohaZadaniNode.objects.create(uloha=u, first_child = zad)
p.ulohazadaninode = uloha_zadani u.ulohazadaninode = uloha_zadani
otec_syn(mozna_tema_vcn, uloha_zadani)
# TODO dělá se podproblém takto??? TODO # FIXME: Tohle dává zadání vždy jako prvního potomka témátka, spec. se naskládají v opačném pořadí a nemůže mezi nimi vzniknout žádný (orgo-)text
otec_syn(tema_node, uloha_zadani)
# Text vzoráku stejně
obsah = rnd.choice(reseni)
text_vzoraku = Text.objects.create( text_vzoraku = Text.objects.create(
na_web = rnd.choice(reseni), na_web = obsah,
do_cisla = rnd.choice(reseni) do_cisla = obsah,
) )
vzorak = TextNode.objects.create(text = text_vzoraku) vzorak = TextNode.objects.create(text = text_vzoraku)
uloha_vzorak = UlohaVzorakNode.objects.create(uloha=p, first_child = vzorak) uloha_vzorak = UlohaVzorakNode.objects.create(uloha=u, first_child = vzorak)
p.UlohaVzorakNode = uloha_vzorak u.UlohaVzorakNode = uloha_vzorak
res_tema_vcn = cisla[ci+2-1].cislonode.first_child
while res_tema_vcn.tema != tema: # Najdeme správný TemaVCisleNode pro vložení vzoráku
res_tema_vcn = res_tema_vcn.succ res_tema_node = None;
otec_syn(res_tema_vcn, uloha_vzorak) for node in potomci(cislo_se_vzorakem.cislonode):
if isinstance(node, TemaVCisleNode) and node.tema == tema:
res_tema_node = node
if res_tema_node is None:
raise LookupError("Nenalezen Node pro vložení vzoráku")
# FIXME: Stejný problém jako výše: vzoráky se dají na začátek v opačném pořadí.
otec_syn(res_tema_node, uloha_vzorak)
p.save() u.save()
mozna_tema_vcn = mozna_tema_vcn.succ
return return
def gen_novinky(rnd, organizatori): def gen_novinky(rnd, organizatori):
@ -604,6 +631,7 @@ def create_test_data(size = 6, rnd = None):
rocniky = gen_rocniky(last_rocnik, size) rocniky = gen_rocniky(last_rocnik, size)
# cisla # cisla
# rocnik_cisla je pole polí čísel (typ Cislo), vnitřní pole odpovídají jednotlivým ročníkům.
rocnik_cisla = gen_cisla(rnd, rocniky) rocnik_cisla = gen_cisla(rnd, rocniky)
# generování obyčejných úloh do čísel # generování obyčejných úloh do čísel
@ -611,6 +639,7 @@ def create_test_data(size = 6, rnd = None):
# generování témat, zatím v prvních třech číslech po jednom # generování témat, zatím v prvních třech číslech po jednom
# FIXME: více témat # FIXME: více témat
# rocnik_temata je pole polí trojic (první číslo :int, poslední číslo :int, téma:Tema), přičemž každé vnitřní pole odpovídá ročníku a FIXME: je to takhle fuj a když to někdo vidí poprvé, tak je z toho smutný, protože vůbec neví, co se děje a co má čekat.
rocnik_temata = gen_temata(rnd, rocniky, rocnik_cisla, organizatori) rocnik_temata = gen_temata(rnd, rocniky, rocnik_cisla, organizatori)
# generování úloh k tématům ve všech číslech # generování úloh k tématům ve všech číslech

124
seminar/treelib.py

@ -0,0 +1,124 @@
from django.core.exceptions import ObjectDoesNotExist
# NOTE: node.prev a node.succ jsou implementovány přímo v models.TreeNode
# TODO: Všechny tyto funkce se naivně spoléhají na to, že jako parametr dostanou nějaký TreeNode (některé možná zvládnou i None)
# Slouží k debugování pro rychlé získání představy o podobě podstromu pod tímto TreeNode.
def print_tree(node,indent=0):
# 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,node, node.id))
if node.first_child:
print_tree(node.first_child, indent=indent+2)
if node.succ:
print_tree(node.succ, indent=indent)
# Django je trošku hloupé, takže node.prev nevrací None, ale hází django.core.exceptions.ObjectDoesNotExist
def safe_pred(node):
try:
return node.prev
except ObjectDoesNotExist:
return None
# A to samé pro .father_of_first
def safe_father_of_first(node):
return node.prev
except ObjectDoesNotExist:
return None
## Rodinné vztahy
def get_parent(node):
# Nejdřív získáme prvního potomka...
while safe_pred(node) is not None:
node = safe_pred(node)
# ... a z prvního potomka umíme najít rodiče
return safe_father_of_first(node)
# Obecný next: další Node v "the-right-order" pořadí (já, pak potomci, pak sousedé)
def general_next(node):
# Máme potomka?
if node.first_child is not None:
return node.first_child
# Nemáme potomka.
# Chceme najít následníka sebe, nebo některého (toho nejblíž příbuzného) z našich předků (tatínka, dědečka, ...)
while node.succ is None:
node = get_parent(node)
if node is None:
return None # žádný z předků nemá následníka, takže žádny vrchol nenásleduje.
return node.succ
def last_brother(node):
while node.succ is not None:
node = node.succ
return node
def general_prev(node):
# Předchůdce je buď rekurzivně poslední potomek předchůdce, nebo náš otec.
# Otce vyřešíme nejdřív:
if safe_pred(node) is None:
return safe_father_of_first(node)
pred = safe_pred(node)
while pred.first_child is not None:
pred = last_brother(pred.first_child)
# pred nyní nemá žádné potomky, takže je to poslední rekurzivní potomek původního předchůdce
return pred
# Generátor bratrů
# Generátor potomků níže spoléhá na to, že se tohle dá volat i s parametrem None.
def all_brothers(node):
current = node
while current is not None:
yield current
current = current.succ
# Generátor potomků
def all_children(node):
brothers = all_brothers(node.first_child)
for br in brothers:
yield br
# Generátor následníků v "the-right-order"
## Filtrační hledání
# Najdi dalšího bratra nějakého typu, nebo None.
# hledá i podtřídy, i.e. get_next_brother_of_type(neco, TreeNode) je prostě succ.
def get_next_brother_of_type(current, type):
pass
def get_prev_brother_of_type(current, type):
pass
# Totéž pro "the-right-order" pořadí
def get_next_node_of_type(current, type):
pass
def get_next_node_of_type(current, type):
pass
# Editace stromu:
def create_node_after(predecessor, type, **kwargs):
pass
# Vyrábí prvního syna, ostatní nalepí za (existují-li)
def create_child(parent, type, **kwargs):
pass
def create_node_before(some, arguments, but, i, dont, know, which, yet):
pass
# Tohle bude hell.
# ValueError, pokud je (aspoň) jeden parametr None
def swap(node, other):
pass
def swap_pred(node):
pass
def swap_succ(node):
pass
# Rotace stromu
# Dokumentace viz wiki:
# (lower bude jednoduchá rotace, ne mega, existence jednoduché rotace mi došla až po nakreslení obrázku)
def raise_node(node):
pass
def lower_node(node):
pass

2
seminar/urls.py

@ -109,6 +109,7 @@ urlpatterns = [
path('auth/resitel/', views.ResitelView.as_view(), name='seminar_resitel'), path('auth/resitel/', views.ResitelView.as_view(), name='seminar_resitel'),
path('autocomplete/skola/',views.SkolaAutocomplete.as_view(), name='autocomplete_skola'), path('autocomplete/skola/',views.SkolaAutocomplete.as_view(), name='autocomplete_skola'),
path('autocomplete/resitel/',views.ResitelAutocomplete.as_view(), name='autocomplete_resitel'), path('autocomplete/resitel/',views.ResitelAutocomplete.as_view(), name='autocomplete_resitel'),
path('autocomplete/problem/odevzdatelny',views.OdevzdatelnyProblemAutocomplete.as_view(), name='autocomplete_problem_odevzdatelny'),
path('auth/reset_password/', views.PasswordResetView.as_view(), name='reset_password'), path('auth/reset_password/', views.PasswordResetView.as_view(), name='reset_password'),
path('auth/change_password/', views.PasswordChangeView.as_view(), name='change_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_done/', views.PasswordResetDoneView.as_view(), name='reset_password_done'),
@ -118,6 +119,7 @@ urlpatterns = [
path('temp/add_solution', views.AddSolutionView.as_view(),name='seminar_vloz_reseni'), path('temp/add_solution', views.AddSolutionView.as_view(),name='seminar_vloz_reseni'),
path('temp/submit_solution', views.SubmitSolutionView.as_view(),name='seminar_nahraj_reseni'),
path('', views.TitulniStranaView.as_view(), name='titulni_strana'), path('', views.TitulniStranaView.as_view(), name='titulni_strana'),

16
seminar/utils.py

@ -4,6 +4,8 @@ import datetime
from django.contrib.auth.decorators import user_passes_test from django.contrib.auth.decorators import user_passes_test
from html.parser import HTMLParser from html.parser import HTMLParser
import seminar.models as m
staff_member_required = user_passes_test(lambda u: u.is_staff) staff_member_required = user_passes_test(lambda u: u.is_staff)
class FirstTagParser(HTMLParser): class FirstTagParser(HTMLParser):
@ -43,7 +45,6 @@ def from_roman(rom):
def seznam_problemu(): def seznam_problemu():
from .models import Problem, Resitel, Rocnik, Reseni, Cislo
problemy = [] problemy = []
# Pomocna fce k formatovani problemovych hlasek # Pomocna fce k formatovani problemovych hlasek
@ -65,27 +66,26 @@ def seznam_problemu():
# Duplicita jmen # Duplicita jmen
jmena = {} jmena = {}
for r in Resitel.objects.all(): for r in m.Resitel.objects.all():
j = r.plne_jmeno() j = r.plne_jmeno()
if j not in jmena: if j not in jmena:
jmena[j] = [] jmena[j] = []
jmena[j].append(r) jmena[j].append(r)
for j in jmena: for j in jmena:
if len(jmena[j]) > 1: if len(jmena[j]) > 1:
prb(Resitel, u'Duplicitní jméno "%s"' % (j, ), jmena[j]) prb(m.Resitel, u'Duplicitní jméno "%s"' % (j, ), jmena[j])
# Data maturity a narození # Data maturity a narození
for r in Resitel.objects.all(): for r in m.Resitel.objects.all():
if not r.rok_maturity: if not r.rok_maturity:
prb(Resitel, u'Neznámý rok maturity', [r]) prb(m.Resitel, u'Neznámý rok maturity', [r])
if r.rok_maturity and (r.rok_maturity < 1990 or r.rok_maturity > datetime.date.today().year + 10): if r.rok_maturity and (r.rok_maturity < 1990 or r.rok_maturity > datetime.date.today().year + 10):
prb(Resitel, u'Podezřelé datum maturity', [r]) prb(m.Resitel, u'Podezřelé datum maturity', [r])
if r.datum_narozeni and (r.datum_narozeni.year < 1970 or r.datum_narozeni.year > datetime.date.today().year - 12): if r.datum_narozeni and (r.datum_narozeni.year < 1970 or r.datum_narozeni.year > datetime.date.today().year - 12):
prb(Resitel, u'Podezřelé datum narození', [r]) prb(m.Resitel, u'Podezřelé datum narození', [r])
# if not r.email: # if not r.email:
# prb(Resitel, u'Neznámý email', [r]) # prb(Resitel, u'Neznámý email', [r])
return problemy return problemy

2
seminar/views/__init__.py

@ -0,0 +1,2 @@
from .views_all import *
from .autocomplete import *

63
seminar/views/autocomplete.py

@ -0,0 +1,63 @@
from dal import autocomplete
from django.shortcuts import get_object_or_404
import seminar.models as m
from .helpers import LoginRequiredAjaxMixin
class SkolaAutocomplete(autocomplete.Select2QuerySetView):
def get_queryset(self):
# Don't forget to filter out results depending on the visitor !
qs = m.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 ResitelAutocomplete(LoginRequiredAjaxMixin,autocomplete.Select2QuerySetView):
def get_queryset(self):
qs = m.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
class OdevzdatelnyProblemAutocomplete(autocomplete.Select2QuerySetView):
def get_queryset(self):
nastaveni = get_object_or_404(m.Nastaveni)
rocnik = nastaveni.aktualni_rocnik
temata = m.Tema.objects.filter(rocnik=rocnik, stav=m.Problem.STAV_ZADANY)
ulohy = m.Uloha.objects.filter(cislo_deadline__rocnik = rocnik)
ulohy.union(temata)
qs = ulohy
if self.q:
qs = qs.filter(
Q(nazev__startswith=self.q))
return qs
# Ceka na autocomplete v3
# class OrganizatorAutocomplete(autocomplete.Select2QuerySetView):
# def get_queryset(self):
# if not self.request.user.is_authenticated():
# return Organizator.objects.none()
#
# qs = aktivniOrganizatori()
#
# if self.q:
# if self.q[0] == "!":
# qs = Organizator.objects.all()
# query = self.q[1:]
# else:
# query = self.q
# qs = qs.filter(
# Q(prezdivka__isstartswith=query)|
# Q(user__first_name__isstartswith=query)|
# Q(user__last_name__isstartswith=query))
#
# return qs

8
seminar/views/helpers.py

@ -0,0 +1,8 @@
from dal import autocomplete
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)

0
seminar/unicodecsv.py → seminar/views/unicodecsv.py

91
seminar/views/utils.py

@ -0,0 +1,91 @@
# -*- coding: utf-8 -*-
import datetime
from django.contrib.auth.decorators import user_passes_test
from html.parser import HTMLParser
import seminar.models as m
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:
if i not in d:
d[i] = 0
d[i] += 1
return d
roman_numerals = zip((1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1),
('M', 'CM', 'D', 'CD','C', 'XC','L','XL','X','IX','V','IV','I'))
def roman(num):
res = ""
for i, n in roman_numerals:
res += n * (num // i)
num %= i
return res
def from_roman(rom):
if not rom:
return 0
for i, n in roman_numerals:
if rom.upper().startswith(n):
return i + from_roman(rom[len(n):])
raise Exception('Invalid roman numeral: "%s"', rom)
def seznam_problemu():
problemy = []
# Pomocna fce k formatovani problemovych hlasek
def prb(cls, msg, objs=None):
s = u'<b>%s:</b> %s' % (cls.__name__, msg)
if objs:
s += u' ['
for o in objs:
try:
url = o.admin_url()
except:
url = None
if url:
s += u'<a href="%s">%s</a>, ' % (url, o.pk, )
else:
s += u'%s, ' % (o.pk, )
s = s[:-2] + u']'
problemy.append(s)
# Duplicita jmen
jmena = {}
for r in m.Resitel.objects.all():
j = r.plne_jmeno()
if j not in jmena:
jmena[j] = []
jmena[j].append(r)
for j in jmena:
if len(jmena[j]) > 1:
prb(m.Resitel, u'Duplicitní jméno "%s"' % (j, ), jmena[j])
# Data maturity a narození
for r in m.Resitel.objects.all():
if not r.rok_maturity:
prb(m.Resitel, u'Neznámý rok maturity', [r])
if r.rok_maturity and (r.rok_maturity < 1990 or r.rok_maturity > datetime.date.today().year + 10):
prb(m.Resitel, u'Podezřelé datum maturity', [r])
if r.datum_narozeni and (r.datum_narozeni.year < 1970 or r.datum_narozeni.year > datetime.date.today().year - 12):
prb(m.Resitel, u'Podezřelé datum narození', [r])
# if not r.email:
# prb(Resitel, u'Neznámý email', [r])
return problemy

94
seminar/views.py → seminar/views/views_all.py

@ -9,20 +9,19 @@ from django.utils.translation import ugettext as _
from django.http import Http404,HttpResponseBadRequest,HttpResponseRedirect from django.http import Http404,HttpResponseBadRequest,HttpResponseRedirect
from django.db.models import Q, Sum, Count from django.db.models import Q, Sum, Count
from django.views.decorators.csrf import ensure_csrf_cookie from django.views.decorators.csrf import ensure_csrf_cookie
from django.views.generic.edit import FormView from django.views.generic.edit import FormView, CreateView
from django.contrib.auth import authenticate, login, get_user_model, logout from django.contrib.auth import authenticate, login, get_user_model, logout
from django.contrib.auth import views as auth_views from django.contrib.auth import views as auth_views
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.auth.mixins import LoginRequiredMixin
from django.db import transaction from django.db import transaction
from dal import autocomplete
import seminar.models as s 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 seminar.models import Problem, Cislo, Reseni, Nastaveni, Rocnik, Soustredeni, Organizator, Resitel, Novinky, Soustredeni_Ucastnici, Pohadka, Tema, Clanek, Osoba, Skola # Tohle je stare a chceme se toho zbavit. Pouzivejte s.ToCoChci
#from .models import VysledkyZaCislo, VysledkyKCisluZaRocnik, VysledkyKCisluOdjakziva #from .models import VysledkyZaCislo, VysledkyKCisluZaRocnik, VysledkyKCisluOdjakziva
from . import utils from seminar import utils
from .unicodecsv import UnicodeWriter from .unicodecsv import UnicodeWriter
from .forms import PrihlaskaForm, LoginForm, ProfileEditForm from seminar.forms import PrihlaskaForm, LoginForm, ProfileEditForm
import seminar.forms as f import seminar.forms as f
from datetime import timedelta, date, datetime from datetime import timedelta, date, datetime
@ -1223,6 +1222,42 @@ class AddSolutionView(LoginRequiredMixin, FormView):
form_class = f.VlozReseniForm form_class = f.VlozReseniForm
success_url = '/' success_url = '/'
class SubmitSolutionView(LoginRequiredMixin, CreateView):
model = s.Reseni
template_name = 'seminar/nahraj_reseni.html'
form_class = f.NahrajReseniForm
success_url = '/'
def get_context_data(self,**kwargs):
data = super().get_context_data(**kwargs)
if self.request.POST:
data['prilohy'] = f.ReseniSPrilohamiFormSet(self.request.POST,self.request.FILES)
else:
data['prilohy'] = f.ReseniSPrilohamiFormSet()
return data
# FIXME prepsat tak, aby form_valid se volalo jen tehdy, kdyz je form i formset validni
# Inspirace: https://stackoverflow.com/questions/41599809/using-a-django-filefield-in-an-inline-formset
def form_valid(self,form):
context = self.get_context_data()
prilohy = context['prilohy']
if not prilohy.is_valid():
return super().form_invalid(form)
with transaction.atomic():
self.object = form.save()
self.object.resitele.add(Resitel.objects.get(osoba__user = self.request.user))
self.object.cas_doruceni = timezone.now()
self.object.forma = s.Reseni.FORMA_UPLOAD
self.object.save()
prilohy.instance = self.object
prilohy.save()
return HttpResponseRedirect(self.get_success_url())
def resetPasswordView(request): def resetPasswordView(request):
pass pass
@ -1384,59 +1419,10 @@ def prihlaskaView(request):
return render(request, 'seminar/prihlaska.html', {'form': form}) return render(request, 'seminar/prihlaska.html', {'form': form})
class SkolaAutocomplete(autocomplete.Select2QuerySetView):
def get_queryset(self):
# Don't forget to filter out results depending on the visitor !
qs = Skola.objects.all()
if self.q:
qs = qs.filter(
Q(nazev__istartswith=self.q)|
Q(kratky_nazev__istartswith=self.q)|
Q(ulice__istartswith=self.q)|
Q(mesto__istartswith=self.q))
return qs
class LoginRequiredAjaxMixin(object):
def dispatch(self, request, *args, **kwargs):
#if request.is_ajax() and not request.user.is_authenticated: # Pokud to otevřu jako stránku, tak se omezení neuplatní, takže to asi nechceme
if not request.user.is_authenticated:
return JsonResponse(data={'results': [], 'pagination': {}}, status=401)
return super(LoginRequiredAjaxMixin, self).dispatch(request, *args, **kwargs)
class ResitelAutocomplete(LoginRequiredAjaxMixin,autocomplete.Select2QuerySetView):
def get_queryset(self):
qs = Resitel.objects.all()
if self.q:
qs = qs.filter(
Q(osoba__jmeno__startswith=self.q)|
Q(osoba__prijmeni__startswith=self.q)|
Q(osoba__prezdivka__startswith=self.q)
)
return qs
# Ceka na autocomplete v3
# class OrganizatorAutocomplete(autocomplete.Select2QuerySetView):
# def get_queryset(self):
# if not self.request.user.is_authenticated():
# return Organizator.objects.none()
#
# qs = aktivniOrganizatori()
#
# if self.q:
# if self.q[0] == "!":
# qs = Organizator.objects.all()
# query = self.q[1:]
# else:
# query = self.q
# qs = qs.filter(
# Q(prezdivka__isstartswith=query)|
# Q(user__first_name__isstartswith=query)|
# Q(user__last_name__isstartswith=query))
#
# return qs
# FIXME: Tohle asi vlastně vůbec nepatří do aplikace 'seminar' # FIXME: Tohle asi vlastně vůbec nepatří do aplikace 'seminar'
class LoginView(auth_views.LoginView): class LoginView(auth_views.LoginView):
# Jen vezmeme vestavěný a dáme mu vhodný template a přesměrovací URL # Jen vezmeme vestavěný a dáme mu vhodný template a přesměrovací URL

1
sitetree_new.json

File diff suppressed because one or more lines are too long
Loading…
Cancel
Save