Galerie: začátek podpory jiných typů [WIP]
Co funguje: ~~nic, protože není vygenerovaná migrace~~ Nejspíš lecos, ale nejde to otestovat. Speciálně, pokud zavolám funkce z `galerie.typy` ručně ze shellu, tak se to popřeškáluje a výsledná URL jde skutečně vyrenderovat na webu (resp. šlo v nějaké průběžné verzi před zavedením templatetagu). TODO: - ve views a modelech se pořád ještě vyskytuje `obrazek_stredni` a `obrazek_velky` - nevím, co dělá Admin a nahrávání obrázků - chybí funkce na tipování typů nahraných obrázků (a její použití v kódu) - podpora pro videjka zatím není vůbec (jen připravený kód) - DummyBazmek nemá použitelné placeholdery (představuji si SVG s nějakým vykřičníkem a odkazem do Admina
This commit is contained in:
		
							parent
							
								
									b7498b42b2
								
							
						
					
					
						commit
						b25d475148
					
				
					 6 changed files with 169 additions and 40 deletions
				
			
		|  | @ -28,14 +28,14 @@ def prepnout_fotogalerii_do_org_rezimu(modeladmin, request, queryset): | ||||||
| 
 | 
 | ||||||
| class GalerieInline(admin.TabularInline): | class GalerieInline(admin.TabularInline): | ||||||
| 	model = Obrazek | 	model = Obrazek | ||||||
| 	fields = ['obrazek_velky', 'nazev', 'popis', 'poradi'] | 	fields = ['soubor', 'nazev', 'popis', 'poradi'] | ||||||
| 	readonly_fields = ['nazev'] | 	readonly_fields = ['nazev'] | ||||||
| 	formfield_overrides = { | 	formfield_overrides = { | ||||||
| 		models.TextField: {'widget': forms.TextInput}, | 		models.TextField: {'widget': forms.TextInput}, | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| class ObrazekAdmin(admin.ModelAdmin): | class ObrazekAdmin(admin.ModelAdmin): | ||||||
| 	list_display = ('obrazek_velky', 'nazev', 'popis', 'obrazek_maly_tag', 'poradi') | 	list_display = ('soubor', 'nazev', 'popis', 'poradi') | ||||||
| 	search_fields = ['nazev','popis'] | 	search_fields = ['nazev','popis'] | ||||||
| 
 | 
 | ||||||
| class GalerieAdmin(admin.ModelAdmin): | class GalerieAdmin(admin.ModelAdmin): | ||||||
|  |  | ||||||
|  | @ -22,10 +22,12 @@ def obrazek_filename_maly(): | ||||||
| 	pass | 	pass | ||||||
| def obrazek_filename_stredni(): | def obrazek_filename_stredni(): | ||||||
| 	pass | 	pass | ||||||
|  | def obrazek_filename(): | ||||||
|  | 	pass | ||||||
| def obrazek_filename_velky(): | def obrazek_filename_velky(): | ||||||
| 	pass | 	pass | ||||||
| 
 | 
 | ||||||
| def obrazek_filename(self, filename): | def galerie_filename(self, filename): | ||||||
| 	gal = self.galerie | 	gal = self.galerie | ||||||
| 	cislo_gal = gal.pk | 	cislo_gal = gal.pk | ||||||
| 
 | 
 | ||||||
|  | @ -42,31 +44,32 @@ def obrazek_filename(self, filename): | ||||||
| 
 | 
 | ||||||
| 	return os.path.join(*cesta) | 	return os.path.join(*cesta) | ||||||
| 
 | 
 | ||||||
|  | # Technicky misnomer: takový `Obrazek` může být i videjko a výhledově i něco dalšího… | ||||||
| class Obrazek(models.Model): | class Obrazek(models.Model): | ||||||
| 	# „originál“ (modulo max. velikost uploadu na web FIXME!) | 	# „originál“ (modulo max. velikost uploadu na web FIXME!) | ||||||
| 	obrazek_velky = models.FileField(upload_to=obrazek_filename, | 	soubor = models.FileField(upload_to=galerie_filename, | ||||||
| 		help_text = "Lze vložit libovolně velký obrázek. Ideální je, aby alespoň jeden rozměr měl alespoň 500px.") | 		help_text = "Lze vložit libovolně velký obrázek. Ideální je, aby alespoň jeden rozměr měl alespoň 500px.") | ||||||
| 	# To, co se zobrazí v galerii jako hlavní obrázek (při prohlížení konkrétního obrázku a jako tittulní obrázek u galerií, které nemají vlastní obrázky (kupř. Vávrovka 2015)) | 	class Typ(models.TextChoices): | ||||||
| #	obrazek_stredni = ImageSpecField(source='obrazek_velky', | 		OBRAZEK = 'obrazek', 'Obrázek' | ||||||
| #				processors=[Transpose(Transpose.AUTO), ResizeToFit(900, 675, upscale=False)], | 		VIDEO = 'video', 'Video' | ||||||
| #				options={'quality': 95}) | 		NEVIM = 'nevim', 'Neznámý typ' | ||||||
| 	# Zmenšené obrázky v přehledu obrázků a pod hlavním obrázkem (předchozí/následující) | 	typ = models.CharField('Typ', max_length=16, blank=False, null=False, choices=Typ.choices, default=Typ.NEVIM) | ||||||
| #	obrazek_maly = ImageSpecField(source='obrazek_velky', | 	# Filename by default; slouží k řazení | ||||||
| #				processors=[Transpose(Transpose.AUTO), ResizeToFit(167, 167, upscale=False)], |  | ||||||
| #				options={'quality': 95}) |  | ||||||
| 	nazev = models.CharField('Název', max_length=50, blank=True, null=True) | 	nazev = models.CharField('Název', max_length=50, blank=True, null=True) | ||||||
|  | 	# ~~Rádoby~~ vtipný popisek od orgů | ||||||
| 	popis = models.TextField('Popis', blank=True, null=True) | 	popis = models.TextField('Popis', blank=True, null=True) | ||||||
| 	datum_vlozeni = models.DateTimeField('Datum vložení', auto_now_add=True) | 	datum_vlozeni = models.DateTimeField('Datum vložení', auto_now_add=True) | ||||||
| 	galerie = models.ForeignKey('Galerie', blank=True, null=True, on_delete=models.SET_NULL) | 	galerie = models.ForeignKey('Galerie', blank=True, null=True, on_delete=models.SET_NULL) | ||||||
|  | 	# Primární klíč k řazení pro overridování řazení podle názvu | ||||||
| 	poradi = models.IntegerField('Pořadí', blank=True, null=True) | 	poradi = models.IntegerField('Pořadí', blank=True, null=True) | ||||||
| 
 | 
 | ||||||
| 	def __str__(self): | 	def __str__(self): | ||||||
| 			return self.obrazek_velky.name | 		return f'Obrázek {self.nazev} ({self.soubor.name})' | ||||||
| 
 | 
 | ||||||
| 	class Meta: | 	class Meta: | ||||||
| 		verbose_name = 'Obrázek' | 		verbose_name = 'Obrázek' | ||||||
| 		verbose_name_plural = 'Obrázky' | 		verbose_name_plural = 'Obrázky' | ||||||
| 		ordering = ['nazev'] | 		ordering = ['nazev'] #FIXME: zohlednit pořadí a asi i příslušnost ke galerii | ||||||
| 
 | 
 | ||||||
| 	def save(self, *args, **kwargs): | 	def save(self, *args, **kwargs): | ||||||
| 		# obrázek potřebuje název, protože se z něj generuje cesta pro jeho uložení | 		# obrázek potřebuje název, protože se z něj generuje cesta pro jeho uložení | ||||||
|  | @ -75,6 +78,17 @@ class Obrazek(models.Model): | ||||||
| 			self.nazev = os.path.basename(self.obrazek_velky.name) | 			self.nazev = os.path.basename(self.obrazek_velky.name) | ||||||
| 		super(Obrazek, self).save(*args, **kwargs) | 		super(Obrazek, self).save(*args, **kwargs) | ||||||
| 
 | 
 | ||||||
|  | 	def jako_bazmek(self): | ||||||
|  | 		""" | ||||||
|  | 		Hlavní metoda pro dělání `galerie.typy.ZobrazitelnyBazmek` z DB objektů | ||||||
|  | 		""" | ||||||
|  | 		import galerie.typy as typy # pozor, cyklí! | ||||||
|  | 		match self.typ: | ||||||
|  | 			case self.Typ.OBRAZEK: return typy.Obrazek(self) | ||||||
|  | 			case self.Typ.VIDEO: return typy.Video(self) | ||||||
|  | 			case self.Typ.NEVIM: return typy.DummyBazmek(self) | ||||||
|  | 			case _: raise ValueError("Neznámý typ obrázku, bug v kódu!") | ||||||
|  | 
 | ||||||
| 
 | 
 | ||||||
| class Galerie(models.Model): | class Galerie(models.Model): | ||||||
| 	nazev = models.CharField('Název', max_length=100) | 	nazev = models.CharField('Název', max_length=100) | ||||||
|  |  | ||||||
|  | @ -1,5 +1,6 @@ | ||||||
| {% extends "galerie/base.html" %} | {% extends "galerie/base.html" %} | ||||||
| 
 | 
 | ||||||
|  | {% load bazmeky %} | ||||||
| 
 | 
 | ||||||
| {% block nadpis1a %} | {% block nadpis1a %} | ||||||
| {{galerie.nazev}}: {{ obrazek.popis | default:"Fotka" }} | {{galerie.nazev}}: {{ obrazek.popis | default:"Fotka" }} | ||||||
|  | @ -63,11 +64,12 @@ | ||||||
|       {% endwith %} |       {% endwith %} | ||||||
|     {% endif%} |     {% endif%} | ||||||
|     <span id="nahoru" class="kotva_obrazku"></span> |     <span id="nahoru" class="kotva_obrazku"></span> | ||||||
|     <img src="{{obrazek.obrazek_stredni.url}}" |     {% zobrazit obrazek.jako_bazmek | ||||||
|          height="{{vyska}}" |          height=vyska | ||||||
|          width="{{sirka}}" |          width=sirka | ||||||
|          alt="{{obrazek.popis}}" |          alt=obrazek.popis | ||||||
|          class="obrazek"> |          title=obrazek.popis | ||||||
|  |          class=obrazek" %} | ||||||
|           |           | ||||||
|     {% if obrazky_dalsi %} |     {% if obrazky_dalsi %} | ||||||
|       {% with obrazky_dalsi|first as dalsi_obrazek %} |       {% with obrazky_dalsi|first as dalsi_obrazek %} | ||||||
|  | @ -78,7 +80,7 @@ | ||||||
|     {% endif%} |     {% endif%} | ||||||
|   </div> |   </div> | ||||||
|   <!--<div>--> |   <!--<div>--> | ||||||
|   <!--<a href="{{ obrazek.obrazek_velky.url }}">Obrázek v plné velikosti</a>--> |   <!--<a href="{{ obrazek.soubor.url }}">Obrázek v plné velikosti</a>--> | ||||||
|   <!--</div>--> |   <!--</div>--> | ||||||
| 
 | 
 | ||||||
|   {# Popisek fotky #} |   {# Popisek fotky #} | ||||||
|  | @ -109,21 +111,21 @@ | ||||||
|       {% endif %} |       {% endif %} | ||||||
|       {# nahledy predchozich obrazku #} |       {# nahledy predchozich obrazku #} | ||||||
|       {% for obrazek in obrazky_predchozi %} |       {% for obrazek in obrazky_predchozi %} | ||||||
|       <a href="../{{obrazek.pk}}#nahoru"><img src="{{obrazek.obrazek_maly.url}}" height="100"></a> |       <a href="../{{obrazek.pk}}#nahoru">{% zmenseny_nahled obrazek.jako_bazmek height=100 %}</a> | ||||||
|       {% endfor %} |       {% endfor %} | ||||||
|     </div> |     </div> | ||||||
| 
 | 
 | ||||||
|     <img src={{obrazek.obrazek_maly.url}} |     {% zmenseny_nahled obrazek.jako_bazmek | ||||||
|        height="{{obrazek.obrazek_maly.height}}" |        alt=obrazek.popis | ||||||
|        width="{{obrazek.obrazek_maly.width}}" |        class=obrazek | ||||||
|        alt="{{obrazek.popis}}" |        id=prostredni %} | ||||||
|        class="obrazek" |        {# height=obrazek.obrazek_maly.height | ||||||
|        id="prostredni"> |        width=obrazek.obrazek_maly.width #} | ||||||
| 
 | 
 | ||||||
|     <div class="navigace"> |     <div class="navigace"> | ||||||
|       {# nahledy nasledujicich obrazku #} |       {# nahledy nasledujicich obrazku #} | ||||||
|       {% for obrazek in obrazky_dalsi %} |       {% for obrazek in obrazky_dalsi %} | ||||||
|       <a href="../{{obrazek.pk}}#nahoru"><img src="{{obrazek.obrazek_maly.url}}" height="100"></a> |       <a href="../{{obrazek.pk}}#nahoru">{% zmenseny_nahled obrazek.jako_bazmek height=100 %}</a> | ||||||
|       {% endfor %} |       {% endfor %} | ||||||
|       {# odkaz na nasledujici galerii #} |       {# odkaz na nasledujici galerii #} | ||||||
|       {% if nasledujici_galerie %} |       {% if nasledujici_galerie %} | ||||||
|  |  | ||||||
|  | @ -1,5 +1,7 @@ | ||||||
| {% extends "galerie/base.html" %} | {% extends "galerie/base.html" %} | ||||||
| 
 | 
 | ||||||
|  | {% load bazmeky %} | ||||||
|  | 
 | ||||||
| {% block nadpis1a %} | {% block nadpis1a %} | ||||||
| Galerie {{galerie.nazev}} | Galerie {{galerie.nazev}} | ||||||
| {% endblock %} | {% endblock %} | ||||||
|  | @ -23,7 +25,7 @@ Galerie {{galerie.nazev}} | ||||||
|   {% if not obrazky %} |   {% if not obrazky %} | ||||||
|    <div class="galerie_hlavicka"> |    <div class="galerie_hlavicka"> | ||||||
|     {% if galerie.titulni_obrazek %} |     {% if galerie.titulni_obrazek %} | ||||||
|       <img src="{{ galerie.titulni_obrazek.obrazek_stredni.url }}" class="titulni_obrazek"> |     {% zobrazit galerie.titulni_obrazek.jako_bazmek class=titulni_obrazek %} | ||||||
|     {% endif %} |     {% endif %} | ||||||
|    </div> |    </div> | ||||||
|   {% endif %} |   {% endif %} | ||||||
|  | @ -52,10 +54,7 @@ Galerie {{galerie.nazev}} | ||||||
|           {% endif %} |           {% endif %} | ||||||
|          class="podgalerie_nahled"> |          class="podgalerie_nahled"> | ||||||
|             {% if galerie.titulni_obrazek %} |             {% if galerie.titulni_obrazek %} | ||||||
|               {% with galerie.titulni_obrazek.obrazek_maly as obrazek %} |               {% zmenseny_nahled galerie.titulni_obrazek.jako_bazmek %} | ||||||
|                 <img src="{{ obrazek.url }}" |  | ||||||
|                 /> |  | ||||||
|               {% endwith %} |  | ||||||
|             {% endif %} |             {% endif %} | ||||||
|             <div class="nazev_galerie"> |             <div class="nazev_galerie"> | ||||||
|             {{ galerie|truncatechars:max_delka_nazvu }} |             {{ galerie|truncatechars:max_delka_nazvu }} | ||||||
|  | @ -87,13 +86,12 @@ Galerie {{galerie.nazev}} | ||||||
|             {% if obrazek.popis %} |             {% if obrazek.popis %} | ||||||
|               title="{{ obrazek.popis }}" |               title="{{ obrazek.popis }}" | ||||||
|             {% endif %} |             {% endif %} | ||||||
|             href="./{{obrazek.pk}}#nahoru" class="galerie_nahled"><span class="vystredeno"></span><img |             href="./{{obrazek.pk}}#nahoru" class="galerie_nahled"> | ||||||
|             src="{{obrazek.obrazek_maly.url}}" |           <span class="vystredeno"></span> | ||||||
|             {% if obrazek.popis %} |           {% zmenseny_nahled obrazek.jako_bazmek  %} | ||||||
|               title="{{ obrazek.popis }}" | {#            title=obrazek.popis (a byl tu if, že se použil jen když existoval…) | ||||||
|             {% endif %} |             width=obrazek.obrazek_maly.width | ||||||
|             width="{{ obrazek.obrazek_maly.width }}" |             height=obrazek.obrazek_maly.height  #} | ||||||
|             height="{{ obrazek.obrazek_maly.height }}" /> |  | ||||||
|         </a> |         </a> | ||||||
|     {% endfor %} |     {% endfor %} | ||||||
|     <br> |     <br> | ||||||
|  |  | ||||||
							
								
								
									
										13
									
								
								galerie/templatetags/bazmeky.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								galerie/templatetags/bazmeky.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,13 @@ | ||||||
|  | """ | ||||||
|  | Pomocné tagy pro zobrazování `galerie.typy.ZobrazitelnyBazmek`. Jen volají příslušnou metodu na bazmeku. | ||||||
|  | """ | ||||||
|  | from django import template | ||||||
|  | register = template.Library() | ||||||
|  | 
 | ||||||
|  | @register.simple_tag | ||||||
|  | def zobrazit(bazmek, /, **kwargs): | ||||||
|  | 	return bazmek.zobrazit(**kwargs) | ||||||
|  | 
 | ||||||
|  | @register.simple_tag | ||||||
|  | def zmenseny_nahled(bazmek, /, **kwargs): | ||||||
|  | 	return bazmek.zmenseny_nahled(**kwargs) | ||||||
							
								
								
									
										102
									
								
								galerie/typy.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										102
									
								
								galerie/typy.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,102 @@ | ||||||
|  | """ | ||||||
|  | Naše typy objektů v galeriích. | ||||||
|  | 
 | ||||||
|  | V databázi jsou všechny v jedné tabulce, protože se liší jen prezentací navenek. Všechny implementují ~~rozhraní~~ ABC `ZobrazitelnyBazmek`. | ||||||
|  | 
 | ||||||
|  | Doporučené použití: TODO | ||||||
|  | """ | ||||||
|  | 
 | ||||||
|  | import abc | ||||||
|  | from datetime import datetime | ||||||
|  | 
 | ||||||
|  | from django.utils.safestring import mark_safe, SafeString | ||||||
|  | from django.utils.html import format_html, format_html_join | ||||||
|  | 
 | ||||||
|  | from imagekit import ImageSpec | ||||||
|  | from imagekit.cachefiles import ImageCacheFile | ||||||
|  | from imagekit.processors import ResizeToFit, Transpose | ||||||
|  | 
 | ||||||
|  | HTML = str | SafeString | ||||||
|  | 
 | ||||||
|  | from galerie.models import Obrazek as DbObrazek | ||||||
|  | 
 | ||||||
|  | class ZobrazitelnyBazmek(abc.ABC): | ||||||
|  | 	def __init__(self, db_objekt): | ||||||
|  | 		self.db_objekt = db_objekt | ||||||
|  | 		# zkratka | ||||||
|  | 		self.soubor = db_objekt.soubor | ||||||
|  | 
 | ||||||
|  | 	# Volá se z templatetagu | ||||||
|  | 	@abc.abstractmethod | ||||||
|  | 	def zobrazit(self, **kwargs) -> HTML: | ||||||
|  | 		"""To, co se zobrazí v galerii jako hlavní obrázek (při prohlížení konkrétního obrázku a jako tittulní obrázek u galerií, které nemají vlastní obrázky (kupř. Vávrovka 2015))""" | ||||||
|  | 		... | ||||||
|  | 	@abc.abstractmethod | ||||||
|  | 	def zmenseny_nahled(self, **kwargs) -> HTML: | ||||||
|  | 		"""Zmenšené obrázky v přehledu obrázků a pod hlavním obrázkem (předchozí/následující)""" | ||||||
|  | 		... | ||||||
|  | 	 | ||||||
|  | 	@property | ||||||
|  | 	#@abc.abstractmethod | ||||||
|  | 	def cas_porizeni(self) -> datetime | None: | ||||||
|  | 		# TODO: použít tohle na automatické řazení věcí. Má vytáhnout datum z metadat | ||||||
|  | 		raise NotImplementedError("Prosím, naimplementuj hledání času vzniku v metadatech (nebo vrať None).") | ||||||
|  | 
 | ||||||
|  | 	#TODO: nativní rozměry? | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | ### Obrázky ### | ||||||
|  | # Odpovídá původnímu chování (bo se mi nechce vymýšlet novoty… | ||||||
|  | class ObrazekStredniSpec(ImageSpec): | ||||||
|  | 	"""Specifikace obrázku pro velké zobrazení""" | ||||||
|  | 	processors = [ | ||||||
|  | 		Transpose(Transpose.AUTO), # Rotuj podle dat v EXIFu | ||||||
|  | 		ResizeToFit(900, 675, upscale=False), | ||||||
|  | 		] | ||||||
|  | 	format = 'JPEG' | ||||||
|  | 	options = {'quality': 95} # Proč tolik? | ||||||
|  | 
 | ||||||
|  | class ObrazekMalySpec(ImageSpec): | ||||||
|  | 	"""Specifikace obrázku pro náhledy (pod hlavním obrázkem nebo v přehledu galerie)""" | ||||||
|  | 	processors = [ | ||||||
|  | 		Transpose(Transpose.AUTO), | ||||||
|  | 		ResizeToFit(167, 167, upscale=False), | ||||||
|  | 		] | ||||||
|  | 	format = 'JPEG' | ||||||
|  | 	options = {'quality': 95} | ||||||
|  | 
 | ||||||
|  | class Obrazek(ZobrazitelnyBazmek): | ||||||
|  | 	"""Obrázek pro zobrazení | ||||||
|  | 
 | ||||||
|  | 	Použije některý z ImageSpec-ů jako popis transformace a ImageCacheFile pro uložení výsledného obrázku. | ||||||
|  | 
 | ||||||
|  | 	Reference: https://django-imagekit.readthedocs.io/en/latest/#defining-specs-outside-of-models | ||||||
|  | 	Reference: https://django-imagekit.readthedocs.io/en/latest/caching.html | ||||||
|  | 	""" | ||||||
|  | 	def zobrazit(self, **kwargs): | ||||||
|  | 		# Jak se takový cachefile používá je potřeba vyčíst ze zdrojáků? | ||||||
|  | 		file = ImageCacheFile(ObrazekStredniSpec(source=self.soubor)) | ||||||
|  | 		file.generate() | ||||||
|  | 		attrs = format_html_join(' ', r'{}="{}"', ((mark_safe(k), v) for k, v in kwargs.items())) | ||||||
|  | 		html = format_html(r'<img src="{}" {} />', file.url, attrs) | ||||||
|  | 		return html | ||||||
|  | 
 | ||||||
|  | 	def zmenseny_nahled(self, **kwargs): | ||||||
|  | 		file = ImageCacheFile(ObrazekMalySpec(source=self.soubor)) | ||||||
|  | 		file.generate() | ||||||
|  | 		attrs = format_html_join(' ', r'{}="{}"', ((mark_safe(k), v) for k, v in kwargs.items())) | ||||||
|  | 		html = format_html(r'<img src="{}" {} />', file.url, attrs) | ||||||
|  | 		return html | ||||||
|  | 		 | ||||||
|  | 
 | ||||||
|  | class Video(ZobrazitelnyBazmek): | ||||||
|  | 	... | ||||||
|  | 
 | ||||||
|  | class DummyBazmek(ZobrazitelnyBazmek): | ||||||
|  | 	def zobrazit(self, **kwargs): | ||||||
|  | 		return r'<p>Tohle zobrazit neumím :-(</p>' | ||||||
|  | 	def zmenseny_nahled(self, **kwargs): | ||||||
|  | 		return r'<p>Tohle zobrazit neumím :-(</p>' | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def tipniTyp(soubor) -> DbObrazek.Typ: ... | ||||||
		Loading…
	
		Reference in a new issue
	
	 Pavel "LEdoian" Turinsky
						Pavel "LEdoian" Turinsky