bunch of changes
This commit is contained in:
@ -5,13 +5,8 @@ from datetime import datetime, timedelta
|
||||
from app import db
|
||||
from plugins.auth.models import User
|
||||
from plugins.plant.models import (
|
||||
Plant, PlantCommonName, PlantScientificName, PlantOwnershipLog,
|
||||
PlantLineage
|
||||
Plant, PlantCommonName, PlantScientificName
|
||||
)
|
||||
from plugins.growlog.models import PlantUpdate
|
||||
from plugins.media.models import Media, ImageHeart, FeaturedImage
|
||||
from plugins.submission.models import Submission, SubmissionImage
|
||||
|
||||
|
||||
@click.command(name='preload-data') # 🔧 changed from preload_data
|
||||
@with_appcontext
|
||||
@ -38,12 +33,6 @@ def preload_data(auto=False):
|
||||
db.session.commit()
|
||||
|
||||
# COMMON & SCIENTIFIC NAMES
|
||||
#monstera_common = PlantCommonName(name='Monstera')
|
||||
#deliciosa_sci = PlantScientificName(name='Monstera deliciosa')
|
||||
#aurea_sci = PlantScientificName(name='Monstera aurea')
|
||||
#db.session.add_all([monstera_common, deliciosa_sci, aurea_sci])
|
||||
#db.session.commit()
|
||||
|
||||
joepii_common = PlantCommonName(name='Philodendron Joepii')
|
||||
queen_common = PlantCommonName(name='Anthurium Warocqueanum Queen')
|
||||
thai_common = PlantCommonName(name='Thai Constellation Monstera')
|
||||
@ -60,78 +49,5 @@ def preload_data(auto=False):
|
||||
db.session.add_all([joepii_sci, queen_sci, thai_sci, generic_sci])
|
||||
db.session.commit()
|
||||
|
||||
# PLANTS
|
||||
""" parent_plant = Plant(
|
||||
common_name_id=monstera_common.id,
|
||||
scientific_name_id=deliciosa_sci.id,
|
||||
created_by_user_id=admin.id
|
||||
)
|
||||
child_plant = Plant(
|
||||
common_name_id=monstera_common.id,
|
||||
scientific_name_id=aurea_sci.id,
|
||||
created_by_user_id=user.id,
|
||||
parent_id=1
|
||||
)
|
||||
db.session.add_all([parent_plant, child_plant])
|
||||
db.session.flush()
|
||||
|
||||
# LINEAGE & OWNERSHIP
|
||||
db.session.add(PlantLineage(parent_plant_id=parent_plant.id, child_plant_id=child_plant.id))
|
||||
db.session.add(PlantOwnershipLog(
|
||||
plant_id=child_plant.id,
|
||||
user_id=user.id,
|
||||
date_acquired=datetime.utcnow() - timedelta(days=20)
|
||||
))
|
||||
db.session.commit()
|
||||
|
||||
# UPDATE & MEDIA
|
||||
update = PlantUpdate(
|
||||
plant_id=child_plant.id,
|
||||
update_type='Repotted',
|
||||
description='Moved to a 6" pot with a new moss pole.',
|
||||
)
|
||||
db.session.add(update)
|
||||
db.session.flush()
|
||||
|
||||
db.session.add(Media(
|
||||
file_url='uploads/demo_plant_update.jpg',
|
||||
update_id=update.id,
|
||||
caption='Freshly repotted.'
|
||||
))
|
||||
db.session.commit()
|
||||
|
||||
# SUBMISSION & IMAGE
|
||||
submission = Submission(
|
||||
user_id=user.id,
|
||||
plant_id=child_plant.id,
|
||||
common_name='Monstera',
|
||||
scientific_name='Monstera aurea',
|
||||
price=120.00,
|
||||
source='Etsy',
|
||||
height=45,
|
||||
width=30,
|
||||
leaf_count=5,
|
||||
potting_mix='2:1:1 bark:pumice:coco',
|
||||
container_size='6"',
|
||||
health_status='Healthy',
|
||||
notes='Some minor yellowing on one leaf.'
|
||||
)
|
||||
db.session.add(submission)
|
||||
db.session.flush()
|
||||
|
||||
image = SubmissionImage(
|
||||
submission_id=submission.id,
|
||||
file_path='uploads/demo_submission.jpg',
|
||||
is_visible=True
|
||||
)
|
||||
db.session.add(image)
|
||||
db.session.flush()
|
||||
|
||||
db.session.add_all([
|
||||
ImageHeart(user_id=admin.id, submission_image_id=image.id),
|
||||
FeaturedImage(submission_image_id=image.id, override_text='Gorgeous coloration', is_featured=True)
|
||||
])
|
||||
db.session.commit() """
|
||||
|
||||
if not auto:
|
||||
click.echo("🎉 Demo data seeded successfully.")
|
||||
|
@ -1,16 +1,25 @@
|
||||
# plugins/importer/routes.py
|
||||
|
||||
import csv
|
||||
import io
|
||||
import difflib
|
||||
|
||||
from flask import Blueprint, request, render_template, redirect, flash, session, url_for
|
||||
from flask_login import login_required, current_user
|
||||
from app.neo4j_utils import get_neo4j_handler
|
||||
from plugins.plant.models import db, Plant, PlantCommon, PlantScientific
|
||||
from flask_wtf.csrf import generate_csrf
|
||||
|
||||
bp = Blueprint("importer", __name__, template_folder="templates")
|
||||
from app.neo4j_utils import get_neo4j_handler
|
||||
from plugins.plant.models import (
|
||||
db,
|
||||
Plant, PlantCommonName, PlantScientificName, PlantOwnershipLog
|
||||
)
|
||||
|
||||
bp = Blueprint("importer", __name__, template_folder="templates", url_prefix="/import")
|
||||
|
||||
REQUIRED_HEADERS = {"uuid", "plant_type", "name"}
|
||||
|
||||
@bp.route("/import/", methods=["GET", "POST"])
|
||||
|
||||
@bp.route("/", methods=["GET", "POST"])
|
||||
@login_required
|
||||
def upload():
|
||||
if request.method == "POST":
|
||||
@ -24,48 +33,58 @@ def upload():
|
||||
stream = io.StringIO(decoded)
|
||||
reader = csv.DictReader(stream)
|
||||
|
||||
headers = set(reader.fieldnames)
|
||||
if not REQUIRED_HEADERS.issubset(headers):
|
||||
flash(f"Missing required CSV headers: {REQUIRED_HEADERS - headers}", "error")
|
||||
headers = set(reader.fieldnames or [])
|
||||
missing = REQUIRED_HEADERS - headers
|
||||
if missing:
|
||||
flash(f"Missing required CSV headers: {missing}", "error")
|
||||
return redirect(request.url)
|
||||
|
||||
session["pending_rows"] = []
|
||||
review_list = []
|
||||
|
||||
all_common = {c.name.lower(): c for c in PlantCommon.query.all()}
|
||||
all_scientific = {s.name.lower(): s for s in PlantScientific.query.all()}
|
||||
# Preload existing common/scientific names
|
||||
all_common = {c.name.lower(): c for c in PlantCommonName.query.all()}
|
||||
all_scientific = {s.name.lower(): s for s in PlantScientificName.query.all()}
|
||||
|
||||
for row in reader:
|
||||
uuid = row.get("uuid")
|
||||
name = row.get("name", "").strip()
|
||||
sci_name = row.get("scientific_name", "").strip()
|
||||
plant_type = row.get("plant_type", "plant")
|
||||
mother_uuid = row.get("mother_uuid", "").strip()
|
||||
uuid_raw = row.get("uuid", "")
|
||||
uuid = uuid_raw.strip().strip('"')
|
||||
|
||||
if not all([uuid, name, plant_type]):
|
||||
name_raw = row.get("name", "")
|
||||
name = name_raw.strip()
|
||||
|
||||
sci_raw = row.get("scientific_name", "")
|
||||
sci_name = sci_raw.strip()
|
||||
|
||||
plant_type = row.get("plant_type", "").strip() or "plant"
|
||||
|
||||
mother_raw = row.get("mother_uuid", "")
|
||||
mother_uuid = mother_raw.strip().strip('"')
|
||||
|
||||
# If any required field is missing, skip
|
||||
if not (uuid and name and plant_type):
|
||||
continue
|
||||
|
||||
name_lc = name.lower()
|
||||
sci_lc = sci_name.lower()
|
||||
# Try fuzzy‐matching scientific names if needed
|
||||
suggested_match = None
|
||||
original_input = sci_name
|
||||
original_sci = sci_name
|
||||
name_lc = name.lower()
|
||||
sci_lc = sci_name.lower()
|
||||
|
||||
# Fuzzy match scientific name
|
||||
if sci_lc and sci_lc not in all_scientific:
|
||||
close = difflib.get_close_matches(sci_lc, all_scientific.keys(), n=1, cutoff=0.85)
|
||||
if close:
|
||||
suggested_match = all_scientific[close[0]].name
|
||||
|
||||
# Infer from common name
|
||||
if not sci_lc and name_lc in all_common:
|
||||
sci_obj = PlantScientific.query.filter_by(common_id=all_common[name_lc].id).first()
|
||||
sci_obj = PlantScientificName.query.filter_by(common_id=all_common[name_lc].id).first()
|
||||
if sci_obj:
|
||||
sci_name = sci_obj.name
|
||||
elif not sci_lc:
|
||||
close_common = difflib.get_close_matches(name_lc, all_common.keys(), n=1, cutoff=0.85)
|
||||
if close_common:
|
||||
match_name = close_common[0]
|
||||
sci_obj = PlantScientific.query.filter_by(common_id=all_common[match_name].id).first()
|
||||
sci_obj = PlantScientificName.query.filter_by(common_id=all_common[match_name].id).first()
|
||||
if sci_obj:
|
||||
suggested_match = sci_obj.name
|
||||
sci_name = sci_obj.name
|
||||
@ -74,17 +93,17 @@ def upload():
|
||||
"uuid": uuid,
|
||||
"name": name,
|
||||
"sci_name": sci_name,
|
||||
"original_sci_name": original_input,
|
||||
"original_sci_name": original_sci,
|
||||
"plant_type": plant_type,
|
||||
"mother_uuid": mother_uuid,
|
||||
"suggested_scientific_name": suggested_match,
|
||||
})
|
||||
|
||||
if suggested_match and suggested_match != original_input:
|
||||
if suggested_match and suggested_match != original_sci:
|
||||
review_list.append({
|
||||
"uuid": uuid,
|
||||
"common_name": name,
|
||||
"user_input": original_input or "(blank)",
|
||||
"user_input": original_sci or "(blank)",
|
||||
"suggested_name": suggested_match
|
||||
})
|
||||
|
||||
@ -92,44 +111,60 @@ def upload():
|
||||
return redirect(url_for("importer.review"))
|
||||
|
||||
except Exception as e:
|
||||
flash(f"Import failed: {str(e)}", "error")
|
||||
flash(f"Import failed: {e}", "error")
|
||||
return redirect(request.url)
|
||||
|
||||
return render_template("importer/upload.html")
|
||||
return render_template("importer/upload.html", csrf_token=generate_csrf())
|
||||
|
||||
|
||||
@bp.route("/import/review", methods=["GET", "POST"])
|
||||
@bp.route("/review", methods=["GET", "POST"])
|
||||
@login_required
|
||||
def review():
|
||||
rows = session.get("pending_rows", [])
|
||||
rows = session.get("pending_rows", [])
|
||||
review_list = session.get("review_list", [])
|
||||
|
||||
if request.method == "POST":
|
||||
neo = get_neo4j_handler()
|
||||
neo = get_neo4j_handler()
|
||||
added = 0
|
||||
|
||||
# —————————————————————————————————————————————
|
||||
# (1) CREATE MySQL records & MERGE every Neo4j node
|
||||
# —————————————————————————————————————————————
|
||||
for row in rows:
|
||||
uuid = row["uuid"]
|
||||
name = row["name"]
|
||||
sci_name = row["sci_name"]
|
||||
user_input = row["original_sci_name"]
|
||||
plant_type = row["plant_type"]
|
||||
mother_uuid = row["mother_uuid"]
|
||||
suggested = row.get("suggested_scientific_name")
|
||||
uuid_raw = row["uuid"]
|
||||
uuid = uuid_raw.strip().strip('"')
|
||||
|
||||
common = PlantCommon.query.filter_by(name=name).first()
|
||||
name_raw = row["name"]
|
||||
name = name_raw.strip()
|
||||
|
||||
sci_raw = row["sci_name"]
|
||||
sci_name = sci_raw.strip()
|
||||
|
||||
plant_type = row["plant_type"].strip()
|
||||
|
||||
mother_raw = row["mother_uuid"]
|
||||
mother_uuid = mother_raw.strip().strip('"')
|
||||
|
||||
suggested = row.get("suggested_scientific_name")
|
||||
|
||||
# ——— MySQL: PlantCommonName ———
|
||||
common = PlantCommonName.query.filter_by(name=name).first()
|
||||
if not common:
|
||||
common = PlantCommon(name=name)
|
||||
common = PlantCommonName(name=name)
|
||||
db.session.add(common)
|
||||
db.session.flush()
|
||||
|
||||
accepted = request.form.get(f"confirm_{uuid}")
|
||||
sci_name_to_use = suggested if (suggested and accepted) else sci_name
|
||||
# ——— MySQL: PlantScientificName ———
|
||||
accepted = request.form.get(f"confirm_{uuid}")
|
||||
sci_to_use = suggested if (suggested and accepted) else sci_name
|
||||
|
||||
scientific = PlantScientific.query.filter_by(name=sci_name_to_use).first()
|
||||
scientific = PlantScientificName.query.filter_by(name=sci_to_use).first()
|
||||
if not scientific:
|
||||
scientific = PlantScientific(name=sci_name_to_use, common_id=common.id)
|
||||
scientific = PlantScientificName(name=sci_to_use, common_id=common.id)
|
||||
db.session.add(scientific)
|
||||
db.session.flush()
|
||||
|
||||
# ——— MySQL: Plant row ———
|
||||
plant = Plant.query.filter_by(uuid=uuid).first()
|
||||
if not plant:
|
||||
plant = Plant(
|
||||
@ -141,18 +176,60 @@ def review():
|
||||
is_verified=bool(accepted)
|
||||
)
|
||||
db.session.add(plant)
|
||||
db.session.flush() # so plant.id is available immediately
|
||||
added += 1
|
||||
|
||||
neo.create_plant_node(uuid, name)
|
||||
if mother_uuid:
|
||||
neo.create_plant_node(mother_uuid, "Parent")
|
||||
neo.create_lineage(uuid, mother_uuid)
|
||||
# ——— MySQL: Create initial ownership log entry ———
|
||||
log = PlantOwnershipLog(
|
||||
plant_id = plant.id,
|
||||
user_id = current_user.id,
|
||||
date_acquired = datetime.utcnow(),
|
||||
transferred = False,
|
||||
is_verified = bool(accepted)
|
||||
)
|
||||
db.session.add(log)
|
||||
|
||||
# ——— Neo4j: ensure a node exists for this plant UUID ———
|
||||
neo.create_plant_node(uuid, name)
|
||||
|
||||
# Commit MySQL so that all Plant/OwnershipLog rows exist
|
||||
db.session.commit()
|
||||
|
||||
|
||||
# —————————————————————————————————————————————
|
||||
# (2) CREATE Neo4j LINEAGE relationships (child → parent). (Unchanged)
|
||||
# —————————————————————————————————————————————
|
||||
for row in rows:
|
||||
child_raw = row.get("uuid", "")
|
||||
child_uuid = child_raw.strip().strip('"')
|
||||
|
||||
mother_raw = row.get("mother_uuid", "")
|
||||
mother_uuid = mother_raw.strip().strip('"')
|
||||
|
||||
print(
|
||||
f"[DEBUG] row → child_raw={child_raw!r}, child_uuid={child_uuid!r}; "
|
||||
f"mother_raw={mother_raw!r}, mother_uuid={mother_uuid!r}"
|
||||
)
|
||||
|
||||
if mother_uuid:
|
||||
neo.create_plant_node(mother_uuid, name="Unknown")
|
||||
neo.create_lineage(child_uuid, mother_uuid)
|
||||
else:
|
||||
print(f"[DEBUG] Skipping LINEAGE creation for child {child_uuid!r} (no mother_uuid)")
|
||||
|
||||
# (Optional) Check two known UUIDs
|
||||
neo.debug_check_node("8b1059c8-8dd3-487a-af19-1eb548788e87")
|
||||
neo.debug_check_node("2ee2e0e7-69de-4d8f-abfe-4ed973c3d760")
|
||||
|
||||
neo.close()
|
||||
flash(f"{added} plants added.", "success")
|
||||
flash(f"{added} plants added (MySQL) + Neo4j nodes/relations created.", "success")
|
||||
|
||||
session.pop("pending_rows", None)
|
||||
session.pop("review_list", None)
|
||||
return redirect(url_for("importer.upload"))
|
||||
|
||||
return render_template("importer/review.html", review_list=review_list)
|
||||
return render_template(
|
||||
"importer/review.html",
|
||||
review_list=review_list,
|
||||
csrf_token=generate_csrf()
|
||||
)
|
||||
|
@ -1,43 +1,38 @@
|
||||
{% extends "core_ui/base.html" %}
|
||||
{% block title %}Review Scientific Names{% endblock %}
|
||||
|
||||
{% block title %}Review Matches{% endblock %}
|
||||
{% block content %}
|
||||
<div class="container py-4">
|
||||
<h2 class="mb-4">🔍 Review Suggested Matches</h2>
|
||||
|
||||
<form method="POST">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
||||
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
|
||||
{% if review_list %}
|
||||
<div class="table-responsive">
|
||||
<table class="table table-bordered align-middle">
|
||||
<thead>
|
||||
<p class="text-muted mb-3">Confirm the suggested scientific name replacements below. Only confirmed matches will override user input.</p>
|
||||
<table class="table table-bordered table-sm align-middle">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Common Name</th>
|
||||
<th>User Input</th>
|
||||
<th>Suggested Match</th>
|
||||
<th>Confirm</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for row in review_list %}
|
||||
<tr>
|
||||
<th>Common Name</th>
|
||||
<th>Suggested Scientific Name</th>
|
||||
<th>Confirm?</th>
|
||||
<td>{{ row.common_name }}</td>
|
||||
<td><code>{{ row.user_input }}</code></td>
|
||||
<td><code>{{ row.suggested_name }}</code></td>
|
||||
<td>
|
||||
<input type="checkbox" name="confirm_{{ row.uuid }}" value="1">
|
||||
</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for row in review_list %}
|
||||
<tr>
|
||||
<td>{{ row.common_name }}</td>
|
||||
<td>{{ row.suggested_name }}</td>
|
||||
<td>
|
||||
<input class="form-check-input" type="checkbox" name="confirm_{{ row.uuid }}" value="1" checked>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% else %}
|
||||
<p>No suggestions were made. You can safely continue.</p>
|
||||
<p>No matches found that need confirmation.</p>
|
||||
{% endif %}
|
||||
|
||||
<div class="mt-3">
|
||||
<button type="submit" class="btn btn-primary">Confirm and Import</button>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary mt-3">Finalize Import</button>
|
||||
</form>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
@ -1,10 +1,8 @@
|
||||
{% extends "core_ui/base.html" %}
|
||||
{% block title %}CSV Import{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container py-4">
|
||||
<h2 class="mb-4">📤 Import Plant Data</h2>
|
||||
|
||||
{% with messages = get_flashed_messages(with_categories=true) %}
|
||||
{% if messages %}
|
||||
{% for category, message in messages %}
|
||||
@ -15,14 +13,13 @@
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
|
||||
<form method="POST" enctype="multipart/form-data">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
|
||||
<div class="mb-3">
|
||||
<label for="file" class="form-label">Choose CSV File</label>
|
||||
<input type="file" class="form-control" id="file" name="file" required>
|
||||
<div class="form-text">
|
||||
Must include: <code>uuid</code>, <code>plant_type</code>, <code>name</code><br>
|
||||
Required: <code>uuid</code>, <code>plant_type</code>, <code>name</code><br>
|
||||
Optional: <code>scientific_name</code>, <code>mother_uuid</code>
|
||||
</div>
|
||||
</div>
|
||||
|
62
plugins/ownership/routes.py
Normal file
62
plugins/ownership/routes.py
Normal file
@ -0,0 +1,62 @@
|
||||
# plugins/ownership/routes.py
|
||||
|
||||
from datetime import datetime
|
||||
|
||||
from flask import Blueprint, request, jsonify, abort
|
||||
from flask_login import login_required, current_user
|
||||
|
||||
from plugins.plant.models import db, Plant, PlantOwnershipLog
|
||||
from plugins.auth.models import User # Adjust import path if User lives elsewhere
|
||||
|
||||
bp = Blueprint("ownership", __name__, url_prefix="/ownership")
|
||||
|
||||
|
||||
@bp.route("/transfer/<string:plant_uuid>", methods=["POST"])
|
||||
@login_required
|
||||
def transfer(plant_uuid):
|
||||
"""
|
||||
Transfer a plant from the current owner to another user:
|
||||
Required JSON or form data: { "new_owner_id": <int> }
|
||||
"""
|
||||
data = request.get_json() or request.form
|
||||
new_owner_id = data.get("new_owner_id", None)
|
||||
if not new_owner_id:
|
||||
return jsonify({"error": "new_owner_id is required"}), 400
|
||||
|
||||
# 1) Fetch the plant by UUID
|
||||
plant = Plant.query.filter_by(uuid=plant_uuid).first()
|
||||
if not plant:
|
||||
return jsonify({"error": "Plant not found"}), 404
|
||||
|
||||
# 2) Only current owner (or some admin) can transfer
|
||||
if plant.owner_id != current_user.id:
|
||||
return jsonify({"error": "Only the current owner can transfer this plant"}), 403
|
||||
|
||||
# 3) Verify the new owner exists
|
||||
new_owner = User.query.get(new_owner_id)
|
||||
if not new_owner:
|
||||
return jsonify({"error": "New owner user not found"}), 404
|
||||
|
||||
# 4) Create a log entry before changing owner
|
||||
log = PlantOwnershipLog(
|
||||
plant_id = plant.id,
|
||||
user_id = new_owner.id,
|
||||
date_acquired = datetime.utcnow(),
|
||||
transferred = True,
|
||||
# If you want to store a reference to Neo4j node, set graph_node_id here.
|
||||
graph_node_id = None,
|
||||
is_verified = False
|
||||
)
|
||||
db.session.add(log)
|
||||
|
||||
# 5) Update the Plant.owner_id
|
||||
plant.owner_id = new_owner.id
|
||||
db.session.add(plant)
|
||||
|
||||
db.session.commit()
|
||||
|
||||
return jsonify({
|
||||
"message": f"Plant '{plant.uuid}' transferred to user {new_owner.username}.",
|
||||
"plant_uuid": plant.uuid,
|
||||
"new_owner_id": new_owner.id
|
||||
}), 200
|
@ -1,96 +1,129 @@
|
||||
from flask_sqlalchemy import SQLAlchemy
|
||||
import uuid as uuid_lib
|
||||
# plugins/plant/models.py
|
||||
|
||||
from datetime import datetime
|
||||
import uuid as uuid_lib
|
||||
|
||||
# Import the central SQLAlchemy instance, not a new one
|
||||
from app import db
|
||||
|
||||
# Association table for tags
|
||||
# If your User model lives in plugins/auth/models.py, import it here:
|
||||
from plugins.auth.models import User
|
||||
|
||||
# -----------------------------
|
||||
# (We no longer need PlantLineage)
|
||||
# -----------------------------
|
||||
|
||||
# Association table for tags (unchanged)
|
||||
plant_tags = db.Table(
|
||||
'plant_tags',
|
||||
db.metadata,
|
||||
db.Column('plant_id', db.Integer, db.ForeignKey('plant.id'), primary_key=True),
|
||||
db.Column('tag_id', db.Integer, db.ForeignKey('tag.id'), primary_key=True),
|
||||
db.Column('tag_id', db.Integer, db.ForeignKey('tag.id'), primary_key=True),
|
||||
extend_existing=True
|
||||
)
|
||||
|
||||
class Tag(db.Model):
|
||||
__tablename__ = 'tag'
|
||||
__table_args__ = {'extend_existing': True}
|
||||
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
name = db.Column(db.String(255), unique=True, nullable=False)
|
||||
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
name = db.Column(db.String(128), unique=True, nullable=False)
|
||||
# … any other columns you had …
|
||||
|
||||
|
||||
class PlantCommonName(db.Model):
|
||||
__tablename__ = 'plant_common_name'
|
||||
__table_args__ = {'extend_existing': True}
|
||||
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
name = db.Column(db.String(255), unique=True, nullable=False)
|
||||
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
name = db.Column(db.String(128), unique=True, nullable=False)
|
||||
created_at = db.Column(db.DateTime, default=datetime.utcnow)
|
||||
|
||||
scientific_names = db.relationship(
|
||||
'plugins.plant.models.PlantScientificName',
|
||||
backref=db.backref('common', lazy='joined'),
|
||||
lazy=True,
|
||||
cascade='all, delete-orphan'
|
||||
)
|
||||
|
||||
|
||||
class PlantScientificName(db.Model):
|
||||
__tablename__ = 'plant_scientific_name'
|
||||
__table_args__ = {'extend_existing': True}
|
||||
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
name = db.Column(db.String(255), unique=True, nullable=False)
|
||||
common_id = db.Column(db.Integer, db.ForeignKey('plant_common_name.id'), nullable=False)
|
||||
|
||||
class PlantLineage(db.Model):
|
||||
__tablename__ = 'plant_lineage'
|
||||
__table_args__ = {'extend_existing': True}
|
||||
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
child_plant_id = db.Column(db.Integer, db.ForeignKey('plant.id'), nullable=False)
|
||||
parent_plant_id = db.Column(db.Integer, db.ForeignKey('plant.id'), nullable=False)
|
||||
type = db.Column(db.String(50), nullable=False) # cutting, seed, division
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
name = db.Column(db.String(256), unique=True, nullable=False)
|
||||
common_id = db.Column(db.Integer, db.ForeignKey('plant_common_name.id'), nullable=False)
|
||||
created_at = db.Column(db.DateTime, default=datetime.utcnow)
|
||||
|
||||
plants = db.relationship(
|
||||
'plugins.plant.models.Plant',
|
||||
backref='scientific',
|
||||
lazy='dynamic'
|
||||
)
|
||||
|
||||
|
||||
class PlantOwnershipLog(db.Model):
|
||||
__tablename__ = 'plant_ownership_log'
|
||||
__table_args__ = {'extend_existing': True}
|
||||
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
plant_id = db.Column(db.Integer, db.ForeignKey('plant.id'), nullable=False)
|
||||
user_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False)
|
||||
start_time = db.Column(db.DateTime, nullable=False)
|
||||
end_time = db.Column(db.DateTime, nullable=True)
|
||||
transfer_note = db.Column(db.Text, nullable=True)
|
||||
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
plant_id = db.Column(db.Integer, db.ForeignKey('plant.id'), nullable=False)
|
||||
user_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False)
|
||||
date_acquired = db.Column(db.DateTime, default=datetime.utcnow)
|
||||
transferred = db.Column(db.Boolean, default=False, nullable=False)
|
||||
graph_node_id = db.Column(db.String(255), nullable=True) # optional
|
||||
is_verified = db.Column(db.Boolean, default=False, nullable=False)
|
||||
|
||||
user = db.relationship('plugins.auth.models.User', backref='ownership_logs', lazy=True)
|
||||
|
||||
|
||||
class Plant(db.Model):
|
||||
__tablename__ = 'plant'
|
||||
__table_args__ = {'extend_existing': True}
|
||||
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
uuid = db.Column(db.String(36), default=lambda: str(uuid_lib.uuid4()), unique=True, nullable=False)
|
||||
custom_slug = db.Column(db.String(255), unique=True, nullable=True)
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
uuid = db.Column(db.String(36), default=lambda: str(uuid_lib.uuid4()), unique=True, nullable=False)
|
||||
custom_slug = db.Column(db.String(255), unique=True, nullable=True)
|
||||
|
||||
owner_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False)
|
||||
common_id = db.Column(db.Integer, db.ForeignKey('plant_common_name.id'), nullable=False)
|
||||
scientific_id = db.Column(db.Integer, db.ForeignKey('plant_scientific_name.id'), nullable=False)
|
||||
owner_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False)
|
||||
common_id = db.Column(db.Integer, db.ForeignKey('plant_common_name.id'), nullable=False)
|
||||
scientific_id = db.Column(db.Integer, db.ForeignKey('plant_scientific_name.id'), nullable=False)
|
||||
|
||||
plant_type = db.Column(db.String(50), nullable=False)
|
||||
status = db.Column(db.String(50), nullable=False, default='active')
|
||||
notes = db.Column(db.Text, nullable=True)
|
||||
plant_type = db.Column(db.String(50), nullable=False)
|
||||
created_at = db.Column(db.DateTime, default=datetime.utcnow)
|
||||
updated_at = db.Column(db.DateTime, onupdate=datetime.utcnow)
|
||||
|
||||
created_at = db.Column(db.DateTime, default=datetime.utcnow)
|
||||
transferred = db.Column(db.Boolean, default=False)
|
||||
graph_node_id = db.Column(db.String(255), nullable=True)
|
||||
|
||||
is_verified = db.Column(db.Boolean, nullable=False, default=False)
|
||||
updates = db.relationship(
|
||||
'plugins.growlog.models.PlantUpdate',
|
||||
backref='plant',
|
||||
lazy=True,
|
||||
cascade='all, delete-orphan'
|
||||
)
|
||||
tags = db.relationship(
|
||||
'plugins.plant.models.Tag',
|
||||
secondary=plant_tags,
|
||||
backref='plants',
|
||||
lazy='dynamic'
|
||||
)
|
||||
|
||||
# Relationships
|
||||
updates = db.relationship('PlantUpdate', backref='growlog', lazy=True)
|
||||
lineage = db.relationship('PlantLineage', backref='child', lazy=True, foreign_keys='PlantLineage.child_plant_id')
|
||||
tags = db.relationship('Tag', secondary=plant_tags, backref='plants')
|
||||
|
||||
common_name = db.relationship(
|
||||
'PlantCommonName',
|
||||
backref=db.backref('plants', lazy='dynamic'),
|
||||
lazy=True
|
||||
)
|
||||
common_name = db.relationship(
|
||||
'plugins.plant.models.PlantCommonName',
|
||||
backref=db.backref('plants', lazy='dynamic'),
|
||||
lazy=True
|
||||
)
|
||||
scientific_name = db.relationship(
|
||||
'PlantScientificName',
|
||||
backref=db.backref('plants', lazy='dynamic'),
|
||||
lazy=True
|
||||
)
|
||||
|
||||
PlantCommon = PlantCommonName
|
||||
PlantScientific = PlantScientificName
|
||||
'plugins.plant.models.PlantScientificName',
|
||||
backref=db.backref('plants', lazy='dynamic'),
|
||||
lazy=True
|
||||
)
|
||||
|
||||
ownership_logs = db.relationship(
|
||||
'plugins.plant.models.PlantOwnershipLog',
|
||||
backref='plant',
|
||||
lazy=True,
|
||||
cascade='all, delete-orphan'
|
||||
)
|
||||
|
||||
def __repr__(self):
|
||||
return f"<Plant {self.uuid} ({self.plant_type})>"
|
||||
|
Reference in New Issue
Block a user