Compare commits

...

39 commits

Author SHA1 Message Date
bed107aeac Merge remote-tracking branch 'origin/master' into upgrade_odevzdavatka
# Conflicts:
#	mamweb/static/css/mamweb.css
2023-06-12 22:20:24 +02:00
2ab6e76fbe Merge pull request 'Vylepšení hodnotítka fix #1354 fix #1237' (!20) from vylepseni_odevzdavatka into master
Reviewed-on: #20
2023-06-12 22:17:07 +02:00
1a63195b58 Merge branch 'master' into vylepseni_odevzdavatka 2023-06-12 22:07:38 +02:00
03f0a6fd7a Jak řešit 2023 (děkujeme Tomovi) 2023-06-02 00:27:23 +02:00
fc4fc87798 Revert "Jak řesit (oproti mamtex je tam ubraný nějaký text navíc)"
This reverts commit 389a979f4c.
2023-06-02 00:22:30 +02:00
4b6e82120d FIX: Posílání e-mailu i řešitelům, kteří nechtějí papírové číslo 2023-06-01 15:08:55 +02:00
b2f0c47449 Oprava předmětu v e-mailu o novém čísle (oprava commitu 3c958c91) 2023-06-01 14:47:03 +02:00
389a979f4c Jak řesit (oproti mamtex je tam ubraný nějaký text navíc)
<text
       xml:space="preserve"
       style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:3.52778px;line-height:1.15;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;letter-spacing:0px;word-spacing:0px;display:inline;stroke-width:0.264583"
       x="147.6949"
       y="126.75229"
       id="text6805-0-9-7"><tspan
         sodipodi:role="line"
         x="0"
         y="0"
         style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:3.52778px;font-family:'Latin Modern Sans';-inkscape-font-specification:'Latin Modern Sans';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:center;writing-mode:lr-tb;text-anchor:middle;stroke-width:0.264583"
         id="tspan141408-9">Důležitý je celý postup, </tspan><tspan
         sodipodi:role="line"
         x="0"
         y="0"
         style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:3.52778px;font-family:'Latin Modern Sans';-inkscape-font-specification:'Latin Modern Sans';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:center;writing-mode:lr-tb;text-anchor:middle;stroke-width:0.264583"
         id="tspan61014">nejen výsledek.</tspan></text>
2023-06-01 14:42:01 +02:00
3c958c917b E-mail řešitelům k prvnímu číslu 2023-06-01 14:24:14 +02:00
669255461b Vložit řešení: i nezadané problémy 2023-05-22 23:39:04 +02:00
c93fa6c574 oprava „Vložit řešení: více řešitelů“ a více řešení 2023-05-22 23:34:37 +02:00
831ed6c64c zbylé FIXME po „Vložit řešení: více řešitelů“ 2023-05-22 23:21:19 +02:00
c7ce943fc0 Vložit řešení: více řešitelů 2023-05-22 23:10:38 +02:00
0c5c923b06 Autocomplete řešitelů: oprava víceslovného hledání 2023-05-22 23:06:16 +02:00
Pavel "LEdoian" Turinsky
61e71efcc4 Make: správně nastavit orgům práva
Tohle je trošku fishy, protože by se to teoreticky mělo dít i u
produkce, leč tam zůstávají práva persistentně v DB (a až se rozbijí,
tak se budeme divit)…
2023-05-15 23:45:26 +02:00
27b38e6b4d Korektury návodu na hodnotítko. 2023-03-06 21:38:16 +01:00
d63b5286a4 Nástřel návodu v hodnotítku 2023-03-06 21:14:58 +01:00
c0fa59a504 Merge branch 'master' into vylepseni_odevzdavatka 2023-03-06 20:17:03 +01:00
6e1b1ef4e8 Komentář 4 změny = beze změny 2023-03-06 20:16:36 +01:00
9868bff329 fuj, print 2023-03-06 20:11:50 +01:00
a70d32a4ad zalámání 2023-03-06 20:10:44 +01:00
6742ecdb8b add: okomentovány vzorečky na přepočet bodů 2023-02-13 22:48:25 +01:00
d66effcd46 move: přesunuty vzorečky na přepočet bodů do seminar.utils 2023-02-13 22:46:54 +01:00
c8516d6eda add: checky v hodnotítku 2023-02-13 22:32:14 +01:00
2b1c5e4e6e fix: maximum bodů pro hodnocení 2023-02-06 23:26:11 +01:00
db16df391b fix: maximum bodů pro hodnocení 2023-02-06 23:17:55 +01:00
4f7b64fae2 fix: maximum bodů pro hodnocení 2023-02-06 23:17:04 +01:00
52ac8364eb Merge remote-tracking branch 'origin/master' into vylepseni_odevzdavatka 2023-02-06 22:16:08 +01:00
6f49ab3cfa Jemné textové úpravy 2023-02-06 22:11:36 +01:00
648084d1f9 add: zobrazování max. počtu bodů i v necelkem 2023-02-06 22:09:57 +01:00
50bdb19e14 smazáno FIXME (to už jsem udělal) 2023-02-06 21:20:01 +01:00
ec0174dcdd Merge remote-tracking branch 'origin/master' into vylepseni_odevzdavatka
# Conflicts:
#	odevzdavatko/templates/odevzdavatko/detail.html
2023-02-06 21:14:13 +01:00
02fe280d4a neumím css, z toho si nic nedělejte 2023-02-06 21:10:32 +01:00
fa688ec8f3 add: skrýt teamovou část při jednom řešiteli v řešení 2023-02-06 21:00:19 +01:00
0c7a411c1f fix: nekonečné desetiné rozvoje 2023-01-03 00:21:12 +01:00
87a209bf2a fix: nekonečné desetiné rozvoje 2023-01-03 00:13:29 +01:00
d9756d5f60 add: ukládání různých bodů 2023-01-02 23:44:36 +01:00
39da362586 add: frontend k bodům 2023-01-02 23:44:04 +01:00
57b7c6372d add: různé druhy bodů u hodnocení 2023-01-02 23:42:53 +01:00
13 changed files with 20031 additions and 71547 deletions

View file

@ -37,9 +37,9 @@ class ResitelAutocomplete(LoginRequiredAjaxMixin,autocomplete.Select2QuerySetVie
query = Q() query = Q()
for part in parts: for part in parts:
query &= ( query &= (
Q(osoba__jmeno__istartswith=self.q)| Q(osoba__jmeno__istartswith=part)|
Q(osoba__prijmeni__istartswith=self.q)| Q(osoba__prijmeni__istartswith=part)|
Q(osoba__prezdivka__istartswith=self.q) Q(osoba__prezdivka__istartswith=part)
) )
qs = qs.filter(query) qs = qs.filter(query)
return qs return qs

View file

@ -8,3 +8,4 @@ ensure_venv
./manage.py testdata ./manage.py testdata
./manage.py loaddata data/* ./manage.py loaddata data/*
make/sync_prod_flatpages make/sync_prod_flatpages
./manage.py load_org_permissions deploy_v2/admin_org_prava.json

View file

@ -1256,6 +1256,11 @@ label[for=id_skola] {
font-weight: bold; font-weight: bold;
} }
/* detail řešení */
.bodovani>input {
width: 4em;
}
/* Select2 používaný hlavně multiple selectem. Přidání checkboxů a změna barvy. */ /* Select2 používaný hlavně multiple selectem. Přidání checkboxů a změna barvy. */
/* Podle https://stackoverflow.com/a/48290544 */ /* Podle https://stackoverflow.com/a/48290544 */

View file

@ -21,16 +21,25 @@ class DateInput(forms.DateInput):
class PosliReseniForm(forms.Form): class PosliReseniForm(forms.Form):
#FIXME jen podproblémy daného problému problem = forms.ModelMultipleChoiceField(
problem = forms.ModelChoiceField(label='Problém',queryset=m.Problem.objects.all()) queryset=m.Problem.objects.all(),
label="Problémy",
widget=autocomplete.ModelSelect2Multiple(
url='autocomplete_problem',
attrs={
'data-placeholder--id': '-1',
'data-placeholder--text': '---',
'data-allow-clear': 'true'
},
),
)
# to_field_name # to_field_name
#problem = models.ManyToManyField(Problem, verbose_name='problém', help_text='Problém', #problem = models.ManyToManyField(Problem, verbose_name='problém', help_text='Problém',
# through='Hodnoceni') # through='Hodnoceni')
# FIXME pridat vice resitelu resitel = forms.ModelMultipleChoiceField(label="Řešitelé",
resitel = forms.ModelChoiceField(label="Řešitel",
queryset=Resitel.objects.all(), queryset=Resitel.objects.all(),
widget=autocomplete.ModelSelect2( widget=autocomplete.ModelSelect2Multiple(
url='autocomplete_resitel', url='autocomplete_resitel',
attrs = {'data-placeholder--id': '-1', attrs = {'data-placeholder--id': '-1',
'data-placeholder--text' : '---', 'data-placeholder--text' : '---',
@ -117,6 +126,24 @@ class JednoHodnoceniForm(forms.ModelForm):
'feedback': forms.Textarea(attrs={'rows': 1, 'cols': 30, 'class': 'feedback'}), 'feedback': forms.Textarea(attrs={'rows': 1, 'cols': 30, 'class': 'feedback'}),
} }
body_celkem = forms.DecimalField(required=False, decimal_places=1)
body_neprepocitane = forms.DecimalField(required=False, decimal_places=1)
body_neprepocitane_celkem = forms.DecimalField(required=False, decimal_places=1)
def __init__(self, *args, initial=None, **kwargs):
if initial is not None:
body_max = initial["body_max"]
body_neprepocitane_max = initial["body_neprepocitane_max"]
del(initial["body_max"])
del(initial["body_neprepocitane_max"])
super().__init__(*args, initial=initial, **kwargs)
if initial is not None:
self.fields['body'].widget.attrs['placeholder'] = body_max
self.fields['body_celkem'].widget.attrs['placeholder'] = body_max
self.fields['body_neprepocitane'].widget.attrs['placeholder'] = body_neprepocitane_max
self.fields['body_neprepocitane_celkem'].widget.attrs['placeholder'] = body_neprepocitane_max
OhodnoceniReseniFormSet = formset_factory(JednoHodnoceniForm, OhodnoceniReseniFormSet = formset_factory(JednoHodnoceniForm,
extra = 0, extra = 0,
) )

View file

@ -49,8 +49,18 @@ $(document).ready(function(){
$('#id_form-' + form_idx + '-deadline_body')[0].value = $('#id_form-' + (form_idx - 1) + '-deadline_body')[0].value $('#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); $('#id_form-TOTAL_FORMS').val(parseInt(form_idx) + 1);
$('.bodovani').children().change(function(){
$(this).parent().parent().children(".bodovani").children().attr("disabled", true);
$(this).attr("disabled", false);
})
}); });
$('.smazat_hodnoceni').click(function(){ $('.smazat_hodnoceni').click(function(){
deleteForm("form",this); deleteForm("form",this);
}); });
$('.bodovani').children().change(function(){
$(this).parent().parent().children(".bodovani").children().attr("disabled", true);
$(this).attr("disabled", false);
})
}); });

View file

@ -4,6 +4,13 @@
{% load mail %} {% load mail %}
{% load jmena %} {% load jmena %}
{# Přišlo mi to hezčí, než psát všude if. #}
{% block custom_css %}
{% if object.resitele.count == 1 %}
<style>.teamovaCast {display: none}</style>
{% endif %}
{% endblock %}
{% block content %} {% block content %}
{% if edit %} {% if edit %}
@ -76,6 +83,22 @@
<h3>Neveřejná poznámka:</h3> <h3>Neveřejná poznámka:</h3>
<p>{{ poznamka_form.poznamka }}</p> <p>{{ poznamka_form.poznamka }}</p>
<script>vporadku=true;</script>
{% for h in hodnoceni %}{% if h.body < 0.0 %}
<script>
if(vporadku){
vporadku=false;
alert(
"Pozor! Některé hodnocení má záporné body.\n\n" +
"Buď jde o záměr, nebo o špatné zadáný počet bodů (např. součet bodů za úlohu) nebo se něco pokazilo.\n\n" +
"Pokud se to děje neočekávaně a opakovaně, napiš webařům :)"
);
}
</script>
{% endif %}{% endfor %}
{# Hodnocení: #} {# Hodnocení: #}
<h3>Hodnocení:</h3> <h3>Hodnocení:</h3>
<table> <table>
@ -83,12 +106,15 @@
{{ form.management_form }} {{ form.management_form }}
</table> </table>
<table id="form_set"> <table id="form_set">
<tr><th>Problém</th><th>Body</th><th>Deadline pro body</th><th>Zpětná vazba pro řešitele</th></tr> <tr><th>Problém</th><th>{# 📖 #}🧍</th><th>{# 🔵 #}🧍∑</th><th class="teamovaCast">{# 💪 #}🧑‍🤝‍🧑</th><th class="teamovaCast">{# ❤ #}🧑‍🤝‍🧑∑</th><th>Deadline pro body</th><th>Zpětná vazba pro řešitele</th></tr>
{% for subform in form %} {% for subform in form %}
<tbody> <tbody>
<tr class="hodnoceni"> <tr class="hodnoceni">
<td>{{ subform.problem }}</td> <td>{{ subform.problem }}</td>
<td>{{ subform.body }}</td> <td class="bodovani">{{ subform.body }}</td>
<td class="bodovani">{{ subform.body_celkem }}</td>
<td class="bodovani teamovaCast">{{ subform.body_neprepocitane }}</td>
<td class="bodovani teamovaCast">{{ subform.body_neprepocitane_celkem }}</td>
<td>{{ subform.deadline_body }}</td> <td>{{ subform.deadline_body }}</td>
<td>{{ subform.feedback }}</td> <td>{{ subform.feedback }}</td>
<td class="has_smazat_hodnoceni"><a href="#" class="smazat_hodnoceni" id="id_{{subform.prefix}}-jsremove" title="Smazat hodnocení"><img src="{% static "odevzdavatko/cross.png" %}" alt="Smazat"></a></td> <td class="has_smazat_hodnoceni"><a href="#" class="smazat_hodnoceni" id="id_{{subform.prefix}}-jsremove" title="Smazat hodnocení"><img src="{% static "odevzdavatko/cross.png" %}" alt="Smazat"></a></td>
@ -104,7 +130,10 @@
<table id="empty_form" style="display: none;"> <table id="empty_form" style="display: none;">
<tr class="hodnoceni"> <tr class="hodnoceni">
<td>{{ form.empty_form.problem }}</td> <td>{{ form.empty_form.problem }}</td>
<td>{{ form.empty_form.body }}</td> <td class="bodovani">{{ form.empty_form.body }}</td>
<td class="bodovani">{{ form.empty_form.body_celkem }}</td>
<td class="bodovani teamovaCast">{{ form.empty_form.body_neprepocitane }}</td>
<td class="bodovani teamovaCast">{{ form.empty_form.body_neprepocitane_celkem }}</td>
<td>{{ form.empty_form.deadline_body }}</td> <td>{{ form.empty_form.deadline_body }}</td>
<td>{{ form.empty_form.feedback }}</td> <td>{{ form.empty_form.feedback }}</td>
<td class="has_smazat_hodnoceni"><a href="#" class="smazat_hodnoceni" id="id_{{form.empty_form.prefix}}-jsremove" title="Smazat hodnocení"><img src="{% static "odevzdavatko/cross.png" %}" alt="Smazat"></a></td> <td class="has_smazat_hodnoceni"><a href="#" class="smazat_hodnoceni" id="id_{{form.empty_form.prefix}}-jsremove" title="Smazat hodnocení"><img src="{% static "odevzdavatko/cross.png" %}" alt="Smazat"></a></td>
@ -114,16 +143,61 @@
{% else %} {% else %}
<h3>Hodnocení:</h3> <h3>Hodnocení:</h3>
<table class="dosla_reseni"> <table class="dosla_reseni">
<tr><th>Problém</th><th>Body</th><th>Zpětná vazba od opravovatele</th></tr> <tr><th>Problém</th><th>{# 📖 #}🧍</th><th>{# 🔵 #}🧍∑</th><th class="teamovaCast">{# 💪 #}🧑‍🤝‍🧑</th><th class="teamovaCast">{# ❤ #}🧑‍🤝‍🧑∑</th><th>Zpětná vazba od opravovatele</th></tr>
{% for h in hodnoceni %} {% for h in hodnoceni %}
<tr class="hodnoceni"> <tr class="hodnoceni">
<td>{{ h.problem }}</td> <td>{{ h.problem }}</td>
<td>{{ h.body }}</td> <td class="bodovani">{{ h.body }}</td>
<td class="bodovani">{{ h.body_celkem }}</td>
<td class="bodovani teamovaCast">{{ h.body_neprepocitane }}</td>
<td class="bodovani teamovaCast">{{ h.body_neprepocitane_celkem }}</td>
<td>{{ h.feedback | linebreaks }}</td> <td>{{ h.feedback | linebreaks }}</td>
</tr> </tr>
{% endfor %} {% endfor %}
</table> </table>
{% endif %} {% endif %}
<h3>Vysvětlivky:</h3>
<dl>
<dt>{# 📖 #}🧍</dt>
<dd>Body za toto řešení.</dd>
<dt>{# 🔵 #}🧍∑</dt>
<dd>Body za tento problém/úlohu (součet za všechna řešení).</dd>
<dt class="teamovaCast">{# 💪 #}🧑‍🤝‍🧑</dt>
<dd class="teamovaCast">Body, které by dostal tým, kdyby to řešil jako jeden řešitel, za toto řešení.</dd>
<dt class="teamovaCast">{# ❤ #}🧑‍🤝‍🧑∑</dt>
<dd class="teamovaCast">Body, které by dostal tým, kdyby to řešil jako jeden řešitel, za tento problém/úlohu (součet za všechna řešení).</dd>
</dl>
{% if edit %}
<h3>Návod pro hodnocení:</h3>
Sloupce:
<ol>
<li>Pokud to neudělal řešitel, je třeba pomocí pluska přidat řádky (případně křížkem smazat) a vyplnit problémy tak, aby zde byly všechny, které řešení řeší (body zadáváme přímo k úlohám, ne k témátku samotnému).</li>
<li>Pak je třeba do jednoho ze 2 nebo 4 sloupců vyplnit body (lze udělovat desetiny, setiny už udělovat nejde):
<ul>
<li>TLDR: pokud si počítáš a kontroluješ vše sám, vyplňuj do nejlevějšího. Pokud naopak vždy vyplňuješ to, kolik řešení má dostat bodů (bez ohledu na počet řešitelů a předchozí odevzdání), vyplňuj nejpravější.</li>
<li>Zaprvé je třeba dávat pozor, že řešitel už mohl dostat body za danou úlohu (to je rozdíl mezi lichými a sudými sloupci).</li>
<li>Zadruhé řešení, na kterém se spolupracovalo, dostává body přepočítané podle vzorečku <a href="https://mam.matfyz.cz/jak-resit/podrobneji/">zde dole</a>. To dělá rozdíl mezi prvními a druhými dvěma sloupci, pokud se oboje zobrazují.</li>
<li>Co který sloupec znamená, je napsáno výše ve vysvětlivkách.
</ul>
</li>
<li>Pokud nemáš důvod, deadline neměň. Sloupeček s deadlinem znamená, do kterého deadlinu se započítají body (nemusí se shodovat s deadlinem řešení).</li>
<li>Poslední sloupec je na zpětnou vazbu řešiteli, tedy (na rozdíl od Neveřejné poznámky, která je určena pro synchronizaci orgů) ji uvidí řešitelé. Zatím jen pasivně (nechodí e-mail). Pohled řešitele si můžete prohlédnout <a href="{% url 'odevzdavatko_resitel_reseni' reseni.id %}">zde</a>. Pokud chcete z nějakého důvodu napsat řešitelům e-mail, klikněte na „Poslat mail všem řešitelům“.</li>
</ol>
Další poznámky
<ul>
<li>Pokud chceš zadané body smazat (rozmyslel sis to a ohodnotíš to později), smaž body v libovolném sloupeci.</li>
<li>Ne, soubory si zatím nejde stáhnout lépe než proklikáním všech řešeních. Stejně tak nejde hromadně bodovat. Třeba někdy půjde.</li>
<li>Pokud řešitel odevzdal něco nesouvisejícího, nebo něco duplicitně, tak mu za to dejte nulu a jako problém nastavte něco, co odevzdal (ať se mu ve výsledkovce nezobrazuje 0 na špatném místě). A upozorni ho.</li>
<li>Ano, lze zadávat záporné body (např. za podvádění), web vás bude silně upozorňovat, ale jinak mu to nevadí.</li>
<li>Libovolné problémy s hodnotítkem řeš s {% maillink 'webaři' to='web@mam.mff.cuni.cz' subject='Hodnotítko' %}.</li>
</ul>
{% endif %}
{% endblock %} {% endblock %}

View file

@ -224,12 +224,18 @@ class DetailReseniView(DetailView):
self.reseni = get_object_or_404(m.Reseni, id=self.kwargs['pk']) self.reseni = get_object_or_404(m.Reseni, id=self.kwargs['pk'])
result = [] # Slovníky s klíči problem, body, deadline_body -- initial data pro f.OhodnoceniReseniFormSet result = [] # Slovníky s klíči problem, body, deadline_body -- initial data pro f.OhodnoceniReseniFormSet
for hodn in m.Hodnoceni.objects.filter(reseni=self.reseni): for hodn in m.Hodnoceni.objects.filter(reseni=self.reseni):
result.append({ seznam_atributu = [
"problem": hodn.problem, "problem",
"body": hodn.body, "body",
"deadline_body": hodn.deadline_body, "body_celkem",
"feedback": hodn.feedback, "body_neprepocitane",
}) "body_neprepocitane_celkem",
"body_max",
"body_neprepocitane_max",
"deadline_body",
"feedback",
]
result.append({attr: getattr(hodn, attr) for attr in seznam_atributu})
return result return result
def get_context_data(self, **kw): def get_context_data(self, **kw):
@ -296,11 +302,23 @@ def hodnoceniReseniView(request, pk, *args, **kwargs):
# Vyrobíme nová podle formsetu # Vyrobíme nová podle formsetu
for form in formset: for form in formset:
data_for_hodnoceni = form.cleaned_data
data_for_body = data_for_hodnoceni.copy()
del(data_for_hodnoceni["body_celkem"])
del(data_for_hodnoceni["body_neprepocitane"])
del(data_for_hodnoceni["body_neprepocitane_celkem"])
hodnoceni = m.Hodnoceni( hodnoceni = m.Hodnoceni(
reseni=reseni, reseni=reseni,
**form.cleaned_data, **form.cleaned_data,
) )
logger.info(f"Creating Hodnoceni: {hodnoceni}") logger.info(f"Creating Hodnoceni: {hodnoceni}")
zmeny_bodu = [it for it in form.changed_data if it.startswith("body")]
if len(zmeny_bodu) == 1:
hodnoceni.__setattr__(zmeny_bodu[0], data_for_body[zmeny_bodu[0]])
# > jedna změna je špatně, ale 4 "změny" znamenají že nebylo nic zadáno
if len(zmeny_bodu) > 1 and len(zmeny_bodu) != 4:
logger.warning(f"Hodnocení {hodnoceni} mělo mít nastavené víc různých bodů: {zmeny_bodu}. Nastavuji -0.1.")
hodnoceni.body = -0.1
hodnoceni.save() hodnoceni.save()
return redirect(success_url) return redirect(success_url)
@ -360,8 +378,8 @@ class PosliReseniView(LoginRequiredMixin, FormView):
forma=data['forma'], forma=data['forma'],
poznamka=data['poznamka'], poznamka=data['poznamka'],
) )
nove_reseni.resitele.add(data['resitel']) nove_reseni.resitele.add(*data['resitel'])
nove_reseni.problem.add(data['problem']) nove_reseni.problem.add(*data['problem'])
nove_reseni.save() nove_reseni.save()
context = self.get_context_data() context = self.get_context_data()

View file

@ -14,6 +14,8 @@ from seminar.models import personalni as pm
from seminar.models import treenode as tm from seminar.models import treenode as tm
from seminar.models import base as bm from seminar.models import base as bm
from seminar.utils import vzorecek_na_prepocet, inverze_vzorecku_na_prepocet
@reversion.register(ignore_duplicates=True) @reversion.register(ignore_duplicates=True)
class Reseni(bm.SeminarModelBase): class Reseni(bm.SeminarModelBase):
@ -115,6 +117,61 @@ class Hodnoceni(bm.SeminarModelBase):
feedback = models.TextField('zpětná vazba', blank=True, default='', help_text='Zpětná vazba řešiteli (plain text)') feedback = models.TextField('zpětná vazba', blank=True, default='', help_text='Zpětná vazba řešiteli (plain text)')
@property
def body_celkem(self):
# FIXME řeším jen prvního řešitele.
return Hodnoceni.objects.filter(problem=self.problem, reseni__resitele=self.reseni.resitele.first(), body__isnull=False).aggregate(Sum("body"))["body__sum"]
@body_celkem.setter
def body_celkem(self, value):
if value is None:
self.body = None
else:
if self.body is None:
self.body = 0
if self.body_celkem is None:
self.body += value
else:
self.body += value - self.body_celkem
@property
def body_neprepocitane(self):
if self.body is None:
return None
return inverze_vzorecku_na_prepocet(self.body, self.reseni.resitele.count())
@body_neprepocitane.setter
def body_neprepocitane(self, value):
if value is None:
self.body = None
else:
self.body = vzorecek_na_prepocet(value, self.reseni.resitele.count())
@property
def body_neprepocitane_celkem(self):
if self.body_celkem is None:
return None
return inverze_vzorecku_na_prepocet(self.body_celkem, self.reseni.resitele.count())
@body_neprepocitane_celkem.setter
def body_neprepocitane_celkem(self, value):
if value is None:
self.body = None
else:
self.body_celkem = vzorecek_na_prepocet(value, self.reseni.resitele.count())
@property
def body_max(self):
if self.body_neprepocitane_max is None:
return None
return vzorecek_na_prepocet(self.body_neprepocitane_max, self.reseni.resitele.count())
@property
def body_neprepocitane_max(self):
if not isinstance(self.problem.get_real_instance(), am.Uloha):
return None
return self.problem.uloha.max_body
def __str__(self): def __str__(self):
return "{}, {}, {}".format(self.problem, self.reseni, self.body) return "{}, {}, {}".format(self.problem, self.reseni, self.body)

View file

@ -270,17 +270,27 @@ class Cislo(SeminarModelBase):
'na adrese {} najdete nejnovější číslo.\n' \ 'na adrese {} najdete nejnovější číslo.\n' \
'Vaše M&M\n'.format(odkaz) 'Vaše M&M\n'.format(odkaz)
predmet_prvni = 'Právě vyšlo 1. číslo M&M, pomoz nám ho poslat dál!'
text_mailu_prvni = 'Milý řešiteli,\n'\
'právě jsme na našem webu zveřejnili první číslo {}. ročníku, najdeš ho na tomto odkazu: {}.\n\n'\
'Doufáme, že tě M&M baví, a byli bychom rádi, kdyby mohlo dělat radost i dalším středoškolákům. Máme na tebe proto jednu prosbu. Sdílej prosím odkaz alespoň s jedním svým kamarádem, který by mohl mít o řešení M&M zájem. Je to pro nás moc důležité a velmi nám tím pomůžeš. Díky!\n\n'\
'Organizátoři M&M\n'.format(self.rocnik.rocnik, odkaz)
predmet_resitel = predmet_prvni if self.poradi == "1" else predmet
text_mailu_resitel = text_mailu_prvni if self.poradi == "1" else text_mailu
# Prijemci e-mailu # Prijemci e-mailu
resitele_vsichni = aktivniResitele(self).filter(zasilat_cislo_emailem=True) resitele_vsichni = aktivniResitele(self).filter(zasilat_cislo_emailem=True)
def posli(text, resitele): def posli(subject, text, resitele):
emaily = map(lambda resitel: resitel.osoba.email, resitele) emaily = map(lambda resitel: resitel.osoba.email, resitele)
if not settings.POSLI_MAILOVOU_NOTIFIKACI: if not settings.POSLI_MAILOVOU_NOTIFIKACI:
print("Poslal bych upozornění na tyto adresy: ", " ".join(emaily)) print("Poslal bych upozornění na tyto adresy: ", " ".join(emaily))
return return
email = EmailMessage( email = EmailMessage(
subject=predmet, subject=subject,
body=text, body=text,
from_email=poslat_z_mailu, from_email=poslat_z_mailu,
bcc=list(emaily) bcc=list(emaily)
@ -291,12 +301,12 @@ class Cislo(SeminarModelBase):
paticka = "---\nK odběru těchto e-mailů jste se přihlásili na stránkách https://mam.matfyz.cz. Z odběru se lze odhlásit na https://mam.matfyz.cz/resitel/osobni-udaje/" paticka = "---\nK odběru těchto e-mailů jste se přihlásili na stránkách https://mam.matfyz.cz. Z odběru se lze odhlásit na https://mam.matfyz.cz/resitel/osobni-udaje/"
posli(text_mailu + paticka, resitele_vsichni.filter(zasilat=pm.Resitel.zasilat_cislo_papirove)) posli(predmet_resitel, text_mailu_resitel + paticka, resitele_vsichni.filter(zasilat_cislo_papirove=False))
posli(text_mailu + 'P. S. Brzy budeme též rozesílat papírovou verzi čísla. Připomínáme, že pokud papírovou verzi čísla nevyužijete, můžete v https://mam.mff.cuni.cz/resitel/osobni-udaje/ zaškrtnout, abychom vám ji neposílali. Čísla vždy můžete nalézt v našem archivu a dál vám budou chodit e-mailem. Děkujeme.\n' + paticka, posli(predmet_resitel, text_mailu_resitel + 'P. S. Brzy budeme též rozesílat papírovou verzi čísla. Připomínáme, že pokud papírovou verzi čísla nevyužijete, můžete v https://mam.mff.cuni.cz/resitel/osobni-udaje/ zaškrtnout, abychom vám ji neposílali. Čísla vždy můžete nalézt v našem archivu a dál vám budou chodit e-mailem. Děkujeme.\n' + paticka,
resitele_vsichni.exclude(zasilat=pm.Resitel.zasilat_cislo_papirove)) resitele_vsichni.filter(zasilat_cislo_papirove=True))
paticka_prijemce = "---\nPokud tyto e-maily nechcete nadále dostávat, prosíme, ozvěte se nám na mam@matfyz.cz." paticka_prijemce = "---\nPokud tyto e-maily nechcete nadále dostávat, prosíme, ozvěte se nám na mam@matfyz.cz."
posli(text_mailu + paticka_prijemce, pm.Prijemce.objects.filter(zasilat_cislo_emailem=True)) posli(predmet, text_mailu + paticka_prijemce, pm.Prijemce.objects.filter(zasilat_cislo_emailem=True))
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
super().save(*args, **kwargs) super().save(*args, **kwargs)

File diff suppressed because it is too large Load diff

Before

Width:  |  Height:  |  Size: 1.8 MiB

After

Width:  |  Height:  |  Size: 664 KiB

File diff suppressed because it is too large Load diff

Before

Width:  |  Height:  |  Size: 1.8 MiB

After

Width:  |  Height:  |  Size: 689 KiB

File diff suppressed because it is too large Load diff

Before

Width:  |  Height:  |  Size: 1.8 MiB

After

Width:  |  Height:  |  Size: 767 KiB

View file

@ -1,6 +1,7 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
import datetime import datetime
import decimal
from django.contrib.auth import get_user_model from django.contrib.auth import get_user_model
from django.contrib.auth.decorators import permission_required, \ from django.contrib.auth.decorators import permission_required, \
@ -44,6 +45,16 @@ AnonymousUser.je_org = False
AnonymousUser.je_resitel = False AnonymousUser.je_resitel = False
def vzorecek_na_prepocet(body, resitelu):
""" Vzoreček na přepočet plných bodů na parciálni, když má řešení více řešitelů. """
return body * 3 / (resitelu + 2)
def inverze_vzorecku_na_prepocet(body: decimal.Decimal, resitelu) -> decimal.Decimal:
""" Vzoreček na přepočet parciálních bodů na plné, když má řešení více řešitelů. """
return round(body * (resitelu + 2) / 3, 1)
class FirstTagParser(HTMLParser): class FirstTagParser(HTMLParser):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
self.firstTag = None self.firstTag = None