images working, featured still broken

This commit is contained in:
2025-06-24 20:10:35 -05:00
parent 289f2f0ca1
commit 7a8ec5face
3 changed files with 111 additions and 80 deletions

BIN
nip.zip

Binary file not shown.

View File

@ -232,15 +232,6 @@ def serve(plugin, filename):
return send_from_directory(disk_dir, filename) return send_from_directory(disk_dir, filename)
@bp.route("/files/<path:filename>")
def media_file(filename):
base = current_app.config["UPLOAD_FOLDER"]
full = os.path.normpath(os.path.join(base, filename))
if not full.startswith(os.path.abspath(base)):
abort(404)
return send_from_directory(base, filename)
@bp.route("/<filename>") @bp.route("/<filename>")
def media_public(filename): def media_public(filename):
base = current_app.config["UPLOAD_FOLDER"] base = current_app.config["UPLOAD_FOLDER"]
@ -286,32 +277,64 @@ def add_media(plant_uuid):
return redirect(request.referrer or url_for("plant.edit", uuid_val=plant_uuid)) return redirect(request.referrer or url_for("plant.edit", uuid_val=plant_uuid))
@bp.route("/feature/<string:context>/<int:context_id>/<int:media_id>", methods=["POST"]) @bp.route("/<context>/<int:context_id>/<filename>")
@login_required def media_file(context, context_id, filename):
def set_featured_image(context, context_id, media_id): # your existing serve_context_media logic here
media = Media.query.get_or_404(media_id) # (unchanged)
if media.uploader_id != current_user.id and current_user.role != "admin": from flask import current_app, send_from_directory
return jsonify({"error": "Not authorized"}), 403 import os
valid = {"user", "plant", "growlog", "vendor"}
if context in valid:
plugin = context
elif context.endswith("s") and context[:-1] in valid:
plugin = context[:-1]
else:
abort(404)
media = Media.query.filter_by(
plugin=plugin,
related_id=context_id,
filename=filename
).first_or_404()
base = current_app.config["UPLOAD_FOLDER"]
directory = os.path.join(base, plugin, str(context_id))
return send_from_directory(directory, filename)
@bp.route('/featured/<context>/<int:context_id>/<int:media_id>', methods=['POST'])
def set_featured_image(context, context_id, media_id):
"""
Singleselect “featured” toggle for any plugin (plants, grow_logs, etc).
"""
# normalize to plural
plugin_ctx = context if context.endswith('s') else context + 's'
if plugin_ctx not in ('plants', 'grow_logs', 'users', 'vendors'):
abort(404)
# must own that media row
media = Media.query.filter_by(
plugin=plugin_ctx,
related_id=context_id,
id=media_id
).first_or_404()
# clear out any existing
FeaturedImage.query.filter_by( FeaturedImage.query.filter_by(
context=context, context=plugin_ctx,
context_id=context_id context_id=context_id
).delete() ).delete()
feat = FeaturedImage( # insert new featured row
fi = FeaturedImage(
media_id=media.id, media_id=media.id,
context=context, context=plugin_ctx,
context_id=context_id, context_id=context_id,
is_featured=True is_featured=True
) )
db.session.add(feat) db.session.add(fi)
if context == "plant":
plant = Plant.query.get_or_404(context_id)
plant.featured_media_id = media.id
db.session.commit() db.session.commit()
return jsonify({"status": "success", "media_id": media.id})
return jsonify({"media_id": media.id})
@bp.route("/delete/<int:media_id>", methods=["POST"]) @bp.route("/delete/<int:media_id>", methods=["POST"])

View File

@ -5,6 +5,7 @@
<div class="container mt-4"> <div class="container mt-4">
<h2>Edit Plant</h2> <h2>Edit Plant</h2>
{# ——— Plant Edit Form ——— #}
<form method="POST" enctype="multipart/form-data"> <form method="POST" enctype="multipart/form-data">
{{ form.hidden_tag() }} {{ form.hidden_tag() }}
@ -54,6 +55,7 @@
<hr> <hr>
{# ——— Upload New Image ——— #}
<h4>Upload Image</h4> <h4>Upload Image</h4>
<form <form
method="POST" method="POST"
@ -68,40 +70,46 @@
</div> </div>
</form> </form>
{# ——— Existing Images & Featured Toggle ——— #}
<h4>Existing Images</h4> <h4>Existing Images</h4>
<form method="POST" id="delete-images-form" <form method="POST"
id="delete-images-form"
action="{{ url_for('media.bulk_delete_media', plant_uuid=plant.uuid) }}" action="{{ url_for('media.bulk_delete_media', plant_uuid=plant.uuid) }}"
onsubmit="return confirm('Are you sure you want to delete selected images?');"> onsubmit="return confirm('Are you sure you want to delete selected images?');">
{{ form.csrf_token }} {{ form.csrf_token }}
<div class="row"> <div class="row">
{% for media in plant.media %} {% for media in plant.media_items %}
<div class="col-md-3 mb-4"> <div class="col-md-3 mb-4">
<div class="card h-100"> <div class="card h-100">
<img <img
id="image-{{ media.id }}" id="image-{{ media.id }}"
src="{{ generate_image_url(media) }}" src="{{ url_for('media.media_file',
context='plants',
context_id=plant.id,
filename=media.filename) }}"
class="card-img-top img-fluid" class="card-img-top img-fluid"
alt="Plant Image" alt="Plant Image"
style="object-fit:cover; height:150px;" style="object-fit:cover; height:150px;"
> >
<div class="card-body text-center"> <div class="card-body text-center">
{# Featured radio driven off media.featured #}
<div class="form-check mb-2"> <div class="form-check mb-2">
<input <input
class="form-check-input featured-radio" class="form-check-input featured-radio"
type="radio" type="radio"
name="featured_media" name="featured_media"
value="{{ media.id }}" value="{{ media.id }}"
{% if plant.featured_media_id == media.id %} checked {% endif %} {% if media.featured %}checked{% endif %}
data-url="{{ url_for('media.set_featured_image', data-url="{{ url_for('media.set_featured_image',
context='plant', context='plants',
context_id=plant.id, context_id=plant.id,
media_id=media.id) }}" media_id=media.id) }}"
> >
<label class="form-check-label">Featured</label> <label class="form-check-label">Featured</label>
</div> </div>
<div class="d-grid gap-1"> {# Rotate button #}
<button <button
type="button" type="button"
class="btn btn-outline-secondary btn-sm rotate-btn" class="btn btn-outline-secondary btn-sm rotate-btn"
@ -109,16 +117,17 @@
data-id="{{ media.id }}" data-id="{{ media.id }}"
>Rotate</button> >Rotate</button>
{# Delete checkbox #}
<div class="form-check mt-2"> <div class="form-check mt-2">
<input class="form-check-input delete-checkbox" <input
class="form-check-input delete-checkbox"
type="checkbox" type="checkbox"
name="delete_ids" name="delete_ids"
value="{{ media.id }}"> value="{{ media.id }}"
>
<label class="form-check-label">Delete</label> <label class="form-check-label">Delete</label>
</div> </div>
</div> </div>
</div>
</div> </div>
</div> </div>
{% else %} {% else %}
@ -132,39 +141,38 @@
{% endblock %} {% endblock %}
{% block scripts %} {% block scripts %}
{{ super() }}
<script> <script>
const csrfToken = "{{ form.csrf_token._value() }}"; const csrfToken = "{{ form.csrf_token._value() }}";
// Rotate // Rotate buttons
document.querySelectorAll('.rotate-btn').forEach(btn => { document.querySelectorAll('.rotate-btn').forEach(btn => {
btn.addEventListener('click', () => { btn.addEventListener('click', () => {
const url = btn.dataset.url; fetch(btn.dataset.url, {
const id = btn.dataset.id;
fetch(url, {
method: 'POST', method: 'POST',
headers: { 'X-CSRFToken': csrfToken } headers: { 'X-CSRFToken': csrfToken }
}) })
.then(r => { .then(r => {
if (!r.ok) throw Error(); if (!r.ok) throw Error();
const img = document.getElementById(`image-${id}`); const img = document.getElementById(`image-${btn.dataset.id}`);
img.src = img.src.split('?')[0] + `?v=${Date.now()}`; img.src = img.src.split('?')[0] + `?v=${Date.now()}`;
}) })
.catch(() => alert('Failed to rotate image.')); .catch(() => alert('Failed to rotate image.'));
}); });
}); });
// Feature // Featuredradio AJAX
document.querySelectorAll('.featured-radio').forEach(radio => { document.querySelectorAll('.featured-radio').forEach(radio => {
radio.addEventListener('change', () => { radio.addEventListener('change', () => {
const url = radio.dataset.url; fetch(radio.dataset.url, {
fetch(url, {
method: 'POST', method: 'POST',
headers: { 'X-CSRFToken': csrfToken } headers: { 'X-CSRFToken': csrfToken }
}) })
.then(r => { .then(r => {
if (!r.ok) throw Error(); if (!r.ok) throw Error();
// uncheck all, then check this one // uncheck them all, then check this one
document.querySelectorAll('.featured-radio').forEach(r => r.checked = false); document.querySelectorAll('.featured-radio')
.forEach(r => r.checked = false);
radio.checked = true; radio.checked = true;
}) })
.catch(() => alert('Could not set featured image.')); .catch(() => alert('Could not set featured image.'));