Merge pull request 'Vylepšení odevzdávátka' (!13) from vylepseni_odevzdavatka into master
Reviewed-on: #13
This commit is contained in:
		
						commit
						3110eb92a5
					
				
					 17 changed files with 218 additions and 170 deletions
				
			
		|  | @ -22,6 +22,7 @@ urlpatterns = [ | ||||||
| 	# Autocomplete | 	# Autocomplete | ||||||
| 	path('api/autocomplete/skola/', views.SkolaAutocomplete.as_view(), name='autocomplete_skola'), | 	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/', 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/odevzdatelny', views.OdevzdatelnyProblemAutocomplete.as_view(), name='autocomplete_problem_odevzdatelny'), | ||||||
| 	path('api/autocomplete/problem/vsechny', views.ProblemAutocomplete.as_view(), name='autocomplete_problem'), | 	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) | 			qs = qs.filter(query) | ||||||
| 		return qs | 		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( | ||||||
|  | 			prezdivka_resitele__isnull=False | ||||||
|  | 		).exclude( | ||||||
|  | 			prezdivka_resitele="" | ||||||
|  | 		).filter( | ||||||
|  | 			prezdivka_resitele__icontains=self.q | ||||||
|  | 		).all() | ||||||
|  | 		return qs | ||||||
|  | 
 | ||||||
|  | 	def get_result_label(self, result): | ||||||
|  | 		return result.prezdivka_resitele | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
| class OdevzdatelnyProblemAutocomplete(autocomplete.Select2QuerySetView): | class OdevzdatelnyProblemAutocomplete(autocomplete.Select2QuerySetView): | ||||||
| 	""" View k :mod:`dal.autocomplete` pro vyhledávání problémů především v odevzdávátku. """ | 	""" View k :mod:`dal.autocomplete` pro vyhledávání problémů především v odevzdávátku. """ | ||||||
| 	def get_queryset(self): | 	def get_queryset(self): | ||||||
|  |  | ||||||
|  | @ -63,7 +63,7 @@ class PosliReseniForm(forms.Form): | ||||||
| class NahrajReseniForm(forms.ModelForm): | class NahrajReseniForm(forms.ModelForm): | ||||||
| 	class Meta: | 	class Meta: | ||||||
| 		model = m.Reseni | 		model = m.Reseni | ||||||
| 		fields = ('problem',) | 		fields = ('problem', 'resitele') | ||||||
| 		help_texts = {'problem':''} # Nezobrazovat help text ve formuláři | 		help_texts = {'problem':''} # Nezobrazovat help text ve formuláři | ||||||
| 		 | 		 | ||||||
| 		widgets = {'problem': | 		widgets = {'problem': | ||||||
|  | @ -72,6 +72,13 @@ class NahrajReseniForm(forms.ModelForm): | ||||||
| 					attrs = {'data-placeholder--id': '-1', | 					attrs = {'data-placeholder--id': '-1', | ||||||
| 						'data-placeholder--text' : '---', | 						'data-placeholder--text' : '---', | ||||||
| 						'data-allow-clear': 'true'}, | 						'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
									
								
							
							
						
						
									
										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
 | ||||||
|  |         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 %} | {% block content %} | ||||||
| 
 | 
 | ||||||
| {# FIXME: Necopypastovat! Tohle je zkopírované ze static/odevzdavatko/dynamic_formsets.js #} |   {% if edit %} | ||||||
| <script type='text/javascript'> |     <script src="{% static 'odevzdavatko/dynamic_formsets_for_detail.js' %}"></script> | ||||||
| // Credit https://medium.com/all-about-django/adding-forms-dynamically-to-a-django-formset-375f1090c2b0 |     <script src="{% static 'odevzdavatko/check_for_detail.js' %}"></script> | ||||||
| function updateElementIndex(el, prefix, ndx) { |   {% endif %} | ||||||
| 	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> |  | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| <p>Řešené problémy: {{ object.problem.all | join:", " }}</p> | <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 edit %} | ||||||
| {% if forloop.revcounter0 != 0 %}, {% endif %} {% endfor %}</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> | ||||||
|  | {% else %} | ||||||
|  |   <p>Řešitelé: {{ object.resitele.all | join:", " }}</p> | ||||||
|  | {% endif %} | ||||||
| 
 | 
 | ||||||
| {# https://docs.djangoproject.com/en/3.1/ref/models/instances/#django.db.models.Model.get_FOO_display #} | {# 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>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><a href="{{ priloha.soubor.url }}" download>{{ priloha.split | last }}</a></td> | ||||||
| 	<td>{{ priloha.res_poznamka }}</td> | 	<td>{{ priloha.res_poznamka }}</td> | ||||||
| 	<td>{{ priloha.vytvoreno }}</td></tr> | 	<td>{{ priloha.vytvoreno }}</td></tr> | ||||||
| 	{# TODO: Orgo-poznámka, ideálně jako formulář #} |  | ||||||
| {% endfor %} | {% endfor %} | ||||||
| </table> | </table> | ||||||
| {% else %} | {% else %} | ||||||
| <p>Žádné přílohy</p> | <p>Žádné přílohy</p> | ||||||
| {% endif %} | {% endif %} | ||||||
| 
 | 
 | ||||||
|  |   {% if edit %} | ||||||
| <form method=post onsubmit="return zkontroluj_hodnoceni();"> | <form method=post onsubmit="return zkontroluj_hodnoceni();"> | ||||||
| {# Poznámka #} | {# Poznámka #} | ||||||
| <h3>Neveřejná poznámka:</h3> | <h3>Neveřejná poznámka:</h3> | ||||||
|  | @ -116,7 +69,7 @@ $(document).ready(function(){ | ||||||
| </table> | </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> | <input type=submit value="Uložit"></form> | ||||||
| 
 | 
 | ||||||
| <table id="empty_form" style="display: none;"> | <table id="empty_form" style="display: none;"> | ||||||
|  | @ -129,28 +82,19 @@ $(document).ready(function(){ | ||||||
| 	</tr> | 	</tr> | ||||||
| </table> | </table> | ||||||
| 
 | 
 | ||||||
|  |   {% else %} | ||||||
|  | <h3>Hodnocení:</h3> | ||||||
|  | <table class="dosla_reseni"> | ||||||
|  | <tr><th>Problém</th><th>Body</th><th>Zpětná vazba od opravovatele</th></tr> | ||||||
|  | {% for h in hodnoceni %} | ||||||
|  | 	<tr class="hodnoceni"> | ||||||
|  | 		<td>{{ h.problem }}</td> | ||||||
|  | 		<td>{{ h.body }}</td> | ||||||
|  | 		<td>{{ h.feedback }}</td> | ||||||
|  | 	</tr> | ||||||
|  | {% endfor %} | ||||||
|  | </table> | ||||||
|  |   {% 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 %} | {% 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 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> | ||||||
|  | 
 | ||||||
| <form enctype="multipart/form-data" action="{% url 'seminar_nahraj_reseni' %}" method="post" onsubmit="return zkontroluj_prilohy();"> | <form enctype="multipart/form-data" action="{% url 'seminar_nahraj_reseni' %}" method="post" onsubmit="return zkontroluj_prilohy();"> | ||||||
|   {% csrf_token %} |   {% csrf_token %} | ||||||
|   <table class='form' id="reseni"> |   <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/', 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/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: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/all', org_required(views.SeznamReseniView.as_view())), | ||||||
| 	path('org/reseni/akt', org_required(views.SeznamAktualnichReseniView.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'), | ||||||
| ] | ] | ||||||
|  |  | ||||||
|  | @ -216,6 +216,7 @@ class ReseniProblemuView(MultipleObjectTemplateResponseMixin, MultipleObjectMixi | ||||||
| 
 | 
 | ||||||
| ## XXX: https://docs.djangoproject.com/en/3.1/topics/class-based-views/mixins/#avoid-anything-more-complex | ## XXX: https://docs.djangoproject.com/en/3.1/topics/class-based-views/mixins/#avoid-anything-more-complex | ||||||
| class DetailReseniView(DetailView): | class DetailReseniView(DetailView): | ||||||
|  | 	""" Náhled na řešení. Editace je v :py:class:`EditReseniView`. """ | ||||||
| 	model = m.Reseni | 	model = m.Reseni | ||||||
| 	template_name = 'odevzdavatko/detail.html' | 	template_name = 'odevzdavatko/detail.html' | ||||||
| 	 | 	 | ||||||
|  | @ -232,18 +233,44 @@ class DetailReseniView(DetailView): | ||||||
| 		return result | 		return result | ||||||
| 
 | 
 | ||||||
| 	def get_context_data(self, **kw): | 	def get_context_data(self, **kw): | ||||||
|  | 		self.check_access() | ||||||
| 		ctx = super().get_context_data(**kw) | 		ctx = super().get_context_data(**kw) | ||||||
| 		ctx['form'] = f.OhodnoceniReseniFormSet( | 		hodnoceni = self.aktualni_hodnoceni() | ||||||
| 				initial = self.aktualni_hodnoceni() | 		ctx["hodnoceni"] = hodnoceni | ||||||
| 				) |  | ||||||
| 		ctx['poznamka_form'] = f.PoznamkaReseniForm(instance=self.reseni) |  | ||||||
| 		return ctx | 		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 | ||||||
|  | 		if not self.request.user.je_org: | ||||||
|  | 			raise PermissionDenied() | ||||||
|  | 
 | ||||||
| 
 | 
 | ||||||
| def hodnoceniReseniView(request, pk, *args, **kwargs): | def hodnoceniReseniView(request, pk, *args, **kwargs): | ||||||
| 	reseni = get_object_or_404(m.Reseni, pk=pk) | 	reseni = get_object_or_404(m.Reseni, pk=pk) | ||||||
| 	template_name = 'odevzdavatko/detail.html' |  | ||||||
| 	form_class = f.OhodnoceniReseniFormSet |  | ||||||
| 	success_url = reverse('odevzdavatko_detail_reseni', kwargs={'pk': pk}) | 	success_url = reverse('odevzdavatko_detail_reseni', kwargs={'pk': pk}) | ||||||
| 
 | 
 | ||||||
| 	# FIXME: Použit initial i tady a nebastlit hodnocení tak nízkoúrovňově | 	# FIXME: Použit initial i tady a nebastlit hodnocení tak nízkoúrovňově | ||||||
|  | @ -275,33 +302,6 @@ def hodnoceniReseniView(request, pk, *args, **kwargs): | ||||||
| 	return redirect(success_url) | 	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): | class PrehledOdevzdanychReseni(ListView): | ||||||
|  | @ -413,6 +413,7 @@ class NahrajReseniView(LoginRequiredMixin, CreateView): | ||||||
| 		with transaction.atomic(): | 		with transaction.atomic(): | ||||||
| 			self.object = form.save() | 			self.object = form.save() | ||||||
| 			self.object.resitele.add(m.Resitel.objects.get(osoba__user = self.request.user)) | 			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.cas_doruceni = timezone.now() | ||||||
| 			self.object.forma = m.Reseni.FORMA_UPLOAD | 			self.object.forma = m.Reseni.FORMA_UPLOAD | ||||||
| 			self.object.save() | 			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') | 			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) | 	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) | 	prijmeni = forms.CharField(label='Příjmení', max_length=256, required=True) | ||||||
| 	pohlavi_muz = forms.ChoiceField(label='Pohlaví', | 	pohlavi_muz = forms.ChoiceField(label='Pohlaví', | ||||||
| 			choices = ((True,'muž'),(False,'žena')), required=True) | 			choices = ((True,'muž'),(False,'žena')), required=True) | ||||||
|  | @ -105,6 +106,14 @@ class PrihlaskaForm(PasswordResetForm): | ||||||
| 			pass | 			pass | ||||||
| 		return email | 		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): | 	def clean_zasilat(self): | ||||||
| 		zasilat = self.cleaned_data.get('zasilat') | 		zasilat = self.cleaned_data.get('zasilat') | ||||||
| 		ulice = self.cleaned_data.get('ulice') | 		ulice = self.cleaned_data.get('ulice') | ||||||
|  | @ -138,6 +147,7 @@ class ProfileEditForm(forms.Form): | ||||||
| 			disabled=True) | 			disabled=True) | ||||||
| 
 | 
 | ||||||
| 	jmeno = forms.CharField(label='Jméno', max_length=256, required=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) | 	prijmeni = forms.CharField(label='Příjmení', max_length=256, required=True) | ||||||
| 	pohlavi_muz = forms.ChoiceField(label='Pohlaví', | 	pohlavi_muz = forms.ChoiceField(label='Pohlaví', | ||||||
| 			choices = ((True,'muž'),(False,'žena')), required=True) | 			choices = ((True,'muž'),(False,'žena')), required=True) | ||||||
|  | @ -190,6 +200,15 @@ class ProfileEditForm(forms.Form): | ||||||
| #			pass | #			pass | ||||||
| #		return username | #		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): | 	def clean_email(self): | ||||||
| 		err_logger = logging.getLogger('seminar.prihlaska.problem') | 		err_logger = logging.getLogger('seminar.prihlaska.problem') | ||||||
| 		email = self.cleaned_data.get('email') | 		email = self.cleaned_data.get('email') | ||||||
|  |  | ||||||
|  | @ -44,6 +44,7 @@ | ||||||
|     </h4> |     </h4> | ||||||
|       <table class="form"> |       <table class="form"> | ||||||
|        {% include "personalni/udaje/prihlaska_field.html" with field=form.jmeno %} |        {% 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.prijmeni %} | ||||||
|        {% include "personalni/udaje/prihlaska_field.html" with field=form.pohlavi_muz%} |        {% include "personalni/udaje/prihlaska_field.html" with field=form.pohlavi_muz%} | ||||||
|        {% include "personalni/udaje/prihlaska_field.html" with field=form.email %} |        {% 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. | 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> | ||||||
| <p class="gdpr"> | <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. | ||||||
| </p> | </p> | ||||||
| <p class="gdpr"> | <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. | 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> |         </h4> | ||||||
|           <table class="form"> |           <table class="form"> | ||||||
|             {% include "personalni/udaje/prihlaska_field.html" with field=form.jmeno %} |             {% 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.prijmeni %} | ||||||
|             {% include "personalni/udaje/prihlaska_field.html" with field=form.pohlavi_muz%} |             {% include "personalni/udaje/prihlaska_field.html" with field=form.pohlavi_muz%} | ||||||
|             {% include "personalni/udaje/prihlaska_field.html" with field=form.email %} |             {% include "personalni/udaje/prihlaska_field.html" with field=form.email %} | ||||||
|  |  | ||||||
|  | @ -160,6 +160,7 @@ def resitelEditView(request): | ||||||
| 
 | 
 | ||||||
| 			if resitel_edit: | 			if resitel_edit: | ||||||
| 				## Změny v řešiteli | 				## Změny v řešiteli | ||||||
|  | 				resitel_edit.prezdivka_resitele = fcd['prezdivka_resitele'] | ||||||
| 				resitel_edit.skola = fcd['skola'] | 				resitel_edit.skola = fcd['skola'] | ||||||
| 				resitel_edit.rok_maturity = fcd['rok_maturity'] | 				resitel_edit.rok_maturity = fcd['rok_maturity'] | ||||||
| 				resitel_edit.zasilat = fcd['zasilat'] | 				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]}') | 					err_logger.warning(f'Zaregistrovala se osoba s kolizním jménem. ID osob: {[o.id for o in kolize]}') | ||||||
| 
 | 
 | ||||||
| 				r = s.Resitel( | 				r = s.Resitel( | ||||||
|  | 					prezdivka_resitele=fcd['prezdivka_resitele'], | ||||||
| 					rok_maturity = fcd['rok_maturity'], | 					rok_maturity = fcd['rok_maturity'], | ||||||
| 					zasilat = fcd['zasilat'], | 					zasilat = fcd['zasilat'], | ||||||
| 					zasilat_cislo_emailem = fcd['zasilat_cislo_emailem'] | 					zasilat_cislo_emailem = fcd['zasilat_cislo_emailem'] | ||||||
|  |  | ||||||
							
								
								
									
										18
									
								
								seminar/migrations/0110_resitel_prezdivka.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										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 | 	# Interní ID | ||||||
| 	id = models.AutoField(primary_key = True) | 	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', | 	osoba = models.OneToOneField(Osoba, blank=False, null=False, verbose_name='osoba', | ||||||
| 		on_delete=models.PROTECT) | 		on_delete=models.PROTECT) | ||||||
| 	 | 	 | ||||||
|  |  | ||||||
		Loading…
	
		Reference in a new issue