verejny kontaktnicek #71
					 7 changed files with 142 additions and 57 deletions
				
			
		|  | @ -12,7 +12,7 @@ | |||
| 
 | ||||
| <br> | ||||
| 
 | ||||
| {% for rocnik, hodnoceni in podle_rocniku %} | ||||
| {% for rocnik, hodnoceni, suma_bodu in podle_rocniku %} | ||||
| <h1>Ročník {{ rocnik }}</h1> | ||||
| <table class="moje_reseni plne_ohranicena_tabulka"> | ||||
| 	<tr> | ||||
|  | @ -33,7 +33,7 @@ | |||
| 	</tr> | ||||
| 	{% endfor %} | ||||
| </table> | ||||
| 
 | ||||
| <p>Celkový počet bodů {{suma_bodu}}</p> | ||||
| <br> | ||||
| 
 | ||||
| {% endfor %} | ||||
|  |  | |||
|  | @ -356,7 +356,12 @@ class PrehledOdevzdanychReseni(ListView): | |||
| 		# Chceme to mít seřazené, takže místo comphrerehsion ručně postavíme pole polí. Django templates neumí použít OrderedDict :-/ | ||||
| 		podle_rocniku = [] | ||||
| 		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))) | ||||
| 			suma_bodu = 0 | ||||
| 			hodnoceni = list(hodnoceni) | ||||
| 			for i in hodnoceni :  | ||||
| 				if i.body != None : suma_bodu += i.body | ||||
| 			podle_rocniku.append((rocnik, hodnoceni, suma_bodu)) | ||||
| 		 | ||||
| 		ctx['podle_rocniku'] = reversed(podle_rocniku) # Od nejnovějšího ročníku | ||||
| 		# TODO: Umožnit stažení / zobrazení řešení | ||||
| 		return ctx | ||||
|  |  | |||
|  | @ -29,7 +29,7 @@ | |||
| } | ||||
| 
 | ||||
| {% for u in ucastnici %} | ||||
| 	{% with o=u.osoba %} | ||||
| 	{% with o=u.resitel.osoba %} | ||||
| 	\stvrzenka{{o.jmeno|sloz}}{{o.prijmeni|sloz}} | ||||
| 	{% endwith %} | ||||
| {% endfor %} | ||||
|  |  | |||
|  | @ -26,12 +26,12 @@ urlpatterns = [ | |||
| 				), | ||||
| 				path( | ||||
| 					'export_ucastniku', | ||||
| 					org_required(views.soustredeniUcastniciExportView), | ||||
| 					org_required(views.SoustredeniUcastniciExportView.as_view()), | ||||
| 					name='soustredeni_ucastnici_export' | ||||
| 				), | ||||
| 				path( | ||||
| 					'stvrzenky.pdf', | ||||
| 					org_required(views.soustredeniStvrzenkyView), | ||||
| 					org_required(views.SoustredeniStvrzenkyView.as_view()), | ||||
| 					name='soustredeni_ucastnici_stvrzenky' | ||||
| 				), | ||||
| 				path( | ||||
|  |  | |||
|  | @ -1,4 +1,4 @@ | |||
| from django.shortcuts import get_object_or_404, render | ||||
| from django.shortcuts import get_object_or_404 | ||||
| from django.http import HttpResponse | ||||
| from django.views import generic | ||||
| from django.contrib.staticfiles.finders import find | ||||
|  | @ -6,13 +6,9 @@ from django.http import Http404 | |||
| from django.core.exceptions import PermissionDenied | ||||
| 
 | ||||
| import csv | ||||
| import tempfile | ||||
| import shutil | ||||
| import subprocess | ||||
| from pathlib import Path | ||||
| import http | ||||
| 
 | ||||
| import personalni.views | ||||
| import various.views.generic | ||||
| from personalni.views import obalkyView | ||||
| 
 | ||||
| from .models import Soustredeni, Soustredeni_Ucastnici | ||||
| from various.models import Nastaveni | ||||
|  | @ -36,73 +32,78 @@ class SoustredeniListView(generic.ListView): | |||
| 		) | ||||
| 
 | ||||
| 
 | ||||
| def soustredeniObalkyView(request, soustredeni): | ||||
| 	soustredeni = get_object_or_404(Soustredeni, id=soustredeni) | ||||
| 	return personalni.views.obalkyView(request, soustredeni.ucastnici.all()) | ||||
| class KonkretniSoustredeniMixin: | ||||
| 	""" Přidá k View s parametrem `soustredeni` atribut `self.soustredeni` """ | ||||
| 	def setup(self, request, *args, **kwargs): | ||||
| 		super().setup(request, *args, **kwargs) | ||||
| 		soustredeni_id = self.kwargs["soustredeni"] | ||||
| 		self.soustredeni = get_object_or_404(Soustredeni, id=soustredeni_id) | ||||
| 
 | ||||
| 
 | ||||
| class SoustredeniUcastniciBaseView(generic.ListView): | ||||
| class SoustredeniUcastniciBaseView( | ||||
| 	KonkretniSoustredeniMixin, | ||||
| 	various.views.generic.NeprazdnyListView, | ||||
| ): | ||||
| 	""" | ||||
| 		Slouží jako ListView účastníků soustředění | ||||
| 		+ háže inteligentní chybu při soustředění bez účastníků | ||||
| 	""" | ||||
| 	model = Soustredeni_Ucastnici | ||||
| 	if_prazdny_title = "K soustředění nejsou přidaní žádní účastníci" | ||||
| 	if_prazdny_text = "K tebou zvolenému soustředění nejsou přidaní žádní účastníci, tedy není co zobrazit. Můžeš to zkusit změnit v adminu, případně se zeptej webařů :-)" | ||||
| 
 | ||||
| 	def get_queryset(self): | ||||
| 		soustredeni = get_object_or_404( | ||||
| 			Soustredeni, | ||||
| 			pk=self.kwargs["soustredeni"] | ||||
| 		) | ||||
| 		return Soustredeni_Ucastnici.objects.filter( | ||||
| 			soustredeni=soustredeni).select_related('resitel') | ||||
| 			soustredeni=self.soustredeni).select_related('resitel', 'resitel__osoba') | ||||
| 
 | ||||
| 
 | ||||
| # FIXME předělat jako ostatní (vyžaduje předělání `obalkyView`) | ||||
| def soustredeniObalkyView(request, soustredeni): | ||||
| 	soustredeni = get_object_or_404(Soustredeni, id=soustredeni) | ||||
| 	return obalkyView(request, soustredeni.ucastnici.all()) | ||||
| 
 | ||||
| 
 | ||||
| class SoustredeniMailyUcastnikuView(SoustredeniUcastniciBaseView): | ||||
| 	""" Seznam e-mailů řešitelů oddělených čárkami. """ | ||||
| 	model = Soustredeni_Ucastnici | ||||
| 	template_name = 'soustredeni/maily_ucastniku.txt' | ||||
| 
 | ||||
| 
 | ||||
| class SoustredeniUcastniciView(SoustredeniUcastniciBaseView): | ||||
| 	""" HTML tabulka účastníků pro tisk. """ | ||||
| 	model = Soustredeni_Ucastnici | ||||
| 	template_name = 'soustredeni/seznam_ucastniku.html' | ||||
| 
 | ||||
| 
 | ||||
| def soustredeniUcastniciExportView(request, soustredeni): | ||||
| 	soustredeni = get_object_or_404(Soustredeni, id=soustredeni) | ||||
| 	ucastnici = soustredeni.ucastnici.all() | ||||
| class SoustredeniUcastniciExportView(SoustredeniUcastniciBaseView): | ||||
| 	""" CSV tabulka účastníků. """ | ||||
| 	def render(self, request, *args, **kwargs): | ||||
| 		response = HttpResponse(content_type='text/csv') | ||||
| 		response['Content-Disposition'] = 'attachment; filename="ucastnici.csv"' | ||||
| 
 | ||||
| 		writer = csv.writer(response) | ||||
| 		writer.writerow(["jmeno", "prijmeni", "rok_maturity", "telefon", "email", "ulice", "mesto", "psc","stat"]) | ||||
| 	for u in ucastnici: | ||||
| 		o = u.osoba | ||||
| 		writer.writerow([o.jmeno, o.prijmeni, str(u.rok_maturity), o.telefon, o.email, o.ulice, o.mesto, o.psc, o.stat.name]) | ||||
| 		for u in self.object_list: | ||||
| 			o = u.resitel.osoba | ||||
| 			writer.writerow([o.jmeno, o.prijmeni, str(u.resitel.rok_maturity), o.telefon, o.email, o.ulice, o.mesto, o.psc, o.stat.name]) | ||||
| 		return response | ||||
| 
 | ||||
| def soustredeniStvrzenkyView(request, soustredeni): | ||||
| 	soustredeni = get_object_or_404(Soustredeni, id=soustredeni) | ||||
| 	ucastnici = soustredeni.ucastnici.all() | ||||
| 	if ucastnici.count() == 0: | ||||
| 		return HttpResponse( | ||||
| 			render(request, 'universal.html', { | ||||
| 				'title': 'Není pro koho vyrobit stvrzenky.', | ||||
| 				'text': 'Právě ses pokusil/a vygenerovat stvrzenky pro prázdnou množinu lidí. Můžeš to zkusit změnit, případně se zeptej webařů :-)', | ||||
| 				}), | ||||
| 			status=http.HTTPStatus.NOT_FOUND, | ||||
| 			) | ||||
| 	castka = Nastaveni.get_solo().cena_sous | ||||
| 	tex = render(request, 'soustredeni/stvrzenky.tex', {'ucastnici': ucastnici, 'soustredeni': soustredeni, 'castka': castka}).content | ||||
| 
 | ||||
| 	with tempfile.TemporaryDirectory() as tempdirfn: | ||||
| 		tempdir = Path(tempdirfn) | ||||
| 		with open(tempdir / "stvrzenky.tex", "w") as texfile: | ||||
| 			texfile.write(tex.decode()) | ||||
| class SoustredeniStvrzenkyView( | ||||
| 	various.views.generic.TeXResponseMixin, | ||||
| 	SoustredeniUcastniciBaseView, | ||||
| ): | ||||
| 	template_name = 'soustredeni/stvrzenky.tex' | ||||
| 	dalsi_potrebne_soubory = [find('soustredeni/logomm.pdf')] | ||||
| 
 | ||||
| 		shutil.copy(find('soustredeni/logomm.pdf'), tempdir) | ||||
| 		subprocess.call(["pdflatex", "stvrzenky.tex"], cwd = tempdir, stdout=subprocess.DEVNULL) | ||||
| 	if_prazdny_title = "Není pro koho vyrobit stvrzenky." | ||||
| 	if_prazdny_text = "Právě ses pokusil/a vygenerovat stvrzenky pro prázdnou množinu lidí. Můžeš to zkusit změnit, případně se zeptej webařů :-)" | ||||
| 
 | ||||
| 		with open(tempdir / "stvrzenky.pdf", "rb") as pdffile: | ||||
| 			response = HttpResponse(pdffile.read(), content_type='application/pdf') | ||||
| 	return response | ||||
| 	def get_context_data(self, **kwargs): | ||||
| 		context = super().get_context_data(**kwargs) | ||||
| 
 | ||||
| 		context["castka"] = Nastaveni.get_solo().cena_sous | ||||
| 		context["soustredeni"] = self.soustredeni | ||||
| 		context["ucastnici"] = self.object_list | ||||
| 		return context | ||||
| 
 | ||||
| class SoustredeniAbstraktyView(generic.DetailView): | ||||
| 	model = Soustredeni | ||||
|  |  | |||
|  | @ -1,5 +1,22 @@ | |||
| """ | ||||
| Stejně jako je `django.views.generic` jsou zde generické Views | ||||
| a pár mixinů, které upravují chování Views. | ||||
| """ | ||||
| 
 | ||||
| import http | ||||
| import shutil | ||||
| import subprocess | ||||
| import tempfile | ||||
| 
 | ||||
| from pathlib import Path | ||||
| 
 | ||||
| import django.views | ||||
| 
 | ||||
| from django.http import HttpResponse | ||||
| from django.shortcuts import render | ||||
| from django.template.loader import render_to_string | ||||
| from django.views import generic | ||||
| 
 | ||||
| 
 | ||||
| def viewMethodSwitch(get, post): | ||||
| 	""" | ||||
|  | @ -27,3 +44,65 @@ def viewMethodSwitch(get, post): | |||
| 			return thePostView(request, *args, **kwargs) | ||||
| 
 | ||||
| 	return NewView.as_view() | ||||
| 
 | ||||
| 
 | ||||
| class NeprazdnyListView(generic.ListView): | ||||
| 	""" | ||||
| 		Použití jako generic.ListView, jen při prázdném listu vyhodí M&M stránku | ||||
| 		s titlem `self.if_prazdny_title` a textem `self.if_prazdny_text` | ||||
| 		a způsob renderování (např. CSV) lze změnit přepsáním metody render. | ||||
| 	""" | ||||
| 	allow_empty = False # Interní djangová věc | ||||
| 	if_prazdny_title = "V seznamu nic není" | ||||
| 	if_prazdny_text = "V seznamu nic není. Zkus to napravit v adminu, nebo se zeptej webařů." | ||||
| 
 | ||||
| 	# Skoro copy-paste generic.list.ListView.get, | ||||
| 	# protože nemůžu chytat 404, neboť může nastat i v get_context_data | ||||
| 	def get(self, request, *args, **kwargs): | ||||
| 		self.object_list = self.get_queryset() | ||||
| 
 | ||||
| 		if self.get_paginate_by(self.object_list) is not None and hasattr( | ||||
| 				self.object_list, "exists" | ||||
| 		): | ||||
| 			is_empty = not self.object_list.exists() | ||||
| 		else: | ||||
| 			is_empty = not self.object_list | ||||
| 		if is_empty: | ||||
| 			return render(request, 'universal.html', { | ||||
| 				'title': self.if_prazdny_title, | ||||
| 				'text': self.if_prazdny_text, | ||||
| 			}, status=http.HTTPStatus.NOT_FOUND) | ||||
| 
 | ||||
| 		return self.render(request, *args, **kwargs) | ||||
| 
 | ||||
| 	# Tohle jsem vyčlenil, aby šlo generovat i něco jiného než template | ||||
| 	def render(self, request, *args, **kwargs): | ||||
| 		context = self.get_context_data() | ||||
| 		return self.render_to_response(context) | ||||
| 
 | ||||
| 
 | ||||
| class TeXResponseMixin: | ||||
| 	""" | ||||
| 		Mixin pro TemplateView, aby výsledek projel TeXem a vrátil rovnou PDF. | ||||
| 		Obrázky a jiné soubory lze přidat nastavením `dalsi_potrebne_soubory` | ||||
| 		(např. na `[django.contrib.staticfiles.finders.find('bla')]`, | ||||
| 		nebo jiný seznam absolutních cest). | ||||
| 	""" | ||||
| 	dalsi_potrebne_soubory = [] | ||||
| 	tex_prikaz = "pdflatex" | ||||
| 
 | ||||
| 	def render_to_response(self, context, **response_kwargs): | ||||
| 		zdrojak = render_to_string(self.get_template_names(), context) | ||||
| 
 | ||||
| 		with tempfile.TemporaryDirectory() as tempdirfn: | ||||
| 			tempdir = Path(tempdirfn) | ||||
| 			with open(tempdir / "main.tex", "w") as texfile: | ||||
| 				texfile.write(zdrojak) | ||||
| 			for file in self.dalsi_potrebne_soubory: | ||||
| 				shutil.copy(file, tempdir) | ||||
| 			subprocess.call([self.tex_prikaz, "main.tex"], cwd=tempdir, stdout=subprocess.DEVNULL) | ||||
| 
 | ||||
| 			with open(tempdir / "main.pdf", "rb") as pdffile: | ||||
| 				response = HttpResponse(pdffile.read(), content_type='application/pdf', **response_kwargs) | ||||
| 		return response | ||||
| 
 | ||||
|  |  | |||
		Loading…
	
		Reference in a new issue