Files
VerpotJeLot/app.py
2025-05-22 22:56:46 +02:00

1318 lines
60 KiB
Python

from flask import Flask, render_template, request, redirect, url_for, flash, session
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
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 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)
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')
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()
except Exception:
environment_count = climate_count = product_count = plant_count = 0
return dict(
environment_count=environment_count,
climate_count=climate_count,
product_count=product_count,
plant_count=plant_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')
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)
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 '',
})
return render_template('home.html',
plants=display_plants,
climates=Climate.query.all(),
environments=Environment.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(),
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
)
@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()
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.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)
@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'))
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']))
# 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()
# 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,
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,
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,
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,
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,
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,
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,
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,
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,
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,
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,
products=','.join(str(p.id) for p in plant['products'] if p),
description=plant['description'],
care_guide=plant['care_guide']
))
db.session.commit()
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)