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 datetime import datetime
from math import floor from math import floor
from django.core.cache import cache
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.db import models from django.db import models
from django.db.models import Sum 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") 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") is_public = models.BooleanField(default=False, verbose_name="Published")
def _cache_key(self, suffix):
return f'album_{self.pk}_{suffix}'
@property @property
def photos_in_album(self): 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 @property
def photos_views(self): 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 return views
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
}
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
if not self.slug: if not self.slug:

View file

@ -1,6 +1,7 @@
import os import os
from datetime import datetime from datetime import datetime
from django.core.cache import cache
from django.db import models from django.db import models
from django.db.models.signals import post_save from django.db.models.signals import post_save
from django.dispatch import receiver from django.dispatch import receiver
@ -47,7 +48,6 @@ class Photo(models.Model):
self.slug = os.path.basename(self.photo.name) self.slug = os.path.basename(self.photo.name)
super().save(*args, **kwargs) super().save(*args, **kwargs)
def add_like(self): def add_like(self):
self.likes += 1 self.likes += 1
self.save(update_fields=['likes']) self.save(update_fields=['likes'])
@ -79,13 +79,19 @@ class Photo(models.Model):
def create_photo(sender, instance, created, **kwargs): @receiver(post_save, sender=Photo)
if created: def handle_photo_creation(sender, instance, created, **kwargs):
""" Add exif metadata """ if not created:
return
# Lisää EXIF metadata
if instance.photo and instance.photo.path:
exif = Exif(instance.photo.path) exif = Exif(instance.photo.path)
instance.exif = exif.data instance.exif = exif.data
""" Update taken datetime """
instance.taken_at = exif.datetimeoriginal('Europe/Helsinki') 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" %} {% extends "base.html" %}
{% load static %} {% load static %}
{% load image_tags %}
<!-- Title --> <!-- Title -->
{% block title %} Albums {% endblock %} {% block title %} Albums {% endblock %}
@ -29,13 +30,16 @@
<!-- Album cover image --> <!-- Album cover image -->
<div class="uk-card-media-top"> <div class="uk-card-media-top">
<a href="{{ album.get_absolute_url }}"> <a href="{{ album.get_absolute_url }}">
<img src="{{ album.cover_image_data.url }}" {% with album|cover_image_data as img %}
width="{{ album.cover_image_data.width }}" <img
height="{{ album.cover_image_data.height }}" src="{{ img.url }}"
style="aspect-ratio: {{ album.cover_image_data.aspect_ratio }};" width="{{ img.width }}"
height="{{ img.height }}"
style="aspect-ratio: {{ img.aspect_ratio }};"
alt="Cover image for {{ album.name }}" alt="Cover image for {{ album.name }}"
title="{{ album.name }}" title="{{ album.name }}"
loading="lazy"> loading="lazy">
{% endwith %}
</a> </a>
</div> </div>

View file

@ -1,5 +1,6 @@
{% extends "base.html" %} {% extends "base.html" %}
{% load static %} {% load static %}
{% load image_tags %}
<!-- Title --> <!-- Title -->
{% block title %} Search {% endblock %} {% block title %} Search {% endblock %}
@ -21,6 +22,7 @@
</div> </div>
<div class="uk-grid-small uk-child-width-1-2@s uk-child-width-1-3@m" uk-grid="masonry: true"> <div class="uk-grid-small uk-child-width-1-2@s uk-child-width-1-3@m" uk-grid="masonry: true">
{% if results %} {% if results %}
{% for album in results %} {% for album in results %}
<div> <div>
@ -29,13 +31,16 @@
<!-- Album cover image --> <!-- Album cover image -->
<div class="uk-card-media-top"> <div class="uk-card-media-top">
<a href="{{ album.get_absolute_url }}"> <a href="{{ album.get_absolute_url }}">
<img src="{{ album.cover_image_data.url }}" {% with album|cover_image_data as img %}
width="{{ album.cover_image_data.width }}" <img
height="{{ album.cover_image_data.height }}" src="{{ img.url }}"
style="aspect-ratio: {{ album.cover_image_data.aspect_ratio }};" width="{{ img.width }}"
height="{{ img.height }}"
style="aspect-ratio: {{ img.aspect_ratio }};"
alt="Cover image for {{ album.name }}" alt="Cover image for {{ album.name }}"
title="{{ album.name }}" title="{{ album.name }}"
loading="lazy"> loading="lazy">
{% endwith %}
</a> </a>
</div> </div>
@ -56,6 +61,7 @@
{% else %} {% else %}
<p>Not found: "{{ query }}".</p> <p>Not found: "{{ query }}".</p>
{% endif %} {% endif %}
</div> </div>
{% include "./partials/pagination.html" %} {% 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.conf import settings
from django.core.cache import cache
from django.core.paginator import Paginator 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.shortcuts import get_object_or_404, redirect, render
from django.views.generic import DetailView, ListView, TemplateView from django.views.generic import DetailView, ListView, TemplateView
@ -11,9 +12,27 @@ class AlbumsList(ListView):
"""Displays a paginated list of public albums.""" """Displays a paginated list of public albums."""
model = Album model = Album
template_name = 'gallery/album_list.html' template_name = 'gallery/album_list.html'
queryset = Album.objects.filter(is_public=True).order_by('-album_date')
paginate_by = 30 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): class AlbumDetail(DetailView):
"""Shows a single album and its paginated photos.""" """Shows a single album and its paginated photos."""
@ -25,7 +44,8 @@ class AlbumDetail(DetailView):
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super().get_context_data(**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) paginator = Paginator(photos, 30)
page_obj = paginator.get_page(self.request.GET.get('page')) page_obj = paginator.get_page(self.request.GET.get('page'))
@ -67,7 +87,8 @@ class PhotoDetail(DetailView):
def get_object(self, queryset=None): def get_object(self, queryset=None):
album_slug = self.kwargs.get('album_slug') album_slug = self.kwargs.get('album_slug')
photo_slug = self.kwargs.get('photo_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 # Track views using session to avoid duplicate counts
if photo.slug not in self.request.session: if photo.slug not in self.request.session: