Merge branch 'vysledkovky2' into develop
This commit is contained in:
		
						commit
						4aa67d6151
					
				
					 37 changed files with 1170 additions and 1001 deletions
				
			
		|  | @ -633,5 +633,20 @@ | ||||||
| 		"codename": "view_fotkaurlvazba", | 		"codename": "view_fotkaurlvazba", | ||||||
| 		"ct_app_label": "header_fotky", | 		"ct_app_label": "header_fotky", | ||||||
| 		"ct_model": "fotkaurlvazba" | 		"ct_model": "fotkaurlvazba" | ||||||
|  | 	}, | ||||||
|  | 	{ | ||||||
|  | 		"codename": "add_deadline", | ||||||
|  | 		"ct_app_label": "seminar", | ||||||
|  | 		"ct_model": "deadline" | ||||||
|  | 	}, | ||||||
|  | 	{ | ||||||
|  | 		"codename": "change_deadline", | ||||||
|  | 		"ct_app_label": "seminar", | ||||||
|  | 		"ct_model": "deadline" | ||||||
|  | 	}, | ||||||
|  | 	{ | ||||||
|  | 		"codename": "view_deadline", | ||||||
|  | 		"ct_app_label": "seminar", | ||||||
|  | 		"ct_model": "deadline" | ||||||
| 	} | 	} | ||||||
| ] | ] | ||||||
|  |  | ||||||
|  | @ -2,6 +2,7 @@ from django import forms | ||||||
| from dal import autocomplete | from dal import autocomplete | ||||||
| from django.forms import formset_factory | from django.forms import formset_factory | ||||||
| from django.forms.models import inlineformset_factory | from django.forms.models import inlineformset_factory | ||||||
|  | from django.utils import timezone | ||||||
| 
 | 
 | ||||||
| from seminar.models import Resitel | from seminar.models import Resitel | ||||||
| import seminar.models as m | import seminar.models as m | ||||||
|  | @ -87,7 +88,7 @@ ReseniSPrilohamiFormSet = inlineformset_factory(m.Reseni,m.PrilohaReseni, | ||||||
| class JednoHodnoceniForm(forms.ModelForm): | class JednoHodnoceniForm(forms.ModelForm): | ||||||
| 	class Meta: | 	class Meta: | ||||||
| 		model = m.Hodnoceni | 		model = m.Hodnoceni | ||||||
| 		fields = ('problem', 'body', 'cislo_body') | 		fields = ('problem', 'body', 'deadline_body') | ||||||
| 		widgets = { | 		widgets = { | ||||||
| 			'problem': autocomplete.ModelSelect2( | 			'problem': autocomplete.ModelSelect2( | ||||||
| 				url='autocomplete_problem_odevzdatelny',   # FIXME: Dovolit i starší? | 				url='autocomplete_problem_odevzdatelny',   # FIXME: Dovolit i starší? | ||||||
|  | @ -141,7 +142,6 @@ class OdevzdavatkoTabulkaFiltrForm(forms.Form): | ||||||
| 		from django.db.utils import OperationalError | 		from django.db.utils import OperationalError | ||||||
| 		try: | 		try: | ||||||
| 			aktualni_rocnik = m.Nastaveni.get_solo().aktualni_rocnik | 			aktualni_rocnik = m.Nastaveni.get_solo().aktualni_rocnik | ||||||
| 			aktualni_cislo = m.Nastaveni.get_solo().aktualni_cislo |  | ||||||
| 		except OperationalError: | 		except OperationalError: | ||||||
| 			# django.db.utils.OperationalError: no such table: seminar_nastaveni | 			# django.db.utils.OperationalError: no such table: seminar_nastaveni | ||||||
| 			# Nemáme databázi, takže to selhalo. Pro jistotu vrátíme aspoň dvě možnosti, ať to nepadá dál | 			# Nemáme databázi, takže to selhalo. Pro jistotu vrátíme aspoň dvě možnosti, ať to nepadá dál | ||||||
|  | @ -152,31 +152,18 @@ class OdevzdavatkoTabulkaFiltrForm(forms.Form): | ||||||
| 		# FIXME: Tohle je hnusný monkey patch, mělo by to být nějak zahrnuto výš. | 		# FIXME: Tohle je hnusný monkey patch, mělo by to být nějak zahrnuto výš. | ||||||
| 		if rocnik is not None: | 		if rocnik is not None: | ||||||
| 			aktualni_rocnik = rocnik | 			aktualni_rocnik = rocnik | ||||||
| 			aktualni_cislo = m.Cislo.objects.filter(rocnik=rocnik).order_by('poradi').last() |  | ||||||
| 
 | 
 | ||||||
| 		result = [] | 		result = [] | ||||||
| 
 | 
 | ||||||
| 		for cislo in m.Cislo.objects.filter( | 		for deadline in m.Deadline.objects.filter( | ||||||
| 				rocnik=aktualni_rocnik, | 				deadline__lte=timezone.now(), | ||||||
| 				poradi__lte=aktualni_cislo.poradi, | 				cislo__rocnik=aktualni_rocnik | ||||||
| 				).reverse():	# Standardně se řadí od nejnovějšího čísla | 				).order_by("deadline"): | ||||||
| 			# Předem je mi líto kohokoliv, kdo tyhle řádky bude číst... | 
 | ||||||
| 			if cislo.datum_vydani is not None and cislo.datum_vydani <= datetime.date.today(): | 			result.append(( | ||||||
| 				result.append(( | 				strftime(DATE_FORMAT, deadline.deadline.timetuple()), | ||||||
| 					strftime(DATE_FORMAT, cislo.datum_vydani.timetuple()), | 				str(deadline))) | ||||||
| 					f"Vydání {cislo.poradi}. čísla")) | 
 | ||||||
| 			if cislo.datum_preddeadline is not None and cislo.datum_preddeadline <= datetime.date.today(): |  | ||||||
| 				result.append(( |  | ||||||
| 					strftime(DATE_FORMAT, cislo.datum_preddeadline.timetuple()), |  | ||||||
| 					f"Předdeadline {cislo.poradi}. čísla")) |  | ||||||
| 			if cislo.datum_deadline_soustredeni is not None and cislo.datum_deadline_soustredeni <= datetime.date.today(): |  | ||||||
| 				result.append(( |  | ||||||
| 					strftime(DATE_FORMAT, cislo.datum_deadline_soustredeni.timetuple()), |  | ||||||
| 					f"Sous. deadline {cislo.poradi}. čísla")) |  | ||||||
| 			if cislo.datum_deadline is not None and cislo.datum_deadline <= datetime.date.today(): |  | ||||||
| 				result.append(( |  | ||||||
| 					strftime(DATE_FORMAT, cislo.datum_deadline.timetuple()), |  | ||||||
| 					f"Finální deadline {cislo.poradi}. čísla")) |  | ||||||
| 		result.append(( | 		result.append(( | ||||||
| 			strftime(DATE_FORMAT, datetime.date.today().timetuple()), f"Dnes")) | 			strftime(DATE_FORMAT, datetime.date.today().timetuple()), f"Dnes")) | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -49,6 +49,10 @@ $(document).ready(function(){ | ||||||
| 		$('.smazat_hodnoceni').click(function(){ | 		$('.smazat_hodnoceni').click(function(){ | ||||||
| 			deleteForm("form",this); | 			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); | 		$('#id_form-TOTAL_FORMS').val(parseInt(form_idx) + 1); | ||||||
| 	}); | 	}); | ||||||
| 	$('.smazat_hodnoceni').click(function(){ | 	$('.smazat_hodnoceni').click(function(){ | ||||||
|  | @ -66,7 +70,7 @@ $(document).ready(function(){ | ||||||
| {# 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> | ||||||
| 
 | 
 | ||||||
| <p>Doručeno {{ object.cas_doruceni }}, deadline: {{object.cas_doruceni | deadline_html }}</p> | <p>Doručeno {{ object.cas_doruceni }}, deadline: {{object.deadline_reseni | deadline_html }}</p> | ||||||
| 
 | 
 | ||||||
| {# Soubory: #} | {# Soubory: #} | ||||||
| <h3>Přílohy:</h3> | <h3>Přílohy:</h3> | ||||||
|  | @ -97,13 +101,13 @@ $(document).ready(function(){ | ||||||
| {{ 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>Číslo pro body</th></tr> | <tr><th>Problém</th><th>Body</th><th>Deadline pro body</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>{{ subform.body }}</td> | ||||||
| 		<td>{{ subform.cislo_body }}</td> | 		<td>{{ subform.deadline_body }}</td> | ||||||
| 		<td><a href="#" class="smazat_hodnoceni" id="id_{{subform.prefix}}-jsremove"><img src="{% static "odevzdavatko/cross.png" %}" alt="Smazat"></a></td> | 		<td><a href="#" class="smazat_hodnoceni" id="id_{{subform.prefix}}-jsremove"><img src="{% static "odevzdavatko/cross.png" %}" alt="Smazat"></a></td> | ||||||
| 	</tr> | 	</tr> | ||||||
|     </tbody> |     </tbody> | ||||||
|  | @ -118,7 +122,7 @@ $(document).ready(function(){ | ||||||
| 	<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>{{ form.empty_form.body }}</td> | ||||||
| 		<td>{{ form.empty_form.cislo_body }}</td> | 		<td>{{ form.empty_form.deadline_body }}</td> | ||||||
| 		<td><a href="#" class="smazat_hodnoceni" id="id_{{form.empty_form.prefix}}-jsremove"><img src="{% static "odevzdavatko/cross.png" %}" alt="Smazat"></a></td> | 		<td><a href="#" class="smazat_hodnoceni" id="id_{{form.empty_form.prefix}}-jsremove"><img src="{% static "odevzdavatko/cross.png" %}" alt="Smazat"></a></td> | ||||||
| 	</tr> | 	</tr> | ||||||
| </table> | </table> | ||||||
|  |  | ||||||
|  | @ -12,7 +12,7 @@ | ||||||
| {# 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> | ||||||
| 
 | 
 | ||||||
| <p>Doručeno {{ object.cas_doruceni }}, deadline: {{object.cas_doruceni | deadline_html }}</p> | <p>Doručeno {{ object.cas_doruceni }}, deadline: {{object.deadline_reseni | deadline_html }}</p> | ||||||
| 
 | 
 | ||||||
| {# Soubory: #} | {# Soubory: #} | ||||||
| <h3>Přílohy:</h3> | <h3>Přílohy:</h3> | ||||||
|  | @ -37,12 +37,12 @@ | ||||||
| {# Hodnocení: #} | {# Hodnocení: #} | ||||||
| <h3>Hodnocení:</h3> | <h3>Hodnocení:</h3> | ||||||
| <table id="form_set" class="dosla_reseni"> | <table id="form_set" class="dosla_reseni"> | ||||||
| <tr><th>Problém</th><th>Body</th>{# <th>Číslo pro body</th> #}</tr> | <tr><th>Problém</th><th>Body</th>{# <th>Deadline pro body</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>{{ h.body }}</td> | ||||||
| {#		<td>{{ h.cislo_body }}</td>#} | {#		<td>{{ h.deadline_body }}</td>#} | ||||||
| 	</tr> | 	</tr> | ||||||
| {% endfor %} | {% endfor %} | ||||||
| </table> | </table> | ||||||
|  |  | ||||||
|  | @ -6,7 +6,7 @@ | ||||||
| <h3>Označení deadlinů</h3> | <h3>Označení deadlinů</h3> | ||||||
| <ul> | <ul> | ||||||
| 	<li>Ⓢ deadline pro účast na soustředění</li> | 	<li>Ⓢ deadline pro účast na soustředění</li> | ||||||
| 	<li>♲ 1. deadline</li> | 	<li>⭯ 1. deadline</li> | ||||||
| 	<li>✓ 2. deadline</li> | 	<li>✓ 2. deadline</li> | ||||||
| </ul> | </ul> | ||||||
| 
 | 
 | ||||||
|  | @ -29,7 +29,7 @@ | ||||||
| 		<td class="problem odevzdanareseni_small"><span title="{{ hodn.problem.nazev }}">{{ hodn.problem.nazev | zkrat_nazev_problemu:27 }}</span></td> | 		<td class="problem odevzdanareseni_small"><span title="{{ hodn.problem.nazev }}">{{ hodn.problem.nazev | zkrat_nazev_problemu:27 }}</span></td> | ||||||
| 		<td class="problem odevzdanareseni_mini"><span title="{{ hodn.problem.nazev }}">{{ hodn.problem.nazev | zkrat_nazev_problemu:10 }}</span></td> | 		<td class="problem odevzdanareseni_mini"><span title="{{ hodn.problem.nazev }}">{{ hodn.problem.nazev | zkrat_nazev_problemu:10 }}</span></td> | ||||||
|         <td>{{ hodn.body|default_if_none:"---" }}</td> |         <td>{{ hodn.body|default_if_none:"---" }}</td> | ||||||
| 		<td>{{ hodn.reseni.cas_doruceni | deadline_html }}</td> | 		<td>{{ hodn.deadline_body | deadline_html }}</td> | ||||||
| 	</tr> | 	</tr> | ||||||
| 	{% endfor %} | 	{% endfor %} | ||||||
| </table> | </table> | ||||||
|  |  | ||||||
|  | @ -4,7 +4,7 @@ | ||||||
| {% block content %} | {% block content %} | ||||||
| 
 | 
 | ||||||
| {% for dl, mnozina_reseni in reseni_podle_deadlinu.items %} | {% for dl, mnozina_reseni in reseni_podle_deadlinu.items %} | ||||||
| <h1>{{ dl.2 | deadline_html }}</h1> | <h1>{{ dl | deadline_html }}</h1> | ||||||
| <ul> | <ul> | ||||||
| 	{% for obj in mnozina_reseni %} | 	{% for obj in mnozina_reseni %} | ||||||
| 	<li>{{ obj.sum_body }} b za <a href="{% url 'odevzdavatko_detail_reseni' pk=obj.id %}">{{ obj }}</a> ({{ obj.get_forma_display }} {{ obj.cas_doruceni }}) | 	<li>{{ obj.sum_body }} b za <a href="{% url 'odevzdavatko_detail_reseni' pk=obj.id %}">{{ obj }}</a> ({{ obj.get_forma_display }} {{ obj.cas_doruceni }}) | ||||||
|  |  | ||||||
|  | @ -19,7 +19,7 @@ import logging | ||||||
| import seminar.models as m | import seminar.models as m | ||||||
| from . import forms as f | from . import forms as f | ||||||
| from .forms import OdevzdavatkoTabulkaFiltrForm as FiltrForm | from .forms import OdevzdavatkoTabulkaFiltrForm as FiltrForm | ||||||
| from seminar.utils import resi_v_rocniku, deadline | from seminar.utils import resi_v_rocniku | ||||||
| from seminar.views import formularOKView | from seminar.views import formularOKView | ||||||
| 
 | 
 | ||||||
| logger = logging.getLogger(__name__) | logger = logging.getLogger(__name__) | ||||||
|  | @ -202,7 +202,7 @@ class ReseniProblemuView(MultipleObjectTemplateResponseMixin, MultipleObjectMixi | ||||||
| 		# XXX: Předat groupby do template nejde: https://stackoverflow.com/questions/6906593/itertools-groupby-in-a-django-template | 		# XXX: Předat groupby do template nejde: https://stackoverflow.com/questions/6906593/itertools-groupby-in-a-django-template | ||||||
| 		# Django má {% regroup %}, ale ten potřebuje, aby klíč byl atribut položky: https://docs.djangoproject.com/en/3.2/ref/templates/builtins/#regroup | 		# Django má {% regroup %}, ale ten potřebuje, aby klíč byl atribut položky: https://docs.djangoproject.com/en/3.2/ref/templates/builtins/#regroup | ||||||
| 		# Takže rozbalíme groupby do slovníku klíč → seznam sami (dictionary comphrehension) | 		# Takže rozbalíme groupby do slovníku klíč → seznam sami (dictionary comphrehension) | ||||||
| 		ctx['reseni_podle_deadlinu'] = {k: list(v) for k,v in groupby(ctx['object_list'], lambda r: deadline(r.cas_doruceni))} | 		ctx['reseni_podle_deadlinu'] = {k: list(v) for k,v in groupby(ctx['object_list'], lambda r: r.deadline_reseni)} | ||||||
| 
 | 
 | ||||||
| 		# Pro sitetree: | 		# Pro sitetree: | ||||||
| 		ctx["resitel_id"] = self.kwargs['resitel'] | 		ctx["resitel_id"] = self.kwargs['resitel'] | ||||||
|  | @ -216,12 +216,12 @@ class DetailReseniView(DetailView): | ||||||
| 	 | 	 | ||||||
| 	def aktualni_hodnoceni(self): | 	def aktualni_hodnoceni(self): | ||||||
| 		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, cislo_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( | 			result.append( | ||||||
| 				{"problem": hodn.problem,  | 				{"problem": hodn.problem,  | ||||||
| 				"body": hodn.body, | 				"body": hodn.body, | ||||||
| 				"cislo_body": hodn.cislo_body, | 				"deadline_body": hodn.deadline_body, | ||||||
| 				}) | 				}) | ||||||
| 		return result | 		return result | ||||||
| 
 | 
 | ||||||
|  | @ -260,11 +260,11 @@ def hodnoceniReseniView(request, pk, *args, **kwargs): | ||||||
| 			for form in formset: | 			for form in formset: | ||||||
| 				problem = form.cleaned_data['problem'] | 				problem = form.cleaned_data['problem'] | ||||||
| 				body = form.cleaned_data['body'] | 				body = form.cleaned_data['body'] | ||||||
| 				cislo_body = form.cleaned_data['cislo_body'] | 				deadline_body = form.cleaned_data['deadline_body'] | ||||||
| 				hodnoceni = m.Hodnoceni( | 				hodnoceni = m.Hodnoceni( | ||||||
| 					problem=problem, | 					problem=problem, | ||||||
| 					body=body, | 					body=body, | ||||||
| 					cislo_body=cislo_body, | 					deadline_body=deadline_body, | ||||||
| 					reseni=reseni, | 					reseni=reseni, | ||||||
| 					) | 					) | ||||||
| 				logger.info(f"Creating Hodnoceni: {hodnoceni}") | 				logger.info(f"Creating Hodnoceni: {hodnoceni}") | ||||||
|  | @ -285,7 +285,7 @@ class ResitelReseniView(DetailView): | ||||||
| 				{ | 				{ | ||||||
| 					"problem": hodn.problem, | 					"problem": hodn.problem, | ||||||
| 					"body": hodn.body, | 					"body": hodn.body, | ||||||
| 					# "cislo_body": hodn.cislo_body, | 					# "deadline_body": hodn.deadline_body, | ||||||
| 				} | 				} | ||||||
| 			) | 			) | ||||||
| 		return result | 		return result | ||||||
|  | @ -320,9 +320,8 @@ class PrehledOdevzdanychReseni(ListView): | ||||||
| 		ctx = super().get_context_data(*args, **kwargs) | 		ctx = super().get_context_data(*args, **kwargs) | ||||||
| 		# Ročník určujeme podle čísla, do jehož deadlinu došlo řešení. | 		# Ročník určujeme podle čísla, do jehož deadlinu došlo řešení. | ||||||
| 		# Chceme to mít seřazené, takže místo comphrerehsion ručně postavíme pole polí. Django templates neumí použít OrderedDict :-/ | 		# Chceme to mít seřazené, takže místo comphrerehsion ručně postavíme pole polí. Django templates neumí použít OrderedDict :-/ | ||||||
| 		# TODO: Funkce deadline vrací deadliny v jiném ročníku, zvlášť pokud se vyrobí řešení až po deadlinu (třeba při poslání mailem) |  | ||||||
| 		podle_rocniku = [] | 		podle_rocniku = [] | ||||||
| 		for rocnik, hodnoceni in groupby(ctx['object_list'], lambda ho: deadline(ho.reseni.cas_doruceni)[1].rocnik if deadline(ho.reseni.cas_doruceni) is not None else None): | 		for rocnik, hodnoceni in groupby(ctx['object_list'], lambda ho: ho.deadline_body.cislo.rocnik if ho.deadline_body is not None else None): | ||||||
| 			podle_rocniku.append((rocnik, list(hodnoceni))) | 			podle_rocniku.append((rocnik, list(hodnoceni))) | ||||||
| 		ctx['podle_rocniku'] = reversed(podle_rocniku) # Od nejnovějšího ročníku | 		ctx['podle_rocniku'] = reversed(podle_rocniku) # Od nejnovějšího ročníku | ||||||
| 		# TODO: Umožnit stažení / zobrazení řešení | 		# TODO: Umožnit stažení / zobrazení řešení | ||||||
|  | @ -412,6 +411,7 @@ class NahrajReseniView(LoginRequiredMixin, CreateView): | ||||||
| 			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.cas_doruceni = timezone.now() | 			self.object.cas_doruceni = timezone.now() | ||||||
|  | 			self.object.deadline = m.Deadline.objects.filter(deadline__gte=self.object.cas_doruceni).first() | ||||||
| 			self.object.forma = m.Reseni.FORMA_UPLOAD | 			self.object.forma = m.Reseni.FORMA_UPLOAD | ||||||
| 			self.object.save() | 			self.object.save() | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -40,7 +40,7 @@ class OrgoRozcestnikView(TemplateView): | ||||||
| 		# přes treenody (a dát si přitom pozor na MezicisloNode) | 		# přes treenody (a dát si přitom pozor na MezicisloNode) | ||||||
| 
 | 
 | ||||||
| 		neobodovana_reseni = s.Hodnoceni.objects.filter(body__isnull=True) | 		neobodovana_reseni = s.Hodnoceni.objects.filter(body__isnull=True) | ||||||
| 		reseni_mimo_cislo = s.Hodnoceni.objects.filter(cislo_body__isnull=True) | 		reseni_mimo_cislo = s.Hodnoceni.objects.filter(deadline_body__isnull=True) | ||||||
| 		context['pocet_neobodovanych_reseni'] = neobodovana_reseni.count() | 		context['pocet_neobodovanych_reseni'] = neobodovana_reseni.count() | ||||||
| 		context['pocet_reseni_mimo_cislo'] = reseni_mimo_cislo.count() | 		context['pocet_reseni_mimo_cislo'] = reseni_mimo_cislo.count() | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -7,14 +7,21 @@ from polymorphic.admin import PolymorphicParentModelAdmin, PolymorphicChildModel | ||||||
| from solo.admin import SingletonModelAdmin | from solo.admin import SingletonModelAdmin | ||||||
| from django.utils.safestring import mark_safe | from django.utils.safestring import mark_safe | ||||||
| 
 | 
 | ||||||
| from seminar.utils import hlavni_problem |  | ||||||
| 
 |  | ||||||
| # Todo: reversion | # Todo: reversion | ||||||
| 
 | 
 | ||||||
| import seminar.models as m | import seminar.models as m | ||||||
| 
 | 
 | ||||||
| admin.site.register(m.Rocnik) | admin.site.register(m.Rocnik) | ||||||
| 
 | 
 | ||||||
|  | admin.site.register(m.Deadline) | ||||||
|  | admin.site.register(m.ZmrazenaVysledkovka) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class DeadlineAdminInline(admin.TabularInline): | ||||||
|  |     model = m.Deadline | ||||||
|  |     extra = 0 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
| class CisloForm(ModelForm): | class CisloForm(ModelForm): | ||||||
| 	class Meta: | 	class Meta: | ||||||
| 		model = m.Cislo | 		model = m.Cislo | ||||||
|  | @ -65,6 +72,7 @@ class CisloForm(ModelForm): | ||||||
| class CisloAdmin(admin.ModelAdmin): | class CisloAdmin(admin.ModelAdmin): | ||||||
| 	form = CisloForm | 	form = CisloForm | ||||||
| 	actions = ['force_publish'] | 	actions = ['force_publish'] | ||||||
|  | 	inlines = (DeadlineAdminInline,) | ||||||
| 
 | 
 | ||||||
| 	def force_publish(self,request,queryset): | 	def force_publish(self,request,queryset): | ||||||
| 		for cislo in queryset: | 		for cislo in queryset: | ||||||
|  | @ -90,7 +98,7 @@ class CisloAdmin(admin.ModelAdmin): | ||||||
| 					ch.stav = m.Problem.STAV_ZADANY | 					ch.stav = m.Problem.STAV_ZADANY | ||||||
| 					ch.save() | 					ch.save() | ||||||
| 
 | 
 | ||||||
| 					hp = hlavni_problem(ch) | 					hp = ch.hlavni_problem | ||||||
| 					if hp.stav not in (m.Problem.STAV_ZADANY, m.Problem.STAV_VYRESENY): | 					if hp.stav not in (m.Problem.STAV_ZADANY, m.Problem.STAV_VYRESENY): | ||||||
| 						hp.stav = m.Problem.STAV_ZADANY | 						hp.stav = m.Problem.STAV_ZADANY | ||||||
| 						hp.save() | 						hp.save() | ||||||
|  |  | ||||||
|  | @ -0,0 +1,11 @@ | ||||||
|  | from django.core.management.base import BaseCommand | ||||||
|  | import seminar.models as m | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class Command(BaseCommand): | ||||||
|  | 	help = "Všem deadlinům se zveřejněnou výsledkovkou vygeneruj výsledkovku" | ||||||
|  | 
 | ||||||
|  | 	def handle(self, *args, **options): | ||||||
|  | 		for deadline in m.Deadline.objects.filter(verejna_vysledkovka=True): | ||||||
|  | 			deadline.vygeneruj_vysledkovku() | ||||||
|  | 		 | ||||||
							
								
								
									
										81
									
								
								seminar/migrations/0103_deadline.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										81
									
								
								seminar/migrations/0103_deadline.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,81 @@ | ||||||
|  | # Generated by Django 3.2.15 on 2022-10-01 08:44 | ||||||
|  | 
 | ||||||
|  | import datetime | ||||||
|  | from django.db import migrations, models | ||||||
|  | import django.db.models.deletion | ||||||
|  | from django.utils import timezone | ||||||
|  | 
 | ||||||
|  | import seminar.models as m | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def vytvor_deadliny(apps, schema_editor): | ||||||
|  |     Cislo = apps.get_model('seminar', 'Cislo') | ||||||
|  |     Deadline = apps.get_model('seminar', 'Deadline') | ||||||
|  | 
 | ||||||
|  |     for cislo in Cislo.objects.all(): | ||||||
|  |         if cislo.rocnik.rocnik < 26: | ||||||
|  |             Deadline.objects.create( | ||||||
|  |                 cislo=cislo, | ||||||
|  |                 typ=m.Deadline.TYP_CISLA, | ||||||
|  |                 deadline=timezone.make_aware(datetime.datetime.combine(datetime.date(1994 + cislo.rocnik.rocnik, 6, int(cislo.poradi[0])), datetime.time.min)), | ||||||
|  |                 verejna_vysledkovka=cislo.verejna_vysledkovka, | ||||||
|  |             ) | ||||||
|  |             continue | ||||||
|  | 
 | ||||||
|  |         def vytvor_deadline(date: datetime.date, typ): | ||||||
|  |             Deadline.objects.create( | ||||||
|  |                 cislo=cislo, | ||||||
|  |                 typ=typ, | ||||||
|  |                 deadline=timezone.make_aware(datetime.datetime.combine(date, datetime.time.min)) + datetime.timedelta(days=1), | ||||||
|  |                 verejna_vysledkovka=True | ||||||
|  |             ) | ||||||
|  | 
 | ||||||
|  |         if cislo.datum_deadline_soustredeni and cislo.datum_deadline_soustredeni == cislo.datum_preddeadline: | ||||||
|  |             vytvor_deadline( | ||||||
|  |                 date=cislo.datum_deadline_soustredeni, | ||||||
|  |                 typ=m.Deadline.TYP_PRVNI_A_SOUS | ||||||
|  |             ) | ||||||
|  |         else: | ||||||
|  |             if cislo.datum_deadline_soustredeni: | ||||||
|  |                 vytvor_deadline( | ||||||
|  |                     date=cislo.datum_deadline_soustredeni, | ||||||
|  |                     typ=m.Deadline.TYP_SOUS | ||||||
|  |                 ) | ||||||
|  |             if cislo.datum_preddeadline: | ||||||
|  |                 vytvor_deadline( | ||||||
|  |                     date=cislo.datum_preddeadline, | ||||||
|  |                     typ=m.Deadline.TYP_PRVNI | ||||||
|  |                 ) | ||||||
|  | 
 | ||||||
|  |         if cislo.datum_deadline: | ||||||
|  |             vytvor_deadline( | ||||||
|  |                 date=cislo.datum_deadline, | ||||||
|  |                 typ=m.Deadline.TYP_CISLA | ||||||
|  |             ) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class Migration(migrations.Migration): | ||||||
|  | 
 | ||||||
|  |     dependencies = [ | ||||||
|  |         ('seminar', '0102_osoba_jak_se_dozvedeli'), | ||||||
|  |     ] | ||||||
|  | 
 | ||||||
|  |     operations = [ | ||||||
|  |         migrations.CreateModel( | ||||||
|  |             name='Deadline', | ||||||
|  |             fields=[ | ||||||
|  |                 ('id', models.AutoField(primary_key=True, serialize=False)), | ||||||
|  |                 ('deadline', models.DateTimeField(default=timezone.make_aware(datetime.datetime.combine(timezone.now(), datetime.time.max)))), | ||||||
|  |                 ('typ', models.CharField(choices=[('cisla', 'Deadline celého čísla'), ('prvni', 'První deadline'), ('prvniasous', 'Sousový a první deadline'), ('sous', 'Sousový deadline')], max_length=32, verbose_name='typ deadlinu')), | ||||||
|  |                 ('verejna_vysledkovka', models.BooleanField(db_column='verejna_vysledkovka', default=False, verbose_name='veřejná výsledkovka')), | ||||||
|  |                 ('cislo', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='deadline_v_cisle', to='seminar.cislo', verbose_name='deadline v čísle')), | ||||||
|  |             ], | ||||||
|  |             options={ | ||||||
|  |                 'verbose_name': 'Deadline', | ||||||
|  |                 'verbose_name_plural': 'Deadliny', | ||||||
|  |                 'db_table': 'seminar_deadliny', | ||||||
|  |                 'ordering': ['deadline'], | ||||||
|  |             }, | ||||||
|  |         ), | ||||||
|  |         migrations.RunPython(vytvor_deadliny, migrations.RunPython.noop), | ||||||
|  |     ] | ||||||
							
								
								
									
										86
									
								
								seminar/migrations/0104_hodnoceni_deadline_body.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										86
									
								
								seminar/migrations/0104_hodnoceni_deadline_body.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,86 @@ | ||||||
|  | # Generated by Django 3.2.15 on 2022-10-01 09:28 | ||||||
|  | 
 | ||||||
|  | from django.db import migrations, models | ||||||
|  | import django.db.models.deletion | ||||||
|  | from logging import getLogger | ||||||
|  | 
 | ||||||
|  | log = getLogger(__name__) | ||||||
|  | 
 | ||||||
|  | def prirad_deadliny(apps, schema_editor): | ||||||
|  |     Hodnoceni = apps.get_model('seminar', 'Hodnoceni') | ||||||
|  |     Deadline = apps.get_model('seminar', 'Deadline') | ||||||
|  | 
 | ||||||
|  |     for h in Hodnoceni.objects.all(): | ||||||
|  |         if h.cislo_body is not None and h.cislo_body.rocnik.rocnik < 26: | ||||||
|  |             # Deadline připravený v minulé migraci | ||||||
|  |             h.deadline_body = h.cislo_body.deadline_v_cisle.get() | ||||||
|  |             h.save() | ||||||
|  |             continue | ||||||
|  | 
 | ||||||
|  |         p = h.problem | ||||||
|  | 
 | ||||||
|  |         if p.polymorphic_ctype.model == 'tema': | ||||||
|  |             t = p.tema | ||||||
|  |             d = Deadline.objects.filter(cislo__rocnik=t.rocnik, deadline__gte=h.reseni.cas_doruceni).first() | ||||||
|  | 
 | ||||||
|  |             if d is None: | ||||||
|  |                 d = Deadline.objects.filter(cislo__rocnik=t.rocnik).last() | ||||||
|  | 
 | ||||||
|  |             if d is not None: | ||||||
|  |                 h.deadline_body = d | ||||||
|  |                 h.save() | ||||||
|  |                 continue | ||||||
|  | 
 | ||||||
|  |         cislo = None | ||||||
|  | 
 | ||||||
|  |         if p.polymorphic_ctype.model == 'uloha': | ||||||
|  |             u = p.uloha | ||||||
|  |             cislo = u.cislo_zadani | ||||||
|  | 
 | ||||||
|  |         if p.polymorphic_ctype.model == 'clanek': | ||||||
|  |             c = p.clanek | ||||||
|  |             if c.cislo is not None: | ||||||
|  |                 cislo = c.cislo | ||||||
|  | 
 | ||||||
|  |         if cislo is None: | ||||||
|  |             log.warning(f"Číslo hodnocení {h.id} se nepodařilo určit exaktním způsobem. Dané hodnocení házím do jeho cislo_body: {h.cislo_body}.") | ||||||
|  |             cislo = h.cislo_body | ||||||
|  | 
 | ||||||
|  |         if cislo is not None: | ||||||
|  |             d = Deadline.objects.filter(cislo=cislo, deadline__gte=h.reseni.cas_doruceni).first() | ||||||
|  |             if d is None: | ||||||
|  |                 d = Deadline.objects.filter(cislo=cislo).last() | ||||||
|  |             if d is not None: | ||||||
|  |                 h.deadline_body = d | ||||||
|  |                 h.save() | ||||||
|  |                 continue | ||||||
|  | 
 | ||||||
|  |         d = Deadline.objects.filter(deadline__gte=h.reseni.cas_doruceni).first() | ||||||
|  |         h.deadline_body = d | ||||||
|  |         h.save() | ||||||
|  | 
 | ||||||
|  |         log.warning(f"Deadline hodnocení {h.id} se nepodařil určit exaktnějším způsobem. Zkouším další. Přiřazen {h.deadline_body}. Původní cislo_body: {h.cislo_body}.") | ||||||
|  | 
 | ||||||
|  |         # Zběžná kontrola. Předpokládá, že M&M má méně než 10 čísel v ročníku | ||||||
|  |         # a že první znak pořadí je int určující dané pořadí (schroustání 7-8). | ||||||
|  |         if h.cislo_body and h.deadline_body and ( | ||||||
|  |                     int(h.deadline_body.cislo.poradi[0]) + 2 < int(h.cislo_body.poradi[0]) | ||||||
|  |                     or int(h.deadline_body.cislo.poradi[0]) > int(h.cislo_body.poradi[0]) | ||||||
|  |                 ): | ||||||
|  |             log.error(f"Hodnocení {h.id} se špatně změnilo číslo z {h.cislo_body} na {h.deadline_body.cislo}") | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class Migration(migrations.Migration): | ||||||
|  | 
 | ||||||
|  |     dependencies = [ | ||||||
|  |         ('seminar', '0103_deadline'), | ||||||
|  |     ] | ||||||
|  | 
 | ||||||
|  |     operations = [ | ||||||
|  |         migrations.AddField( | ||||||
|  |             model_name='hodnoceni', | ||||||
|  |             name='deadline_body', | ||||||
|  |             field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='hodnoceni', to='seminar.deadline', verbose_name='deadline pro body'), | ||||||
|  |         ), | ||||||
|  |         migrations.RunPython(prirad_deadliny, migrations.RunPython.noop), | ||||||
|  |     ] | ||||||
							
								
								
									
										50
									
								
								seminar/migrations/0105_odstraneni_deadlinu_cisla.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										50
									
								
								seminar/migrations/0105_odstraneni_deadlinu_cisla.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,50 @@ | ||||||
|  | # Generated by Django 3.2.15 on 2022-10-09 10:14 | ||||||
|  | 
 | ||||||
|  | from django.db import migrations | ||||||
|  | from seminar.models import Deadline | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def vrat_deadliny(apps, schema_editor): | ||||||
|  |     Cislo = apps.get_model('seminar', 'Cislo') | ||||||
|  | 
 | ||||||
|  |     for cislo in Cislo.objects.all(): | ||||||
|  |         prvni_deadline = cislo.deadline_v_cisle.filter(typ=Deadline.TYP_PRVNI).last() | ||||||
|  |         sous_deadline = cislo.deadline_v_cisle.filter(typ=Deadline.TYP_SOUS).last() | ||||||
|  |         prvni_a_sous_deadline = cislo.deadline_v_cisle.filter(typ=Deadline.TYP_PRVNI_A_SOUS).last() | ||||||
|  |         posledni_deadline = cislo.deadline_v_cisle.filter(typ=Deadline.TYP_CISLA).last() | ||||||
|  | 
 | ||||||
|  |         if prvni_a_sous_deadline is not None: | ||||||
|  |             cislo.datum_deadline_soustredeni = prvni_a_sous_deadline.deadline.date() | ||||||
|  |             cislo.datum_preddeadline = prvni_a_sous_deadline.deadline.date() | ||||||
|  |         else: | ||||||
|  |             if sous_deadline is not None: | ||||||
|  |                 cislo.datum_deadline_soustredeni = sous_deadline.deadline.date() | ||||||
|  |             if prvni_deadline is not None: | ||||||
|  |                 cislo.datum_preddeadline = prvni_deadline.deadline.date() | ||||||
|  | 
 | ||||||
|  |         if posledni_deadline: | ||||||
|  |             cislo.datum_deadline = posledni_deadline.deadline.date() | ||||||
|  | 
 | ||||||
|  |         cislo.save() | ||||||
|  | 
 | ||||||
|  | class Migration(migrations.Migration): | ||||||
|  | 
 | ||||||
|  |     dependencies = [ | ||||||
|  |         ('seminar', '0104_hodnoceni_deadline_body'), | ||||||
|  |     ] | ||||||
|  | 
 | ||||||
|  |     operations = [ | ||||||
|  |         migrations.RunPython(migrations.RunPython.noop, vrat_deadliny), | ||||||
|  |         migrations.RemoveField( | ||||||
|  |             model_name='cislo', | ||||||
|  |             name='datum_deadline', | ||||||
|  |         ), | ||||||
|  |         migrations.RemoveField( | ||||||
|  |             model_name='cislo', | ||||||
|  |             name='datum_deadline_soustredeni', | ||||||
|  |         ), | ||||||
|  |         migrations.RemoveField( | ||||||
|  |             model_name='cislo', | ||||||
|  |             name='datum_preddeadline', | ||||||
|  |         ), | ||||||
|  |     ] | ||||||
							
								
								
									
										27
									
								
								seminar/migrations/0106_remove_cislo_verejna_vysledkovka.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								seminar/migrations/0106_remove_cislo_verejna_vysledkovka.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,27 @@ | ||||||
|  | # Generated by Django 3.2.15 on 2022-10-09 11:04 | ||||||
|  | 
 | ||||||
|  | from django.db import migrations | ||||||
|  | from seminar.models import Deadline | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def vrat_verejnost(apps, schema_editor): | ||||||
|  |     Cislo = apps.get_model('seminar', 'Cislo') | ||||||
|  | 
 | ||||||
|  |     for cislo in Cislo.objects.all(): | ||||||
|  |         cislo.verejna_vysledkovka = cislo.deadline_v_cisle.filter(typ=Deadline.TYP_CISLA, verejna_vysledkovka=True).exists() | ||||||
|  |         cislo.save() | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class Migration(migrations.Migration): | ||||||
|  | 
 | ||||||
|  |     dependencies = [ | ||||||
|  |         ('seminar', '0105_odstraneni_deadlinu_cisla'), | ||||||
|  |     ] | ||||||
|  | 
 | ||||||
|  |     operations = [ | ||||||
|  |         migrations.RunPython(migrations.RunPython.noop, vrat_verejnost), | ||||||
|  |         migrations.RemoveField( | ||||||
|  |             model_name='cislo', | ||||||
|  |             name='verejna_vysledkovka', | ||||||
|  |         ), | ||||||
|  |     ] | ||||||
							
								
								
									
										26
									
								
								seminar/migrations/0107_zmrazenavysledkovka.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								seminar/migrations/0107_zmrazenavysledkovka.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,26 @@ | ||||||
|  | # Generated by Django 3.2.15 on 2022-10-10 07:23 | ||||||
|  | 
 | ||||||
|  | from django.db import migrations, models | ||||||
|  | import django.db.models.deletion | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class Migration(migrations.Migration): | ||||||
|  | 
 | ||||||
|  |     dependencies = [ | ||||||
|  |         ('seminar', '0106_remove_cislo_verejna_vysledkovka'), | ||||||
|  |     ] | ||||||
|  | 
 | ||||||
|  |     operations = [ | ||||||
|  |         migrations.CreateModel( | ||||||
|  |             name='ZmrazenaVysledkovka', | ||||||
|  |             fields=[ | ||||||
|  |                 ('deadline', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, primary_key=True, related_name='vysledkovka_v_deadlinu', serialize=False, to='seminar.deadline')), | ||||||
|  |                 ('html', models.TextField()), | ||||||
|  |             ], | ||||||
|  |             options={ | ||||||
|  |                 'verbose_name': 'Zmražená výsledkovka', | ||||||
|  |                 'verbose_name_plural': 'Zmražené výsledkovky', | ||||||
|  |                 'db_table': 'seminar_vysledkovky', | ||||||
|  |             }, | ||||||
|  |         ), | ||||||
|  |     ] | ||||||
|  | @ -78,6 +78,9 @@ class Reseni(bm.SeminarModelBase): | ||||||
|         return "{}({}): {}({})".format(self.resitele.first(),len(self.resitele.all()), self.problem.first() ,len(self.problem.all())) |         return "{}({}): {}({})".format(self.resitele.first(),len(self.resitele.all()), self.problem.first() ,len(self.problem.all())) | ||||||
|     # NOTE: Potenciální DB HOG (bez select_related) |     # NOTE: Potenciální DB HOG (bez select_related) | ||||||
| 
 | 
 | ||||||
|  |     def deadline_reseni(self): | ||||||
|  |         return am.Deadline.objects.filter(deadline__gte=self.cas_doruceni).order_by("deadline").first() | ||||||
|  | 
 | ||||||
| ## Pravdepodobne uz nebude potreba: | ## Pravdepodobne uz nebude potreba: | ||||||
| #	def save(self, *args, **kwargs): | #	def save(self, *args, **kwargs): | ||||||
| #		if ((self.cislo_body is None) and (self.problem.cislo_reseni) and | #		if ((self.cislo_body is None) and (self.problem.cislo_reseni) and | ||||||
|  | @ -101,6 +104,10 @@ class Hodnoceni(bm.SeminarModelBase): | ||||||
|     cislo_body = models.ForeignKey(am.Cislo, verbose_name='číslo pro body', |     cislo_body = models.ForeignKey(am.Cislo, verbose_name='číslo pro body', | ||||||
|                                    related_name='hodnoceni', blank=True, null=True, on_delete=models.PROTECT) |                                    related_name='hodnoceni', blank=True, null=True, on_delete=models.PROTECT) | ||||||
| 
 | 
 | ||||||
|  |     # V ročníku < 26 nastaveno na deadline vygenerovaný pro původní cislo_body | ||||||
|  |     deadline_body = models.ForeignKey(am.Deadline, verbose_name='deadline pro body', | ||||||
|  |                                    related_name='hodnoceni', blank=True, null=True, on_delete=models.PROTECT) | ||||||
|  | 
 | ||||||
|     reseni = models.ForeignKey(Reseni, verbose_name='řešení', on_delete=models.CASCADE) |     reseni = models.ForeignKey(Reseni, verbose_name='řešení', on_delete=models.CASCADE) | ||||||
| 
 | 
 | ||||||
|     problem = models.ForeignKey(am.Problem, verbose_name='problém', |     problem = models.ForeignKey(am.Problem, verbose_name='problém', | ||||||
|  |  | ||||||
|  | @ -323,7 +323,7 @@ class Resitel(SeminarModelBase): | ||||||
| 		#  - proto se započítávají dvojnásobně a byly posunuté hranice titulů | 		#  - proto se započítávají dvojnásobně a byly posunuté hranice titulů | ||||||
| 		#  - staré tituly se ale nemají odebrat, pokud řešitel v t.č. minulém (26.) ročníku měl titul, má ho mít pořád. | 		#  - staré tituly se ale nemají odebrat, pokud řešitel v t.č. minulém (26.) ročníku měl titul, má ho mít pořád. | ||||||
| 		from .odevzdavatko import Hodnoceni | 		from .odevzdavatko import Hodnoceni | ||||||
| 		hodnoceni_do_25_rocniku = Hodnoceni.objects.filter(cislo_body__rocnik__rocnik__lte=25,reseni__in=self.reseni_set.all()) | 		hodnoceni_do_25_rocniku = Hodnoceni.objects.filter(deadline_body__cislo__rocnik__rocnik__lte=25,reseni__in=self.reseni_set.all()) | ||||||
| 		novejsi_hodnoceni = Hodnoceni.objects.filter(reseni__in=self.reseni_set.all()).difference(hodnoceni_do_25_rocniku) | 		novejsi_hodnoceni = Hodnoceni.objects.filter(reseni__in=self.reseni_set.all()).difference(hodnoceni_do_25_rocniku) | ||||||
| 
 | 
 | ||||||
| 		def body_z_hodnoceni(hh : list): | 		def body_z_hodnoceni(hh : list): | ||||||
|  | @ -361,7 +361,7 @@ class Resitel(SeminarModelBase): | ||||||
| 				return Titul.akad | 				return Titul.akad | ||||||
| 
 | 
 | ||||||
| 		from .odevzdavatko import Hodnoceni | 		from .odevzdavatko import Hodnoceni | ||||||
| 		hodnoceni_do_26_rocniku = Hodnoceni.objects.filter(cislo_body__rocnik__rocnik__lte=26,reseni__in=self.reseni_set.all()) | 		hodnoceni_do_26_rocniku = Hodnoceni.objects.filter(deadline_body__cislo__rocnik__rocnik__lte=26,reseni__in=self.reseni_set.all()) | ||||||
| 		novejsi_body = body_z_hodnoceni( | 		novejsi_body = body_z_hodnoceni( | ||||||
| 			Hodnoceni.objects.filter(reseni__in=self.reseni_set.all()) | 			Hodnoceni.objects.filter(reseni__in=self.reseni_set.all()) | ||||||
| 			.difference(hodnoceni_do_26_rocniku) | 			.difference(hodnoceni_do_26_rocniku) | ||||||
|  |  | ||||||
|  | @ -1,4 +1,5 @@ | ||||||
| # -*- coding: utf-8 -*- | # -*- coding: utf-8 -*- | ||||||
|  | import datetime | ||||||
| import os | import os | ||||||
| import subprocess | import subprocess | ||||||
| import pathlib | import pathlib | ||||||
|  | @ -7,6 +8,8 @@ import logging | ||||||
| 
 | 
 | ||||||
| from django.contrib.sites.shortcuts import get_current_site | from django.contrib.sites.shortcuts import get_current_site | ||||||
| from django.db import models | from django.db import models | ||||||
|  | from django.db.models import Q | ||||||
|  | from django.template.loader import render_to_string | ||||||
| from django.utils import timezone | from django.utils import timezone | ||||||
| from django.conf import settings | from django.conf import settings | ||||||
| from django.urls import reverse | from django.urls import reverse | ||||||
|  | @ -22,7 +25,6 @@ from taggit.managers import TaggableManager | ||||||
| from reversion import revisions as reversion | from reversion import revisions as reversion | ||||||
| 
 | 
 | ||||||
| from seminar.utils import roman | from seminar.utils import roman | ||||||
| from seminar.utils import hlavni_problem |  | ||||||
| from treenode import treelib | from treenode import treelib | ||||||
| 
 | 
 | ||||||
| from unidecode import unidecode # Používám pro získání ID odkazu (ještě je to někde po někom zakomentované) | from unidecode import unidecode # Používám pro získání ID odkazu (ještě je to někde po někom zakomentované) | ||||||
|  | @ -96,7 +98,7 @@ class Rocnik(SeminarModelBase): | ||||||
| 		return vc[-1] if vc else None | 		return vc[-1] if vc else None | ||||||
| 
 | 
 | ||||||
| 	def verejne_vysledkovky_cisla(self): | 	def verejne_vysledkovky_cisla(self): | ||||||
| 		vc = list(self.cisla.filter(verejna_vysledkovka=True)) | 		vc = list(self.cisla.filter(deadline_v_cisle__verejna_vysledkovka=True).distinct()) | ||||||
| 		vc.sort(key=lambda c: c.poradi) | 		vc.sort(key=lambda c: c.poradi) | ||||||
| 		return vc | 		return vc | ||||||
| 
 | 
 | ||||||
|  | @ -156,27 +158,10 @@ class Cislo(SeminarModelBase): | ||||||
| 
 | 
 | ||||||
| 	datum_vydani = models.DateField('datum vydání', blank=True, null=True, | 	datum_vydani = models.DateField('datum vydání', blank=True, null=True, | ||||||
| 		help_text='Datum vydání finální verze') | 		help_text='Datum vydání finální verze') | ||||||
|   |  | ||||||
| 	datum_deadline_soustredeni = models.DateField( |  | ||||||
| 		'datum deadline soustředění', |  | ||||||
| 		blank=True, null=True, |  | ||||||
| 		help_text='Datum pro příjem řešení pro účast na soustředění') |  | ||||||
|   |  | ||||||
| 	datum_preddeadline = models.DateField('datum předdeadline', blank=True, null=True, |  | ||||||
| 		help_text='Datum pro příjem řešení, která se otisknou v dalším čísle') |  | ||||||
| 
 |  | ||||||
| 	datum_deadline = models.DateField('datum deadline', blank=True, null=True, |  | ||||||
| 		help_text='Datum pro příjem řešení úloh zadaných v tomto čísle') |  | ||||||
| 
 | 
 | ||||||
| 	verejne_db = models.BooleanField('číslo zveřejněno', | 	verejne_db = models.BooleanField('číslo zveřejněno', | ||||||
| 		db_column='verejne', default=False) | 		db_column='verejne', default=False) | ||||||
| 
 | 
 | ||||||
| 	verejna_vysledkovka = models.BooleanField( |  | ||||||
| 		'zveřejněna výsledkovka', |  | ||||||
| 		default=False, |  | ||||||
| 		help_text='Je-li false u veřejného čísla, ' |  | ||||||
| 				'není výsledkovka zatím veřejná.') |  | ||||||
| 
 |  | ||||||
| 	poznamka = models.TextField('neveřejná poznámka', blank=True, | 	poznamka = models.TextField('neveřejná poznámka', blank=True, | ||||||
| 		help_text='Neveřejná poznámka k číslu (plain text)') | 		help_text='Neveřejná poznámka k číslu (plain text)') | ||||||
| 
 | 
 | ||||||
|  | @ -218,7 +203,7 @@ class Cislo(SeminarModelBase): | ||||||
| 
 | 
 | ||||||
| 	def relativni_v_rocniku(self, rel_index): | 	def relativni_v_rocniku(self, rel_index): | ||||||
| 		"Číslo o `index` dále v ročníku. None pokud neexistuje." | 		"Číslo o `index` dále v ročníku. None pokud neexistuje." | ||||||
| 		cs = self.rocnik.cisla.order_by('cislo').all() | 		cs = self.rocnik.cisla.order_by('poradi').all() | ||||||
| 		i = list(cs).index(self) + rel_index | 		i = list(cs).index(self) + rel_index | ||||||
| 		if (i < 0) or (i >= len(cs)): | 		if (i < 0) or (i >= len(cs)): | ||||||
| 			return None | 			return None | ||||||
|  | @ -316,16 +301,97 @@ class Cislo(SeminarModelBase): | ||||||
| 			from seminar.models.treenode import CisloNode | 			from seminar.models.treenode import CisloNode | ||||||
| 			CisloNode.objects.create(cislo=self) | 			CisloNode.objects.create(cislo=self) | ||||||
| 
 | 
 | ||||||
| 	def clean(self): | 	def zlomovy_deadline_pro_papirove_cislo(self): | ||||||
| 		# Finální deadline má být až poslední a je povinný, pokud nějaký deadline existuje. | 		prvni_deadline = Deadline.objects.filter(Q(typ=Deadline.TYP_PRVNI) | Q(typ=Deadline.TYP_PRVNI_A_SOUS), cislo=self).first() | ||||||
| 		# Existence: | 		if prvni_deadline is None: | ||||||
| 		if self.datum_deadline is None and (self.datum_preddeadline is not None or self.datum_deadline_soustredeni is not None): | 			posledni_deadline = self.posledni_deadline | ||||||
| 			raise ValidationError({'datum_deadline': "Číslo musí mít finální deadline, pokud má nějaké deadliny"}) | 			if posledni_deadline is None: | ||||||
| 		if self.datum_deadline is not None: | 				# TODO promyslet, co se má stát tady | ||||||
| 			if self.datum_preddeadline is not None and self.datum_preddeadline > self.datum_deadline: | 				return Deadline.objects.filter(Q(cislo__poradi__lt=self.poradi, cislo__rocnik=self.rocnik) | Q(cislo__rocnik__rocnik__lt=self.rocnik.rocnik)).order_by("deadline").last() | ||||||
| 				raise ValidationError({'datum_preddeadline': "Předdeadline musí předcházet finálnímu deadlinu"}) | 			return posledni_deadline | ||||||
| 			if self.datum_deadline_soustredeni is not None and self.datum_deadline_soustredeni > self.datum_deadline: | 		return prvni_deadline | ||||||
| 				raise ValidationError({'datum_deadline_soustredeni': "Soustřeďkový deadline musí předcházet finálnímu deadlinu"}) | 
 | ||||||
|  | 	@property | ||||||
|  | 	def posledni_deadline(self): | ||||||
|  | 		return self.deadline_v_cisle.all().order_by("deadline").last() | ||||||
|  | 
 | ||||||
|  | class Deadline(SeminarModelBase): | ||||||
|  | 	class Meta: | ||||||
|  | 		db_table = 'seminar_deadliny' | ||||||
|  | 		verbose_name = 'Deadline' | ||||||
|  | 		verbose_name_plural = 'Deadliny' | ||||||
|  | 		ordering = ['deadline'] | ||||||
|  | 
 | ||||||
|  | 	def __init__(self, *args, **kwargs): | ||||||
|  | 		super().__init__(*args, **kwargs) | ||||||
|  | 		self.__original_verejna_vysledkovka = self.verejna_vysledkovka | ||||||
|  | 
 | ||||||
|  | 	id = models.AutoField(primary_key=True) | ||||||
|  | 
 | ||||||
|  | 	# V ročníku < 26 nastaveno na datetime.datetime.combine(datetime.date(1994 + cislo.rocnik.rocnik, 6, int(cislo.poradi[0])), datetime.time.min) | ||||||
|  | 	deadline = models.DateTimeField(blank=False, default=timezone.make_aware(datetime.datetime.combine(timezone.now(), datetime.time.max))) | ||||||
|  | 
 | ||||||
|  | 	cislo = models.ForeignKey(Cislo, verbose_name='deadline v čísle', | ||||||
|  | 		related_name='deadline_v_cisle', blank=False, | ||||||
|  | 		on_delete=models.CASCADE) | ||||||
|  | 
 | ||||||
|  | 	TYP_CISLA = 'cisla' | ||||||
|  | 	TYP_PRVNI_A_SOUS = 'prvniasous' | ||||||
|  | 	TYP_PRVNI = 'prvni' | ||||||
|  | 	TYP_SOUS = 'sous' | ||||||
|  | 	TYP_CHOICES = [ | ||||||
|  | 		(TYP_CISLA, 'Deadline celého čísla'), | ||||||
|  | 		(TYP_PRVNI, 'První deadline'), | ||||||
|  | 		(TYP_PRVNI_A_SOUS, 'Sousový a první deadline'), | ||||||
|  | 		(TYP_SOUS, 'Sousový deadline'), | ||||||
|  | 	] | ||||||
|  | 	CHOICES_MAP = dict(TYP_CHOICES) | ||||||
|  | 	typ = models.CharField('typ deadlinu', max_length=32, | ||||||
|  | 							choices=TYP_CHOICES, blank=False) | ||||||
|  | 
 | ||||||
|  | 	verejna_vysledkovka = models.BooleanField('veřejná výsledkovka', | ||||||
|  | 											  db_column='verejna_vysledkovka', | ||||||
|  | 											  default=False) | ||||||
|  | 
 | ||||||
|  | 	def __str__(self): | ||||||
|  | 		return self.CHOICES_MAP[self.typ] + " " + str(self.cislo) | ||||||
|  | 
 | ||||||
|  | 	def save(self, *args, **kwargs): | ||||||
|  | 		super().save(*args, **kwargs) | ||||||
|  | 		if self.verejna_vysledkovka and not self.__original_verejna_vysledkovka: | ||||||
|  | 			self.vygeneruj_vysledkovku() | ||||||
|  | 		if not self.verejna_vysledkovka and hasattr(self, "vysledkovka_v_deadlinu"): | ||||||
|  | 			self.vysledkovka_v_deadlinu.delete() | ||||||
|  | 
 | ||||||
|  | 	def vygeneruj_vysledkovku(self): | ||||||
|  | 		from vysledkovky.utils import VysledkovkaCisla | ||||||
|  | 		if hasattr(self, "vysledkovka_v_deadlinu"): | ||||||
|  | 			self.vysledkovka_v_deadlinu.delete() | ||||||
|  | 		vysledkovka = VysledkovkaCisla(self.cislo, jen_verejne=True, do_deadlinu=self) | ||||||
|  | 		if len(vysledkovka.radky_vysledkovky) != 0: | ||||||
|  | 			ZmrazenaVysledkovka.objects.create( | ||||||
|  | 				deadline=self, | ||||||
|  | 				html=render_to_string( | ||||||
|  | 					"vysledkovky/vysledkovka_cisla.html", | ||||||
|  | 					context={"vysledkovka": vysledkovka, "oznaceni_vysledkovky": self.id} | ||||||
|  | 				) | ||||||
|  | 			) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class ZmrazenaVysledkovka(SeminarModelBase): | ||||||
|  | 	class Meta: | ||||||
|  | 		db_table = 'seminar_vysledkovky' | ||||||
|  | 		verbose_name = 'Zmražená výsledkovka' | ||||||
|  | 		verbose_name_plural = 'Zmražené výsledkovky' | ||||||
|  | 
 | ||||||
|  | 	deadline = models.OneToOneField( | ||||||
|  | 		Deadline, | ||||||
|  | 		on_delete=models.CASCADE, | ||||||
|  | 		primary_key=True, | ||||||
|  | 		related_name="vysledkovka_v_deadlinu" | ||||||
|  | 	) | ||||||
|  | 
 | ||||||
|  | 	html = models.TextField(null=False, blank=False) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| @reversion.register(ignore_duplicates=True) | @reversion.register(ignore_duplicates=True) | ||||||
|  | @ -437,9 +503,13 @@ class Problem(SeminarModelBase,PolymorphicModel): | ||||||
| 	def admin_url(self): | 	def admin_url(self): | ||||||
| 			return reverse('admin:seminar_problem_change', args=(self.id, )) | 			return reverse('admin:seminar_problem_change', args=(self.id, )) | ||||||
| 
 | 
 | ||||||
|  | 	@cached_property | ||||||
| 	def hlavni_problem(self): | 	def hlavni_problem(self): | ||||||
| 		""" Pro daný problém vrátí jeho nejvyšší nadproblém.""" | 		""" Pro daný problém vrátí jeho nejvyšší nadproblém.""" | ||||||
| 		return hlavni_problem(self) | 		problem = self | ||||||
|  | 		while not (problem.nadproblem is None): | ||||||
|  | 			problem = problem.nadproblem | ||||||
|  | 		return problem | ||||||
| 
 | 
 | ||||||
| # FIXME - k úloze | # FIXME - k úloze | ||||||
| 	def body_v_zavorce(self): | 	def body_v_zavorce(self): | ||||||
|  |  | ||||||
|  | @ -68,23 +68,27 @@ | ||||||
| {% endcomment %} | {% endcomment %} | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|   {% if cislo.verejna_vysledkovka %} |  {% for deadline, nadpis, vysledkovka in deadliny_s_vysledkovkami %} | ||||||
|   <h2>Výsledkovka</h2> |     {% if deadline.vysledkovka_v_deadlinu or vysledkovka.radky_vysledkovky %} | ||||||
| 
 | 
 | ||||||
|   {% else %} |     {% if not deadline.verejna_vysledkovka %} | ||||||
|     {% if user.je_org %} |  | ||||||
|       <div class='mam-org-only'> |       <div class='mam-org-only'> | ||||||
|       <h2>Výsledkovka (neveřejná)</h2> |       <h2>{{ nadpis }} (neveřejná)</h2> | ||||||
|  |     {% else %} | ||||||
|  |       <h2>{{ nadpis }}</h2> | ||||||
|     {% endif %} |     {% endif %} | ||||||
|   {% endif %} |  | ||||||
| 
 | 
 | ||||||
|   {% if cislo.verejna_vysledkovka or user.je_org %} |         {% if deadline.vysledkovka_v_deadlinu %} | ||||||
|       {% include "vysledkovky/vysledkovka_cisla.html" %} |             {{ deadline.vysledkovka_v_deadlinu.html | safe }} | ||||||
|   {% endif %} |         {% else %} | ||||||
|  |       {% include "vysledkovky/vysledkovka_cisla.html" with oznaceni_vysledkovky=forloop.counter %} | ||||||
|  |         {% endif %} | ||||||
| 
 | 
 | ||||||
|   {% if not cislo.verejna_vysledkovka and user.je_org %} |   {% if not deadline.verejna_vysledkovka %} | ||||||
|       </div> |       </div> | ||||||
|   {% endif %} |   {% endif %} | ||||||
|  |      {% endif %} | ||||||
|  |  {% endfor %} | ||||||
| 
 | 
 | ||||||
| </div>  | </div>  | ||||||
| {% endblock content %}  | {% endblock content %}  | ||||||
|  |  | ||||||
|  | @ -1,9 +1,9 @@ | ||||||
| \setlength{\tabcolsep}{3pt} | \setlength{\tabcolsep}{3pt} | ||||||
| \begin{longtable}{|r|l|c|r|{% for p in problemy %}c@{\hskip.5em}{% endfor %}|r|r|}\hline | \begin{longtable}{|r|l|c|r|{% for p in vysledkovka.temata_a_spol %}c@{\hskip.5em}{% endfor %}{% if vysledkovka.je_nejake_ostatni %}|c@{\hskip.5em}{% endif %}|r|r|}\hline | ||||||
| & & & & \multicolumn{ {{ problemy|length}} }{c|}{\textbf{Témata}} & & \\\textbf{Poř.}& \textbf{Jméno}& \textbf{R.}& \raisebox{0.7mm}{$\sum_{-1}$}& {% for p in problemy %}\textbf{ {{ p.kod_v_rocniku }} }&{% endfor %}\raisebox{0.7mm}{$\sum_0$}&\raisebox{0.7mm}{$\sum_1$}\\\hline | & & & & \multicolumn{ {{ vysledkovka.temata_a_spol|length}} }{c|}{\textbf{Témata}} & & {% if vysledkovka.je_nejake_ostatni %}&{\hskip.5em}{% endif %} \\\textbf{Poř.}& \textbf{Jméno}& \textbf{R.}& \raisebox{0.7mm}{$\sum_{-1}$}& {% for p in vysledkovka.temata_a_spol %}\textbf{ {{ p.kod_v_rocniku }} }&{% endfor %}{% if vysledkovka.je_nejake_ostatni %}\textbf{Ostatní}&{% endif %}\raisebox{0.7mm}{$\sum_0$}&\raisebox{0.7mm}{$\sum_1$}\\\hline | ||||||
| \endhead | \endhead | ||||||
| \hline | \hline | ||||||
| \endfoot  | \endfoot  | ||||||
| {% for rv in radky_vysledkovky %}{{rv.poradi}}&{% if rv.titul %}\titul{ {{ rv.titul}}}{% endif %}{{rv.resitel.osoba.jmeno|slice:":1"}}. {{rv.resitel.osoba.prijmeni}}&{{rv.rocnik_resitele|default:""}}&{{rv.body_celkem_odjakziva}}&{% for b in rv.body_problemy_sezn %}{{b}}&{% endfor %}{{rv.body_cislo}}&{{rv.body_rocnik|default:0}}\\ | {% for rv in vysledkovka.radky_vysledkovky %}{{rv.poradi}}&{% if rv.titul %}\titul{ {{ rv.titul}}}{% endif %}{{rv.resitel.osoba.jmeno|slice:":1"}}. {{rv.resitel.osoba.prijmeni}}&{{rv.rocnik_resitele|default:""}}&{{rv.body_celkem_odjakziva}}&{% for b in rv.body_za_temata_seznam %}{{b}}&{% endfor %}{{rv.body_cislo}}&{{rv.body_rocnik|default:0}}\\ | ||||||
| {% endfor %} | {% endfor %} | ||||||
| \end{longtable} | \end{longtable} | ||||||
|  |  | ||||||
|  | @ -3,13 +3,29 @@ | ||||||
| {% block content %} | {% block content %} | ||||||
|   <h1> |   <h1> | ||||||
|     {% block nadpis1a %} |     {% block nadpis1a %} | ||||||
|       Odměny {{ cislo }} |       Odměny | ||||||
|     {% endblock %} |     {% endblock %} | ||||||
|   </h1> |   </h1> | ||||||
|  | 
 | ||||||
|  |   <h2>Od prvního deadlinu {{ from_cislo }} do prvního deadlinu {{ to_cislo }}</h2> | ||||||
|   <ul> |   <ul> | ||||||
| 	  {% for z in zmeny %} | 	  {% for z in zmeny_prvni_prvni %} | ||||||
| 	  <li> {{z.jmeno}}: {{z.ftitul}} → {{z.ttitul}}</li> | 	  <li> {{z.jmeno}}: {{z.ftitul}} → {{z.ttitul}}</li> | ||||||
| 	  {% endfor %} | 	  {% endfor %} | ||||||
|   </ul>  |   </ul> | ||||||
|  | 
 | ||||||
|  |   <h2>Od {{ from_cislo }} do prvního deadlinu {{ to_cislo }} (pro první číslo)</h2> | ||||||
|  |   <ul> | ||||||
|  | 	  {% for z in zmeny_posledni_prvni %} | ||||||
|  | 	  <li> {{z.jmeno}}: {{z.ftitul}} → {{z.ttitul}}</li> | ||||||
|  | 	  {% endfor %} | ||||||
|  |   </ul> | ||||||
|  | 
 | ||||||
|  |   <h2>Od prvního deadlinu {{ from_cislo }} do {{ to_cislo }} (pro poslední číslo)</h2> | ||||||
|  |   <ul> | ||||||
|  | 	  {% for z in zmeny_prvni_posledni %} | ||||||
|  | 	  <li> {{z.jmeno}}: {{z.ftitul}} → {{z.ttitul}}</li> | ||||||
|  | 	  {% endfor %} | ||||||
|  |   </ul> | ||||||
| 
 | 
 | ||||||
| {% endblock content %} | {% endblock content %} | ||||||
|  |  | ||||||
|  | @ -112,7 +112,7 @@ | ||||||
| {% endif %} | {% endif %} | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|   {% if vysledkovka %} |   {% if vysledkovka.radky_vysledkovky %} | ||||||
|     <h2>Výsledková listina</h2> |     <h2>Výsledková listina</h2> | ||||||
|       {% include "vysledkovky/vysledkovka_rocnik.html" %} |       {% include "vysledkovky/vysledkovka_rocnik.html" %} | ||||||
|   {% endif %} |   {% endif %} | ||||||
|  | @ -120,10 +120,12 @@ | ||||||
|   {% if user.je_org %} |   {% if user.je_org %} | ||||||
|     <div class='mam-org-only'> |     <div class='mam-org-only'> | ||||||
|     <p><a href='vysledkovka.tex' download>Výsledkovka ročníku (LaTeX, včetně neveřejných)</a></p> |     <p><a href='vysledkovka.tex' download>Výsledkovka ročníku (LaTeX, včetně neveřejných)</a></p> | ||||||
|  |     <p><a href="tituly.tex" download>Tituly (TeX, do konce ročníku = pro poslední číslo)</a></p> | ||||||
|  |     <p><a href="posledni_vysledkovka.tex" download>Výsledkovka posledního čísla</a></p> | ||||||
|     {# FIXME: Sice to sem asi nepatří sémanticky, ale bylo to nejjednodušší… #} |     {# FIXME: Sice to sem asi nepatří sémanticky, ale bylo to nejjednodušší… #} | ||||||
|     <p><a href='{% url 'seminar_rocnik_resitele_csv' rocnik=rocnik.rocnik %}' download>CSV export řešitelů</a></p> |     <p><a href='{% url 'seminar_rocnik_resitele_csv' rocnik=rocnik.rocnik %}' download>CSV export řešitelů</a></p> | ||||||
|     <h2>Výsledková listina včetně neveřejných bodů</h2> |     <h2>Výsledková listina včetně neveřejných bodů</h2> | ||||||
|         {% include "vysledkovky/vysledkovka_rocnik_neverejna.html" %} |         {% include "vysledkovky/vysledkovka_rocnik.html" with vysledkovka=vysledkovka_neverejna %} | ||||||
|     </div> |     </div> | ||||||
|   {% endif %} |   {% endif %} | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -1,13 +1,13 @@ | ||||||
| {% with lb="{" %} | {% with lb="{" %} | ||||||
| {% with rb="}" %} | {% with rb="}" %} | ||||||
| {% with radky_vysledkovky=radky_vysledkovky_s_neverejnymi cisla=cisla_s_neverejnymi %} | {% with vysledkovka=vysledkovka_neverejna %} | ||||||
| \setlength{\tabcolsep}{3pt} | \setlength{\tabcolsep}{3pt} | ||||||
| \begin{longtable}{|r|l|c|r|{% for cislo in cisla %}c{% if not forloop.last %}@{\hskip.5em}{% endif %}{% endfor %}|r|}\hline | \begin{longtable}{|r|l|c|r|{% for cislo in vysledkovka.cisla_rocniku %}c{% if not forloop.last %}@{\hskip.5em}{% endif %}{% endfor %}|r|}\hline | ||||||
| & & & & \multicolumn{{ lb }}{{ cisla|length }}}{c|}{\textbf{Číslo}} & \\\textbf{Poř.} & \textbf{Jméno} & \textbf{R.} & \raisebox{0.7mm}{$\sum_{-1}$} & {% for cislo in cisla %}\textbf{{ lb }}{{ cislo.poradi }}{{ rb }} & {% endfor %}\raisebox{0.7mm}{$\sum_1$} \\\hline | & & & & \multicolumn{{ lb }}{{ vysledkovka.cisla_rocniku|length }}}{c|}{\textbf{Číslo}} & \\\textbf{Poř.} & \textbf{Jméno} & \textbf{R.} & \raisebox{0.7mm}{$\sum_{-1}$} & {% for cislo in vysledkovka.cisla_rocniku %}\textbf{{ lb }}{{ cislo.poradi }}{{ rb }} & {% endfor %}\raisebox{0.7mm}{$\sum_1$} \\\hline | ||||||
| \endhead | \endhead | ||||||
| \hline | \hline | ||||||
| \endfoot | \endfoot | ||||||
| {% for rv in radky_vysledkovky %}{{ rv.poradi }} & {% if rv.titul %}\titul{{ lb }}{{ rv.titul }}}~{% endif %}{{ rv.resitel.osoba.jmeno|slice:":1" }}.~{{ rv.resitel.osoba.prijmeni }} & {% if rv.rocnik_resitele %}{{ rv.rocnik_resitele }}{% endif %} & {{ rv.body_celkem_odjakziva }} {% for b in rv.body_cisla_sezn %} & {{ b }}{% endfor %} & {{ rv.body_rocnik }} \\ | {% for rv in vysledkovka.radky_vysledkovky %}{{ rv.poradi }} & {% if rv.titul %}\titul{{ lb }}{{ rv.titul }}}~{% endif %}{{ rv.resitel.osoba.jmeno|slice:":1" }}.~{{ rv.resitel.osoba.prijmeni }} & {% if rv.rocnik_resitele %}{{ rv.rocnik_resitele }}{% endif %} & {{ rv.body_celkem_odjakziva }} {% for b in rv.body_cisla_seznam %} & {{ b }}{% endfor %} & {{ rv.body_rocnik }} \\ | ||||||
| {% endfor %}\end{longtable} | {% endfor %}\end{longtable} | ||||||
| {% endwith %} | {% endwith %} | ||||||
| {% endwith %} | {% endwith %} | ||||||
|  |  | ||||||
|  | @ -19,17 +19,17 @@ function sousdeadline() { | ||||||
| <hr> | <hr> | ||||||
| <div class="odpocet"> | <div class="odpocet"> | ||||||
| 	<b><big>Do | 	<b><big>Do | ||||||
|   {% if typ_deadline == 'soustredeni' %} |   {% if nejblizsi_deadline.typ == nejblizsi_deadline.TYP_SOUS or nejblizsi_deadline.typ == nejblizsi_deadline.TYP_PRVNI_A_SOUS %} | ||||||
|   <a href="" onClick="sousdeadline()" |   <a href="" onClick="sousdeadline()" | ||||||
|      title="Body za řešení, která nám přijdou do tohoto deadlinu, se ještě započítají pro účast na připravovaném soustředění."> |      title="Body za řešení, která nám přijdou do tohoto deadlinu, se ještě započítají pro účast na připravovaném soustředění."> | ||||||
|      deadlinu</a> odeslání <a href="/aktualni/zadani/">řešení |      deadlinu</a> odeslání <a href="/aktualni/zadani/">řešení | ||||||
|   </a> pro účast na soustředění |   </a> pro účast na soustředění | ||||||
| 
 | 
 | ||||||
|   {% elif typ_deadline == 'preddeadline' %} <a href="" onClick="preddeadline()" |   {% elif nejblizsi_deadline.typ == 'preddeadline' %} <a href="" onClick="preddeadline()" | ||||||
|   title="Řešení, která nám přijdou do tohoto deadlinu, se pokusíme opravit co nejdříve, abyste měli ještě šanci si je ještě opravit před definitivním deadlinem čísla.">1. deadlinu</a> aktuálního <a href="/aktualni/zadani/">čísla</a>  |   title="Řešení, která nám přijdou do tohoto deadlinu, se pokusíme opravit co nejdříve, abyste měli ještě šanci si je ještě opravit před definitivním deadlinem čísla.">1. deadlinu</a> aktuálního <a href="/aktualni/zadani/">čísla</a>  | ||||||
|   {% else %} deadlinu aktuálního <a href="/aktualni/zadani/">čísla</a>  |   {% else %} deadlinu aktuálního <a href="/aktualni/zadani/">čísla</a>  | ||||||
|   {% endif %}zbývá: |   {% endif %}zbývá: | ||||||
|       {{nejblizsi_deadline|timeuntil}}</big></b> |       {{nejblizsi_deadline.deadline|timeuntil}}</big></b> | ||||||
| </div> | </div> | ||||||
| <hr> | <hr> | ||||||
| {% endif %} | {% endif %} | ||||||
|  |  | ||||||
|  | @ -8,7 +8,7 @@ | ||||||
|     {% endblock %} |     {% endblock %} | ||||||
|   </h1> |   </h1> | ||||||
| 
 | 
 | ||||||
|   {% if radky_vysledkovky %} |   {% if vysledkovka.radky_vysledkovky %} | ||||||
|       {% include "vysledkovky/vysledkovka_rocnik.html" %} |       {% include "vysledkovky/vysledkovka_rocnik.html" %} | ||||||
|   {% else %} |   {% else %} | ||||||
|     <p>V tomto ročníku zatím žádné výsledky nejsou.</p> |     <p>V tomto ročníku zatím žádné výsledky nejsou.</p> | ||||||
|  | @ -22,7 +22,7 @@ | ||||||
|   {% if user.je_org and vysledkovka_s_neverejnymi %} |   {% if user.je_org and vysledkovka_s_neverejnymi %} | ||||||
|     <div class='mam-org-only'> |     <div class='mam-org-only'> | ||||||
|     <h1>Výsledky včetně neveřejných</h1> |     <h1>Výsledky včetně neveřejných</h1> | ||||||
|         {% include "vysledkovky/vysledkovka_rocnik_neverejna.html" %} |         {% include "vysledkovky/vysledkovka_rocnik.html" with vysledkovka=vysledkovka_neverejna %} | ||||||
|     </div> |     </div> | ||||||
|   {% endif %} |   {% endif %} | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -16,18 +16,20 @@ | ||||||
| 	<hr> | 	<hr> | ||||||
| 	<div class="zadani_termin"> | 	<div class="zadani_termin"> | ||||||
| 	Termíny pro odeslání řešení {{ac.poradi}}. série:<br> | 	Termíny pro odeslání řešení {{ac.poradi}}. série:<br> | ||||||
| 	 |  | ||||||
| 	{% if ac.datum_deadline_soustredeni %}	 |  | ||||||
| 			<span class="datum">{{ac.datum_deadline_soustredeni}}</span> pro účast na soustředění<br> |  | ||||||
| 	{% endif %} |  | ||||||
| 
 | 
 | ||||||
| 	{% if ac.datum_preddeadline %} |     {% for deadline in ac.deadline_v_cisle.all %} | ||||||
| 			<span class="datum">{{ac.datum_preddeadline}}</span> pro otištění v dalším čísle<br> |         {% if deadline.typ == deadline.TYP_SOUS or deadline.typ == deadline.TYP_PRVNI_A_SOUS %} | ||||||
| 	{% endif %} |                 <span class="datum">{{deadline.deadline.date}}</span> pro účast na soustředění<br> | ||||||
|  |         {% endif %} | ||||||
| 
 | 
 | ||||||
| 	{% if ac.datum_deadline %} |         {% if deadline.typ == deadline.TYP_PRVNI or deadline.typ == deadline.TYP_PRVNI_A_SOUS %} | ||||||
| 			<span class="datum">{{ac.datum_deadline}}</span> definitivní deadline<br> |                 <span class="datum">{{deadline.deadline.date}}</span> pro otištění v dalším čísle<br> | ||||||
| 	{% endif %} |         {% endif %} | ||||||
|  | 
 | ||||||
|  |         {% if deadline.typ == deadline.TYP_CISLA %} | ||||||
|  |                 <span class="datum">{{deadline.deadline.date}}</span> definitivní deadline<br> | ||||||
|  |         {% endif %} | ||||||
|  |     {% endfor %} | ||||||
| 
 | 
 | ||||||
| 	</div> | 	</div> | ||||||
| 	<hr> | 	<hr> | ||||||
|  |  | ||||||
|  | @ -1,44 +1,32 @@ | ||||||
| from django import template | from django import template | ||||||
| from django.utils.safestring import mark_safe | from django.utils.safestring import mark_safe | ||||||
| from seminar.utils import TypDeadline, deadline |  | ||||||
| register = template.Library() | register = template.Library() | ||||||
| 
 | import seminar.models as m | ||||||
| @register.filter(name='deadline') |  | ||||||
| def deadline_text(datum): |  | ||||||
| 	if deadline(datum) is None: |  | ||||||
| 		return 'Neznámý deadline' |  | ||||||
| 	typ, cislo, dl = deadline(datum) |  | ||||||
| 	strings = { |  | ||||||
| 		TypDeadline.PredDeadline: f"1. deadline čísla {cislo} ({dl})", |  | ||||||
| 		TypDeadline.SousDeadline: f"Soustřeďkový deadline čísla {cislo} ({dl})", |  | ||||||
| 		TypDeadline.FinalDeadline: f"Finální deadline čísla {cislo} ({dl})", |  | ||||||
| 		} |  | ||||||
| 	return strings[typ] |  | ||||||
| 
 | 
 | ||||||
| @register.filter(name='deadline_kratseji') | @register.filter(name='deadline_kratseji') | ||||||
| def deadline_kratsi_text(datum): | def deadline_kratsi_text(deadline: m.Deadline): | ||||||
| 	if deadline(datum) is None: | 	if deadline is None: | ||||||
| 		return 'NONE' | 		return 'NONE' | ||||||
| 	typ, cislo, dl = deadline(datum) |  | ||||||
| 	strings = { | 	strings = { | ||||||
| 		TypDeadline.PredDeadline: f"{cislo} ♲", | 		m.Deadline.TYP_PRVNI: f"{deadline.cislo} ⭯", | ||||||
| 		TypDeadline.SousDeadline: f"{cislo} Ⓢ", | 		m.Deadline.TYP_SOUS: f"{deadline.cislo} Ⓢ", | ||||||
| 		TypDeadline.FinalDeadline: f"{cislo} ✓", | 		m.Deadline.TYP_PRVNI_A_SOUS: f"{deadline.cislo} ⭯Ⓢ", | ||||||
|  | 		m.Deadline.TYP_CISLA: f"{deadline.cislo} ✓", | ||||||
| 		} | 		} | ||||||
| 	return strings[typ] | 	return strings[deadline.typ] | ||||||
| 
 | 
 | ||||||
| @register.filter(name='deadline_html') | @register.filter(name='deadline_html') | ||||||
| def deadline_html(datum): | def deadline_html(deadline: m.Deadline): | ||||||
| 	if deadline(datum) is None: | 	if deadline is None: | ||||||
| 		return 'Neznámý deadline' | 		return 'Neznámý deadline' | ||||||
| 	typ, _, _ = deadline(datum) | 	text = deadline_kratsi_text(deadline) | ||||||
| 	text = deadline_kratsi_text(datum) |  | ||||||
| 	classes = { | 	classes = { | ||||||
| 		TypDeadline.PredDeadline: 'preddeadline', | 		m.Deadline.TYP_PRVNI: 'preddeadline', | ||||||
| 		TypDeadline.SousDeadline: 'sous_deadline', | 		m.Deadline.TYP_SOUS: 'sous_deadline', | ||||||
| 		TypDeadline.FinalDeadline: 'final_deadline', | 		m.Deadline.TYP_PRVNI_A_SOUS: 'sous_deadline', | ||||||
|  | 		m.Deadline.TYP_CISLA: 'final_deadline', | ||||||
| 		} | 		} | ||||||
| 	return mark_safe(f'<span class="{classes[typ]}">{text}</span>') | 	return mark_safe(f'<span class="{classes[deadline.typ]}">{text}</span>') | ||||||
| 
 | 
 | ||||||
| @register.filter(name='zkrat_nazev_problemu') | @register.filter(name='zkrat_nazev_problemu') | ||||||
| def zkrat_nazev_problemu(nazev,width): | def zkrat_nazev_problemu(nazev,width): | ||||||
|  |  | ||||||
|  | @ -1,149 +0,0 @@ | ||||||
| from django.test import TestCase |  | ||||||
| from datetime import date |  | ||||||
| 
 |  | ||||||
| import seminar.models as m |  | ||||||
| from seminar.utils import deadline, TypDeadline |  | ||||||
| 
 |  | ||||||
| class DeadlineTestCase(TestCase): |  | ||||||
| 	def setUp(self): |  | ||||||
| 		# Chceme pár ročníků a v nich pár čísel |  | ||||||
| 		r1 = m.Rocnik.objects.create(rocnik=1, prvni_rok=2000, exportovat=False) |  | ||||||
| 		r2 = m.Rocnik.objects.create(rocnik=2, prvni_rok=2001, exportovat=False) |  | ||||||
| 		r3 = m.Rocnik.objects.create(rocnik=3, prvni_rok=2002, exportovat=False) |  | ||||||
| 
 |  | ||||||
| 		# První číslo mívá soustřeďkový deadline… |  | ||||||
| 		c1_1 = m.Cislo.objects.create(rocnik=r1, poradi='1', |  | ||||||
| 				datum_vydani=date.fromisoformat('2000-05-22'), |  | ||||||
| 				datum_preddeadline=date.fromisoformat('2000-09-11'), |  | ||||||
| 				datum_deadline_soustredeni=date.fromisoformat('2000-09-11'), |  | ||||||
| 				datum_deadline=date.fromisoformat('2000-10-01'), |  | ||||||
| 			) |  | ||||||
| 		c1_2 = m.Cislo.objects.create(rocnik=r1, poradi='2', |  | ||||||
| 				datum_vydani=date.fromisoformat('2000-10-19'), |  | ||||||
| 				datum_preddeadline=date.fromisoformat('2000-12-05'), |  | ||||||
| 				datum_deadline=date.fromisoformat('2001-01-02'), |  | ||||||
| 			) |  | ||||||
| 		# Některá čísla nemají předdeadline… |  | ||||||
| 		c1_3 = m.Cislo.objects.create(rocnik=r1, poradi='3', |  | ||||||
| 				datum_vydani=date.fromisoformat('2001-01-28'), |  | ||||||
| 				datum_deadline=date.fromisoformat('2001-03-12'), |  | ||||||
| 			) |  | ||||||
| 		# Poslední číslo nemá ani normální deadline… |  | ||||||
| 		c1_4 = m.Cislo.objects.create(rocnik=r1, poradi='4-5', |  | ||||||
| 				datum_vydani=date.fromisoformat('2001-04-24'), |  | ||||||
| 			) |  | ||||||
| 		# První číslo dalšího ročníku se někdy vydá dřív, než poslední minulého… |  | ||||||
| 		c2_1 = m.Cislo.objects.create(rocnik=r2, poradi='1', |  | ||||||
| 				datum_vydani=date.fromisoformat('2001-04-19'), |  | ||||||
| 				datum_deadline_soustredeni=date.fromisoformat('2001-09-26'), |  | ||||||
| 				datum_deadline=date.fromisoformat('2001-10-07'), |  | ||||||
| 			) |  | ||||||
| 		# Tohle číslo má finální deadline až po vydání prvního čísla dalšího ročníku |  | ||||||
| 		# To samé se skoro stalo na přelomu (reálných) ročníků 27 a 28. |  | ||||||
| 		c2_2 = m.Cislo.objects.create(rocnik=r2, poradi='2', |  | ||||||
| 				datum_vydani=date.fromisoformat('2002-03-14'), |  | ||||||
| 				datum_preddeadline=date.fromisoformat('2002-05-26'), |  | ||||||
| 				datum_deadline=date.fromisoformat('2002-06-30'), |  | ||||||
| 			) |  | ||||||
| 		# Závěrečné číslo druhého ročníku až na podzim |  | ||||||
| 		c2_3 = m.Cislo.objects.create(rocnik=r2, poradi='3', |  | ||||||
| 				datum_vydani=date.fromisoformat('2002-09-05'), |  | ||||||
| 			) |  | ||||||
| 		# Divný případ: sous deadline stejný jako finální |  | ||||||
| 		c3_1 = m.Cislo.objects.create(rocnik=r3, poradi='1', |  | ||||||
| 				datum_vydani=date.fromisoformat('2002-06-02'), |  | ||||||
| 				datum_preddeadline=date.fromisoformat('2002-08-31'), |  | ||||||
| 				datum_deadline=date.fromisoformat('2002-09-30'), |  | ||||||
| 				datum_deadline_soustredeni=date.fromisoformat('2002-09-30'), |  | ||||||
| 			) |  | ||||||
| 
 |  | ||||||
| 	# Celkový harmonogram: |  | ||||||
| 	# 2000-05-22	začátek 1. ročníku, číslo 1.1 |  | ||||||
| 	 |  | ||||||
| 	# 2000-09-11	sous a 1. deadline 1.1 |  | ||||||
| 	# 2000-10-01	finální deadline 1.1 |  | ||||||
| 	# 2000-10-19	Vydání 1.2 |  | ||||||
| 	# 2000-12-05	předdeadline 1.2 |  | ||||||
| 	# 2001-01-02	finální deadline 1.2 |  | ||||||
| 	# 2001-01-28	vyd 1.3 |  | ||||||
| 	# 2001-03-12	deadline 1.3 |  | ||||||
| 	# 2001-04-19	Začátek 2. ročníku, číslo 2.1 |  | ||||||
| 	# 2001-04-24	Vydání 1.4-5 -- závěrečné číslo 1. roč. |  | ||||||
| 	 |  | ||||||
| 	# 2001-09-26	Sous-deadline 2.1 |  | ||||||
| 	# 2001-10-07	Deadline 2.1 |  | ||||||
| 	# 2002-03-14	Pí den, vydání 2.2 |  | ||||||
| 	# 2002-05-26	Předdeadline 2.2 |  | ||||||
| 	# 2002-06-02	Třetí ročník, vydání 3.1 |  | ||||||
| 	# 2002-06-30	Deadline 2.2 |  | ||||||
| 
 |  | ||||||
| 	# 2002-08-31	Předdeadline 3.1 |  | ||||||
| 	# 2002-09-04	Vydání 2.3, konec 2. roč. |  | ||||||
| 	# 2002-09-30	Sous a finální deadline 3.1 |  | ||||||
| 	 |  | ||||||
| 	def test_deadline_spravne_vysledky(self): |  | ||||||
| 		"""V každém intervalu mezi deadliny dostáváme ten správný deadline""" |  | ||||||
| 		# První ročník |  | ||||||
| 		self.assertEqual(deadline(date.fromisoformat('2000-05-30')), (TypDeadline.PredDeadline, m.Cislo.objects.get(rocnik__rocnik=1, poradi='1'), date.fromisoformat('2000-09-11'))) |  | ||||||
| 		self.assertEqual(deadline(date.fromisoformat('2000-09-15')), (TypDeadline.FinalDeadline, m.Cislo.objects.get(rocnik__rocnik=1, poradi='1'), date.fromisoformat('2000-10-01'))) |  | ||||||
| 
 |  | ||||||
| 		# Trochu divný případ, kdy někdo něco pošle před vydáním čísla. Ale článkům se to asi stát může… |  | ||||||
| 		self.assertEqual(deadline(date.fromisoformat('2000-10-10')), (TypDeadline.PredDeadline, m.Cislo.objects.get(rocnik__rocnik=1, poradi='2'), date.fromisoformat('2000-12-05'))) |  | ||||||
| 		self.assertEqual(deadline(date.fromisoformat('2000-10-22')), (TypDeadline.PredDeadline, m.Cislo.objects.get(rocnik__rocnik=1, poradi='2'), date.fromisoformat('2000-12-05'))) |  | ||||||
| 		self.assertEqual(deadline(date.fromisoformat('2000-12-15')), (TypDeadline.FinalDeadline, m.Cislo.objects.get(rocnik__rocnik=1, poradi='2'), date.fromisoformat('2001-01-02'))) |  | ||||||
| 		self.assertEqual(deadline(date.fromisoformat('2001-01-08')), (TypDeadline.FinalDeadline, m.Cislo.objects.get(rocnik__rocnik=1, poradi='3'), date.fromisoformat('2001-03-12'))) |  | ||||||
| 		self.assertEqual(deadline(date.fromisoformat('2001-01-30')), (TypDeadline.FinalDeadline, m.Cislo.objects.get(rocnik__rocnik=1, poradi='3'), date.fromisoformat('2001-03-12'))) |  | ||||||
| 		self.assertEqual(deadline(date.fromisoformat('2001-03-15')), (TypDeadline.SousDeadline, m.Cislo.objects.get(rocnik__rocnik=2, poradi='1'), date.fromisoformat('2001-09-26'))) |  | ||||||
| 		self.assertEqual(deadline(date.fromisoformat('2001-04-22')), (TypDeadline.SousDeadline, m.Cislo.objects.get(rocnik__rocnik=2, poradi='1'), date.fromisoformat('2001-09-26'))) |  | ||||||
| 		self.assertEqual(deadline(date.fromisoformat('2001-04-30')), (TypDeadline.SousDeadline, m.Cislo.objects.get(rocnik__rocnik=2, poradi='1'), date.fromisoformat('2001-09-26'))) |  | ||||||
| 
 |  | ||||||
| 		# Druhý ročník |  | ||||||
| 		# Pro jistotu ještě prázdniny |  | ||||||
| 		self.assertEqual(deadline(date.fromisoformat('2001-07-30')), (TypDeadline.SousDeadline, m.Cislo.objects.get(rocnik__rocnik=2, poradi='1'), date.fromisoformat('2001-09-26'))) |  | ||||||
| 		self.assertEqual(deadline(date.fromisoformat('2001-09-27')), (TypDeadline.FinalDeadline, m.Cislo.objects.get(rocnik__rocnik=2, poradi='1'), date.fromisoformat('2001-10-07'))) |  | ||||||
| 		self.assertEqual(deadline(date.fromisoformat('2001-12-27')), (TypDeadline.PredDeadline, m.Cislo.objects.get(rocnik__rocnik=2, poradi='2'), date.fromisoformat('2002-05-26'))) |  | ||||||
| 		self.assertEqual(deadline(date.fromisoformat('2002-03-15')), (TypDeadline.PredDeadline, m.Cislo.objects.get(rocnik__rocnik=2, poradi='2'), date.fromisoformat('2002-05-26'))) |  | ||||||
| 		self.assertEqual(deadline(date.fromisoformat('2002-03-15')), (TypDeadline.PredDeadline, m.Cislo.objects.get(rocnik__rocnik=2, poradi='2'), date.fromisoformat('2002-05-26'))) |  | ||||||
| 		 |  | ||||||
| 		self.assertEqual(deadline(date.fromisoformat('2002-05-27')), (TypDeadline.FinalDeadline, m.Cislo.objects.get(rocnik__rocnik=2, poradi='2'), date.fromisoformat('2002-06-30'))) |  | ||||||
| 		# Tohle je trochu podezřelý případ, protože relevantní deadliny existují dvě… Ale ten pro minulý ročník je těsnější a realističtější |  | ||||||
| 		self.assertEqual(deadline(date.fromisoformat('2002-06-03')), (TypDeadline.FinalDeadline, m.Cislo.objects.get(rocnik__rocnik=2, poradi='2'), date.fromisoformat('2002-06-30'))) |  | ||||||
| 		self.assertEqual(deadline(date.fromisoformat('2002-07-01')), (TypDeadline.PredDeadline, m.Cislo.objects.get(rocnik__rocnik=3, poradi='1'), date.fromisoformat('2002-08-31'))) |  | ||||||
| 
 |  | ||||||
| 		# Třetí ročník |  | ||||||
| 		self.assertEqual(deadline(date.fromisoformat('2002-09-01')), (TypDeadline.SousDeadline, m.Cislo.objects.get(rocnik__rocnik=3, poradi='1'), date.fromisoformat('2002-09-30'))) |  | ||||||
| 		self.assertEqual(deadline(date.fromisoformat('2002-09-05')), (TypDeadline.SousDeadline, m.Cislo.objects.get(rocnik__rocnik=3, poradi='1'), date.fromisoformat('2002-09-30'))) |  | ||||||
| 
 |  | ||||||
| 	def test_deadline_ve_zlomove_dny(self): |  | ||||||
| 		"""Pro dny, kdy je deadline nebo vydání čísla, pořád dostáváme správné deadliny. |  | ||||||
| 
 |  | ||||||
| 		Testuje hlavně přítomnost někde nějakých off-by-one""" |  | ||||||
| 		self.assertEqual(deadline(date.fromisoformat('2000-05-22')), (TypDeadline.PredDeadline, m.Cislo.objects.get(rocnik__rocnik=1, poradi='1'), date.fromisoformat('2000-09-11'))) |  | ||||||
| 
 |  | ||||||
| 		self.assertEqual(deadline(date.fromisoformat('2000-09-11')), (TypDeadline.PredDeadline, m.Cislo.objects.get(rocnik__rocnik=1, poradi='1'), date.fromisoformat('2000-09-11'))) |  | ||||||
| 		self.assertEqual(deadline(date.fromisoformat('2000-10-01')), (TypDeadline.FinalDeadline, m.Cislo.objects.get(rocnik__rocnik=1, poradi='1'), date.fromisoformat('2000-10-01'))) |  | ||||||
| 		self.assertEqual(deadline(date.fromisoformat('2000-10-19')), (TypDeadline.PredDeadline, m.Cislo.objects.get(rocnik__rocnik=1, poradi='2'), date.fromisoformat('2000-12-05'))) |  | ||||||
| 		self.assertEqual(deadline(date.fromisoformat('2000-12-05')), (TypDeadline.PredDeadline, m.Cislo.objects.get(rocnik__rocnik=1, poradi='2'), date.fromisoformat('2000-12-05'))) |  | ||||||
| 		self.assertEqual(deadline(date.fromisoformat('2001-01-02')), (TypDeadline.FinalDeadline, m.Cislo.objects.get(rocnik__rocnik=1, poradi='2'), date.fromisoformat('2001-01-02'))) |  | ||||||
| 		self.assertEqual(deadline(date.fromisoformat('2001-01-28')), (TypDeadline.FinalDeadline, m.Cislo.objects.get(rocnik__rocnik=1, poradi='3'), date.fromisoformat('2001-03-12'))) |  | ||||||
| 		self.assertEqual(deadline(date.fromisoformat('2001-03-12')), (TypDeadline.FinalDeadline, m.Cislo.objects.get(rocnik__rocnik=1, poradi='3'), date.fromisoformat('2001-03-12'))) |  | ||||||
| 		self.assertEqual(deadline(date.fromisoformat('2001-04-19')), (TypDeadline.SousDeadline, m.Cislo.objects.get(rocnik__rocnik=2, poradi='1'), date.fromisoformat('2001-09-26'))) |  | ||||||
| 		self.assertEqual(deadline(date.fromisoformat('2001-04-24')), (TypDeadline.SousDeadline, m.Cislo.objects.get(rocnik__rocnik=2, poradi='1'), date.fromisoformat('2001-09-26'))) |  | ||||||
| 
 |  | ||||||
| 		self.assertEqual(deadline(date.fromisoformat('2001-09-26')), (TypDeadline.SousDeadline, m.Cislo.objects.get(rocnik__rocnik=2, poradi='1'), date.fromisoformat('2001-09-26'))) |  | ||||||
| 		self.assertEqual(deadline(date.fromisoformat('2001-10-07')), (TypDeadline.FinalDeadline, m.Cislo.objects.get(rocnik__rocnik=2, poradi='1'), date.fromisoformat('2001-10-07'))) |  | ||||||
| 		self.assertEqual(deadline(date.fromisoformat('2002-03-14')), (TypDeadline.PredDeadline, m.Cislo.objects.get(rocnik__rocnik=2, poradi='2'), date.fromisoformat('2002-05-26'))) |  | ||||||
| 		self.assertEqual(deadline(date.fromisoformat('2002-05-26')), (TypDeadline.PredDeadline, m.Cislo.objects.get(rocnik__rocnik=2, poradi='2'), date.fromisoformat('2002-05-26'))) |  | ||||||
| 		self.assertEqual(deadline(date.fromisoformat('2002-06-02')), (TypDeadline.FinalDeadline, m.Cislo.objects.get(rocnik__rocnik=2, poradi='2'), date.fromisoformat('2002-06-30'))) |  | ||||||
| 		self.assertEqual(deadline(date.fromisoformat('2002-06-30')), (TypDeadline.FinalDeadline, m.Cislo.objects.get(rocnik__rocnik=2, poradi='2'), date.fromisoformat('2002-06-30'))) |  | ||||||
| 
 |  | ||||||
| 		self.assertEqual(deadline(date.fromisoformat('2002-08-31')), (TypDeadline.PredDeadline, m.Cislo.objects.get(rocnik__rocnik=3, poradi='1'), date.fromisoformat('2002-08-31'))) |  | ||||||
| 		self.assertEqual(deadline(date.fromisoformat('2002-09-04')), (TypDeadline.SousDeadline, m.Cislo.objects.get(rocnik__rocnik=3, poradi='1'), date.fromisoformat('2002-09-30'))) |  | ||||||
| 		self.assertEqual(deadline(date.fromisoformat('2002-09-30')), (TypDeadline.SousDeadline, m.Cislo.objects.get(rocnik__rocnik=3, poradi='1'), date.fromisoformat('2002-09-30'))) |  | ||||||
| 
 |  | ||||||
| 	def test_deadline_pro_datetime(self): |  | ||||||
| 		"""Testuje, že i pro datetime dostáváme správné deadliny""" |  | ||||||
| 		self.skipTest('Chybí implementace testu') |  | ||||||
| 
 |  | ||||||
| 	def test_moc_pozdni_deadline(self): |  | ||||||
| 		self.assertIsNone(deadline(date.max)) |  | ||||||
|  | @ -297,7 +297,7 @@ def gen_reseni_ulohy(rnd, cisla, uloha, pocet_resitelu, poradi_cisla, resitele_c | ||||||
| 			res_vyber.remove(resitele[0]) | 			res_vyber.remove(resitele[0]) | ||||||
| 
 | 
 | ||||||
| 		# Vytvoření řešení. | 		# Vytvoření řešení. | ||||||
| 		if uloha.cislo_zadani.datum_deadline is not None: | 		if uloha.cislo_zadani.zlomovy_deadline_pro_papirove_cislo() is not None: | ||||||
| 			# combine, abychom dostali plný čas a ne jen datum | 			# combine, abychom dostali plný čas a ne jen datum | ||||||
| 			cas_doruceni = datetime.datetime.combine(uloha.cislo_zadani.datum_deadline, datetime.datetime.min.time()) - datetime.timedelta(days=random.randint(0, 40)) - datetime.timedelta(minutes=random.randint(0, 60*24)) | 			cas_doruceni = datetime.datetime.combine(uloha.cislo_zadani.datum_deadline, datetime.datetime.min.time()) - datetime.timedelta(days=random.randint(0, 40)) - datetime.timedelta(minutes=random.randint(0, 60*24)) | ||||||
| 			# astimezone, protože jinak vyhazuje warning o nenastavené TZ | 			# astimezone, protože jinak vyhazuje warning o nenastavené TZ | ||||||
|  |  | ||||||
|  | @ -72,6 +72,16 @@ urlpatterns = [ | ||||||
| 		org_required(views.resiteleRocnikuCsvExportView), | 		org_required(views.resiteleRocnikuCsvExportView), | ||||||
| 		name='seminar_rocnik_resitele_csv' | 		name='seminar_rocnik_resitele_csv' | ||||||
| 	), | 	), | ||||||
|  | 	path( | ||||||
|  | 		'rocnik/<int:rocnik>/tituly.tex', | ||||||
|  | 		org_required(views.TitulyViewRocnik), | ||||||
|  | 		name='seminar_rocnik_titul' | ||||||
|  | 	), | ||||||
|  | 	path( | ||||||
|  | 		'rocnik/<int:rocnik>/posledni_vysledkovka.tex', | ||||||
|  | 		org_required(views.PosledniCisloVysledkovkaView.as_view()), | ||||||
|  | 		name='seminar_rocnik_posledni_vysledkovka' | ||||||
|  | 	), | ||||||
| 	path( | 	path( | ||||||
| 		'cislo/<int:rocnik>.<str:cislo>/vysledkovka.tex', | 		'cislo/<int:rocnik>.<str:cislo>/vysledkovka.tex', | ||||||
| 		org_required(views.CisloVysledkovkaView.as_view()), | 		org_required(views.CisloVysledkovkaView.as_view()), | ||||||
|  |  | ||||||
							
								
								
									
										142
									
								
								seminar/utils.py
									
									
									
									
									
								
							
							
						
						
									
										142
									
								
								seminar/utils.py
									
									
									
									
									
								
							|  | @ -14,9 +14,6 @@ from django.contrib.auth.models import AnonymousUser | ||||||
| from django.contrib.contenttypes.models import ContentType | from django.contrib.contenttypes.models import ContentType | ||||||
| from django.core.exceptions import ObjectDoesNotExist | from django.core.exceptions import ObjectDoesNotExist | ||||||
| 
 | 
 | ||||||
| from enum import Enum |  | ||||||
| from enum import auto |  | ||||||
| 
 |  | ||||||
| import logging | import logging | ||||||
| 
 | 
 | ||||||
| import seminar.models as m | import seminar.models as m | ||||||
|  | @ -179,11 +176,11 @@ def resi_v_rocniku(rocnik, cislo=None): | ||||||
| 	if cislo is None: | 	if cislo is None: | ||||||
| 		# filtrujeme pouze podle ročníku | 		# filtrujeme pouze podle ročníku | ||||||
| 		return m.Resitel.objects.filter(rok_maturity__gte=rocnik.druhy_rok(), | 		return m.Resitel.objects.filter(rok_maturity__gte=rocnik.druhy_rok(), | ||||||
| 										reseni__hodnoceni__cislo_body__rocnik=rocnik).distinct() | 										reseni__hodnoceni__deadline_body__cislo__rocnik=rocnik).distinct() | ||||||
| 	else:  # filtrujeme podle ročníku i čísla | 	else:  # filtrujeme podle ročníku i čísla | ||||||
| 		return m.Resitel.objects.filter(rok_maturity__gte=rocnik.druhy_rok(), | 		return m.Resitel.objects.filter(rok_maturity__gte=rocnik.druhy_rok(), | ||||||
| 										reseni__hodnoceni__cislo_body__rocnik=rocnik, | 										reseni__hodnoceni__deadline_body__cislo__rocnik=rocnik, | ||||||
| 										reseni__hodnoceni__cislo_body__poradi__lte=cislo.poradi).distinct() | 										reseni__hodnoceni__deadline_body__cislo__poradi__lte=cislo.poradi).distinct() | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def aktivniResitele(cislo, pouze_letosni=False): | def aktivniResitele(cislo, pouze_letosni=False): | ||||||
|  | @ -248,139 +245,6 @@ def viewMethodSwitch(get, post): | ||||||
| 	 | 	 | ||||||
| 	return NewView.as_view() | 	return NewView.as_view() | ||||||
| 
 | 
 | ||||||
| def cisla_rocniku(rocnik, jen_verejne=True): |  | ||||||
| 	""" |  | ||||||
| 	Vrátí všechna čísla daného ročníku. |  | ||||||
| 	Parametry: |  | ||||||
| 		rocnik (Rocnik): ročník semináře |  | ||||||
| 		jen_verejne (bool): zda se mají vrátit jen veřejná, nebo všechna čísla |  | ||||||
| 	Vrátí: |  | ||||||
| 		seznam objektů typu Cislo |  | ||||||
| 	"""	 |  | ||||||
| 	if jen_verejne: |  | ||||||
| 		return rocnik.verejne_vysledkovky_cisla() |  | ||||||
| 	else: |  | ||||||
| 		return rocnik.cisla.all().order_by('poradi') |  | ||||||
| 
 |  | ||||||
| def hlavni_problem(problem): |  | ||||||
| 	""" Pro daný problém vrátí jeho nejvyšší nadproblém.""" |  | ||||||
| 	while not(problem.nadproblem == None): |  | ||||||
| 		problem = problem.nadproblem |  | ||||||
| 	return problem |  | ||||||
| 
 |  | ||||||
| def problemy_rocniku(rocnik, jen_verejne=True): |  | ||||||
| 	return  m.Problem.objects.filter(hodnoceni__in = m.Hodnoceni.objects.filter(cislo_body__in = cisla_rocniku(rocnik, jen_verejne))).distinct().select_related('nadproblem').select_related('nadproblem__nadproblem') |  | ||||||
| 
 |  | ||||||
| def problemy_cisla(cislo): |  | ||||||
| 	""" Vrátí seznam všech problémů s body v daném čísle. """ |  | ||||||
| 	return m.Problem.objects.filter(hodnoceni__in = m.Hodnoceni.objects.filter(cislo_body = cislo)).distinct().non_polymorphic().select_related('nadproblem').select_related('nadproblem__nadproblem') |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| def hlavni_problemy_f(problemy=None): |  | ||||||
| 	""" Vrátí seznam všech problémů, které již nemají nadproblém. """ |  | ||||||
| 	# hlavní problémy čísla  |  | ||||||
| 	# (mají vlastní sloupeček ve výsledkovce, nemají nadproblém) |  | ||||||
| 	hlavni_problemy = set() |  | ||||||
| 	for p in problemy: |  | ||||||
| 		hlavni_problemy.add(hlavni_problem(p)) |  | ||||||
| 		 |  | ||||||
| 	# zunikátnění |  | ||||||
| 	hlavni_problemy = list(hlavni_problemy) |  | ||||||
| 	hlavni_problemy.sort(key=lambda k: k.kod_v_rocniku) # setřídit podle t1, t2, c3, ... |  | ||||||
| 	 |  | ||||||
| 	return hlavni_problemy |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| def podproblemy_v_cislu(cislo, problemy=None, hlavni_problemy=None): |  | ||||||
| 	""" Vrátí seznam všech problémů s body v daném čísle v poli 'indexovaném' tématy. """ |  | ||||||
| 	if problemy is None: |  | ||||||
| 		problemy = problemy_cisla(cislo) |  | ||||||
| 	if hlavni_problemy is None: |  | ||||||
| 		hlavni_problemy = hlavni_problemy_f(problemy) |  | ||||||
| 
 |  | ||||||
| 	podproblemy = dict((hp.id, []) for hp in hlavni_problemy) |  | ||||||
| 	hlavni_problemy = set(hlavni_problemy) |  | ||||||
| 	podproblemy[-1] = [] |  | ||||||
| 
 |  | ||||||
| 	for problem in problemy: |  | ||||||
| 		h_problem = hlavni_problem(problem) |  | ||||||
| 		if h_problem in hlavni_problemy: |  | ||||||
| 			podproblemy[h_problem.id].append(problem) |  | ||||||
| 		else: |  | ||||||
| 			podproblemy[-1].append(problem) |  | ||||||
| 
 |  | ||||||
| 	for podproblem in podproblemy.keys(): |  | ||||||
| 		def int_or_zero(p): |  | ||||||
| 			try: |  | ||||||
| 				return int(p.kod) |  | ||||||
| 			except ValueError: |  | ||||||
| 				return 0 |  | ||||||
| 
 |  | ||||||
| 		podproblemy[podproblem] = sorted(podproblemy[podproblem], key=int_or_zero) |  | ||||||
| 
 |  | ||||||
| 	return podproblemy |  | ||||||
| 
 |  | ||||||
| class TypDeadline(Enum): |  | ||||||
| 	PredDeadline = auto() |  | ||||||
| 	SousDeadline = auto() |  | ||||||
| 	FinalDeadline = auto() |  | ||||||
| 
 |  | ||||||
| def deadline_v_rocniku(datum, rocnik): |  | ||||||
| 	"""Funkce pro dohledání, ke kterému deadlinu daného ročníku se datum váže. |  | ||||||
| 	 |  | ||||||
| 	Vrací trojici (TypDeadline, Cislo, datumDeadline: date). |  | ||||||
| 
 |  | ||||||
| 	V případě nevalidního volání není aktuálně chování definováno(!) |  | ||||||
| 	""" |  | ||||||
| 	cisla = m.Cislo.objects.filter(rocnik=rocnik) |  | ||||||
| 	deadliny = [] |  | ||||||
| 	for c in cisla: |  | ||||||
| 		if c.datum_preddeadline is not None: |  | ||||||
| 			deadliny.append((TypDeadline.PredDeadline, c, c.datum_preddeadline)) |  | ||||||
| 		if c.datum_deadline_soustredeni is not None: |  | ||||||
| 			deadliny.append((TypDeadline.SousDeadline, c, c.datum_deadline_soustredeni)) |  | ||||||
| 		if c.datum_deadline is not None: |  | ||||||
| 			deadliny.append((TypDeadline.FinalDeadline, c, c.datum_deadline)) |  | ||||||
| 	deadliny = sorted(deadliny, key=lambda x: x[2])	# podle data |  | ||||||
| 	for dl in deadliny: |  | ||||||
| 		if datum <= dl[2]: |  | ||||||
| 			# První takový deadline je ten nejtěsnější |  | ||||||
| 			return dl |  | ||||||
| 	logger.error(f'Pro datum {datum} v ročníku {rocnik} neexistuje deadline.') |  | ||||||
| 
 |  | ||||||
| def deadline(datum): |  | ||||||
| 	"""Funkce pro dohledání, ke kterému deadlinu se datum váže. |  | ||||||
| 	 |  | ||||||
| 	Vrací trojici (TypDeadline, Cislo, datumDeadline: date). Pokud se deadline nenajde, vrátí None |  | ||||||
| 	""" |  | ||||||
| 
 |  | ||||||
| 	if isinstance(datum, datetime.datetime): |  | ||||||
| 		datum = datum.date() |  | ||||||
| 	rok = datum.year |  | ||||||
| 	# Dva ročníky podezřelé z obsahování dat |  | ||||||
| 	try: |  | ||||||
| 		pozdejsi_rocnik = m.Rocnik.objects.get(prvni_rok=rok) |  | ||||||
| 	except m.Rocnik.DoesNotExist: |  | ||||||
| 		pozdejsi_rocnik = None |  | ||||||
| 
 |  | ||||||
| 	try: |  | ||||||
| 		drivejsi_rocnik = m.Rocnik.objects.get(prvni_rok=rok-1) |  | ||||||
| 	except m.Rocnik.DoesNotExist: |  | ||||||
| 		drivejsi_rocnik = None |  | ||||||
| 
 |  | ||||||
| 	if drivejsi_rocnik is not None: |  | ||||||
| 		# Předpokládáme, že neexistuje číslo, které má deadline ale nemá finální deadline. |  | ||||||
| 		# Seznam čísel je potřeba ručně setřídit chronologicky, protože Model říká, že se řadí od nejnovějšího |  | ||||||
| 		posledni_deadline_drivejsiho_rocniku = m.Cislo.objects.filter(rocnik=drivejsi_rocnik, datum_deadline__isnull=False).order_by('poradi').last().datum_deadline |  | ||||||
| 
 |  | ||||||
| 	logger.debug(f'Nalezené ročníky: {drivejsi_rocnik}, {pozdejsi_rocnik}') |  | ||||||
| 	if drivejsi_rocnik is not None and datum <= posledni_deadline_drivejsiho_rocniku: |  | ||||||
| 		logger.debug(f'Hledám v dřívějším ročníku: {drivejsi_rocnik}') |  | ||||||
| 		return deadline_v_rocniku(datum, drivejsi_rocnik) |  | ||||||
| 	else: |  | ||||||
| 		logger.debug(f'Hledám v pozdějším ročníku: {pozdejsi_rocnik}') |  | ||||||
| 		return deadline_v_rocniku(datum, pozdejsi_rocnik) |  | ||||||
| 
 |  | ||||||
| 
 | 
 | ||||||
| def sync_skoly(base_url): | def sync_skoly(base_url): | ||||||
| 	"""Stáhne všechny školy z mamwebu na adrese <base_url> a uloží je do databáze""" | 	"""Stáhne všechny školy z mamwebu na adrese <base_url> a uloží je do databáze""" | ||||||
|  |  | ||||||
|  | @ -11,14 +11,16 @@ from django.core.exceptions import PermissionDenied | ||||||
| 
 | 
 | ||||||
| import seminar.models as s | import seminar.models as s | ||||||
| import seminar.models as m | import seminar.models as m | ||||||
| from seminar.models import Problem, Cislo, Reseni, Nastaveni, Rocnik, Organizator, Resitel, Novinky, Tema, Clanek # Tohle je stare a chceme se toho zbavit. Pouzivejte s.ToCoChci | from seminar.models import Problem, Cislo, Reseni, Nastaveni, Rocnik, \ | ||||||
|  | 	Organizator, Resitel, Novinky, Tema, Clanek, \ | ||||||
|  | 	Deadline  # Tohle je stare a chceme se toho zbavit. Pouzivejte s.ToCoChci | ||||||
| #from .models import VysledkyZaCislo, VysledkyKCisluZaRocnik, VysledkyKCisluOdjakziva | #from .models import VysledkyZaCislo, VysledkyKCisluZaRocnik, VysledkyKCisluOdjakziva | ||||||
| from seminar import utils | from seminar import utils | ||||||
| from treenode import treelib | from treenode import treelib | ||||||
| import treenode.templatetags as tnltt | import treenode.templatetags as tnltt | ||||||
| import treenode.serializers as vr | import treenode.serializers as vr | ||||||
| from vysledkovky.utils import body_resitelu | from vysledkovky.utils import body_resitelu, VysledkovkaCisla, \ | ||||||
| from vysledkovky.views import vysledkovka_rocniku, vysledkovka_cisla | 	VysledkovkaRocniku, VysledkovkaDoTeXu | ||||||
| 
 | 
 | ||||||
| from datetime import date, datetime | from datetime import date, datetime | ||||||
| from django.utils import timezone | from django.utils import timezone | ||||||
|  | @ -206,26 +208,17 @@ def ZadaniAktualniVysledkovkaView(request): | ||||||
| 	nastaveni = get_object_or_404(Nastaveni) | 	nastaveni = get_object_or_404(Nastaveni) | ||||||
| 	# Aktualni verejna vysledkovka | 	# Aktualni verejna vysledkovka | ||||||
| 	rocnik = nastaveni.aktualni_rocnik | 	rocnik = nastaveni.aktualni_rocnik | ||||||
| 	context = vysledkovka_rocniku( | 	context = {'vysledkovka': VysledkovkaRocniku(rocnik, True)} | ||||||
| 		rocnik=rocnik, |  | ||||||
| 		request=request, |  | ||||||
| 		sneverejnou=True |  | ||||||
| 	) |  | ||||||
| 
 | 
 | ||||||
| 	# kdyz neni verejna vysledkovka, tak zobraz starou | 	# kdyz neni verejna vysledkovka, tak zobraz starou | ||||||
| 	if len(context['cisla']) == 0: | 	if len(context['vysledkovka'].cisla_rocniku) == 0: | ||||||
| 		try: | 		try: | ||||||
| 			minuly_rocnik = Rocnik.objects.get( | 			minuly_rocnik = Rocnik.objects.get( | ||||||
| 				prvni_rok=(rocnik.prvni_rok-1)) | 				rocnik=(rocnik.rocnik-1)) | ||||||
| 			rocnik = minuly_rocnik | 			rocnik = minuly_rocnik | ||||||
| 
 | 
 | ||||||
| 			# Přepíšeme prázdnou výsledkovku výsledkovkou z minulého ročníku | 			# Přepíšeme prázdnou výsledkovku výsledkovkou z minulého ročníku | ||||||
| 			context = vysledkovka_rocniku( | 			context['vysledkovka'] = VysledkovkaRocniku(rocnik, True) | ||||||
| 				rocnik=rocnik, |  | ||||||
| 				context=context, |  | ||||||
| 				request=request, |  | ||||||
| 				sneverejnou=True |  | ||||||
| 			) |  | ||||||
| 		except ObjectDoesNotExist: | 		except ObjectDoesNotExist: | ||||||
| 			pass | 			pass | ||||||
| 
 | 
 | ||||||
|  | @ -268,23 +261,8 @@ class TitulniStranaView(generic.ListView): | ||||||
| 		context = super(TitulniStranaView, self).get_context_data(**kwargs) | 		context = super(TitulniStranaView, self).get_context_data(**kwargs) | ||||||
| 		nastaveni = get_object_or_404(Nastaveni) | 		nastaveni = get_object_or_404(Nastaveni) | ||||||
| 
 | 
 | ||||||
| 		deadline_soustredeni = (nastaveni.aktualni_cislo.datum_deadline_soustredeni, "soustredeni") | 		deadline = m.Deadline.objects.filter(deadline__gte=timezone.now()).order_by("deadline").first() | ||||||
| 		preddeadline = (nastaveni.aktualni_cislo.datum_preddeadline, "preddeadline") | 		context['nejblizsi_deadline'] = deadline | ||||||
| 		deadline = (nastaveni.aktualni_cislo.datum_deadline, "deadline") |  | ||||||
| 
 |  | ||||||
| 		try: |  | ||||||
| 			nejblizsi_deadline = sorted(filter(lambda dl: dl[0] is not None and dl[0] >= date.today(), [deadline_soustredeni, preddeadline, deadline]))[0] |  | ||||||
| 			if nejblizsi_deadline[0] == deadline_soustredeni[0]: |  | ||||||
| 				nejblizsi_deadline = deadline_soustredeni |  | ||||||
| 		except IndexError: |  | ||||||
| 			nejblizsi_deadline = (None, None) # neni zadna aktualni deadline |  | ||||||
| 
 |  | ||||||
| 		if nejblizsi_deadline[0] is not None: |  | ||||||
| 			context['nejblizsi_deadline'] = datetime.combine(nejblizsi_deadline[0], datetime.max.time()) |  | ||||||
| 		else: |  | ||||||
| 			context['nejblizsi_deadline'] = None |  | ||||||
| 
 |  | ||||||
| 		context['typ_deadline'] = nejblizsi_deadline[1] |  | ||||||
| 
 | 
 | ||||||
| 		# Aktuální témata | 		# Aktuální témata | ||||||
| 		nazvy_a_odkazy_na_aktualni_temata = [] | 		nazvy_a_odkazy_na_aktualni_temata = [] | ||||||
|  | @ -377,17 +355,10 @@ class RocnikView(generic.DetailView): | ||||||
| 		return get_object_or_404(queryset,rocnik=self.kwargs.get('rocnik')) | 		return get_object_or_404(queryset,rocnik=self.kwargs.get('rocnik')) | ||||||
| 
 | 
 | ||||||
| 	def get_context_data(self, **kwargs): | 	def get_context_data(self, **kwargs): | ||||||
| 		start = time.time() |  | ||||||
| 		context = super(RocnikView, self).get_context_data(**kwargs) | 		context = super(RocnikView, self).get_context_data(**kwargs) | ||||||
| 		context = vysledkovka_rocniku( | 		context["vysledkovka"] = VysledkovkaRocniku(context["rocnik"], True) | ||||||
| 			rocnik=context["rocnik"], | 		context["neprazdna_vysledkovka"] = len(context['vysledkovka'].cisla_rocniku) != 0 | ||||||
| 			context=context, | 		context["vysledkovka_neverejna"] = VysledkovkaRocniku(context["rocnik"], False) | ||||||
| 			request=self.request, |  | ||||||
| 			sneverejnou=True |  | ||||||
| 		) |  | ||||||
| 		end = time.time() |  | ||||||
| 		print("Kontext:", end-start) |  | ||||||
| 
 |  | ||||||
| 		return context | 		return context | ||||||
| 
 | 
 | ||||||
| def resiteleRocnikuCsvExportView(request, rocnik): | def resiteleRocnikuCsvExportView(request, rocnik): | ||||||
|  | @ -452,8 +423,23 @@ class CisloView(generic.DetailView): | ||||||
| 
 | 
 | ||||||
| 		cislo = context['cislo'] | 		cislo = context['cislo'] | ||||||
| 		context['prevcislo'] = Cislo.objects.filter((Q(rocnik__lt=self.object.rocnik) | Q(poradi__lt=self.object.poradi))&Q(rocnik__lte=self.object.rocnik)).first() | 		context['prevcislo'] = Cislo.objects.filter((Q(rocnik__lt=self.object.rocnik) | Q(poradi__lt=self.object.poradi))&Q(rocnik__lte=self.object.rocnik)).first() | ||||||
| 		# vrátíme context (aktuálně obsahuje jen věci ohledně výsledkovky | 
 | ||||||
| 		return vysledkovka_cisla(cislo, context) | 		deadliny = Deadline.objects.filter(cislo=cislo).reverse() | ||||||
|  | 		deadliny_s_vysledkovkami = [] | ||||||
|  | 
 | ||||||
|  | 		nadpisy = { | ||||||
|  | 			m.Deadline.TYP_CISLA: "Výsledkovka", | ||||||
|  | 			m.Deadline.TYP_PRVNI: "Výsledkovka do prvního deadlinu", | ||||||
|  | 			m.Deadline.TYP_PRVNI_A_SOUS: "Výsledkovka do prvního deadlinu a deadlinu pro účast na soustředění", | ||||||
|  | 			m.Deadline.TYP_SOUS: "Výsledkovka do deadlinu pro účast na soustředění", | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		for deadline in deadliny: | ||||||
|  | 			if self.request.user.je_org | deadline.verejna_vysledkovka: | ||||||
|  | 				deadliny_s_vysledkovkami.append((deadline, nadpisy[deadline.typ], VysledkovkaCisla(cislo, not self.request.user.je_org, deadline))) | ||||||
|  | 
 | ||||||
|  | 		context['deadliny_s_vysledkovkami'] = deadliny_s_vysledkovkami | ||||||
|  | 		return context | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class ArchivTemataView(generic.ListView): | class ArchivTemataView(generic.ListView): | ||||||
|  | @ -476,17 +462,41 @@ class OdmenyView(generic.TemplateView): | ||||||
| 		fromcislo = Cislo.objects.get(rocnik=self.kwargs.get('frocnik'), poradi=self.kwargs.get('fcislo')) | 		fromcislo = Cislo.objects.get(rocnik=self.kwargs.get('frocnik'), poradi=self.kwargs.get('fcislo')) | ||||||
| 		tocislo = Cislo.objects.get(rocnik=self.kwargs.get('trocnik'), poradi=self.kwargs.get('tcislo')) | 		tocislo = Cislo.objects.get(rocnik=self.kwargs.get('trocnik'), poradi=self.kwargs.get('tcislo')) | ||||||
| 		resitele = aktivniResitele(tocislo) | 		resitele = aktivniResitele(tocislo) | ||||||
| 		frombody = body_resitelu(resitele, fromcislo) | 
 | ||||||
| 		tobody = body_resitelu(resitele, tocislo) | 		def get_diff(from_deadline: Deadline, to_deadline: Deadline): | ||||||
| 		outlist = [] | 			frombody = body_resitelu(resitele=resitele, jen_verejne=False, do=from_deadline) | ||||||
| 		for (aid, tbody) in tobody.items(): | 			tobody = body_resitelu(resitele=resitele, jen_verejne=False, do=to_deadline) | ||||||
| 			fbody = frombody.get(aid,0) | 			outlist = [] | ||||||
| 			resitel = Resitel.objects.get(pk=aid) | 			for (aid, tbody) in tobody.items(): | ||||||
| 			ftitul = resitel.get_titul(fbody) | 				fbody = frombody.get(aid,0) | ||||||
| 			ttitul = resitel.get_titul(tbody) | 				resitel = Resitel.objects.get(pk=aid) | ||||||
| 			if ftitul != ttitul: | 				ftitul = resitel.get_titul(fbody) | ||||||
| 				outlist.append({'jmeno': resitel.osoba.plne_jmeno(), 'ftitul': ftitul, 'ttitul': ttitul}) | 				ttitul = resitel.get_titul(tbody) | ||||||
| 		context['zmeny'] = outlist | 				if ftitul != ttitul: | ||||||
|  | 					outlist.append({'jmeno': resitel.osoba.plne_jmeno(), 'ftitul': ftitul, 'ttitul': ttitul}) | ||||||
|  | 			return outlist | ||||||
|  | 
 | ||||||
|  | 		def posledni_deadline_oprava(cislo: Cislo) -> Deadline: | ||||||
|  | 			posledni_deadline = cislo.posledni_deadline | ||||||
|  | 			if posledni_deadline is None: | ||||||
|  | 				return Deadline.objects.filter(Q(cislo__poradi__lt=cislo.poradi, cislo__rocnik=cislo.rocnik) | Q(cislo__rocnik__rocnik__lt=cislo.rocnik.rocnik)).order_by("deadline").last() | ||||||
|  | 			return posledni_deadline | ||||||
|  | 
 | ||||||
|  | 		context["from_cislo"] = fromcislo | ||||||
|  | 		context["to_cislo"] = tocislo | ||||||
|  | 		context["zmeny_prvni_prvni"] = get_diff( | ||||||
|  | 			fromcislo.zlomovy_deadline_pro_papirove_cislo(), | ||||||
|  | 			tocislo.zlomovy_deadline_pro_papirove_cislo() | ||||||
|  | 		) | ||||||
|  | 		context["zmeny_prvni_posledni"] = get_diff( | ||||||
|  | 			fromcislo.zlomovy_deadline_pro_papirove_cislo(), | ||||||
|  | 			posledni_deadline_oprava(tocislo) | ||||||
|  | 		) | ||||||
|  | 		context["zmeny_posledni_prvni"] = get_diff( | ||||||
|  | 			posledni_deadline_oprava(fromcislo), | ||||||
|  | 			tocislo.zlomovy_deadline_pro_papirove_cislo() | ||||||
|  | 		) | ||||||
|  | 
 | ||||||
| 		return context | 		return context | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | @ -504,6 +514,60 @@ class CisloVysledkovkaView(CisloView): | ||||||
| 	content_type = 'text/plain; charset=UTF8' | 	content_type = 'text/plain; charset=UTF8' | ||||||
| 	#vypise na stranku textovy obsah vyTeXane vysledkovky k okopirovani | 	#vypise na stranku textovy obsah vyTeXane vysledkovky k okopirovani | ||||||
| 
 | 
 | ||||||
|  | 	def get_context_data(self, **kwargs): | ||||||
|  | 		context = super(CisloVysledkovkaView, self).get_context_data() | ||||||
|  | 		cislo = context['cislo'] | ||||||
|  | 
 | ||||||
|  | 		cislopred = cislo.predchozi() | ||||||
|  | 		if cislopred is not None: | ||||||
|  | 			context['vysledkovka'] = VysledkovkaDoTeXu( | ||||||
|  | 				cislo, | ||||||
|  | 				od_vyjma=cislopred.zlomovy_deadline_pro_papirove_cislo(), | ||||||
|  | 				do_vcetne=cislo.zlomovy_deadline_pro_papirove_cislo(), | ||||||
|  | 			) | ||||||
|  | 		else: | ||||||
|  | 			context['vysledkovka'] = VysledkovkaCisla( | ||||||
|  | 				cislo, | ||||||
|  | 				jen_verejne=False, | ||||||
|  | 				do_deadlinu=cislo.zlomovy_deadline_pro_papirove_cislo(), | ||||||
|  | 			) | ||||||
|  | 		return context | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | # Podle předchozího | ||||||
|  | class PosledniCisloVysledkovkaView(generic.DetailView): | ||||||
|  | 	"""View vytvořené pro zobrazení výsledkovky posledního čísla v TeXu.""" | ||||||
|  | 
 | ||||||
|  | 	model = Rocnik | ||||||
|  | 	template_name = 'seminar/archiv/cislo_vysledkovka.tex' | ||||||
|  | 	content_type = 'text/plain; charset=UTF8' | ||||||
|  | 
 | ||||||
|  | 	def get_object(self, queryset=None): | ||||||
|  | 		if queryset is None: | ||||||
|  | 			queryset = self.get_queryset() | ||||||
|  | 		rocnik_arg = self.kwargs.get('rocnik') | ||||||
|  | 		queryset = queryset.filter(rocnik=rocnik_arg) | ||||||
|  | 
 | ||||||
|  | 		try: | ||||||
|  | 			obj = queryset.get() | ||||||
|  | 		except queryset.model.DoesNotExist: | ||||||
|  | 			raise Http404(_("No %(verbose_name)s found matching the query") % | ||||||
|  | 						{'verbose_name': queryset.model._meta.verbose_name}) | ||||||
|  | 		return obj | ||||||
|  | 
 | ||||||
|  | 	def get_context_data(self, **kwargs): | ||||||
|  | 		context = super(PosledniCisloVysledkovkaView, self).get_context_data() | ||||||
|  | 		rocnik = context['rocnik'] | ||||||
|  | 		cislo = rocnik.cisla.order_by("poradi").last() | ||||||
|  | 		cislopred = cislo.predchozi() | ||||||
|  | 		context['vysledkovka'] = VysledkovkaDoTeXu( | ||||||
|  | 			cislo, | ||||||
|  | 			od_vyjma=cislopred.zlomovy_deadline_pro_papirove_cislo(), | ||||||
|  | 			do_vcetne=cislo.deadline_v_cisle.order_by("deadline").last(), | ||||||
|  | 		) | ||||||
|  | 		return context | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
| class RocnikVysledkovkaView(RocnikView): | class RocnikVysledkovkaView(RocnikView): | ||||||
| 	""" View vytvořené pro stránku zobrazující výsledkovku ročníku v TeXu.""" | 	""" View vytvořené pro stránku zobrazující výsledkovku ročníku v TeXu.""" | ||||||
| 	model = Rocnik | 	model = Rocnik | ||||||
|  | @ -556,17 +620,23 @@ def oldObalkovaniView(request, rocnik, cislo): | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| ### Tituly | ### Tituly | ||||||
|  | def TitulyViewRocnik(request, rocnik): | ||||||
|  | 	return TitulyView(request, rocnik, None) | ||||||
|  | 
 | ||||||
| 
 | 
 | ||||||
| def TitulyView(request, rocnik, cislo): | def TitulyView(request, rocnik, cislo): | ||||||
| 	""" View pro stažení makra titulů v TeXu.""" | 	""" View pro stažení makra titulů v TeXu.""" | ||||||
| 	rocnik_obj = Rocnik.objects.get(rocnik = rocnik) | 	rocnik_obj = Rocnik.objects.get(rocnik = rocnik) | ||||||
| 	resitele = Resitel.objects.filter(rok_maturity__gte = rocnik_obj.prvni_rok) | 	resitele = Resitel.objects.filter(rok_maturity__gte = rocnik_obj.prvni_rok) | ||||||
| 	cislo_obj = Cislo.objects.get(rocnik = rocnik_obj, poradi = cislo) |  | ||||||
| 
 | 
 | ||||||
| 	asciijmena = [] | 	asciijmena = [] | ||||||
| 	jmenovci = False # detekuje, zda jsou dva řešitelé jmenovci (modulo nabodeníčka), | 	jmenovci = False # detekuje, zda jsou dva řešitelé jmenovci (modulo nabodeníčka), | ||||||
| 		# pokud ano, vrátí se jako true | 		# pokud ano, vrátí se jako true | ||||||
| 	slovnik_s_body = body_resitelu(resitele, cislo_obj) | 	if cislo is not None: | ||||||
|  | 		cislo_obj = Cislo.objects.get(rocnik=rocnik_obj, poradi=cislo) | ||||||
|  | 		slovnik_s_body = body_resitelu(do=cislo_obj.zlomovy_deadline_pro_papirove_cislo(), jen_verejne=False) | ||||||
|  | 	else: | ||||||
|  | 		slovnik_s_body = body_resitelu(do=Deadline.objects.filter(cislo__rocnik=rocnik_obj).last(), jen_verejne=False) | ||||||
| 
 | 
 | ||||||
| 	for resitel in resitele: | 	for resitel in resitele: | ||||||
| 		resitel.titul = resitel.get_titul(slovnik_s_body[resitel.id]) | 		resitel.titul = resitel.get_titul(slovnik_s_body[resitel.id]) | ||||||
|  |  | ||||||
|  | @ -3,21 +3,21 @@ | ||||||
|     <tr class='border-b'> |     <tr class='border-b'> | ||||||
|         <th class='border-r'># |         <th class='border-r'># | ||||||
|         <th class='border-r'>Jméno |         <th class='border-r'>Jméno | ||||||
|             {% for p in problemy %} |             {% for p in vysledkovka.temata_a_spol%} | ||||||
|                 <th class='border-r' id="problem{{ forloop.counter0 }}">{# <a href="{{ p.verejne_url }}"> #}{{ p.kod_v_rocniku }}{# </a> #} |                 <th class='border-r' id="problem{{ oznaceni_vysledkovky }}_{{ forloop.counter0 }}">{# <a href="{{ p.verejne_url }}"> #}{{ p.kod_v_rocniku }}{# </a> #} | ||||||
| 
 | 
 | ||||||
|                     {# TODELETE #} |                     {# TODELETE #} | ||||||
|                     {% for podproblemy in podproblemy_iter.next %} |                     {% for podproblemy in vysledkovka.podproblemy_iter.next %} | ||||||
|                         <th class='border-r podproblem{{ forloop.parentloop.counter0 }} podproblem'>{# <a href="{{ podproblemy.verejne_url }}"> #}{{ podproblemy.kod_v_rocniku }}{# </a> #} |                         <th class='border-r podproblem{{ oznaceni_vysledkovky }}_{{ forloop.parentloop.counter0 }} podproblem'>{# <a href="{{ podproblemy.verejne_url }}"> #}{{ podproblemy.kod_v_rocniku }}{# </a> #} | ||||||
|                     {% endfor %} |                     {% endfor %} | ||||||
|                 {# TODELETE #} |                 {# TODELETE #} | ||||||
| 
 | 
 | ||||||
|             {% endfor %} |             {% endfor %} | ||||||
|         {% if ostatni %}<th class='border-r' id='problem{{ problemy | length }}'>Ostatní {% endif %} |         {% if vysledkovka.je_nejake_ostatni %}<th class='border-r' id='problem{{ oznaceni_vysledkovky }}_{{ vysledkovka.temata_a_spol| length }}'>Ostatní {% endif %} | ||||||
| 
 | 
 | ||||||
|         {# TODELETE #} |         {# TODELETE #} | ||||||
|         {% for podproblemy in podproblemy_iter.next %} |         {% for podproblemy in vysledkovka.podproblemy_iter.next %} | ||||||
|             <th class='border-r podproblem{{ problemy | length }} podproblem'>{# <a href="{{ podproblemy.verejne_url }}"> #}{{ podproblemy.kod_v_rocniku }}{# </a> #} |             <th class='border-r podproblem{{ oznaceni_vysledkovky }}_{{ vysledkovka.temata_a_spol| length }} podproblem'>{# <a href="{{ podproblemy.verejne_url }}"> #}{{ podproblemy.kod_v_rocniku }}{# </a> #} | ||||||
|         {% endfor %} |         {% endfor %} | ||||||
|         {# TODELETE #} |         {# TODELETE #} | ||||||
| 
 | 
 | ||||||
|  | @ -25,7 +25,7 @@ | ||||||
|         <th class='border-r'>Za číslo |         <th class='border-r'>Za číslo | ||||||
|         <th class='border-r'>Za ročník |         <th class='border-r'>Za ročník | ||||||
|         <th class='border-r'>Odjakživa |         <th class='border-r'>Odjakživa | ||||||
|             {% for rv in radky_vysledkovky %} |             {% for rv in vysledkovka.radky_vysledkovky %} | ||||||
|                 <tr> |                 <tr> | ||||||
|                     <td class='border-r'>{% autoescape off %}{{ rv.poradi }}{% endautoescape %} |                     <td class='border-r'>{% autoescape off %}{{ rv.poradi }}{% endautoescape %} | ||||||
|                     <th class='border-r'> |                     <th class='border-r'> | ||||||
|  | @ -33,14 +33,12 @@ | ||||||
|                             {{ rv.titul }}<sup>MM</sup> |                             {{ rv.titul }}<sup>MM</sup> | ||||||
|                         {% endif %} |                         {% endif %} | ||||||
|                         {{ rv.resitel.osoba.plne_jmeno }} |                         {{ rv.resitel.osoba.plne_jmeno }} | ||||||
|                         {% for b in rv.body_problemy_sezn %} |                         {% for b in rv.body_za_temata_seznam %} | ||||||
|                             <td class='border-r'>{{ b }} |                             <td class='border-r'>{{ b }} | ||||||
| 
 | 
 | ||||||
|                                 {# TODELETE #} |  | ||||||
|                                 {% for body_podproblemu in rv.body_podproblemy_iter.next %} |                                 {% for body_podproblemu in rv.body_podproblemy_iter.next %} | ||||||
|                                     <td class='border-r podproblem{{ forloop.parentloop.counter0 }} podproblem'>{{ body_podproblemu }} |                                     <td class='border-r podproblem{{ oznaceni_vysledkovky }}_{{ forloop.parentloop.counter0 }} podproblem'>{{ body_podproblemu }} | ||||||
|                                 {% endfor %} |                                 {% endfor %} | ||||||
|                             {# TODELETE #} |  | ||||||
| 
 | 
 | ||||||
|                         {% endfor %} |                         {% endfor %} | ||||||
|                     <td class='border-r'>{{ rv.body_cislo }} |                     <td class='border-r'>{{ rv.body_cislo }} | ||||||
|  | @ -55,29 +53,29 @@ | ||||||
| 
 | 
 | ||||||
| {# TODELETE #} | {# TODELETE #} | ||||||
| <script> | <script> | ||||||
|     {% for p in problemy %} |     {% for p in vysledkovka.temata_a_spol%} | ||||||
|         diplayed{{ forloop.counter0 }} = false; |         displayed{{ oznaceni_vysledkovky }}_{{ forloop.counter0 }} = false; | ||||||
|         $(".podproblem{{ forloop.counter0 }}").css("display", "none") |         $(".podproblem{{ oznaceni_vysledkovky }}_{{ forloop.counter0 }}").css("display", "none") | ||||||
|         $("#problem{{ forloop.counter0 }}")[0].addEventListener('click', podproblem{{ forloop.counter0 }}); |         $("#problem{{ oznaceni_vysledkovky }}_{{ forloop.counter0 }}")[0].addEventListener('click', podproblem{{ oznaceni_vysledkovky }}_{{ forloop.counter0 }}); | ||||||
|         function podproblem{{ forloop.counter0 }}(event) { |         function podproblem{{ oznaceni_vysledkovky }}_{{ forloop.counter0 }}(event) { | ||||||
|             diplayed{{ forloop.counter0 }} = !diplayed{{ forloop.counter0 }}; |             displayed{{ oznaceni_vysledkovky }}_{{ forloop.counter0 }} = !displayed{{ oznaceni_vysledkovky }}_{{ forloop.counter0 }}; | ||||||
|             if (diplayed{{ forloop.counter0 }}) { |             if (displayed{{ oznaceni_vysledkovky }}_{{ forloop.counter0 }}) { | ||||||
|                 $(".podproblem{{ forloop.counter0 }}").css("display", ""); |                 $(".podproblem{{ oznaceni_vysledkovky }}_{{ forloop.counter0 }}").css("display", ""); | ||||||
|             } else { |             } else { | ||||||
|                 $(".podproblem{{ forloop.counter0 }}").css("display", "none"); |                 $(".podproblem{{ oznaceni_vysledkovky }}_{{ forloop.counter0 }}").css("display", "none"); | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|     {% endfor %} |     {% endfor %} | ||||||
|     {% if ostatni %} |     {% if vysledkovka.je_nejake_ostatni %} | ||||||
|         diplayed{{ problemy | length }} = false; |         displayed{{ oznaceni_vysledkovky }}_{{ vysledkovka.temata_a_spol| length }} = false; | ||||||
|         $(".podproblem{{ problemy | length }}").css("display", "none") |         $(".podproblem{{ oznaceni_vysledkovky }}_{{ vysledkovka.temata_a_spol| length }}").css("display", "none") | ||||||
|         $("#problem{{ problemy | length }}")[0].addEventListener('click', podproblem{{ problemy | length }}); |         $("#problem{{ oznaceni_vysledkovky }}_{{ vysledkovka.temata_a_spol| length }}")[0].addEventListener('click', podproblem{{ oznaceni_vysledkovky }}_{{ vysledkovka.temata_a_spol| length }}); | ||||||
|         function podproblem{{ problemy | length }}(event) { |         function podproblem{{ oznaceni_vysledkovky }}_{{ vysledkovka.temata_a_spol| length }}(event) { | ||||||
|             diplayed{{ problemy | length }} = !diplayed{{ problemy | length }}; |             displayed{{ oznaceni_vysledkovky }}_{{ vysledkovka.temata_a_spol| length }} = !displayed{{ oznaceni_vysledkovky }}_{{ vysledkovka.temata_a_spol| length }}; | ||||||
|             if (diplayed{{ problemy | length }}) { |             if (displayed{{ oznaceni_vysledkovky }}_{{ vysledkovka.temata_a_spol| length }}) { | ||||||
|                 $(".podproblem{{ problemy | length }}").css("display", ""); |                 $(".podproblem{{ oznaceni_vysledkovky }}_{{ vysledkovka.temata_a_spol| length }}").css("display", ""); | ||||||
|             } else { |             } else { | ||||||
|                 $(".podproblem{{ problemy | length }}").css("display", "none"); |                 $(".podproblem{{ oznaceni_vysledkovky }}_{{ vysledkovka.temata_a_spol| length }}").css("display", "none"); | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|     {% endif %} |     {% endif %} | ||||||
|  |  | ||||||
|  | @ -4,13 +4,13 @@ | ||||||
|     <th class='border-r'>Jméno |     <th class='border-r'>Jméno | ||||||
|     <th class='border-r'>R. |     <th class='border-r'>R. | ||||||
|     <th class='border-r'>Odjakživa |     <th class='border-r'>Odjakživa | ||||||
|         {% for c in cisla %} |         {% for c in vysledkovka.cisla_rocniku %} | ||||||
|     <th class='border-r'><a href="{{ c.verejne_url }}"> |     <th class='border-r'><a href="{{ c.verejne_url }}"> | ||||||
|             {{c.rocnik.rocnik}}.{{ c.poradi }}</a> |             {{c.rocnik.rocnik}}.{{ c.poradi }}</a> | ||||||
|     {% endfor %} |     {% endfor %} | ||||||
|     <th class='border-r'>Celkem |     <th class='border-r'>Celkem | ||||||
| 
 | 
 | ||||||
| {% for rv in radky_vysledkovky %} | {% for rv in vysledkovka.radky_vysledkovky %} | ||||||
|   <tr> |   <tr> | ||||||
|     <td class='border-r'>{% autoescape off %}{{ rv.poradi }}{% endautoescape %} |     <td class='border-r'>{% autoescape off %}{{ rv.poradi }}{% endautoescape %} | ||||||
|     <th class='border-r'> |     <th class='border-r'> | ||||||
|  | @ -20,7 +20,7 @@ | ||||||
|       {{ rv.resitel.osoba.plne_jmeno }} |       {{ rv.resitel.osoba.plne_jmeno }} | ||||||
|     <td class='border-r'>{{ rv.rocnik_resitele }} |     <td class='border-r'>{{ rv.rocnik_resitele }} | ||||||
|     <td class='border-r'>{{ rv.body_celkem_odjakziva }} |     <td class='border-r'>{{ rv.body_celkem_odjakziva }} | ||||||
|     {% for b in rv.body_cisla_sezn %} |     {% for b in rv.body_cisla_seznam %} | ||||||
|     <td class='border-r'>{{ b }} |     <td class='border-r'>{{ b }} | ||||||
|     {% endfor %} |     {% endfor %} | ||||||
|     <td class='border-r'><b>{{ rv.body_rocnik }}</b> |     <td class='border-r'><b>{{ rv.body_rocnik }}</b> | ||||||
|  |  | ||||||
|  | @ -1 +0,0 @@ | ||||||
| {% include "vysledkovky/vysledkovka_rocnik.html" with radky_vysledkovky=radky_vysledkovky_s_neverejnymi cisla=cisla_s_neverejnymi %} |  | ||||||
|  | @ -1,462 +1,492 @@ | ||||||
|  | import abc | ||||||
|  | from functools import cached_property | ||||||
|  | from typing import Union # TODO: s pythonem 3.10 přepsat na '|' | ||||||
|  | 
 | ||||||
| import seminar.models as m | import seminar.models as m | ||||||
| from django.db.models import Q, Sum, Count | from django.db.models import Q, Sum | ||||||
| from seminar.utils import aktivniResitele, resi_v_rocniku, cisla_rocniku, hlavni_problem, hlavni_problemy_f, problemy_cisla, podproblemy_v_cislu | from seminar.utils import resi_v_rocniku | ||||||
| import time |  | ||||||
| 
 | 
 | ||||||
| ROCNIK_ZRUSENI_TEMAT = 25 | ROCNIK_ZRUSENI_TEMAT = 25 | ||||||
| 
 | 
 | ||||||
| def sloupec_s_poradim(setrizene_body): |  | ||||||
| 	""" |  | ||||||
| 	Ze seznamu obsahujícího sestupně setřízené body řešitelů za daný ročník  |  | ||||||
| 	vytvoří seznam s pořadími (včetně 3.-5. a pak 2 volná místa atp.), |  | ||||||
| 	podle toho, jak jdou za sebou ve výsledkovce. |  | ||||||
| 	Parametr: |  | ||||||
| 		setrizene_body (seznam integerů): sestupně setřízená čísla |  | ||||||
| 
 | 
 | ||||||
| 	Výstup: |  | ||||||
| 		sloupec_s_poradim (seznam stringů) |  | ||||||
| 	""" |  | ||||||
| 
 |  | ||||||
| 	# ze seznamu obsahujícího setřízené body spočítáme sloupec s pořadím	 |  | ||||||
| 	aktualni_poradi = 1 |  | ||||||
| 	sloupec_s_poradim = [] |  | ||||||
| 
 |  | ||||||
| 	# seskupíme seznam všech bodů podle hodnot |  | ||||||
| 	for index in range(0, len(setrizene_body)): |  | ||||||
| 		# pokud je pořadí větší než číslo řádku, tak jsme vypsali větší rozsah a chceme |  | ||||||
| 		# vypsat už jen prázdné místo, než dojdeme na správný řádek |  | ||||||
| 		if (index + 1) < aktualni_poradi: |  | ||||||
| 			sloupec_s_poradim.append("") |  | ||||||
| 			continue |  | ||||||
| 		velikost_skupiny = 0 |  | ||||||
| 		# zjistíme počet po sobě jdoucích stejných hodnot |  | ||||||
| 		while setrizene_body[index] == setrizene_body[index + velikost_skupiny]: |  | ||||||
| 			velikost_skupiny = velikost_skupiny + 1 |  | ||||||
| 			# na konci musíme ošetřit přetečení seznamu |  | ||||||
| 			if (index + velikost_skupiny) > len(setrizene_body) - 1: |  | ||||||
| 				break |  | ||||||
| 		# pokud je velikost skupiny 1, vypíšu pořadí |  | ||||||
| 		if velikost_skupiny == 1: |  | ||||||
| 			sloupec_s_poradim.append("{}.".format(aktualni_poradi)) |  | ||||||
| 		# pokud je skupina větší, vypíšu rozsah |  | ||||||
| 		else: |  | ||||||
| 			sloupec_s_poradim.append("{}.–{}.".format(aktualni_poradi,  |  | ||||||
| 						aktualni_poradi+velikost_skupiny-1)) |  | ||||||
| 		# zvětšíme aktuální pořadí o tolik, kolik pozic bylo přeskočeno |  | ||||||
| 		aktualni_poradi = aktualni_poradi + velikost_skupiny |  | ||||||
| 	return sloupec_s_poradim |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| def body_resitelu(resitele, za, odjakziva=True, jen_verejne=False): |  | ||||||
| 	""" Funkce počítající počty bodů pro zadané řešitele,  |  | ||||||
| 	buď odjakživa do daného ročníku/čísla anebo za daný ročník/číslo. |  | ||||||
| 	Parametry: |  | ||||||
| 		resitele (seznam obsahující položky typu Resitel): aktivní řešitelé |  | ||||||
| 		za (Rocnik/Cislo): za co se mají počítat body  |  | ||||||
| 			(generování starších výsledkovek) |  | ||||||
| 		odjakziva (bool): zda se mají počítat body odjakživa, nebo jen za číslo/ročník |  | ||||||
| 			zadané v "za" |  | ||||||
| 	Výstup: |  | ||||||
| 		slovník (Resitel.id):body |  | ||||||
| 	"""		 |  | ||||||
| 	resitele_id = [r.id for r in resitele] |  | ||||||
| 	# Zjistíme, typ objektu v parametru "za" |  | ||||||
| 	if isinstance(za, m.Rocnik): |  | ||||||
| 		cislo = None |  | ||||||
| 		rocnik = za |  | ||||||
| 		rok = rocnik.prvni_rok |  | ||||||
| 	elif isinstance(za, m.Cislo): |  | ||||||
| 		cislo = za |  | ||||||
| 		rocnik = None |  | ||||||
| 		rok = cislo.rocnik.prvni_rok |  | ||||||
| 	else: |  | ||||||
| 		assert True, "body_resitelu: za není ani číslo ani ročník."	 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 	# Kvůli rychlosti používáme sčítáme body už v databázi, viz  |  | ||||||
| 	# https://docs.djangoproject.com/en/3.0/topics/db/aggregation/,  |  | ||||||
| 	# sekce Filtering on annotations (protože potřebujeme filtrovat výsledky |  | ||||||
| 	# jen do nějakého data, abychom uměli správně nagenerovat výsledkovky i  |  | ||||||
| 	# za historická čísla. |  | ||||||
| 
 |  | ||||||
| 	# Níže se vytváří dotaz na součet bodů za správně vyfiltrovaná hodnocení,  |  | ||||||
| 	# který se použije ve výsledném dotazu. |  | ||||||
| 	if cislo and odjakziva: # Body se sčítají odjakživa do zadaného čísla. |  | ||||||
| 		# Vyfiltrujeme všechna hodnocení, která jsou buď ze starších ročníků,  |  | ||||||
| 		# anebo ze stejného ročníku, jak je zadané číslo, tam ale sčítáme jen |  | ||||||
| 		# pro čísla s pořadím nejvýše stejným, jako má zadané číslo. |  | ||||||
| 		body_k_zapocteni = Sum('reseni__hodnoceni__body',  |  | ||||||
| 			filter=( Q(reseni__hodnoceni__cislo_body__rocnik__prvni_rok__lt=rok) | |  | ||||||
| 				 Q(reseni__hodnoceni__cislo_body__rocnik__prvni_rok=rok, |  | ||||||
| 				   reseni__hodnoceni__cislo_body__poradi__lte=cislo.poradi) )) |  | ||||||
| 	elif cislo and not odjakziva: # Body se sčítají za dané číslo. |  | ||||||
| 		body_k_zapocteni = Sum('reseni__hodnoceni__body', |  | ||||||
| 			filter=( Q(reseni__hodnoceni__cislo_body__rocnik__prvni_rok=rok, |  | ||||||
| 					reseni__hodnoceni__cislo_body__poradi__lte=cislo.poradi) )) |  | ||||||
| 	elif rocnik and odjakziva: # Spočítáme body za starší ročníky až do zadaného včetně. |  | ||||||
| 		if jen_verejne: |  | ||||||
| 			body_k_zapocteni = Sum('reseni__hodnoceni__body', |  | ||||||
| 									filter= Q(reseni__hodnoceni__cislo_body__rocnik__prvni_rok__lte=rok, |  | ||||||
| 											reseni__hodnoceni__cislo_body__verejna_vysledkovka=True)) |  | ||||||
| 		else: |  | ||||||
| 			body_k_zapocteni = Sum('reseni__hodnoceni__body', |  | ||||||
| 				filter= Q(reseni__hodnoceni__cislo_body__rocnik__prvni_rok__lte=rok)) |  | ||||||
| 	elif rocnik and not odjakziva: # Spočítáme body za daný ročník. |  | ||||||
| 		if jen_verejne: |  | ||||||
| 			body_k_zapocteni = Sum('reseni__hodnoceni__body', |  | ||||||
| 								   filter=Q(reseni__hodnoceni__cislo_body__rocnik=rocnik, |  | ||||||
| 											reseni__hodnoceni__cislo_body__verejna_vysledkovka=True)) |  | ||||||
| 		else: |  | ||||||
| 			body_k_zapocteni = Sum('reseni__hodnoceni__body', |  | ||||||
| 								   filter=Q(reseni__hodnoceni__cislo_body__rocnik=rocnik)) |  | ||||||
| 	else:  |  | ||||||
| 		assert True, "body_resitelu: Neplatná kombinace za a odjakživa." |  | ||||||
| 
 |  | ||||||
| 	# Následující řádek přidá ke každému řešiteli údaj ".body" se součtem jejich bodů |  | ||||||
| 	resitele_s_body = m.Resitel.objects.filter(id__in=resitele_id).annotate( |  | ||||||
| 		body=body_k_zapocteni) |  | ||||||
| 	# Teď jen z QuerySetu řešitelů anotovaných body vygenerujeme slovník  |  | ||||||
| 	# indexovaný řešitelským id obsahující body.  |  | ||||||
| 	# Pokud jsou body None, nahradíme za 0. |  | ||||||
| 	slovnik = {int(res.id) : (res.body if res.body else 0) for res in resitele_s_body} |  | ||||||
| 	return slovnik |  | ||||||
| 
 |  | ||||||
| class RadekVysledkovkyRocniku(object): |  | ||||||
| 	""" Obsahuje věci, které se hodí vědět při konstruování výsledkovky. |  | ||||||
| 	Umožňuje snazší práci v templatu (lepší, než seznam).""" |  | ||||||
| 
 |  | ||||||
| 	def __init__(self, poradi, resitel, body_cisla_sezn, body_rocnik, body_odjakziva, rok): |  | ||||||
| 		self.poradi = poradi |  | ||||||
| 		self.resitel = resitel |  | ||||||
| 		self.rocnik_resitele = resitel.rocnik(rok) |  | ||||||
| 		self.body_rocnik = body_rocnik |  | ||||||
| 		self.body_celkem_odjakziva = body_odjakziva |  | ||||||
| 		self.body_cisla_sezn = body_cisla_sezn |  | ||||||
| 		self.titul = resitel.get_titul(body_odjakziva) |  | ||||||
| 
 |  | ||||||
| def setrid_resitele_a_body(slov_resitel_body): |  | ||||||
| 	setrizeni_resitele_id = [dvojice[0] for dvojice in slov_resitel_body] |  | ||||||
| 	setrizene_body = [dvojice[1] for dvojice in slov_resitel_body] |  | ||||||
| 	return setrizeni_resitele_id,  setrizene_body |  | ||||||
| 
 |  | ||||||
| def data_vysledkovky_rocniku(rocnik, jen_verejne=True): |  | ||||||
| 	""" Přebírá ročník (např. context["rocnik"]) a vrací výsledkovou listinu ve |  | ||||||
| 	formě vhodné pro šablonu "seminar/vysledkovka_rocniku.html" |  | ||||||
| 	""" |  | ||||||
| 	 |  | ||||||
| 	start = time.time() |  | ||||||
| 
 |  | ||||||
| 	## TODO možná chytřeji vybírat aktivní řešitele |  | ||||||
| 	# aktivní řešitelé - chceme letos něco poslal, TODO později vyfiltrujeme ty, kdo mají  |  | ||||||
| 	# u alespoň jedné hodnoty něco jiného než NULL |  | ||||||
| 	aktivni_resitele = list(resi_v_rocniku(rocnik)) |  | ||||||
| 	cisla = cisla_rocniku(rocnik, jen_verejne) |  | ||||||
| 	body_cisla_slov = {} |  | ||||||
| 	for cislo in cisla: |  | ||||||
| 		# získáme body za číslo |  | ||||||
| 		_, cislobody = secti_body_za_cislo(cislo, aktivni_resitele) |  | ||||||
| 		body_cisla_slov[cislo.id] = cislobody |  | ||||||
| 
 |  | ||||||
| 	# získáme body za ročník, seznam obsahuje dvojice (řešitel_id, body) setřízené sestupně |  | ||||||
| 	resitel_rocnikbody_sezn = secti_body_za_rocnik(rocnik, aktivni_resitele, jen_verejne=jen_verejne) |  | ||||||
| 
 |  | ||||||
| 	# setřídíme řešitele podle počtu bodů a získáme seznam s body od nejvyšších po nenižší |  | ||||||
| 	setrizeni_resitele_id, setrizene_body = setrid_resitele_a_body(resitel_rocnikbody_sezn) |  | ||||||
| 	poradi = sloupec_s_poradim(setrizene_body) |  | ||||||
| 
 |  | ||||||
| 	# získáme body odjakživa |  | ||||||
| 	resitel_odjakzivabody_slov = body_resitelu(aktivni_resitele, rocnik, jen_verejne=jen_verejne) |  | ||||||
| 
 |  | ||||||
| 	# vytvoříme jednotlivé sloupce výsledkovky |  | ||||||
| 	radky_vysledkovky = [] |  | ||||||
| 	i = 0 |  | ||||||
| 	setrizeni_resitele_dict = {} # Tento slovnik se vyrab |  | ||||||
| 	for r in m.Resitel.objects.filter(id__in=setrizeni_resitele_id).select_related('osoba'): |  | ||||||
| 		setrizeni_resitele_dict[r.id] = r |  | ||||||
| 	 |  | ||||||
| 	for ar_id in setrizeni_resitele_id: |  | ||||||
| 		# seznam počtu bodů daného řešitele pro jednotlivá čísla |  | ||||||
| 		body_cisla_sezn = [] |  | ||||||
| 		for cislo in cisla: |  | ||||||
| 			body_cisla_sezn.append(body_cisla_slov[cislo.id][ar_id]) |  | ||||||
| 
 |  | ||||||
| 		# vytáhneme informace pro daného řešitele |  | ||||||
| 		radek = RadekVysledkovkyRocniku( |  | ||||||
| 			poradi[i], # pořadí |  | ||||||
| 			setrizeni_resitele_dict[ar_id], # řešitel (z id) |  | ||||||
| 			body_cisla_sezn, # seznam bodů za čísla |  | ||||||
| 			setrizene_body[i], # body za ročník (spočítané výše s pořadím) |  | ||||||
| 			resitel_odjakzivabody_slov[ar_id], # body odjakživa |  | ||||||
| 			rocnik) # ročník semináře pro získání ročníku řešitele |  | ||||||
| 		radky_vysledkovky.append(radek) |  | ||||||
| 		i += 1 |  | ||||||
| 
 |  | ||||||
| 	end = time.time() |  | ||||||
| 	print("Vysledkovka rocniku",end-start) |  | ||||||
| 
 |  | ||||||
| 	radky_vysledkovky = [radek for radek in radky_vysledkovky if radek.body_rocnik > 0] |  | ||||||
| 	return radky_vysledkovky, cisla |  | ||||||
| 
 |  | ||||||
| class RadekVysledkovkyCisla(object): |  | ||||||
| 	"""Obsahuje věci, které se hodí vědět při konstruování výsledkovky. |  | ||||||
| 	Umožňuje snazší práci v templatu (lepší, než seznam).""" |  | ||||||
| 
 |  | ||||||
| 	def __init__(self, poradi, resitel, body_problemy_sezn, |  | ||||||
| 				body_cislo, body_rocnik, body_odjakziva, rok, body_podproblemy, body_podproblemy_iter): |  | ||||||
| 		self.resitel = resitel |  | ||||||
| 		self.rocnik_resitele = resitel.rocnik(rok) |  | ||||||
| 		self.body_cislo = body_cislo |  | ||||||
| 		self.body_rocnik = body_rocnik |  | ||||||
| 		self.body_celkem_odjakziva = body_odjakziva |  | ||||||
| 		self.poradi = poradi |  | ||||||
| 		self.body_problemy_sezn = body_problemy_sezn |  | ||||||
| 		self.titul = resitel.get_titul(body_odjakziva) |  | ||||||
| 		self.body_podproblemy = body_podproblemy |  | ||||||
| 		self.body_podproblemy_iter = body_podproblemy_iter  # TODELETE |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| def pricti_body(slovnik, resitel, body): |  | ||||||
| 	""" Přiřazuje danému řešiteli body do slovníku. """ |  | ||||||
| 	# testujeme na None (""), pokud je to první řešení  |  | ||||||
| 	# daného řešitele, předěláme na 0 |  | ||||||
| 	# (v dalším kroku přičteme reálný počet bodů), |  | ||||||
| 	# rozlišujeme tím mezi 0 a neodevzdaným řešením |  | ||||||
| 
 |  | ||||||
| 	# Speciálně pokud jsou body None (hodnocení není obodované), vraťse |  | ||||||
| 	# TODO nejde to udělat lépe? |  | ||||||
| 	if body is None: |  | ||||||
| 		return |  | ||||||
| 
 |  | ||||||
| 	if slovnik[resitel.id] == "": |  | ||||||
| 		slovnik[resitel.id] = 0 |  | ||||||
| 	 |  | ||||||
| 	slovnik[resitel.id] += body |  | ||||||
| 
 |  | ||||||
| def secti_body_za_rocnik(za, aktivni_resitele, jen_verejne): |  | ||||||
| 	""" Spočítá body za ročník (celý nebo do daného čísla),  |  | ||||||
| 		setřídí je sestupně a vrátí jako seznam. |  | ||||||
| 	Parametry: |  | ||||||
| 		za (typu Rocnik nebo Cislo)	spočítá za ročník, nebo za ročník až do  |  | ||||||
| 						daného čísla |  | ||||||
| 	""" |  | ||||||
| 	# spočítáme všem řešitelům jejich body za ročník (False => ne odjakživa) |  | ||||||
| 	resitel_rocnikbody_slov = body_resitelu(aktivni_resitele, za, False, jen_verejne=jen_verejne) |  | ||||||
| 	# zeptáme se na dvojice (řešitel, body) za ročník a setřídíme sestupně |  | ||||||
| 	resitel_rocnikbody_sezn = sorted(resitel_rocnikbody_slov.items(), |  | ||||||
| 					key = lambda x: x[1], reverse = True) |  | ||||||
| 	return resitel_rocnikbody_sezn |  | ||||||
| 
 |  | ||||||
| def secti_body_za_cislo(cislo, aktivni_resitele, hlavni_problemy=None): |  | ||||||
| 	""" Spočítá u řešitelů body za číslo a za jednotlivé hlavní problémy (témata).""" |  | ||||||
| 	# TODO setřídit hlavní problémy čísla podle id, ať jsou ve stejném pořadí pokaždé |  | ||||||
| 	# pro každý hlavní problém zavedeme slovník s body za daný hlavní problém  |  | ||||||
| 	# pro jednotlivé řešitele (slovník slovníků hlavních problémů) |  | ||||||
| 
 |  | ||||||
| 	print("Scitam cislo",cislo) |  | ||||||
| 
 |  | ||||||
| 	if hlavni_problemy is None: |  | ||||||
| 		hlavni_problemy = hlavni_problemy_f(problemy_cisla(cislo)) |  | ||||||
| 
 |  | ||||||
| 	def ne_clanek_ne_konfera(problem): |  | ||||||
| 		inst = problem.get_real_instance() |  | ||||||
| 		return not(isinstance(inst, m.Clanek) or isinstance(inst, m.Konfera)) |  | ||||||
| 
 |  | ||||||
| 	if cislo.rocnik.rocnik < ROCNIK_ZRUSENI_TEMAT: |  | ||||||
| 		temata_a_spol = hlavni_problemy |  | ||||||
| 	else: |  | ||||||
| 		temata_a_spol = list(filter(ne_clanek_ne_konfera, hlavni_problemy)) |  | ||||||
| 
 |  | ||||||
| 	hlavni_problemy_slovnik = {} |  | ||||||
| 	for hp in temata_a_spol: |  | ||||||
| 		hlavni_problemy_slovnik[hp.id] = {} |  | ||||||
| 
 |  | ||||||
| 	hlavni_problemy_slovnik[-1] = {} |  | ||||||
| 
 |  | ||||||
| 	# zakládání prázdných záznamů pro řešitele |  | ||||||
| 	cislobody = {} |  | ||||||
| 	for ar in aktivni_resitele: |  | ||||||
| 		# řešitele převedeme na řetězec pomocí unikátního id |  | ||||||
| 		cislobody[ar.id] = "" |  | ||||||
| 		for hp in temata_a_spol: |  | ||||||
| 			slovnik = hlavni_problemy_slovnik[hp.id] |  | ||||||
| 			slovnik[ar.id] = "" |  | ||||||
| 
 |  | ||||||
| 		hlavni_problemy_slovnik[-1][ar.id] = "" |  | ||||||
| 
 |  | ||||||
| 	hodnoceni_do_cisla = m.Hodnoceni.objects.prefetch_related('problem', 'reseni', 'reseni__resitele').filter(cislo_body=cislo) |  | ||||||
| 
 |  | ||||||
| 	start = time.time() |  | ||||||
| 
 |  | ||||||
| 	for hodnoceni in hodnoceni_do_cisla: |  | ||||||
| 		prob = hodnoceni.problem |  | ||||||
| 		nadproblem = hlavni_problem(prob) |  | ||||||
| 		if ne_clanek_ne_konfera(nadproblem): |  | ||||||
| 			nadproblem_slovnik = hlavni_problemy_slovnik[nadproblem.id] |  | ||||||
| 		else: |  | ||||||
| 			nadproblem_slovnik = hlavni_problemy_slovnik[-1] |  | ||||||
| 
 |  | ||||||
| 		body = hodnoceni.body |  | ||||||
| 
 |  | ||||||
| 		# a mít více řešitelů |  | ||||||
| 		for resitel in hodnoceni.reseni.resitele.all(): |  | ||||||
| 			if resitel not in aktivni_resitele: |  | ||||||
| 				print("Skipping {}".format(resitel.id)) |  | ||||||
| 				continue |  | ||||||
| 			pricti_body(cislobody, resitel, body) |  | ||||||
| 			pricti_body(nadproblem_slovnik, resitel, body) |  | ||||||
| 	end = time.time() |  | ||||||
| 	print("for cykly:", end-start) |  | ||||||
| 	return hlavni_problemy_slovnik, cislobody |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| def secti_body_za_cislo_podle_temat(cislo, aktivni_resitele, podproblemy=None, temata=None): |  | ||||||
| 	""" Spočítá u řešitelů body za číslo za úlohy v jednotlivých hlavních problémech (témata).""" |  | ||||||
| 	if temata is None: |  | ||||||
| 		temata = hlavni_problemy_f(problemy_cisla(cislo)) |  | ||||||
| 
 |  | ||||||
| 	if podproblemy is None: |  | ||||||
| 		podproblemy_v_cislu(cislo, hlavni_problemy=temata) |  | ||||||
| 
 |  | ||||||
| 	body_slovnik = {} |  | ||||||
| 	for tema in temata: |  | ||||||
| 		body_slovnik[tema.id] = {} |  | ||||||
| 		for problem in podproblemy[tema.id]: |  | ||||||
| 			body_slovnik[tema.id][problem.id] = {} |  | ||||||
| 	body_slovnik[-1] = {} |  | ||||||
| 	for problem in podproblemy[-1]: |  | ||||||
| 		body_slovnik[-1][problem.id] = {} |  | ||||||
| 
 |  | ||||||
| 	# zakládání prázdných záznamů pro řešitele |  | ||||||
| 	for ar in aktivni_resitele: |  | ||||||
| 		for tema in temata: |  | ||||||
| 			for problem in podproblemy[tema.id]: |  | ||||||
| 				body_slovnik[tema.id][problem.id][ar.id] = "" |  | ||||||
| 
 |  | ||||||
| 		for problem in podproblemy[-1]: |  | ||||||
| 			body_slovnik[-1][problem.id][ar.id] = "" |  | ||||||
| 
 |  | ||||||
| 	temata = set(t.id for t in temata) |  | ||||||
| 
 |  | ||||||
| 	hodnoceni_do_cisla = m.Hodnoceni.objects.prefetch_related('problem', 'reseni', 'reseni__resitele').filter(cislo_body=cislo) |  | ||||||
| 
 |  | ||||||
| 	for hodnoceni in hodnoceni_do_cisla: |  | ||||||
| 		prob = hodnoceni.problem |  | ||||||
| 		nadproblem = hlavni_problem(prob) |  | ||||||
| 		if nadproblem.id in temata: |  | ||||||
| 			nadproblem_slovnik = body_slovnik[nadproblem.id] |  | ||||||
| 		else: |  | ||||||
| 			nadproblem_slovnik = body_slovnik[-1] |  | ||||||
| 
 |  | ||||||
| 		problem_slovnik = nadproblem_slovnik[prob.id] |  | ||||||
| 
 |  | ||||||
| 		body = hodnoceni.body |  | ||||||
| 
 |  | ||||||
| 		# a mít více řešitelů |  | ||||||
| 		for resitel in hodnoceni.reseni.resitele.all(): |  | ||||||
| 			if resitel not in aktivni_resitele: |  | ||||||
| 				print("Skipping {}".format(resitel.id)) |  | ||||||
| 				continue |  | ||||||
| 			pricti_body(problem_slovnik, resitel, body) |  | ||||||
| 	return body_slovnik |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| # TODELETE |  | ||||||
| class FixedIterator: | class FixedIterator: | ||||||
| 	def next(self): | 	def next(self): | ||||||
| 		return self.niter.__next__() | 		return self.niter.__next__() | ||||||
| 
 | 
 | ||||||
| 	def __init__(self, niter): | 	def __init__(self, niter): | ||||||
| 		self.niter = niter | 		self.niter = niter | ||||||
| # TODELETE |  | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def data_vysledkovky_cisla(cislo): | def body_resitelu( | ||||||
| 	problemy = problemy_cisla(cislo) | 		za: Union[m.Cislo, m.Rocnik, None] = None, | ||||||
| 	hlavni_problemy = hlavni_problemy_f(problemy) | 		do: m.Deadline = None, | ||||||
| 	## TODO možná chytřeji vybírat aktivní řešitele | 		od: m.Deadline = None, | ||||||
| 	# aktivní řešitelé - chceme letos něco poslal, TODO později vyfiltrujeme ty, kdo mají  | 		jen_verejne: bool = True, | ||||||
| 	# u alespoň jedné hodnoty něco jiného než NULL | 		resitele=None, | ||||||
| 	aktivni_resitele = list(resi_v_rocniku(cislo.rocnik)) | 		null=0 # Výchozí hodnota, pokud pro daného řešitele nejsou body | ||||||
|  | ) -> dict[int, int]: | ||||||
|  | 	filtr = Q() | ||||||
| 
 | 
 | ||||||
| 	# získáme body za číslo | 	if jen_verejne: | ||||||
| 	hlavni_problemy_slovnik, cislobody = secti_body_za_cislo(cislo, aktivni_resitele, hlavni_problemy) | 		filtr &= Q(reseni__hodnoceni__deadline_body__verejna_vysledkovka=True) | ||||||
| 
 | 
 | ||||||
| 	# získáme body za ročník, seznam obsahuje dvojice (řešitel_id, body) setřízené sestupně | 	# Zjistíme, typ objektu v parametru "za" | ||||||
| 	resitel_rocnikbody_sezn = secti_body_za_rocnik(cislo, aktivni_resitele, jen_verejne=True) | 	if isinstance(za, m.Rocnik): | ||||||
|  | 		filtr &= Q(reseni__hodnoceni__deadline_body__cislo__rocnik=za) | ||||||
|  | 	elif isinstance(za, m.Cislo): | ||||||
|  | 		filtr &= Q(reseni__hodnoceni__deadline_body__cislo=za) | ||||||
| 
 | 
 | ||||||
| 	# získáme body odjakživa | 	if do: | ||||||
| 	resitel_odjakzivabody_slov = body_resitelu(aktivni_resitele, cislo, jen_verejne=True) | 		filtr &= Q(reseni__hodnoceni__deadline_body__deadline__lte=do.deadline) | ||||||
| 
 | 
 | ||||||
| 	# řešitelé setřídění podle bodů za číslo sestupně | 	if od: | ||||||
| 	setrizeni_resitele_id = [dvojice[0] for dvojice in resitel_rocnikbody_sezn] | 		filtr &= Q(reseni__hodnoceni__deadline_body__deadline__gte=od.deadline) | ||||||
| 			 |  | ||||||
| 	# spočítáme pořadí řešitelů |  | ||||||
| 	setrizeni_resitele_body = [dvojice[1] for dvojice in resitel_rocnikbody_sezn] |  | ||||||
| 	poradi = sloupec_s_poradim(setrizeni_resitele_body) |  | ||||||
| 
 | 
 | ||||||
| 	# vytvoříme jednotlivé sloupce výsledkovky | 	resiteleQuery = m.Resitel.objects.all() | ||||||
| 	radky_vysledkovky = [] |  | ||||||
| 	i = 0 |  | ||||||
| 
 | 
 | ||||||
|  | 	if resitele is not None: | ||||||
|  | 		resitele_id = [r.id for r in resitele] | ||||||
|  | 		resiteleQuery = resiteleQuery.filter(id__in=resitele_id) | ||||||
|  | 
 | ||||||
|  | 	# Přidáme ke každému řešiteli údaj ".body" se součtem jejich bodů | ||||||
|  | 	resitele_s_body = resiteleQuery.annotate( | ||||||
|  | 		body=Sum('reseni__hodnoceni__body', filter=filtr)) | ||||||
|  | 
 | ||||||
|  | 	# Teď jen z QuerySetu řešitelů anotovaných body vygenerujeme slovník | ||||||
|  | 	# indexovaný řešitelským id obsahující body. | ||||||
|  | 	# Pokud jsou body None, nahradíme za 0. | ||||||
|  | 	slovnik = { | ||||||
|  | 		int(res.id): (res.body if res.body else null) for res in resitele_s_body | ||||||
|  | 	} | ||||||
|  | 	return slovnik | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class Vysledkovka(abc.ABC): | ||||||
|  | 	jen_verejne: bool | ||||||
|  | 	rocnik: m.Rocnik | ||||||
|  | 	do_deadlinu: m.Deadline | ||||||
|  | 
 | ||||||
|  | 	@property | ||||||
|  | 	@abc.abstractmethod | ||||||
|  | 	def aktivni_resitele(self) -> list[m.Resitel]: | ||||||
|  | 		... | ||||||
|  | 
 | ||||||
|  | 	@cached_property | ||||||
|  | 	def resitele_s_body_za_rocnik_setrizeny_seznam(self) -> list[tuple[int, int]]: | ||||||
|  | 		# spočítáme všem řešitelům jejich body za ročník | ||||||
|  | 		resitel_body_za_rocnik_slovnik = body_resitelu( | ||||||
|  | 			resitele=self.aktivni_resitele, | ||||||
|  | 			za=self.rocnik, | ||||||
|  | 			jen_verejne=self.jen_verejne, | ||||||
|  | 			do=self.do_deadlinu | ||||||
|  | 		) | ||||||
|  | 
 | ||||||
|  | 		# zeptáme se na dvojice (řešitel, body) za ročník a setřídíme sestupně | ||||||
|  | 		resitele_s_body_za_rocnik_setrizeny_seznam = sorted( | ||||||
|  | 			resitel_body_za_rocnik_slovnik.items(), | ||||||
|  | 			key=lambda x: x[1], reverse=True | ||||||
|  | 		) | ||||||
|  | 
 | ||||||
|  | 		return resitele_s_body_za_rocnik_setrizeny_seznam | ||||||
|  | 
 | ||||||
|  | 	@cached_property | ||||||
|  | 	def body_za_rocnik_seznamy(self) -> tuple[list[int], list[int]]: | ||||||
|  | 		if len(self.resitele_s_body_za_rocnik_setrizeny_seznam) == 0: | ||||||
|  | 			return [], [] | ||||||
|  | 		return tuple(zip(*self.resitele_s_body_za_rocnik_setrizeny_seznam)) | ||||||
|  | 
 | ||||||
|  | 	@property | ||||||
|  | 	def setrizeni_resitele_id(self) -> list[int]: | ||||||
|  | 		return self.body_za_rocnik_seznamy[0] | ||||||
|  | 
 | ||||||
|  | 	@property | ||||||
|  | 	def setrizene_body(self) -> list[int]: | ||||||
|  | 		return self.body_za_rocnik_seznamy[1] | ||||||
|  | 
 | ||||||
|  | 	@cached_property | ||||||
|  | 	def resitel_body_odjakziva_slovnik(self) -> dict[int, int]: | ||||||
|  | 		return body_resitelu(jen_verejne=self.jen_verejne, do=self.do_deadlinu) | ||||||
|  | 
 | ||||||
|  | 	@cached_property | ||||||
|  | 	def poradi(self): | ||||||
|  | 		# ze seznamu obsahujícího setřízené body spočítáme sloupec s pořadím | ||||||
|  | 		aktualni_poradi = 1 | ||||||
|  | 		sloupec_s_poradim = [] | ||||||
|  | 
 | ||||||
|  | 		# seskupíme seznam všech bodů podle hodnot | ||||||
|  | 		for index in range(0, len(self.setrizene_body)): | ||||||
|  | 			# pokud je pořadí větší než číslo řádku, tak jsme vypsali větší rozsah | ||||||
|  | 			# a chceme vypsat už jen prázdné místo, než dojdeme na správný řádek | ||||||
|  | 			if (index + 1) < aktualni_poradi: | ||||||
|  | 				sloupec_s_poradim.append("") | ||||||
|  | 				continue | ||||||
|  | 			velikost_skupiny = 0 | ||||||
|  | 			# zjistíme počet po sobě jdoucích stejných hodnot | ||||||
|  | 			while self.setrizene_body[index] == self.setrizene_body[ | ||||||
|  | 				index + velikost_skupiny]: | ||||||
|  | 				velikost_skupiny += 1 | ||||||
|  | 				# na konci musíme ošetřit přetečení seznamu | ||||||
|  | 				if (index + velikost_skupiny) > len(self.setrizene_body) - 1: | ||||||
|  | 					break | ||||||
|  | 			# pokud je velikost skupiny 1, vypíšu pořadí | ||||||
|  | 			if velikost_skupiny == 1: | ||||||
|  | 				sloupec_s_poradim.append(f"{aktualni_poradi}.") | ||||||
|  | 			# pokud je skupina větší, vypíšu rozsah | ||||||
|  | 			else: | ||||||
|  | 				sloupec_s_poradim.append( | ||||||
|  | 					f"{aktualni_poradi}.–{aktualni_poradi + velikost_skupiny - 1}." | ||||||
|  | 				) | ||||||
|  | 			# zvětšíme aktuální pořadí o tolik, kolik pozic bylo přeskočeno | ||||||
|  | 			aktualni_poradi += velikost_skupiny | ||||||
|  | 		return sloupec_s_poradim | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class VysledkovkaRocniku(Vysledkovka): | ||||||
|  | 
 | ||||||
|  | 	def __init__(self, rocnik: m.Rocnik, jen_verejne: bool = True): | ||||||
|  | 		self.rocnik = rocnik | ||||||
|  | 		self.jen_verejne = jen_verejne | ||||||
|  | 		self.do_deadlinu = m.Deadline.objects.filter(cislo__rocnik=rocnik).last() | ||||||
|  | 
 | ||||||
|  | 	@cached_property | ||||||
|  | 	def aktivni_resitele(self) -> list[m.Resitel]: | ||||||
|  | 		return list(resi_v_rocniku(self.rocnik)) | ||||||
|  | 
 | ||||||
|  | 	@cached_property | ||||||
|  | 	def cisla_rocniku(self) -> list[m.Cislo]: | ||||||
|  | 		""" Vrátí všechna čísla daného ročníku. """ | ||||||
|  | 		if self.jen_verejne: | ||||||
|  | 			return self.rocnik.verejne_vysledkovky_cisla() | ||||||
|  | 		else: | ||||||
|  | 			return self.rocnik.cisla.all().order_by('poradi') | ||||||
|  | 
 | ||||||
|  | 	@cached_property | ||||||
|  | 	def body_za_cisla_slovnik(self) -> dict[int, dict[int, int]]: # Výstup: m.Cislo.id → ( m.Resitel.id → body ) | ||||||
|  | 		# TODO: Body jsou decimal! | ||||||
|  | 		body_cisla_slovnik = dict() | ||||||
|  | 		for cislo in self.cisla_rocniku: | ||||||
|  | 			# získáme body za číslo | ||||||
|  | 			body_za_cislo = body_resitelu( | ||||||
|  | 				za=cislo, | ||||||
|  | 				resitele=self.aktivni_resitele, | ||||||
|  | 				jen_verejne=self.jen_verejne, | ||||||
|  | 				null="" | ||||||
|  | 			) | ||||||
|  | 			body_cisla_slovnik[cislo.id] = body_za_cislo | ||||||
|  | 		return body_cisla_slovnik | ||||||
|  | 
 | ||||||
|  | 	class RadekVysledkovkyRocniku: | ||||||
|  | 		# TODO: přepsat na dataclass | ||||||
|  | 		""" Obsahuje věci, které se hodí vědět při konstruování výsledkovky. | ||||||
|  | 		Umožňuje snazší práci v templatu (lepší, než seznam).""" | ||||||
|  | 
 | ||||||
|  | 		def __init__(self, poradi, resitel, body_cisla_seznam, body_rocnik, body_odjakziva, rok): | ||||||
|  | 			self.poradi = poradi | ||||||
|  | 			self.resitel = resitel | ||||||
|  | 			self.rocnik_resitele = resitel.rocnik(rok) | ||||||
|  | 			self.body_rocnik = body_rocnik | ||||||
|  | 			self.body_celkem_odjakziva = body_odjakziva | ||||||
|  | 			self.body_cisla_seznam = body_cisla_seznam | ||||||
|  | 			self.titul = resitel.get_titul(body_odjakziva) | ||||||
|  | 
 | ||||||
|  | 	@cached_property | ||||||
|  | 	def radky_vysledkovky(self) -> list[RadekVysledkovkyRocniku]: | ||||||
|  | 		radky_vysledkovky = [] | ||||||
|  | 
 | ||||||
|  | 		setrizeni_resitele_dict = dict() | ||||||
|  | 		for r in m.Resitel.objects.filter( | ||||||
|  | 				id__in=self.setrizeni_resitele_id | ||||||
|  | 		).select_related('osoba'): | ||||||
|  | 			setrizeni_resitele_dict[r.id] = r | ||||||
|  | 
 | ||||||
|  | 		for i, ar_id in enumerate(self.setrizeni_resitele_id): | ||||||
|  | 			if self.setrizene_body[i] > 0: | ||||||
|  | 				# seznam počtu bodů daného řešitele pro jednotlivá čísla | ||||||
|  | 				body_cisla_seznam = [] | ||||||
|  | 				for cislo in self.cisla_rocniku: | ||||||
|  | 					body_cisla_seznam.append(self.body_za_cisla_slovnik[cislo.id][ar_id]) | ||||||
|  | 
 | ||||||
|  | 				# Pokud řešitel dostal nějaké body | ||||||
|  | 				if self.resitele_s_body_za_rocnik_setrizeny_seznam[i] != 0: | ||||||
|  | 					# vytáhneme informace pro daného řešitele | ||||||
|  | 					radek = self.RadekVysledkovkyRocniku( | ||||||
|  | 						poradi=self.poradi[i], | ||||||
|  | 						resitel=setrizeni_resitele_dict[ar_id], | ||||||
|  | 						body_cisla_seznam=body_cisla_seznam, | ||||||
|  | 						body_rocnik=self.setrizene_body[i], | ||||||
|  | 						body_odjakziva=self.resitel_body_odjakziva_slovnik[ar_id], | ||||||
|  | 						rok=self.rocnik)  # ročník semináře pro získání ročníku řešitele | ||||||
|  | 					radky_vysledkovky.append(radek) | ||||||
|  | 
 | ||||||
|  | 		return radky_vysledkovky | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class VysledkovkaCisla(Vysledkovka): | ||||||
|  | 	def __init__( | ||||||
|  | 			self, | ||||||
|  | 			cislo: m.Cislo, | ||||||
|  | 			jen_verejne: bool = True, | ||||||
|  | 			do_deadlinu: m.Deadline = None | ||||||
|  | 	): | ||||||
|  | 		self.cislo = cislo | ||||||
|  | 		self.rocnik = cislo.rocnik | ||||||
|  | 		self.jen_verejne = jen_verejne | ||||||
|  | 		if do_deadlinu is None: | ||||||
|  | 			do_deadlinu = m.Deadline.objects.filter(cislo=cislo).last() | ||||||
|  | 		self.do_deadlinu = do_deadlinu | ||||||
|  | 
 | ||||||
|  | 	@cached_property | ||||||
|  | 	def aktivni_resitele(self) -> list[m.Resitel]: | ||||||
|  | 		# TODO možná chytřeji vybírat aktivní řešitele | ||||||
|  | 		return list(resi_v_rocniku(self.rocnik)) | ||||||
|  | 
 | ||||||
|  | 	@cached_property | ||||||
|  | 	def problemy(self) -> list[m.Problem]: | ||||||
|  | 		""" Vrátí seznam všech problémů s body v daném čísle. """ | ||||||
|  | 		return m.Problem.objects.filter( | ||||||
|  | 			hodnoceni__in=m.Hodnoceni.objects.filter(deadline_body__cislo=self.cislo) | ||||||
|  | 		).distinct().non_polymorphic().select_related('nadproblem').select_related('nadproblem__nadproblem') | ||||||
|  | 
 | ||||||
|  | 	@cached_property | ||||||
|  | 	def hlavni_problemy(self) -> list[m.Problem]: | ||||||
|  | 		""" Vrátí seznam všech problémů, které již nemají nadproblém. """ | ||||||
|  | 		# hlavní problémy čísla | ||||||
|  | 		# (mají vlastní sloupeček ve výsledkovce, nemají nadproblém) | ||||||
|  | 		hlavni_problemy = set() | ||||||
|  | 		for p in self.problemy: | ||||||
|  | 			hlavni_problemy.add(p.hlavni_problem) | ||||||
|  | 
 | ||||||
|  | 		# zunikátnění | ||||||
|  | 		hlavni_problemy = list(hlavni_problemy) | ||||||
|  | 		hlavni_problemy.sort( | ||||||
|  | 			key=lambda k: k.kod_v_rocniku)  # setřídit podle t1, t2, c3, ... | ||||||
|  | 
 | ||||||
|  | 		return hlavni_problemy | ||||||
|  | 
 | ||||||
|  | 	# Není cached, protože si myslím, že queryset lze použít ve for jen jednou. | ||||||
|  | 	@property | ||||||
|  | 	def hodnoceni_do_cisla(self): | ||||||
|  | 		hodnoceni = m.Hodnoceni.objects.prefetch_related('reseni__resitele').select_related('problem', 'reseni') | ||||||
|  | 		if self.jen_verejne: | ||||||
|  | 			hodnoceni = hodnoceni.filter(deadline_body__verejna_vysledkovka=True) | ||||||
|  | 		return hodnoceni.filter( | ||||||
|  | 			deadline_body__cislo=self.cislo, | ||||||
|  | 			deadline_body__deadline__lte=self.do_deadlinu.deadline, | ||||||
|  | 			body__isnull=False, | ||||||
|  | 		) | ||||||
|  | 
 | ||||||
|  | 	@cached_property | ||||||
|  | 	def sectene_body(self): | ||||||
|  | 		""" | ||||||
|  | 			Sečte body za číslo, hlavní problémy a podproblémy. | ||||||
|  | 
 | ||||||
|  | 			Problém s ID '-1' znamená problémy bez nadproblémů, jež nejsou témata, tj. články, úlohy, konfery, … | ||||||
|  | 		""" | ||||||
|  | 
 | ||||||
|  | 		# Body za číslo | ||||||
|  | 		body_za_cislo = {ar.id: "" for ar in self.aktivni_resitele} | ||||||
|  | 
 | ||||||
|  | 		# Body za hlavní problémy | ||||||
|  | 		body_za_temata = { | ||||||
|  | 			hp.id: {ar.id: "" for ar in self.aktivni_resitele} | ||||||
|  | 			for hp in self.temata_a_spol | ||||||
|  | 		} | ||||||
|  | 		# Ostatní body | ||||||
|  | 		body_za_temata[-1] = {ar.id: "" for ar in self.aktivni_resitele} | ||||||
|  | 
 | ||||||
|  | 		# Body za podproblémy | ||||||
|  | 		body_za_problemy = { | ||||||
|  | 			tema.id: { | ||||||
|  | 				problem.id: {ar.id: "" for ar in self.aktivni_resitele} | ||||||
|  | 				for problem in self.podproblemy[tema.id] | ||||||
|  | 			} | ||||||
|  | 			for tema in self.temata_a_spol | ||||||
|  | 		} | ||||||
|  | 		# Ostatní body | ||||||
|  | 		body_za_problemy[-1] = { | ||||||
|  | 				problem.id: {ar.id: "" for ar in self.aktivni_resitele} | ||||||
|  | 				for problem in self.podproblemy[-1] | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		# Sečteme hodnocení | ||||||
|  | 		for hodnoceni in self.hodnoceni_do_cisla: | ||||||
|  | 			prob = hodnoceni.problem | ||||||
|  | 			nadproblem = prob.hlavni_problem.id | ||||||
|  | 
 | ||||||
|  | 			# Když nadproblém není "téma", pak je "Ostatní" | ||||||
|  | 			if nadproblem not in body_za_temata: | ||||||
|  | 				nadproblem = -1 | ||||||
|  | 
 | ||||||
|  | 			problem_slovnik = body_za_problemy[nadproblem][prob.id] | ||||||
|  | 			nadproblem_slovnik = body_za_temata[nadproblem] | ||||||
|  | 
 | ||||||
|  | 			body = hodnoceni.body | ||||||
|  | 
 | ||||||
|  | 			# Může mít více řešitelů | ||||||
|  | 			for resitel in hodnoceni.reseni.resitele.all(): | ||||||
|  | 				if resitel not in self.aktivni_resitele: | ||||||
|  | 					continue | ||||||
|  | 				self.pricti_body(body_za_cislo, resitel, body) | ||||||
|  | 				self.pricti_body(nadproblem_slovnik, resitel, body) | ||||||
|  | 				self.pricti_body(problem_slovnik, resitel, body) | ||||||
|  | 		return body_za_cislo, body_za_temata, body_za_problemy | ||||||
|  | 
 | ||||||
|  | 	@cached_property | ||||||
|  | 	def body_za_temata(self) -> dict[int, dict[int, str]]: | ||||||
|  | 		return self.sectene_body[1] | ||||||
|  | 
 | ||||||
|  | 	@cached_property | ||||||
|  | 	def body_za_cislo(self) -> dict[int, str]: | ||||||
|  | 		return self.sectene_body[0] | ||||||
|  | 
 | ||||||
|  | 	@cached_property | ||||||
|  | 	def problemy_slovnik(self): | ||||||
|  | 		return self.sectene_body[2] | ||||||
|  | 
 | ||||||
|  | 	@cached_property | ||||||
|  | 	def temata_a_spol(self) -> list[m.Problem]: | ||||||
|  | 		if self.rocnik.rocnik < ROCNIK_ZRUSENI_TEMAT: | ||||||
|  | 			return self.hlavni_problemy | ||||||
|  | 		else: | ||||||
|  | 			return list(filter(self.ne_clanek_ne_konfera, self.hlavni_problemy)) | ||||||
|  | 
 | ||||||
|  | 	@cached_property | ||||||
|  | 	def je_nejake_ostatni(self): | ||||||
|  | 		return len(self.hlavni_problemy) - len(self.temata_a_spol) > 0 | ||||||
|  | 
 | ||||||
|  | 	@cached_property | ||||||
|  | 	def podproblemy(self) -> dict[int, list[m.Problem]]: | ||||||
|  | 		podproblemy = {hp.id: [] for hp in self.temata_a_spol} | ||||||
|  | 		temata_a_spol = set(self.temata_a_spol) | ||||||
|  | 		podproblemy[-1] = [] | ||||||
|  | 
 | ||||||
|  | 		for problem in self.problemy: | ||||||
|  | 			h_problem = problem.hlavni_problem | ||||||
|  | 			if h_problem in temata_a_spol: | ||||||
|  | 				podproblemy[h_problem.id].append(problem) | ||||||
|  | 			else: | ||||||
|  | 				podproblemy[-1].append(problem) | ||||||
|  | 
 | ||||||
|  | 		for podproblem in podproblemy.keys(): | ||||||
|  | 			def int_or_zero(p): | ||||||
|  | 				try: | ||||||
|  | 					return int(p.kod) | ||||||
|  | 				except ValueError: | ||||||
|  | 					return 0 | ||||||
|  | 
 | ||||||
|  | 			podproblemy[podproblem] = sorted(podproblemy[podproblem], key=int_or_zero) | ||||||
|  | 		return podproblemy | ||||||
|  | 
 | ||||||
|  | 	@cached_property | ||||||
|  | 	def podproblemy_seznam(self) -> list[list[m.Problem]]: | ||||||
|  | 		return [self.podproblemy[it.id] for it in self.temata_a_spol] + [self.podproblemy[-1]] | ||||||
|  | 
 | ||||||
|  | 	@cached_property | ||||||
|  | 	def podproblemy_iter(self) -> FixedIterator: | ||||||
|  | 		return FixedIterator(self.podproblemy_seznam.__iter__()) | ||||||
|  | 
 | ||||||
|  | 	class RadekVysledkovkyCisla(object): | ||||||
|  | 		# TODO: Přepsat na dataclass | ||||||
|  | 		"""Obsahuje věci, které se hodí vědět při konstruování výsledkovky. | ||||||
|  | 		Umožňuje snazší práci v templatu (lepší, než seznam).""" | ||||||
|  | 
 | ||||||
|  | 		def __init__(self, poradi, resitel, temata_seznamk, body_cislo, body_rocnik, body_odjakziva, rok, body_podproblemy, body_podproblemy_iter): | ||||||
|  | 			self.resitel = resitel | ||||||
|  | 			self.rocnik_resitele = resitel.rocnik(rok) | ||||||
|  | 			self.body_cislo = body_cislo | ||||||
|  | 			self.body_rocnik = body_rocnik | ||||||
|  | 			self.body_celkem_odjakziva = body_odjakziva | ||||||
|  | 			self.poradi = poradi | ||||||
|  | 			self.body_za_temata_seznam = temata_seznamk | ||||||
|  | 			self.titul = resitel.get_titul(body_odjakziva) | ||||||
|  | 			self.body_podproblemy = body_podproblemy | ||||||
|  | 			self.body_podproblemy_iter = body_podproblemy_iter | ||||||
|  | 
 | ||||||
|  | 	@cached_property | ||||||
|  | 	def radky_vysledkovky(self) -> list[RadekVysledkovkyCisla]: | ||||||
|  | 		# vytvoříme jednotlivé sloupce výsledkovky | ||||||
|  | 		radky_vysledkovky = [] | ||||||
|  | 
 | ||||||
|  | 		setrizeni_resitele_slovnik = {} | ||||||
|  | 		setrizeni_resitele = m.Resitel.objects.filter(id__in=self.setrizeni_resitele_id).select_related('osoba') | ||||||
|  | 
 | ||||||
|  | 		for r in setrizeni_resitele: | ||||||
|  | 			setrizeni_resitele_slovnik[r.id] = r | ||||||
|  | 
 | ||||||
|  | 		for i, ar_id in enumerate(self.setrizeni_resitele_id): | ||||||
|  | 			if self.setrizene_body[i] > 0: | ||||||
|  | 				# získáme seznam bodů za problémy pro daného řešitele | ||||||
|  | 				body_problemy = [] | ||||||
|  | 				body_podproblemy = [] | ||||||
|  | 				for hp in self.temata_a_spol: | ||||||
|  | 					body_problemy.append(self.body_za_temata[hp.id][ar_id]) | ||||||
|  | 					body_podproblemy.append([ | ||||||
|  | 						self.problemy_slovnik[hp.id][it.id][ar_id] | ||||||
|  | 						for it in self.podproblemy[hp.id] | ||||||
|  | 					]) | ||||||
|  | 				if self.je_nejake_ostatni: | ||||||
|  | 					body_problemy.append(self.body_za_temata[-1][ar_id]) | ||||||
|  | 					body_podproblemy.append( | ||||||
|  | 						[self.problemy_slovnik[-1][it.id][ar_id] for it in self.podproblemy[-1]]) | ||||||
|  | 				# vytáhneme informace pro daného řešitele | ||||||
|  | 				radek = self.RadekVysledkovkyCisla( | ||||||
|  | 					poradi=self.poradi[i], | ||||||
|  | 					resitel=setrizeni_resitele_slovnik[ar_id], | ||||||
|  | 					temata_seznamk=body_problemy, | ||||||
|  | 					body_cislo=self.body_za_cislo[ar_id], | ||||||
|  | 					body_rocnik=self.setrizene_body[i], | ||||||
|  | 					body_odjakziva=self.resitel_body_odjakziva_slovnik[ar_id], | ||||||
|  | 					rok=self.rocnik, | ||||||
|  | 					body_podproblemy=body_podproblemy,  # body všech podproblémů | ||||||
|  | 					body_podproblemy_iter=FixedIterator(body_podproblemy.__iter__()) | ||||||
|  | 				)  # ročník semináře pro zjištění ročníku řešitele | ||||||
|  | 				radky_vysledkovky.append(radek) | ||||||
|  | 		return radky_vysledkovky | ||||||
|  | 
 | ||||||
|  | 	@staticmethod | ||||||
|  | 	def pricti_body(slovnik, resitel, body): | ||||||
|  | 		""" Přiřazuje danému řešiteli body do slovníku. """ | ||||||
|  | 		# testujeme na None (""), pokud je to první řešení | ||||||
|  | 		# daného řešitele, předěláme na 0 | ||||||
|  | 		# (v dalším kroku přičteme reálný počet bodů), | ||||||
|  | 		# rozlišujeme tím mezi 0 a neodevzdaným řešením | ||||||
|  | 
 | ||||||
|  | 		if slovnik[resitel.id] == "": | ||||||
|  | 			slovnik[resitel.id] = 0 | ||||||
|  | 
 | ||||||
|  | 		slovnik[resitel.id] += body | ||||||
|  | 
 | ||||||
|  | 	@staticmethod | ||||||
| 	def ne_clanek_ne_konfera(problem): | 	def ne_clanek_ne_konfera(problem): | ||||||
| 		 | 		inst = problem.get_real_instance() | ||||||
| 		return not(isinstance(problem.get_real_instance(), m.Clanek) or isinstance(problem.get_real_instance(), m.Konfera)) | 		return not (isinstance(inst, m.Clanek) or isinstance(inst, m.Konfera)) | ||||||
| 
 | 
 | ||||||
| 	if cislo.rocnik.rocnik < ROCNIK_ZRUSENI_TEMAT: |  | ||||||
| 		temata_a_spol = hlavni_problemy |  | ||||||
| 	else: |  | ||||||
| 		temata_a_spol = list(filter(ne_clanek_ne_konfera, hlavni_problemy)) |  | ||||||
| 
 | 
 | ||||||
| 	# získáme body u jednotlivých témat | class VysledkovkaDoTeXu(VysledkovkaCisla): | ||||||
| 	podproblemy = podproblemy_v_cislu(cislo, problemy, temata_a_spol) | 	def __init__( | ||||||
| 	problemy_slovnik = secti_body_za_cislo_podle_temat(cislo, aktivni_resitele, podproblemy, temata_a_spol) | 			self, | ||||||
|  | 			nejake_cislo: m.Cislo, | ||||||
|  | 			od_vyjma: m.Deadline, | ||||||
|  | 			do_vcetne: m.Deadline | ||||||
|  | 	): | ||||||
|  | 		super().__init__(nejake_cislo, False, do_vcetne) | ||||||
|  | 		self.od_deadlinu = od_vyjma | ||||||
| 
 | 
 | ||||||
| 	# def not_empty(value): | 	@cached_property | ||||||
| 	# 	return value != '' | 	def problemy(self) -> list[m.Problem]: | ||||||
| 	# | 		return m.Problem.objects.filter(hodnoceni__in=m.Hodnoceni.objects.filter( | ||||||
| 	# je_nejake_ostatni = any(filter(not_empty, hlavni_problemy_slovnik[-1].values())) > 0 | 				deadline_body__deadline__gt=self.od_deadlinu.deadline, | ||||||
|  | 				deadline_body__deadline__lte=self.do_deadlinu.deadline, | ||||||
|  | 			)).distinct().non_polymorphic().select_related('nadproblem').select_related('nadproblem__nadproblem') | ||||||
| 
 | 
 | ||||||
| 	je_nejake_ostatni = len(hlavni_problemy) - len(temata_a_spol) > 0 | 	@property | ||||||
| 
 | 	def hodnoceni_do_cisla(self): | ||||||
| 	setrizeni_resitele_slovnik = {} | 		hodnoceni = m.Hodnoceni.objects.prefetch_related( | ||||||
| 	setrizeni_resitele = m.Resitel.objects.filter(id__in=setrizeni_resitele_id).select_related('osoba') | 			'problem', 'reseni', 'reseni__resitele') | ||||||
| 	for r in setrizeni_resitele: | 		if self.jen_verejne: | ||||||
| 		setrizeni_resitele_slovnik[r.id] = r | 			hodnoceni = hodnoceni.filter(deadline_body__verejna_vysledkovka=True) | ||||||
| 
 | 		return hodnoceni.filter( | ||||||
| 	for ar_id in setrizeni_resitele_id: | 			deadline_body__deadline__gt=self.od_deadlinu.deadline, | ||||||
| 		# získáme seznam bodů za problémy pro daného řešitele | 			deadline_body__deadline__lte=self.do_deadlinu.deadline, | ||||||
| 		body_problemy = [] | 			body__isnull=False, | ||||||
| 		body_podproblemy = [] | 		) | ||||||
| 		for hp in temata_a_spol: |  | ||||||
| 			body_problemy.append(hlavni_problemy_slovnik[hp.id][ar_id]) |  | ||||||
| 			body_podproblemy.append([problemy_slovnik[hp.id][it.id][ar_id] for it in podproblemy[hp.id]]) |  | ||||||
| 		if je_nejake_ostatni: |  | ||||||
| 			body_problemy.append(hlavni_problemy_slovnik[-1][ar_id]) |  | ||||||
| 			body_podproblemy.append([problemy_slovnik[-1][it.id][ar_id] for it in podproblemy[-1]]) |  | ||||||
| 		# vytáhneme informace pro daného řešitele |  | ||||||
| 		radek = RadekVysledkovkyCisla( |  | ||||||
| 			poradi[i], # pořadí |  | ||||||
| 			setrizeni_resitele_slovnik[ar_id], # řešitel (z id) |  | ||||||
| 			body_problemy, # seznam bodů za hlavní problémy čísla |  | ||||||
| 			cislobody[ar_id], # body za číslo |  | ||||||
| 			setrizeni_resitele_body[i], # body za ročník (spočítané výše s pořadím) |  | ||||||
| 			resitel_odjakzivabody_slov[ar_id], # body odjakživa |  | ||||||
| 			cislo.rocnik, |  | ||||||
| 			body_podproblemy,  # body všech podproblémů |  | ||||||
| 			FixedIterator(body_podproblemy.__iter__())  # TODELETE |  | ||||||
| 		)  # ročník semináře pro zjištění ročníku řešitele |  | ||||||
| 		radky_vysledkovky.append(radek) |  | ||||||
| 		i += 1 |  | ||||||
| 
 |  | ||||||
| 	# vytahané informace předáváme do kontextu |  | ||||||
| 	pt = [podproblemy[it.id] for it in temata_a_spol]+[podproblemy[-1]] |  | ||||||
| 	radky_vysledkovky = [radek for radek in radky_vysledkovky if radek.body_rocnik > 0] |  | ||||||
| 	return ( |  | ||||||
| 		radky_vysledkovky, |  | ||||||
| 		temata_a_spol, |  | ||||||
| 		je_nejake_ostatni, |  | ||||||
| 		pt, |  | ||||||
| 		FixedIterator(pt.__iter__()) |  | ||||||
| 	) |  | ||||||
|  |  | ||||||
|  | @ -1,37 +0,0 @@ | ||||||
| from .utils import data_vysledkovky_cisla, \ |  | ||||||
|     data_vysledkovky_rocniku |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| def vysledkovka_cisla(cislo, context=None): |  | ||||||
|     if context is None: |  | ||||||
|         context = {} |  | ||||||
|     context['cislo'] = cislo |  | ||||||
| 
 |  | ||||||
|     ( |  | ||||||
|         context['radky_vysledkovky'], |  | ||||||
|         context['problemy'], |  | ||||||
|         context['ostatni'], |  | ||||||
|         context['podproblemy'], |  | ||||||
|         context['podproblemy_iter'] |  | ||||||
|     ) = data_vysledkovky_cisla(cislo) |  | ||||||
|     return context |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| def vysledkovka_rocniku(rocnik, context=None, request=None, sneverejnou=False): |  | ||||||
|     if context is None: |  | ||||||
|         context = {} |  | ||||||
| 
 |  | ||||||
|     ( |  | ||||||
|         context['radky_vysledkovky'], |  | ||||||
|         context['cisla'] |  | ||||||
|     ) = data_vysledkovky_rocniku(rocnik) |  | ||||||
| 
 |  | ||||||
|     context['vysledkovka'] = len(context['cisla']) != 0 |  | ||||||
| 
 |  | ||||||
|     if sneverejnou and request and request.user.je_org: |  | ||||||
|         ( |  | ||||||
|             context['radky_vysledkovky_s_neverejnymi'], |  | ||||||
|             context['cisla_s_neverejnymi'] |  | ||||||
|         ) = data_vysledkovky_rocniku(rocnik, jen_verejne=False) |  | ||||||
| 
 |  | ||||||
|     return context |  | ||||||
		Loading…
	
		Reference in a new issue