From 473df39cbe6b8ef4243c1e11da51d4f2bba6898f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9DNyymix=E2=80=9D?= Date: Mon, 14 Apr 2025 10:26:50 +0300 Subject: [PATCH] Add cache and optimize code --- gallery/models/album.py | 52 ++++++++--------------- gallery/models/photo.py | 22 ++++++---- gallery/templates/gallery/album_list.html | 12 ++++-- gallery/templates/gallery/search.html | 14 ++++-- gallery/templatetags/image_tags.py | 39 +++++++++++++++++ gallery/views.py | 29 +++++++++++-- 6 files changed, 114 insertions(+), 54 deletions(-) create mode 100644 gallery/templatetags/image_tags.py diff --git a/gallery/models/album.py b/gallery/models/album.py index b6db339..94766c3 100644 --- a/gallery/models/album.py +++ b/gallery/models/album.py @@ -1,6 +1,7 @@ from datetime import datetime from math import floor +from django.core.cache import cache from django.core.exceptions import ValidationError from django.db import models from django.db.models import Sum @@ -19,47 +20,30 @@ class Album(models.Model): cover = models.ForeignKey("Photo", blank=True, null=True, on_delete=models.SET_NULL, related_name='cover_to', verbose_name="Album cover") is_public = models.BooleanField(default=False, verbose_name="Published") + def _cache_key(self, suffix): + return f'album_{self.pk}_{suffix}' + @property def photos_in_album(self): - return self.photos.count() + key = self._cache_key('photo_count') + count = cache.get(key) + + if count is None: + count = self.photos.count() + cache.set(key, count, 60 * 60 * 24) # Cache 24 h + + return count @property def photos_views(self): - return self.photos.aggregate(total_views=Sum('views'))['total_views'] or 0 + key = self._cache_key('photo_views') + views = cache.get(key) + if views is None: + views = self.photos.aggregate(total_views=Sum('views'))['total_views'] or 0 + cache.set(key, views, 60 * 5) # Cache 5 min - @property - def cover_image_data(self): - def compute_size(photo, max_w=720, max_h=720): - if not photo.width or not photo.height: - return (None, None) - aspect = photo.aspect_ratio - if photo.width > photo.height: - w = min(photo.width, max_w) - h = floor(w / aspect) - else: - h = min(photo.height, max_h) - w = floor(h * aspect) - return (w, h) - - photo = self.cover or self.photos.order_by('-width').first() - if photo: - url = photo.photo_md.url - w, h = compute_size(photo) - return { - "url": url, - "width": w, - "height": h, - "aspect_ratio": round(photo.aspect_ratio, 3) - } - - return { - "url": static('img/placeholder.png'), - "width": 1200, - "height": 800, - "aspect_ratio": 1.5 - } - + return views def save(self, *args, **kwargs): if not self.slug: diff --git a/gallery/models/photo.py b/gallery/models/photo.py index c65209f..5db49d1 100644 --- a/gallery/models/photo.py +++ b/gallery/models/photo.py @@ -1,6 +1,7 @@ import os from datetime import datetime +from django.core.cache import cache from django.db import models from django.db.models.signals import post_save from django.dispatch import receiver @@ -47,7 +48,6 @@ class Photo(models.Model): self.slug = os.path.basename(self.photo.name) super().save(*args, **kwargs) - def add_like(self): self.likes += 1 self.save(update_fields=['likes']) @@ -58,7 +58,7 @@ class Photo(models.Model): def get_next(self): return self.__class__.objects.filter(taken_at__gt=self.taken_at, album=self.album).order_by('taken_at').first() - + def get_prev(self): return self.__class__.objects.filter(taken_at__lt=self.taken_at, album=self.album).order_by('-taken_at').first() @@ -79,13 +79,19 @@ class Photo(models.Model): -def create_photo(sender, instance, created, **kwargs): - if created: - """ Add exif metadata """ +@receiver(post_save, sender=Photo) +def handle_photo_creation(sender, instance, created, **kwargs): + if not created: + return + + # Lisää EXIF metadata + if instance.photo and instance.photo.path: exif = Exif(instance.photo.path) instance.exif = exif.data - """ Update taken datetime """ instance.taken_at = exif.datetimeoriginal('Europe/Helsinki') - instance.save() + instance.save(update_fields=['exif', 'taken_at']) -post_save.connect(create_photo, sender=Photo) \ No newline at end of file + # Aseta cover, jos albumilla ei ole vielä sellaista + if instance.album and not instance.album.cover: + instance.album.cover = instance + instance.album.save(update_fields=['cover']) \ No newline at end of file diff --git a/gallery/templates/gallery/album_list.html b/gallery/templates/gallery/album_list.html index 260f6c3..2c47d64 100644 --- a/gallery/templates/gallery/album_list.html +++ b/gallery/templates/gallery/album_list.html @@ -1,5 +1,6 @@ {% extends "base.html" %} {% load static %} +{% load image_tags %} {% block title %} Albums {% endblock %} @@ -29,13 +30,16 @@
- Cover image for {{ album.name }} + {% endwith %}
diff --git a/gallery/templates/gallery/search.html b/gallery/templates/gallery/search.html index df64ade..1026350 100644 --- a/gallery/templates/gallery/search.html +++ b/gallery/templates/gallery/search.html @@ -1,5 +1,6 @@ {% extends "base.html" %} {% load static %} +{% load image_tags %} {% block title %} Search {% endblock %} @@ -21,6 +22,7 @@
+ {% if results %} {% for album in results %}
@@ -29,13 +31,16 @@ @@ -56,6 +61,7 @@ {% else %}

Not found: "{{ query }}".

{% endif %} +
{% include "./partials/pagination.html" %} diff --git a/gallery/templatetags/image_tags.py b/gallery/templatetags/image_tags.py new file mode 100644 index 0000000..d129897 --- /dev/null +++ b/gallery/templatetags/image_tags.py @@ -0,0 +1,39 @@ +from math import floor + +from django import template +from django.templatetags.static import static + +register = template.Library() + +@register.filter +def cover_image_data(album): + """ + Palauttaa dictin: url, width, height, aspect_ratio. + Jos coveria ei ole, palauttaa placeholder-kuvan ja oletusmitat. + """ + photo = getattr(album, "cover", None) + max_w, max_h = 720, 720 + + if not photo or not photo.width or not photo.height: + return { + "url": static("img/placeholder.png"), + "width": 1200, + "height": 800, + "aspect_ratio": round(1200 / 800, 3), + } + + aspect = photo.aspect_ratio + + if photo.width > photo.height: + w = min(photo.width, max_w) + h = floor(w / aspect) + else: + h = min(photo.height, max_h) + w = floor(h * aspect) + + return { + "url": photo.photo_md.url, + "width": w, + "height": h, + "aspect_ratio": round(aspect, 3), + } diff --git a/gallery/views.py b/gallery/views.py index 6da5e87..f7e7db4 100644 --- a/gallery/views.py +++ b/gallery/views.py @@ -1,6 +1,7 @@ from django.conf import settings +from django.core.cache import cache from django.core.paginator import Paginator -from django.db.models import Q +from django.db.models import Count, Q, Sum from django.shortcuts import get_object_or_404, redirect, render from django.views.generic import DetailView, ListView, TemplateView @@ -11,9 +12,27 @@ class AlbumsList(ListView): """Displays a paginated list of public albums.""" model = Album template_name = 'gallery/album_list.html' - queryset = Album.objects.filter(is_public=True).order_by('-album_date') paginate_by = 30 + def get_queryset(self): + page = self.request.GET.get('page', 1) + key = f'album_list_queryset_page_{page}' + queryset = cache.get(key) + + if not queryset: + queryset = ( + Album.objects.filter(is_public=True) + .select_related('cover') + .annotate( + photo_count=Count('photos'), + total_views=Sum('photos__views') + ) + .order_by('-album_date') + ) + cache.set(key, queryset, 60 * 5) + + return queryset + class AlbumDetail(DetailView): """Shows a single album and its paginated photos.""" @@ -25,7 +44,8 @@ class AlbumDetail(DetailView): def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) - photos = self.object.photos.all().order_by('taken_at') + #photos = self.object.photos.all().order_by('taken_at') + photos = self.object.photos.all().select_related('album').order_by('taken_at') paginator = Paginator(photos, 30) page_obj = paginator.get_page(self.request.GET.get('page')) @@ -67,7 +87,8 @@ class PhotoDetail(DetailView): def get_object(self, queryset=None): album_slug = self.kwargs.get('album_slug') photo_slug = self.kwargs.get('photo_slug') - photo = get_object_or_404(Photo, slug=photo_slug, album__slug=album_slug) + #photo = get_object_or_404(Photo, slug=photo_slug, album__slug=album_slug) + photo = get_object_or_404(Photo.objects.select_related('album'), slug=photo_slug, album__slug=album_slug) # Track views using session to avoid duplicate counts if photo.slug not in self.request.session: