Předělání sousových views #57
					 4 changed files with 134 additions and 54 deletions
				
			
		|  | @ -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,16 +1,12 @@ | ||||||
| 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 | ||||||
| 
 | 
 | ||||||
| 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 | ||||||
|  | @ -34,73 +30,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) | ||||||
| 
				
				zelvuska marked this conversation as resolved
				
			 | |||||||
| 		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 | ||||||
| 
				
				zelvuska marked this conversation as resolved
				
					
					
						Outdated
					
				
			 
				
					
						ledoian
						commented  Tohle je naše věc, nebo něco co využívá  Tohle je naše věc, nebo něco co využívá `ListView` interně? | |||||||
|  | 	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" | ||||||
| 
				
				zelvuska marked this conversation as resolved
				
					
					
						Outdated
					
				
			 
				
					
						ledoian
						commented  Navrhuju spíš pojmenovat  Navrhuju spíš pojmenovat `tex_command` nebo i česky `tex_prikaz`. | |||||||
|  | 
 | ||||||
|  | 	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
	
	
Nemám to teď nacachované, ale pročže nepoužijeme generický export řešitelů, který máme už u výsledkovek?
Protože to takhle už bylo (tj. není to předmětem tohodle pullrequestu). Teď bych to nechal a můžem upravit časem.