verejny kontaktnicek #71
					 7 changed files with 142 additions and 57 deletions
				
			
		|  | @ -12,7 +12,7 @@ | ||||||
| 
 | 
 | ||||||
| <br> | <br> | ||||||
| 
 | 
 | ||||||
| {% for rocnik, hodnoceni in podle_rocniku %} | {% for rocnik, hodnoceni, suma_bodu in podle_rocniku %} | ||||||
| <h1>Ročník {{ rocnik }}</h1> | <h1>Ročník {{ rocnik }}</h1> | ||||||
| <table class="moje_reseni plne_ohranicena_tabulka"> | <table class="moje_reseni plne_ohranicena_tabulka"> | ||||||
| 	<tr> | 	<tr> | ||||||
|  | @ -33,7 +33,7 @@ | ||||||
| 	</tr> | 	</tr> | ||||||
| 	{% endfor %} | 	{% endfor %} | ||||||
| </table> | </table> | ||||||
| 
 | <p>Celkový počet bodů {{suma_bodu}}</p> | ||||||
| <br> | <br> | ||||||
| 
 | 
 | ||||||
| {% endfor %} | {% 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 :-/ | 		# 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 = [] | 		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): | 		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 | 		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í | ||||||
| 		return ctx | 		return ctx | ||||||
|  |  | ||||||
|  | @ -29,7 +29,7 @@ | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| {% for u in ucastnici %} | {% for u in ucastnici %} | ||||||
| 	{% with o=u.osoba %} | 	{% with o=u.resitel.osoba %} | ||||||
| 	\stvrzenka{{o.jmeno|sloz}}{{o.prijmeni|sloz}} | 	\stvrzenka{{o.jmeno|sloz}}{{o.prijmeni|sloz}} | ||||||
| 	{% endwith %} | 	{% endwith %} | ||||||
| {% endfor %} | {% endfor %} | ||||||
|  |  | ||||||
|  | @ -26,12 +26,12 @@ urlpatterns = [ | ||||||
| 				), | 				), | ||||||
| 				path( | 				path( | ||||||
| 					'export_ucastniku', | 					'export_ucastniku', | ||||||
| 					org_required(views.soustredeniUcastniciExportView), | 					org_required(views.SoustredeniUcastniciExportView.as_view()), | ||||||
| 					name='soustredeni_ucastnici_export' | 					name='soustredeni_ucastnici_export' | ||||||
| 				), | 				), | ||||||
| 				path( | 				path( | ||||||
| 					'stvrzenky.pdf', | 					'stvrzenky.pdf', | ||||||
| 					org_required(views.soustredeniStvrzenkyView), | 					org_required(views.SoustredeniStvrzenkyView.as_view()), | ||||||
| 					name='soustredeni_ucastnici_stvrzenky' | 					name='soustredeni_ucastnici_stvrzenky' | ||||||
| 				), | 				), | ||||||
| 				path( | 				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.http import HttpResponse | ||||||
| from django.views import generic | from django.views import generic | ||||||
| from django.contrib.staticfiles.finders import find | from django.contrib.staticfiles.finders import find | ||||||
|  | @ -6,13 +6,9 @@ from django.http import Http404 | ||||||
| from django.core.exceptions import PermissionDenied | from django.core.exceptions import PermissionDenied | ||||||
| 
 | 
 | ||||||
| import csv | 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 .models import Soustredeni, Soustredeni_Ucastnici | ||||||
| from various.models import Nastaveni | from various.models import Nastaveni | ||||||
|  | @ -36,73 +32,78 @@ class SoustredeniListView(generic.ListView): | ||||||
| 		) | 		) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def soustredeniObalkyView(request, soustredeni): | class KonkretniSoustredeniMixin: | ||||||
| 	soustredeni = get_object_or_404(Soustredeni, id=soustredeni) | 	""" Přidá k View s parametrem `soustredeni` atribut `self.soustredeni` """ | ||||||
| 	return personalni.views.obalkyView(request, soustredeni.ucastnici.all()) | 	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 | 	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): | 	def get_queryset(self): | ||||||
| 		soustredeni = get_object_or_404( |  | ||||||
| 			Soustredeni, |  | ||||||
| 			pk=self.kwargs["soustredeni"] |  | ||||||
| 		) |  | ||||||
| 		return Soustredeni_Ucastnici.objects.filter( | 		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): | class SoustredeniMailyUcastnikuView(SoustredeniUcastniciBaseView): | ||||||
| 	""" Seznam e-mailů řešitelů oddělených čárkami. """ | 	""" Seznam e-mailů řešitelů oddělených čárkami. """ | ||||||
| 	model = Soustredeni_Ucastnici |  | ||||||
| 	template_name = 'soustredeni/maily_ucastniku.txt' | 	template_name = 'soustredeni/maily_ucastniku.txt' | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class SoustredeniUcastniciView(SoustredeniUcastniciBaseView): | class SoustredeniUcastniciView(SoustredeniUcastniciBaseView): | ||||||
| 	""" HTML tabulka účastníků pro tisk. """ | 	""" HTML tabulka účastníků pro tisk. """ | ||||||
| 	model = Soustredeni_Ucastnici |  | ||||||
| 	template_name = 'soustredeni/seznam_ucastniku.html' | 	template_name = 'soustredeni/seznam_ucastniku.html' | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def soustredeniUcastniciExportView(request, soustredeni): | class SoustredeniUcastniciExportView(SoustredeniUcastniciBaseView): | ||||||
| 	soustredeni = get_object_or_404(Soustredeni, id=soustredeni) | 	""" CSV tabulka účastníků. """ | ||||||
| 	ucastnici = soustredeni.ucastnici.all() | 	def render(self, request, *args, **kwargs): | ||||||
| 		response = HttpResponse(content_type='text/csv') | 		response = HttpResponse(content_type='text/csv') | ||||||
| 		response['Content-Disposition'] = 'attachment; filename="ucastnici.csv"' | 		response['Content-Disposition'] = 'attachment; filename="ucastnici.csv"' | ||||||
| 
 | 
 | ||||||
| 		writer = csv.writer(response) | 		writer = csv.writer(response) | ||||||
| 		writer.writerow(["jmeno", "prijmeni", "rok_maturity", "telefon", "email", "ulice", "mesto", "psc","stat"]) | 		writer.writerow(["jmeno", "prijmeni", "rok_maturity", "telefon", "email", "ulice", "mesto", "psc","stat"]) | ||||||
| 	for u in ucastnici: | 		for u in self.object_list: | ||||||
| 		o = u.osoba | 			o = u.resitel.osoba | ||||||
| 		writer.writerow([o.jmeno, o.prijmeni, str(u.rok_maturity), o.telefon, o.email, o.ulice, o.mesto, o.psc, o.stat.name]) | 			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 | 		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: | class SoustredeniStvrzenkyView( | ||||||
| 		tempdir = Path(tempdirfn) | 	various.views.generic.TeXResponseMixin, | ||||||
| 		with open(tempdir / "stvrzenky.tex", "w") as texfile: | 	SoustredeniUcastniciBaseView, | ||||||
| 			texfile.write(tex.decode()) | ): | ||||||
|  | 	template_name = 'soustredeni/stvrzenky.tex' | ||||||
|  | 	dalsi_potrebne_soubory = [find('soustredeni/logomm.pdf')] | ||||||
| 
 | 
 | ||||||
| 		shutil.copy(find('soustredeni/logomm.pdf'), tempdir) | 	if_prazdny_title = "Není pro koho vyrobit stvrzenky." | ||||||
| 		subprocess.call(["pdflatex", "stvrzenky.tex"], cwd = tempdir, stdout=subprocess.DEVNULL) | 	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: | 	def get_context_data(self, **kwargs): | ||||||
| 			response = HttpResponse(pdffile.read(), content_type='application/pdf') | 		context = super().get_context_data(**kwargs) | ||||||
| 	return response | 
 | ||||||
|  | 		context["castka"] = Nastaveni.get_solo().cena_sous | ||||||
|  | 		context["soustredeni"] = self.soustredeni | ||||||
|  | 		context["ucastnici"] = self.object_list | ||||||
|  | 		return context | ||||||
| 
 | 
 | ||||||
| class SoustredeniAbstraktyView(generic.DetailView): | class SoustredeniAbstraktyView(generic.DetailView): | ||||||
| 	model = Soustredeni | 	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 | 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): | def viewMethodSwitch(get, post): | ||||||
| 	""" | 	""" | ||||||
|  | @ -27,3 +44,65 @@ def viewMethodSwitch(get, post): | ||||||
| 			return thePostView(request, *args, **kwargs) | 			return thePostView(request, *args, **kwargs) | ||||||
| 
 | 
 | ||||||
| 	return NewView.as_view() | 	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