- Add media_folder field to Post model for organized file storage
- Create MediaConfig class for centralized media management settings
- Update community routes to use post-specific media folders
- Add thumbnail generation for uploaded images
- Implement structured folder layout: app/static/media/posts/{post_folder}/
- Add utility functions for image and GPX file handling
- Create media management script for migration and maintenance
- Add proper file validation and MIME type checking
- Include routes for serving images, thumbnails, and GPX files
- Maintain backward compatibility with existing uploads
- Add comprehensive documentation and migration tools
Each post now gets its own media folder with subfolders for:
- images/ (with thumbnails/ subfolder)
- gpx/
Post content remains in database for optimal query performance while
media files are organized in dedicated folders for better management.
188 lines
7.4 KiB
Python
Executable File
188 lines
7.4 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""
|
|
Media Management Utility for Motorcycle Adventure Community
|
|
|
|
This script provides utilities for managing post media files:
|
|
- Create missing media folders for existing posts
|
|
- Clean up orphaned media folders
|
|
- Migrate files from old structure to new structure
|
|
- Generate thumbnails for images
|
|
|
|
Usage:
|
|
python manage_media.py --help
|
|
"""
|
|
|
|
import os
|
|
import sys
|
|
import argparse
|
|
import shutil
|
|
from datetime import datetime
|
|
import uuid
|
|
|
|
# Add the app directory to the path
|
|
sys.path.insert(0, os.path.join(os.path.dirname(__file__), 'app'))
|
|
|
|
from app import create_app
|
|
from app.models import Post, PostImage, GPXFile
|
|
from app.extensions import db
|
|
|
|
def create_missing_media_folders():
|
|
"""Create media folders for existing posts that don't have them"""
|
|
app = create_app()
|
|
|
|
with app.app_context():
|
|
posts_without_folders = Post.query.filter_by(media_folder=None).all()
|
|
|
|
print(f"Found {len(posts_without_folders)} posts without media folders")
|
|
|
|
for post in posts_without_folders:
|
|
# Generate a media folder name
|
|
folder_name = f"post_{uuid.uuid4().hex[:8]}_{post.created_at.strftime('%Y%m%d')}"
|
|
post.media_folder = folder_name
|
|
|
|
# Create the folder structure
|
|
media_path = os.path.join(app.root_path, 'static', 'media', 'posts', folder_name)
|
|
os.makedirs(os.path.join(media_path, 'images'), exist_ok=True)
|
|
os.makedirs(os.path.join(media_path, 'gpx'), exist_ok=True)
|
|
|
|
print(f"Created media folder for post {post.id}: {folder_name}")
|
|
|
|
db.session.commit()
|
|
print("Media folders created successfully!")
|
|
|
|
def migrate_old_files():
|
|
"""Migrate files from old upload structure to new media structure"""
|
|
app = create_app()
|
|
|
|
with app.app_context():
|
|
# Migrate images
|
|
old_images_path = os.path.join(app.instance_path, 'uploads', 'images')
|
|
if os.path.exists(old_images_path):
|
|
print("Migrating image files...")
|
|
|
|
for image in PostImage.query.all():
|
|
if image.post.media_folder:
|
|
old_path = os.path.join(old_images_path, image.filename)
|
|
new_path = os.path.join(app.root_path, 'static', 'media', 'posts',
|
|
image.post.media_folder, 'images', image.filename)
|
|
|
|
if os.path.exists(old_path) and not os.path.exists(new_path):
|
|
os.makedirs(os.path.dirname(new_path), exist_ok=True)
|
|
shutil.move(old_path, new_path)
|
|
print(f"Moved image: {image.filename}")
|
|
|
|
# Migrate GPX files
|
|
old_gpx_path = os.path.join(app.instance_path, 'uploads', 'gpx')
|
|
if os.path.exists(old_gpx_path):
|
|
print("Migrating GPX files...")
|
|
|
|
for gpx_file in GPXFile.query.all():
|
|
if gpx_file.post.media_folder:
|
|
old_path = os.path.join(old_gpx_path, gpx_file.filename)
|
|
new_path = os.path.join(app.root_path, 'static', 'media', 'posts',
|
|
gpx_file.post.media_folder, 'gpx', gpx_file.filename)
|
|
|
|
if os.path.exists(old_path) and not os.path.exists(new_path):
|
|
os.makedirs(os.path.dirname(new_path), exist_ok=True)
|
|
shutil.move(old_path, new_path)
|
|
print(f"Moved GPX file: {gpx_file.filename}")
|
|
|
|
print("File migration completed!")
|
|
|
|
def clean_orphaned_folders():
|
|
"""Remove media folders that don't have corresponding posts"""
|
|
app = create_app()
|
|
|
|
with app.app_context():
|
|
media_posts_path = os.path.join(app.root_path, 'static', 'media', 'posts')
|
|
|
|
if not os.path.exists(media_posts_path):
|
|
print("No media posts directory found")
|
|
return
|
|
|
|
# Get all folder names from database
|
|
used_folders = set(post.media_folder for post in Post.query.filter(Post.media_folder.isnot(None)).all())
|
|
|
|
# Get all actual folders
|
|
actual_folders = set(name for name in os.listdir(media_posts_path)
|
|
if os.path.isdir(os.path.join(media_posts_path, name)))
|
|
|
|
# Find orphaned folders
|
|
orphaned_folders = actual_folders - used_folders
|
|
|
|
if orphaned_folders:
|
|
print(f"Found {len(orphaned_folders)} orphaned folders:")
|
|
for folder in orphaned_folders:
|
|
folder_path = os.path.join(media_posts_path, folder)
|
|
print(f" {folder}")
|
|
|
|
# Ask for confirmation before deleting
|
|
response = input(f"Delete folder {folder}? (y/N): ")
|
|
if response.lower() == 'y':
|
|
shutil.rmtree(folder_path)
|
|
print(f" Deleted: {folder}")
|
|
else:
|
|
print(f" Skipped: {folder}")
|
|
else:
|
|
print("No orphaned folders found")
|
|
|
|
def show_media_stats():
|
|
"""Show statistics about media storage"""
|
|
app = create_app()
|
|
|
|
with app.app_context():
|
|
total_posts = Post.query.count()
|
|
posts_with_media_folders = Post.query.filter(Post.media_folder.isnot(None)).count()
|
|
total_images = PostImage.query.count()
|
|
total_gpx_files = GPXFile.query.count()
|
|
|
|
print("Media Storage Statistics:")
|
|
print(f" Total posts: {total_posts}")
|
|
print(f" Posts with media folders: {posts_with_media_folders}")
|
|
print(f" Posts without media folders: {total_posts - posts_with_media_folders}")
|
|
print(f" Total images: {total_images}")
|
|
print(f" Total GPX files: {total_gpx_files}")
|
|
|
|
# Calculate total storage used
|
|
media_posts_path = os.path.join(app.root_path, 'static', 'media', 'posts')
|
|
if os.path.exists(media_posts_path):
|
|
total_size = 0
|
|
for root, dirs, files in os.walk(media_posts_path):
|
|
for file in files:
|
|
file_path = os.path.join(root, file)
|
|
total_size += os.path.getsize(file_path)
|
|
|
|
print(f" Total storage used: {total_size / (1024*1024):.2f} MB")
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(description='Media Management Utility')
|
|
parser.add_argument('--create-folders', action='store_true',
|
|
help='Create missing media folders for existing posts')
|
|
parser.add_argument('--migrate-files', action='store_true',
|
|
help='Migrate files from old structure to new structure')
|
|
parser.add_argument('--clean-orphaned', action='store_true',
|
|
help='Clean up orphaned media folders')
|
|
parser.add_argument('--stats', action='store_true',
|
|
help='Show media storage statistics')
|
|
parser.add_argument('--all', action='store_true',
|
|
help='Run all operations (create folders, migrate files)')
|
|
|
|
args = parser.parse_args()
|
|
|
|
if args.all:
|
|
create_missing_media_folders()
|
|
migrate_old_files()
|
|
elif args.create_folders:
|
|
create_missing_media_folders()
|
|
elif args.migrate_files:
|
|
migrate_old_files()
|
|
elif args.clean_orphaned:
|
|
clean_orphaned_folders()
|
|
elif args.stats:
|
|
show_media_stats()
|
|
else:
|
|
parser.print_help()
|
|
|
|
if __name__ == '__main__':
|
|
main()
|