This commit is contained in:
2025-06-04 03:04:37 -05:00
parent c885ede8af
commit d0338a0849
5 changed files with 61 additions and 22 deletions

View File

@ -1,13 +1,19 @@
import csv
import io
from flask import Blueprint, request, render_template, redirect, flash
from flask_login import login_required, current_user
from werkzeug.utils import secure_filename
from app.neo4j_utils import get_neo4j_handler
from plugins.plant.models import db, Plant, PlantCommon, PlantScientific
bp = Blueprint("importer", __name__, template_folder="templates")
REQUIRED_HEADERS = {"uuid", "plant_type", "name", "scientific_name", "mother_uuid"}
REQUIRED_FIELDS = {"uuid", "plant_type", "name"}
@bp.route("/import/", methods=["GET", "POST"])
@login_required
def upload():
if request.method == "POST":
file = request.files.get("file")
@ -16,22 +22,36 @@ def upload():
return redirect(request.url)
try:
# Handle UTF-8 BOM (Byte Order Mark) if present
decoded = file.read().decode("utf-8-sig")
stream = io.StringIO(decoded)
reader = csv.DictReader(stream)
# Validate headers
if reader.fieldnames is None:
flash("Invalid CSV file: No headers found.", "error")
return redirect(request.url)
headers = set(h.strip() for h in reader.fieldnames)
missing = REQUIRED_HEADERS - headers
if missing:
flash(f"Missing required column(s): {', '.join(missing)}", "error")
return redirect(request.url)
neo = get_neo4j_handler()
added_count = 0
unknown_scientific_count = 0
for row in reader:
uuid = row.get("uuid")
name = row.get("name")
sci_name = row.get("scientific_name")
plant_type = row.get("plant_type", "plant")
mother_uuid = row.get("mother_uuid")
for i, row in enumerate(reader, start=2):
uuid = row.get("uuid", "").strip()
name = row.get("name", "").strip()
plant_type = row.get("plant_type", "").strip()
sci_name = row.get("scientific_name", "").strip()
mother_uuid = row.get("mother_uuid", "").strip()
if not all([uuid, name, sci_name]):
continue # skip incomplete rows
# Ensure required fields are present
if not all([uuid, name, plant_type]):
flash(f"Row {i} skipped: missing required data (uuid, name, or plant_type).", "warning")
continue
# Common Name
common = PlantCommon.query.filter_by(name=name).first()
@ -40,12 +60,25 @@ def upload():
db.session.add(common)
db.session.flush()
# Scientific Name
scientific = PlantScientific.query.filter_by(name=sci_name).first()
# Scientific Name (fallback or assign 'Unknown')
scientific = None
if sci_name:
scientific = PlantScientific.query.filter_by(name=sci_name).first()
if not scientific:
scientific = PlantScientific(name=sci_name, common_id=common.id)
db.session.add(scientific)
db.session.flush()
# Try resolving from existing records by common_id
scientific = PlantScientific.query.filter_by(common_id=common.id).first()
if not scientific:
# Fallback to 'Unknown'
unknown = PlantScientific.query.filter_by(name="Unknown").first()
if not unknown:
unknown = PlantScientific(name="Unknown", common_id=common.id)
db.session.add(unknown)
db.session.flush()
scientific = unknown
unknown_scientific_count += 1
# Plant
plant = Plant.query.filter_by(uuid=uuid).first()
@ -54,20 +87,26 @@ def upload():
uuid=uuid,
common_id=common.id,
scientific_id=scientific.id,
plant_type=plant_type
plant_type=plant_type,
owner_id=current_user.id
)
db.session.add(plant)
added_count += 1
# Neo4j
neo.create_plant_node(uuid, name)
if mother_uuid and mother_uuid.strip():
neo.create_plant_node(mother_uuid.strip(), "Parent")
neo.create_lineage(uuid, mother_uuid.strip())
if mother_uuid:
neo.create_plant_node(mother_uuid, "Parent")
neo.create_lineage(uuid, mother_uuid)
db.session.commit()
neo.close()
flash("CSV imported successfully with lineage.", "success")
msg = f"CSV imported successfully: {added_count} plants added."
if unknown_scientific_count > 0:
msg += f" {unknown_scientific_count} assigned 'Unknown' as scientific name."
flash(msg, "success")
except Exception as e:
flash(f"Import failed: {str(e)}", "error")

View File

@ -5,7 +5,7 @@
<div class="alert alert-info" role="alert">
<strong>Expected CSV headers (in this exact order):</strong>
<code>{{ headers }}</code>
<code>uuid,plant_type,name,scientific_name,mother_uuid</code>
</div>
<form method="POST" enctype="multipart/form-data" class="needs-validation" novalidate>