Merge pull request 'Vylepšení odevzdávátka' (!13) from vylepseni_odevzdavatka into master
Reviewed-on: #13
This commit is contained in:
commit
3110eb92a5
17 changed files with 218 additions and 170 deletions
|
@ -22,6 +22,7 @@ urlpatterns = [
|
||||||
# Autocomplete
|
# Autocomplete
|
||||||
path('api/autocomplete/skola/', views.SkolaAutocomplete.as_view(), name='autocomplete_skola'),
|
path('api/autocomplete/skola/', views.SkolaAutocomplete.as_view(), name='autocomplete_skola'),
|
||||||
path('api/autocomplete/resitel/', org_required(views.ResitelAutocomplete.as_view()), name='autocomplete_resitel'),
|
path('api/autocomplete/resitel/', org_required(views.ResitelAutocomplete.as_view()), name='autocomplete_resitel'),
|
||||||
|
path('api/autocomplete/resitel_public/', views.PublicResitelAutocomplete.as_view(), name='autocomplete_resitel_public'),
|
||||||
path('api/autocomplete/problem/odevzdatelny', views.OdevzdatelnyProblemAutocomplete.as_view(), name='autocomplete_problem_odevzdatelny'),
|
path('api/autocomplete/problem/odevzdatelny', views.OdevzdatelnyProblemAutocomplete.as_view(), name='autocomplete_problem_odevzdatelny'),
|
||||||
path('api/autocomplete/problem/vsechny', views.ProblemAutocomplete.as_view(), name='autocomplete_problem'),
|
path('api/autocomplete/problem/vsechny', views.ProblemAutocomplete.as_view(), name='autocomplete_problem'),
|
||||||
|
|
||||||
|
|
|
@ -44,6 +44,29 @@ class ResitelAutocomplete(LoginRequiredAjaxMixin,autocomplete.Select2QuerySetVie
|
||||||
qs = qs.filter(query)
|
qs = qs.filter(query)
|
||||||
return qs
|
return qs
|
||||||
|
|
||||||
|
|
||||||
|
class PublicResitelAutocomplete(LoginRequiredAjaxMixin, autocomplete.Select2QuerySetView):
|
||||||
|
"""
|
||||||
|
View k :mod:`dal.autocomplete` pro vyhledávání řešitelů podle přezdívky
|
||||||
|
především v odevzdávátku.
|
||||||
|
"""
|
||||||
|
def get_queryset(self):
|
||||||
|
letos = m.Nastaveni.get_solo().aktualni_rocnik
|
||||||
|
qs = m.Resitel.objects.filter(
|
||||||
|
rok_maturity__gte=letos.druhy_rok()
|
||||||
|
).filter(
|
||||||
|
prezdivka_resitele__isnull=False
|
||||||
|
).exclude(
|
||||||
|
prezdivka_resitele=""
|
||||||
|
).filter(
|
||||||
|
prezdivka_resitele__icontains=self.q
|
||||||
|
).all()
|
||||||
|
return qs
|
||||||
|
|
||||||
|
def get_result_label(self, result):
|
||||||
|
return result.prezdivka_resitele
|
||||||
|
|
||||||
|
|
||||||
class OdevzdatelnyProblemAutocomplete(autocomplete.Select2QuerySetView):
|
class OdevzdatelnyProblemAutocomplete(autocomplete.Select2QuerySetView):
|
||||||
""" View k :mod:`dal.autocomplete` pro vyhledávání problémů především v odevzdávátku. """
|
""" View k :mod:`dal.autocomplete` pro vyhledávání problémů především v odevzdávátku. """
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
|
|
|
@ -63,7 +63,7 @@ class PosliReseniForm(forms.Form):
|
||||||
class NahrajReseniForm(forms.ModelForm):
|
class NahrajReseniForm(forms.ModelForm):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = m.Reseni
|
model = m.Reseni
|
||||||
fields = ('problem',)
|
fields = ('problem', 'resitele')
|
||||||
help_texts = {'problem':''} # Nezobrazovat help text ve formuláři
|
help_texts = {'problem':''} # Nezobrazovat help text ve formuláři
|
||||||
|
|
||||||
widgets = {'problem':
|
widgets = {'problem':
|
||||||
|
@ -72,6 +72,13 @@ class NahrajReseniForm(forms.ModelForm):
|
||||||
attrs = {'data-placeholder--id': '-1',
|
attrs = {'data-placeholder--id': '-1',
|
||||||
'data-placeholder--text' : '---',
|
'data-placeholder--text' : '---',
|
||||||
'data-allow-clear': 'true'},
|
'data-allow-clear': 'true'},
|
||||||
|
),
|
||||||
|
'resitele':
|
||||||
|
autocomplete.ModelSelect2Multiple(
|
||||||
|
url='autocomplete_resitel_public',
|
||||||
|
attrs = {'data-placeholder--id': '-1',
|
||||||
|
'data-placeholder--text' : '---',
|
||||||
|
'data-allow-clear': 'true'},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
22
odevzdavatko/static/odevzdavatko/check_for_detail.js
Normal file
22
odevzdavatko/static/odevzdavatko/check_for_detail.js
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
// Kontrola, že org neposílá nějakou blbost v detail.html
|
||||||
|
|
||||||
|
function zkontroluj_hodnoceni() {
|
||||||
|
const pocet = $('.hodnoceni').length;
|
||||||
|
if (pocet === 1) { // vidím pouze plusko
|
||||||
|
const vysledek = confirm("Odstranil jsi všechny problémy tohoto řešení. Nepůjde tedy dohledat přes problémy, co řeší, tj. například v došlých řešeních. Přesto odeslat?");
|
||||||
|
if (!vysledek) {
|
||||||
|
event.preventDefault();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function problem_is_empty(elem, index, array) {return elem.firstElementChild.children.length !== 1 && elem.firstElementChild.children[1].textContent === "";}
|
||||||
|
|
||||||
|
if ($('.hodnoceni').toArray().some(problem_is_empty)) {
|
||||||
|
alert("Neuloženo! Nezadal jsi problém, ke kterému posíláš hodnocení. Pokud je toto hodnocení navíc, smaž ho prosím křížkem a znovu odešli.")
|
||||||
|
event.preventDefault()
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
|
@ -0,0 +1,56 @@
|
||||||
|
// FIXME: Necopypastovat! Tohle je zkopírované ze static/odevzdavatko/dynamic_formsets.js
|
||||||
|
|
||||||
|
|
||||||
|
// 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('tr').remove();
|
||||||
|
var forms = $('.hodnoceni');
|
||||||
|
var formCount = forms.length - 1; // There is one extra such form hidden as template!
|
||||||
|
$('#id_' + prefix + '-TOTAL_FORMS').val(formCount);
|
||||||
|
for (var i=0; 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(){
|
||||||
|
$('#pridat_hodnoceni').click(function() {
|
||||||
|
var form_idx = $('#id_form-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
|
||||||
|
$('.smazat_hodnoceni').click(function(){
|
||||||
|
deleteForm("form",this);
|
||||||
|
});
|
||||||
|
// Copy deadline
|
||||||
|
if (form_idx !== "0") {
|
||||||
|
$('#id_form-' + form_idx + '-deadline_body')[0].value = $('#id_form-' + (form_idx - 1) + '-deadline_body')[0].value
|
||||||
|
}
|
||||||
|
$('#id_form-TOTAL_FORMS').val(parseInt(form_idx) + 1);
|
||||||
|
});
|
||||||
|
$('.smazat_hodnoceni').click(function(){
|
||||||
|
deleteForm("form",this);
|
||||||
|
});
|
||||||
|
});
|
|
@ -4,68 +4,21 @@
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
|
||||||
{# FIXME: Necopypastovat! Tohle je zkopírované ze static/odevzdavatko/dynamic_formsets.js #}
|
{% if edit %}
|
||||||
<script type='text/javascript'>
|
<script src="{% static 'odevzdavatko/dynamic_formsets_for_detail.js' %}"></script>
|
||||||
// Credit https://medium.com/all-about-django/adding-forms-dynamically-to-a-django-formset-375f1090c2b0
|
<script src="{% static 'odevzdavatko/check_for_detail.js' %}"></script>
|
||||||
function updateElementIndex(el, prefix, ndx) {
|
{% endif %}
|
||||||
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('tr').remove();
|
|
||||||
var forms = $('.hodnoceni');
|
|
||||||
var formCount = forms.length - 1; // There is one extra such form hidden as template!
|
|
||||||
$('#id_' + prefix + '-TOTAL_FORMS').val(formCount);
|
|
||||||
for (var i=0; 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(){
|
|
||||||
$('#pridat_hodnoceni').click(function() {
|
|
||||||
var form_idx = $('#id_form-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
|
|
||||||
$('.smazat_hodnoceni').click(function(){
|
|
||||||
deleteForm("form",this);
|
|
||||||
});
|
|
||||||
// Copy deadline
|
|
||||||
if (form_idx !== "0") {
|
|
||||||
$('#id_form-' + form_idx + '-deadline_body')[0].value = $('#id_form-' + (form_idx - 1) + '-deadline_body')[0].value
|
|
||||||
}
|
|
||||||
$('#id_form-TOTAL_FORMS').val(parseInt(form_idx) + 1);
|
|
||||||
});
|
|
||||||
$('.smazat_hodnoceni').click(function(){
|
|
||||||
deleteForm("form",this);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
|
|
||||||
<p>Řešené problémy: {{ object.problem.all | join:", " }}</p>
|
<p>Řešené problémy: {{ object.problem.all | join:", " }}</p>
|
||||||
|
|
||||||
<p>Řešitelé: {% for r in object.resitele.all %} {{ r }} (<a href="mailto:{{ r.osoba.email }}?subject={{ "Oprava řešení M&M " | urlencode }}{{ object.problem.all.0.hlavni_problem | urlencode }}">{{ r.osoba.email }}</a>)
|
{% if edit %}
|
||||||
{% if forloop.revcounter0 != 0 %}, {% endif %} {% endfor %}</p>
|
<p>Řešitelé:
|
||||||
|
{% for r in object.resitele.all %}{{ r }} (<a href="mailto:{{ r.osoba.email }}?subject={{ "Oprava řešení M&M " | urlencode }}{{ object.problem.all.0.hlavni_problem | urlencode }}">{{ r.osoba.email }}</a>){% if forloop.revcounter0 != 0 %}, {% endif %}{% endfor %}
|
||||||
|
</p>
|
||||||
|
{% else %}
|
||||||
|
<p>Řešitelé: {{ object.resitele.all | join:", " }}</p>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
{# https://docs.djangoproject.com/en/3.1/ref/models/instances/#django.db.models.Model.get_FOO_display #}
|
{# https://docs.djangoproject.com/en/3.1/ref/models/instances/#django.db.models.Model.get_FOO_display #}
|
||||||
<p>Forma: {{ object.get_forma_display }}</p>
|
<p>Forma: {{ object.get_forma_display }}</p>
|
||||||
|
@ -82,13 +35,13 @@ $(document).ready(function(){
|
||||||
<td><a href="{{ priloha.soubor.url }}" download>{{ priloha.split | last }}</a></td>
|
<td><a href="{{ priloha.soubor.url }}" download>{{ priloha.split | last }}</a></td>
|
||||||
<td>{{ priloha.res_poznamka }}</td>
|
<td>{{ priloha.res_poznamka }}</td>
|
||||||
<td>{{ priloha.vytvoreno }}</td></tr>
|
<td>{{ priloha.vytvoreno }}</td></tr>
|
||||||
{# TODO: Orgo-poznámka, ideálně jako formulář #}
|
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</table>
|
</table>
|
||||||
{% else %}
|
{% else %}
|
||||||
<p>Žádné přílohy</p>
|
<p>Žádné přílohy</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
{% if edit %}
|
||||||
<form method=post onsubmit="return zkontroluj_hodnoceni();">
|
<form method=post onsubmit="return zkontroluj_hodnoceni();">
|
||||||
{# Poznámka #}
|
{# Poznámka #}
|
||||||
<h3>Neveřejná poznámka:</h3>
|
<h3>Neveřejná poznámka:</h3>
|
||||||
|
@ -116,7 +69,7 @@ $(document).ready(function(){
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
|
|
||||||
<a href="#" title="Přidat hodnocení"> <img src="{% static "odevzdavatko/plus.png" %}" id="pridat_hodnoceni" alt="Přidat hodnocení"></a> </br>
|
<a href="#" title="Přidat hodnocení"> <img src="{% static "odevzdavatko/plus.png" %}" id="pridat_hodnoceni" alt="Přidat hodnocení"></a> <br/>
|
||||||
<input type=submit value="Uložit"></form>
|
<input type=submit value="Uložit"></form>
|
||||||
|
|
||||||
<table id="empty_form" style="display: none;">
|
<table id="empty_form" style="display: none;">
|
||||||
|
@ -129,28 +82,19 @@ $(document).ready(function(){
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
|
{% else %}
|
||||||
|
<h3>Hodnocení:</h3>
|
||||||
|
<table class="dosla_reseni">
|
||||||
|
<tr><th>Problém</th><th>Body</th><th>Zpětná vazba od opravovatele</th></tr>
|
||||||
|
{% for h in hodnoceni %}
|
||||||
|
<tr class="hodnoceni">
|
||||||
|
<td>{{ h.problem }}</td>
|
||||||
|
<td>{{ h.body }}</td>
|
||||||
|
<td>{{ h.feedback }}</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</table>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
<script type="text/javascript">
|
|
||||||
function zkontroluj_hodnoceni() {
|
|
||||||
const pocet = $('.hodnoceni').length;
|
|
||||||
if (pocet === 1) { {# vydím pouze plusko #}
|
|
||||||
const vysledek = confirm("Odstranil jsi všechny problémy tohoto řešení. Nepůjde tedy dohledat přes problémy, co řeší, tj. například v došlých řešeních. Přesto odeslat?");
|
|
||||||
if (!vysledek) {
|
|
||||||
event.preventDefault();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function problem_is_empty(elem, index, array) {return elem.firstElementChild.children.length !== 1 && elem.firstElementChild.children[1].textContent === "";}
|
|
||||||
|
|
||||||
if ($('.hodnoceni').toArray().some(problem_is_empty)) {
|
|
||||||
alert("Neuloženo! Nezadal jsi problém, ke kterému posíláš hodnocení. Pokud je toto hodnocení navíc, smaž ho prosím křížkem a znovu odešli.")
|
|
||||||
event.preventDefault()
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -1,51 +0,0 @@
|
||||||
{% extends "base.html" %}
|
|
||||||
{% load static %}
|
|
||||||
{% load deadliny %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
|
|
||||||
<p>Řešené problémy: {{ object.problem.all | join:", " }}</p>
|
|
||||||
|
|
||||||
<p>Řešitelé: {% for r in object.resitele.all %} {{ r }}
|
|
||||||
{% if forloop.revcounter0 != 0 %}, {% endif %} {% endfor %}</p>
|
|
||||||
|
|
||||||
{# https://docs.djangoproject.com/en/3.1/ref/models/instances/#django.db.models.Model.get_FOO_display #}
|
|
||||||
<p>Forma: {{ object.get_forma_display }}</p>
|
|
||||||
|
|
||||||
<p>Doručeno {{ object.cas_doruceni }}, deadline: {{object.deadline_reseni | deadline_html }}</p>
|
|
||||||
|
|
||||||
{# Soubory: #}
|
|
||||||
<h3>Přílohy:</h3>
|
|
||||||
{% if object.prilohy.all %}
|
|
||||||
<table class="dosla_reseni">
|
|
||||||
<tr><th>Soubor</th><th>Řešitelova poznámka</th><th>Datum</th></tr>
|
|
||||||
{% for priloha in object.prilohy.all %}
|
|
||||||
<tr>
|
|
||||||
<td><a href="{{ priloha.soubor.url }}" download>{{ priloha.split | last }}</a></td>
|
|
||||||
<td>{{ priloha.res_poznamka }}</td>
|
|
||||||
<td>{{ priloha.vytvoreno }}</td></tr>
|
|
||||||
{# TODO: Orgo-poznámka, ideálně jako formulář #}
|
|
||||||
{% endfor %}
|
|
||||||
</table>
|
|
||||||
{% else %}
|
|
||||||
<p>Žádné přílohy</p>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{#<h3>Poznámka:</h3>#}
|
|
||||||
{#<p>{{ poznamka }}</p>#}
|
|
||||||
|
|
||||||
{# Hodnocení: #}
|
|
||||||
<h3>Hodnocení:</h3>
|
|
||||||
<table id="form_set" class="dosla_reseni">
|
|
||||||
<tr><th>Problém</th><th>Body</th><th>Zpětná vazba od opravovatele</th>{# <th>Deadline pro body</th> #}</tr>
|
|
||||||
{% for h in hodnoceni %}
|
|
||||||
<tr class="hodnoceni">
|
|
||||||
<td>{{ h.problem }}</td>
|
|
||||||
<td>{{ h.body }}</td>
|
|
||||||
<td>{{ h.feedback }}</td>
|
|
||||||
{# <td>{{ h.deadline_body }}</td>#}
|
|
||||||
</tr>
|
|
||||||
{% endfor %}
|
|
||||||
</table>
|
|
||||||
|
|
||||||
{% endblock %}
|
|
|
@ -13,6 +13,8 @@
|
||||||
|
|
||||||
<p style="text-align: justify">Když řešení různých témátek vložíš každé zvlášť, lépe se v nich vyznáme a třeba ti je i rychleji opravíme.</p>
|
<p style="text-align: justify">Když řešení různých témátek vložíš každé zvlášť, lépe se v nich vyznáme a třeba ti je i rychleji opravíme.</p>
|
||||||
|
|
||||||
|
<p>Pokud řešíte ve více lidech, je <strong>nutné</strong> přidat tyto lidi jako „Autory řešení“. V tomto poli se vyhledává podle přezdívek, které si lze nastavit v „Osobní údaje“. Sebe vyplňovat nemusíte a za skupinu odevzdávejte pouze <strong>jednou</strong> (ne každý sám).</p>
|
||||||
|
|
||||||
<form enctype="multipart/form-data" action="{% url 'seminar_nahraj_reseni' %}" method="post" onsubmit="return zkontroluj_prilohy();">
|
<form enctype="multipart/form-data" action="{% url 'seminar_nahraj_reseni' %}" method="post" onsubmit="return zkontroluj_prilohy();">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<table class='form' id="reseni">
|
<table class='form' id="reseni">
|
||||||
|
|
|
@ -26,9 +26,9 @@ urlpatterns = [
|
||||||
path('org/reseni/', org_required(views.TabulkaOdevzdanychReseniView.as_view()), name='odevzdavatko_tabulka'),
|
path('org/reseni/', org_required(views.TabulkaOdevzdanychReseniView.as_view()), name='odevzdavatko_tabulka'),
|
||||||
path('org/reseni/rocnik/<int:rocnik>/', org_required(views.TabulkaOdevzdanychReseniView.as_view()), name='odevzdavatko_tabulka'),
|
path('org/reseni/rocnik/<int:rocnik>/', org_required(views.TabulkaOdevzdanychReseniView.as_view()), name='odevzdavatko_tabulka'),
|
||||||
path('org/reseni/<int:problem>/<int:resitel>/', org_required(views.ReseniProblemuView.as_view()), name='odevzdavatko_reseni_resitele_k_problemu'),
|
path('org/reseni/<int:problem>/<int:resitel>/', org_required(views.ReseniProblemuView.as_view()), name='odevzdavatko_reseni_resitele_k_problemu'),
|
||||||
path('org/reseni/<int:pk>', org_required(viewMethodSwitch(get=views.DetailReseniView.as_view(), post=views.hodnoceniReseniView)), name='odevzdavatko_detail_reseni'),
|
path('org/reseni/<int:pk>', org_required(viewMethodSwitch(get=views.EditReseniView.as_view(), post=views.hodnoceniReseniView)), name='odevzdavatko_detail_reseni'),
|
||||||
path('org/reseni/all', org_required(views.SeznamReseniView.as_view())),
|
path('org/reseni/all', org_required(views.SeznamReseniView.as_view())),
|
||||||
path('org/reseni/akt', org_required(views.SeznamAktualnichReseniView.as_view())),
|
path('org/reseni/akt', org_required(views.SeznamAktualnichReseniView.as_view())),
|
||||||
|
|
||||||
path('resitel/reseni/<int:pk>', resitel_or_org_required(views.ResitelReseniView.as_view()), name='odevzdavatko_resitel_reseni'),
|
path('resitel/reseni/<int:pk>', resitel_or_org_required(views.DetailReseniView.as_view()), name='odevzdavatko_resitel_reseni'),
|
||||||
]
|
]
|
||||||
|
|
|
@ -216,6 +216,7 @@ class ReseniProblemuView(MultipleObjectTemplateResponseMixin, MultipleObjectMixi
|
||||||
|
|
||||||
## XXX: https://docs.djangoproject.com/en/3.1/topics/class-based-views/mixins/#avoid-anything-more-complex
|
## XXX: https://docs.djangoproject.com/en/3.1/topics/class-based-views/mixins/#avoid-anything-more-complex
|
||||||
class DetailReseniView(DetailView):
|
class DetailReseniView(DetailView):
|
||||||
|
""" Náhled na řešení. Editace je v :py:class:`EditReseniView`. """
|
||||||
model = m.Reseni
|
model = m.Reseni
|
||||||
template_name = 'odevzdavatko/detail.html'
|
template_name = 'odevzdavatko/detail.html'
|
||||||
|
|
||||||
|
@ -232,18 +233,44 @@ class DetailReseniView(DetailView):
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def get_context_data(self, **kw):
|
def get_context_data(self, **kw):
|
||||||
|
self.check_access()
|
||||||
ctx = super().get_context_data(**kw)
|
ctx = super().get_context_data(**kw)
|
||||||
ctx['form'] = f.OhodnoceniReseniFormSet(
|
hodnoceni = self.aktualni_hodnoceni()
|
||||||
initial = self.aktualni_hodnoceni()
|
ctx["hodnoceni"] = hodnoceni
|
||||||
)
|
|
||||||
ctx['poznamka_form'] = f.PoznamkaReseniForm(instance=self.reseni)
|
|
||||||
return ctx
|
return ctx
|
||||||
|
|
||||||
|
def get(self, request, *args, **kwargs):
|
||||||
|
"""
|
||||||
|
Oproti :py:class:`django.views.generic.detail.BaseDetailView`
|
||||||
|
kontroluje přístup pomocí :py:meth:`check_access`
|
||||||
|
"""
|
||||||
|
response = super().get(self, request, *args, **kwargs)
|
||||||
|
self.check_access()
|
||||||
|
return response
|
||||||
|
|
||||||
|
def check_access(self):
|
||||||
|
""" Řešitel musí být součástí řešení, jinak se na něj nemá co dívat. """
|
||||||
|
if not self.object.resitele.filter(osoba__user=self.request.user).exists():
|
||||||
|
raise PermissionDenied()
|
||||||
|
|
||||||
|
|
||||||
|
class EditReseniView(DetailReseniView):
|
||||||
|
""" Editace (hlavně hodnocení) řešení. """
|
||||||
|
def get_context_data(self, **kw):
|
||||||
|
ctx = super().get_context_data(**kw)
|
||||||
|
ctx['form'] = f.OhodnoceniReseniFormSet(initial=ctx["hodnoceni"])
|
||||||
|
ctx['poznamka_form'] = f.PoznamkaReseniForm(instance=self.reseni)
|
||||||
|
ctx['edit'] = True
|
||||||
|
return ctx
|
||||||
|
|
||||||
|
def check_access(self):
|
||||||
|
# Na orga máme nároky už v urls.py ale better safe then sorry
|
||||||
|
if not self.request.user.je_org:
|
||||||
|
raise PermissionDenied()
|
||||||
|
|
||||||
|
|
||||||
def hodnoceniReseniView(request, pk, *args, **kwargs):
|
def hodnoceniReseniView(request, pk, *args, **kwargs):
|
||||||
reseni = get_object_or_404(m.Reseni, pk=pk)
|
reseni = get_object_or_404(m.Reseni, pk=pk)
|
||||||
template_name = 'odevzdavatko/detail.html'
|
|
||||||
form_class = f.OhodnoceniReseniFormSet
|
|
||||||
success_url = reverse('odevzdavatko_detail_reseni', kwargs={'pk': pk})
|
success_url = reverse('odevzdavatko_detail_reseni', kwargs={'pk': pk})
|
||||||
|
|
||||||
# FIXME: Použit initial i tady a nebastlit hodnocení tak nízkoúrovňově
|
# FIXME: Použit initial i tady a nebastlit hodnocení tak nízkoúrovňově
|
||||||
|
@ -275,33 +302,6 @@ def hodnoceniReseniView(request, pk, *args, **kwargs):
|
||||||
return redirect(success_url)
|
return redirect(success_url)
|
||||||
|
|
||||||
|
|
||||||
class ResitelReseniView(DetailView):
|
|
||||||
model = m.Reseni
|
|
||||||
template_name = 'odevzdavatko/detail_resitele.html'
|
|
||||||
|
|
||||||
def aktualni_hodnoceni(self):
|
|
||||||
self.reseni = get_object_or_404(m.Reseni, id=self.kwargs['pk'])
|
|
||||||
result = []
|
|
||||||
for hodn in m.Hodnoceni.objects.filter(reseni=self.reseni):
|
|
||||||
result.append(
|
|
||||||
{
|
|
||||||
"problem": hodn.problem,
|
|
||||||
"body": hodn.body,
|
|
||||||
"feedback": hodn.feedback,
|
|
||||||
# "deadline_body": hodn.deadline_body,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
return result
|
|
||||||
|
|
||||||
def get_context_data(self, **kw):
|
|
||||||
ctx = super().get_context_data(**kw)
|
|
||||||
hodnoceni = self.aktualni_hodnoceni()
|
|
||||||
if not self.reseni.resitele.filter(osoba__user=self.request.user).exists():
|
|
||||||
raise PermissionDenied()
|
|
||||||
# ctx['poznamka'] = f.PoznamkaReseniForm(instance=self.reseni)
|
|
||||||
ctx["hodnoceni"] = hodnoceni
|
|
||||||
return ctx
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class PrehledOdevzdanychReseni(ListView):
|
class PrehledOdevzdanychReseni(ListView):
|
||||||
|
@ -413,6 +413,7 @@ class NahrajReseniView(LoginRequiredMixin, CreateView):
|
||||||
with transaction.atomic():
|
with transaction.atomic():
|
||||||
self.object = form.save()
|
self.object = form.save()
|
||||||
self.object.resitele.add(m.Resitel.objects.get(osoba__user = self.request.user))
|
self.object.resitele.add(m.Resitel.objects.get(osoba__user = self.request.user))
|
||||||
|
self.object.resitele.add(*form.cleaned_data["resitele"])
|
||||||
self.object.cas_doruceni = timezone.now()
|
self.object.cas_doruceni = timezone.now()
|
||||||
self.object.forma = m.Reseni.FORMA_UPLOAD
|
self.object.forma = m.Reseni.FORMA_UPLOAD
|
||||||
self.object.save()
|
self.object.save()
|
||||||
|
|
|
@ -32,6 +32,7 @@ class PrihlaskaForm(PasswordResetForm):
|
||||||
help_text='Tímto jménem se následně budeš přihlašovat pro odevzdání řešení a další činnosti v semináři')
|
help_text='Tímto jménem se následně budeš přihlašovat pro odevzdání řešení a další činnosti v semináři')
|
||||||
|
|
||||||
jmeno = forms.CharField(label='Jméno', max_length=256, required=True)
|
jmeno = forms.CharField(label='Jméno', max_length=256, required=True)
|
||||||
|
prezdivka_resitele = forms.CharField(label='Přezdívka (veřejná)', max_length=256, required=False)
|
||||||
prijmeni = forms.CharField(label='Příjmení', max_length=256, required=True)
|
prijmeni = forms.CharField(label='Příjmení', max_length=256, required=True)
|
||||||
pohlavi_muz = forms.ChoiceField(label='Pohlaví',
|
pohlavi_muz = forms.ChoiceField(label='Pohlaví',
|
||||||
choices = ((True,'muž'),(False,'žena')), required=True)
|
choices = ((True,'muž'),(False,'žena')), required=True)
|
||||||
|
@ -105,6 +106,14 @@ class PrihlaskaForm(PasswordResetForm):
|
||||||
pass
|
pass
|
||||||
return email
|
return email
|
||||||
|
|
||||||
|
def clean_prezdivka_resitele(self):
|
||||||
|
prezdivka_resitele = self.cleaned_data.get('prezdivka_resitele')
|
||||||
|
if prezdivka_resitele == '':
|
||||||
|
return prezdivka_resitele
|
||||||
|
if Resitel.objects.filter(prezdivka_resitele=prezdivka_resitele).count() > 0:
|
||||||
|
raise forms.ValidationError('Přezdívka je již použita')
|
||||||
|
return prezdivka_resitele
|
||||||
|
|
||||||
def clean_zasilat(self):
|
def clean_zasilat(self):
|
||||||
zasilat = self.cleaned_data.get('zasilat')
|
zasilat = self.cleaned_data.get('zasilat')
|
||||||
ulice = self.cleaned_data.get('ulice')
|
ulice = self.cleaned_data.get('ulice')
|
||||||
|
@ -138,6 +147,7 @@ class ProfileEditForm(forms.Form):
|
||||||
disabled=True)
|
disabled=True)
|
||||||
|
|
||||||
jmeno = forms.CharField(label='Jméno', max_length=256, required=True)
|
jmeno = forms.CharField(label='Jméno', max_length=256, required=True)
|
||||||
|
prezdivka_resitele = forms.CharField(label='Přezdívka (veřejná)', max_length=256, required=False)
|
||||||
prijmeni = forms.CharField(label='Příjmení', max_length=256, required=True)
|
prijmeni = forms.CharField(label='Příjmení', max_length=256, required=True)
|
||||||
pohlavi_muz = forms.ChoiceField(label='Pohlaví',
|
pohlavi_muz = forms.ChoiceField(label='Pohlaví',
|
||||||
choices = ((True,'muž'),(False,'žena')), required=True)
|
choices = ((True,'muž'),(False,'žena')), required=True)
|
||||||
|
@ -190,6 +200,15 @@ class ProfileEditForm(forms.Form):
|
||||||
# pass
|
# pass
|
||||||
# return username
|
# return username
|
||||||
#
|
#
|
||||||
|
|
||||||
|
def clean_prezdivka_resitele(self):
|
||||||
|
prezdivka_resitele = self.cleaned_data.get('prezdivka_resitele')
|
||||||
|
if prezdivka_resitele == '':
|
||||||
|
return prezdivka_resitele
|
||||||
|
if Resitel.objects.filter(prezdivka_resitele=prezdivka_resitele).exclude(osoba__user__username=self.username).count() > 0:
|
||||||
|
raise forms.ValidationError('Přezdívka je již použita')
|
||||||
|
return prezdivka_resitele
|
||||||
|
|
||||||
def clean_email(self):
|
def clean_email(self):
|
||||||
err_logger = logging.getLogger('seminar.prihlaska.problem')
|
err_logger = logging.getLogger('seminar.prihlaska.problem')
|
||||||
email = self.cleaned_data.get('email')
|
email = self.cleaned_data.get('email')
|
||||||
|
|
|
@ -44,6 +44,7 @@
|
||||||
</h4>
|
</h4>
|
||||||
<table class="form">
|
<table class="form">
|
||||||
{% include "personalni/udaje/prihlaska_field.html" with field=form.jmeno %}
|
{% include "personalni/udaje/prihlaska_field.html" with field=form.jmeno %}
|
||||||
|
{% include "personalni/udaje/prihlaska_field.html" with field=form.prezdivka_resitele %}
|
||||||
{% include "personalni/udaje/prihlaska_field.html" with field=form.prijmeni %}
|
{% include "personalni/udaje/prihlaska_field.html" with field=form.prijmeni %}
|
||||||
{% include "personalni/udaje/prihlaska_field.html" with field=form.pohlavi_muz%}
|
{% include "personalni/udaje/prihlaska_field.html" with field=form.pohlavi_muz%}
|
||||||
{% include "personalni/udaje/prihlaska_field.html" with field=form.email %}
|
{% include "personalni/udaje/prihlaska_field.html" with field=form.email %}
|
||||||
|
|
|
@ -11,7 +11,7 @@ Získáváme od Tebe údaje vyplněné v přihlášce do semináře (jméno, př
|
||||||
Slibujeme Ti, že Tvá osobní data nezneužijeme k ničemu, co by nesouviselo s M&M nebo s dalšími aktivitami Matfyzu, a nikdy je nepředáme nikomu cizímu. Údaje využíváme k zajištění chodu semináře a také je sdílíme s ostatními propagačními akcemi Matfyzu, abychom mohli vyhodnocovat úspěšnost akcí. Pokud budeš mít zájem, budeme Ti také posílat zajímavé zprávy a novinky týkajíci se Matfyzu.
|
Slibujeme Ti, že Tvá osobní data nezneužijeme k ničemu, co by nesouviselo s M&M nebo s dalšími aktivitami Matfyzu, a nikdy je nepředáme nikomu cizímu. Údaje využíváme k zajištění chodu semináře a také je sdílíme s ostatními propagačními akcemi Matfyzu, abychom mohli vyhodnocovat úspěšnost akcí. Pokud budeš mít zájem, budeme Ti také posílat zajímavé zprávy a novinky týkajíci se Matfyzu.
|
||||||
</p>
|
</p>
|
||||||
<p class="gdpr">
|
<p class="gdpr">
|
||||||
Veřejně vystavujeme pouze výsledkové listiny, které také uchováváme pro archivní účely. Pokud ale z nějakého důvodu nebudeš chtít mít své jméno či školu uvedené ve výsledkové listině, není problém to zařídit, napiš nám. Z tištěných materiálů samozřejmě údaje už odstranit nemůžeme.
|
Veřejně vystavujeme pouze seznam přezdívek (pro výběr spoluřešitelů k řešení) a výsledkové listiny, které také uchováváme pro archivní účely. Pokud ale z nějakého důvodu nebudeš chtít mít své jméno či školu uvedené ve výsledkové listině, není problém to zařídit, napiš nám. Z tištěných materiálů samozřejmě údaje už odstranit nemůžeme.
|
||||||
</p>
|
</p>
|
||||||
<p class="gdpr">
|
<p class="gdpr">
|
||||||
Na soustředěních a dalších akcích semináře navíc pořizujeme fotografie a videozáznamy a používáme je ke zpravodajským a propagačním účelům. Pro propagační účely si od Tebe vyžádáme samostatný souhlas na začátku akce.
|
Na soustředěních a dalších akcích semináře navíc pořizujeme fotografie a videozáznamy a používáme je ke zpravodajským a propagačním účelům. Pro propagační účely si od Tebe vyžádáme samostatný souhlas na začátku akce.
|
||||||
|
|
|
@ -46,6 +46,7 @@
|
||||||
</h4>
|
</h4>
|
||||||
<table class="form">
|
<table class="form">
|
||||||
{% include "personalni/udaje/prihlaska_field.html" with field=form.jmeno %}
|
{% include "personalni/udaje/prihlaska_field.html" with field=form.jmeno %}
|
||||||
|
{% include "personalni/udaje/prihlaska_field.html" with field=form.prezdivka_resitele %}
|
||||||
{% include "personalni/udaje/prihlaska_field.html" with field=form.prijmeni %}
|
{% include "personalni/udaje/prihlaska_field.html" with field=form.prijmeni %}
|
||||||
{% include "personalni/udaje/prihlaska_field.html" with field=form.pohlavi_muz%}
|
{% include "personalni/udaje/prihlaska_field.html" with field=form.pohlavi_muz%}
|
||||||
{% include "personalni/udaje/prihlaska_field.html" with field=form.email %}
|
{% include "personalni/udaje/prihlaska_field.html" with field=form.email %}
|
||||||
|
|
|
@ -160,6 +160,7 @@ def resitelEditView(request):
|
||||||
|
|
||||||
if resitel_edit:
|
if resitel_edit:
|
||||||
## Změny v řešiteli
|
## Změny v řešiteli
|
||||||
|
resitel_edit.prezdivka_resitele = fcd['prezdivka_resitele']
|
||||||
resitel_edit.skola = fcd['skola']
|
resitel_edit.skola = fcd['skola']
|
||||||
resitel_edit.rok_maturity = fcd['rok_maturity']
|
resitel_edit.rok_maturity = fcd['rok_maturity']
|
||||||
resitel_edit.zasilat = fcd['zasilat']
|
resitel_edit.zasilat = fcd['zasilat']
|
||||||
|
@ -263,6 +264,7 @@ def prihlaskaView(request):
|
||||||
err_logger.warning(f'Zaregistrovala se osoba s kolizním jménem. ID osob: {[o.id for o in kolize]}')
|
err_logger.warning(f'Zaregistrovala se osoba s kolizním jménem. ID osob: {[o.id for o in kolize]}')
|
||||||
|
|
||||||
r = s.Resitel(
|
r = s.Resitel(
|
||||||
|
prezdivka_resitele=fcd['prezdivka_resitele'],
|
||||||
rok_maturity = fcd['rok_maturity'],
|
rok_maturity = fcd['rok_maturity'],
|
||||||
zasilat = fcd['zasilat'],
|
zasilat = fcd['zasilat'],
|
||||||
zasilat_cislo_emailem = fcd['zasilat_cislo_emailem']
|
zasilat_cislo_emailem = fcd['zasilat_cislo_emailem']
|
||||||
|
|
18
seminar/migrations/0110_resitel_prezdivka.py
Normal file
18
seminar/migrations/0110_resitel_prezdivka.py
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
# Generated by Django 2.2.28 on 2022-11-21 22:07
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('seminar', '0109_hodnoceni_feedback'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='resitel',
|
||||||
|
name='prezdivka_resitele',
|
||||||
|
field=models.CharField(blank=True, max_length=256, null=True, unique=True, verbose_name='přezdívka řešitele'),
|
||||||
|
),
|
||||||
|
]
|
|
@ -211,6 +211,8 @@ class Resitel(SeminarModelBase):
|
||||||
# Interní ID
|
# Interní ID
|
||||||
id = models.AutoField(primary_key = True)
|
id = models.AutoField(primary_key = True)
|
||||||
|
|
||||||
|
prezdivka_resitele = models.CharField('přezdívka řešitele', blank=True, null=True, max_length=256, unique=True)
|
||||||
|
|
||||||
osoba = models.OneToOneField(Osoba, blank=False, null=False, verbose_name='osoba',
|
osoba = models.OneToOneField(Osoba, blank=False, null=False, verbose_name='osoba',
|
||||||
on_delete=models.PROTECT)
|
on_delete=models.PROTECT)
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue