From: Jörn Menne Date: Wed, 12 Feb 2025 15:22:06 +0000 (+0100) Subject: Add imagepreviews for an arbitrary number of images X-Git-Url: https://git.menne-pb.de/?a=commitdiff_plain;h=e46fe0619468b7ee646529c40114e36231e8c75c;p=pinpoint.git Add imagepreviews for an arbitrary number of images --- diff --git a/georeport/admin.py b/georeport/admin.py index 046a082..59de7b6 100644 --- a/georeport/admin.py +++ b/georeport/admin.py @@ -10,9 +10,18 @@ from django.contrib.auth.admin import GroupAdmin, UserAdmin from django.contrib.auth.models import Group, User from django.core.mail import send_mail from django.shortcuts import reverse +from django.utils.html import format_html from django.utils.translation import ngettext -from georeport.models import Category, Report +from georeport.models import Category, Image, Report + +from django import forms +from .minio import get_url + +import pdb +from django.utils.safestring import mark_safe + +# TODO: reorder class CategoryInline(admin.TabularInline): @@ -105,18 +114,35 @@ def getAllowedUsers(category): return qs +class ImageInline(admin.StackedInline): + model = Image + extra = 0 + can_delete = True + + @override + def has_change_permission(self, request, obj=None): + return False + + def image_preview(self, obj): + if obj.file: + url = get_url(obj.file) + return format_html(f'') + return "" + + fields = ("image_preview",) + readonly_fields = ("image_preview",) + + @admin.register(Report) class ReportAdmin(admin.ModelAdmin): + # TODO: If images are added through admin, they are not in minio. This feature has to be added exclude = [ "_oldState", ] - readonly_fields = [ - "created_at", - "updated_at", - ] - + readonly_fields = ["created_at", "updated_at"] list_display = ["title", "category__name", "state", "published"] list_filter = ["state"] + inlines = [ImageInline] @admin.action(description="Publish selected reports.") def make_public(self, request, queryset): @@ -159,6 +185,8 @@ class ReportAdmin(admin.ModelAdmin): obj._oldstate = obj.state super().save_model(request, obj, form, change) + actions = [make_public] + def send_update(report): # TODO: Tests diff --git a/georeport/forms.py b/georeport/forms.py index 00fd531..cc36a69 100644 --- a/georeport/forms.py +++ b/georeport/forms.py @@ -2,7 +2,7 @@ # GNU General Public License v3.0 (see LICSENE or https://www.gnu.org/license/gpl-3.0.md) from django.forms import ModelForm -from .models import Report +from .models import Image, Report class ReportForm(ModelForm): @@ -13,4 +13,10 @@ class ReportForm(ModelForm): class Meta: model = Report - fields = ["title", "description", "email", "category","latitude", "longitude"] + fields = ["title", "description", "email", "category", "latitude", "longitude"] + + +class ImageForm(ModelForm): + class Meta: + model = Image + fields = ["file", "report"] diff --git a/georeport/migrations/0005_image.py b/georeport/migrations/0005_image.py new file mode 100644 index 0000000..d15491e --- /dev/null +++ b/georeport/migrations/0005_image.py @@ -0,0 +1,22 @@ +# Generated by Django 5.1.5 on 2025-02-12 11:34 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('georeport', '0004_rename_user_category_users_alter_report_title'), + ] + + operations = [ + migrations.CreateModel( + name='Image', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('file', models.CharField(max_length=255)), + ('report', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='images', to='georeport.report')), + ], + ), + ] diff --git a/georeport/minio.py b/georeport/minio.py new file mode 100644 index 0000000..24cffe9 --- /dev/null +++ b/georeport/minio.py @@ -0,0 +1,62 @@ +# Copyright: (c) 2025, Jörn Menne +# GNU General Public License v3.0 (see LICSENE or https://www.gnu.org/license/gpl-3.0.md) + +from django.conf import settings +import os +import shutil +from minio import Minio +from .models import Report +from .forms import ImageForm + +client = Minio( + "localhost:9000", access_key="minio", secret_key="minio123", secure=False +) + + +def handle_file_uploads(request_files, report_dict): + """ + Handles the upload of files to + minio + """ + report = ( + Report.objects.filter(title=report_dict["title"]) # type:ignore + .filter(latitude=report_dict["latitude"]) + .filter(longitude=report_dict["longitude"]) + .first() + ) + # client = Minio( + # f"{settings.MINIO_HOST}:{settings.MINIO_PORT}", + # access_key=settings.MINIO_ACCESS_KEY, + # secret_key=settings.SECRET_KEY, + # secure=False, + # ) + + files = request_files.getlist("image") + bucketname = settings.BUCKET_NAME + if not client.bucket_exists(bucketname): + client.make_bucket(bucketname) + + pathprefix = "./georeport/static/georeport/images" + for f in files: + if not os.path.exists(pathprefix): + os.makedirs(pathprefix) + + image = {} + image["file"] = f.name + path = os.path.join(pathprefix, f.name) + with open(path, "wb+") as dest: + for chunk in f.chunks(): + dest.write(chunk) + client.fput_object(bucketname, image["file"], path) + + image["report"] = report + imgForm = ImageForm(image) + imgForm.save() + shutil.rmtree(pathprefix) + + +def get_url(filename): + """ + Wrapper for presigned_get_object + """ + return client.presigned_get_object(settings.BUCKET_NAME, filename) diff --git a/georeport/models.py b/georeport/models.py index d38b9d7..0e38a02 100644 --- a/georeport/models.py +++ b/georeport/models.py @@ -92,3 +92,16 @@ class Report(models.Model): # TODO: Image NEXT +class Image(models.Model): + """ + Representation of an image located in the minio + Each image is connected to exactly one report + """ + + file = models.CharField(max_length=255) + + report = models.ForeignKey(Report, related_name="images", on_delete=models.CASCADE) + + @override + def __str__(self) -> str: + return str(self.file) diff --git a/georeport/templates/georeport/detail.html b/georeport/templates/georeport/detail.html index cac8ebc..6522a53 100644 --- a/georeport/templates/georeport/detail.html +++ b/georeport/templates/georeport/detail.html @@ -17,13 +17,12 @@ GNU General Public License v3.0 (see LICSENE or https://www.gnu.org/license/gpl-

Longitude: {{ report.longitude }}

Status: {{ report.get_state_display }}

Kategorie: {{ report.category }}

- Back {% for img in report.images.all %} - - {{img.alt}} + {{img.file}} {% endfor %} + Back {% endblock %} diff --git a/georeport/views.py b/georeport/views.py index d3bd53f..3ccf2f3 100644 --- a/georeport/views.py +++ b/georeport/views.py @@ -7,23 +7,26 @@ Each view is associated with a url in urls.py. A view takes a request and creates a respond for the request. """ -from django.core.exceptions import PermissionDenied -from django.http import HttpResponse, HttpResponseForbidden, JsonResponse -from django.shortcuts import get_object_or_404, render, redirect -from django.utils.http import urlsafe_base64_decode -from django.views.decorators.http import require_GET, require_safe, require_http_methods - -from pinpoint_report.settings import DEFAULT_FROM_EMAIL -from .models import Category, Report +import os +import shutil +from base64 import urlsafe_b64decode -from .forms import ReportForm +from Crypto.Cipher import ChaCha20 from django.conf import settings +from django.core.exceptions import PermissionDenied from django.core.mail import send_mail +from django.http import HttpResponse, JsonResponse +from django.shortcuts import get_object_or_404, redirect, render +from django.views.decorators.http import require_GET, require_http_methods, require_safe +from minio import Minio -from Crypto.Cipher import ChaCha20 -from base64 import urlsafe_b64decode +from pinpoint_report.settings import DEFAULT_FROM_EMAIL from .admin import send_update +from .forms import ImageForm, ReportForm +from .models import Category, Report + +from .minio import handle_file_uploads, get_url # TODO: test @@ -131,7 +134,8 @@ def create_report_view(request): reportForm.save() send_creation_confirmation(report) send_creation_mail(report) - + # TODO: restrict to set number of images + handle_file_uploads(request.FILES, report) return redirect("georeport:index") return render( @@ -148,9 +152,16 @@ def report_detail_view(request, id): Returns the detail-view page of a single report """ report = get_object_or_404(Report, pk=id) - + images = report.images.all() + urls = {} + for image in images: + urls[image.file] = get_url(image.file) if report.published: - return render(request, "georeport/detail.html", context={"report": report}) + return render( + request, + "georeport/detail.html", + context={"report": report, "urls": urls}, + ) raise PermissionDenied diff --git a/pinpoint_report/settings.py b/pinpoint_report/settings.py index c4dfc8f..aeb344d 100644 --- a/pinpoint_report/settings.py +++ b/pinpoint_report/settings.py @@ -13,7 +13,6 @@ https://docs.djangoproject.com/en/5.1/ref/settings/ import sys from pathlib import Path from Crypto.Random import get_random_bytes -from minio import Minio # Build paths inside the project like this: BASE_DIR / 'subdir'. @@ -163,6 +162,4 @@ MINIO_PORT = 9000 MINIO_ACCESS_KEY = "minio" MINIO_SECRET_KEY = "minio123" -client = Minio( - f"{MINIO_HOST}:{MINIO_PORT}", access_key=MINIO_ACCESS_KEY, secret_key=SECRET_KEY -) +BUCKET_NAME = "pinpoint"