diff --git a/gallery/converters.py b/gallery/converters.py new file mode 100644 index 0000000..3467908 --- /dev/null +++ b/gallery/converters.py @@ -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 \ No newline at end of file diff --git a/gallery/migrations/0001_initial.py b/gallery/migrations/0001_initial.py index e27c6f7..9dbdc01 100644 --- a/gallery/migrations/0001_initial.py +++ b/gallery/migrations/0001_initial.py @@ -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 django.db.models.deletion @@ -56,7 +56,7 @@ class Migration(migrations.Migration): name='Photo', fields=[ ('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')), ('width', models.PositiveIntegerField(default=0, editable=False, verbose_name='Photo Width')), ('height', models.PositiveIntegerField(default=0, editable=False, verbose_name='Photo Height')), @@ -70,6 +70,7 @@ class Migration(migrations.Migration): options={ 'verbose_name_plural': 'Photos', 'ordering': ('-taken_at',), + 'unique_together': {('album', 'slug')}, }, ), migrations.AddField( @@ -88,8 +89,4 @@ class Migration(migrations.Migration): 'verbose_name_plural': 'Redirs', }, ), - migrations.AddConstraint( - model_name='photo', - constraint=models.UniqueConstraint(fields=('slug',), name='unique_photo_slug'), - ), ] diff --git a/gallery/models/photo.py b/gallery/models/photo.py index ae8654a..8e5550d 100644 --- a/gallery/models/photo.py +++ b/gallery/models/photo.py @@ -1,9 +1,11 @@ import os -import random from datetime import datetime 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.utils.text import slugify from imagekit.models import ImageSpecField from imagekit.processors import ResizeToFit @@ -17,10 +19,9 @@ def get_upload_path(instance, filename): class Photo(models.Model): 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") - # Thumbnail-versiot 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_bg = ImageSpecField(source='photo', processors=[ResizeToFit(1920, 1920)], format='JPEG', options={'quality': 90}) @@ -40,20 +41,12 @@ class Photo(models.Model): @property def aspect_ratio(self): return self.width / self.height + + def save(self, *args, **kwargs): + if not self.slug: + self.slug = os.path.basename(self.photo.name) + super().save(*args, **kwargs) - @staticmethod - def generate_unique_slug(album, datetime_taken): - """ - Luo uniikin slugin, joka perustuu ottopäivämäärään. - 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): self.likes += 1 @@ -69,34 +62,26 @@ class Photo(models.Model): def get_prev(self): 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): return reverse('gallery:photo_url', kwargs={'album_slug': self.album.slug, 'photo_slug': self.slug}) class Meta: - constraints = [models.UniqueConstraint(fields=['slug'], name='unique_photo_slug')] + unique_together = ('album', "slug") verbose_name_plural = "Photos" ordering = ('-taken_at',) def __str__(self): - return f'{self.slug} ({self.orientation}) {self.is_favorite}' \ No newline at end of file + 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) \ No newline at end of file diff --git a/gallery/urls.py b/gallery/urls.py index 65c5c6c..b3f6f68 100644 --- a/gallery/urls.py +++ b/gallery/urls.py @@ -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' @@ -9,7 +12,7 @@ urlpatterns = [ path('', views.AlbumsList.as_view(), name='main_url'), path('about/', views.About.as_view(), name='about_url'), path('photostream/', views.PhotosList.as_view(), name='photos_url'), - path('albums///', views.PhotoDetail.as_view(), name='photo_url'), + path('albums//', views.PhotoDetail.as_view(), name='photo_url'), path('albums//', views.AlbumDetail.as_view(), name='album_url'), path('albums/', views.AlbumsList.as_view(), name='albums_url'), path('/', views.redirect_to_album, name='redirect_to_album'),