images working, featured still broken
This commit is contained in:
@ -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):
|
||||||
|
"""
|
||||||
|
Single‐select “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"])
|
||||||
|
@ -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
|
// Featured‐radio 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.'));
|
||||||
|
Reference in New Issue
Block a user