Vylepšení odevzdávátka #13
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=""
|
||||||
zelvuska marked this conversation as resolved
|
|||||||
|
).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 %}
|
||||||
zelvuska marked this conversation as resolved
zelvuska
commented
Pardon, snažil jsem se nějak dostat sem do konverzace aktuální stav těch 5 řádků, ale asi to neumím. Pardon, snažil jsem se nějak dostat sem do konverzace aktuální stav těch 5 řádků, ale asi to neumím.
|
|||||||
|
|
||||||
{# 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>
|
||||||
ledoian marked this conversation as resolved
ledoian
commented
Tohle nečtu, to se uvidí na testwebu, jak to vypadá… Čitelné to nejspíš je, tak asi dobrý… Tohle nečtu, to se uvidí na testwebu, jak to vypadá… Čitelné to nejspíš je, tak asi dobrý…
zelvuska
commented
A jak to vypadá? A jak to vypadá?
ledoian
commented
LGTM LGTM
|
|||||||
|
<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'),
|
||||||
]
|
]
|
||||||
|
|
|
@ -211,6 +211,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'
|
||||||
|
|
||||||
|
@ -227,18 +228,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ě
|
||||||
|
@ -270,33 +297,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):
|
||||||
|
@ -408,6 +408,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.
|
||||||
zelvuska marked this conversation as resolved
ledoian
commented
👍 👍
|
|||||||
</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
Tenhle způsob zalámání nejspíš nikde nemáme – trochu mi to rozbíjí čtení kódu. Ale možná nikde nemáme takovýhle kód, takže by to pokus o fluent kód (vizte níž) udělal možná taky…
Pod fluent kódem si představuji cca toto:
(Je to o něco kratší a neskáče tam odsazení jak na pile, což mi přijde trošičku lepší pro čtení, ale je to jen malá preference…)
Tak takhle mi zase chvíli trvá, než mi dojde, k čemu ta tečka na začátku řádku patří…