feat(digiserver-v2): port edited media report and deploy badge features from standalone

- Added edited_media_report route to players blueprint with tabular report view
- Created edited_media_report.html template with summary stats, dark mode, print/PDF support
- Created _deploy_badge.html partial template for deployment status display
- Added deployment status column and badge CSS to players_list.html
- Added 'View Report' button to manage_player.html next to 'View All Edited Media'
- Fixed api.py receive_edited_media with robust content lookup:
  - Fallback content lookup by regex path matching (edited_media/<id>/)
  - Fallback via PlayerEdit record if direct lookup fails
  - First-edit original file preservation (moves original to versionized folder)

Enterprise-specific features (portal SSO, internal sync, playlist permissions, WAL mode) are preserved unchanged.
This commit is contained in:
ske087
2026-07-21 10:42:31 +03:00
parent 83a8f79c36
commit cdaa17803a
6 changed files with 509 additions and 2 deletions
+28 -2
View File
@@ -765,6 +765,8 @@ def receive_edited_media():
# Import required modules
import os
import shutil
import re
from werkzeug.utils import secure_filename
from app.models.player_edit import PlayerEdit
@@ -772,6 +774,19 @@ def receive_edited_media():
original_name = metadata['original_name']
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"
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
@@ -781,20 +796,31 @@ def receive_edited_media():
edited_media_dir = os.path.join(base_upload_dir, 'edited_media', str(content.id))
os.makedirs(edited_media_dir, exist_ok=True)
# 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 with version suffix
version = metadata['version']
new_filename = metadata['new_name']
edited_file_path = os.path.join(edited_media_dir, new_filename)
file.save(edited_file_path)
# Save metadata JSON file
# Save metadata JSON side-car file
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
old_filename = content.filename
content.filename = f"edited_media/{content.id}/{new_filename}"