Browse Source

Merge remote-tracking branch 'origin/master' into upgrade_odevzdavatka

# Conflicts:
#	mamweb/static/css/mamweb.css
pull/30/head
Jonas Havelka 1 year ago
parent
commit
bed107aeac
  1. 6
      api/views/autocomplete.py
  2. 1
      make/init_local
  3. 5
      mamweb/static/css/mamweb.css
  4. 37
      odevzdavatko/forms.py
  5. 10
      odevzdavatko/static/odevzdavatko/dynamic_formsets_for_detail.js
  6. 84
      odevzdavatko/templates/odevzdavatko/detail.html
  7. 34
      odevzdavatko/views.py
  8. 57
      seminar/models/odevzdavatko.py
  9. 22
      seminar/models/tvorba.py
  10. 30478
      seminar/templates/seminar/jakresit/jakresit_1.svg
  11. 30365
      seminar/templates/seminar/jakresit/jakresit_2.svg
  12. 30600
      seminar/templates/seminar/jakresit/jakresit_3.svg
  13. 11
      seminar/utils.py

6
api/views/autocomplete.py

@ -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

1
make/init_local

@ -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

5
mamweb/static/css/mamweb.css

@ -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 */

37
odevzdavatko/forms.py

@ -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,
) )

10
odevzdavatko/static/odevzdavatko/dynamic_formsets_for_detail.js

@ -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);
})
}); });

84
odevzdavatko/templates/odevzdavatko/detail.html

@ -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 %}

34
odevzdavatko/views.py

@ -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()

57
seminar/models/odevzdavatko.py

@ -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)

22
seminar/models/tvorba.py

@ -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)

30478
seminar/templates/seminar/jakresit/jakresit_1.svg

File diff suppressed because it is too large

Before

Width:  |  Height:  |  Size: 1.8 MiB

After

Width:  |  Height:  |  Size: 664 KiB

30365
seminar/templates/seminar/jakresit/jakresit_2.svg

File diff suppressed because it is too large

Before

Width:  |  Height:  |  Size: 1.8 MiB

After

Width:  |  Height:  |  Size: 689 KiB

30600
seminar/templates/seminar/jakresit/jakresit_3.svg

File diff suppressed because it is too large

Before

Width:  |  Height:  |  Size: 1.8 MiB

After

Width:  |  Height:  |  Size: 767 KiB

11
seminar/utils.py

@ -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

Loading…
Cancel
Save