- Track last_login_at on User model, updated on every successful login - Show last login date in admin panel user table - Fix admin/garden date display (datetime strings already contain T separator) - Fix My Garden Internal Server Error (MySQL does not support NULLS LAST syntax) - Fix Log Batch infinite loop when user has zero varieties - Auto-fill batch dates from today when creating a new batch, calculated from selected variety's week offsets (germination, greenhouse, garden) - Update README with new features and batch date auto-fill formula table Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
129 lines
4.6 KiB
Python
129 lines
4.6 KiB
Python
import enum
|
|
from sqlalchemy import Column, Integer, String, Date, Boolean, Text, DateTime, Enum, ForeignKey
|
|
from sqlalchemy.orm import relationship
|
|
from sqlalchemy.sql import func
|
|
from database import Base
|
|
|
|
|
|
class Category(str, enum.Enum):
|
|
vegetable = "vegetable"
|
|
herb = "herb"
|
|
flower = "flower"
|
|
fruit = "fruit"
|
|
|
|
|
|
class SunRequirement(str, enum.Enum):
|
|
full_sun = "full_sun"
|
|
part_shade = "part_shade"
|
|
full_shade = "full_shade"
|
|
|
|
|
|
class WaterNeeds(str, enum.Enum):
|
|
low = "low"
|
|
medium = "medium"
|
|
high = "high"
|
|
|
|
|
|
class BatchStatus(str, enum.Enum):
|
|
planned = "planned"
|
|
germinating = "germinating"
|
|
seedling = "seedling"
|
|
potted_up = "potted_up"
|
|
hardening = "hardening"
|
|
garden = "garden"
|
|
harvested = "harvested"
|
|
failed = "failed"
|
|
|
|
|
|
class User(Base):
|
|
__tablename__ = "users"
|
|
|
|
id = Column(Integer, primary_key=True, autoincrement=True)
|
|
email = Column(String(255), unique=True, nullable=False)
|
|
hashed_password = Column(String(255), nullable=False)
|
|
is_admin = Column(Boolean, default=False, nullable=False)
|
|
is_disabled = Column(Boolean, default=False, nullable=False)
|
|
created_at = Column(DateTime, server_default=func.now())
|
|
last_login_at = Column(DateTime, nullable=True)
|
|
|
|
varieties = relationship("Variety", back_populates="user", cascade="all, delete-orphan")
|
|
batches = relationship("Batch", back_populates="user", cascade="all, delete-orphan")
|
|
settings = relationship("Settings", back_populates="user", uselist=False, cascade="all, delete-orphan")
|
|
notification_logs = relationship("NotificationLog", back_populates="user", cascade="all, delete-orphan")
|
|
|
|
|
|
class Variety(Base):
|
|
__tablename__ = "varieties"
|
|
|
|
id = Column(Integer, primary_key=True, index=True)
|
|
user_id = Column(Integer, ForeignKey("users.id", ondelete="CASCADE"), nullable=False, index=True)
|
|
name = Column(String(100), nullable=False)
|
|
variety_name = Column(String(100))
|
|
category = Column(Enum(Category), default=Category.vegetable)
|
|
weeks_to_start = Column(Integer)
|
|
weeks_to_greenhouse = Column(Integer)
|
|
weeks_to_garden = Column(Integer)
|
|
days_to_germinate = Column(Integer, default=7)
|
|
direct_sow_ok = Column(Boolean, default=False)
|
|
frost_tolerant = Column(Boolean, default=False)
|
|
sun_requirement = Column(Enum(SunRequirement), default=SunRequirement.full_sun)
|
|
water_needs = Column(Enum(WaterNeeds), default=WaterNeeds.medium)
|
|
color = Column(String(7), default="#52b788")
|
|
notes = Column(Text)
|
|
created_at = Column(DateTime, server_default=func.now())
|
|
|
|
user = relationship("User", back_populates="varieties")
|
|
batches = relationship("Batch", back_populates="variety", cascade="all, delete-orphan")
|
|
|
|
|
|
class Batch(Base):
|
|
__tablename__ = "batches"
|
|
|
|
id = Column(Integer, primary_key=True, index=True)
|
|
user_id = Column(Integer, ForeignKey("users.id", ondelete="CASCADE"), nullable=False, index=True)
|
|
variety_id = Column(Integer, ForeignKey("varieties.id"), nullable=False)
|
|
label = Column(String(100))
|
|
quantity = Column(Integer, default=1)
|
|
sow_date = Column(Date)
|
|
germination_date = Column(Date)
|
|
greenhouse_date = Column(Date)
|
|
garden_date = Column(Date)
|
|
status = Column(Enum(BatchStatus), default=BatchStatus.planned)
|
|
notes = Column(Text)
|
|
created_at = Column(DateTime, server_default=func.now())
|
|
|
|
user = relationship("User", back_populates="batches")
|
|
variety = relationship("Variety", back_populates="batches")
|
|
|
|
|
|
class Settings(Base):
|
|
__tablename__ = "settings"
|
|
|
|
id = Column(Integer, primary_key=True, autoincrement=True)
|
|
user_id = Column(Integer, ForeignKey("users.id", ondelete="CASCADE"), unique=True, nullable=False)
|
|
last_frost_date = Column(Date)
|
|
first_frost_fall_date = Column(Date)
|
|
ntfy_topic = Column(String(200))
|
|
ntfy_server = Column(String(200), default="https://ntfy.sh")
|
|
notification_time = Column(String(5), default="07:00")
|
|
timezone = Column(String(50), default="UTC")
|
|
location_name = Column(String(100))
|
|
ntfy_username = Column(String(200))
|
|
ntfy_password = Column(String(200))
|
|
ntfy_api_key = Column(String(200))
|
|
|
|
user = relationship("User", back_populates="settings")
|
|
|
|
|
|
class NotificationLog(Base):
|
|
__tablename__ = "notification_log"
|
|
|
|
id = Column(Integer, primary_key=True, index=True)
|
|
user_id = Column(Integer, ForeignKey("users.id", ondelete="CASCADE"), nullable=True)
|
|
sent_at = Column(DateTime, server_default=func.now())
|
|
message = Column(Text)
|
|
status = Column(String(20))
|
|
error = Column(Text)
|
|
|
|
user = relationship("User", back_populates="notification_logs")
|