From c552a4571e05c14e621bc3534c06e9a4b577ae52 Mon Sep 17 00:00:00 2001 From: Stef Date: Thu, 7 Aug 2025 17:00:56 +0200 Subject: [PATCH] Add barcode-based meal logging workflow Introduces a new add_meal blueprint with routes and templates for scanning barcodes, adding new food items, and logging meals. Updates FoodItemForm and FoodLogForm validation, changes FoodLog.amount to float, and integrates the new workflow into the daily log UI. Refactors user routes and templates to support the new meal logging process. --- app.py | 2 + application/add_meal/routes.py | 131 ++++++++++++++++++ application/add_meal/templates/add_item.html | 49 +++++++ .../add_meal/templates/scan_barcode.html | 62 +++++++++ application/add_meal/templates/step4.html | 23 +++ application/user/routes.py | 18 +-- .../templates/{test.html => daily_log.html} | 16 +-- application/user/templates/get_item.html | 2 +- forms.py | 8 +- models.py | 4 +- 10 files changed, 287 insertions(+), 28 deletions(-) create mode 100644 application/add_meal/routes.py create mode 100644 application/add_meal/templates/add_item.html create mode 100644 application/add_meal/templates/scan_barcode.html create mode 100644 application/add_meal/templates/step4.html rename application/user/templates/{test.html => daily_log.html} (70%) diff --git a/app.py b/app.py index 93d892b..b57b369 100644 --- a/app.py +++ b/app.py @@ -10,6 +10,7 @@ from models import User from application import db, app, login_manager from application.admin.routes import admin_bp from application.user.routes import user_bp +from application.add_meal.routes import bp as add_meal_bp from typing import Optional # Config @@ -26,6 +27,7 @@ def load_user(user_id: int): # Register blueprints app.register_blueprint(admin_bp) app.register_blueprint(user_bp) +app.register_blueprint(add_meal_bp) # Routes diff --git a/application/add_meal/routes.py b/application/add_meal/routes.py new file mode 100644 index 0000000..2af491c --- /dev/null +++ b/application/add_meal/routes.py @@ -0,0 +1,131 @@ +from flask import ( + Blueprint, + redirect, + url_for, + render_template, + session, +) +from flask_login import current_user +from forms import FoodItemForm, FoodLogForm +from application import db +from models import FoodItem, FoodLog + +bp = Blueprint( + "add_meal", + __name__, + url_prefix="/add_meal", + template_folder="templates", +) + + +@bp.before_request +def login_required(): + if not current_user.is_authenticated: + return redirect(url_for("login")) + + +@bp.route("/select_meal/", 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/", methods=["GET"]) +def step3(barcode: str): + if "meal_type" not in session: + return redirect("/") + assert barcode.isdigit() + item = current_user.food_items.filter_by(barcode=barcode).first() + if item is None: + # Does not exist, add item + return redirect(url_for("add_meal.step3_alt1", barcode=barcode)) + else: + session["item_id"] = item.id + return redirect(url_for("add_meal.step4")) + + +@bp.route("/step3_alt1/", methods=["GET", "POST"]) +def step3_alt1(barcode: str): + form = FoodItemForm() + if form.validate_on_submit(): + print("[DEBUG] Valid form") + if ( + current_user.food_items.filter_by( + barcode=form.barcode.data + ).first() + is None + ): + assert form.name.data is not None + 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 + assert form.barcode.data is not None + db.session.add( + FoodItem( + name=form.name.data, + owner_id=current_user.id, + energy=form.energy.data, + protein=form.protein.data, + carbs=form.carbs.data, + fat=form.fat.data, + barcode=( + form.barcode.data + if form.barcode.data.isdigit() + else None + ), + saturated_fat=form.saturated_fat.data, + sugar=form.sugar.data, + ) + ) + db.session.commit() + print("[DEBUG] New item added") + return redirect(url_for("add_meal.step3", barcode=form.barcode.data)) + print("[DEBUG] Invalid form") + if barcode.isdigit(): + form.barcode.data = barcode + return render_template("add_item.html", form=form) + + +@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"]) + + assert item + 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"], + ) + ) + db.session.commit() + session.pop("meal_type") + session.pop("item_id") + return redirect("/") + + 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) diff --git a/application/add_meal/templates/add_item.html b/application/add_meal/templates/add_item.html new file mode 100644 index 0000000..04b77bc --- /dev/null +++ b/application/add_meal/templates/add_item.html @@ -0,0 +1,49 @@ +{% extends "base.html" %} + +{% block content %} +
+ {{ form.hidden_tag() }} + +
+ {{ form.barcode.label(class="form-label") }} + {{ form.barcode(class="form-control-plaintext", readonly=true) }} +
+ +
+ {{ form.name.label(class="form-label") }} + {{ form.name(class="form-control") }} +
+ +
+ {{ form.energy.label(class="form-label") }} + {{ form.energy(class="form-control") }} +
+ +
+ {{ form.protein.label(class="form-label") }} + {{ form.protein(class="form-control") }} +
+ +
+ {{ form.carbs.label(class="form-label") }} + {{ form.carbs(class="form-control") }} +
+ +
+ {{ form.sugar.label(class="form-label") }} + {{ form.sugar(class="form-control") }} +
+ +
+ {{ form.fat.label(class="form-label") }} + {{ form.fat(class="form-control") }} +
+ +
+ {{ form.saturated_fat.label(class="form-label") }} + {{ form.saturated_fat(class="form-control") }} +
+ + {{ form.submit(class="btn btn-primary") }} +
+{% endblock%} \ No newline at end of file diff --git a/application/add_meal/templates/scan_barcode.html b/application/add_meal/templates/scan_barcode.html new file mode 100644 index 0000000..a62dfd9 --- /dev/null +++ b/application/add_meal/templates/scan_barcode.html @@ -0,0 +1,62 @@ +{% extends "base.html" %} + +{% block content %} +
+
+

Barcode Scanner

+

Use your camera to scan barcodes

+
+ +
+ +
+ +
+ + +
+
+ + + +{% endblock %} \ No newline at end of file diff --git a/application/add_meal/templates/step4.html b/application/add_meal/templates/step4.html new file mode 100644 index 0000000..85cd332 --- /dev/null +++ b/application/add_meal/templates/step4.html @@ -0,0 +1,23 @@ +{% extends "base.html" %} + +{% block content %} +

{{ tod }}

+

{{ item.name }}

+ +
+ {{ form.hidden_tag() }} + +
+
+ {{item_id}} +
+
+ +
+ {{ form.amount.label(class="form-label") }} + {{ form.amount(class="form-control") }} +
+ + {{ form.submit(class="btn btn-primary") }} +
+{% endblock%} \ No newline at end of file diff --git a/application/user/routes.py b/application/user/routes.py index b376641..f980988 100644 --- a/application/user/routes.py +++ b/application/user/routes.py @@ -47,21 +47,10 @@ def delete_food_item(id: int): return redirect(url_for("user.dashboard")) -fields = [ - "barcode", - "name", - "energy", - "protein", - "carbs", - "sugar", - "fat", - "saturated_fat", -] - - @user_bp.route("/add_food_item/", methods=["GET", "POST"]) def add_food_item(barcode): form = FoodItemForm(barcode=barcode) + print(form) if form.validate_on_submit(): print("[DEBUG] Valid form") @@ -92,6 +81,7 @@ def add_food_item(barcode): else: print("[DEBUG] Invalid form") if form.barcode.data: + print("1") return render_template("add_food_item.html", form=form) else: return redirect("/") @@ -217,7 +207,7 @@ def add_meal(): @user_bp.route("/", methods=["GET"]) -def test(): +def daily_log(): today = datetime.now(timezone.utc).date() logs_today = current_user.food_logs.filter_by(date_=today).all() logs = [[], [], [], []] @@ -225,7 +215,7 @@ def test(): logs[log.part_of_day].append(log) print(logs) return render_template( - "test.html", date=(today.strftime("%d/%m/%y")), logs=logs + "daily_log.html", date=(today.strftime("%d/%m/%y")), logs=logs ) diff --git a/application/user/templates/test.html b/application/user/templates/daily_log.html similarity index 70% rename from application/user/templates/test.html rename to application/user/templates/daily_log.html index 2b26b5c..6769d6a 100644 --- a/application/user/templates/test.html +++ b/application/user/templates/daily_log.html @@ -25,11 +25,11 @@ Food Nutritional Info

Breakfast

- Add + Add
{% for log in logs[0] %} -

{{log.food_item.name}}

+

{{log.food_item.name}} - {{log.amount}}

{% endfor %}
@@ -37,33 +37,33 @@ Food Nutritional Info

Lunch

- Add + Add
{% for log in logs[1] %} -

{{log.food_item.name}}

+

{{log.food_item.name}} - {{log.amount}}

{% endfor %}

Dinner

- Add + Add
{% for log in logs[2] %} -

{{log.food_item.name}}

+

{{log.food_item.name}} - {{log.amount}}

{% endfor %}

Snacks

- Add + Add
{% for log in logs[3] %} -

{{log.food_item.name}}

+

{{log.food_item.name}} - {{log.amount}}

{% endfor %}
diff --git a/application/user/templates/get_item.html b/application/user/templates/get_item.html index 51d3b9a..6fac4cf 100644 --- a/application/user/templates/get_item.html +++ b/application/user/templates/get_item.html @@ -55,7 +55,7 @@ if (!response.ok) { throw new Error('Network response was not OK'); } - return response.json(); // or response.text(), response.blob(), etc. + return response.json(); }) .then(data => { const baseURL2 = "{{url_for('user.select_item', item_id='0')}}" diff --git a/forms.py b/forms.py index 7dcd4f4..f5781eb 100644 --- a/forms.py +++ b/forms.py @@ -16,17 +16,19 @@ class LoginForm(FlaskForm): class FoodItemForm(FlaskForm): - barcode = StringField("Barcode", validators=[Optional()]) + barcode = StringField("Barcode", validators=[InputRequired()]) name = StringField("Product Name", validators=[DataRequired()]) energy = IntegerField("Energy per 100g", validators=[InputRequired()]) protein = FloatField("protein per 100g", validators=[InputRequired()]) carbs = FloatField("carbs per 100g", validators=[InputRequired()]) sugar = FloatField("sugar per 100g", validators=[Optional()]) fat = FloatField("fat per 100g", validators=[InputRequired()]) - saturated_fat = FloatField("saturated_fat per 100g") + saturated_fat = FloatField( + "saturated_fat per 100g", validators=[Optional()] + ) submit = SubmitField("Add Item") class FoodLogForm(FlaskForm): - amount = IntegerField("amount of food (g/ml)") + amount = FloatField("amount of food (g/ml)", validators=[DataRequired()]) submit = SubmitField("Log Item") diff --git a/models.py b/models.py index cb5292b..d3b7500 100644 --- a/models.py +++ b/models.py @@ -120,13 +120,13 @@ class FoodLog(db.Model): ) 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.Integer, nullable=False) + amount = db.Column(db.Float, nullable=False) def __init__( self, food_item_id: int, user_id: int, - amount: int, + amount: float, part_of_day: int, date_: Optional[date] = None, ):