stuff is working again
This commit is contained in:
@ -21,10 +21,6 @@ from flask_login import login_required, current_user
|
||||
from flask_wtf.csrf import generate_csrf
|
||||
from werkzeug.utils import secure_filename
|
||||
from werkzeug.datastructures import FileStorage
|
||||
import qrcode
|
||||
from PIL import Image, ImageDraw, ImageFont
|
||||
from qrcode.image.pil import PilImage
|
||||
from qrcode.constants import ERROR_CORRECT_H
|
||||
|
||||
# Application
|
||||
from app import db
|
||||
@ -60,7 +56,8 @@ def index():
|
||||
# Required headers for your sub-app export ZIP
|
||||
PLANT_HEADERS = [
|
||||
"UUID","Type","Name","Scientific Name",
|
||||
"Vendor Name","Price","Mother UUID","Notes"
|
||||
"Vendor Name","Price","Mother UUID","Notes",
|
||||
"Short ID"
|
||||
]
|
||||
MEDIA_HEADERS = [
|
||||
"Plant UUID","Image Path","Uploaded At","Source Type"
|
||||
@ -152,10 +149,11 @@ def upload():
|
||||
return redirect(request.url)
|
||||
media_rows = list(mreader)
|
||||
|
||||
# --- import plants ---
|
||||
# --- import plants (first pass, only set mother_uuid if parent exists) ---
|
||||
neo = get_neo4j_handler()
|
||||
added_plants = 0
|
||||
plant_map = {}
|
||||
|
||||
for row in plant_rows:
|
||||
common = PlantCommonName.query.filter_by(name=row["Name"]).first()
|
||||
if not common:
|
||||
@ -163,7 +161,9 @@ def upload():
|
||||
db.session.add(common)
|
||||
db.session.flush()
|
||||
|
||||
scientific = PlantScientificName.query.filter_by(name=row["Scientific Name"]).first()
|
||||
scientific = PlantScientificName.query.filter_by(
|
||||
name=row["Scientific Name"]
|
||||
).first()
|
||||
if not scientific:
|
||||
scientific = PlantScientificName(
|
||||
name=row["Scientific Name"],
|
||||
@ -172,12 +172,20 @@ def upload():
|
||||
db.session.add(scientific)
|
||||
db.session.flush()
|
||||
|
||||
raw_mu = row.get("Mother UUID") or None
|
||||
mu_for_insert = raw_mu if raw_mu in plant_map else None
|
||||
|
||||
p = Plant(
|
||||
uuid=row["UUID"],
|
||||
common_id=common.id,
|
||||
scientific_id=scientific.id,
|
||||
plant_type=row["Type"],
|
||||
owner_id=current_user.id,
|
||||
vendor_name=row["Vendor Name"] or None,
|
||||
price=float(row["Price"]) if row["Price"] else None,
|
||||
mother_uuid=mu_for_insert,
|
||||
notes=row["Notes"] or None,
|
||||
short_id=(row.get("Short ID") or None),
|
||||
data_verified=True
|
||||
)
|
||||
db.session.add(p)
|
||||
@ -194,15 +202,26 @@ def upload():
|
||||
db.session.add(log)
|
||||
|
||||
neo.create_plant_node(p.uuid, row["Name"])
|
||||
if row.get("Mother UUID"):
|
||||
if raw_mu:
|
||||
neo.create_lineage(
|
||||
child_uuid=p.uuid,
|
||||
parent_uuid=row["Mother UUID"]
|
||||
parent_uuid=raw_mu
|
||||
)
|
||||
|
||||
added_plants += 1
|
||||
|
||||
# --- import media (FIX: now passing plant_id) ---
|
||||
db.session.commit()
|
||||
|
||||
# --- second pass: backfill mother_uuid for all rows ---
|
||||
for row in plant_rows:
|
||||
raw_mu = row.get("Mother UUID") or None
|
||||
if raw_mu:
|
||||
Plant.query.filter_by(uuid=row["UUID"]).update({
|
||||
'mother_uuid': raw_mu
|
||||
})
|
||||
db.session.commit()
|
||||
|
||||
# --- import media (unchanged) ---
|
||||
added_media = 0
|
||||
for mrow in media_rows:
|
||||
plant_uuid = mrow["Plant UUID"]
|
||||
@ -211,7 +230,7 @@ def upload():
|
||||
continue
|
||||
|
||||
subpath = mrow["Image Path"].split('uploads/', 1)[-1]
|
||||
src = os.path.join(tmpdir, "images", subpath)
|
||||
src = os.path.join(tmpdir, "images", subpath)
|
||||
if not os.path.isfile(src):
|
||||
continue
|
||||
|
||||
@ -227,7 +246,7 @@ def upload():
|
||||
uploader_id=current_user.id,
|
||||
plugin="plant",
|
||||
related_id=plant_id,
|
||||
plant_id=plant_id # ← ensure the FK is set!
|
||||
plant_id=plant_id
|
||||
)
|
||||
media.uploaded_at = datetime.fromisoformat(mrow["Uploaded At"])
|
||||
media.caption = mrow["Source Type"]
|
||||
@ -268,11 +287,11 @@ def upload():
|
||||
all_sci = {s.name.lower(): s for s in PlantScientificName.query.all()}
|
||||
|
||||
for row in reader:
|
||||
uuid_val = row.get("uuid", "").strip().strip('"')
|
||||
name = row.get("name", "").strip()
|
||||
sci_name = row.get("scientific_name", "").strip()
|
||||
plant_type = row.get("plant_type", "").strip() or "plant"
|
||||
mother_uuid= row.get("mother_uuid", "").strip().strip('"')
|
||||
uuid_val = row.get("uuid", "").strip().strip('"')
|
||||
name = row.get("name", "").strip()
|
||||
sci_name = row.get("scientific_name", "").strip()
|
||||
plant_type = row.get("plant_type", "").strip() or "plant"
|
||||
mother_uuid = row.get("mother_uuid", "").strip().strip('"')
|
||||
|
||||
if not (uuid_val and name and plant_type):
|
||||
continue
|
||||
@ -290,11 +309,11 @@ def upload():
|
||||
)
|
||||
|
||||
item = {
|
||||
"uuid": uuid_val,
|
||||
"name": name,
|
||||
"sci_name": sci_name,
|
||||
"suggested": suggested,
|
||||
"plant_type": plant_type,
|
||||
"uuid": uuid_val,
|
||||
"name": name,
|
||||
"sci_name": sci_name,
|
||||
"suggested": suggested,
|
||||
"plant_type": plant_type,
|
||||
"mother_uuid": mother_uuid
|
||||
}
|
||||
review_list.append(item)
|
||||
@ -302,21 +321,21 @@ def upload():
|
||||
|
||||
session["review_list"] = review_list
|
||||
return redirect(url_for("utility.review"))
|
||||
|
||||
|
||||
# ── Direct Media Upload Flow ───────────────────────────────────────
|
||||
plugin = request.form.get("plugin", '')
|
||||
plugin = request.form.get("plugin", "")
|
||||
related_id = request.form.get("related_id", 0)
|
||||
plant_id = request.form.get("plant_id", None)
|
||||
growlog_id = request.form.get("growlog_id", None)
|
||||
caption = request.form.get("caption", None)
|
||||
|
||||
|
||||
now = datetime.utcnow()
|
||||
unique_id = str(uuid.uuid4()).replace("-", "")
|
||||
secure_name= secure_filename(file.filename)
|
||||
storage_path = os.path.join(
|
||||
current_app.config['UPLOAD_FOLDER'],
|
||||
current_app.config["UPLOAD_FOLDER"],
|
||||
str(current_user.id),
|
||||
now.strftime('%Y/%m/%d')
|
||||
now.strftime("%Y/%m/%d")
|
||||
)
|
||||
os.makedirs(storage_path, exist_ok=True)
|
||||
|
||||
@ -397,6 +416,7 @@ def review():
|
||||
scientific_id = scientific.id,
|
||||
plant_type = plant_type,
|
||||
owner_id = current_user.id,
|
||||
mother_uuid = mother_uuid or None,
|
||||
data_verified = verified
|
||||
)
|
||||
db.session.add(plant)
|
||||
@ -521,74 +541,91 @@ def export_data():
|
||||
# ────────────────────────────────────────────────────────────────────────────────
|
||||
# QR-Code Generation Helpers & Routes
|
||||
# ────────────────────────────────────────────────────────────────────────────────
|
||||
def generate_label_with_name(qr_url, name, filename):
|
||||
from PIL import Image, ImageDraw, ImageFont
|
||||
import qrcode
|
||||
from qrcode.image.pil import PilImage
|
||||
from qrcode.constants import ERROR_CORRECT_H
|
||||
from flask import current_app, send_file
|
||||
|
||||
def generate_label_with_name(qr_url, name, download_filename):
|
||||
"""
|
||||
Build a 1.5"x1.5" PNG (300dpi) with a QR code
|
||||
and the plant name underneath.
|
||||
"""
|
||||
# Generate QR code
|
||||
qr = qrcode.QRCode(version=2, error_correction=ERROR_CORRECT_H, box_size=10, border=1)
|
||||
qr.add_data(qr_url)
|
||||
qr.make(fit=True)
|
||||
qr_img = qr.make_image(image_factory=PilImage, fill_color="black", back_color="white").convert("RGB")
|
||||
|
||||
dpi = 300
|
||||
# Create 1.5"x1.5" canvas at 300 DPI
|
||||
dpi = 300
|
||||
label_px = int(1.5 * dpi)
|
||||
canvas_h = label_px + 400
|
||||
label_img = Image.new("RGB", (label_px, canvas_h), "white")
|
||||
label_img.paste(qr_img.resize((label_px, label_px)), (0, 0))
|
||||
label_img = Image.new("RGB", (label_px, label_px), "white")
|
||||
|
||||
font_path = os.path.join(current_app.root_path, '..', 'font', 'ARIALLGT.TTF')
|
||||
draw = ImageDraw.Draw(label_img)
|
||||
text = (name or '').strip()
|
||||
# Resize QR code
|
||||
qr_size = 350
|
||||
qr_img = qr_img.resize((qr_size, qr_size), Image.LANCZOS)
|
||||
qr_x = (label_px - qr_size) // 2
|
||||
label_img.paste(qr_img, (qr_x, 10))
|
||||
|
||||
# Load font
|
||||
font_path = os.path.abspath(os.path.join(current_app.root_path, '..', 'font', 'ARIALLGT.TTF'))
|
||||
draw = ImageDraw.Draw(label_img)
|
||||
name = (name or '').strip()
|
||||
font_size = 28
|
||||
|
||||
while font_size > 10:
|
||||
try:
|
||||
font = ImageFont.truetype(font_path, font_size)
|
||||
except OSError:
|
||||
font = ImageFont.load_default()
|
||||
if draw.textlength(text, font=font) <= label_px - 20:
|
||||
if draw.textlength(name, font=font) <= label_px - 20:
|
||||
break
|
||||
font_size -= 1
|
||||
while draw.textlength(text, font=font) > label_px - 20 and len(text) > 1:
|
||||
text = text[:-1]
|
||||
if len(text) < len((name or '').strip()):
|
||||
text += "…"
|
||||
x = (label_px - draw.textlength(text, font=font)) // 2
|
||||
y = label_px + 20
|
||||
draw.text((x, y), text, font=font, fill="black")
|
||||
|
||||
if draw.textlength(name, font=font) > label_px - 20:
|
||||
while draw.textlength(name + "…", font=font) > label_px - 20 and len(name) > 1:
|
||||
name = name[:-1]
|
||||
name += "…"
|
||||
|
||||
# Draw text centered
|
||||
text_x = (label_px - draw.textlength(name, font=font)) // 2
|
||||
text_y = 370
|
||||
draw.text((text_x, text_y), name, font=font, fill="black")
|
||||
|
||||
buf = io.BytesIO()
|
||||
label_img.save(buf, format='PNG', dpi=(dpi, dpi))
|
||||
buf.seek(0)
|
||||
return send_file(buf, mimetype='image/png', download_name=download_filename, as_attachment=True)
|
||||
|
||||
return send_file(
|
||||
buf,
|
||||
mimetype='image/png',
|
||||
as_attachment=True,
|
||||
download_name=filename
|
||||
)
|
||||
|
||||
|
||||
@bp.route('/<uuid:uuid_val>/download_qr', methods=['GET'])
|
||||
@bp.route('/download_qr/<string:uuid_val>', methods=['GET'])
|
||||
@login_required
|
||||
def download_qr(uuid_val):
|
||||
p = Plant.query.filter_by(uuid=uuid_val).first_or_404()
|
||||
if not p.short_id:
|
||||
# Private “Direct QR” → f/<short_id> on plant.cards
|
||||
p = Plant.query.filter_by(uuid=uuid_val, owner_id=current_user.id).first_or_404()
|
||||
if not getattr(p, 'short_id', None):
|
||||
p.short_id = Plant.generate_short_id()
|
||||
db.session.commit()
|
||||
qr_url = f'https://plant.cards/{p.short_id}'
|
||||
|
||||
base = current_app.config.get('PLANT_CARDS_BASE_URL', 'https://plant.cards')
|
||||
qr_url = f"{base}/f/{p.short_id}"
|
||||
filename = f"{p.short_id}.png"
|
||||
return generate_label_with_name(
|
||||
qr_url,
|
||||
p.common_name.name or p.scientific_name,
|
||||
filename
|
||||
)
|
||||
return generate_label_with_name(qr_url, p.common_name.name, filename)
|
||||
|
||||
|
||||
@bp.route('/<uuid:uuid_val>/download_qr_card', methods=['GET'])
|
||||
@bp.route('/download_qr_card/<string:uuid_val>', methods=['GET'])
|
||||
def download_qr_card(uuid_val):
|
||||
# Public “Card QR” → /<short_id> on plant.cards
|
||||
p = Plant.query.filter_by(uuid=uuid_val).first_or_404()
|
||||
if not p.short_id:
|
||||
if not getattr(p, 'short_id', None):
|
||||
p.short_id = Plant.generate_short_id()
|
||||
db.session.commit()
|
||||
qr_url = f'https://plant.cards/{p.short_id}'
|
||||
|
||||
base = current_app.config.get('PLANT_CARDS_BASE_URL', 'https://plant.cards')
|
||||
qr_url = f"{base}/{p.short_id}"
|
||||
filename = f"{p.short_id}_card.png"
|
||||
return generate_label_with_name(
|
||||
qr_url,
|
||||
p.common_name.name or p.scientific_name,
|
||||
filename
|
||||
)
|
||||
return generate_label_with_name(qr_url, p.common_name.name, filename)
|
||||
|
Reference in New Issue
Block a user