]> git.menne-pb.de Git - pinpoint.git/commitdiff
Add imagepreviews for an arbitrary number of images
authorJörn Menne <jmenne@fedora.de>
Wed, 12 Feb 2025 15:22:06 +0000 (16:22 +0100)
committerJörn Menne <jmenne@fedora.de>
Thu, 13 Feb 2025 08:13:10 +0000 (09:13 +0100)
georeport/admin.py
georeport/forms.py
georeport/migrations/0005_image.py [new file with mode: 0644]
georeport/minio.py [new file with mode: 0644]
georeport/models.py
georeport/templates/georeport/detail.html
georeport/views.py
pinpoint_report/settings.py

index 046a082f2a1b28be57f09e74aa0afe04b056cdb2..59de7b65c0e07b32f972aed764300ad5b2e9bdf4 100644 (file)
@@ -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'<img src="{url}" width="300px" height="300px"/>')
+        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
index 00fd531ebb22538532a39cde7c2b7862eae399f2..cc36a694cdece1ef6b0d7a0299a51542324f3da3 100644 (file)
@@ -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 (file)
index 0000000..d15491e
--- /dev/null
@@ -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 (file)
index 0000000..24cffe9
--- /dev/null
@@ -0,0 +1,62 @@
+# Copyright: (c) 2025, Jörn Menne <jmenne@posteo.de>
+# 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)
index d38b9d7a550daf0586f433f98f1e3f6d6035b042..0e38a025dc277729848435fc0746c94a09623a51 100644 (file)
@@ -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)
index cac8ebc3e89c1360b67dca0b933985ed3c6e8274..6522a535481910c43597ceb64c7e1fbfd54ae0c4 100644 (file)
@@ -17,13 +17,12 @@ GNU General Public License v3.0 (see LICSENE or https://www.gnu.org/license/gpl-
     <p id="p-lng" data-lng="{{ report.longitude }}">Longitude: {{ report.longitude }}</p>
     <p>Status: {{ report.get_state_display }} </p>
     <p>Kategorie: {{ report.category }} </p>
-    <a href="{% url 'georeport:index' %}">Back</a>
     <script src="{% static 'georeport/addMarker.js' %}"></script>
 <!--    <img src="{{report.image.url}}" alt="Kein Bild vorhanden" scale=0.25>-->
     {% for img in report.images.all %}
-        <!--<img src="{% static 'georeport/images/' %}{{img.file}}" alt={{img.alt}} scale=0.25 width=500px>-->
-        <img src={{urls|key:img.alt}} alt={{img.alt}} width=500px>
+        <img src={{urls|key:img.file}} alt={{img.file}} width=500px>
     {% endfor %}
+    <a href="{% url 'georeport:index' %}">Back</a>
 </div>
 
 {% endblock %}
index d3bd53ff3e5a648f0a1e5b06576e16c5bd3b1e49..3ccf2f3df74de19a1c4c39ad21a23b10264048c8 100644 (file)
@@ -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
 
index c4dfc8f0982476c3d28a2e467203fbe503865cf2..aeb344d55228e24f9575de147759f70dc9bedac8 100644 (file)
@@ -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"