Code optimization

This commit is contained in:
2025-02-01 01:21:44 +01:00
parent e68c2eca63
commit ad74ae2c51
14 changed files with 147 additions and 199 deletions

View File

@@ -30,11 +30,6 @@ docker-compose up
### Command Befehle ### Command Befehle
Erstellt die fehlenden Thumbs für die Alben in der Galerie:
<code>
python3 fet2020/manage.py create_thumbs
</code>
Erstellt alle Searchindexes neu: Erstellt alle Searchindexes neu:
<code> <code>
python3 fet2020/manage.py rebuild_index python3 fet2020/manage.py rebuild_index

View File

@@ -15,11 +15,7 @@ class AlbumAdmin(admin.ModelAdmin):
"Der Ordner für die Fotos liegt am Server unter '/mnt/save/fotos/www'. " "Der Ordner für die Fotos liegt am Server unter '/mnt/save/fotos/www'. "
"Fette Schriften sind Pflichtfelder." "Fette Schriften sind Pflichtfelder."
) )
return super().add_view( return super().add_view(request, form_url, extra_context=extra_context)
request,
form_url,
extra_context=extra_context,
)
def change_view(self, request, object_id, form_url="", extra_context=None): def change_view(self, request, object_id, form_url="", extra_context=None):
extra_context = extra_context or {} extra_context = extra_context or {}
@@ -27,12 +23,7 @@ class AlbumAdmin(admin.ModelAdmin):
"Der Ordner für die Fotos liegt am Server unter '/mnt/save/fotos/www'. " "Der Ordner für die Fotos liegt am Server unter '/mnt/save/fotos/www'. "
"Fette Schriften sind Pflichtfelder." "Fette Schriften sind Pflichtfelder."
) )
return super().change_view( return super().change_view(request, object_id, form_url, extra_context=extra_context)
request,
object_id,
form_url,
extra_context=extra_context,
)
def save_model(self, request, obj, form, change): def save_model(self, request, obj, form, change):
obj.author = request.user obj.author = request.user

View File

@@ -0,0 +1,6 @@
from django.db import models
class Status(models.TextChoices):
DRAFT = "10", "DRAFT"
PUBLIC = "20", "PUBLIC"

View File

@@ -11,11 +11,7 @@ class AlbumAdminForm(forms.ModelForm):
widgets = {"description": CKEditorUploadingWidget(config_name="default")} widgets = {"description": CKEditorUploadingWidget(config_name="default")}
labels = { labels = {"slug": "Permalink", "event_place": "Event Ort", "description": "Beschreibung"}
"slug": "Permalink",
"event_place": "Event Ort",
"description": "Beschreibung",
}
help_texts = { help_texts = {
"folder_name": "Füge den Ordnername (ohne Pfade) ein.", "folder_name": "Füge den Ordnername (ohne Pfade) ein.",

View File

@@ -1,16 +0,0 @@
import logging
from django.core.management.base import BaseCommand
from gallery.utils import create_thumbs, get_folder_list
logger = logging.getLogger(__name__)
class Command(BaseCommand):
def handle(self, *args, **options):
for folder in get_folder_list():
logger.info("Folder '%s' in process.", folder)
create_thumbs(folder)
logger.info("Command 'create thumbs' ended.")

View File

@@ -0,0 +1,11 @@
from django.db import models
from .choices import Status
class AlbumManager(models.Manager):
def get_queryset(self):
return super().get_queryset().order_by("-event_date")
def public(self):
return self.get_queryset().filter(status=Status.PUBLIC)

View File

@@ -1,45 +1,44 @@
import logging
from random import randint
from django.conf import settings
from django.db import models from django.db import models
from django.urls import reverse from django.urls import reverse
from django.utils import timezone from django.utils import timezone
from django.utils.text import slugify from django.utils.text import slugify
from .choices import Status
from .managers import AlbumManager
from .utils import get_image_list from .utils import get_image_list
logger = logging.getLogger(__name__)
class Album(models.Model): class Album(models.Model):
title = models.CharField(verbose_name="Titel", max_length=200) title = models.CharField(verbose_name="Titel", max_length=200)
slug = models.SlugField(unique=True, null=True, blank=True) slug = models.SlugField(unique=True, null=True, blank=True)
folder_name = models.CharField(verbose_name="Ordner Name", max_length=200) folder_name = models.CharField(verbose_name="Ordner Name", max_length=200)
thumbnail = models.CharField(verbose_name="Thumbnail", max_length=200, null=True, blank=True) thumbnail = models.CharField(verbose_name="Thumbnail", max_length=200, blank=True, default="")
event_date = models.DateField( event_date = models.DateField(
verbose_name="Event Datum", verbose_name="Event Datum", null=True, blank=True, default=timezone.now
null=True,
blank=True,
default=timezone.now,
) )
event_place = models.CharField(max_length=200, blank=True) event_place = models.CharField(max_length=200, blank=True)
photographer = models.CharField( photographer = models.CharField(
verbose_name="Fotograph(en)", verbose_name="Fotograph(en)", max_length=200, blank=True, default=""
max_length=200,
null=True,
blank=True,
) )
DRAFT = "10" # TextChoices
PUBLIC = "20" Status = Status
STATUS = (
(DRAFT, "DRAFT"),
(PUBLIC, "PUBLIC"),
)
status = models.CharField(max_length=2, choices=STATUS, default=DRAFT)
description = models.TextField(null=True, blank=True) status = models.CharField(max_length=2, choices=Status.choices, default=Status.DRAFT)
description = models.TextField(blank=True, default="")
# Managers # Managers
objects = models.Manager() objects = AlbumManager()
class Meta: class Meta:
verbose_name = "Album" verbose_name = "Album"
@@ -57,14 +56,32 @@ class Album(models.Model):
def get_absolute_url(self): def get_absolute_url(self):
return reverse("gallery:album", kwargs={"slug": self.slug}) return reverse("gallery:album", kwargs={"slug": self.slug})
def get_images(self): def clean(self):
if not self.images:
logger.info("No thumbnails are generated.")
@property
def images(self) -> list:
return get_image_list(self.folder_name) return get_image_list(self.folder_name)
def get_images_length_sub_3(self): def get_images_length_sub_3(self):
return len(self.get_images()) - 3 return len(self.images) - 3
def get_model_name(self): def get_model_name(self):
return self._meta.model_name return self._meta.model_name
def get_thumbnail(self): @property
return None def get_album_thumbnail(self):
if img_list := self.images:
return next(
(
img["thumb_url"]
for img in img_list
if self.thumbnail and self.thumbnail in img["title"]
),
img_list[randint(0, len(img_list) - 1)]["thumb_url"],
)
self.status = Album.Status.DRAFT
logger.info("Album '%s' is empty.", self.title)
return settings.STATIC_URL + "img/FET-Logo-2014-quadrat.png"

View File

@@ -1,11 +1,12 @@
from django.urls import path from django.urls import path
from . import apps, views from . import apps, views
from .views import AlbumDetailView, DraftAlbumDetailView
app_name = apps.GalleryConfig.name app_name = apps.GalleryConfig.name
urlpatterns = [ urlpatterns = [
path("", views.index, name="index"), path("", views.index, name="index"),
path("<slug:slug>/", views.show_album, name="album"), path("<slug:slug>/", AlbumDetailView.as_view(), name="album"),
path("draft/<slug:slug>/", views.show_draft_album, name="album_draft"), path("draft/<slug:slug>/", DraftAlbumDetailView.as_view(), name="album_draft"),
] ]

View File

@@ -1,80 +1,56 @@
import logging import logging
import os import os
from pathlib import Path
from django.conf import settings from django.conf import settings
from django.core.validators import get_available_image_extensions
from PIL import Image, ImageOps from PIL import Image, ImageOps
gallery_path = settings.GALLERY["path"] gallery_path = Path(settings.MEDIA_ROOT) / settings.GALLERY["path"]
gallery_thumb_path = settings.GALLERY["thumb_path"] gallery_path_url = Path(settings.MEDIA_URL) / settings.GALLERY["path"]
gallery_thumb_path = Path(settings.MEDIA_ROOT) / settings.GALLERY["thumb_path"]
gallery_thumb_path_url = Path(settings.MEDIA_URL) / settings.GALLERY["thumb_path"]
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
size = (320, 320) size = (320, 320)
valid_images = [".jpg", ".png"]
def get_image_list(folder_name): def get_image_list(folder_name: str) -> list:
file_path = os.path.join(settings.MEDIA_ROOT + gallery_path, folder_name) image_path = Path(gallery_path) / folder_name
thumb_path = Path(gallery_thumb_path) / folder_name
img_list = [] img_list = []
if os.path.exists(file_path): if not Path(image_path).exists():
for img in os.listdir(file_path): logger.info("Image path '%s' not found.", image_path)
ext = os.path.splitext(img)[1] return img_list
if ext.lower() not in valid_images:
Path(thumb_path).mkdir(exist_ok=True)
for _file in os.listdir(image_path):
if Path(_file).suffix.lower()[1:] not in get_available_image_extensions():
continue continue
thumb_path = os.path.join(settings.MEDIA_ROOT + gallery_thumb_path, folder_name) thumb_file_path = Path(thumb_path) / f"thumb_{_file}"
thumb_file_path = os.path.join(thumb_path, f"thumb_{img}") if not Path(thumb_file_path).exists():
if os.path.exists(thumb_file_path): with Image.open(Path(image_path) / _file, "r") as im:
thumb_url = os.path.join( if im._getexif() is not None:
settings.MEDIA_URL + gallery_thumb_path, im = ImageOps.exif_transpose(im)
folder_name + "/" + f"thumb_{img}",
) thumb = ImageOps.fit(im, size, Image.Resampling.LANCZOS)
else: thumb.save(thumb_file_path)
thumb_url = None logger.info("Save thumb 'thumb_%s'.", _file)
img_dict = { img_dict = {
"title": img, "title": _file,
"image_url": os.path.join( "image_url": Path(gallery_path_url) / folder_name / _file,
settings.MEDIA_URL + gallery_path, "thumb_url": Path(gallery_thumb_path_url) / folder_name / f"thumb_{_file}",
folder_name + "/" + img,
),
"thumb_url": thumb_url,
} }
img_list.append(img_dict) img_list.append(img_dict)
return img_list return img_list
def get_folder_list(): def get_folder_list():
if os.path.exists(settings.MEDIA_ROOT + gallery_path): if Path(gallery_path).exists():
return next(os.walk(settings.MEDIA_ROOT + gallery_path))[1] return next(os.walk(gallery_path))[1]
return None return None
def create_thumbs(folder_path):
file_path = os.path.join(settings.MEDIA_ROOT + gallery_path, folder_path)
thumb_path = os.path.join(settings.MEDIA_ROOT + gallery_thumb_path, folder_path)
if os.path.exists(file_path):
os.makedirs(thumb_path, exist_ok=True)
for f in os.listdir(file_path):
ext = os.path.splitext(f)[1]
if ext.lower() not in valid_images:
continue
thumb_file_path = os.path.join(thumb_path, f"thumb_{f}")
if os.path.exists(thumb_file_path):
continue
image_path = os.path.join(file_path, f)
logger.info("Edit picture '%s'.", f)
with Image.open(str(image_path), "r") as image:
if image._getexif() is not None:
image = ImageOps.exif_transpose(image)
thumb = ImageOps.fit(image, size, Image.ANTIALIAS)
thumb.save(thumb_file_path)
logger.info("Save thumb 'thumb_%s'.", f)

View File

@@ -1,94 +1,65 @@
import logging
from collections import deque from collections import deque
from random import randint
from django.conf import settings from django.contrib.auth.mixins import LoginRequiredMixin
from django.http import Http404 from django.http import Http404
from django.shortcuts import render from django.shortcuts import render
from django.utils.text import slugify from django.utils.text import slugify
from django.views.generic.detail import DetailView
from authentications.decorators import authenticated_user
from .models import Album from .models import Album
from .utils import create_thumbs, get_folder_list from .utils import get_folder_list
logger = logging.getLogger(__name__)
def index(request): def index(request):
if request.user.is_authenticated: if request.user.is_authenticated:
albums = deque(Album.objects.all().order_by("-event_date")) albums = deque(Album.objects.all())
# get albums that are in the server but not in the db. # Get albums that are in the server but not in the db.
folders = get_folder_list() for folder in get_folder_list():
if folders:
for folder in folders:
if not Album.objects.filter(folder_name=folder): if not Album.objects.filter(folder_name=folder):
albums.append( albums.append(
Album( Album(title=folder, slug=slugify(folder), folder_name=folder, event_date=None)
title=folder,
slug=slugify(folder),
folder_name=folder,
event_date=None,
),
) )
else:
# show only PUBLIC albums.
albums = deque(Album.objects.filter(status=Album.PUBLIC).order_by("-event_date"))
for album in list(albums):
img_list = album.get_images()
if img_list:
if album.thumbnail:
for img in img_list:
if album.thumbnail in img["title"]:
album.thumbnail = img["thumb_url"]
break
else: else:
value = randint(0, len(img_list) - 1) # Show only PUBLIC albums.
album.thumbnail = img_list[value]["thumb_url"] albums = Album.objects.public()
else:
value = randint(0, len(img_list) - 1)
album.thumbnail = img_list[value]["thumb_url"]
else:
# empty album is temporarily set to DRAFT.
album.thumbnail = settings.STATIC_URL + "img/FET-Logo-2014-quadrat.png"
album.status = Album.DRAFT
context = { context = {"albums": albums}
"albums": albums,
}
return render(request, "gallery/index.html", context) return render(request, "gallery/index.html", context)
def show_album(request, slug): class AlbumDetailView(DetailView):
album = Album.objects.filter(slug=slug).first() model = Album
if not album: template_name = "gallery/album.html"
def get_queryset(self):
return (
Album.objects.public()
if not self.request.user.is_authenticated
else Album.objects.all()
)
class DraftAlbumDetailView(LoginRequiredMixin, DetailView):
model = Album
template_name = "gallery/album.html"
def get_object(self, queryset=None):
slug = self.kwargs.get(self.slug_url_kwarg)
if not (album := Album.objects.filter(slug=slug).first()):
for folder in get_folder_list(): for folder in get_folder_list():
if slug == slugify(folder): if slug == slugify(folder):
album = Album( album = Album(
title=folder, title=folder, slug=slugify(folder), folder_name=folder, event_date=None
slug=slugify(folder),
folder_name=folder,
event_date=None,
) )
break break
else: else:
raise Http404("wrong album slug") raise Http404("Album slug not found.")
img_list = album.get_images() return album
if not img_list:
# empty album is temporarily set to DRAFT.
album.status = Album.DRAFT
create_thumbs(album.folder_name)
context = {
"album": album,
"images": img_list,
}
return render(request, "gallery/album.html", context)
@authenticated_user
def show_draft_album(request, slug):
return show_album(request, slug)

View File

@@ -13,7 +13,7 @@
<h1 class="page-title">Fotos</h1> <h1 class="page-title">Fotos</h1>
<section class="text-gray-800 dark:text-gray-200 my-2 sm:my-4"> <section class="text-gray-800 dark:text-gray-200 my-2 sm:my-4">
{% if album.status == album.DRAFT %} {% if album.status == album.Status.DRAFT %}
<h2 class="text-gray-900 dark:text-gray-100 text-lg font-medium tracking-wide"><i class="fa-solid fa-eye-slash text-gray-600 dark:text-gray-300 mr-2" title="Album ist nicht öffentlich!"></i>{{ album.title }}</h2> <h2 class="text-gray-900 dark:text-gray-100 text-lg font-medium tracking-wide"><i class="fa-solid fa-eye-slash text-gray-600 dark:text-gray-300 mr-2" title="Album ist nicht öffentlich!"></i>{{ album.title }}</h2>
{% else %} {% else %}
<h2 class="text-gray-900 dark:text-gray-100 text-lg font-medium tracking-wide">{{ album.title }}</h2> <h2 class="text-gray-900 dark:text-gray-100 text-lg font-medium tracking-wide">{{ album.title }}</h2>
@@ -34,7 +34,7 @@
{% endif %} {% endif %}
</section> </section>
{% if request.user.is_authenticated %} {% if request.user.is_authenticated and album.id %}
<a href="{% url 'admin:gallery_album_change' album.id %}" class="page-subtitle block btn-small btn-primary w-full sm:w-max sm:mr-0 sm:ml-auto"> <a href="{% url 'admin:gallery_album_change' album.id %}" class="page-subtitle block btn-small btn-primary w-full sm:w-max sm:mr-0 sm:ml-auto">
<i class="fa-regular fa-folder-open mr-1"></i>Album bearbeiten <i class="fa-regular fa-folder-open mr-1"></i>Album bearbeiten
</a> </a>
@@ -52,7 +52,7 @@
</div> </div>
<div id="links" class="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 xl:grid-cols-6 2xl:grid-cols-7 justify-items-center gap-3"> <div id="links" class="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 xl:grid-cols-6 2xl:grid-cols-7 justify-items-center gap-3">
{% for image in images %} {% for image in object.images %}
<a id="{{ image.title }}" href="{{ image.image_url }}" title="{{ image.title }}" class="block max-w-xs sm:max-w-none"> <a id="{{ image.title }}" href="{{ image.image_url }}" title="{{ image.title }}" class="block max-w-xs sm:max-w-none">
{% if image.thumb_url %} {% if image.thumb_url %}
<img src="{{ image.thumb_url }}" alt="{{ image.title }}" class="rounded-sm"> <img src="{{ image.thumb_url }}" alt="{{ image.title }}" class="rounded-sm">

View File

@@ -15,15 +15,15 @@
<div class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 xl:grid-cols-4 2xl:grid-cols-5 justify-items-center gap-4"> <div class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 xl:grid-cols-4 2xl:grid-cols-5 justify-items-center gap-4">
{% for album in albums %} {% for album in albums %}
{% if request.user.is_authenticated and album.status == album.DRAFT %} {% if request.user.is_authenticated and album.status == album.Status.DRAFT %}
<a href="{% url 'gallery:album_draft' album.slug %}" class="block max-w-xs sm:max-w-none"> <a href="{% url 'gallery:album_draft' album.slug %}" class="block max-w-xs sm:max-w-none">
<img src="{{ album.thumbnail }}" class="rounded"> <img src="{{ album.get_album_thumbnail }}" class="rounded">
<h2 class="px-2 text-proprietary-dark dark:text-sky-300">{{ album.title }}</h2> <h2 class="px-2 text-proprietary-dark dark:text-sky-300">{{ album.title }}</h2>
<h3 class="px-2 text-sm text-proprietary dark:text-sky-400 font-normal"><i class="fa-solid fa-calendar-day mr-1"></i>{{ album.event_date }}</h3> <h3 class="px-2 text-sm text-proprietary dark:text-sky-400 font-normal"><i class="fa-solid fa-calendar-day mr-1"></i>{{ album.event_date }}</h3>
</a> </a>
{% elif album.status == album.PUBLIC %} {% elif album.status == album.Status.PUBLIC %}
<a href="{% url 'gallery:album' album.slug %}" class="block max-w-xs sm:max-w-none"> <a href="{% url 'gallery:album' album.slug %}" class="block max-w-xs sm:max-w-none">
<img src="{{ album.thumbnail }}" class="rounded"> <img src="{{ album.get_album_thumbnail }}" class="rounded">
<h2 class="px-2 text-proprietary-dark dark:text-sky-300">{{ album.title }}</h2> <h2 class="px-2 text-proprietary-dark dark:text-sky-300">{{ album.title }}</h2>
<h3 class="px-2 text-sm text-proprietary dark:text-sky-400 font-normal"><i class="fa-solid fa-calendar-day mr-1"></i>{{ album.event_date }}</h3> <h3 class="px-2 text-sm text-proprietary dark:text-sky-400 font-normal"><i class="fa-solid fa-calendar-day mr-1"></i>{{ album.event_date }}</h3>
</a> </a>