46602f1933
Remove dead code identified in docs/SANITIZATION-REVIEW.md:
- app/blueprints/content_old.py and app/blueprints/playlist.py
- app/models/group.py, app/utils/nginx_config_reader.py
- orphaned templates (content_list, edit_content, upload_content,
player_page) and the related group/Template references
Result: 6 blueprints, 82 routes, no dead modules or orphan templates.
Add files that deploy.sh and docker-entrypoint.sh already require but
which were never tracked:
- https_manager.py (referenced by deploy.sh, migrate_network.sh,
docker-entrypoint.sh)
- Caddyfile.example (seeded by deploy.sh; its absence aborts deploy)
Relocate generated Graphify artifacts from graphify-out/ to
docs/graphify-out/ (110 files, no content change) and archive the
superseded docs under docs/.
Ignore hygiene:
- ignore ad-hoc .env backups (.env.bak*) — they contain live secrets
- keep the pre-sanitization snapshots (docs/legacy code/,
docs/old_code_documentation/) on disk but out of the repo
Fix .env.example: drop a duplicated config block, genericize the
hardcoded host IP, and document HOSTNAME_INTERNAL.
112 lines
4.3 KiB
Python
112 lines
4.3 KiB
Python
"""Content model for media files."""
|
|
from datetime import datetime
|
|
from typing import Optional, List
|
|
|
|
from app.extensions import db
|
|
|
|
|
|
class Content(db.Model):
|
|
"""Content model representing media files for display.
|
|
|
|
Attributes:
|
|
id: Primary key
|
|
filename: Original filename
|
|
content_type: Type of content (image, video, pdf, presentation, other)
|
|
duration: Default display duration in seconds
|
|
file_size: File size in bytes
|
|
description: Optional content description
|
|
uploaded_at: Upload timestamp
|
|
"""
|
|
__tablename__ = 'content'
|
|
|
|
id = db.Column(db.Integer, primary_key=True)
|
|
filename = db.Column(db.String(255), nullable=False, unique=True, index=True)
|
|
# The pristine original filename as uploaded. `filename` gets overwritten
|
|
# with an edited_media/... path when a player edits the content, so this
|
|
# column preserves the original so it can always be referenced/restored.
|
|
original_filename = db.Column(db.String(255), nullable=True, index=True)
|
|
content_type = db.Column(db.String(50), nullable=False, index=True)
|
|
duration = db.Column(db.Integer, default=10, nullable=True)
|
|
file_size = db.Column(db.BigInteger, nullable=True)
|
|
# For 'weblink' content this holds the web page URL to display on the player.
|
|
# NULL for file-based content (image/video/pdf).
|
|
url = db.Column(db.String(2048), nullable=True)
|
|
description = db.Column(db.Text, nullable=True)
|
|
uploaded_at = db.Column(db.DateTime, default=datetime.utcnow,
|
|
nullable=False, index=True)
|
|
|
|
# Relationships - many-to-many with playlists
|
|
playlists = db.relationship('Playlist', secondary='playlist_content',
|
|
back_populates='contents', lazy='dynamic')
|
|
|
|
def __repr__(self) -> str:
|
|
"""String representation of Content."""
|
|
return f'<Content {self.filename} (Type={self.content_type})>'
|
|
|
|
@property
|
|
def file_size_mb(self) -> float:
|
|
"""Get file size in megabytes."""
|
|
if self.file_size:
|
|
return round(self.file_size / (1024 * 1024), 2)
|
|
return 0.0
|
|
|
|
@property
|
|
def original_display_name(self) -> str:
|
|
"""Name of the original (unedited) file for display purposes."""
|
|
return self.original_filename or self.filename
|
|
|
|
@property
|
|
def original_media_path(self) -> str:
|
|
"""Path (relative to uploads/) where the original unedited file lives.
|
|
|
|
When a player edits content the original file is archived under
|
|
edited_media/<id>/original_<name>. If never edited, the original IS the
|
|
current file.
|
|
"""
|
|
if self.original_filename and self.original_filename != self.filename:
|
|
return f"edited_media/{self.id}/original_{self.original_filename}"
|
|
return self.filename
|
|
|
|
@property
|
|
def current_media_path(self) -> str:
|
|
"""Path (relative to uploads/) of the current/latest version to display."""
|
|
return self.filename
|
|
|
|
def is_image(self) -> bool:
|
|
"""Check if content is an image."""
|
|
return self.content_type == 'image'
|
|
|
|
def is_video(self) -> bool:
|
|
"""Check if content is a video."""
|
|
return self.content_type == 'video'
|
|
|
|
def is_pdf(self) -> bool:
|
|
"""Check if content is a PDF."""
|
|
return self.content_type == 'pdf'
|
|
|
|
def is_weblink(self) -> bool:
|
|
"""Check if content is a web link (URL) rather than an uploaded file."""
|
|
return self.content_type == 'weblink'
|
|
|
|
@property
|
|
def has_player_edits(self) -> bool:
|
|
"""Whether this content has been edited on a player."""
|
|
return self.edits.count() > 0
|
|
|
|
@property
|
|
def original_display_name(self) -> str:
|
|
"""Display name of the original (unedited) file."""
|
|
return self.original_filename or self.filename
|
|
|
|
@property
|
|
def original_media_path(self) -> str:
|
|
"""uploads/-relative path of the pristine original file.
|
|
|
|
After a player edit the original is archived at
|
|
edited_media/<id>/original_<name>; before any edit it is simply the
|
|
current file.
|
|
"""
|
|
if self.original_filename and self.filename != self.original_filename:
|
|
return f"edited_media/{self.id}/original_{self.original_filename}"
|
|
return self.filename
|