From 8750f266d8831c520f142268e55c120df51e14eb Mon Sep 17 00:00:00 2001
From: Julian Rother <julian@cccv.de>
Date: Sun, 5 Mar 2023 04:11:39 +0100
Subject: [PATCH] Add uncached server-side thumbnails

---
 warehouse/__init__.py              | 12 +++++++++++-
 warehouse/models.py                |  6 ++++++
 warehouse/templates/item/list.html |  2 +-
 warehouse/templates/item/view.html |  2 +-
 warehouse/utils/__init__.py        | 11 ++++++++++-
 5 files changed, 29 insertions(+), 4 deletions(-)

diff --git a/warehouse/__init__.py b/warehouse/__init__.py
index 211efbd..e02dcc4 100644
--- a/warehouse/__init__.py
+++ b/warehouse/__init__.py
@@ -6,7 +6,7 @@ from flask import Flask, render_template, request, redirect, url_for, Response,
 from requests_oauthlib import OAuth2Session
 
 from .models import db, Item, Location, Photo
-from .utils import render_pdf, print_pdf, qrcode_svg, render_markdown
+from .utils import render_pdf, print_pdf, qrcode_svg, render_markdown, send_thumbnail
 
 app = Flask(__name__, instance_relative_config=True)
 app.config.from_pyfile('config.cfg')
@@ -183,6 +183,16 @@ def item_locate(item_id):
 def photo_show(photo_id):
 	return Photo.query.get_or_404(photo_id).send()
 
+@app.route('/photos/<int:photo_id>/small')
+def photo_small(photo_id):
+	photo = Photo.query.get_or_404(photo_id)
+	return send_thumbnail(photo.image, 130, 100)
+
+@app.route('/photos/<int:photo_id>/medium')
+def photo_medium(photo_id):
+	photo = Photo.query.get_or_404(photo_id)
+	return send_thumbnail(photo.image, 390, 300)
+
 @app.route('/C/<code>')
 @app.route('/c/<code>')
 def qrcode_url(code):
diff --git a/warehouse/models.py b/warehouse/models.py
index 866bacc..5803213 100644
--- a/warehouse/models.py
+++ b/warehouse/models.py
@@ -3,6 +3,7 @@ import os
 
 from flask import current_app, send_file
 from flask_sqlalchemy import SQLAlchemy
+from PIL import Image
 
 db = SQLAlchemy()
 
@@ -41,6 +42,11 @@ class Photo(db.Model):
 		path = os.path.join(current_app.config['UPLOAD_FOLDER'], self.path)
 		return send_file(path, mimetype=self.mimetype)
 
+	@property
+	def image(self):
+		path = os.path.join(current_app.config['UPLOAD_FOLDER'], self.path)
+		return Image.open(path, formats=['JPEG'])
+
 def token_typable():
 	alphabet = '23456789ABCDEFGHJKLMNPQRSTUVWX' # not '01OIYZ'
 	return ''.join([secrets.choice(alphabet) for _ in range(5)])
diff --git a/warehouse/templates/item/list.html b/warehouse/templates/item/list.html
index 2df46a6..985e503 100644
--- a/warehouse/templates/item/list.html
+++ b/warehouse/templates/item/list.html
@@ -24,7 +24,7 @@
 			<td style="width: 140px;">
 				{% if item.photos %}
 					<a href="{{ url_for('item_view', item_id=item.id) }}" style="width: 130px; height: 100px;" class="d-flex align-items-center justify-content-center border text-bg-light">
-						<img src="{{ url_for('photo_show', photo_id=(item.photos|first).id) }}" style="max-width: 100%; max-height: 100%;">
+						<img src="{{ url_for('photo_small', photo_id=(item.photos|first).id) }}" style="max-width: 100%; max-height: 100%;">
 					</a>
 				{% endif %}
 			</td>
diff --git a/warehouse/templates/item/view.html b/warehouse/templates/item/view.html
index ce0d961..7e6905d 100644
--- a/warehouse/templates/item/view.html
+++ b/warehouse/templates/item/view.html
@@ -39,7 +39,7 @@
 				{% for photo in item.photos %}
 				<div class="carousel-item {{ 'active' if loop.first }}" style="width: 100%; height: 100%;">
 					<a href="{{ url_for('photo_show', photo_id=photo.id) }}" style="width: 100%; height: 100%;" class="d-flex align-items-center justify-content-center">
-						<img src="{{ url_for('photo_show', photo_id=photo.id) }}" style="max-width: 100%; max-height: 100%;">
+						<img src="{{ url_for('photo_medium', photo_id=photo.id) }}" style="max-width: 100%; max-height: 100%;">
 					</a>
 				</div>
 				{% endfor %}
diff --git a/warehouse/utils/__init__.py b/warehouse/utils/__init__.py
index 1fbb582..676a459 100644
--- a/warehouse/utils/__init__.py
+++ b/warehouse/utils/__init__.py
@@ -2,7 +2,8 @@ from .pdf import render_pdf
 from .ipp import print_pdf
 from .codes import qrcode_svg
 
-from flask import Markup
+import io
+from flask import Markup, send_file
 import markdown
 import bleach
 
@@ -16,3 +17,11 @@ def render_markdown(text, short=False):
 		result = result.split('<p>', 1)[-1]
 		result = result.split('</p>', 1)[0]
 	return Markup(result)
+
+def send_thumbnail(img, max_width, max_height):
+	scale = min(max_width/img.width, max_height/img.height)
+	img.thumbnail((int(img.width*scale), int(img.height*scale)))
+	buf = io.BytesIO()
+	img.save(buf, format='JPEG', quality=70)
+	buf.seek(0)
+	return send_file(buf, mimetype='image/jpeg')
-- 
GitLab