RFC: Videjka a další bazmeky do galerií #99
|  | @ -30,14 +30,14 @@ def prepnout_fotogalerii_do_org_rezimu(modeladmin, request, queryset): | |||
| 
 | ||||
| class GalerieInline(admin.TabularInline): | ||||
| 	model = Obrazek | ||||
| 	fields = ['obrazek_velky', 'nazev', 'popis', 'obrazek_maly_tag', 'poradi'] | ||||
| 	readonly_fields = ['nazev', 'obrazek_maly_tag'] | ||||
| 	fields = ['soubor', 'nazev', 'popis', 'typ', 'poradi'] | ||||
| 	readonly_fields = ['nazev'] | ||||
| 	formfield_overrides = { | ||||
| 		models.TextField: {'widget': forms.TextInput}, | ||||
| 	} | ||||
| 
 | ||||
| class ObrazekAdmin(admin.ModelAdmin): | ||||
| 	list_display = ('obrazek_velky', 'nazev', 'popis', 'obrazek_maly_tag', 'poradi') | ||||
| 	list_display = ('soubor', 'nazev', 'popis', 'poradi') | ||||
| 	search_fields = ['nazev','popis'] | ||||
| 
 | ||||
| class GalerieAdmin(admin.ModelAdmin): | ||||
|  |  | |||
							
								
								
									
										33
									
								
								galerie/migrations/0017_obecne_typy_souboru.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						|  | @ -0,0 +1,33 @@ | |||
| # Generated by Django 4.2.20 on 2025-05-05 00:21 | ||||
| 
 | ||||
| from django.db import migrations, models | ||||
| import galerie.models | ||||
| 
 | ||||
| def zatim_byly_jen_obrazky(apps, schema_editor): | ||||
|     Obrazek = apps.get_model("galerie", "Obrazek") | ||||
|     Obrazek.objects.all().update(typ='obrazek') | ||||
| 
 | ||||
| class Migration(migrations.Migration): | ||||
| 
 | ||||
|     dependencies = [ | ||||
|         ('galerie', '0016_alter_obrazek_galerie'), | ||||
|     ] | ||||
| 
 | ||||
|     operations = [ | ||||
|         migrations.RenameField( | ||||
|             model_name='obrazek', | ||||
|             old_name='obrazek_velky', | ||||
|             new_name='soubor', | ||||
|         ), | ||||
|         migrations.AlterField( | ||||
|             model_name='obrazek', | ||||
|             name='soubor', | ||||
|             field=models.FileField(help_text='Lze vložit libovolně velký obrázek. Ideální je, aby alespoň jeden rozměr měl alespoň 500px.', upload_to=galerie.models.galerie_filename), | ||||
|         ), | ||||
|         migrations.AddField( | ||||
|             model_name='obrazek', | ||||
|             name='typ', | ||||
|             field=models.CharField(choices=[('obrazek', 'Obrázek'), ('video', 'Video'), ('nevim', 'Neznámý typ')], default='nevim', max_length=16, verbose_name='Typ'), | ||||
|         ), | ||||
|         migrations.RunPython(zatim_byly_jen_obrazky, migrations.RunPython.noop) | ||||
|  | ||||
|     ] | ||||
|  | @ -24,10 +24,12 @@ def obrazek_filename_maly(): | |||
| 	pass | ||||
| def obrazek_filename_stredni(): | ||||
| 	pass | ||||
| def obrazek_filename(): | ||||
| 	pass | ||||
| def obrazek_filename_velky(): | ||||
| 	pass | ||||
| 
 | ||||
| def obrazek_filename(self, filename): | ||||
| def galerie_filename(self, filename): | ||||
| 	gal = self.galerie | ||||
| 	cislo_gal = gal.pk | ||||
| 
 | ||||
|  | @ -44,43 +46,51 @@ def obrazek_filename(self, filename): | |||
| 
 | ||||
| 	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): | ||||
| 	obrazek_velky = models.ImageField(upload_to=obrazek_filename, | ||||
| 	# „originál“ (modulo max. velikost uploadu na web FIXME!) | ||||
| 
				
					
						ledoian
						commented  doku doku | ||||
| 	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.") | ||||
| 	obrazek_stredni = ImageSpecField(source='obrazek_velky', | ||||
| 				processors=[Transpose(Transpose.AUTO), ResizeToFit(900, 675, upscale=False)], | ||||
| 				options={'quality': 95}) | ||||
| 	obrazek_maly = ImageSpecField(source='obrazek_velky', | ||||
| 				processors=[Transpose(Transpose.AUTO), ResizeToFit(167, 167, upscale=False)], | ||||
| 				options={'quality': 95}) | ||||
| 	class Typ(models.TextChoices): | ||||
| 		OBRAZEK = 'obrazek', 'Obrázek' | ||||
| 		VIDEO = 'video', 'Video' | ||||
| 		NEVIM = 'nevim', 'Neznámý typ' | ||||
| 	typ = models.CharField('Typ', max_length=16, blank=False, null=False, choices=Typ.choices, default=Typ.NEVIM) | ||||
| 	# Filename by default; slouží k řazení | ||||
| 
				
					
						ledoian
						commented  doku nebo help_text doku nebo help_text | ||||
| 	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) | ||||
| 	datum_vlozeni = models.DateTimeField('Datum vložení', auto_now_add=True) | ||||
| 	galerie = models.ForeignKey('Galerie', blank=True, null=True, on_delete=models.CASCADE) | ||||
| 	# Primární klíč k řazení pro overridování řazení podle názvu | ||||
| 	poradi = models.IntegerField('Pořadí', blank=True, null=True) | ||||
| 
 | ||||
| 	def __str__(self): | ||||
| 			return self.obrazek_velky.name | ||||
| 		return f'Obrázek {self.nazev} ({self.soubor.name})' | ||||
| 
 | ||||
| 	class Meta: | ||||
| 		verbose_name = 'Obrázek' | ||||
| 		verbose_name_plural = 'Obrázky' | ||||
| 		ordering = ['nazev'] | ||||
| 
 | ||||
| 	def obrazek_maly_tag(self): | ||||
| 		if not self.obrazek_maly: | ||||
| 			return '' | ||||
| 		return u'<img src="{}">'.format(self.obrazek_maly.url) | ||||
| 	obrazek_maly_tag.short_description = "Náhled" | ||||
| 	obrazek_maly_tag.allow_tags = True | ||||
| 		ordering = ['nazev'] #FIXME: zohlednit pořadí a asi i příslušnost ke galerii | ||||
| 
				
					
						ledoian
						commented  FIXME FIXME | ||||
| 
 | ||||
| 	def save(self, *args, **kwargs): | ||||
| 		# obrázek potřebuje název, protože se z něj generuje cesta pro jeho uložení | ||||
| 		# (a pak se podle něj taky řadí) | ||||
| 		if self.nazev is None: | ||||
| 			self.nazev = os.path.basename(self.obrazek_velky.name) | ||||
| 			self.nazev = os.path.basename(self.soubor.name) | ||||
| 		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): | ||||
| 	nazev = models.CharField('Název', max_length=100) | ||||
|  |  | |||
|  | @ -6,9 +6,10 @@ | |||
| /* velká fotka */ | ||||
| /* zmenšování spolu s oknem prohlížeče */ | ||||
| .galerie .obrazek, .titulni_obrazek { | ||||
| 	max-width: 100%; | ||||
| 	max-width: max(100%, 900px); | ||||
| 
				
				zelvuska marked this conversation as resolved
				
			 
				
					
						zelvuska
						commented  900 je nějak moc, ne? (Třeba když jsi na mobilu, tak to znamená, že uvidíš minimum z toho obrázku, ne?) 900 je nějak moc, ne? (Třeba když jsi na mobilu, tak to znamená, že uvidíš minimum z toho obrázku, ne?) 
				
					
						ledoian
						commented  To je vzaté z původních  To je vzaté z původních `views.py`, za to nemůžu já :-D Ale je to hodně, co třeba `600px`? (Haluzím velmi náhodně a na monitoru, zas to nemůže být moc málo, jinak se to zmenší moc…) 
				
					
						zelvuska
						commented  Aha, tak to asi nechme… Aha, tak to asi nechme… | ||||
| 	max-height: 900px; | ||||
| 	height: auto; | ||||
| 	width: auto\9; /* ie8 */ | ||||
| 	width: auto; | ||||
| } | ||||
| 
 | ||||
| .predchozi_obrazek{ | ||||
|  | @ -75,6 +76,8 @@ | |||
| 
 | ||||
| .galerie_nahledy img { | ||||
| 	margin: 10px; | ||||
| 	max-height: 100px; | ||||
| 	max-width: 200px; | ||||
| 
				
					
						ledoian
						commented  Hodnoty random nastřelené… (asi nevadí) Hodnoty random nastřelené… (asi nevadí) | ||||
| } | ||||
| 
 | ||||
| .galerie_nahledy div.navigace { | ||||
|  |  | |||
							
								
								
									
										38
									
								
								galerie/static/galerie/neznamy_placeholder.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						|  | @ -0,0 +1,38 @@ | |||
| <?xml version="1.0" encoding="UTF-8" standalone="no"?> | ||||
| <!-- Created with Inkscape (http://www.inkscape.org/) --> | ||||
| 
 | ||||
| <svg | ||||
|    width="768" | ||||
|    height="576" | ||||
|    viewBox="0 0 768 576" | ||||
|    version="1.1" | ||||
|    id="svg1" | ||||
|    xmlns="http://www.w3.org/2000/svg" | ||||
|    xmlns:svg="http://www.w3.org/2000/svg"> | ||||
|   <defs | ||||
|      id="defs1" /> | ||||
|   <g | ||||
|      id="layer1"> | ||||
|     <path | ||||
|        style="fill:#6f2509;stroke:#6f2509;stroke-width:24.4876;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1;paint-order:markers fill stroke;fill-opacity:1" | ||||
|        id="path1" | ||||
|        d="m 171.56486,156.34958 277.81099,481.18278 -555.62201,-2e-5 z" | ||||
|        transform="translate(212.43515,-108.94158)" /> | ||||
|     <g | ||||
|        id="g4" | ||||
|        style="fill:#ff0000" | ||||
|        transform="matrix(0.63613865,0,0,0.62847938,383.26722,188.1938)"> | ||||
|       <circle | ||||
|          style="fill:#ff0000;fill-opacity:1;stroke:none;stroke-width:32.6502;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1;paint-order:markers fill stroke" | ||||
|          id="path2" | ||||
|          cx="0" | ||||
|          cy="430.19247" | ||||
|          r="64.870293" /> | ||||
|       <path | ||||
|          style="fill:#ff0000;fill-opacity:1;stroke:none;stroke-width:24.4876;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1;paint-order:markers fill stroke" | ||||
|          id="path3" | ||||
|          d="m -93.037653,283.56151 -174.608127,-302.430127 349.216236,-10e-6 z" | ||||
|          transform="matrix(0.69829007,0,0,1.0128734,64.967275,29.408685)" /> | ||||
|     </g> | ||||
|   </g> | ||||
| </svg> | ||||
| After Width: | Height: | Size: 1.4 KiB | 
							
								
								
									
										61
									
								
								galerie/static/galerie/video_placeholder.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						|  | @ -0,0 +1,61 @@ | |||
| <?xml version="1.0" encoding="UTF-8" standalone="no"?> | ||||
| <!-- Created with Inkscape (http://www.inkscape.org/) --> | ||||
| 
 | ||||
| <svg | ||||
|    width="768" | ||||
|    height="576" | ||||
|    viewBox="0 0 768 576" | ||||
|    version="1.1" | ||||
|    id="svg1" | ||||
|    sodipodi:docname="video_placeholder.svg" | ||||
|    inkscape:version="1.4.1 (93de688d07, 2025-03-30)" | ||||
|    xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" | ||||
|    xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" | ||||
|    xmlns="http://www.w3.org/2000/svg" | ||||
|    xmlns:svg="http://www.w3.org/2000/svg"> | ||||
|   <sodipodi:namedview | ||||
|      id="namedview1" | ||||
|      pagecolor="#505050" | ||||
|      bordercolor="#ffffff" | ||||
|      borderopacity="1" | ||||
|      inkscape:showpageshadow="0" | ||||
|      inkscape:pageopacity="0" | ||||
|      inkscape:pagecheckerboard="1" | ||||
|      inkscape:deskcolor="#d1d1d1" | ||||
|      inkscape:zoom="1.1715686" | ||||
|      inkscape:cx="377.69874" | ||||
|      inkscape:cy="285.08787" | ||||
|      inkscape:window-width="1920" | ||||
|      inkscape:window-height="1164" | ||||
|      inkscape:window-x="0" | ||||
|      inkscape:window-y="0" | ||||
|      inkscape:window-maximized="0" | ||||
|      inkscape:current-layer="svg1" /> | ||||
|   <defs | ||||
|      id="defs1" /> | ||||
|   <g | ||||
|      id="layer2" | ||||
|      style="stroke:#aaaaaa;stroke-opacity:1;fill:#cccccc;fill-opacity:1"> | ||||
|     <g | ||||
|        id="rect1" | ||||
|        style="opacity:1;fill:#cccccc;fill-opacity:1" | ||||
|        transform="matrix(0.95921938,0,0,0.86504219,15.659757,38.867849)"> | ||||
|       <path | ||||
|          style="baseline-shift:baseline;display:inline;overflow:visible;vector-effect:none;stroke:none;stroke-linecap:round;stroke-linejoin:round;paint-order:markers fill stroke;enable-background:accumulate;stop-color:#000000;stop-opacity:1;fill:#cccccc;fill-opacity:1" | ||||
|          d="M 0,-12.244141 C -6.7620501,-12.243653 -12.243653,-6.7620501 -12.244141,0 v 576 c 4.89e-4,6.76205 5.4820913,12.24365 12.244141,12.24414 h 768 c 6.76205,-4.9e-4 12.24365,-5.48209 12.24414,-12.24414 V 0 C 780.24365,-6.7620497 774.76205,-12.243652 768,-12.244141 Z" | ||||
|          id="path4" /> | ||||
|     </g> | ||||
|   </g> | ||||
|   <g | ||||
|      id="layer1"> | ||||
|     <g | ||||
|        id="path1" | ||||
|        style="opacity:1;fill:#ff0000;fill-opacity:0.771591" | ||||
|        transform="matrix(0.77572769,0,0,0.77572769,169.75532,213.5323)"> | ||||
|       <path | ||||
|          style="baseline-shift:baseline;display:inline;overflow:visible;vector-effect:none;fill:#f5b34f;fill-opacity:1;stroke-linecap:round;stroke-linejoin:round;paint-order:markers fill stroke;enable-background:accumulate;stop-color:#000000;stop-opacity:1" | ||||
|          d="m 54.501953,-169.58203 c -5.851459,1.03154 -10.117969,6.11495 -10.119141,12.05664 v 507.05078 c 8.82e-4,9.42554 10.204294,15.31604 18.367188,10.60352 L 501.86719,106.60352 c 8.16111,-4.71353 8.16111,-16.493503 0,-21.207036 L 62.75,-168.12891 c -2.493096,-1.43876 -5.413362,-1.95325 -8.248047,-1.45312 z" | ||||
|          id="path3" /> | ||||
|     </g> | ||||
|   </g> | ||||
| </svg> | ||||
| After Width: | Height: | Size: 2.7 KiB | 
|  | @ -1,5 +1,6 @@ | |||
| {% extends "galerie/base.html" %} | ||||
| 
 | ||||
| {% load bazmeky %} | ||||
| 
 | ||||
| {% block nadpis1a %} | ||||
| {{galerie.nazev}}: {{ obrazek.popis | default:"Fotka" }} | ||||
|  | @ -64,11 +65,7 @@ | |||
|       {% endwith %} | ||||
|     {% endif%} | ||||
|     <span id="nahoru" class="kotva_obrazku"></span> | ||||
|     <img src="{{obrazek.obrazek_stredni.url}}" | ||||
|          height="{{vyska}}" | ||||
|          width="{{sirka}}" | ||||
|          alt="{{obrazek.popis}}" | ||||
|          class="obrazek"> | ||||
|     {% zobrazit obrazek.jako_bazmek alt=obrazek.popis title=obrazek.popis class="obrazek" %} | ||||
|           | ||||
|     {% if obrazky_dalsi %} | ||||
|       {% with obrazky_dalsi|first as dalsi_obrazek %} | ||||
|  | @ -79,7 +76,7 @@ | |||
|     {% endif%} | ||||
|   </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>--> | ||||
| 
 | ||||
|   {# Popisek fotky #} | ||||
|  | @ -110,21 +107,14 @@ | |||
|       {% endif %} | ||||
|       {# nahledy predchozich obrazku #} | ||||
|       {% 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> | ||||
| 
				
					
						ledoian
						commented  zrušit height? zrušit height? | ||||
|       {% endfor %} | ||||
|     </div> | ||||
| 
 | ||||
|     <img src={{obrazek.obrazek_maly.url}} | ||||
|        height="{{obrazek.obrazek_maly.height}}" | ||||
|        width="{{obrazek.obrazek_maly.width}}" | ||||
|        alt="{{obrazek.popis}}" | ||||
|        class="obrazek" | ||||
|        id="prostredni"> | ||||
|     {% zmenseny_nahled obrazek.jako_bazmek alt=obrazek.popis class="obrazek" id="prostredni" %} | ||||
| 
 | ||||
|     <div class="navigace"> | ||||
|       {# nahledy nasledujicich obrazku #} | ||||
|       {% 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> | ||||
| 
				
					
						ledoian
						commented  i tady zrušit height? i tady zrušit height? | ||||
|       {% endfor %} | ||||
|       {# odkaz na nasledujici galerii #} | ||||
|       {% if nasledujici_galerie %} | ||||
|  |  | |||
|  | @ -1,5 +1,7 @@ | |||
| {% extends "galerie/base.html" %} | ||||
| 
 | ||||
| {% load bazmeky %} | ||||
| 
 | ||||
| {% block nadpis1a %} | ||||
| Galerie {{galerie.nazev}} | ||||
| {% endblock %} | ||||
|  | @ -26,7 +28,7 @@ Galerie {{galerie.nazev}} | |||
|   {% if not obrazky %} | ||||
|    <div class="galerie_hlavicka"> | ||||
|     {% 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 %} | ||||
|    </div> | ||||
|   {% endif %} | ||||
|  | @ -55,10 +57,7 @@ Galerie {{galerie.nazev}} | |||
|             {% endif %} | ||||
|            class="podgalerie_nahled {% if pgalerie.zobrazit == 1 or pgalerie.zobrazit == 2 %}mam-org-only{% endif %}{% if pgalerie.zobrazit == 3 %}mam-resitel-only{% endif %}"> | ||||
|               {% if pgalerie.titulni_obrazek %} | ||||
|                 {% with pgalerie.titulni_obrazek.obrazek_maly as obrazek %} | ||||
|                   <img src="{{ obrazek.url }}" | ||||
|                   /> | ||||
|                 {% endwith %} | ||||
|                 {% zmenseny_nahled pgalerie.titulni_obrazek.jako_bazmek class="" %} | ||||
| 
				
					
						ledoian
						commented  Má mít class nebo tam ten atribut nemá být vůbec Má mít class nebo tam ten atribut nemá být vůbec | ||||
|               {% endif %} | ||||
|               <div class="nazev_galerie"> | ||||
|               {{ pgalerie|truncatechars:max_delka_nazvu }} | ||||
|  | @ -96,13 +95,10 @@ Galerie {{galerie.nazev}} | |||
|             {% if obrazek.popis %} | ||||
|               title="{{ obrazek.popis }}" | ||||
|             {% endif %} | ||||
|             href="./{{obrazek.pk}}#nahoru" class="galerie_nahled"><span class="vystredeno"></span><img | ||||
|             src="{{obrazek.obrazek_maly.url}}" | ||||
|             {% if obrazek.popis %} | ||||
|               title="{{ obrazek.popis }}" | ||||
|             {% endif %} | ||||
|             width="{{ obrazek.obrazek_maly.width }}" | ||||
|             height="{{ obrazek.obrazek_maly.height }}" /> | ||||
|             href="./{{obrazek.pk}}#nahoru" class="galerie_nahled"> | ||||
|           <span class="vystredeno"></span> | ||||
|           {% zmenseny_nahled obrazek.jako_bazmek  %} | ||||
| {#            title=obrazek.popis (a byl tu if, že se použil jen když existoval…) width=obrazek.obrazek_maly.width height=obrazek.obrazek_maly.height  #} | ||||
| 
				
					
						ledoian
						commented  smazat? title by se měl použít z vnějšího tagu, či? smazat? title by se měl použít z vnějšího tagu, či? | ||||
|         </a> | ||||
|     {% endfor %} | ||||
|     <br> | ||||
|  |  | |||
							
								
								
									
										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) | ||||
							
								
								
									
										140
									
								
								galerie/typy.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						|  | @ -0,0 +1,140 @@ | |||
| """ | ||||
| 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 | ||||
| import logging | ||||
| 
 | ||||
| logger = logging.getLogger(__name__) | ||||
| 
 | ||||
| from django.contrib.staticfiles.finders import find | ||||
| # FIXME: static fily na lokálním webu?? | ||||
| find = lambda x: '/static/'+x | ||||
| 
				
					
						ledoian
						commented  FIXME FIXME | ||||
| from django.utils.safestring import mark_safe, SafeString | ||||
| from django.utils.html import format_html, format_html_join | ||||
| from django.urls import reverse | ||||
| 
 | ||||
| from imagekit import ImageSpec | ||||
| from imagekit.cachefiles import ImageCacheFile | ||||
| from imagekit.processors import ResizeToFit, Transpose | ||||
| 
 | ||||
| HTML = str | SafeString | ||||
| 
				
					
						ledoian
						commented  Komentář, že v Py3.12 má být  Komentář, že v Py3.12 má být `type HTML = ...` | ||||
| 
 | ||||
| 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} | ||||
| 
 | ||||
| def _fmt_attrs(attrs): | ||||
| 	return format_html_join(' ', r'{}="{}"', ((mark_safe(k), v) for k, v in attrs.items())) | ||||
| 
 | ||||
| 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 = _fmt_attrs(kwargs) | ||||
| 		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 = _fmt_attrs(kwargs) | ||||
| 		html = format_html(r'<img src="{}" {} />', file.url, attrs) | ||||
| 		return html | ||||
| 		 | ||||
| 
 | ||||
| class Video(ZobrazitelnyBazmek): | ||||
| 	def __init__(self, *a, **kwa): | ||||
| 		super().__init__(*a, **kwa) | ||||
| 		self.placeholder = find('galerie/video_placeholder.svg') | ||||
| 
 | ||||
| 	def zobrazit(self, **kwargs): | ||||
| 		attrs = _fmt_attrs(kwargs) | ||||
| 		# Atributy specifické pro video musíme vesměs vyřešit tady… (šlo by to {% if %}-ovat podle typu, ale to je spíš haluz. | ||||
| 		html = format_html(r'<video src="{}" preload="metadata" controls {} />', self.soubor.url, attrs) | ||||
| 		return html | ||||
| 	def zmenseny_nahled(self, **kwargs): | ||||
| 		attrs = _fmt_attrs(kwargs) | ||||
| 		return format_html(r'<img src="{}" {} />', self.placeholder, attrs) | ||||
| 
 | ||||
| class DummyBazmek(ZobrazitelnyBazmek): | ||||
| 	def __init__(self, *a, **kwa): | ||||
| 		super().__init__(*a, **kwa) | ||||
| 		self.placeholder = find('galerie/neznamy_placeholder.svg') | ||||
| 
 | ||||
| 	def zobrazit(self, **kwargs): | ||||
| 		attrs = _fmt_attrs(kwargs) | ||||
| 		# Stavíme HTML ad-hoc, co se může rozbít :'-) | ||||
| 		obrazek = format_html(r'<img src="{}"/>', self.placeholder, attrs) | ||||
| 		popisek = format_html(r'<p style="text-align: center; font-size: 1.1em;">Prosím oprav typ aktuálního obrázku v <a href="{}">adminu</a>!</p>', reverse('admin:galerie_obrazek_change', args=(self.db_objekt.id,))) | ||||
| 
				
					
						ledoian
						commented  hnus hnus | ||||
| 		return format_html(r'<div {}>{}{}</div>', attrs, obrazek, popisek) | ||||
| 	def zmenseny_nahled(self, **kwargs): | ||||
| 		attrs = _fmt_attrs(kwargs) | ||||
| 		return format_html(r'<img src="{}" {} />', self.placeholder, attrs) | ||||
| 
 | ||||
| def tipniTyp(soubor) -> DbObrazek.Typ: | ||||
| 	from PIL import Image, UnidentifiedImageError | ||||
| 	try: | ||||
| 		Image.open(soubor) | ||||
| 		return DbObrazek.Typ.OBRAZEK | ||||
| 	except UnidentifiedImageError: | ||||
| 		return DbObrazek.Typ.NEVIM | ||||
| 	logger.warning("Nepodařilo se tipnout typ nečekaným způsobem!") | ||||
| 	return DbObrazek.Typ.NEVIM | ||||
| 	 | ||||
|  | @ -89,10 +89,6 @@ def nahled(request, pk, soustredeni): | |||
| 
 | ||||
| def detail(request, pk, fotka, soustredeni): | ||||
| 	"""Zobrazeni nahledu fotky s id 'fotka'.""" | ||||
| 	MAX_VYSKA = 900 | ||||
| 	MAX_SIRKA = 900 | ||||
| 	MAX_VYSKA_MALA = 100 | ||||
| 	MAX_SIRKA_MALA = 200 | ||||
| 	NAHLEDU = 1 | ||||
| 
 | ||||
| 	galerie = get_object_or_404(Galerie, pk=pk) | ||||
|  | @ -141,23 +137,11 @@ def detail(request, pk, fotka, soustredeni): | |||
| 	else: | ||||
| 		form = KomentarForm({'komentar': obrazek.popis}) | ||||
| 	 | ||||
| 	# Preskalovani obrazku do vybraneho prostoru. | ||||
| 	vyska = obrazek.obrazek_stredni.height | ||||
| 	sirka = obrazek.obrazek_stredni.width | ||||
| 	if vyska > MAX_VYSKA: | ||||
| 		sirka = sirka * MAX_VYSKA / vyska	 | ||||
| 		vyska = MAX_VYSKA | ||||
| 	if sirka > MAX_SIRKA: | ||||
| 		vyska = vyska * MAX_SIRKA / sirka | ||||
| 		sirka = MAX_SIRKA | ||||
| 
 | ||||
| 	return render(request, 'galerie/Galerie.html', | ||||
| 		{'galerie' : galerie, | ||||
| 		 'predchozi_galerie' : predchozi_galerie, | ||||
| 		 'nasledujici_galerie' : nasledujici_galerie, | ||||
| 		 'obrazek' : obrazek, | ||||
| 		 'vyska' : vyska, | ||||
| 		 'sirka' : sirka, | ||||
| 		 'obrazky_predchozi' : predchozi_obrazky, | ||||
| 		 'obrazky_dalsi' : nasledujici_obrazky, | ||||
| 		 'upravy_popisku' : dovolit_upravy_popisku(galerie, request), | ||||
|  | @ -200,9 +184,11 @@ def new_galerie(request, galerie, soustredeni): | |||
| 			gal.save() | ||||
| 
 | ||||
| 			# zpracovani obrazku v galerii | ||||
| 			from galerie.typy import tipniTyp | ||||
| 			for obr in request.FILES.getlist('obr'): | ||||
| 				o = Obrazek() | ||||
| 				o.obrazek_velky = obr | ||||
| 				o.soubor = obr | ||||
| 				o.typ = tipniTyp(obr) | ||||
| 				o.nazev = str(obr) | ||||
| 				o.galerie = gal | ||||
| 				o.save() | ||||
|  |  | |||
Možná by směrem zpátky spíš mělo selhat, pokud v galerii existuje ne-obrázek…