diff --git a/config/settings.py b/config/settings.py index 9c5a182..589b738 100644 --- a/config/settings.py +++ b/config/settings.py @@ -37,6 +37,7 @@ INSTALLED_APPS = [ 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', + 'imagekit', 'gallery' ] @@ -121,6 +122,12 @@ STATICFILES_DIRS = [ BASE_DIR / 'static', ] + +# Media files + +MEDIA_URL = 'media/' +MEDIA_ROOT = BASE_DIR / 'media' + # Default primary key field type # https://docs.djangoproject.com/en/5.1/ref/settings/#default-auto-field diff --git a/config/urls.py b/config/urls.py index 4f44605..0bb4894 100644 --- a/config/urls.py +++ b/config/urls.py @@ -25,4 +25,5 @@ urlpatterns = [ ] if settings.DEBUG: - urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT) \ No newline at end of file + urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT) + urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) diff --git a/gallery/admin.py b/gallery/admin.py index 716a0c7..802f500 100644 --- a/gallery/admin.py +++ b/gallery/admin.py @@ -1,8 +1,7 @@ from django.contrib import admin +from imagekit.admin import AdminThumbnail -from gallery.models import Album, City, Location - -# Register your models here. +from gallery.models import Album, City, Location, Photo class CityAdmin(admin.ModelAdmin): @@ -19,6 +18,7 @@ class LocationAdmin(admin.ModelAdmin): search_fields = ('place', 'city__name',) list_per_page = 30 + class AlbumAdmin(admin.ModelAdmin): prepopulated_fields = {'slug': ('name',)} list_display = ('__str__',) @@ -27,7 +27,15 @@ class AlbumAdmin(admin.ModelAdmin): list_per_page = 30 +class PhotoAdmin(admin.ModelAdmin): + list_display = ('__str__', 'album', 'admin_thumbnail',) + list_display_links = ('__str__',) + list_editable = ('album',) + readonly_fields = ['slug', 'taken_at', 'height', 'width', 'exif', ] + admin_thumbnail = AdminThumbnail(image_field='photo_thumbnail',) + + admin.site.register(City, CityAdmin) admin.site.register(Location, LocationAdmin) admin.site.register(Album, AlbumAdmin) - +admin.site.register(Photo, PhotoAdmin) diff --git a/gallery/migrations/0001_initial.py b/gallery/migrations/0001_initial.py index 395758a..179863c 100644 --- a/gallery/migrations/0001_initial.py +++ b/gallery/migrations/0001_initial.py @@ -1,7 +1,8 @@ -# Generated by Django 5.1.4 on 2024-12-29 21:23 +# Generated by Django 5.1.4 on 2025-01-05 15:24 import datetime import django.db.models.deletion +import gallery.models from django.db import migrations, models @@ -49,4 +50,17 @@ class Migration(migrations.Migration): 'verbose_name_plural': 'Albums', }, ), + migrations.CreateModel( + 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, 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')), + ('taken_at', models.DateTimeField(blank=True, editable=False, null=True, verbose_name='Taken at')), + ('exif', models.JSONField(blank=True, editable=False, null=True, verbose_name='Exif Metadata')), + ('album', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='photos', to='gallery.album', verbose_name='Album')), + ], + ), ] diff --git a/gallery/models.py b/gallery/models.py index 95af69e..24293d4 100644 --- a/gallery/models.py +++ b/gallery/models.py @@ -1,8 +1,14 @@ -from datetime import datetime +import os +from datetime import datetime, timedelta from django.db import models from django.urls import reverse from django.utils.text import slugify +from imagekit.models import ImageSpecField +from imagekit.processors import ResizeToFill +from PIL import Image + +from gallery.exif import Exif # Create your models here. @@ -53,3 +59,40 @@ class Album(models.Model): def __str__(self): return '{}'.format(self.name) + +class Photo(models.Model): + + def _get_upload_path(instance, filename): + return os.path.join('albums', str(instance.album.slug), filename) + + def _generate_unique_slug(self, datetime_taken=None): + datetime_taken = datetime_taken or datetime.now() + slug = datetime_taken.strftime('%y%m%d%H%M%S') + counter = 0 + while Photo.objects.filter(album=self.album, slug=slug).exists(): + counter += 1 + slug = (datetime_taken + timedelta(seconds=counter)).strftime('%y%m%d%H%M%S') + return slug + + album = models.ForeignKey(Album, on_delete=models.CASCADE, related_name='photos', verbose_name="Album") + slug = models.CharField(max_length=15, editable=False, verbose_name="Photo Slug") + photo = models.ImageField(upload_to=_get_upload_path, height_field='height', width_field='width', verbose_name="Photo") + photo_thumbnail = ImageSpecField(source='photo', processors=[ResizeToFill(100, 100)], format='JPEG', options={'quality': 70}) + width = models.PositiveIntegerField(default=0, editable=False, verbose_name="Photo Width") + height = models.PositiveIntegerField(default=0, editable=False, verbose_name="Photo Height") + taken_at = models.DateTimeField(blank=True, null=True, editable=False, verbose_name="Taken at") + exif = models.JSONField(blank=True, null=True, editable=False, verbose_name="Exif Metadata") + + def save(self, *args, **kwargs): + self.exif_data = Exif(self.photo.file) + datetime_taken = getattr(self.exif_data, 'datetimeoriginal', datetime.now)() + self.slug = self.slug or self._generate_unique_slug(datetime_taken) + self.taken_at = self.taken_at or datetime_taken + self.exif = getattr(self.exif_data, 'data', None) + super().save(*args, **kwargs) + + def get_absolute_url(self): + return reverse('gallery:photo_url', kwargs={'album_path': self.album.slug, 'photo_slug': self.slug}) + + def __str__(self): + return f'{self.slug} ({self.album.name})' diff --git a/gallery/templates/gallery/album_detail.html b/gallery/templates/gallery/album_detail.html index 7f7f5b6..9011f13 100644 --- a/gallery/templates/gallery/album_detail.html +++ b/gallery/templates/gallery/album_detail.html @@ -13,5 +13,8 @@ {% block content %}