mirror of
https://github.com/StefBuwalda/cal_counter.git
synced 2025-10-29 19:00:00 +00:00
Introduces a new add_meal_v2 blueprint with barcode scanning, item search, and improved meal logging UI. Adds user timezone support: login form now captures timezone, User model and database schema updated, and timezone is set on login. Refactors templates and forms to support these changes, and removes the old login template.
156 lines
5.1 KiB
Python
156 lines
5.1 KiB
Python
from flask_login import UserMixin # type: ignore
|
|
from werkzeug.security import generate_password_hash, check_password_hash
|
|
from application import db
|
|
from typing import Optional
|
|
from forms import FoodItemForm
|
|
from datetime import datetime, timezone, date
|
|
from application.utils import is_valid_timezone
|
|
|
|
|
|
class User(UserMixin, db.Model):
|
|
__tablename__ = "user"
|
|
id = db.Column(db.Integer, primary_key=True)
|
|
username = db.Column(db.String(150), unique=True, nullable=False)
|
|
password = db.Column(db.String, nullable=False)
|
|
timezone = db.Column(db.String(64), nullable=False, default="UTC")
|
|
is_admin = db.Column(db.Boolean, nullable=False, default=False)
|
|
must_change_password = db.Column(db.Boolean, nullable=False, default=False)
|
|
|
|
food_items = db.relationship("FoodItem", lazy="dynamic", backref="user")
|
|
food_logs = db.relationship("FoodLog", lazy="dynamic", backref="user")
|
|
|
|
def __init__(
|
|
self,
|
|
username: str,
|
|
password: str,
|
|
is_admin: bool = False,
|
|
must_change_password: bool = False,
|
|
) -> None:
|
|
super().__init__()
|
|
self.username = username
|
|
self.password = generate_password_hash(password=password)
|
|
self.is_admin = is_admin
|
|
self.must_change_password = must_change_password
|
|
|
|
def check_password(self, password: str) -> bool:
|
|
return check_password_hash(pwhash=self.password, password=password)
|
|
|
|
def change_password(self, password: str) -> None:
|
|
self.password = generate_password_hash(password=password)
|
|
|
|
def set_pw_change(self, change: bool) -> None:
|
|
self.must_change_password = change
|
|
|
|
def set_timezone(self, tz: str) -> None:
|
|
if is_valid_timezone(tz):
|
|
self.timezone = tz
|
|
|
|
|
|
class Unit(db.Model):
|
|
__tablename__ = "unit"
|
|
id = db.Column(db.Integer, primary_key=True)
|
|
symbol = db.Column(db.String(10), unique=True, nullable=False)
|
|
name = db.Column(db.String(50), unique=True, nullable=False)
|
|
|
|
|
|
class FoodItem(db.Model):
|
|
__tablename__ = "food_item"
|
|
|
|
id = db.Column(db.Integer, primary_key=True)
|
|
barcode = db.Column(db.String)
|
|
owner_id = db.Column(db.Integer, db.ForeignKey("user.id"), nullable=False)
|
|
name = db.Column(db.String(150), nullable=False)
|
|
|
|
energy_100: float = db.Column(db.Float, nullable=False)
|
|
protein_100: float = db.Column(db.Float, nullable=False)
|
|
carbs_100: float = db.Column(db.Float, nullable=False)
|
|
sugar_100: Optional[float] = db.Column(db.Float)
|
|
fat_100: float = db.Column(db.Float, nullable=False)
|
|
saturated_fat_100: Optional[float] = db.Column(db.Float)
|
|
|
|
food_logs = db.relationship(
|
|
"FoodLog",
|
|
backref="food_item",
|
|
lazy="dynamic",
|
|
cascade="all, delete-orphan",
|
|
)
|
|
|
|
__table_args__ = (
|
|
db.UniqueConstraint("barcode", "owner_id", name="barcode_owner_key"),
|
|
db.UniqueConstraint("name", "owner_id", name="name_owner_key"),
|
|
)
|
|
|
|
def __init__(
|
|
self,
|
|
name: str,
|
|
owner_id: int,
|
|
energy: float,
|
|
protein: float,
|
|
carbs: float,
|
|
fat: float,
|
|
barcode: Optional[str] = None,
|
|
sugar: Optional[float] = None,
|
|
saturated_fat: Optional[float] = None,
|
|
):
|
|
if barcode and not barcode.isdigit():
|
|
raise ValueError("Barcode must contain only digits.")
|
|
self.name = name
|
|
self.owner_id = owner_id
|
|
self.energy_100 = energy
|
|
self.protein_100 = protein
|
|
self.carbs_100 = carbs
|
|
self.sugar_100 = sugar
|
|
self.fat_100 = fat
|
|
self.saturated_fat_100 = saturated_fat
|
|
self.barcode = barcode
|
|
|
|
def updateFromForm(self, form: FoodItemForm):
|
|
self.name = form.name.data
|
|
self.energy_100 = form.energy.data or 0
|
|
self.protein_100 = form.protein.data or 0
|
|
self.carbs_100 = form.carbs.data or 0
|
|
self.sugar_100 = form.sugar.data
|
|
self.fat_100 = form.fat.data or 0
|
|
self.saturated_fat_100 = form.saturated_fat.data
|
|
|
|
def macros(self) -> tuple[float, float, float, float]:
|
|
return (
|
|
self.energy_100,
|
|
self.fat_100,
|
|
self.carbs_100,
|
|
self.protein_100,
|
|
)
|
|
|
|
|
|
class FoodLog(db.Model):
|
|
__tablename__ = "food_log"
|
|
id = db.Column(db.Integer, primary_key=True)
|
|
datetime_created = db.Column(
|
|
db.DateTime, default=datetime.now(timezone.utc), nullable=False
|
|
)
|
|
date_ = db.Column(
|
|
db.Date, default=datetime.now(timezone.utc).date, nullable=False
|
|
)
|
|
food_item_id = db.Column(
|
|
db.Integer, db.ForeignKey("food_item.id"), nullable=False
|
|
)
|
|
part_of_day = db.Column(db.Integer, nullable=False)
|
|
user_id = db.Column(db.Integer, db.ForeignKey("user.id"), nullable=False)
|
|
amount = db.Column(db.Float, nullable=False)
|
|
|
|
def __init__(
|
|
self,
|
|
food_item_id: int,
|
|
user_id: int,
|
|
amount: float,
|
|
part_of_day: int,
|
|
date_: Optional[date] = None,
|
|
):
|
|
super().__init__()
|
|
self.food_item_id = food_item_id
|
|
self.user_id = user_id
|
|
self.amount = amount
|
|
if date_ is not None:
|
|
self.date_ = date_
|
|
self.part_of_day = part_of_day
|