mirror of
https://github.com/StefBuwalda/cal_counter.git
synced 2025-10-30 03:10: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.
188 lines
5.3 KiB
Python
188 lines
5.3 KiB
Python
from flask import (
|
|
Blueprint,
|
|
redirect,
|
|
url_for,
|
|
render_template,
|
|
session,
|
|
request,
|
|
jsonify,
|
|
abort,
|
|
)
|
|
from flask_login import current_user
|
|
from forms import FoodItemForm, FoodLogForm
|
|
from application import db
|
|
from models import FoodItem, FoodLog
|
|
from sqlalchemy import and_, or_
|
|
from datetime import datetime, timedelta, timezone
|
|
from sqlalchemy.sql.elements import BinaryExpression
|
|
from typing import cast
|
|
|
|
bp = Blueprint(
|
|
"add_meal_v2",
|
|
__name__,
|
|
url_prefix="/add_meal_v2",
|
|
template_folder="templates",
|
|
)
|
|
|
|
|
|
@bp.before_request
|
|
def login_required():
|
|
if not current_user.is_authenticated:
|
|
return redirect(url_for("auth.login"))
|
|
|
|
|
|
@bp.route("/select_meal/<int:meal_type>", methods=["GET"])
|
|
def step1(meal_type: int):
|
|
assert type(meal_type) is int
|
|
assert 0 <= meal_type <= 3
|
|
session["meal_type"] = meal_type
|
|
return redirect(url_for("add_meal.step2"))
|
|
|
|
|
|
@bp.route("/get_barcode", methods=["GET"])
|
|
def step2():
|
|
return render_template("scan_barcode.html")
|
|
|
|
|
|
@bp.route("/step3/<string:input>", methods=["GET"])
|
|
def step3(input: str):
|
|
# check if meal_type cookie is set
|
|
if "meal_type" not in session:
|
|
return redirect("/")
|
|
|
|
# Check if input is a barcode
|
|
if input.isdigit():
|
|
item = current_user.food_items.filter_by(barcode=input).first()
|
|
|
|
else:
|
|
item = current_user.food_items.filter_by(name=input).first()
|
|
|
|
if item is None:
|
|
# Does not exist, add item
|
|
return redirect(url_for("add_meal.step3_alt1", input=input))
|
|
|
|
# Track item to add and continue to next step
|
|
session["item_id"] = item.id
|
|
return redirect(url_for("add_meal.step4"))
|
|
|
|
|
|
@bp.route("/step3_alt1/<string:input>", methods=["GET"])
|
|
def step3_alt1(input: str):
|
|
form = FoodItemForm()
|
|
|
|
if input.isdigit():
|
|
form.barcode.data = input
|
|
else:
|
|
form.name.data = input
|
|
return render_template("add_item.html", form=form)
|
|
|
|
|
|
@bp.route("/step3_alt1/<string:input>", methods=["POST"])
|
|
def step3_alt1_post(input: str):
|
|
form = FoodItemForm()
|
|
|
|
if form.validate_on_submit():
|
|
# Form has valid input
|
|
barcode = form.barcode.data
|
|
name = form.name.data
|
|
assert name
|
|
assert form.energy.data is not None
|
|
assert form.protein.data is not None
|
|
assert form.carbs.data is not None
|
|
assert form.fat.data is not None
|
|
|
|
# Check if name or barcode already exists
|
|
name_filter = cast(BinaryExpression, FoodItem.name == name)
|
|
barcode_filter = cast(BinaryExpression, FoodItem.barcode == barcode)
|
|
filter_exp = or_(name_filter, barcode_filter)
|
|
item = current_user.food_items.filter(filter_exp).first()
|
|
|
|
if item is None:
|
|
# Item does not exist, add to DB
|
|
barcode = (
|
|
barcode if barcode else None
|
|
) # Turn empty strings into None
|
|
db.session.add(
|
|
FoodItem(
|
|
name=name,
|
|
owner_id=current_user.id,
|
|
energy=form.energy.data,
|
|
protein=form.protein.data,
|
|
carbs=form.carbs.data,
|
|
fat=form.fat.data,
|
|
barcode=barcode,
|
|
saturated_fat=form.saturated_fat.data,
|
|
sugar=form.sugar.data,
|
|
)
|
|
)
|
|
db.session.commit()
|
|
print("[DEBUG] New FoodItem Added")
|
|
input = barcode if barcode else name # update input
|
|
else:
|
|
print(f"Item exists: {item.barcode} {item.name}")
|
|
|
|
# Item added or already present, return to step 3.
|
|
return redirect(url_for("add_meal.step3", input=input))
|
|
else:
|
|
print("[DEBUG] Form Invalid")
|
|
return redirect(url_for("add_meal.step3_alt1", input=input))
|
|
|
|
|
|
@bp.route("/step4", methods=["GET", "POST"])
|
|
def step4():
|
|
if "item_id" not in session:
|
|
return redirect(url_for("add_meal.step2"))
|
|
form = FoodLogForm()
|
|
item = db.session.get(FoodItem, session["item_id"])
|
|
|
|
offset = session["offset"]
|
|
if offset is None or item is None:
|
|
abort(404)
|
|
|
|
today = datetime.now(timezone.utc).date()
|
|
day = today + timedelta(days=offset)
|
|
|
|
if form.validate_on_submit():
|
|
assert form.amount.data
|
|
db.session.add(
|
|
FoodLog(
|
|
food_item_id=item.id,
|
|
user_id=current_user.id,
|
|
amount=form.amount.data,
|
|
part_of_day=session["meal_type"],
|
|
date_=day,
|
|
)
|
|
)
|
|
db.session.commit()
|
|
session.pop("meal_type")
|
|
session.pop("item_id")
|
|
return redirect(url_for("user.daily_log", offset=offset))
|
|
|
|
match session["meal_type"]:
|
|
case 0:
|
|
tod = "Breakfast"
|
|
case 1:
|
|
tod = "Lunch"
|
|
case 2:
|
|
tod = "Dinner"
|
|
case 3:
|
|
tod = "Snack"
|
|
case _:
|
|
tod = "Unknown"
|
|
return render_template("step4.html", tod=tod, item=item, form=form)
|
|
|
|
|
|
@bp.route("/query", methods=["GET"])
|
|
def query():
|
|
q = request.args.get("q", "").strip().lower()
|
|
if not q:
|
|
return jsonify([])
|
|
|
|
words = q.split()
|
|
filters = [
|
|
FoodItem.name.ilike(f"%{word}%") for word in words # type: ignore
|
|
]
|
|
|
|
results = current_user.food_items.filter(and_(*filters)).all()
|
|
return jsonify([item.name for item in results])
|