1468 lines
67 KiB
Python
1468 lines
67 KiB
Python
from flask import Flask, render_template, request, redirect, url_for, flash, session, jsonify
|
|
from flask_sqlalchemy import SQLAlchemy
|
|
from flask_migrate import Migrate
|
|
from datetime import datetime
|
|
from werkzeug.security import generate_password_hash, check_password_hash
|
|
from werkzeug.utils import secure_filename
|
|
import os
|
|
from config import Config
|
|
import random
|
|
|
|
app = Flask(__name__)
|
|
app.config.from_object(Config)
|
|
|
|
os.makedirs(app.config['UPLOAD_FOLDER'], exist_ok=True)
|
|
|
|
db = SQLAlchemy(app)
|
|
migrate = Migrate(app, db)
|
|
|
|
# Database Models
|
|
class User(db.Model):
|
|
id = db.Column(db.Integer, primary_key=True)
|
|
username = db.Column(db.String(80), unique=True, nullable=False)
|
|
password_hash = db.Column(db.String(256), nullable=False)
|
|
|
|
def set_password(self, password):
|
|
self.password_hash = generate_password_hash(password)
|
|
|
|
def check_password(self, password):
|
|
return check_password_hash(self.password_hash, password)
|
|
|
|
class Environment(db.Model):
|
|
id = db.Column(db.Integer, primary_key=True)
|
|
name = db.Column(db.String(100), unique=True, nullable=False)
|
|
description = db.Column(db.Text, nullable=True)
|
|
icon = db.Column(db.String(200), nullable=True)
|
|
|
|
def __repr__(self):
|
|
return f"Environment('{self.name}')"
|
|
|
|
class Climate(db.Model):
|
|
id = db.Column(db.Integer, primary_key=True)
|
|
name = db.Column(db.String(100), unique=True, nullable=False)
|
|
description = db.Column(db.Text, nullable=True)
|
|
icon = db.Column(db.String(200), nullable=True)
|
|
|
|
def __repr__(self):
|
|
return f"Climate('{self.name}')"
|
|
|
|
class Product(db.Model):
|
|
id = db.Column(db.Integer, primary_key=True)
|
|
name = db.Column(db.String(100), unique=True, nullable=False)
|
|
description = db.Column(db.Text, nullable=True)
|
|
|
|
def __repr__(self):
|
|
return f"Product('{self.name}')"
|
|
|
|
class Section(db.Model):
|
|
id = db.Column(db.Integer, primary_key=True)
|
|
name = db.Column(db.String(50), nullable=False, unique=True)
|
|
description = db.Column(db.Text, nullable=True)
|
|
icon = db.Column(db.String(200), nullable=True)
|
|
|
|
def __repr__(self):
|
|
return f"Section('{self.name}')"
|
|
|
|
class Plant(db.Model):
|
|
id = db.Column(db.Integer, primary_key=True)
|
|
name = db.Column(db.String(100), nullable=False)
|
|
picture = db.Column(db.String(200), nullable=True)
|
|
climate_id = db.Column(db.Integer, db.ForeignKey('climate.id'), nullable=True)
|
|
environment_id = db.Column(db.Integer, db.ForeignKey('environment.id'), nullable=True)
|
|
light_id = db.Column(db.Integer, db.ForeignKey('light.id'), nullable=True)
|
|
toxicity_id = db.Column(db.Integer, db.ForeignKey('toxicity.id'), nullable=True)
|
|
size_id = db.Column(db.Integer, db.ForeignKey('size.id'), nullable=True)
|
|
care_difficulty_id = db.Column(db.Integer, db.ForeignKey('care_difficulty.id'), nullable=True)
|
|
growth_rate_id = db.Column(db.Integer, db.ForeignKey('growth_rate.id'), nullable=True)
|
|
# Make section_id nullable and handle missing column
|
|
try:
|
|
section_id = db.Column(db.Integer, db.ForeignKey('section.id'), nullable=True)
|
|
except Exception:
|
|
section_id = None
|
|
products = db.Column(db.Text, nullable=True) # Will store product IDs as comma-separated values
|
|
description = db.Column(db.Text, nullable=True)
|
|
date_added = db.Column(db.DateTime, nullable=False, default=datetime.utcnow)
|
|
care_guide = db.Column(db.Text, nullable=True) # New rich text care guide
|
|
|
|
climate = db.relationship('Climate', backref='plants')
|
|
environment = db.relationship('Environment', backref='plants')
|
|
light = db.relationship('Light', backref='plant_light')
|
|
toxicity = db.relationship('Toxicity', backref='plant_toxicity')
|
|
size = db.relationship('Size', backref='plant_size')
|
|
care_difficulty = db.relationship('CareDifficulty', backref='plant_care_difficulty')
|
|
growth_rate = db.relationship('GrowthRate', backref='plant_growth_rate')
|
|
# Make section relationship optional
|
|
try:
|
|
section = db.relationship('Section', backref='plants')
|
|
except Exception:
|
|
section = None
|
|
|
|
def __repr__(self):
|
|
return f"Plant('{self.name}', '{self.date_added}')"
|
|
|
|
class Light(db.Model):
|
|
id = db.Column(db.Integer, primary_key=True)
|
|
name = db.Column(db.String(50), nullable=False, unique=True)
|
|
description = db.Column(db.Text, nullable=True)
|
|
icon = db.Column(db.String(200), nullable=True)
|
|
|
|
def __repr__(self):
|
|
return f"Light('{self.name}')"
|
|
|
|
class Toxicity(db.Model):
|
|
id = db.Column(db.Integer, primary_key=True)
|
|
name = db.Column(db.String(50), nullable=False, unique=True)
|
|
description = db.Column(db.Text, nullable=True)
|
|
icon = db.Column(db.String(200), nullable=True)
|
|
|
|
def __repr__(self):
|
|
return f"Toxicity('{self.name}')"
|
|
|
|
class Size(db.Model):
|
|
id = db.Column(db.Integer, primary_key=True)
|
|
name = db.Column(db.String(50), nullable=False, unique=True)
|
|
description = db.Column(db.Text, nullable=True)
|
|
icon = db.Column(db.String(200), nullable=True)
|
|
|
|
def __repr__(self):
|
|
return f"Size('{self.name}')"
|
|
|
|
class CareDifficulty(db.Model):
|
|
id = db.Column(db.Integer, primary_key=True)
|
|
name = db.Column(db.String(50), nullable=False, unique=True)
|
|
description = db.Column(db.Text, nullable=True)
|
|
icon = db.Column(db.String(200), nullable=True)
|
|
|
|
def __repr__(self):
|
|
return f"CareDifficulty('{self.name}')"
|
|
|
|
class GrowthRate(db.Model):
|
|
id = db.Column(db.Integer, primary_key=True)
|
|
name = db.Column(db.String(50), nullable=False, unique=True)
|
|
description = db.Column(db.Text, nullable=True)
|
|
icon = db.Column(db.String(200), nullable=True)
|
|
|
|
def __repr__(self):
|
|
return f"GrowthRate('{self.name}')"
|
|
|
|
# Helper function
|
|
def is_logged_in():
|
|
return session.get('user_id') is not None
|
|
|
|
@app.context_processor
|
|
def inject_is_logged_in():
|
|
return dict(is_logged_in=is_logged_in())
|
|
|
|
@app.context_processor
|
|
def inject_admin_counts():
|
|
try:
|
|
environment_count = Environment.query.count()
|
|
climate_count = Climate.query.count()
|
|
product_count = Product.query.count()
|
|
plant_count = Plant.query.count()
|
|
section_count = Section.query.count()
|
|
except Exception:
|
|
environment_count = climate_count = product_count = plant_count = section_count = 0
|
|
return dict(
|
|
environment_count=environment_count,
|
|
climate_count=climate_count,
|
|
product_count=product_count,
|
|
plant_count=plant_count,
|
|
section_count=section_count
|
|
)
|
|
|
|
def none_if_empty(val):
|
|
return val if val not in (None, '', 'None') else None
|
|
|
|
# Routes
|
|
@app.route('/')
|
|
def home():
|
|
search = request.args.get('search', '').strip()
|
|
climate_id = request.args.get('climate')
|
|
environment_id = request.args.get('environment')
|
|
light_id = request.args.get('light')
|
|
toxicity_id = request.args.get('toxicity')
|
|
size_id = request.args.get('size')
|
|
care_difficulty_id = request.args.get('care_difficulty')
|
|
growth_rate_id = request.args.get('growth_rate')
|
|
section_id = request.args.get('section')
|
|
|
|
query = Plant.query
|
|
if search:
|
|
query = query.filter(Plant.name.ilike(f'%{search}%'))
|
|
if climate_id:
|
|
query = query.filter(Plant.climate_id == climate_id)
|
|
if environment_id:
|
|
query = query.filter(Plant.environment_id == environment_id)
|
|
if light_id:
|
|
query = query.filter(Plant.light_id == light_id)
|
|
if toxicity_id:
|
|
query = query.filter(Plant.toxicity_id == toxicity_id)
|
|
if size_id:
|
|
query = query.filter(Plant.size_id == size_id)
|
|
if care_difficulty_id:
|
|
query = query.filter(Plant.care_difficulty_id == care_difficulty_id)
|
|
if growth_rate_id:
|
|
query = query.filter(Plant.growth_rate_id == growth_rate_id)
|
|
if section_id:
|
|
query = query.filter(Plant.section_id == section_id)
|
|
plants = query.order_by(Plant.date_added.desc()).all()
|
|
|
|
climates_dict = {c.id: {'name': c.name, 'icon': c.icon, 'description': c.description} for c in Climate.query.all()}
|
|
environments_dict = {e.id: {'name': e.name, 'icon': e.icon, 'description': e.description} for e in Environment.query.all()}
|
|
products_dict = {p.id: p.name for p in Product.query.all()}
|
|
|
|
display_plants = []
|
|
for plant in plants:
|
|
product_names = []
|
|
if plant.products:
|
|
for pid in plant.products.split(','):
|
|
pid = pid.strip()
|
|
if pid.isdigit() and int(pid) in products_dict:
|
|
product_names.append(products_dict[int(pid)])
|
|
climate_info = climates_dict.get(plant.climate_id, {'name': '', 'icon': None, 'description': ''})
|
|
environment_info = environments_dict.get(plant.environment_id, {'name': '', 'icon': None, 'description': ''})
|
|
|
|
display_plants.append({
|
|
'id': plant.id,
|
|
'name': plant.name,
|
|
'picture': plant.picture,
|
|
'climate': climate_info['name'],
|
|
'climate_icon': climate_info['icon'],
|
|
'climate_description': climate_info['description'],
|
|
'environment': environment_info['name'],
|
|
'environment_icon': environment_info['icon'],
|
|
'environment_description': environment_info['description'],
|
|
'products': ', '.join(product_names),
|
|
'description': plant.description,
|
|
'date_added': plant.date_added,
|
|
'light': plant.light.name if plant.light else '',
|
|
'light_icon': plant.light.icon if plant.light and plant.light.icon else None,
|
|
'light_description': plant.light.description if plant.light else '',
|
|
'toxicity': plant.toxicity.name if plant.toxicity else '',
|
|
'toxicity_icon': plant.toxicity.icon if plant.toxicity and plant.toxicity.icon else None,
|
|
'toxicity_description': plant.toxicity.description if plant.toxicity else '',
|
|
'size': plant.size.name if plant.size else '',
|
|
'size_icon': plant.size.icon if plant.size and plant.size.icon else None,
|
|
'size_description': plant.size.description if plant.size else '',
|
|
'care_difficulty': plant.care_difficulty.name if plant.care_difficulty else '',
|
|
'care_difficulty_icon': plant.care_difficulty.icon if plant.care_difficulty and plant.care_difficulty.icon else None,
|
|
'care_difficulty_description': plant.care_difficulty.description if plant.care_difficulty else '',
|
|
'growth_rate': plant.growth_rate.name if plant.growth_rate else '',
|
|
'growth_rate_icon': plant.growth_rate.icon if plant.growth_rate and plant.growth_rate.icon else None,
|
|
'growth_rate_description': plant.growth_rate.description if plant.growth_rate else '',
|
|
'section': plant.section
|
|
})
|
|
|
|
return render_template('home.html',
|
|
plants=display_plants,
|
|
climates=Climate.query.all(),
|
|
environments=Environment.query.all(),
|
|
lights=Light.query.all(),
|
|
toxicities=Toxicity.query.all(),
|
|
sizes=Size.query.all(),
|
|
difficulties=CareDifficulty.query.all(),
|
|
growth_rates=GrowthRate.query.all(),
|
|
sections=Section.query.all(),
|
|
search=search,
|
|
selected_climate=climate_id,
|
|
selected_environment=environment_id,
|
|
selected_light=light_id,
|
|
selected_toxicity=toxicity_id,
|
|
selected_size=size_id,
|
|
selected_care_difficulty=care_difficulty_id,
|
|
selected_growth_rate=growth_rate_id,
|
|
selected_section=section_id)
|
|
|
|
@app.route('/login', methods=['GET', 'POST'])
|
|
def login():
|
|
if request.method == 'POST':
|
|
username = request.form['username']
|
|
password = request.form['password']
|
|
user = User.query.filter_by(username=username).first()
|
|
if user and user.check_password(password):
|
|
session['user_id'] = user.id
|
|
flash('Logged in successfully!', 'success')
|
|
return redirect(url_for('manage_environments'))
|
|
else:
|
|
flash('Invalid username or password', 'danger')
|
|
return render_template('login.html')
|
|
|
|
@app.route('/logout')
|
|
def logout():
|
|
session.pop('user_id', None)
|
|
flash('Logged out successfully.', 'success')
|
|
return redirect(url_for('home'))
|
|
|
|
# Environment management routes
|
|
@app.route('/admin/environments', methods=['GET', 'POST'])
|
|
def manage_environments():
|
|
if not is_logged_in():
|
|
return redirect(url_for('login'))
|
|
if request.method == 'POST':
|
|
name = request.form['name']
|
|
description = request.form['description']
|
|
icon_file = request.files.get('icon')
|
|
icon_filename = None
|
|
if not name:
|
|
flash('Name is required!', 'danger')
|
|
print('No name provided')
|
|
return redirect(url_for('manage_environments'))
|
|
if Environment.query.filter_by(name=name).first():
|
|
flash('Environment with this name already exists!', 'danger')
|
|
print('Duplicate environment name')
|
|
return redirect(url_for('manage_environments'))
|
|
if icon_file and icon_file.filename:
|
|
icon_filename = secure_filename(icon_file.filename)
|
|
icon_path = os.path.join('static/icons', icon_filename)
|
|
os.makedirs('static/icons', exist_ok=True)
|
|
try:
|
|
icon_file.save(icon_path)
|
|
except Exception as e:
|
|
print('Error saving icon:', e)
|
|
flash(f'Error saving icon: {e}', 'danger')
|
|
try:
|
|
environment = Environment(name=name, description=description, icon=icon_filename)
|
|
db.session.add(environment)
|
|
db.session.commit()
|
|
flash('Environment added successfully!', 'success')
|
|
print('Environment added:', name)
|
|
except Exception as e:
|
|
db.session.rollback()
|
|
print('Error adding environment:', e)
|
|
flash(f'Error adding environment: {e}', 'danger')
|
|
return redirect(url_for('manage_environments'))
|
|
environments = Environment.query.all()
|
|
return render_template('manage_environments.html',
|
|
environments=environments,
|
|
plant_count=len(Plant.query.all()),
|
|
climate_count=len(Climate.query.all()),
|
|
environment_count=len(environments),
|
|
product_count=len(Product.query.all()),
|
|
light_count=len(Light.query.all()),
|
|
toxicity_count=len(Toxicity.query.all()),
|
|
size_count=len(Size.query.all()),
|
|
difficulty_count=len(CareDifficulty.query.all()),
|
|
rate_count=len(GrowthRate.query.all()))
|
|
|
|
@app.route('/admin/environments/edit/<int:environment_id>', methods=['GET', 'POST'])
|
|
def edit_environment(environment_id):
|
|
if not is_logged_in():
|
|
return redirect(url_for('login'))
|
|
environment = Environment.query.get_or_404(environment_id)
|
|
if request.method == 'POST':
|
|
environment.name = request.form['name']
|
|
environment.description = request.form['description']
|
|
icon_file = request.files.get('icon')
|
|
if icon_file and icon_file.filename:
|
|
icon_filename = secure_filename(icon_file.filename)
|
|
icon_path = os.path.join('static/icons', icon_filename)
|
|
os.makedirs('static/icons', exist_ok=True)
|
|
try:
|
|
icon_file.save(icon_path)
|
|
except Exception as e:
|
|
print('Error saving icon:', e)
|
|
environment.icon = icon_filename
|
|
db.session.commit()
|
|
flash('Environment updated successfully!', 'success')
|
|
return redirect(url_for('manage_environments'))
|
|
return render_template('edit_environment.html', environment=environment)
|
|
|
|
@app.route('/admin/environments/delete/<int:environment_id>', methods=['POST'])
|
|
def delete_environment(environment_id):
|
|
if not is_logged_in():
|
|
return redirect(url_for('login'))
|
|
environment = Environment.query.get_or_404(environment_id)
|
|
db.session.delete(environment)
|
|
db.session.commit()
|
|
flash('Environment deleted successfully!', 'success')
|
|
return redirect(url_for('manage_environments'))
|
|
|
|
# Climate management routes
|
|
@app.route('/admin/climates', methods=['GET', 'POST'])
|
|
def manage_climates():
|
|
if not is_logged_in():
|
|
return redirect(url_for('login'))
|
|
if request.method == 'POST':
|
|
name = request.form['name']
|
|
description = request.form['description']
|
|
icon_file = request.files.get('icon')
|
|
icon_filename = None
|
|
if not name:
|
|
flash('Name is required!', 'danger')
|
|
print('No name provided')
|
|
return redirect(url_for('manage_climates'))
|
|
if Climate.query.filter_by(name=name).first():
|
|
flash('Climate with this name already exists!', 'danger')
|
|
print('Duplicate climate name')
|
|
return redirect(url_for('manage_climates'))
|
|
if icon_file and icon_file.filename:
|
|
icon_filename = secure_filename(icon_file.filename)
|
|
icon_path = os.path.join('static/icons', icon_filename)
|
|
os.makedirs('static/icons', exist_ok=True)
|
|
try:
|
|
icon_file.save(icon_path)
|
|
except Exception as e:
|
|
print('Error saving icon:', e)
|
|
flash(f'Error saving icon: {e}', 'danger')
|
|
try:
|
|
climate = Climate(name=name, description=description, icon=icon_filename)
|
|
db.session.add(climate)
|
|
db.session.commit()
|
|
flash('Climate added successfully!', 'success')
|
|
print('Climate added:', name)
|
|
except Exception as e:
|
|
db.session.rollback()
|
|
print('Error adding climate:', e)
|
|
flash(f'Error adding climate: {e}', 'danger')
|
|
return redirect(url_for('manage_climates'))
|
|
climates = Climate.query.all()
|
|
return render_template('manage_climates.html',
|
|
climates=climates,
|
|
plant_count=len(Plant.query.all()),
|
|
climate_count=len(climates),
|
|
environment_count=len(Environment.query.all()),
|
|
product_count=len(Product.query.all()),
|
|
light_count=len(Light.query.all()),
|
|
toxicity_count=len(Toxicity.query.all()),
|
|
size_count=len(Size.query.all()),
|
|
difficulty_count=len(CareDifficulty.query.all()),
|
|
rate_count=len(GrowthRate.query.all()))
|
|
|
|
@app.route('/admin/climates/edit/<int:climate_id>', methods=['GET', 'POST'])
|
|
def edit_climate(climate_id):
|
|
if not is_logged_in():
|
|
return redirect(url_for('login'))
|
|
climate = Climate.query.get_or_404(climate_id)
|
|
if request.method == 'POST':
|
|
climate.name = request.form['name']
|
|
climate.description = request.form['description']
|
|
icon_file = request.files.get('icon')
|
|
if icon_file and icon_file.filename:
|
|
icon_filename = secure_filename(icon_file.filename)
|
|
icon_path = os.path.join('static/icons', icon_filename)
|
|
os.makedirs('static/icons', exist_ok=True)
|
|
try:
|
|
icon_file.save(icon_path)
|
|
except Exception as e:
|
|
print('Error saving icon:', e)
|
|
climate.icon = icon_filename
|
|
db.session.commit()
|
|
flash('Climate updated successfully!', 'success')
|
|
return redirect(url_for('manage_climates'))
|
|
return render_template('edit_climate.html', climate=climate)
|
|
|
|
@app.route('/admin/climates/delete/<int:climate_id>', methods=['POST'])
|
|
def delete_climate(climate_id):
|
|
if not is_logged_in():
|
|
return redirect(url_for('login'))
|
|
climate = Climate.query.get_or_404(climate_id)
|
|
db.session.delete(climate)
|
|
db.session.commit()
|
|
flash('Climate deleted successfully!', 'success')
|
|
return redirect(url_for('manage_climates'))
|
|
|
|
# Product management routes
|
|
@app.route('/admin/products', methods=['GET', 'POST'])
|
|
def manage_products():
|
|
if not is_logged_in():
|
|
return redirect(url_for('login'))
|
|
|
|
if request.method == 'POST':
|
|
name = request.form['name']
|
|
description = request.form['description']
|
|
product = Product(name=name, description=description)
|
|
db.session.add(product)
|
|
db.session.commit()
|
|
flash('Product added successfully!', 'success')
|
|
return redirect(url_for('manage_products'))
|
|
|
|
products = Product.query.all()
|
|
return render_template('manage_products.html',
|
|
products=products,
|
|
plant_count=len(Plant.query.all()),
|
|
climate_count=len(Climate.query.all()),
|
|
environment_count=len(Environment.query.all()),
|
|
product_count=len(products),
|
|
light_count=len(Light.query.all()),
|
|
toxicity_count=len(Toxicity.query.all()),
|
|
size_count=len(Size.query.all()),
|
|
difficulty_count=len(CareDifficulty.query.all()),
|
|
rate_count=len(GrowthRate.query.all()))
|
|
|
|
@app.route('/admin/products/edit/<int:product_id>', methods=['GET', 'POST'])
|
|
def edit_product(product_id):
|
|
if not is_logged_in():
|
|
return redirect(url_for('login'))
|
|
product = Product.query.get_or_404(product_id)
|
|
if request.method == 'POST':
|
|
product.name = request.form['name']
|
|
product.description = request.form['description']
|
|
db.session.commit()
|
|
flash('Product updated successfully!', 'success')
|
|
return redirect(url_for('manage_products'))
|
|
return render_template('edit_product.html', product=product)
|
|
|
|
@app.route('/admin/products/delete/<int:product_id>', methods=['POST'])
|
|
def delete_product(product_id):
|
|
if not is_logged_in():
|
|
return redirect(url_for('login'))
|
|
product = Product.query.get_or_404(product_id)
|
|
db.session.delete(product)
|
|
db.session.commit()
|
|
flash('Product deleted successfully!', 'success')
|
|
return redirect(url_for('manage_products'))
|
|
|
|
@app.route('/plant/new', methods=['GET', 'POST'])
|
|
def new_plant():
|
|
if not is_logged_in():
|
|
flash('You must be logged in as admin to add a plant.', 'danger')
|
|
return redirect(url_for('login'))
|
|
|
|
if request.method == 'POST':
|
|
name = request.form['name']
|
|
climate_id = request.form['climate_id']
|
|
environment_id = request.form['environment_id']
|
|
light_id = request.form.get('light_id')
|
|
toxicity_id = request.form.get('toxicity_id')
|
|
size_id = request.form.get('size_id')
|
|
care_difficulty_id = request.form.get('care_difficulty_id')
|
|
growth_rate_id = request.form.get('growth_rate_id')
|
|
product_ids = request.form.getlist('product_ids')
|
|
description = request.form['description']
|
|
care_guide = request.form.get('care_guide')
|
|
picture_file = request.files.get('picture')
|
|
picture_filename = None
|
|
|
|
# Required field checks
|
|
if not name:
|
|
flash('Name is required!', 'danger')
|
|
print('No name provided')
|
|
return redirect(url_for('new_plant'))
|
|
if not climate_id:
|
|
flash('Climate is required!', 'danger')
|
|
print('No climate provided')
|
|
return redirect(url_for('new_plant'))
|
|
if not environment_id:
|
|
flash('Environment is required!', 'danger')
|
|
print('No environment provided')
|
|
return redirect(url_for('new_plant'))
|
|
if Plant.query.filter_by(name=name).first():
|
|
flash('A plant with this name already exists!', 'danger')
|
|
print('Duplicate plant name')
|
|
return redirect(url_for('new_plant'))
|
|
|
|
if picture_file and picture_file.filename:
|
|
filename = secure_filename(picture_file.filename)
|
|
picture_path = os.path.join(app.config['UPLOAD_FOLDER'], filename)
|
|
try:
|
|
picture_file.save(picture_path)
|
|
picture_filename = filename
|
|
except Exception as e:
|
|
print('Error saving plant picture:', e)
|
|
flash(f'Error saving plant picture: {e}', 'danger')
|
|
|
|
try:
|
|
plant = Plant(
|
|
name=name,
|
|
picture=picture_filename,
|
|
climate_id=none_if_empty(climate_id),
|
|
environment_id=none_if_empty(environment_id),
|
|
light_id=none_if_empty(light_id),
|
|
toxicity_id=none_if_empty(toxicity_id),
|
|
size_id=none_if_empty(size_id),
|
|
care_difficulty_id=none_if_empty(care_difficulty_id),
|
|
growth_rate_id=none_if_empty(growth_rate_id),
|
|
products=','.join(product_ids),
|
|
description=description,
|
|
care_guide=care_guide
|
|
)
|
|
db.session.add(plant)
|
|
db.session.commit()
|
|
flash('Your plant has been added!', 'success')
|
|
print('Plant added:', name)
|
|
return redirect(url_for('manage_plants', add=1))
|
|
except Exception as e:
|
|
db.session.rollback()
|
|
print('Error adding plant:', e)
|
|
flash(f'Error adding plant: {e}', 'danger')
|
|
return redirect(url_for('new_plant'))
|
|
|
|
climates = Climate.query.all()
|
|
environments = Environment.query.all()
|
|
products = Product.query.all()
|
|
lights = Light.query.order_by(Light.name).all()
|
|
toxicities = Toxicity.query.order_by(Toxicity.name).all()
|
|
sizes = Size.query.order_by(Size.name).all()
|
|
difficulties = CareDifficulty.query.order_by(CareDifficulty.name).all()
|
|
growth_rates = GrowthRate.query.order_by(GrowthRate.name).all()
|
|
|
|
return render_template('create_plant.html',
|
|
climates=climates,
|
|
environments=environments,
|
|
products=products,
|
|
lights=lights,
|
|
toxicities=toxicities,
|
|
sizes=sizes,
|
|
difficulties=difficulties,
|
|
growth_rates=growth_rates)
|
|
|
|
@app.route('/plant/<int:plant_id>')
|
|
def plant(plant_id):
|
|
plant = Plant.query.get_or_404(plant_id)
|
|
product_ids = plant.products.split(',') if plant.products else []
|
|
products = Product.query.filter(Product.id.in_(product_ids)).all() if product_ids else []
|
|
return render_template('post.html', plant=plant, products=products)
|
|
|
|
@app.route('/plant/edit/<int:plant_id>', methods=['GET', 'POST'])
|
|
def edit_plant(plant_id):
|
|
if not is_logged_in():
|
|
flash('You must be logged in as admin to edit a plant.', 'danger')
|
|
return redirect(url_for('login'))
|
|
plant = Plant.query.get_or_404(plant_id)
|
|
climates = Climate.query.all()
|
|
environments = Environment.query.all()
|
|
products = Product.query.all()
|
|
lights = Light.query.order_by(Light.name).all()
|
|
toxicities = Toxicity.query.order_by(Toxicity.name).all()
|
|
sizes = Size.query.order_by(Size.name).all()
|
|
difficulties = CareDifficulty.query.order_by(CareDifficulty.name).all()
|
|
growth_rates = GrowthRate.query.order_by(GrowthRate.name).all()
|
|
sections = Section.query.order_by(Section.name).all()
|
|
|
|
if request.method == 'POST':
|
|
plant.name = request.form['name']
|
|
plant.climate_id = request.form['climate_id']
|
|
plant.environment_id = request.form['environment_id']
|
|
plant.light_id = request.form.get('light_id')
|
|
plant.toxicity_id = request.form.get('toxicity_id')
|
|
plant.size_id = request.form.get('size_id')
|
|
plant.care_difficulty_id = request.form.get('care_difficulty_id')
|
|
plant.growth_rate_id = request.form.get('growth_rate_id')
|
|
plant.section_id = request.form.get('section_id')
|
|
plant.products = ','.join(request.form.getlist('product_ids'))
|
|
plant.description = request.form['description']
|
|
plant.care_guide = request.form.get('care_guide')
|
|
picture_file = request.files.get('picture')
|
|
if picture_file and picture_file.filename:
|
|
filename = secure_filename(picture_file.filename)
|
|
picture_path = os.path.join(app.config['UPLOAD_FOLDER'], filename)
|
|
os.makedirs('static/icons', exist_ok=True)
|
|
try:
|
|
picture_file.save(picture_path)
|
|
except Exception as e:
|
|
print('Error saving icon:', e)
|
|
plant.picture = filename
|
|
db.session.commit()
|
|
flash('Plant updated successfully!', 'success')
|
|
return redirect(url_for('plant', plant_id=plant.id))
|
|
selected_products = plant.products.split(',') if plant.products else []
|
|
return render_template('edit_plant.html',
|
|
plant=plant,
|
|
climates=climates,
|
|
environments=environments,
|
|
products=products,
|
|
selected_products=selected_products,
|
|
lights=lights,
|
|
toxicities=toxicities,
|
|
sizes=sizes,
|
|
difficulties=difficulties,
|
|
growth_rates=growth_rates,
|
|
sections=sections)
|
|
|
|
@app.route('/plant/delete/<int:plant_id>', methods=['POST'])
|
|
def delete_plant(plant_id):
|
|
if not is_logged_in():
|
|
flash('You must be logged in as admin to delete a plant.', 'danger')
|
|
return redirect(url_for('login'))
|
|
plant = Plant.query.get_or_404(plant_id)
|
|
db.session.delete(plant)
|
|
db.session.commit()
|
|
flash('Plant deleted successfully!', 'success')
|
|
return redirect(url_for('manage_plants'))
|
|
|
|
@app.route('/admin/plants')
|
|
def manage_plants():
|
|
plants = Plant.query.all()
|
|
climates = Climate.query.all()
|
|
environments = Environment.query.all()
|
|
products = Product.query.all()
|
|
lights = Light.query.order_by(Light.name).all()
|
|
toxicities = Toxicity.query.order_by(Toxicity.name).all()
|
|
sizes = Size.query.order_by(Size.name).all()
|
|
difficulties = CareDifficulty.query.order_by(CareDifficulty.name).all()
|
|
growth_rates = GrowthRate.query.order_by(GrowthRate.name).all()
|
|
|
|
# Create dictionaries for easy lookup
|
|
climates_dict = {climate.id: climate.name for climate in climates}
|
|
environments_dict = {env.id: env.name for env in environments}
|
|
|
|
return render_template('manage_plants.html',
|
|
plants=plants,
|
|
climates=climates, # list for form
|
|
environments=environments, # list for form
|
|
climates_dict=climates_dict, # dict for table
|
|
environments_dict=environments_dict, # dict for table
|
|
products=products,
|
|
lights=lights,
|
|
toxicities=toxicities,
|
|
sizes=sizes,
|
|
difficulties=difficulties,
|
|
growth_rates=growth_rates,
|
|
plant_count=len(plants),
|
|
climate_count=len(climates),
|
|
environment_count=len(environments),
|
|
product_count=len(products),
|
|
light_count=len(lights),
|
|
toxicity_count=len(toxicities),
|
|
size_count=len(sizes),
|
|
difficulty_count=len(difficulties),
|
|
rate_count=len(growth_rates))
|
|
|
|
@app.route('/admin/attributes')
|
|
def manage_attributes():
|
|
if not is_logged_in():
|
|
return redirect(url_for('login'))
|
|
|
|
lights = Light.query.order_by(Light.name).all()
|
|
toxicities = Toxicity.query.order_by(Toxicity.name).all()
|
|
sizes = Size.query.order_by(Size.name).all()
|
|
difficulties = CareDifficulty.query.order_by(CareDifficulty.name).all()
|
|
growth_rates = GrowthRate.query.order_by(GrowthRate.name).all()
|
|
|
|
return render_template('manage_attributes.html',
|
|
lights=lights,
|
|
toxicities=toxicities,
|
|
sizes=sizes,
|
|
difficulties=difficulties,
|
|
growth_rates=growth_rates)
|
|
|
|
# Light management routes
|
|
@app.route('/admin/lights', methods=['GET', 'POST'])
|
|
def manage_lights():
|
|
if not is_logged_in():
|
|
return redirect(url_for('login'))
|
|
if request.method == 'POST':
|
|
name = request.form['name']
|
|
description = request.form['description']
|
|
icon_file = request.files.get('icon')
|
|
icon_filename = None
|
|
if not name:
|
|
flash('Name is required!', 'danger')
|
|
print('No name provided')
|
|
return redirect(url_for('manage_lights'))
|
|
if Light.query.filter_by(name=name).first():
|
|
flash('Light requirement with this name already exists!', 'danger')
|
|
print('Duplicate light name')
|
|
return redirect(url_for('manage_lights'))
|
|
if icon_file and icon_file.filename:
|
|
icon_filename = secure_filename(icon_file.filename)
|
|
icon_path = os.path.join('static/icons', icon_filename)
|
|
os.makedirs('static/icons', exist_ok=True)
|
|
try:
|
|
icon_file.save(icon_path)
|
|
except Exception as e:
|
|
print('Error saving icon:', e)
|
|
flash(f'Error saving icon: {e}', 'danger')
|
|
try:
|
|
light = Light(name=name, description=description, icon=icon_filename)
|
|
db.session.add(light)
|
|
db.session.commit()
|
|
flash('Light requirement added successfully!', 'success')
|
|
print('Light added:', name)
|
|
except Exception as e:
|
|
db.session.rollback()
|
|
print('Error adding light:', e)
|
|
flash(f'Error adding light: {e}', 'danger')
|
|
return redirect(url_for('manage_lights'))
|
|
lights = Light.query.all()
|
|
return render_template('manage_lights.html',
|
|
lights=lights,
|
|
plant_count=len(Plant.query.all()),
|
|
climate_count=len(Climate.query.all()),
|
|
environment_count=len(Environment.query.all()),
|
|
product_count=len(Product.query.all()),
|
|
light_count=len(lights),
|
|
toxicity_count=len(Toxicity.query.all()),
|
|
size_count=len(Size.query.all()),
|
|
difficulty_count=len(CareDifficulty.query.all()),
|
|
rate_count=len(GrowthRate.query.all()))
|
|
|
|
@app.route('/admin/lights/edit/<int:light_id>', methods=['GET', 'POST'])
|
|
def edit_light(light_id):
|
|
if not is_logged_in():
|
|
return redirect(url_for('login'))
|
|
light = Light.query.get_or_404(light_id)
|
|
if request.method == 'POST':
|
|
light.name = request.form['name']
|
|
light.description = request.form['description']
|
|
icon_file = request.files.get('icon')
|
|
if icon_file and icon_file.filename:
|
|
icon_filename = secure_filename(icon_file.filename)
|
|
icon_path = os.path.join('static/icons', icon_filename)
|
|
os.makedirs('static/icons', exist_ok=True)
|
|
try:
|
|
icon_file.save(icon_path)
|
|
except Exception as e:
|
|
print('Error saving icon:', e)
|
|
light.icon = icon_filename
|
|
db.session.commit()
|
|
flash('Light requirement updated successfully!', 'success')
|
|
return redirect(url_for('manage_lights'))
|
|
return render_template('edit_light.html', light=light)
|
|
|
|
@app.route('/admin/lights/delete/<int:light_id>', methods=['POST'])
|
|
def delete_light(light_id):
|
|
if not is_logged_in():
|
|
return redirect(url_for('login'))
|
|
light = Light.query.get_or_404(light_id)
|
|
db.session.delete(light)
|
|
db.session.commit()
|
|
flash('Light requirement deleted successfully!', 'success')
|
|
return redirect(url_for('manage_lights'))
|
|
|
|
# Toxicity management routes
|
|
@app.route('/admin/toxicities', methods=['GET', 'POST'])
|
|
def manage_toxicities():
|
|
if not is_logged_in():
|
|
return redirect(url_for('login'))
|
|
if request.method == 'POST':
|
|
name = request.form['name']
|
|
description = request.form['description']
|
|
icon_file = request.files.get('icon')
|
|
icon_filename = None
|
|
if not name:
|
|
flash('Name is required!', 'danger')
|
|
print('No name provided')
|
|
return redirect(url_for('manage_toxicities'))
|
|
if Toxicity.query.filter_by(name=name).first():
|
|
flash('Toxicity with this name already exists!', 'danger')
|
|
print('Duplicate toxicity name')
|
|
return redirect(url_for('manage_toxicities'))
|
|
if icon_file and icon_file.filename:
|
|
icon_filename = secure_filename(icon_file.filename)
|
|
icon_path = os.path.join('static/icons', icon_filename)
|
|
os.makedirs('static/icons', exist_ok=True)
|
|
try:
|
|
icon_file.save(icon_path)
|
|
except Exception as e:
|
|
print('Error saving icon:', e)
|
|
flash(f'Error saving icon: {e}', 'danger')
|
|
try:
|
|
toxicity = Toxicity(name=name, description=description, icon=icon_filename)
|
|
db.session.add(toxicity)
|
|
db.session.commit()
|
|
flash('Toxicity level added successfully!', 'success')
|
|
print('Toxicity added:', name)
|
|
except Exception as e:
|
|
db.session.rollback()
|
|
print('Error adding toxicity:', e)
|
|
flash(f'Error adding toxicity: {e}', 'danger')
|
|
return redirect(url_for('manage_toxicities'))
|
|
toxicities = Toxicity.query.all()
|
|
return render_template('manage_toxicities.html',
|
|
toxicities=toxicities,
|
|
plant_count=len(Plant.query.all()),
|
|
climate_count=len(Climate.query.all()),
|
|
environment_count=len(Environment.query.all()),
|
|
product_count=len(Product.query.all()),
|
|
light_count=len(Light.query.all()),
|
|
toxicity_count=len(toxicities),
|
|
size_count=len(Size.query.all()),
|
|
difficulty_count=len(CareDifficulty.query.all()),
|
|
rate_count=len(GrowthRate.query.all()))
|
|
|
|
@app.route('/admin/toxicities/edit/<int:toxicity_id>', methods=['GET', 'POST'])
|
|
def edit_toxicity(toxicity_id):
|
|
if not is_logged_in():
|
|
return redirect(url_for('login'))
|
|
toxicity = Toxicity.query.get_or_404(toxicity_id)
|
|
if request.method == 'POST':
|
|
toxicity.name = request.form['name']
|
|
toxicity.description = request.form['description']
|
|
icon_file = request.files.get('icon')
|
|
if icon_file and icon_file.filename:
|
|
icon_filename = secure_filename(icon_file.filename)
|
|
icon_path = os.path.join('static/icons', icon_filename)
|
|
os.makedirs('static/icons', exist_ok=True)
|
|
try:
|
|
icon_file.save(icon_path)
|
|
except Exception as e:
|
|
print('Error saving icon:', e)
|
|
toxicity.icon = icon_filename
|
|
db.session.commit()
|
|
flash('Toxicity level updated successfully!', 'success')
|
|
return redirect(url_for('manage_toxicities'))
|
|
return render_template('edit_toxicity.html', toxicity=toxicity)
|
|
|
|
@app.route('/admin/toxicities/delete/<int:toxicity_id>', methods=['POST'])
|
|
def delete_toxicity(toxicity_id):
|
|
if not is_logged_in():
|
|
return redirect(url_for('login'))
|
|
toxicity = Toxicity.query.get_or_404(toxicity_id)
|
|
db.session.delete(toxicity)
|
|
db.session.commit()
|
|
flash('Toxicity level deleted successfully!', 'success')
|
|
return redirect(url_for('manage_toxicities'))
|
|
|
|
# Size management routes
|
|
@app.route('/admin/sizes', methods=['GET', 'POST'])
|
|
def manage_sizes():
|
|
if not is_logged_in():
|
|
return redirect(url_for('login'))
|
|
if request.method == 'POST':
|
|
name = request.form['name']
|
|
description = request.form['description']
|
|
icon_file = request.files.get('icon')
|
|
icon_filename = None
|
|
if not name:
|
|
flash('Name is required!', 'danger')
|
|
print('No name provided')
|
|
return redirect(url_for('manage_sizes'))
|
|
if Size.query.filter_by(name=name).first():
|
|
flash('Size with this name already exists!', 'danger')
|
|
print('Duplicate size name')
|
|
return redirect(url_for('manage_sizes'))
|
|
if icon_file and icon_file.filename:
|
|
icon_filename = secure_filename(icon_file.filename)
|
|
icon_path = os.path.join('static/icons', icon_filename)
|
|
os.makedirs('static/icons', exist_ok=True)
|
|
try:
|
|
icon_file.save(icon_path)
|
|
except Exception as e:
|
|
print('Error saving icon:', e)
|
|
flash(f'Error saving icon: {e}', 'danger')
|
|
try:
|
|
size = Size(name=name, description=description, icon=icon_filename)
|
|
db.session.add(size)
|
|
db.session.commit()
|
|
flash('Size category added successfully!', 'success')
|
|
print('Size added:', name)
|
|
except Exception as e:
|
|
db.session.rollback()
|
|
print('Error adding size:', e)
|
|
flash(f'Error adding size: {e}', 'danger')
|
|
return redirect(url_for('manage_sizes'))
|
|
sizes = Size.query.all()
|
|
return render_template('manage_sizes.html',
|
|
sizes=sizes,
|
|
plant_count=len(Plant.query.all()),
|
|
climate_count=len(Climate.query.all()),
|
|
environment_count=len(Environment.query.all()),
|
|
product_count=len(Product.query.all()),
|
|
light_count=len(Light.query.all()),
|
|
toxicity_count=len(Toxicity.query.all()),
|
|
size_count=len(sizes),
|
|
difficulty_count=len(CareDifficulty.query.all()),
|
|
rate_count=len(GrowthRate.query.all()))
|
|
|
|
@app.route('/admin/sizes/edit/<int:size_id>', methods=['GET', 'POST'])
|
|
def edit_size(size_id):
|
|
if not is_logged_in():
|
|
return redirect(url_for('login'))
|
|
size = Size.query.get_or_404(size_id)
|
|
if request.method == 'POST':
|
|
size.name = request.form['name']
|
|
size.description = request.form['description']
|
|
icon_file = request.files.get('icon')
|
|
if icon_file and icon_file.filename:
|
|
icon_filename = secure_filename(icon_file.filename)
|
|
icon_path = os.path.join('static/icons', icon_filename)
|
|
os.makedirs('static/icons', exist_ok=True)
|
|
try:
|
|
icon_file.save(icon_path)
|
|
except Exception as e:
|
|
print('Error saving icon:', e)
|
|
size.icon = icon_filename
|
|
db.session.commit()
|
|
flash('Size category updated successfully!', 'success')
|
|
return redirect(url_for('manage_sizes'))
|
|
return render_template('edit_size.html', size=size)
|
|
|
|
@app.route('/admin/sizes/delete/<int:size_id>', methods=['POST'])
|
|
def delete_size(size_id):
|
|
if not is_logged_in():
|
|
return redirect(url_for('login'))
|
|
size = Size.query.get_or_404(size_id)
|
|
db.session.delete(size)
|
|
db.session.commit()
|
|
flash('Size category deleted successfully!', 'success')
|
|
return redirect(url_for('manage_sizes'))
|
|
|
|
# Care Difficulty management routes
|
|
@app.route('/admin/care-difficulties', methods=['GET', 'POST'])
|
|
def manage_care_difficulties():
|
|
if not is_logged_in():
|
|
return redirect(url_for('login'))
|
|
if request.method == 'POST':
|
|
name = request.form['name']
|
|
description = request.form['description']
|
|
icon_file = request.files.get('icon')
|
|
icon_filename = None
|
|
if not name:
|
|
flash('Name is required!', 'danger')
|
|
print('No name provided')
|
|
return redirect(url_for('manage_care_difficulties'))
|
|
if CareDifficulty.query.filter_by(name=name).first():
|
|
flash('Care difficulty with this name already exists!', 'danger')
|
|
print('Duplicate care difficulty name')
|
|
return redirect(url_for('manage_care_difficulties'))
|
|
if icon_file and icon_file.filename:
|
|
icon_filename = secure_filename(icon_file.filename)
|
|
icon_path = os.path.join('static/icons', icon_filename)
|
|
os.makedirs('static/icons', exist_ok=True)
|
|
try:
|
|
icon_file.save(icon_path)
|
|
except Exception as e:
|
|
print('Error saving icon:', e)
|
|
flash(f'Error saving icon: {e}', 'danger')
|
|
try:
|
|
difficulty = CareDifficulty(name=name, description=description, icon=icon_filename)
|
|
db.session.add(difficulty)
|
|
db.session.commit()
|
|
flash('Care difficulty level added successfully!', 'success')
|
|
print('Care difficulty added:', name)
|
|
except Exception as e:
|
|
db.session.rollback()
|
|
print('Error adding care difficulty:', e)
|
|
flash(f'Error adding care difficulty: {e}', 'danger')
|
|
return redirect(url_for('manage_care_difficulties'))
|
|
difficulties = CareDifficulty.query.all()
|
|
return render_template('manage_care_difficulties.html',
|
|
difficulties=difficulties,
|
|
plant_count=len(Plant.query.all()),
|
|
climate_count=len(Climate.query.all()),
|
|
environment_count=len(Environment.query.all()),
|
|
product_count=len(Product.query.all()),
|
|
light_count=len(Light.query.all()),
|
|
toxicity_count=len(Toxicity.query.all()),
|
|
size_count=len(Size.query.all()),
|
|
difficulty_count=len(difficulties),
|
|
rate_count=len(GrowthRate.query.all()))
|
|
|
|
@app.route('/admin/care-difficulties/edit/<int:difficulty_id>', methods=['GET', 'POST'])
|
|
def edit_care_difficulty(difficulty_id):
|
|
if not is_logged_in():
|
|
return redirect(url_for('login'))
|
|
difficulty = CareDifficulty.query.get_or_404(difficulty_id)
|
|
if request.method == 'POST':
|
|
difficulty.name = request.form['name']
|
|
difficulty.description = request.form['description']
|
|
icon_file = request.files.get('icon')
|
|
if icon_file and icon_file.filename:
|
|
icon_filename = secure_filename(icon_file.filename)
|
|
icon_path = os.path.join('static/icons', icon_filename)
|
|
os.makedirs('static/icons', exist_ok=True)
|
|
try:
|
|
icon_file.save(icon_path)
|
|
except Exception as e:
|
|
print('Error saving icon:', e)
|
|
difficulty.icon = icon_filename
|
|
db.session.commit()
|
|
flash('Care difficulty level updated successfully!', 'success')
|
|
return redirect(url_for('manage_care_difficulties'))
|
|
return render_template('edit_care_difficulty.html', difficulty=difficulty)
|
|
|
|
@app.route('/admin/care-difficulties/delete/<int:difficulty_id>', methods=['POST'])
|
|
def delete_care_difficulty(difficulty_id):
|
|
if not is_logged_in():
|
|
return redirect(url_for('login'))
|
|
difficulty = CareDifficulty.query.get_or_404(difficulty_id)
|
|
db.session.delete(difficulty)
|
|
db.session.commit()
|
|
flash('Care difficulty level deleted successfully!', 'success')
|
|
return redirect(url_for('manage_care_difficulties'))
|
|
|
|
# Growth Rate management routes
|
|
@app.route('/admin/growth-rates', methods=['GET', 'POST'])
|
|
def manage_growth_rates():
|
|
if not is_logged_in():
|
|
return redirect(url_for('login'))
|
|
if request.method == 'POST':
|
|
name = request.form['name']
|
|
description = request.form['description']
|
|
icon_file = request.files.get('icon')
|
|
icon_filename = None
|
|
if not name:
|
|
flash('Name is required!', 'danger')
|
|
print('No name provided')
|
|
return redirect(url_for('manage_growth_rates'))
|
|
if GrowthRate.query.filter_by(name=name).first():
|
|
flash('Growth rate with this name already exists!', 'danger')
|
|
print('Duplicate growth rate name')
|
|
return redirect(url_for('manage_growth_rates'))
|
|
if icon_file and icon_file.filename:
|
|
icon_filename = secure_filename(icon_file.filename)
|
|
icon_path = os.path.join('static/icons', icon_filename)
|
|
os.makedirs('static/icons', exist_ok=True)
|
|
try:
|
|
icon_file.save(icon_path)
|
|
except Exception as e:
|
|
print('Error saving icon:', e)
|
|
flash(f'Error saving icon: {e}', 'danger')
|
|
try:
|
|
rate = GrowthRate(name=name, description=description, icon=icon_filename)
|
|
db.session.add(rate)
|
|
db.session.commit()
|
|
flash('Growth rate category added successfully!', 'success')
|
|
print('Growth rate added:', name)
|
|
except Exception as e:
|
|
db.session.rollback()
|
|
print('Error adding growth rate:', e)
|
|
flash(f'Error adding growth rate: {e}', 'danger')
|
|
return redirect(url_for('manage_growth_rates'))
|
|
rates = GrowthRate.query.all()
|
|
return render_template('manage_growth_rates.html',
|
|
rates=rates,
|
|
plant_count=len(Plant.query.all()),
|
|
climate_count=len(Climate.query.all()),
|
|
environment_count=len(Environment.query.all()),
|
|
product_count=len(Product.query.all()),
|
|
light_count=len(Light.query.all()),
|
|
toxicity_count=len(Toxicity.query.all()),
|
|
size_count=len(Size.query.all()),
|
|
difficulty_count=len(CareDifficulty.query.all()),
|
|
rate_count=len(rates))
|
|
|
|
@app.route('/admin/growth-rates/edit/<int:rate_id>', methods=['GET', 'POST'])
|
|
def edit_growth_rate(rate_id):
|
|
if not is_logged_in():
|
|
return redirect(url_for('login'))
|
|
rate = GrowthRate.query.get_or_404(rate_id)
|
|
if request.method == 'POST':
|
|
rate.name = request.form['name']
|
|
rate.description = request.form['description']
|
|
icon_file = request.files.get('icon')
|
|
if icon_file and icon_file.filename:
|
|
icon_filename = secure_filename(icon_file.filename)
|
|
icon_path = os.path.join('static/icons', icon_filename)
|
|
os.makedirs('static/icons', exist_ok=True)
|
|
try:
|
|
icon_file.save(icon_path)
|
|
except Exception as e:
|
|
print('Error saving icon:', e)
|
|
rate.icon = icon_filename
|
|
db.session.commit()
|
|
flash('Growth rate category updated successfully!', 'success')
|
|
return redirect(url_for('manage_growth_rates'))
|
|
return render_template('edit_growth_rate.html', rate=rate)
|
|
|
|
@app.route('/admin/growth-rates/delete/<int:rate_id>', methods=['POST'])
|
|
def delete_growth_rate(rate_id):
|
|
if not is_logged_in():
|
|
return redirect(url_for('login'))
|
|
rate = GrowthRate.query.get_or_404(rate_id)
|
|
db.session.delete(rate)
|
|
db.session.commit()
|
|
flash('Growth rate category deleted successfully!', 'success')
|
|
return redirect(url_for('manage_growth_rates'))
|
|
|
|
# Section management routes
|
|
@app.route('/admin/sections', methods=['GET', 'POST'])
|
|
def manage_sections():
|
|
if not is_logged_in():
|
|
return redirect(url_for('login'))
|
|
if request.method == 'POST':
|
|
name = request.form['name']
|
|
description = request.form['description']
|
|
icon_file = request.files.get('icon')
|
|
icon_filename = None
|
|
if not name:
|
|
flash('Name is required!', 'danger')
|
|
print('No name provided')
|
|
return redirect(url_for('manage_sections'))
|
|
if Section.query.filter_by(name=name).first():
|
|
flash('Section with this name already exists!', 'danger')
|
|
print('Duplicate section name')
|
|
return redirect(url_for('manage_sections'))
|
|
if icon_file and icon_file.filename:
|
|
icon_filename = secure_filename(icon_file.filename)
|
|
icon_path = os.path.join('static/icons', icon_filename)
|
|
os.makedirs('static/icons', exist_ok=True)
|
|
try:
|
|
icon_file.save(icon_path)
|
|
except Exception as e:
|
|
print('Error saving icon:', e)
|
|
flash(f'Error saving icon: {e}', 'danger')
|
|
try:
|
|
section = Section(name=name, description=description, icon=icon_filename)
|
|
db.session.add(section)
|
|
db.session.commit()
|
|
flash('Section added successfully!', 'success')
|
|
print('Section added:', name)
|
|
except Exception as e:
|
|
db.session.rollback()
|
|
print('Error adding section:', e)
|
|
flash(f'Error adding section: {e}', 'danger')
|
|
return redirect(url_for('manage_sections'))
|
|
sections = Section.query.all()
|
|
return render_template('manage_sections.html',
|
|
sections=sections,
|
|
plant_count=len(Plant.query.all()),
|
|
climate_count=len(Climate.query.all()),
|
|
environment_count=len(Environment.query.all()),
|
|
product_count=len(Product.query.all()),
|
|
light_count=len(Light.query.all()),
|
|
toxicity_count=len(Toxicity.query.all()),
|
|
size_count=len(Size.query.all()),
|
|
difficulty_count=len(CareDifficulty.query.all()),
|
|
rate_count=len(GrowthRate.query.all()),
|
|
section_count=len(sections))
|
|
|
|
@app.route('/admin/sections/edit/<int:section_id>', methods=['GET', 'POST'])
|
|
def edit_section(section_id):
|
|
if not is_logged_in():
|
|
return redirect(url_for('login'))
|
|
section = Section.query.get_or_404(section_id)
|
|
if request.method == 'POST':
|
|
section.name = request.form['name']
|
|
section.description = request.form['description']
|
|
icon_file = request.files.get('icon')
|
|
if icon_file and icon_file.filename:
|
|
icon_filename = secure_filename(icon_file.filename)
|
|
icon_path = os.path.join('static/icons', icon_filename)
|
|
os.makedirs('static/icons', exist_ok=True)
|
|
try:
|
|
icon_file.save(icon_path)
|
|
except Exception as e:
|
|
print('Error saving icon:', e)
|
|
section.icon = icon_filename
|
|
db.session.commit()
|
|
flash('Section updated successfully!', 'success')
|
|
return redirect(url_for('manage_sections'))
|
|
return render_template('edit_section.html', section=section)
|
|
|
|
@app.route('/admin/sections/delete/<int:section_id>', methods=['POST'])
|
|
def delete_section(section_id):
|
|
if not is_logged_in():
|
|
return redirect(url_for('login'))
|
|
section = Section.query.get_or_404(section_id)
|
|
db.session.delete(section)
|
|
db.session.commit()
|
|
flash('Section deleted successfully!', 'success')
|
|
return redirect(url_for('manage_sections'))
|
|
|
|
def seed_db():
|
|
# Environments
|
|
environments = [
|
|
{'name': 'Indoor', 'description': 'Inside the house or building.'},
|
|
{'name': 'Outdoor', 'description': 'Outside, exposed to natural elements.'},
|
|
{'name': 'Greenhouse', 'description': 'Controlled environment for plants.'}
|
|
]
|
|
for env in environments:
|
|
if not Environment.query.filter_by(name=env['name']).first():
|
|
db.session.add(Environment(name=env['name'], description=env['description']))
|
|
|
|
# Sections
|
|
sections = [
|
|
{'name': 'Succulents', 'description': 'Plants that store water in their leaves, stems, or roots.'},
|
|
{'name': 'Tropical', 'description': 'Plants native to tropical regions.'},
|
|
{'name': 'Herbs', 'description': 'Plants used for culinary, medicinal, or aromatic purposes.'},
|
|
{'name': 'Flowering', 'description': 'Plants grown primarily for their flowers.'},
|
|
{'name': 'Foliage', 'description': 'Plants grown primarily for their attractive leaves.'}
|
|
]
|
|
for sec in sections:
|
|
if not Section.query.filter_by(name=sec['name']).first():
|
|
db.session.add(Section(name=sec['name'], description=sec['description']))
|
|
|
|
# Climates
|
|
climates = [
|
|
{'name': 'Tropical', 'description': 'Warm and humid year-round.'},
|
|
{'name': 'Temperate', 'description': 'Moderate temperatures, distinct seasons.'},
|
|
{'name': 'Arid', 'description': 'Dry, little rainfall.'},
|
|
{'name': 'Mediterranean', 'description': 'Mild, wet winters and hot, dry summers.'}
|
|
]
|
|
for clim in climates:
|
|
if not Climate.query.filter_by(name=clim['name']).first():
|
|
db.session.add(Climate(name=clim['name'], description=clim['description']))
|
|
|
|
# Products
|
|
products = [
|
|
{'name': 'Potting Soil', 'description': 'Soil mix for potted plants.'},
|
|
{'name': 'Fertilizer', 'description': 'Nutrient supplement for plants.'},
|
|
{'name': 'Watering Can', 'description': 'Tool for watering plants.'},
|
|
{'name': 'Grow Light', 'description': 'Artificial light for plant growth.'}
|
|
]
|
|
for prod in products:
|
|
if not Product.query.filter_by(name=prod['name']).first():
|
|
db.session.add(Product(name=prod['name'], description=prod['description']))
|
|
|
|
# Light Requirements
|
|
lights = [
|
|
{'name': 'Bright Direct'},
|
|
{'name': 'Bright Indirect'},
|
|
{'name': 'Medium Light'},
|
|
{'name': 'Low Light'}
|
|
]
|
|
for light in lights:
|
|
if not Light.query.filter_by(name=light['name']).first():
|
|
db.session.add(Light(name=light['name']))
|
|
|
|
# Toxicity Levels
|
|
toxicities = [
|
|
{'name': 'Non-toxic'},
|
|
{'name': 'Mildly Toxic'},
|
|
{'name': 'Toxic'},
|
|
{'name': 'Highly Toxic'}
|
|
]
|
|
for toxicity in toxicities:
|
|
if not Toxicity.query.filter_by(name=toxicity['name']).first():
|
|
db.session.add(Toxicity(name=toxicity['name']))
|
|
|
|
# Size Categories
|
|
sizes = [
|
|
{'name': 'Small'},
|
|
{'name': 'Medium'},
|
|
{'name': 'Large'}
|
|
]
|
|
for size in sizes:
|
|
if not Size.query.filter_by(name=size['name']).first():
|
|
db.session.add(Size(name=size['name']))
|
|
|
|
# Care Difficulty Levels
|
|
difficulties = [
|
|
{'name': 'Easy'},
|
|
{'name': 'Moderate'},
|
|
{'name': 'Difficult'}
|
|
]
|
|
for difficulty in difficulties:
|
|
if not CareDifficulty.query.filter_by(name=difficulty['name']).first():
|
|
db.session.add(CareDifficulty(name=difficulty['name']))
|
|
|
|
# Growth Rate Categories
|
|
growth_rates = [
|
|
{'name': 'Slow'},
|
|
{'name': 'Moderate'},
|
|
{'name': 'Fast'}
|
|
]
|
|
for rate in growth_rates:
|
|
if not GrowthRate.query.filter_by(name=rate['name']).first():
|
|
db.session.add(GrowthRate(name=rate['name']))
|
|
|
|
db.session.commit()
|
|
|
|
# Plants (add only if no plants exist)
|
|
if Plant.query.count() == 0:
|
|
indoor = Environment.query.filter_by(name='Indoor').first()
|
|
outdoor = Environment.query.filter_by(name='Outdoor').first()
|
|
greenhouse = Environment.query.filter_by(name='Greenhouse').first()
|
|
tropical = Climate.query.filter_by(name='Tropical').first()
|
|
temperate = Climate.query.filter_by(name='Temperate').first()
|
|
arid = Climate.query.filter_by(name='Arid').first()
|
|
mediterranean = Climate.query.filter_by(name='Mediterranean').first()
|
|
soil = Product.query.filter_by(name='Potting Soil').first()
|
|
fert = Product.query.filter_by(name='Fertilizer').first()
|
|
can = Product.query.filter_by(name='Watering Can').first()
|
|
light = Product.query.filter_by(name='Grow Light').first()
|
|
|
|
# Get attribute instances
|
|
bright_indirect = Light.query.filter_by(name='Bright Indirect').first()
|
|
non_toxic = Toxicity.query.filter_by(name='Non-toxic').first()
|
|
medium = Size.query.filter_by(name='Medium').first()
|
|
easy = CareDifficulty.query.filter_by(name='Easy').first()
|
|
moderate = GrowthRate.query.filter_by(name='Moderate').first()
|
|
|
|
# Get section instances
|
|
succulents = Section.query.filter_by(name='Succulents').first()
|
|
tropical_section = Section.query.filter_by(name='Tropical').first()
|
|
herbs = Section.query.filter_by(name='Herbs').first()
|
|
flowering = Section.query.filter_by(name='Flowering').first()
|
|
foliage = Section.query.filter_by(name='Foliage').first()
|
|
|
|
# 10 example plants
|
|
seed_plants = [
|
|
dict(name='Monstera Deliciosa', climate=tropical, environment=indoor,
|
|
light=bright_indirect, toxicity=non_toxic, size=medium,
|
|
care_difficulty=easy, growth_rate=moderate, section=tropical_section,
|
|
products=[soil, fert], description='A popular tropical houseplant with large, split leaves.',
|
|
care_guide='<ul><li>Water when the top 2 inches of soil are dry.</li><li>Provide bright, indirect light.</li><li>Fertilize monthly during growing season.</li></ul>'),
|
|
dict(name='Aloe Vera', climate=arid, environment=indoor,
|
|
light=bright_indirect, toxicity=non_toxic, size=medium,
|
|
care_difficulty=easy, growth_rate=moderate, section=succulents,
|
|
products=[soil], description='A succulent known for its medicinal properties.',
|
|
care_guide='<ul><li>Allow soil to dry completely between waterings.</li><li>Place in bright, indirect sunlight.</li><li>Use well-draining soil.</li></ul>'),
|
|
dict(name='Fiddle Leaf Fig', climate=tropical, environment=indoor,
|
|
light=bright_indirect, toxicity=non_toxic, size=medium,
|
|
care_difficulty=moderate, growth_rate=moderate, section=foliage,
|
|
products=[soil, fert, can], description='A trendy houseplant with large, violin-shaped leaves.',
|
|
care_guide='<ul><li>Keep soil consistently moist but not soggy.</li><li>Needs bright, filtered light.</li><li>Rotate plant for even growth.</li></ul>'),
|
|
dict(name='Snake Plant', climate=arid, environment=indoor,
|
|
light=bright_indirect, toxicity=non_toxic, size=medium,
|
|
care_difficulty=easy, growth_rate=moderate, section=foliage,
|
|
products=[soil, can], description='A hardy plant that tolerates low light and irregular watering.',
|
|
care_guide='<ul><li>Water sparingly; let soil dry out between waterings.</li><li>Tolerates low to bright light.</li><li>Wipe leaves to remove dust.</li></ul>'),
|
|
dict(name='Spider Plant', climate=temperate, environment=indoor,
|
|
light=bright_indirect, toxicity=non_toxic, size=medium,
|
|
care_difficulty=easy, growth_rate=moderate, section=foliage,
|
|
products=[soil, can], description='An easy-care plant with arching leaves and baby plantlets.',
|
|
care_guide='<ul><li>Keep soil slightly moist.</li><li>Thrives in bright, indirect light.</li><li>Trim brown tips as needed.</li></ul>'),
|
|
dict(name='Peace Lily', climate=tropical, environment=indoor,
|
|
light=bright_indirect, toxicity=non_toxic, size=medium,
|
|
care_difficulty=easy, growth_rate=moderate, section=flowering,
|
|
products=[soil, fert, can], description='A flowering plant that thrives in shade and purifies air.',
|
|
care_guide='<ul><li>Water when leaves droop slightly.</li><li>Prefers low to medium light.</li><li>Remove spent flowers to encourage new blooms.</li></ul>'),
|
|
dict(name='Jade Plant', climate=arid, environment=indoor,
|
|
light=bright_indirect, toxicity=non_toxic, size=medium,
|
|
care_difficulty=easy, growth_rate=moderate, section=succulents,
|
|
products=[soil], description='A succulent with thick, shiny leaves and a tree-like form.',
|
|
care_guide='<ul><li>Let soil dry between waterings.</li><li>Needs several hours of direct sunlight.</li><li>Prune to maintain shape.</li></ul>'),
|
|
dict(name='Tomato', climate=temperate, environment=outdoor,
|
|
light=bright_indirect, toxicity=non_toxic, size=medium,
|
|
care_difficulty=moderate, growth_rate=moderate, section=herbs,
|
|
products=[soil, fert, can], description='A classic edible plant for outdoor gardens.',
|
|
care_guide='<ul><li>Water regularly, keeping soil consistently moist.</li><li>Provide full sun.</li><li>Support with stakes or cages.</li></ul>'),
|
|
dict(name='Basil', climate=mediterranean, environment=greenhouse,
|
|
light=bright_indirect, toxicity=non_toxic, size=medium,
|
|
care_difficulty=easy, growth_rate=moderate, section=herbs,
|
|
products=[soil, fert, light], description='A fragrant herb often grown in greenhouses or sunny windows.',
|
|
care_guide='<ul><li>Water when soil surface is dry.</li><li>Needs at least 6 hours of sunlight daily.</li><li>Pinch off flowers to encourage leaf growth.</li></ul>'),
|
|
dict(name='Cactus', climate=arid, environment=indoor,
|
|
light=bright_indirect, toxicity=non_toxic, size=medium,
|
|
care_difficulty=easy, growth_rate=moderate, section=succulents,
|
|
products=[soil, light], description='A spiny plant adapted to dry, sunny conditions.',
|
|
care_guide='<ul><li>Water sparingly, especially in winter.</li><li>Place in bright, direct sunlight.</li><li>Use cactus-specific soil mix.</li></ul>'),
|
|
]
|
|
for plant in seed_plants:
|
|
db.session.add(Plant(
|
|
name=plant['name'],
|
|
climate_id=plant['climate'].id if plant['climate'] else None,
|
|
environment_id=plant['environment'].id if plant['environment'] else None,
|
|
light_id=plant['light'].id if plant['light'] else None,
|
|
toxicity_id=plant['toxicity'].id if plant['toxicity'] else None,
|
|
size_id=plant['size'].id if plant['size'] else None,
|
|
care_difficulty_id=plant['care_difficulty'].id if plant['care_difficulty'] else None,
|
|
growth_rate_id=plant['growth_rate'].id if plant['growth_rate'] else None,
|
|
section_id=plant['section'].id if plant['section'] else None,
|
|
products=','.join(str(p.id) for p in plant['products'] if p),
|
|
description=plant['description'],
|
|
care_guide=plant['care_guide']
|
|
))
|
|
db.session.commit()
|
|
|
|
@app.route('/recognize-plant', methods=['GET', 'POST'])
|
|
def recognize_plant():
|
|
if request.method == 'GET':
|
|
return render_template('recognize_plant.html')
|
|
|
|
# For proof of concept, just return a random plant
|
|
plants = Plant.query.all()
|
|
if not plants:
|
|
flash('No plants found in the database.', 'warning')
|
|
return redirect(url_for('home'))
|
|
|
|
random_plant = random.choice(plants)
|
|
return jsonify({'redirect': url_for('plant', plant_id=random_plant.id)})
|
|
|
|
if __name__ == "__main__":
|
|
with app.app_context():
|
|
db.create_all() # Create all tables
|
|
# Create admin user if it doesn't exist
|
|
admin = User.query.filter_by(username='admin').first()
|
|
if not admin:
|
|
admin = User(username='admin')
|
|
admin.set_password('admin123') # Set a default password
|
|
db.session.add(admin)
|
|
db.session.commit()
|
|
print("Admin user created with username: admin and password: admin123")
|
|
seed_db() # Seed initial data
|
|
app.run(debug=True) |