Vylepšení odevzdávátka #13
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) { // vydím pouze plusko
|
||||
zelvuska marked this conversation as resolved
Outdated
|
||||
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,67 +4,15 @@
|
|||
|
||||
{% block content %}
|
||||
|
||||
{# FIXME: Necopypastovat! Tohle je zkopírované ze static/odevzdavatko/dynamic_formsets.js #}
|
||||
<script type='text/javascript'>
|
||||
// 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);
|
||||
});
|
||||
});
|
||||
</script>
|
||||
{% if edit %}
|
||||
<script src="{% static 'odevzdavatko/dynamic_formsets_for_detail.js' %}"></script>
|
||||
<script src="{% static 'odevzdavatko/check_for_detail.js' %}"></script>
|
||||
{% endif %}
|
||||
|
||||
|
||||
<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>)
|
||||
<p>Řešitelé: {% for r in object.resitele.all %} {{ r }} {% if edit %}(<a href="mailto:{{ r.osoba.email }}?subject={{ "Oprava řešení M&M " | urlencode }}{{ object.problem.all.0.hlavni_problem | urlencode }}">{{ r.osoba.email }}</a>){% endif %}
|
||||
ledoian
commented
Přijde mi lepší se nesnažit všechno nacpat na jeden řádek. To HTML se vykreslí stejně a v tomhle se mi špatně orientuje… Přijde mi lepší se nesnažit všechno nacpat na jeden řádek. To HTML se vykreslí stejně a v tomhle se mi špatně orientuje…
zelvuska
commented
No až na to, že tam přibydou mezery. Nechceš to nasekat ty? No až na to, že tam přibydou mezery. Nechceš to nasekat ty?
ledoian
commented
Sice nevím, kde přibudou mezery, kouknu na testweb, ale pak to klidně nasekám… Sice nevím, kde přibudou mezery, kouknu na testweb, ale pak to klidně nasekám…
zelvuska
commented
Na testwebu to aktuálně běží… Na testwebu to aktuálně běží…
ledoian
commented
LGTM LGTM
zelvuska
commented
Za mě všechno, nasekáš to? Za mě všechno, nasekáš to?
ledoian
commented
Klidně to mergni, nasekám, ale možná to o pár desítek minut odložím (a když na to zapomenu úplně, tak to bolet taky nebude…) Klidně to mergni, nasekám, ale možná to o pár desítek minut odložím (a když na to zapomenu úplně, tak to bolet taky nebude…)
ledoian
commented
Na jak velkou míru enterprise kódu míříme? DjangoTemplates jsou trochu na palici a těch mezer kvůli odřádkování se neumí zbavit, ale skoro určitě se to dá obejít pomocí triviálního filtru, takže by se pak dalo použít něco jako Ale znamená to mít k tomu ještě kousek kódu vedle. Alternativně se něco takového dá spočítat už ve view, což se ale bude motat s výkonným kódem, který už tak je nejspíš na spodní hranici přehlednosti… (A nebo se smíříme s tím, že ten řádek nejde tak jednoduše přečíst. Ostatně, nebyl by první – výsledkovky v TeXu trpí stejným problémem…) Možná se mi podaří tam dát aspoň pár málo odřádkování, ale těžko odhadnout… Na jak velkou míru enterprise kódu míříme? DjangoTemplates jsou trochu na palici a těch mezer kvůli odřádkování se neumí zbavit, ale skoro určitě se to dá obejít pomocí triviálního filtru, takže by se pak dalo použít něco jako `{{ o | withemail }}`, což by rovnou udělalo tu výslednou věc a ten kód by byl přehlednější, ač na jednom řádku.
Ale znamená to mít k tomu ještě kousek kódu vedle.
Alternativně se něco takového dá spočítat už ve view, což se ale bude motat s výkonným kódem, který už tak je nejspíš na spodní hranici přehlednosti…
(A nebo se smíříme s tím, že ten řádek nejde tak jednoduše přečíst. Ostatně, nebyl by první – výsledkovky v TeXu trpí stejným problémem…)
Možná se mi podaří tam dát aspoň pár málo odřádkování, ale těžko odhadnout…
zelvuska
commented
Co takhle? Co takhle?
ledoian
commented
👎 Pořád je to na jednom řádku a teď tam navíc překážejí komentáře. 👎 Pořád je to na jednom řádku a teď tam navíc překážejí komentáře.
zelvuska
commented
Tak to nechávám na tobě, protože na tohle máš očividně daleko vyšší nároky (mě to totiž ty komentáře naopak hezky oddělují…). Tak to nechávám na tobě, protože na tohle máš očividně daleko vyšší nároky (mě to totiž ty komentáře naopak hezky oddělují…).
zelvuska
commented
Nebo mě ještě napadlo tohle… Nebo mě ještě napadlo tohle…
zelvuska
commented
Případně ještě něco jako
čímž celý e-mail bude na jednom řádku, ale zase je to dost nahouby z pohledu tagů Případně ještě něco jako
```
{% 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 %}
```
čímž celý e-mail bude na jednom řádku, ale zase je to dost nahouby z pohledu tagů
zelvuska
commented
Případně
nebo
Případně
```
{% 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 %}
```
nebo
```
{% 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 %}
```
|
||||
{% if forloop.revcounter0 != 0 %}, {% endif %} {% endfor %}</p>
|
||||
|
||||
{# https://docs.djangoproject.com/en/3.1/ref/models/instances/#django.db.models.Model.get_FOO_display #}
|
||||
|
@ -82,13 +30,13 @@ $(document).ready(function(){
|
|||
<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 %}
|
||||
|
||||
{% if edit %}
|
||||
<form method=post onsubmit="return zkontroluj_hodnoceni();">
|
||||
{# Poznámka #}
|
||||
<h3>Neveřejná poznámka:</h3>
|
||||
|
@ -116,7 +64,7 @@ $(document).ready(function(){
|
|||
</table>
|
||||
|
||||
|
||||
<a href="#"> <img src="{% static "odevzdavatko/plus.png" %}" id="pridat_hodnoceni" alt="Přidat hodnocení"></a> </br>
|
||||
<a href="#"> <img src="{% static "odevzdavatko/plus.png" %}" id="pridat_hodnoceni" alt="Přidat hodnocení"></a> <br/>
|
||||
<input type=submit value="Uložit"></form>
|
||||
|
||||
<table id="empty_form" style="display: none;">
|
||||
|
@ -129,28 +77,19 @@ $(document).ready(function(){
|
|||
</tr>
|
||||
</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>
|
||||
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
|
||||
{% 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 %}
|
||||
|
|
|
@ -26,9 +26,9 @@ urlpatterns = [
|
|||
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/<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/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
|
||||
class DetailReseniView(DetailView):
|
||||
""" Náhled na řešení. Editace je v :py:class:`EditReseniView`. """
|
||||
model = m.Reseni
|
||||
template_name = 'odevzdavatko/detail.html'
|
||||
|
||||
|
@ -227,18 +228,43 @@ class DetailReseniView(DetailView):
|
|||
return result
|
||||
|
||||
def get_context_data(self, **kw):
|
||||
self.check_access()
|
||||
ctx = super().get_context_data(**kw)
|
||||
ctx['form'] = f.OhodnoceniReseniFormSet(
|
||||
initial = self.aktualni_hodnoceni()
|
||||
)
|
||||
ctx['poznamka_form'] = f.PoznamkaReseniForm(instance=self.reseni)
|
||||
hodnoceni = self.aktualni_hodnoceni()
|
||||
ctx["hodnoceni"] = hodnoceni
|
||||
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 """
|
||||
zelvuska marked this conversation as resolved
Outdated
ledoian
commented
Tohle nemá být docstring, ale komentář. Tohle nemá být docstring, ale komentář.
|
||||
pass
|
||||
zelvuska marked this conversation as resolved
Outdated
ledoian
commented
„Better safe than sorry“? Je to ±jeden řádek tak jak tak, a aspoň stačí vidět jen jeden soubor… „Better safe than sorry“? Je to ±jeden řádek tak jak tak, a aspoň stačí vidět jen jeden soubor…
|
||||
|
||||
|
||||
def hodnoceniReseniView(request, pk, *args, **kwargs):
|
||||
reseni = get_object_or_404(m.Reseni, pk=pk)
|
||||
template_name = 'odevzdavatko/detail.html'
|
||||
form_class = f.OhodnoceniReseniFormSet
|
||||
ledoian marked this conversation as resolved
Outdated
ledoian
commented
Tohle zmizelo proč? (Na první pohled to vypadá jako velmi podezřelá změna, ale ještě jsem nedočetl zbytek kódu…) Tohle zmizelo proč? (Na první pohled to vypadá jako velmi podezřelá změna, ale ještě jsem nedočetl zbytek kódu…)
ledoian
commented
Dočetl jsem a tuhle změnu pořád nechápu. Dočetl jsem a tuhle změnu pořád nechápu.
zelvuska
commented
Protože se to tam vůbec nepoužívalo? Protože se to tam vůbec nepoužívalo?
zelvuska
commented
Já vůbec nechápu proč to tam bylo… Já vůbec nechápu proč to tam bylo…
ledoian
commented
LOL aha. Já čekal, že se ten template vykresluje, ale on je tam redirect, tak je to jedno. Budiž :-) LOL aha. Já čekal, že se ten template vykresluje, ale on je tam redirect, tak je to jedno. Budiž :-)
|
||||
success_url = reverse('odevzdavatko_detail_reseni', kwargs={'pk': pk})
|
||||
|
||||
# FIXME: Použit initial i tady a nebastlit hodnocení tak nízkoúrovňově
|
||||
|
@ -270,33 +296,6 @@ def hodnoceniReseniView(request, pk, *args, **kwargs):
|
|||
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):
|
||||
|
|
i