Upgrade odevzdavatka #30
					 13 changed files with 20031 additions and 71547 deletions
				
			
		| 
						 | 
					@ -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
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -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
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -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 */
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -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,
 | 
				
			||||||
		)
 | 
							)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -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);
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -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 %}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -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()
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -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)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -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: 664 KiB After Width: | Height: | Size: 664 KiB  | 
										
											
												File diff suppressed because it is too large
												Load diff
											
										
									
								
							| 
		 Before Width: | Height: | Size: 689 KiB After Width: | Height: | Size: 689 KiB  | 
										
											
												File diff suppressed because it is too large
												Load diff
											
										
									
								
							| 
		 Before Width: | Height: | Size: 767 KiB After Width: | Height: | Size: 767 KiB  | 
| 
						 | 
					@ -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…
	
		Reference in a new issue