Add edited media report page and fix image versioning

- New route GET /players/<id>/edited-media-report with tabular report
- New template edited_media_report.html with User/Filename/Date/Link columns
- Launch Report button (green) next to View All Edited Media button
- Fix: on first edit, move original file to versionized folder as original_<name>
- Fix: content lookup fallback via PlayerEdit records and path regex
- Keep Content.filename pointing to latest edit for player playlist
This commit is contained in:
2026-07-21 09:56:14 +03:00
parent 2af04e1db3
commit 05194a19b6
7 changed files with 718 additions and 54 deletions
+61 -31
View File
@@ -734,75 +734,105 @@ def receive_edited_media():
"""
try:
player = request.player
# Check if file is present
if 'image_file' not in request.files:
return jsonify({'error': 'No image file provided'}), 400
file = request.files['image_file']
if file.filename == '':
return jsonify({'error': 'No file selected'}), 400
# Get metadata
import json
metadata_str = request.form.get('metadata')
if not metadata_str:
return jsonify({'error': 'No metadata provided'}), 400
try:
metadata = json.loads(metadata_str)
except json.JSONDecodeError:
return jsonify({'error': 'Invalid metadata JSON'}), 400
# Validate required metadata fields
required_fields = ['time_of_modification', 'original_name', 'new_name', 'version']
for field in required_fields:
if field not in metadata:
return jsonify({'error': f'Missing required field: {field}'}), 400
# Import required modules
import os
import shutil
from datetime import datetime
from werkzeug.utils import secure_filename
from app.models.player_edit import PlayerEdit
# Find the original content by filename
original_name = metadata['original_name']
version = metadata['version']
new_filename = metadata['new_name']
# ── Content lookup ───────────────────────────────────────────────
# If the player sends the current filename (pointing to edited_media/...),
# we find the content by its ID embedded in the path. Otherwise do a
# direct filename match.
content = Content.query.filter_by(filename=original_name).first()
if not content:
# Try to extract content_id from a path like "edited_media/<id>/file"
import re
m = re.match(r'edited_media/(\d+)/', original_name)
if m:
content = db.session.get(Content, int(m.group(1)))
if not content:
# Last resort look up the most recent PlayerEdit for this
# content and use its content_id.
fallback_edit = PlayerEdit.query.filter_by(new_name=original_name)\
.order_by(PlayerEdit.created_at.desc()).first()
if fallback_edit:
content = db.session.get(Content, fallback_edit.content_id)
if not content:
log_action('warning', f'Player {player.name} tried to edit non-existent content: {original_name}')
return jsonify({'error': f'Original content not found: {original_name}'}), 404
# Create versioned folder structure: edited_media/<content_id>/
# ── Versionized folder ───────────────────────────────────────────
base_upload_dir = os.path.join(current_app.root_path, 'static', 'uploads')
edited_media_dir = os.path.join(base_upload_dir, 'edited_media', str(content.id))
os.makedirs(edited_media_dir, exist_ok=True)
# Save the edited file with version suffix
version = metadata['version']
new_filename = metadata['new_name']
# On the very first edit (v1) move the original file into the
# versionized folder so it is never orphaned.
is_first_edit = PlayerEdit.query.filter_by(content_id=content.id).count() == 0
if is_first_edit:
orig_upload = os.path.join(base_upload_dir, content.filename)
# content.filename might already be an edited_media/ path if
# this is a re-process; only move if it's a plain filename.
if os.path.isfile(orig_upload) and not content.filename.startswith('edited_media/'):
orig_stored = f"original_{content.filename}"
shutil.move(orig_upload, os.path.join(edited_media_dir, orig_stored))
log_action('info', f'Moved original file "{content.filename}" to versionized folder as "{orig_stored}"')
# ── Save the edited file ─────────────────────────────────────────
edited_file_path = os.path.join(edited_media_dir, new_filename)
file.save(edited_file_path)
# Save metadata JSON file
# Side-car metadata JSON
metadata_filename = f"{os.path.splitext(new_filename)[0]}_metadata.json"
metadata_path = os.path.join(edited_media_dir, metadata_filename)
with open(metadata_path, 'w') as f:
json.dump(metadata, f, indent=2)
# Update the content record to reference the edited version path
# Keep original filename unchanged, point to edited_media folder
# ── Point Content.filename to the latest edit ────────────────────
# This tells the player to download the latest edited version.
old_filename = content.filename
content.filename = f"edited_media/{content.id}/{new_filename}"
# Create edit record
# ── Create edit record ───────────────────────────────────────────
time_of_mod = None
if metadata.get('time_of_modification'):
try:
time_of_mod = datetime.fromisoformat(metadata['time_of_modification'].replace('Z', '+00:00'))
except:
time_of_mod = datetime.utcnow()
# Auto-create PlayerUser record if user code is provided
user_code = metadata.get('user_card_data')
log_action('debug', f'Metadata user code: {user_code}')
@@ -817,7 +847,7 @@ def receive_edited_media():
log_action('debug', f'PlayerUser already exists for code: {user_code}')
else:
log_action('debug', 'No user code in metadata')
edit_record = PlayerEdit(
player_id=player.id,
content_id=content.id,
@@ -830,22 +860,22 @@ def receive_edited_media():
edited_file_path=edited_file_path
)
db.session.add(edit_record)
# Update playlist version to force player refresh
# ── Update playlist version to force player refresh ──────────────
playlist = None
if player.playlist_id:
from app.models.playlist import Playlist
playlist = db.session.get(Playlist, player.playlist_id)
if playlist:
playlist.version += 1
# Clear playlist cache
cache.delete_memoized(get_cached_playlist, player.id)
db.session.commit()
log_action('info', f'Player {player.name} uploaded edited media: {old_filename} -> {new_filename} (v{version})')
return jsonify({
'success': True,
'message': 'Edited media received and processed',