From 555c6c0d892799712f1630d20de43aee250978d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9DNyymix=E2=80=9D?= Date: Mon, 24 Feb 2025 20:23:07 +0200 Subject: [PATCH] Add upload form and update Photo model --- gallery/admin.py | 72 ++++++++--- gallery/migrations/0001_initial.py | 7 +- gallery/models/photo.py | 49 +++++--- .../admin/gallery/album/change_form.html | 7 ++ gallery/templates/admin/upload_photos.html | 114 ++++++++++++++++++ 5 files changed, 211 insertions(+), 38 deletions(-) create mode 100644 gallery/templates/admin/gallery/album/change_form.html create mode 100644 gallery/templates/admin/upload_photos.html diff --git a/gallery/admin.py b/gallery/admin.py index 90fb38b..b6398eb 100644 --- a/gallery/admin.py +++ b/gallery/admin.py @@ -1,4 +1,7 @@ from django.contrib import admin +from django.http import JsonResponse +from django.shortcuts import get_object_or_404, render +from django.urls import path, reverse from django.utils.html import format_html from imagekit import ImageSpec from imagekit.admin import AdminThumbnail @@ -15,21 +18,23 @@ class AdminThumbnailSpec(ImageSpec): def cached_admin_thumb(instance): - cached = ImageCacheFile(AdminThumbnailSpec(instance.photo)) - cached.generate() - return cached + if instance.photo: + cached = ImageCacheFile(AdminThumbnailSpec(instance.photo)) + cached.generate() + return cached + return None class RedirAdmin(admin.ModelAdmin): list_display = ('path', 'album', 'test_url') search_fields = ('path',) ordering = ('path',) - list_per_page = 10 + list_per_page = 30 list_editable = ('album',) def formfield_for_foreignkey(self, db_field, request, **kwargs): if db_field.name == "album": - kwargs["queryset"] = Album.objects.all().order_by('name') # Järjestä albumit aakkosjärjestykseen + kwargs["queryset"] = Album.objects.all().order_by('name') return super().formfield_for_foreignkey(db_field, request, **kwargs) def test_url(self, obj): @@ -57,12 +62,50 @@ class LocationAdmin(admin.ModelAdmin): class AlbumAdmin(admin.ModelAdmin): prepopulated_fields = {'slug': ('name',)} - list_display = ('name', 'location', 'album_date', 'is_public', 'thumbnail', ) + list_display = ('name', 'location', 'album_date', 'is_public', 'upload_link', 'thumbnail') search_fields = ('name',) ordering = ('-album_date',) list_per_page = 20 list_editable = ('is_public',) - readonly_fields = ['cover_preview'] # Lisätään esikatselukuva readonly_fieldsiin + readonly_fields = ['cover_preview'] + + change_form_template = "admin/gallery/album/change_form.html" + + def get_urls(self): + urls = super().get_urls() + custom_urls = [ + path('/upload/', self.admin_site.admin_view(self.upload_photos), name="gallery_album_upload"), + ] + return custom_urls + urls + + def upload_photos(self, request, album_id, *args, **kwargs): + """Käsittelee valokuvien lataamisen albumiin.""" + album = get_object_or_404(Album, id=album_id) + + if request.method == 'POST' and request.FILES: + uploaded_files = request.FILES.getlist('photo') + for file in uploaded_files: + Photo.objects.create(album=album, photo=file) + return JsonResponse({"message": "Photos uploaded successfully!"}) + + return render(request, 'admin/upload_photos.html', {'album': album}) + + def upload_link(self, obj): + """Lisää 'Upload Photos' -painikkeen albumin muokkaussivulle""" + if obj.id: + url = reverse("admin:gallery_album_upload", kwargs={"album_id": obj.id}) + return format_html('Upload Photos', url) + return "-" + upload_link.short_description = "Upload Photos" + + def thumbnail(self, obj): + if obj.cover and obj.cover.photo: + return format_html( + '', + obj.cover.photo.url, + ) + return "-" + thumbnail.short_description = "Thumbnail" def cover_preview(self, obj): if obj.cover and obj.cover.photo: @@ -81,21 +124,12 @@ class AlbumAdmin(admin.ModelAdmin): return super().formfield_for_foreignkey(db_field, request, **kwargs) - def thumbnail(self, obj): - if obj.cover and obj.cover.photo: - return format_html( - '', - obj.cover.photo.url, - ) - return "-" - thumbnail.short_description = "Thumbnail" - class PhotoAdmin(admin.ModelAdmin): - list_display = ('slug', 'album', 'is_favorite', 'admin_thumbnail',) + list_display = ('slug', 'album', 'is_favorite', 'admin_thumbnail') list_display_links = ('slug',) - search_fields = ('slug', 'photo',) - readonly_fields = ['slug', 'taken_at', 'height', 'width', 'exif', ] + search_fields = ('slug', 'photo') + readonly_fields = ['slug', 'taken_at', 'height', 'width', 'exif'] admin_thumbnail = AdminThumbnail(image_field=cached_admin_thumb) list_filter = ('album',) list_per_page = 30 diff --git a/gallery/migrations/0001_initial.py b/gallery/migrations/0001_initial.py index 94c7531..c9535d6 100644 --- a/gallery/migrations/0001_initial.py +++ b/gallery/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 5.1.4 on 2025-01-20 19:09 +# Generated by Django 5.1.4 on 2025-02-23 15:54 import datetime import django.db.models.deletion @@ -33,7 +33,8 @@ class Migration(migrations.Migration): ], options={ 'verbose_name_plural': 'Locations', - 'unique_together': {('city', 'place')}, + 'ordering': ['city'], + 'unique_together': {('place', 'city')}, }, ), migrations.CreateModel( @@ -56,7 +57,7 @@ class Migration(migrations.Migration): 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.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')), ('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')), diff --git a/gallery/models/photo.py b/gallery/models/photo.py index ac9ed00..112e008 100644 --- a/gallery/models/photo.py +++ b/gallery/models/photo.py @@ -10,23 +10,21 @@ from gallery.exif import Exif from gallery.models import Album +def get_upload_path(instance, filename): + """Määrittää lopullisen tallennuspolun heti kuvan tallennusvaiheessa.""" + return os.path.join('albums', str(instance.album.slug), filename) + + 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=datetime.now()): - slug = int(datetime_taken.strftime('%y%m%d%H%M%S')) - while Photo.objects.filter(album=self.album, slug=slug).exists(): - slug += 1 - return str(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 = 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}) + 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") @@ -39,6 +37,14 @@ class Photo(models.Model): def orientation(self): return "Portrait" if self.height > self.width else "Landscape" + @staticmethod + def generate_unique_slug(album, datetime_taken): + """Luo yksilöllisen slug-arvon tiedostolle albumin sisällä.""" + slug = int(datetime_taken.strftime('%y%m%d%H%M%S')) + while Photo.objects.filter(album=album, slug=str(slug)).exists(): + slug += 1 + return str(slug) + def add_like(self): self.likes += 1 self.save() @@ -53,12 +59,23 @@ class Photo(models.Model): def get_prev(self): return self.__class__.objects.filter(taken_at__lt=self.taken_at, album=self.album.id).order_by('-taken_at').first() + def extract_metadata(self): + """Lukee Exif-metadatan ja asettaa tiedot ennen kuvan tallennusta.""" + if self.photo: + try: + exif_data = Exif(self.photo.file) # Suoraan muistista, ei tiedostosta + self.taken_at = getattr(exif_data, 'datetimeoriginal', datetime.now)() + self.exif = getattr(exif_data, 'data', None) + except Exception as e: + print(f"Exif-tiedon lukeminen epäonnistui: {e}") + self.taken_at = datetime.now() + def save(self, *args, **kwargs): - self.exif_data = Exif(self.photo.path) - 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) + """Ennen tallennusta luetaan Exif ja asetetaan slug.""" + if not self.slug: + self.extract_metadata() + self.slug = self.generate_unique_slug(self.album, self.taken_at or datetime.now()) + super().save(*args, **kwargs) def get_absolute_url(self): diff --git a/gallery/templates/admin/gallery/album/change_form.html b/gallery/templates/admin/gallery/album/change_form.html new file mode 100644 index 0000000..0452e34 --- /dev/null +++ b/gallery/templates/admin/gallery/album/change_form.html @@ -0,0 +1,7 @@ +{% extends "admin/change_form.html" %} +{% block object-tools-items %} + {{ block.super }} +
  • + Upload Photos +
  • +{% endblock %} \ No newline at end of file diff --git a/gallery/templates/admin/upload_photos.html b/gallery/templates/admin/upload_photos.html new file mode 100644 index 0000000..7cce70d --- /dev/null +++ b/gallery/templates/admin/upload_photos.html @@ -0,0 +1,114 @@ +{% extends "admin/base_site.html" %} +{% block content %} +
    +

    Upload Photos to {{ album.name }}

    + + + + + +
    + {% csrf_token %} + +
    +
    + + Drag files here or +
    + + select them +
    +
    + +
      +

      Total size: 0 MB

      + +
      + + +
      +
      + + +{% endblock %}