Anet
5 years ago
21 changed files with 1004 additions and 155 deletions
Binary file not shown.
File diff suppressed because one or more lines are too long
@ -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'), |
|||
), |
|||
] |
@ -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'), |
|||
), |
|||
] |
@ -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'), |
|||
), |
|||
] |
@ -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'), |
|||
), |
|||
] |
@ -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); |
|||
}); |
|||
}); |
|||
|
@ -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 %} |
@ -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 |
@ -0,0 +1,2 @@ |
|||
from .views_all import * |
|||
from .autocomplete import * |
@ -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 |
@ -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,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 |
|||
|
|||
|
File diff suppressed because one or more lines are too long
Loading…
Reference in new issue