Vylepšení odevzdávátka #13
|
@ -22,6 +22,7 @@ urlpatterns = [
|
|||
# Autocomplete
|
||||
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_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/vsechny', views.ProblemAutocomplete.as_view(), name='autocomplete_problem'),
|
||||
|
||||
|
|
|
@ -44,6 +44,29 @@ class ResitelAutocomplete(LoginRequiredAjaxMixin,autocomplete.Select2QuerySetVie
|
|||
qs = qs.filter(query)
|
||||
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(
|
||||
zelvuska marked this conversation as resolved
Outdated
|
||||
prezdivka_resitele__isnull=False
|
||||
).exclude(
|
||||
zelvuska marked this conversation as resolved
Outdated
auburn
commented
Možná to chceme mít aspoň case-insensitive. Možná to chceme mít aspoň case-insensitive.
zelvuska
commented
asi asi `…__icontains=self.q`
|
||||
prezdivka_resitele=""
|
||||
zelvuska marked this conversation as resolved
ledoian
commented
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…) 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:
```python3
qs = (m.Resitel.objects
.filter(…)
.exclude(...)
.whatever()
.all())
```
(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…)
zelvuska
commented
Tak takhle mi zase chvíli trvá, než mi dojde, k čemu ta tečka na začátku řádku patří… Tak takhle mi zase chvíli trvá, než mi dojde, k čemu ta tečka na začátku řádku patří…
|
||||
).filter(
|
||||
prezdivka_resitele__icontains=self.q
|
||||
).all()
|
||||
return qs
|
||||
|
||||
def get_result_label(self, result):
|
||||
return result.prezdivka_resitele
|
||||
|
||||
|
||||
class OdevzdatelnyProblemAutocomplete(autocomplete.Select2QuerySetView):
|
||||
""" View k :mod:`dal.autocomplete` pro vyhledávání problémů především v odevzdávátku. """
|
||||
def get_queryset(self):
|
||||
|
|
|
@ -63,7 +63,7 @@ class PosliReseniForm(forms.Form):
|
|||
class NahrajReseniForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = m.Reseni
|
||||
fields = ('problem',)
|
||||
fields = ('problem', 'resitele')
|
||||
help_texts = {'problem':''} # Nezobrazovat help text ve formuláři
|
||||
|
||||
widgets = {'problem':
|
||||
|
@ -72,6 +72,13 @@ class NahrajReseniForm(forms.ModelForm):
|
|||
attrs = {'data-placeholder--id': '-1',
|
||||
'data-placeholder--text' : '---',
|
||||
'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
|
@ -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
|
||||
zelvuska marked this conversation as resolved
Outdated
auburn
commented
i i
|
||||
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 %}
|
||||
|
||||
{# 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>)
|
||||
{% if forloop.revcounter0 != 0 %}, {% endif %} {% endfor %}</p>
|
||||
{% if edit %}
|
||||
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 %}
```
|
||||
<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 #}
|
||||
<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>{{ 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 +69,7 @@ $(document).ready(function(){
|
|||
</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>
|
||||
|
||||
<table id="empty_form" style="display: none;">
|
||||
|
@ -129,28 +82,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>
|
||||
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 %}
|
||||
|
|
|
@ -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>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>
|
||||
zelvuska marked this conversation as resolved
Outdated
ledoian
commented
Nejsem si jistý, jestli není lepší spíš používat (To, jak moc velká zeď textu v odevzdávátku vzniká, posoudím za chvíli vizuálně…) Nejsem si jistý, jestli není lepší spíš používat `<emph>` a `<strong>`. A možná bych si odpustil ty vykřičníky, mohly by působit trochu děsivě…
(To, jak moc velká zeď textu v odevzdávátku vzniká, posoudím za chvíli vizuálně…)
|
||||
|
||||
<form enctype="multipart/form-data" action="{% url 'seminar_nahraj_reseni' %}" method="post" onsubmit="return zkontroluj_prilohy();">
|
||||
{% csrf_token %}
|
||||
<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/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,44 @@ 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 ale better safe then sorry
|
||||
zelvuska marked this conversation as resolved
Outdated
ledoian
commented
Tohle nemá být docstring, ale komentář. Tohle nemá být docstring, ale komentář.
|
||||
if not self.request.user.je_org:
|
||||
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…
|
||||
raise PermissionDenied()
|
||||
|
||||
|
||||
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 +297,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):
|
||||
|
@ -408,6 +408,7 @@ class NahrajReseniView(LoginRequiredMixin, CreateView):
|
|||
with transaction.atomic():
|
||||
self.object = form.save()
|
||||
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.forma = m.Reseni.FORMA_UPLOAD
|
||||
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')
|
||||
|
||||
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)
|
||||
pohlavi_muz = forms.ChoiceField(label='Pohlaví',
|
||||
choices = ((True,'muž'),(False,'žena')), required=True)
|
||||
|
@ -105,6 +106,14 @@ class PrihlaskaForm(PasswordResetForm):
|
|||
pass
|
||||
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):
|
||||
zasilat = self.cleaned_data.get('zasilat')
|
||||
ulice = self.cleaned_data.get('ulice')
|
||||
|
@ -138,6 +147,7 @@ class ProfileEditForm(forms.Form):
|
|||
disabled=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)
|
||||
pohlavi_muz = forms.ChoiceField(label='Pohlaví',
|
||||
choices = ((True,'muž'),(False,'žena')), required=True)
|
||||
|
@ -190,6 +200,15 @@ class ProfileEditForm(forms.Form):
|
|||
# pass
|
||||
# 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):
|
||||
zelvuska marked this conversation as resolved
Outdated
ledoian
commented
Tohle velmi zní, jako že ta přezdívka prostě nejde editovat vůbec – pokud už v databázi jsem, tak tohle matchne. Tohle velmi zní, jako že ta přezdívka prostě nejde editovat vůbec – pokud už v databázi jsem, tak tohle matchne.
zelvuska
commented
? ?
ledoian
commented
Pokud si Alice nastaví přezdívku „Axolotl“, tak ji má v databázi. Dotaz Pokud si Alice nastaví přezdívku „Axolotl“, tak ji má v databázi. Dotaz `Restitel.objects.get(prezdivka=Axolotl)` tedy tuto přezdívku najde a celý `ResitelEditForm` kvůli tomu nepůjde Alici odeslat, přestože je to její přezdívka.
zelvuska
commented
Promiň, tvůj komentář jsem pochopil jinak. Samozřejmě tohle je špatně… Dík Promiň, tvůj komentář jsem pochopil jinak. Samozřejmě tohle je špatně… Dík
zelvuska
commented
Ha, já nevím, podle čeho určit, který řešitel to edituje… Ha, já nevím, podle čeho určit, který řešitel to edituje…
ledoian
commented
A nestačí to prostě uložit a zjistit, jestli to vyšlo? (Dost možná nestačí, nemám nastudovanou interakci formů a modelů, ale kdyby to stačilo, tak je to zdaleka nejjednodušší…) A nestačí to prostě uložit a zjistit, jestli to vyšlo? (Dost možná nestačí, nemám nastudovanou interakci formů a modelů, ale kdyby to stačilo, tak je to zdaleka nejjednodušší…)
zelvuska
commented
A poznáš, co selhalo? A přijde mi, že tohle by mělo být ještě v clean_… A poznáš, co selhalo? A přijde mi, že tohle by mělo být ještě v clean_…
zelvuska
commented
Vyřešeno. Ten objekt má při použití nastavený username… Vyřešeno. Ten objekt má při použití nastavený username…
|
||||
err_logger = logging.getLogger('seminar.prihlaska.problem')
|
||||
email = self.cleaned_data.get('email')
|
||||
|
|
|
@ -44,6 +44,7 @@
|
|||
</h4>
|
||||
<table class="form">
|
||||
{% 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.pohlavi_muz%}
|
||||
{% 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.
|
||||
</p>
|
||||
<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 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.
|
||||
|
|
|
@ -46,6 +46,7 @@
|
|||
</h4>
|
||||
<table class="form">
|
||||
{% 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.pohlavi_muz%}
|
||||
{% include "personalni/udaje/prihlaska_field.html" with field=form.email %}
|
||||
|
|
|
@ -160,6 +160,7 @@ def resitelEditView(request):
|
|||
|
||||
if resitel_edit:
|
||||
## Změny v řešiteli
|
||||
resitel_edit.prezdivka_resitele = fcd['prezdivka_resitele']
|
||||
resitel_edit.skola = fcd['skola']
|
||||
resitel_edit.rok_maturity = fcd['rok_maturity']
|
||||
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]}')
|
||||
|
||||
r = s.Resitel(
|
||||
prezdivka_resitele=fcd['prezdivka_resitele'],
|
||||
rok_maturity = fcd['rok_maturity'],
|
||||
zasilat = fcd['zasilat'],
|
||||
zasilat_cislo_emailem = fcd['zasilat_cislo_emailem']
|
||||
|
|
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
|
||||
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',
|
||||
on_delete=models.PROTECT)
|
||||
|
||||
|
|
Nebylo by lepší ty řádky spojit do něčeho jako
prezdivka__len__gt=0
?Mě to takhle asi přijde popisnější a jsem si jistější, že to funguje…