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):
|
||||
model = Obrazek
|
||||
fields = ['obrazek_velky', 'nazev', 'popis', 'poradi']
|
||||
fields = ['soubor', 'nazev', 'popis', '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):
|
||||
|
|
|
@ -22,10 +22,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
|
||||
|
||||
|
@ -42,31 +44,32 @@ 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):
|
||||
# „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.")
|
||||
# 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))
|
||||
# obrazek_stredni = ImageSpecField(source='obrazek_velky',
|
||||
# processors=[Transpose(Transpose.AUTO), ResizeToFit(900, 675, upscale=False)],
|
||||
# options={'quality': 95})
|
||||
# Zmenšené obrázky v přehledu obrázků a pod hlavním obrázkem (předchozí/následující)
|
||||
# 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í
|
||||
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.SET_NULL)
|
||||
# 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']
|
||||
ordering = ['nazev'] #FIXME: zohlednit pořadí a asi i příslušnost ke galerii
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
# 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)
|
||||
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)
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
{% extends "galerie/base.html" %}
|
||||
|
||||
{% load bazmeky %}
|
||||
|
||||
{% block nadpis1a %}
|
||||
{{galerie.nazev}}: {{ obrazek.popis | default:"Fotka" }}
|
||||
|
@ -63,11 +64,12 @@
|
|||
{% 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
|
||||
height=vyska
|
||||
width=sirka
|
||||
alt=obrazek.popis
|
||||
title=obrazek.popis
|
||||
class=obrazek" %}
|
||||
|
||||
{% if obrazky_dalsi %}
|
||||
{% with obrazky_dalsi|first as dalsi_obrazek %}
|
||||
|
@ -78,7 +80,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 #}
|
||||
|
@ -109,21 +111,21 @@
|
|||
{% 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>
|
||||
{% 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 %}
|
||||
{# height=obrazek.obrazek_maly.height
|
||||
width=obrazek.obrazek_maly.width #}
|
||||
|
||||
<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>
|
||||
{% endfor %}
|
||||
{# odkaz na nasledujici galerii #}
|
||||
{% if nasledujici_galerie %}
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
{% extends "galerie/base.html" %}
|
||||
|
||||
{% load bazmeky %}
|
||||
|
||||
{% block nadpis1a %}
|
||||
Galerie {{galerie.nazev}}
|
||||
{% endblock %}
|
||||
|
@ -23,7 +25,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 %}
|
||||
|
@ -52,10 +54,7 @@ Galerie {{galerie.nazev}}
|
|||
{% endif %}
|
||||
class="podgalerie_nahled">
|
||||
{% if galerie.titulni_obrazek %}
|
||||
{% with galerie.titulni_obrazek.obrazek_maly as obrazek %}
|
||||
<img src="{{ obrazek.url }}"
|
||||
/>
|
||||
{% endwith %}
|
||||
{% zmenseny_nahled galerie.titulni_obrazek.jako_bazmek %}
|
||||
{% endif %}
|
||||
<div class="nazev_galerie">
|
||||
{{ galerie|truncatechars:max_delka_nazvu }}
|
||||
|
@ -87,13 +86,12 @@ 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 #}
|
||||
</a>
|
||||
{% endfor %}
|
||||
<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