Change photo slug to filename

This commit is contained in:
Nyymix 2025-03-31 22:05:29 +03:00
parent bac62c6d6d
commit 26a12450f4
4 changed files with 43 additions and 48 deletions

10
gallery/converters.py Normal file
View file

@ -0,0 +1,10 @@
class FilenamePathConverter:
regex = '[a-zA-Z0-9_-]{1,50}\.[a-zA-Z]{3,4}$'
def to_python(self, value):
# convert value to its corresponding python datatype
return value
def to_url(self, value):
# convert the value to str data
return value

View file

@ -1,4 +1,4 @@
# Generated by Django 5.1.7 on 2025-03-28 19:11 # Generated by Django 5.1.7 on 2025-03-31 18:01
import datetime import datetime
import django.db.models.deletion import django.db.models.deletion
@ -56,7 +56,7 @@ class Migration(migrations.Migration):
name='Photo', name='Photo',
fields=[ fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('slug', models.CharField(editable=False, max_length=15, unique=True, verbose_name='Photo Slug')), ('slug', models.CharField(max_length=50, verbose_name='Photo Slug')),
('photo', models.ImageField(height_field='height', upload_to=gallery.models.photo.get_upload_path, verbose_name='Photo', width_field='width')), ('photo', models.ImageField(height_field='height', upload_to=gallery.models.photo.get_upload_path, verbose_name='Photo', width_field='width')),
('width', models.PositiveIntegerField(default=0, editable=False, verbose_name='Photo Width')), ('width', models.PositiveIntegerField(default=0, editable=False, verbose_name='Photo Width')),
('height', models.PositiveIntegerField(default=0, editable=False, verbose_name='Photo Height')), ('height', models.PositiveIntegerField(default=0, editable=False, verbose_name='Photo Height')),
@ -70,6 +70,7 @@ class Migration(migrations.Migration):
options={ options={
'verbose_name_plural': 'Photos', 'verbose_name_plural': 'Photos',
'ordering': ('-taken_at',), 'ordering': ('-taken_at',),
'unique_together': {('album', 'slug')},
}, },
), ),
migrations.AddField( migrations.AddField(
@ -88,8 +89,4 @@ class Migration(migrations.Migration):
'verbose_name_plural': 'Redirs', 'verbose_name_plural': 'Redirs',
}, },
), ),
migrations.AddConstraint(
model_name='photo',
constraint=models.UniqueConstraint(fields=('slug',), name='unique_photo_slug'),
),
] ]

View file

@ -1,9 +1,11 @@
import os import os
import random
from datetime import datetime from datetime import datetime
from django.db import models from django.db import models
from django.db.models.signals import post_save
from django.dispatch import receiver
from django.urls import reverse from django.urls import reverse
from django.utils.text import slugify
from imagekit.models import ImageSpecField from imagekit.models import ImageSpecField
from imagekit.processors import ResizeToFit from imagekit.processors import ResizeToFit
@ -17,10 +19,9 @@ def get_upload_path(instance, filename):
class Photo(models.Model): class Photo(models.Model):
album = models.ForeignKey(Album, on_delete=models.CASCADE, related_name='photos', verbose_name="Album") album = models.ForeignKey(Album, on_delete=models.CASCADE, related_name='photos', verbose_name="Album")
slug = models.CharField(unique=True, max_length=15, editable=False, verbose_name="Photo Slug") slug = models.CharField(max_length=50, verbose_name="Photo Slug")
photo = models.ImageField(upload_to=get_upload_path, height_field='height', width_field='width', verbose_name="Photo") photo = models.ImageField(upload_to=get_upload_path, height_field='height', width_field='width', verbose_name="Photo")
# Thumbnail-versiot
photo_sm = ImageSpecField(source='photo', processors=[ResizeToFit(320, 320)], format='JPEG', options={'quality': 70}) photo_sm = ImageSpecField(source='photo', processors=[ResizeToFit(320, 320)], format='JPEG', options={'quality': 70})
photo_md = ImageSpecField(source='photo', processors=[ResizeToFit(720, 720)], format='JPEG', options={'quality': 80}) photo_md = ImageSpecField(source='photo', processors=[ResizeToFit(720, 720)], format='JPEG', options={'quality': 80})
photo_bg = ImageSpecField(source='photo', processors=[ResizeToFit(1920, 1920)], format='JPEG', options={'quality': 90}) photo_bg = ImageSpecField(source='photo', processors=[ResizeToFit(1920, 1920)], format='JPEG', options={'quality': 90})
@ -41,19 +42,11 @@ class Photo(models.Model):
def aspect_ratio(self): def aspect_ratio(self):
return self.width / self.height return self.width / self.height
@staticmethod def save(self, *args, **kwargs):
def generate_unique_slug(album, datetime_taken): if not self.slug:
""" self.slug = os.path.basename(self.photo.name)
Luo uniikin slugin, joka perustuu ottopäivämäärään. super().save(*args, **kwargs)
Lisää tarvittaessa satunnaisluvun, jos slug on jo olemassa.
"""
base_slug = datetime_taken.strftime('%y%m%d%H%M%S') # esim. 240304153045
slug = f"{base_slug}{random.randint(0, 9)}"
while Photo.objects.filter(slug=slug).exists():
slug = f"{base_slug}{random.randint(10, 99)}" # Jos törmäys, lisää 2 satunnaisnumeroa
return slug
def add_like(self): def add_like(self):
self.likes += 1 self.likes += 1
@ -69,34 +62,26 @@ class Photo(models.Model):
def get_prev(self): def get_prev(self):
return self.__class__.objects.filter(taken_at__lt=self.taken_at, album=self.album).order_by('-taken_at').first() return self.__class__.objects.filter(taken_at__lt=self.taken_at, album=self.album).order_by('-taken_at').first()
def extract_metadata(self):
"""Lukee Exif-metadatan ja asettaa tiedot ennen kuvan tallennusta."""
if not self.photo:
return # Jos kuvaa ei ole, ei tehdä mitään
try:
exif_data = Exif(self.photo.file) # Luetaan Exif-metadata suoraan muistista
self.taken_at = exif_data.datetimeoriginal or datetime.now()
self.exif = exif_data.data or None
except Exception as e:
print(f"Exif-tiedon lukeminen epäonnistui: {e}")
self.taken_at = datetime.now()
def save(self, *args, **kwargs):
"""Ennen tallennusta luetaan Exif ja asetetaan slug, jos se puuttuu."""
if not self.slug:
self.extract_metadata() # Varmistetaan, että ottoaika on saatavilla
self.slug = self.generate_unique_slug(self.album, self.taken_at or datetime.now())
super().save(*args, **kwargs)
def get_absolute_url(self): def get_absolute_url(self):
return reverse('gallery:photo_url', kwargs={'album_slug': self.album.slug, 'photo_slug': self.slug}) return reverse('gallery:photo_url', kwargs={'album_slug': self.album.slug, 'photo_slug': self.slug})
class Meta: class Meta:
constraints = [models.UniqueConstraint(fields=['slug'], name='unique_photo_slug')] unique_together = ('album', "slug")
verbose_name_plural = "Photos" verbose_name_plural = "Photos"
ordering = ('-taken_at',) ordering = ('-taken_at',)
def __str__(self): def __str__(self):
return f'{self.slug} ({self.orientation}) {self.is_favorite}' return f'{self.slug} ({self.orientation}) {self.is_favorite}'
def create_photo(sender, instance, created, **kwargs):
if created:
""" Add exif metadata """
exif = Exif(instance.photo.path)
instance.exif = exif.data
""" Update taken datetime """
instance.taken_at = exif.datetimeoriginal('Europe/Helsinki')
instance.save()
post_save.connect(create_photo, sender=Photo)

View file

@ -1,6 +1,9 @@
from django.urls import path from django.urls import path, register_converter
from . import converters, views
register_converter(converters.FilenamePathConverter, 'filename')
from . import views
app_name = 'gallery' app_name = 'gallery'
@ -9,7 +12,7 @@ urlpatterns = [
path('', views.AlbumsList.as_view(), name='main_url'), path('', views.AlbumsList.as_view(), name='main_url'),
path('about/', views.About.as_view(), name='about_url'), path('about/', views.About.as_view(), name='about_url'),
path('photostream/', views.PhotosList.as_view(), name='photos_url'), path('photostream/', views.PhotosList.as_view(), name='photos_url'),
path('albums/<path:album_slug>/<int:photo_slug>/', views.PhotoDetail.as_view(), name='photo_url'), path('albums/<path:album_slug>/<filename:photo_slug>', views.PhotoDetail.as_view(), name='photo_url'),
path('albums/<path:album_slug>/', views.AlbumDetail.as_view(), name='album_url'), path('albums/<path:album_slug>/', views.AlbumDetail.as_view(), name='album_url'),
path('albums/', views.AlbumsList.as_view(), name='albums_url'), path('albums/', views.AlbumsList.as_view(), name='albums_url'),
path('<path:redir_path>/', views.redirect_to_album, name='redirect_to_album'), path('<path:redir_path>/', views.redirect_to_album, name='redirect_to_album'),