Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 0cf0bd8dec | |||
| af9826547c |
+72
-39
@@ -26,6 +26,7 @@ from jinja2 import TemplateNotFound
|
||||
import os
|
||||
import sys
|
||||
from bs4 import BeautifulSoup
|
||||
from gridfs import GridFS
|
||||
|
||||
# Ensure imports work regardless of whether gunicorn starts in /app or /app/Web.
|
||||
_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__
|
||||
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---------------------------------------------------------------------------- """
|
||||
|
||||
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):
|
||||
"""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', ''):
|
||||
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.')
|
||||
|
||||
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)
|
||||
upload_file.save(logo_path)
|
||||
fs = get_gridfs()
|
||||
|
||||
# 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:
|
||||
thumb_filename = f'school-logo-{safe_tenant}-thumb.png'
|
||||
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
|
||||
with Image.open(io.BytesIO(file_bytes)) as img:
|
||||
if img.mode not in ('RGBA', 'RGB'):
|
||||
img = img.convert('RGBA')
|
||||
|
||||
# Target max size: width up to 520px, height up to 114px (matches CSS constraints)
|
||||
max_thumb_size = (520, 114)
|
||||
img.thumbnail(max_thumb_size, Image.LANCZOS)
|
||||
|
||||
# Save as PNG for predictable rendering in web UI
|
||||
img.save(thumb_path, format='PNG', optimize=True)
|
||||
thumb_io = io.BytesIO()
|
||||
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:
|
||||
app.logger.warning(f"Thumbnail generation failed for {logo_path}: {e}")
|
||||
app.logger.warning(f"Thumbnail generation failed for logo: {e}")
|
||||
thumb_filename = None
|
||||
|
||||
return (logo_filename, thumb_filename)
|
||||
|
||||
"""---------------------------------------------Invoice Generation----------------------------------------------------------------------------- """
|
||||
|
||||
def _parse_money_value(value):
|
||||
@@ -2534,14 +2552,15 @@ def upload_student_cards_excel():
|
||||
|
||||
"""-------------------------------------------------------------File Serving-----------------------------------------------------------------------------"""
|
||||
|
||||
|
||||
@app.route('/uploads/<filename>')
|
||||
def uploaded_file(filename):
|
||||
"""
|
||||
Serve uploaded files from the uploads directory.
|
||||
|
||||
Serve uploaded files from MongoDB GridFS with local path fallbacks.
|
||||
|
||||
Args:
|
||||
filename (str): Name of the file to serve
|
||||
|
||||
|
||||
Returns:
|
||||
flask.Response: The requested file or placeholder image if not found
|
||||
"""
|
||||
@@ -2550,24 +2569,37 @@ def uploaded_file(filename):
|
||||
if 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"
|
||||
dev_path = app.config['UPLOAD_FOLDER']
|
||||
if os.path.exists(os.path.join(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)):
|
||||
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')
|
||||
png_placeholder_path = os.path.join(app.static_folder, 'img', 'no-image.png')
|
||||
|
||||
|
||||
if os.path.exists(svg_placeholder_path):
|
||||
return send_from_directory(app.static_folder, 'img/no-image.svg')
|
||||
elif os.path.exists(png_placeholder_path):
|
||||
return send_from_directory(app.static_folder, 'img/no-image.png')
|
||||
|
||||
|
||||
# Default placeholder from static folder
|
||||
return send_from_directory(app.static_folder, 'favicon.ico')
|
||||
except Exception as e:
|
||||
@@ -10811,7 +10843,7 @@ def admin_school_settings():
|
||||
flash('Bitte melden Sie sich mit einem administrativen Konto an.', 'error')
|
||||
return redirect(url_for('login'))
|
||||
current_permissions = us.get_effective_permissions(session['username'])
|
||||
|
||||
|
||||
if not current_permissions['actions'].get('can_manage_settings', False):
|
||||
flash('Ihnen fehlen die nötigen Berechtigungen, um diese Aktion auszuführen.', 'error')
|
||||
return redirect(url_for('home_admin'))
|
||||
@@ -10855,21 +10887,22 @@ def admin_school_settings():
|
||||
school_info['logo_path'] = saved_logo_filename
|
||||
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:
|
||||
previous_logo_file = os.path.join(app.config['UPLOAD_FOLDER'], previous_logo_path)
|
||||
if os.path.exists(previous_logo_file):
|
||||
try:
|
||||
os.remove(previous_logo_file)
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
old_logo_file = fs.find_one({'filename': previous_logo_path})
|
||||
if old_logo_file:
|
||||
fs.delete(old_logo_file._id)
|
||||
except Exception:
|
||||
pass
|
||||
if previous_thumb_path and previous_thumb_path != saved_thumb_filename:
|
||||
previous_thumb_file = os.path.join(app.config['UPLOAD_FOLDER'], previous_thumb_path)
|
||||
if os.path.exists(previous_thumb_file):
|
||||
try:
|
||||
os.remove(previous_thumb_file)
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
old_thumb_file = fs.find_one({'filename': previous_thumb_path})
|
||||
if old_thumb_file:
|
||||
fs.delete(old_thumb_file._id)
|
||||
except Exception:
|
||||
pass
|
||||
except Exception as exc:
|
||||
flash(f'Logo konnte nicht hochgeladen werden: {exc}', 'error')
|
||||
return redirect(url_for('admin_school_settings'))
|
||||
@@ -10895,7 +10928,7 @@ def admin_school_settings():
|
||||
try:
|
||||
updated_school = cfg.update_school_info(school_info)
|
||||
current_school = updated_school
|
||||
|
||||
|
||||
# Also process filter names
|
||||
filter_name_1 = request.form.get('filter_name_1')
|
||||
if filter_name_1: it.set_filter_name(1, filter_name_1.strip())
|
||||
|
||||
@@ -319,15 +319,15 @@ THUMBNAIL_FOLDER = _get(_conf, ['upload', 'thumbnail_folder'], DEFAULTS['upload'
|
||||
PREVIEW_FOLDER = _get(_conf, ['upload', 'preview_folder'], DEFAULTS['upload']['preview_folder'])
|
||||
QR_CODE_FOLDER = _get(_conf, ['upload', 'qrcode_folder'], DEFAULTS['upload']['qrcode_folder'])
|
||||
|
||||
# Normalize to absolute paths to avoid cwd issues
|
||||
# This guarantees exact matching with Docker volume mounts
|
||||
if not os.path.isabs(UPLOAD_FOLDER):
|
||||
UPLOAD_FOLDER = os.path.join(BASE_DIR, os.path.relpath(UPLOAD_FOLDER, BASE_DIR))
|
||||
UPLOAD_FOLDER = os.path.abspath(os.path.join(BASE_DIR, "uploads"))
|
||||
if not os.path.isabs(THUMBNAIL_FOLDER):
|
||||
THUMBNAIL_FOLDER = os.path.join(BASE_DIR, os.path.relpath(THUMBNAIL_FOLDER, BASE_DIR))
|
||||
THUMBNAIL_FOLDER = os.path.abspath(os.path.join(BASE_DIR, "thumbnails"))
|
||||
if not os.path.isabs(PREVIEW_FOLDER):
|
||||
PREVIEW_FOLDER = os.path.join(BASE_DIR, os.path.relpath(PREVIEW_FOLDER, BASE_DIR))
|
||||
PREVIEW_FOLDER = os.path.abspath(os.path.join(BASE_DIR, "previews"))
|
||||
if not os.path.isabs(QR_CODE_FOLDER):
|
||||
QR_CODE_FOLDER = os.path.join(BASE_DIR, os.path.relpath(QR_CODE_FOLDER, BASE_DIR))
|
||||
QR_CODE_FOLDER = os.path.abspath(os.path.join(BASE_DIR, "QRCodes"))
|
||||
MAX_UPLOAD_MB = _get(_conf, ['upload', 'max_size_mb'], DEFAULTS['upload']['max_size_mb'])
|
||||
IMAGE_MAX_UPLOAD_MB = _get(_conf, ['upload', 'image_max_size_mb'], DEFAULTS['upload']['image_max_size_mb'])
|
||||
VIDEO_MAX_UPLOAD_MB = _get(_conf, ['upload', 'video_max_size_mb'], DEFAULTS['upload']['video_max_size_mb'])
|
||||
|
||||
Reference in New Issue
Block a user