More files
This commit is contained in:
@ -1,3 +1,4 @@
|
||||
from werkzeug.security import generate_password_hash, check_password_hash
|
||||
from flask_login import UserMixin
|
||||
from datetime import datetime
|
||||
from app import db
|
||||
@ -7,7 +8,7 @@ class User(db.Model, UserMixin):
|
||||
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
email = db.Column(db.String(120), unique=True, nullable=False)
|
||||
password_hash = db.Column(db.String(128), nullable=False)
|
||||
password_hash = db.Column(db.Text, nullable=False)
|
||||
role = db.Column(db.String(50), default='user')
|
||||
is_verified = db.Column(db.Boolean, default=False)
|
||||
excluded_from_analytics = db.Column(db.Boolean, default=False)
|
||||
@ -15,113 +16,9 @@ class User(db.Model, UserMixin):
|
||||
|
||||
# Optional: relationship to submissions
|
||||
submissions = db.relationship('Submission', backref='user', lazy=True)
|
||||
|
||||
def set_password(self, password):
|
||||
self.password_hash = generate_password_hash(password)
|
||||
|
||||
|
||||
class Submission(db.Model):
|
||||
__tablename__ = 'submission'
|
||||
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
user_id = db.Column(
|
||||
db.Integer,
|
||||
db.ForeignKey('users.id', name='fk_submission_user_id'),
|
||||
nullable=False
|
||||
)
|
||||
common_name = db.Column(db.String(120), nullable=False)
|
||||
scientific_name = db.Column(db.String(120))
|
||||
price = db.Column(db.Float, nullable=False)
|
||||
source = db.Column(db.String(120))
|
||||
timestamp = db.Column(db.DateTime, default=datetime.utcnow)
|
||||
height = db.Column(db.Float)
|
||||
width = db.Column(db.Float)
|
||||
leaf_count = db.Column(db.Integer)
|
||||
potting_mix = db.Column(db.String(255))
|
||||
container_size = db.Column(db.String(120))
|
||||
health_status = db.Column(db.String(50))
|
||||
notes = db.Column(db.Text)
|
||||
plant_id = db.Column(db.Integer)
|
||||
images = db.relationship('SubmissionImage', backref='submission', lazy=True)
|
||||
|
||||
|
||||
class SubmissionImage(db.Model):
|
||||
__tablename__ = 'submission_images'
|
||||
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
submission_id = db.Column(db.Integer, db.ForeignKey('submission.id'), nullable=False)
|
||||
file_path = db.Column(db.String(255), nullable=False)
|
||||
|
||||
|
||||
class PlantCommonName(db.Model):
|
||||
__tablename__ = 'plants_common'
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
name = db.Column(db.String(120), unique=True, nullable=False)
|
||||
|
||||
|
||||
class PlantScientificName(db.Model):
|
||||
__tablename__ = 'plants_scientific'
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
name = db.Column(db.String(255), unique=True, nullable=False)
|
||||
|
||||
|
||||
class Plant(db.Model):
|
||||
__tablename__ = 'plants'
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
common_name_id = db.Column(db.Integer, db.ForeignKey('plants_common.id'))
|
||||
scientific_name_id = db.Column(db.Integer, db.ForeignKey('plants_scientific.id'))
|
||||
parent_id = db.Column(db.Integer, db.ForeignKey('plants.id'), nullable=True)
|
||||
is_dead = db.Column(db.Boolean, default=False)
|
||||
date_added = db.Column(db.DateTime, default=datetime.utcnow)
|
||||
created_by_user_id = db.Column(db.Integer, db.ForeignKey('users.id'))
|
||||
|
||||
# Relationships
|
||||
updates = db.relationship('PlantUpdate', backref='plant', lazy=True)
|
||||
lineage = db.relationship('PlantLineage', backref='child', lazy=True,
|
||||
foreign_keys='PlantLineage.child_plant_id')
|
||||
|
||||
|
||||
class PlantLineage(db.Model):
|
||||
__tablename__ = 'plant_lineage'
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
parent_plant_id = db.Column(db.Integer, db.ForeignKey('plants.id'), nullable=False)
|
||||
child_plant_id = db.Column(db.Integer, db.ForeignKey('plants.id'), nullable=False)
|
||||
|
||||
|
||||
class PlantOwnershipLog(db.Model):
|
||||
__tablename__ = 'plant_ownership_log'
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
plant_id = db.Column(db.Integer, db.ForeignKey('plants.id'), nullable=False)
|
||||
user_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False)
|
||||
date_acquired = db.Column(db.DateTime, default=datetime.utcnow)
|
||||
date_relinquished = db.Column(db.DateTime, nullable=True)
|
||||
|
||||
|
||||
class PlantUpdate(db.Model):
|
||||
__tablename__ = 'plant_updates'
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
plant_id = db.Column(db.Integer, db.ForeignKey('plants.id'), nullable=False)
|
||||
update_type = db.Column(db.String(100))
|
||||
description = db.Column(db.Text)
|
||||
created_at = db.Column(db.DateTime, default=datetime.utcnow)
|
||||
images = db.relationship('UpdateImage', backref='update', lazy=True)
|
||||
|
||||
|
||||
class UpdateImage(db.Model):
|
||||
__tablename__ = 'update_images'
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
update_id = db.Column(db.Integer, db.ForeignKey('plant_updates.id'), nullable=False)
|
||||
file_path = db.Column(db.String(255), nullable=False)
|
||||
|
||||
|
||||
class ImageHeart(db.Model):
|
||||
__tablename__ = 'image_hearts'
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
user_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False)
|
||||
submission_image_id = db.Column(db.Integer, db.ForeignKey('submission_images.id'), nullable=False)
|
||||
created_at = db.Column(db.DateTime, default=datetime.utcnow)
|
||||
|
||||
|
||||
class FeaturedImage(db.Model):
|
||||
__tablename__ = 'featured_images'
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
submission_image_id = db.Column(db.Integer, db.ForeignKey('submission_images.id'), nullable=False)
|
||||
override_text = db.Column(db.String(255), nullable=True)
|
||||
is_featured = db.Column(db.Boolean, default=True)
|
||||
def check_password(self, password):
|
||||
return check_password_hash(self.password_hash, password)
|
||||
|
@ -4,9 +4,9 @@ from werkzeug.security import check_password_hash
|
||||
from app import db
|
||||
from .models import User
|
||||
|
||||
auth = Blueprint('auth', __name__)
|
||||
bp = Blueprint('auth', __name__, template_folder='templates')
|
||||
|
||||
@auth.route('/auth/login', methods=['GET', 'POST'])
|
||||
@bp.route('/login', methods=['GET', 'POST'])
|
||||
def login():
|
||||
if request.method == 'POST':
|
||||
email = request.form['email']
|
||||
@ -15,14 +15,33 @@ def login():
|
||||
if user and check_password_hash(user.password_hash, password):
|
||||
login_user(user)
|
||||
flash('Logged in successfully.', 'success')
|
||||
return redirect(url_for('core.user_dashboard'))
|
||||
return redirect(url_for('core_ui.home'))
|
||||
else:
|
||||
flash('Invalid credentials.', 'danger')
|
||||
return render_template('login.html')
|
||||
return render_template('auth/login.html')
|
||||
|
||||
@auth.route('/auth/logout')
|
||||
@bp.route('/logout')
|
||||
@login_required
|
||||
def logout():
|
||||
logout_user()
|
||||
flash('Logged out.', 'info')
|
||||
return redirect(url_for('core.index'))
|
||||
return redirect(url_for('core_ui.home'))
|
||||
|
||||
@bp.route('/register', methods=['GET', 'POST'])
|
||||
def register():
|
||||
if request.method == 'POST':
|
||||
email = request.form['email']
|
||||
password = request.form['password']
|
||||
|
||||
existing_user = User.query.filter_by(email=email).first()
|
||||
if existing_user:
|
||||
flash('Email already registered.', 'warning')
|
||||
else:
|
||||
user = User(email=email)
|
||||
user.set_password(password)
|
||||
db.session.add(user)
|
||||
db.session.commit()
|
||||
flash('Account created. You can now log in.', 'success')
|
||||
return redirect(url_for('auth.login'))
|
||||
|
||||
return render_template('auth/register.html')
|
||||
|
@ -1,4 +1,4 @@
|
||||
{% extends 'base.html' %}
|
||||
{% extends 'core_ui/base.html' %}
|
||||
{% block content %}
|
||||
<h2>Login</h2>
|
||||
<form method="POST" action="/login">
|
||||
|
@ -1,15 +1,16 @@
|
||||
{% extends 'base.html' %}
|
||||
{% extends 'core_ui/base.html' %}
|
||||
{% block title %}Register{% endblock %}
|
||||
{% block content %}
|
||||
<h2>Register</h2>
|
||||
<form method="POST" action="/register">
|
||||
<div class="mb-3">
|
||||
<label for="email" class="form-label">Email</label>
|
||||
<input type="email" class="form-control" id="email" name="email" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="password" class="form-label">Password</label>
|
||||
<input type="password" class="form-control" id="password" name="password" required>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-success">Register</button>
|
||||
<form method="POST">
|
||||
<div class="mb-3">
|
||||
<label>Email</label>
|
||||
<input name="email" class="form-control" type="email" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label>Password</label>
|
||||
<input name="password" class="form-control" type="password" required>
|
||||
</div>
|
||||
<button class="btn btn-primary" type="submit">Register</button>
|
||||
</form>
|
||||
{% endblock %}
|
||||
|
@ -1,2 +1,10 @@
|
||||
from .seed import seed_admin
|
||||
from .preload import preload_data
|
||||
from .seed import preload_data, seed_admin
|
||||
|
||||
cli_commands = [
|
||||
preload_data,
|
||||
seed_admin
|
||||
]
|
||||
|
||||
def register_cli(app):
|
||||
for command in cli_commands:
|
||||
app.cli.add_command(command)
|
||||
|
@ -1,79 +0,0 @@
|
||||
import click
|
||||
from flask.cli import with_appcontext
|
||||
from datetime import datetime
|
||||
from app import db
|
||||
from app.models.user import (
|
||||
User, Plant, PlantCommonName, PlantScientificName, Submission, SubmissionImage
|
||||
)
|
||||
|
||||
@click.command("preload-data")
|
||||
@with_appcontext
|
||||
def preload_data():
|
||||
click.echo("[📦] Preloading demo data...")
|
||||
|
||||
if not User.query.filter_by(email="demo@example.com").first():
|
||||
demo_user = User(
|
||||
email="demo@example.com",
|
||||
password_hash="fakehash123", # you can hash a real one if needed
|
||||
role="user",
|
||||
is_verified=True
|
||||
)
|
||||
db.session.add(demo_user)
|
||||
db.session.commit()
|
||||
|
||||
common_entries = {
|
||||
"Monstera Albo": "Monstera deliciosa 'Albo-Variegata'",
|
||||
"Philodendron Pink Princess": "Philodendron erubescens",
|
||||
"Cebu Blue Pothos": "Epipremnum pinnatum"
|
||||
}
|
||||
|
||||
for common_name, sci_name in common_entries.items():
|
||||
common = PlantCommonName.query.filter_by(name=common_name).first()
|
||||
if not common:
|
||||
common = PlantCommonName(name=common_name)
|
||||
db.session.add(common)
|
||||
|
||||
scientific = PlantScientificName.query.filter_by(name=sci_name).first()
|
||||
if not scientific:
|
||||
scientific = PlantScientificName(name=sci_name)
|
||||
db.session.add(scientific)
|
||||
|
||||
db.session.commit()
|
||||
|
||||
common_map = {c.name: c.id for c in PlantCommonName.query.all()}
|
||||
scientific_map = {s.name: s.id for s in PlantScientificName.query.all()}
|
||||
demo_user = User.query.filter_by(email="demo@example.com").first()
|
||||
|
||||
plants = []
|
||||
for i in range(1, 4):
|
||||
plant = Plant(
|
||||
common_name_id=common_map["Monstera Albo"],
|
||||
scientific_name_id=scientific_map["Monstera deliciosa 'Albo-Variegata'"],
|
||||
created_by_user_id=demo_user.id
|
||||
)
|
||||
db.session.add(plant)
|
||||
plants.append(plant)
|
||||
|
||||
db.session.commit()
|
||||
|
||||
for plant in plants:
|
||||
for i in range(2):
|
||||
submission = Submission(
|
||||
user_id=demo_user.id,
|
||||
plant_id=plant.id,
|
||||
common_name="Monstera Albo",
|
||||
scientific_name="Monstera deliciosa 'Albo-Variegata'",
|
||||
price=85.0 + i * 15,
|
||||
source="Etsy",
|
||||
height=12 + i * 2,
|
||||
width=10 + i,
|
||||
potting_mix="Pumice:Bark:Coco 2:1:1",
|
||||
container_size="4 inch",
|
||||
health_status="Healthy",
|
||||
notes="Good variegation",
|
||||
timestamp=datetime.utcnow()
|
||||
)
|
||||
db.session.add(submission)
|
||||
|
||||
db.session.commit()
|
||||
click.echo("[✅] Demo data loaded.")
|
@ -1,20 +0,0 @@
|
||||
# plugins/cli/routes.py
|
||||
|
||||
import click
|
||||
from flask.cli import with_appcontext
|
||||
from app.extensions import db
|
||||
from plugins.plant.models import Plant
|
||||
|
||||
@click.command('preload-data')
|
||||
@with_appcontext
|
||||
def preload_data():
|
||||
"""Preloads plant data into the database."""
|
||||
if not Plant.query.first():
|
||||
db.session.add(Plant(name="Example Plant"))
|
||||
db.session.commit()
|
||||
click.echo("✅ Preloaded sample plant.")
|
||||
else:
|
||||
click.echo("ℹ️ Plant data already exists.")
|
||||
|
||||
# Export command(s) so __init__.py can register them
|
||||
cli_commands = [preload_data]
|
@ -1,26 +1,32 @@
|
||||
import click
|
||||
from flask.cli import with_appcontext
|
||||
from werkzeug.security import generate_password_hash
|
||||
from ..core.models import User
|
||||
from .. import db
|
||||
from plugins.plant.models import Plant
|
||||
from plugins.auth.models import User
|
||||
from app import db
|
||||
|
||||
@click.command("seed-admin")
|
||||
|
||||
@click.command('preload-data')
|
||||
@with_appcontext
|
||||
def preload_data():
|
||||
click.echo("Preloading data...")
|
||||
if not Plant.query.first():
|
||||
plant = Plant(name="Default Plant")
|
||||
db.session.add(plant)
|
||||
db.session.commit()
|
||||
click.echo("Default plant added.")
|
||||
else:
|
||||
click.echo("Plant data already exists.")
|
||||
|
||||
|
||||
@click.command('seed-admin')
|
||||
@with_appcontext
|
||||
def seed_admin():
|
||||
"""Seed a default admin user if none exists."""
|
||||
admin_email = "admin@example.com"
|
||||
admin_password = "admin123"
|
||||
|
||||
if User.query.filter_by(email=admin_email).first():
|
||||
click.echo("[ℹ] Admin user already exists.")
|
||||
return
|
||||
|
||||
user = User(
|
||||
email=admin_email,
|
||||
password_hash=generate_password_hash(admin_password),
|
||||
role="admin",
|
||||
is_verified=True
|
||||
)
|
||||
db.session.add(user)
|
||||
db.session.commit()
|
||||
click.echo(f"[✔] Created default admin: {admin_email}")
|
||||
click.echo("Seeding admin user...")
|
||||
if not User.query.filter_by(email='admin@example.com').first():
|
||||
user = User(email='admin@example.com', role='admin', is_verified=True)
|
||||
user.set_password('password') # Make sure this method exists in your model
|
||||
db.session.add(user)
|
||||
db.session.commit()
|
||||
click.echo("✅ Admin user created.")
|
||||
else:
|
||||
click.echo("ℹ️ Admin user already exists.")
|
||||
|
@ -1 +0,0 @@
|
||||
# core_ui media patch
|
15
plugins/core_ui/routes.py
Normal file
15
plugins/core_ui/routes.py
Normal file
@ -0,0 +1,15 @@
|
||||
from flask import Blueprint, render_template
|
||||
from flask_login import login_required, current_user
|
||||
|
||||
bp = Blueprint('core_ui', __name__, template_folder='templates')
|
||||
|
||||
@bp.route('/')
|
||||
def home():
|
||||
return render_template('core_ui/home.html')
|
||||
|
||||
@bp.route('/admin')
|
||||
@login_required
|
||||
def admin_dashboard():
|
||||
if current_user.role != 'admin':
|
||||
return "Access denied", 403
|
||||
return render_template('core_ui/admin_dashboard.html')
|
11
plugins/core_ui/templates/core_ui/admin_dashboard.html
Normal file
11
plugins/core_ui/templates/core_ui/admin_dashboard.html
Normal file
@ -0,0 +1,11 @@
|
||||
{% extends 'core_ui/base.html' %}
|
||||
{% block title %}Admin Dashboard | Nature In Pots{% endblock %}
|
||||
{% block content %}
|
||||
<h1 class="mb-4 text-danger">Admin Dashboard</h1>
|
||||
<p class="lead">Manage submissions, users, and plugin controls here.</p>
|
||||
<ul>
|
||||
<li><a href="#">View Unapproved Submissions</a></li>
|
||||
<li><a href="#">Manage Users</a></li>
|
||||
<li><a href="#">Export Data</a></li>
|
||||
</ul>
|
||||
{% endblock %}
|
69
plugins/core_ui/templates/core_ui/base.html
Normal file
69
plugins/core_ui/templates/core_ui/base.html
Normal file
@ -0,0 +1,69 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>{% block title %}Nature In Pots Community{% endblock %}</title>
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<style>
|
||||
body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 100vh;
|
||||
}
|
||||
main {
|
||||
flex: 1;
|
||||
}
|
||||
footer {
|
||||
background-color: #f8f9fa;
|
||||
padding: 1rem 0;
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<nav class="navbar navbar-expand-lg navbar-light bg-light mb-4">
|
||||
<div class="container">
|
||||
<a class="navbar-brand" href="{{ url_for('core_ui.home') }}">Nature In Pots</a>
|
||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<div class="collapse navbar-collapse" id="navbarNav">
|
||||
<ul class="navbar-nav me-auto">
|
||||
<li class="nav-item"><a class="nav-link" href="{{ url_for('core_ui.home') }}">Home</a></li>
|
||||
<li class="nav-item"><a class="nav-link" href="{{ url_for('plant.index') }}">Plants</a></li>
|
||||
{% if current_user.is_authenticated and current_user.role == 'admin' %}
|
||||
<li class="nav-item"><a class="nav-link text-danger" href="{{ url_for('core_ui.admin_dashboard') }}">Admin Dashboard</a></li>
|
||||
{% endif %}
|
||||
{% block plugin_links %}{% endblock %}
|
||||
</ul>
|
||||
<ul class="navbar-nav align-items-center">
|
||||
{% if current_user.is_authenticated %}
|
||||
<li class="nav-item ms-3">
|
||||
<a class="nav-link" href="{{ url_for('auth.logout') }}">Logout</a>
|
||||
</li>
|
||||
{% else %}
|
||||
<li class="nav-item ms-3"><a class="nav-link" href="{{ url_for('auth.login') }}">Login</a></li>
|
||||
<li class="nav-item"><a class="nav-link" href="{{ url_for('auth.register') }}">Register</a></li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
<main class="container">
|
||||
{% with messages = get_flashed_messages() %}
|
||||
{% if messages %}
|
||||
<div class="alert alert-warning">
|
||||
{% for message in messages %}
|
||||
<div>{{ message }}</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
{% block content %}{% endblock %}
|
||||
</main>
|
||||
<footer>
|
||||
© {{ current_year | default(2025) }} Nature In Pots Community. All rights reserved.
|
||||
</footer>
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
|
||||
</body>
|
||||
</html>
|
6
plugins/core_ui/templates/core_ui/home.html
Normal file
6
plugins/core_ui/templates/core_ui/home.html
Normal file
@ -0,0 +1,6 @@
|
||||
{% extends 'core_ui/base.html' %}
|
||||
{% block title %}Home | Nature In Pots{% endblock %}
|
||||
{% block content %}
|
||||
<h1 class="mb-4">Welcome to Nature In Pots 🌿</h1>
|
||||
<p>This is the community hub for plant tracking, propagation history, and price sharing.</p>
|
||||
{% endblock %}
|
@ -1,4 +1,4 @@
|
||||
{% extends 'base.html' %}
|
||||
{% extends 'core_ui/base.html' %}
|
||||
{% block content %}
|
||||
<h2>Add Log for Plant #{{ plant.id }}</h2>
|
||||
<form method="POST">
|
||||
|
@ -1,5 +1,5 @@
|
||||
{% import 'core_ui/_media_macros.html' as media %}
|
||||
{% extends 'base.html' %}
|
||||
{% extends 'core_ui/base.html' %}
|
||||
{% block content %}
|
||||
<h2>Logs for Plant #{{ plant.id }}</h2>
|
||||
<a href="{{ url_for('growlog.add_log', plant_id=plant.id) }}">Add New Log</a>
|
||||
|
@ -1,4 +1,4 @@
|
||||
{% extends 'base.html' %}
|
||||
{% extends 'core_ui/base.html' %}
|
||||
{% block content %}
|
||||
<h2>All Uploaded Media</h2>
|
||||
<ul>
|
||||
|
@ -1,4 +1,4 @@
|
||||
{% extends 'base.html' %}
|
||||
{% extends 'core_ui/base.html' %}
|
||||
{% block content %}
|
||||
<h2>Upload Media</h2>
|
||||
<form method="POST" enctype="multipart/form-data">
|
||||
|
@ -9,3 +9,113 @@ class Plant(db.Model):
|
||||
is_active = db.Column(db.Boolean, default=True)
|
||||
created_at = db.Column(db.DateTime, default=datetime.utcnow)
|
||||
updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
|
||||
|
||||
|
||||
class Submission(db.Model):
|
||||
__tablename__ = 'submission'
|
||||
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
user_id = db.Column(
|
||||
db.Integer,
|
||||
db.ForeignKey('users.id', name='fk_submission_user_id'),
|
||||
nullable=False
|
||||
)
|
||||
common_name = db.Column(db.String(120), nullable=False)
|
||||
scientific_name = db.Column(db.String(120))
|
||||
price = db.Column(db.Float, nullable=False)
|
||||
source = db.Column(db.String(120))
|
||||
timestamp = db.Column(db.DateTime, default=datetime.utcnow)
|
||||
height = db.Column(db.Float)
|
||||
width = db.Column(db.Float)
|
||||
leaf_count = db.Column(db.Integer)
|
||||
potting_mix = db.Column(db.String(255))
|
||||
container_size = db.Column(db.String(120))
|
||||
health_status = db.Column(db.String(50))
|
||||
notes = db.Column(db.Text)
|
||||
plant_id = db.Column(db.Integer)
|
||||
images = db.relationship('SubmissionImage', backref='submission', lazy=True)
|
||||
|
||||
|
||||
class SubmissionImage(db.Model):
|
||||
__tablename__ = 'submission_images'
|
||||
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
submission_id = db.Column(db.Integer, db.ForeignKey('submission.id'), nullable=False)
|
||||
file_path = db.Column(db.String(255), nullable=False)
|
||||
|
||||
|
||||
class PlantCommonName(db.Model):
|
||||
__tablename__ = 'plants_common'
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
name = db.Column(db.String(120), unique=True, nullable=False)
|
||||
|
||||
|
||||
class PlantScientificName(db.Model):
|
||||
__tablename__ = 'plants_scientific'
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
name = db.Column(db.String(255), unique=True, nullable=False)
|
||||
|
||||
|
||||
class Plant(db.Model):
|
||||
__tablename__ = 'plants'
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
common_name_id = db.Column(db.Integer, db.ForeignKey('plants_common.id'))
|
||||
scientific_name_id = db.Column(db.Integer, db.ForeignKey('plants_scientific.id'))
|
||||
parent_id = db.Column(db.Integer, db.ForeignKey('plants.id'), nullable=True)
|
||||
is_dead = db.Column(db.Boolean, default=False)
|
||||
date_added = db.Column(db.DateTime, default=datetime.utcnow)
|
||||
created_by_user_id = db.Column(db.Integer, db.ForeignKey('users.id'))
|
||||
|
||||
# Relationships
|
||||
updates = db.relationship('PlantUpdate', backref='plant', lazy=True)
|
||||
lineage = db.relationship('PlantLineage', backref='child', lazy=True,
|
||||
foreign_keys='PlantLineage.child_plant_id')
|
||||
|
||||
|
||||
class PlantLineage(db.Model):
|
||||
__tablename__ = 'plant_lineage'
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
parent_plant_id = db.Column(db.Integer, db.ForeignKey('plants.id'), nullable=False)
|
||||
child_plant_id = db.Column(db.Integer, db.ForeignKey('plants.id'), nullable=False)
|
||||
|
||||
|
||||
class PlantOwnershipLog(db.Model):
|
||||
__tablename__ = 'plant_ownership_log'
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
plant_id = db.Column(db.Integer, db.ForeignKey('plants.id'), nullable=False)
|
||||
user_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False)
|
||||
date_acquired = db.Column(db.DateTime, default=datetime.utcnow)
|
||||
date_relinquished = db.Column(db.DateTime, nullable=True)
|
||||
|
||||
|
||||
class PlantUpdate(db.Model):
|
||||
__tablename__ = 'plant_updates'
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
plant_id = db.Column(db.Integer, db.ForeignKey('plants.id'), nullable=False)
|
||||
update_type = db.Column(db.String(100))
|
||||
description = db.Column(db.Text)
|
||||
created_at = db.Column(db.DateTime, default=datetime.utcnow)
|
||||
images = db.relationship('UpdateImage', backref='update', lazy=True)
|
||||
|
||||
|
||||
class UpdateImage(db.Model):
|
||||
__tablename__ = 'update_images'
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
update_id = db.Column(db.Integer, db.ForeignKey('plant_updates.id'), nullable=False)
|
||||
file_path = db.Column(db.String(255), nullable=False)
|
||||
|
||||
|
||||
class ImageHeart(db.Model):
|
||||
__tablename__ = 'image_hearts'
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
user_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False)
|
||||
submission_image_id = db.Column(db.Integer, db.ForeignKey('submission_images.id'), nullable=False)
|
||||
created_at = db.Column(db.DateTime, default=datetime.utcnow)
|
||||
|
||||
|
||||
class FeaturedImage(db.Model):
|
||||
__tablename__ = 'featured_images'
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
submission_image_id = db.Column(db.Integer, db.ForeignKey('submission_images.id'), nullable=False)
|
||||
override_text = db.Column(db.String(255), nullable=True)
|
||||
is_featured = db.Column(db.Boolean, default=True)
|
||||
|
@ -1,4 +1,4 @@
|
||||
{% extends 'base.html' %}
|
||||
{% extends 'core_ui/base.html' %}
|
||||
{% block content %}
|
||||
<h2>Search Plants</h2>
|
||||
<form method="POST">
|
||||
|
@ -1,4 +1,4 @@
|
||||
{% extends 'base.html' %}
|
||||
{% extends 'core_ui/base.html' %}
|
||||
{% block content %}
|
||||
<h2>Search Results</h2>
|
||||
{% if results %}
|
||||
|
@ -1,4 +1,4 @@
|
||||
{% extends 'base.html' %}
|
||||
{% extends 'core_ui/base.html' %}
|
||||
{% block content %}
|
||||
<p>This page was replaced by AJAX functionality.</p>
|
||||
{% endblock %}
|
Reference in New Issue
Block a user