new implementation of file uploading
This commit is contained in:
+72
-39
@@ -26,6 +26,7 @@ from jinja2 import TemplateNotFound
|
|||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
from bs4 import BeautifulSoup
|
from bs4 import BeautifulSoup
|
||||||
|
from gridfs import GridFS
|
||||||
|
|
||||||
# Ensure imports work regardless of whether gunicorn starts in /app or /app/Web.
|
# Ensure imports work regardless of whether gunicorn starts in /app or /app/Web.
|
||||||
_CURRENT_DIR = os.path.dirname(os.path.abspath(__file__))
|
_CURRENT_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||||
@@ -395,6 +396,12 @@ SENSITIVE_AUDIT_FIELDS = ["email", "username", "full_name", "phone", "borrower",
|
|||||||
APP_VERSION = __version__
|
APP_VERSION = __version__
|
||||||
RELEASE_STATE_FILE = os.path.join(os.path.dirname(BASE_DIR), '.release-version')
|
RELEASE_STATE_FILE = os.path.join(os.path.dirname(BASE_DIR), '.release-version')
|
||||||
|
|
||||||
|
def get_gridfs():
|
||||||
|
"""Return a GridFS instance connected to the current tenant's database context."""
|
||||||
|
client = cfg.MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT)
|
||||||
|
db = client[cfg.MONGODB_DB] # Intercepted by your multi-tenant proxy automatically
|
||||||
|
return GridFS(db)
|
||||||
|
|
||||||
""" -----------------------------------------------------------------------Before Request Handlers---------------------------------------------------------------------------- """
|
""" -----------------------------------------------------------------------Before Request Handlers---------------------------------------------------------------------------- """
|
||||||
|
|
||||||
def _get_csrf_token():
|
def _get_csrf_token():
|
||||||
@@ -797,7 +804,7 @@ def _get_school_info_for_export():
|
|||||||
|
|
||||||
|
|
||||||
def _save_school_logo_upload(upload_file, tenant_id=None, tenant_db=None):
|
def _save_school_logo_upload(upload_file, tenant_id=None, tenant_db=None):
|
||||||
"""Save an uploaded school logo to the shared upload folder with a tenant-specific filename."""
|
"""Save an uploaded school logo directly into MongoDB GridFS."""
|
||||||
if not upload_file or not getattr(upload_file, 'filename', ''):
|
if not upload_file or not getattr(upload_file, 'filename', ''):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@@ -813,33 +820,44 @@ def _save_school_logo_upload(upload_file, tenant_id=None, tenant_db=None):
|
|||||||
raise ValueError('Das Logo benötigt eine Dateiendung.')
|
raise ValueError('Das Logo benötigt eine Dateiendung.')
|
||||||
|
|
||||||
logo_filename = f'school-logo-{safe_tenant}{extension}'
|
logo_filename = f'school-logo-{safe_tenant}{extension}'
|
||||||
logo_path = os.path.join(app.config['UPLOAD_FOLDER'], logo_filename)
|
thumb_filename = f'school-logo-{safe_tenant}-thumb.png'
|
||||||
|
|
||||||
os.makedirs(app.config['UPLOAD_FOLDER'], exist_ok=True)
|
fs = get_gridfs()
|
||||||
upload_file.save(logo_path)
|
|
||||||
|
|
||||||
# Generate a thumbnail for consistent navbar sizing
|
# Read file data into memory
|
||||||
|
upload_file.seek(0)
|
||||||
|
file_bytes = upload_file.read()
|
||||||
|
|
||||||
|
# Save main logo to GridFS
|
||||||
|
fs.put(
|
||||||
|
io.BytesIO(file_bytes),
|
||||||
|
filename=logo_filename,
|
||||||
|
content_type=upload_file.content_type or 'image/png'
|
||||||
|
)
|
||||||
|
|
||||||
|
# Generate thumbnail in-memory and save to GridFS
|
||||||
try:
|
try:
|
||||||
thumb_filename = f'school-logo-{safe_tenant}-thumb.png'
|
with Image.open(io.BytesIO(file_bytes)) as img:
|
||||||
thumb_path = os.path.join(app.config['UPLOAD_FOLDER'], thumb_filename)
|
|
||||||
|
|
||||||
with Image.open(logo_path) as img:
|
|
||||||
# Ensure RGBA for transparency and convert if needed
|
|
||||||
if img.mode not in ('RGBA', 'RGB'):
|
if img.mode not in ('RGBA', 'RGB'):
|
||||||
img = img.convert('RGBA')
|
img = img.convert('RGBA')
|
||||||
|
|
||||||
# Target max size: width up to 520px, height up to 114px (matches CSS constraints)
|
|
||||||
max_thumb_size = (520, 114)
|
max_thumb_size = (520, 114)
|
||||||
img.thumbnail(max_thumb_size, Image.LANCZOS)
|
img.thumbnail(max_thumb_size, Image.LANCZOS)
|
||||||
|
|
||||||
# Save as PNG for predictable rendering in web UI
|
thumb_io = io.BytesIO()
|
||||||
img.save(thumb_path, format='PNG', optimize=True)
|
img.save(thumb_io, format='PNG', optimize=True)
|
||||||
|
thumb_io.seek(0)
|
||||||
|
|
||||||
|
fs.put(
|
||||||
|
thumb_io,
|
||||||
|
filename=thumb_filename,
|
||||||
|
content_type='image/png'
|
||||||
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
app.logger.warning(f"Thumbnail generation failed for {logo_path}: {e}")
|
app.logger.warning(f"Thumbnail generation failed for logo: {e}")
|
||||||
thumb_filename = None
|
thumb_filename = None
|
||||||
|
|
||||||
return (logo_filename, thumb_filename)
|
return (logo_filename, thumb_filename)
|
||||||
|
|
||||||
"""---------------------------------------------Invoice Generation----------------------------------------------------------------------------- """
|
"""---------------------------------------------Invoice Generation----------------------------------------------------------------------------- """
|
||||||
|
|
||||||
def _parse_money_value(value):
|
def _parse_money_value(value):
|
||||||
@@ -2534,14 +2552,15 @@ def upload_student_cards_excel():
|
|||||||
|
|
||||||
"""-------------------------------------------------------------File Serving-----------------------------------------------------------------------------"""
|
"""-------------------------------------------------------------File Serving-----------------------------------------------------------------------------"""
|
||||||
|
|
||||||
|
|
||||||
@app.route('/uploads/<filename>')
|
@app.route('/uploads/<filename>')
|
||||||
def uploaded_file(filename):
|
def uploaded_file(filename):
|
||||||
"""
|
"""
|
||||||
Serve uploaded files from the uploads directory.
|
Serve uploaded files from MongoDB GridFS with local path fallbacks.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
filename (str): Name of the file to serve
|
filename (str): Name of the file to serve
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
flask.Response: The requested file or placeholder image if not found
|
flask.Response: The requested file or placeholder image if not found
|
||||||
"""
|
"""
|
||||||
@@ -2550,24 +2569,37 @@ def uploaded_file(filename):
|
|||||||
if denied:
|
if denied:
|
||||||
return denied
|
return denied
|
||||||
|
|
||||||
# Check production path first (deployed environment)
|
# 1. Try serving from MongoDB GridFS first
|
||||||
|
try:
|
||||||
|
fs = get_gridfs()
|
||||||
|
grid_out = fs.find_one({'filename': filename})
|
||||||
|
if grid_out:
|
||||||
|
return send_file(
|
||||||
|
io.BytesIO(grid_out.read()),
|
||||||
|
mimetype=grid_out.content_type or 'application/octet-stream',
|
||||||
|
download_name=filename
|
||||||
|
)
|
||||||
|
except Exception as grid_err:
|
||||||
|
app.logger.warning(f"GridFS lookup failed for {filename}: {grid_err}")
|
||||||
|
|
||||||
|
# 2. Fallback to production path for legacy local files
|
||||||
prod_path = "/opt/Inventarsystem/Web/uploads"
|
prod_path = "/opt/Inventarsystem/Web/uploads"
|
||||||
dev_path = app.config['UPLOAD_FOLDER']
|
dev_path = app.config['UPLOAD_FOLDER']
|
||||||
if os.path.exists(os.path.join(prod_path, filename)):
|
if os.path.exists(os.path.join(prod_path, filename)):
|
||||||
return send_from_directory(prod_path, filename)
|
return send_from_directory(prod_path, filename)
|
||||||
# Then check development path
|
# 3. Fallback to development path
|
||||||
if os.path.exists(os.path.join(dev_path, filename)):
|
if os.path.exists(os.path.join(dev_path, filename)):
|
||||||
return send_from_directory(dev_path, filename)
|
return send_from_directory(dev_path, filename)
|
||||||
|
|
||||||
# Use a placeholder image if file not found - first try SVG, then PNG
|
# 4. Use a placeholder image if file not found - first try SVG, then PNG
|
||||||
svg_placeholder_path = os.path.join(app.static_folder, 'img', 'no-image.svg')
|
svg_placeholder_path = os.path.join(app.static_folder, 'img', 'no-image.svg')
|
||||||
png_placeholder_path = os.path.join(app.static_folder, 'img', 'no-image.png')
|
png_placeholder_path = os.path.join(app.static_folder, 'img', 'no-image.png')
|
||||||
|
|
||||||
if os.path.exists(svg_placeholder_path):
|
if os.path.exists(svg_placeholder_path):
|
||||||
return send_from_directory(app.static_folder, 'img/no-image.svg')
|
return send_from_directory(app.static_folder, 'img/no-image.svg')
|
||||||
elif os.path.exists(png_placeholder_path):
|
elif os.path.exists(png_placeholder_path):
|
||||||
return send_from_directory(app.static_folder, 'img/no-image.png')
|
return send_from_directory(app.static_folder, 'img/no-image.png')
|
||||||
|
|
||||||
# Default placeholder from static folder
|
# Default placeholder from static folder
|
||||||
return send_from_directory(app.static_folder, 'favicon.ico')
|
return send_from_directory(app.static_folder, 'favicon.ico')
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -10811,7 +10843,7 @@ def admin_school_settings():
|
|||||||
flash('Bitte melden Sie sich mit einem administrativen Konto an.', 'error')
|
flash('Bitte melden Sie sich mit einem administrativen Konto an.', 'error')
|
||||||
return redirect(url_for('login'))
|
return redirect(url_for('login'))
|
||||||
current_permissions = us.get_effective_permissions(session['username'])
|
current_permissions = us.get_effective_permissions(session['username'])
|
||||||
|
|
||||||
if not current_permissions['actions'].get('can_manage_settings', False):
|
if not current_permissions['actions'].get('can_manage_settings', False):
|
||||||
flash('Ihnen fehlen die nötigen Berechtigungen, um diese Aktion auszuführen.', 'error')
|
flash('Ihnen fehlen die nötigen Berechtigungen, um diese Aktion auszuführen.', 'error')
|
||||||
return redirect(url_for('home_admin'))
|
return redirect(url_for('home_admin'))
|
||||||
@@ -10855,21 +10887,22 @@ def admin_school_settings():
|
|||||||
school_info['logo_path'] = saved_logo_filename
|
school_info['logo_path'] = saved_logo_filename
|
||||||
school_info['logo_thumb'] = saved_thumb_filename or ''
|
school_info['logo_thumb'] = saved_thumb_filename or ''
|
||||||
|
|
||||||
# remove previous files if different
|
fs = get_gridfs()
|
||||||
|
# Remove previous logo files from GridFS if different
|
||||||
if previous_logo_path and previous_logo_path != saved_logo_filename:
|
if previous_logo_path and previous_logo_path != saved_logo_filename:
|
||||||
previous_logo_file = os.path.join(app.config['UPLOAD_FOLDER'], previous_logo_path)
|
try:
|
||||||
if os.path.exists(previous_logo_file):
|
old_logo_file = fs.find_one({'filename': previous_logo_path})
|
||||||
try:
|
if old_logo_file:
|
||||||
os.remove(previous_logo_file)
|
fs.delete(old_logo_file._id)
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
if previous_thumb_path and previous_thumb_path != saved_thumb_filename:
|
if previous_thumb_path and previous_thumb_path != saved_thumb_filename:
|
||||||
previous_thumb_file = os.path.join(app.config['UPLOAD_FOLDER'], previous_thumb_path)
|
try:
|
||||||
if os.path.exists(previous_thumb_file):
|
old_thumb_file = fs.find_one({'filename': previous_thumb_path})
|
||||||
try:
|
if old_thumb_file:
|
||||||
os.remove(previous_thumb_file)
|
fs.delete(old_thumb_file._id)
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
flash(f'Logo konnte nicht hochgeladen werden: {exc}', 'error')
|
flash(f'Logo konnte nicht hochgeladen werden: {exc}', 'error')
|
||||||
return redirect(url_for('admin_school_settings'))
|
return redirect(url_for('admin_school_settings'))
|
||||||
@@ -10895,7 +10928,7 @@ def admin_school_settings():
|
|||||||
try:
|
try:
|
||||||
updated_school = cfg.update_school_info(school_info)
|
updated_school = cfg.update_school_info(school_info)
|
||||||
current_school = updated_school
|
current_school = updated_school
|
||||||
|
|
||||||
# Also process filter names
|
# Also process filter names
|
||||||
filter_name_1 = request.form.get('filter_name_1')
|
filter_name_1 = request.form.get('filter_name_1')
|
||||||
if filter_name_1: it.set_filter_name(1, filter_name_1.strip())
|
if filter_name_1: it.set_filter_name(1, filter_name_1.strip())
|
||||||
|
|||||||
Reference in New Issue
Block a user