154 lines
5.8 KiB
Python
154 lines
5.8 KiB
Python
# plugins/media/routes.py
|
|
|
|
import os
|
|
from uuid import uuid4
|
|
from datetime import datetime
|
|
from PIL import Image
|
|
|
|
from flask import (
|
|
Blueprint,
|
|
redirect,
|
|
url_for,
|
|
request,
|
|
flash,
|
|
send_from_directory,
|
|
current_app,
|
|
jsonify
|
|
)
|
|
from flask_login import login_required, current_user
|
|
|
|
from app import db
|
|
from .models import Media, ImageHeart, FeaturedImage
|
|
from plugins.plant.models import Plant
|
|
|
|
bp = Blueprint("media", __name__, url_prefix="/media", template_folder="templates")
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# Make generate_image_url available in all templates
|
|
# -----------------------------------------------------------------------------
|
|
@bp.app_context_processor
|
|
def utility_processor():
|
|
def generate_image_url(path):
|
|
if path:
|
|
return url_for("media.media_file", filename=path)
|
|
w, h = current_app.config.get("STANDARD_IMG_SIZE", (300, 200))
|
|
return f"https://placehold.co/{w}x{h}"
|
|
return dict(generate_image_url=generate_image_url)
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# Helpers & config
|
|
# -----------------------------------------------------------------------------
|
|
def allowed_file(filename):
|
|
ext = filename.rsplit(".", 1)[-1].lower() if "." in filename else ""
|
|
return ext in current_app.config.get("ALLOWED_EXTENSIONS", {"png","jpg","jpeg","gif"})
|
|
|
|
def get_upload_path():
|
|
base = current_app.config.get("UPLOAD_FOLDER", "static/uploads")
|
|
now = datetime.utcnow()
|
|
subdir = os.path.join(str(now.year), f"{now.month:02}", f"{now.day:02}")
|
|
full = os.path.join(base, subdir)
|
|
os.makedirs(full, exist_ok=True)
|
|
return full, subdir
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# Routes
|
|
# -----------------------------------------------------------------------------
|
|
@bp.route("/", methods=["GET"])
|
|
def media_index():
|
|
return redirect(url_for("core_ui.home"))
|
|
|
|
@bp.route("/files/<path:filename>", methods=["GET"])
|
|
def media_file(filename):
|
|
# Strip leading "uploads/" if present
|
|
if filename.startswith("uploads/"):
|
|
filename = filename[len("uploads/"):]
|
|
folder = current_app.config.get("UPLOAD_FOLDER", "static/uploads")
|
|
return send_from_directory(folder, filename)
|
|
|
|
@bp.route("/heart/<int:media_id>", methods=["POST"])
|
|
@login_required
|
|
def toggle_heart(media_id):
|
|
existing = ImageHeart.query.filter_by(user_id=current_user.id, media_id=media_id).first()
|
|
if existing:
|
|
db.session.delete(existing)
|
|
db.session.commit()
|
|
return jsonify({"status": "unhearted"})
|
|
heart = ImageHeart(user_id=current_user.id, media_id=media_id)
|
|
db.session.add(heart)
|
|
db.session.commit()
|
|
return jsonify({"status": "hearted"})
|
|
|
|
@bp.route("/add/<string:plant_uuid>", methods=["POST"])
|
|
@login_required
|
|
def add_media(plant_uuid):
|
|
plant = Plant.query.filter_by(uuid=plant_uuid).first_or_404()
|
|
file = request.files.get("file")
|
|
if not file or not allowed_file(file.filename):
|
|
flash("Invalid or missing file.", "danger")
|
|
return redirect(request.referrer or url_for("plant.edit", uuid_val=plant_uuid))
|
|
|
|
ext = file.filename.rsplit(".", 1)[-1].lower()
|
|
filename = f"{uuid4()}.{ext}"
|
|
full_path, subdir = get_upload_path()
|
|
file.save(os.path.join(full_path, filename))
|
|
|
|
media = Media(
|
|
file_url=os.path.join(subdir, filename).replace("\\", "/"),
|
|
uploader_id=current_user.id,
|
|
plant_id=plant.id
|
|
)
|
|
db.session.add(media)
|
|
db.session.commit()
|
|
flash("Media uploaded successfully.", "success")
|
|
return redirect(request.referrer or url_for("plant.edit", uuid_val=plant_uuid))
|
|
|
|
@bp.route("/feature/<int:media_id>", methods=["POST"])
|
|
@login_required
|
|
def set_featured_image(media_id):
|
|
media = Media.query.get_or_404(media_id)
|
|
if current_user.id != media.uploader_id and current_user.role != "admin":
|
|
flash("Not authorized to set featured image.", "danger")
|
|
return redirect(request.referrer or url_for("core_ui.home"))
|
|
|
|
FeaturedImage.query.filter_by(media_id=media_id).delete()
|
|
featured = FeaturedImage(media_id=media_id, is_featured=True)
|
|
db.session.add(featured)
|
|
db.session.commit()
|
|
flash("Image set as featured.", "success")
|
|
return redirect(request.referrer or url_for("plant.edit", uuid_val=media.plant.uuid))
|
|
|
|
@bp.route("/delete/<int:media_id>", methods=["POST"])
|
|
@login_required
|
|
def delete_media(media_id):
|
|
media = Media.query.get_or_404(media_id)
|
|
if current_user.id != media.uploader_id and current_user.role != "admin":
|
|
flash("Not authorized to delete this media.", "danger")
|
|
return redirect(request.referrer or url_for("core_ui.home"))
|
|
|
|
full_path = os.path.join(current_app.config.get("UPLOAD_FOLDER", "static/uploads"), media.file_url)
|
|
if os.path.exists(full_path):
|
|
os.remove(full_path)
|
|
|
|
db.session.delete(media)
|
|
db.session.commit()
|
|
flash("Media deleted.", "success")
|
|
return redirect(request.referrer or url_for("plant.edit", uuid_val=media.plant.uuid))
|
|
|
|
@bp.route("/rotate/<int:media_id>", methods=["POST"])
|
|
@login_required
|
|
def rotate_media(media_id):
|
|
media = Media.query.get_or_404(media_id)
|
|
if current_user.id != media.uploader_id and current_user.role != "admin":
|
|
flash("Not authorized to rotate this media.", "danger")
|
|
return redirect(request.referrer or url_for("core_ui.home"))
|
|
|
|
full_path = os.path.join(current_app.config.get("UPLOAD_FOLDER", "static/uploads"), media.file_url)
|
|
try:
|
|
with Image.open(full_path) as img:
|
|
img.rotate(-90, expand=True).save(full_path)
|
|
flash("Image rotated successfully.", "success")
|
|
except Exception as e:
|
|
flash(f"Failed to rotate image: {e}", "danger")
|
|
|
|
return redirect(request.referrer or url_for("plant.edit", uuid_val=media.plant.uuid))
|