Add cache and optimize code

This commit is contained in:
Nyymix 2025-04-14 10:26:50 +03:00
parent 2390968029
commit 473df39cbe
6 changed files with 114 additions and 54 deletions

View file

@ -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:

View file

@ -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)
# 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'])

View file

@ -1,5 +1,6 @@
{% extends "base.html" %}
{% load static %}
{% load image_tags %}
<!-- Title -->
{% block title %} Albums {% endblock %}
@ -29,13 +30,16 @@
<!-- Album cover image -->
<div class="uk-card-media-top">
<a href="{{ album.get_absolute_url }}">
<img src="{{ album.cover_image_data.url }}"
width="{{ album.cover_image_data.width }}"
height="{{ album.cover_image_data.height }}"
style="aspect-ratio: {{ album.cover_image_data.aspect_ratio }};"
{% with album|cover_image_data as img %}
<img
src="{{ img.url }}"
width="{{ img.width }}"
height="{{ img.height }}"
style="aspect-ratio: {{ img.aspect_ratio }};"
alt="Cover image for {{ album.name }}"
title="{{ album.name }}"
loading="lazy">
{% endwith %}
</a>
</div>

View file

@ -1,5 +1,6 @@
{% extends "base.html" %}
{% load static %}
{% load image_tags %}
<!-- Title -->
{% block title %} Search {% endblock %}
@ -21,6 +22,7 @@
</div>
<div class="uk-grid-small uk-child-width-1-2@s uk-child-width-1-3@m" uk-grid="masonry: true">
{% if results %}
{% for album in results %}
<div>
@ -29,13 +31,16 @@
<!-- Album cover image -->
<div class="uk-card-media-top">
<a href="{{ album.get_absolute_url }}">
<img src="{{ album.cover_image_data.url }}"
width="{{ album.cover_image_data.width }}"
height="{{ album.cover_image_data.height }}"
style="aspect-ratio: {{ album.cover_image_data.aspect_ratio }};"
{% with album|cover_image_data as img %}
<img
src="{{ img.url }}"
width="{{ img.width }}"
height="{{ img.height }}"
style="aspect-ratio: {{ img.aspect_ratio }};"
alt="Cover image for {{ album.name }}"
title="{{ album.name }}"
loading="lazy">
{% endwith %}
</a>
</div>
@ -56,6 +61,7 @@
{% else %}
<p>Not found: "{{ query }}".</p>
{% endif %}
</div>
{% include "./partials/pagination.html" %}

View file

@ -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),
}

View file

@ -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: