14 Commits

Author SHA1 Message Date
c7395b07d9 Move logout route to auth blueprint
The logout route was relocated from the main app to the auth blueprint for better organization. The logout link in the base template was updated to reference the new route location.
2025-08-11 18:05:30 +02:00
97ff4acf02 Add change password functionality for users
Introduces a change password route, form, and template, allowing authenticated users to update their password. Updates the User model with a method to set the must_change_password flag. Adjusts login and navigation logic to support the new flow and ensures users are redirected to change their password if required.
2025-08-11 18:03:18 +02:00
0da580faf1 Refactor login flow to use auth blueprint
Moved login route and logic from app.py to application/auth/routes.py under the 'auth' blueprint. Updated all references to the login route to use 'auth.login'. Added a dedicated login.html template under application/auth/templates. Adjusted login_required utility and default_return logic for consistency.
2025-08-11 17:43:46 +02:00
ea2ea27d9e Refactor login_required and add auth blueprint
Moved the login_required logic to a new utils.py for reuse. Added a new auth blueprint and registered it in app.py. Updated user blueprint to use the shared login_required function.
2025-08-11 17:33:47 +02:00
cd9ae72864 Reapply "Add must_change_password field to User model"
This reverts commit 47241e341e.
2025-08-11 17:20:45 +02:00
47241e341e Revert "Add must_change_password field to User model"
This reverts commit 42747df92f.
2025-08-11 17:17:30 +02:00
42747df92f Add must_change_password field to User model
Introduces a new boolean column 'must_change_password' to the User model and database schema. This field enforces password change requirements for users and is included in the model's constructor and migration.
2025-08-11 17:17:10 +02:00
fda5f8e17b Update daily_log.html
Added a confirm button
2025-08-11 16:56:51 +02:00
18fa0fdb2d Remove admin food item and barcode test routes and templates
Deleted the /food_items and /barcode_test routes from admin, along with their associated templates and the delete_food functionality. This streamlines the admin blueprint by removing unused or deprecated features.
2025-08-11 16:56:40 +02:00
4d34049850 Change energy_100 column type to Float in food_item
This migration alters the 'energy_100' column in the 'food_item' table from INTEGER to Float to allow for decimal values. The downgrade reverses this change.
2025-08-11 16:52:57 +02:00
3f9bd8984d Change energy field from integer to float
Updated the FoodItem model and FoodItemForm to use float for the energy field instead of integer.
2025-08-11 16:51:31 +02:00
5a0dbef28f Update forms.py
added input mode to form to indicate a decimal value is expected for each value
2025-08-11 16:50:19 +02:00
93406db07e Update add_item.html
Changed the order of macros when adding new item
2025-08-11 16:43:00 +02:00
72fe1b602b Update routes.py
Fixed a bug where redirect was incorrect after adding an item
2025-08-11 16:36:51 +02:00
17 changed files with 314 additions and 186 deletions

36
app.py
View File

@@ -1,28 +1,25 @@
from flask import ( from flask import (
render_template,
redirect, redirect,
url_for, url_for,
request,
send_from_directory, send_from_directory,
) )
from flask_login import ( from flask_login import (
login_required, login_required,
logout_user, logout_user,
login_user,
current_user, current_user,
) )
from forms import LoginForm
from models import User from models import User
from application import db, app, login_manager from application import db, app, login_manager
from application.admin.routes import admin_bp from application.admin.routes import admin_bp
from application.user.routes import user_bp from application.user.routes import user_bp
from application.add_meal.routes import bp as add_meal_bp from application.add_meal.routes import bp as add_meal_bp
from application.auth.routes import bp as auth_bp
from typing import Optional from typing import Optional
# Config # Config
app.config["SECRET_KEY"] = "Stef123" app.config["SECRET_KEY"] = "Stef123"
login_manager.login_view = "login" # type: ignore login_manager.login_view = "auth.login" # type: ignore
@login_manager.user_loader # type: ignore @login_manager.user_loader # type: ignore
@@ -34,6 +31,7 @@ def load_user(user_id: int):
app.register_blueprint(admin_bp) app.register_blueprint(admin_bp)
app.register_blueprint(user_bp) app.register_blueprint(user_bp)
app.register_blueprint(add_meal_bp) app.register_blueprint(add_meal_bp)
app.register_blueprint(auth_bp)
# Routes # Routes
@@ -49,7 +47,7 @@ def default_return(next_page: Optional[str] = None):
@app.route("/") @app.route("/")
@login_required @login_required
def index(): def index():
return redirect(url_for("login")) return redirect(url_for("auth.login"))
@app.route("/favicon.ico") @app.route("/favicon.ico")
@@ -57,32 +55,6 @@ def favicon():
return send_from_directory("static", "favicon.ico") return send_from_directory("static", "favicon.ico")
@app.route("/login", methods=["GET", "POST"])
def login():
if current_user.is_authenticated:
return default_return()
form = LoginForm()
if form.validate_on_submit():
user = User.query.filter_by(username=form.username.data).first()
if user and user.check_password(password=form.password.data):
# User found and password correct
next_page = request.args.get("next") # Get next page if given
login_user(user) # Log in the user
return default_return(next_page=next_page)
else:
pass
# invalid user
return render_template("login.html", form=form)
@app.route("/logout")
@login_required
def logout():
logout_user()
return redirect(url_for("index"))
# Run # Run
if __name__ == "__main__": if __name__ == "__main__":
# If there are no users, create admin account # If there are no users, create admin account

View File

@@ -28,7 +28,7 @@ bp = Blueprint(
@bp.before_request @bp.before_request
def login_required(): def login_required():
if not current_user.is_authenticated: if not current_user.is_authenticated:
return redirect(url_for("login")) return redirect(url_for("auth.login"))
@bp.route("/select_meal/<int:meal_type>", methods=["GET"]) @bp.route("/select_meal/<int:meal_type>", methods=["GET"])
@@ -117,6 +117,7 @@ def step3_alt1_post(input: str):
) )
db.session.commit() db.session.commit()
print("[DEBUG] New FoodItem Added") print("[DEBUG] New FoodItem Added")
input = barcode if barcode else name # update input
else: else:
print(f"Item exists: {item.barcode} {item.name}") print(f"Item exists: {item.barcode} {item.name}")

View File

@@ -20,8 +20,13 @@
</div> </div>
<div class="mb-3"> <div class="mb-3">
{{ form.protein.label(class="form-label") }} {{ form.fat.label(class="form-label") }}
{{ form.protein(class="form-control") }} {{ form.fat(class="form-control") }}
</div>
<div class="mb-3">
{{ form.saturated_fat.label(class="form-label") }}
{{ form.saturated_fat(class="form-control") }}
</div> </div>
<div class="mb-3"> <div class="mb-3">
@@ -35,13 +40,8 @@
</div> </div>
<div class="mb-3"> <div class="mb-3">
{{ form.fat.label(class="form-label") }} {{ form.protein.label(class="form-label") }}
{{ form.fat(class="form-control") }} {{ form.protein(class="form-control") }}
</div>
<div class="mb-3">
{{ form.saturated_fat.label(class="form-label") }}
{{ form.saturated_fat(class="form-control") }}
</div> </div>
{{ form.submit(class="btn btn-primary") }} {{ form.submit(class="btn btn-primary") }}

View File

@@ -1,7 +1,5 @@
from flask import Blueprint, render_template, abort, redirect, url_for from flask import Blueprint, abort
from flask_login import current_user from flask_login import current_user
from models import FoodItem
from application import db
admin_bp = Blueprint( admin_bp = Blueprint(
"admin", "admin",
@@ -15,24 +13,3 @@ admin_bp = Blueprint(
def admin_required(): def admin_required():
if not current_user.is_admin: if not current_user.is_admin:
abort(403) abort(403)
@admin_bp.route("/food_items", methods=["GET"])
def food_items():
items = FoodItem.query.all()
return render_template("food_items.html", items=items)
@admin_bp.route("/barcode_test", methods=["GET"])
def barcode_test():
return render_template("barcode_test.html")
@admin_bp.route("/delete_food/<int:id>", methods=["POST"])
def delete_food(id):
item = FoodItem.query.get(id)
if item:
if item.owner_id == current_user.id:
db.session.delete(item)
db.session.commit()
return redirect(url_for("admin.food_items"))

View File

@@ -1,55 +0,0 @@
{% extends "base.html" %}
{% block title %}
ZXing Barcode Scanner
{% endblock %}
{% block content %}
<div class="container text-center">
<h1 class="mb-4">📷 ZXing Barcode Scanner</h1>
<div class="mb-3">
<video id="video" class="border rounded shadow-sm" width="100%" style="max-width: 500px;"></video>
</div>
<div class="mb-3">
<button id="startButton" class="btn btn-primary">Start Scanning</button>
<button id="stopButton" class="btn btn-danger ms-2">Stop</button>
</div>
<div>
<h5>Result:</h5>
<p id="result" class="fw-bold text-success"></p>
</div>
</div>
<script type="module">
import { BrowserMultiFormatReader } from 'https://cdn.jsdelivr.net/npm/@zxing/library@0.21.3/+esm';
const codeReader = new BrowserMultiFormatReader();
const videoElement = document.getElementById('video');
const resultElement = document.getElementById('result');
document.getElementById('startButton').addEventListener('click', async () => {
await navigator.mediaDevices.getUserMedia({ video: true });
console.log('[DEBUG] Start button clicked');
const devices = await codeReader.listVideoInputDevices();
console.log('[DEBUG] Cameras found:', devices);
const selectedDeviceId = devices[0]?.deviceId;
if (!selectedDeviceId) {
alert('No camera found!');
return;
}
codeReader.decodeFromVideoDevice(selectedDeviceId, videoElement, (result, err, controls) => {
if (result) {
resultElement.textContent = result.getText();
controls.stop();
}
});
});
document.getElementById('stopButton').addEventListener('click', () => {
codeReader.reset();
resultElement.textContent = '';
});
</script>
{% endblock %}

View File

@@ -1,47 +0,0 @@
{% extends "base.html" %}
{% block title %}
Food Nutritional Info
{% endblock %}
{% block content %}
<div class="container mt-5">
<h1 class="mb-4">Food Nutritional Information (per 100g/100ml)</h1>
<div class="table-responsive">
<table class="table table-bordered table-hover align-middle">
<thead class="table-dark">
<tr>
<th>Name</th>
<th>Energy (kcal)</th>
<th>fat (g)</th>
<th>Saturated fat (g)</th>
<th>Sugars (g)</th>
<th>Carbs (g)</th>
<th>Protein (g)</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
{% for food in items %}
<tr>
<td class="bg-body-tertiary">{{ food.name }}</td>
<td class="bg-body-tertiary">{{ food.energy_100 }}</td>
<td class="bg-body-tertiary">{{ food.fat_100 }}</td>
<td class="bg-body-tertiary">{{ food.saturated_fat_100 }}</td>
<td class="bg-body-tertiary">{{ food.sugar_100 }}</td>
<td class="bg-body-tertiary">{{ food.carbs_100 }}</td>
<td class="bg-body-tertiary">{{ food.protein_100 }}</td>
<td class="bg-body-tertiary">
<form method="POST" action="{{ url_for('admin.delete_food', id=food.id) }}"
onsubmit="return confirm('Are you sure you want to delete this item?');">
<button type="submit" class="btn btn-danger btn-sm">Delete</button>
</form>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
{% endblock%}

View File

@@ -0,0 +1,59 @@
from flask import Blueprint, request, render_template, redirect, url_for
from flask_login import current_user, login_user, logout_user
from forms import LoginForm, ChangePasswordForm
from models import User
from application.utils import default_return
from application import db
bp = Blueprint(
"auth",
__name__,
template_folder="templates",
)
@bp.route("/login", methods=["GET", "POST"])
def login():
if current_user.is_authenticated:
return default_return()
form = LoginForm()
if form.validate_on_submit():
user = User.query.filter_by(username=form.username.data).first()
if user and user.check_password(password=form.password.data):
# User found and password correct
next_page = request.args.get("next") # Get next page if given
login_user(user) # Log in the user
return default_return(next_page=next_page)
else:
pass
# invalid user
return render_template("login.html", form=form)
@bp.route("/change_password", methods=["GET", "POST"])
def change_password():
if not current_user.is_authenticated:
return redirect(url_for("auth.login"))
form = ChangePasswordForm()
if form.validate_on_submit():
cur_check = current_user.check_password(
password=form.current_password.data
)
eq_check = form.new_password.data == form.confirm_password.data
if cur_check and eq_check:
current_user.change_password(form.new_password.data)
current_user.set_pw_change(False)
db.session.commit()
return default_return()
return render_template("change_password.html", form=form)
@bp.route("/logout")
def logout():
if not current_user.is_authenticated:
return redirect(url_for("auth.login"))
logout_user()
return redirect(url_for("index"))

View File

@@ -0,0 +1,46 @@
{% extends "base.html" %}
{% block content %}
<div class="container d-flex justify-content-center align-items-center">
<div class="card shadow-sm p-4" style="width: 100%; max-width: 400px;">
<h3 class="mb-4 text-center">Login</h3>
<form method="post">
{{ form.hidden_tag() }}
<div class="mb-3">
{{ form.current_password.label(class="form-label") }}
{{ form.current_password(class="form-control", placeholder="") }}
{% if form.current_password.errors %}
<div class="text-danger small">
{{ form.current_password.errors[0] }}
</div>
{% endif %}
</div>
<div class="mb-3">
{{ form.new_password.label(class="form-label") }}
{{ form.new_password(class="form-control", placeholder="Enter password") }}
{% if form.new_password.errors %}
<div class="text-danger small">
{{ form.new_password.errors[0] }}
</div>
{% endif %}
</div>
<div class="mb-3">
{{ form.confirm_password.label(class="form-label") }}
{{ form.confirm_password(class="form-control", placeholder="Enter password") }}
{% if form.confirm_password.errors %}
<div class="text-danger small">
{{ form.confirm_password.errors[0] }}
</div>
{% endif %}
</div>
<div class="d-grid">
{{ form.submit(class="btn btn-primary btn-lg") }}
</div>
</form>
</div>
</div>
{% endblock%}

View File

@@ -0,0 +1,36 @@
{% extends "base.html" %}
{% block content %}
<div class="container d-flex justify-content-center align-items-center">
<div class="card shadow-sm p-4" style="width: 100%; max-width: 400px;">
<h3 class="mb-4 text-center">Login</h3>
<form method="post">
{{ form.hidden_tag() }}
<div class="mb-3">
{{ form.username.label(class="form-label") }}
{{ form.username(class="form-control", placeholder="Enter username") }}
{% if form.username.errors %}
<div class="text-danger small">
{{ form.username.errors[0] }}
</div>
{% endif %}
</div>
<div class="mb-3">
{{ form.password.label(class="form-label") }}
{{ form.password(class="form-control", placeholder="Enter password") }}
{% if form.password.errors %}
<div class="text-danger small">
{{ form.password.errors[0] }}
</div>
{% endif %}
</div>
<div class="d-grid">
{{ form.submit(class="btn btn-primary btn-lg") }}
</div>
</form>
</div>
</div>
{% endblock%}

View File

@@ -32,11 +32,11 @@
<ul class="navbar-nav"> <ul class="navbar-nav">
{% if current_user.is_authenticated %} {% if current_user.is_authenticated %}
<li class="nav-item"> <li class="nav-item">
<a class="nav-link" href="{{ url_for('logout') }}">Logout</a> <a class="nav-link" href="{{ url_for('auth.logout') }}">Logout</a>
</li> </li>
{% else %} {% else %}
<li class="nav-item"> <li class="nav-item">
<a class="nav-link" href="{{ url_for('login') }}">Login</a> <a class="nav-link" href="{{ url_for('auth.login') }}">Login</a>
</li> </li>
{% endif %} {% endif %}
<li class="nav-item"> <li class="nav-item">

View File

@@ -12,6 +12,7 @@ from application import db
from forms import FoodItemForm from forms import FoodItemForm
from models import FoodItem, FoodLog from models import FoodItem, FoodLog
from datetime import datetime, timezone, timedelta from datetime import datetime, timezone, timedelta
from application.utils import login_required
user_bp = Blueprint( user_bp = Blueprint(
"user", "user",
@@ -20,10 +21,7 @@ user_bp = Blueprint(
) )
@user_bp.before_request user_bp.before_request(login_required)
def login_required():
if not current_user.is_authenticated:
return redirect(url_for("login"))
@user_bp.route("/dashboard", methods=["GET"]) @user_bp.route("/dashboard", methods=["GET"])

View File

@@ -196,7 +196,8 @@ Food Nutritional Info
{{ "{:g}".format(log.amount) }} {{ "{:g}".format(log.amount) }}
</div> </div>
<div class="col-auto text-end align-self-start" style="min-width: 80px;"> <div class="col-auto text-end align-self-start" style="min-width: 80px;">
<form method="POST" action="{{url_for('user.remove_log', id=log.id)}}" class="d-inline"> <form method="POST" action="{{url_for('user.remove_log', id=log.id)}}" class="d-inline"
onsubmit="return confirm('Are you sure you want to delete this item?');">
<button type="submit" class="btn btn-sm btn-danger px-3 py-1" <button type="submit" class="btn btn-sm btn-danger px-3 py-1"
title="Delete">&times;</button> title="Delete">&times;</button>
</form> </form>

21
application/utils.py Normal file
View File

@@ -0,0 +1,21 @@
from flask_login import current_user
from flask import redirect, url_for, flash
from typing import Optional
def login_required():
if not current_user.is_authenticated:
return redirect(url_for("auth.login"))
if current_user.must_change_password:
flash("You have to change your password")
return redirect(url_for("auth.change_password"))
return
def default_return(next_page: Optional[str] = None):
return redirect(url_for("user.daily_log"))
if next_page:
return redirect(next_page)
if current_user.is_admin:
return redirect(url_for("admin.food_items"))
return redirect(url_for("dashboard"))

View File

@@ -3,7 +3,6 @@ from wtforms import (
StringField, StringField,
PasswordField, PasswordField,
SubmitField, SubmitField,
IntegerField,
FloatField, FloatField,
) )
from wtforms.validators import DataRequired, InputRequired, Optional from wtforms.validators import DataRequired, InputRequired, Optional
@@ -12,19 +11,52 @@ from wtforms.validators import DataRequired, InputRequired, Optional
class LoginForm(FlaskForm): class LoginForm(FlaskForm):
username = StringField("Username", validators=[DataRequired()]) username = StringField("Username", validators=[DataRequired()])
password = PasswordField("Password", validators=[DataRequired()]) password = PasswordField("Password", validators=[DataRequired()])
submit = SubmitField("Login") submit = SubmitField("Log in")
class ChangePasswordForm(FlaskForm):
current_password = PasswordField(
"Current password", validators=[DataRequired()]
)
new_password = PasswordField("New password", validators=[DataRequired()])
confirm_password = PasswordField(
"Confirm new password", validators=[DataRequired()]
)
submit = SubmitField("Change password")
class FoodItemForm(FlaskForm): class FoodItemForm(FlaskForm):
barcode = StringField("Barcode", validators=[Optional()]) barcode = StringField("Barcode", validators=[Optional()])
name = StringField("Product Name", validators=[DataRequired()]) name = StringField("Product Name", validators=[DataRequired()])
energy = IntegerField("Energy per 100g", validators=[InputRequired()]) energy = FloatField(
protein = FloatField("protein per 100g", validators=[InputRequired()]) "Energy per 100g",
carbs = FloatField("carbs per 100g", validators=[InputRequired()]) validators=[InputRequired()],
sugar = FloatField("sugar per 100g", validators=[Optional()]) render_kw={"inputmode": "decimal"},
fat = FloatField("fat per 100g", validators=[InputRequired()]) )
protein = FloatField(
"protein per 100g",
validators=[InputRequired()],
render_kw={"inputmode": "decimal"},
)
carbs = FloatField(
"carbs per 100g",
validators=[InputRequired()],
render_kw={"inputmode": "decimal"},
)
sugar = FloatField(
"sugar per 100g",
validators=[Optional()],
render_kw={"inputmode": "decimal"},
)
fat = FloatField(
"fat per 100g",
validators=[InputRequired()],
render_kw={"inputmode": "decimal"},
)
saturated_fat = FloatField( saturated_fat = FloatField(
"saturated_fat per 100g", validators=[Optional()] "saturated_fat per 100g",
validators=[Optional()],
render_kw={"inputmode": "decimal"},
) )
submit = SubmitField("Add Item") submit = SubmitField("Add Item")

View File

@@ -0,0 +1,40 @@
"""empty message
Revision ID: 101002a6ef17
Revises: dea130d45cec
Create Date: 2025-08-11 17:16:34.617851
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = "101002a6ef17"
down_revision = "dea130d45cec"
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table("user", schema=None) as batch_op:
batch_op.add_column(
sa.Column(
"must_change_password",
sa.Boolean(),
nullable=False,
server_default="1",
)
)
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table("user", schema=None) as batch_op:
batch_op.drop_column("must_change_password")
# ### end Alembic commands ###

View File

@@ -0,0 +1,38 @@
"""empty message
Revision ID: dea130d45cec
Revises: f5fbbe915d51
Create Date: 2025-08-11 16:51:55.485569
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = 'dea130d45cec'
down_revision = 'f5fbbe915d51'
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('food_item', schema=None) as batch_op:
batch_op.alter_column('energy_100',
existing_type=sa.INTEGER(),
type_=sa.Float(),
existing_nullable=False)
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('food_item', schema=None) as batch_op:
batch_op.alter_column('energy_100',
existing_type=sa.Float(),
type_=sa.INTEGER(),
existing_nullable=False)
# ### end Alembic commands ###

View File

@@ -12,17 +12,23 @@ class User(UserMixin, db.Model):
username = db.Column(db.String(150), unique=True, nullable=False) username = db.Column(db.String(150), unique=True, nullable=False)
password = db.Column(db.String, nullable=False) password = db.Column(db.String, nullable=False)
is_admin = db.Column(db.Boolean, nullable=False, default=False) 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_items = db.relationship("FoodItem", lazy="dynamic", backref="user")
food_logs = db.relationship("FoodLog", lazy="dynamic", backref="user") food_logs = db.relationship("FoodLog", lazy="dynamic", backref="user")
def __init__( def __init__(
self, username: str, password: str, is_admin: bool = False self,
username: str,
password: str,
is_admin: bool = False,
must_change_password: bool = False,
) -> None: ) -> None:
super().__init__() super().__init__()
self.username = username self.username = username
self.password = generate_password_hash(password=password) self.password = generate_password_hash(password=password)
self.is_admin = is_admin self.is_admin = is_admin
self.must_change_password = must_change_password
def check_password(self, password: str) -> bool: def check_password(self, password: str) -> bool:
return check_password_hash(pwhash=self.password, password=password) return check_password_hash(pwhash=self.password, password=password)
@@ -30,6 +36,9 @@ class User(UserMixin, db.Model):
def change_password(self, password: str) -> None: def change_password(self, password: str) -> None:
self.password = generate_password_hash(password=password) self.password = generate_password_hash(password=password)
def set_pw_change(self, change: bool) -> None:
self.must_change_password = change
class Unit(db.Model): class Unit(db.Model):
__tablename__ = "unit" __tablename__ = "unit"
@@ -45,7 +54,7 @@ class FoodItem(db.Model):
owner_id = db.Column(db.Integer, db.ForeignKey("user.id"), nullable=False) owner_id = db.Column(db.Integer, db.ForeignKey("user.id"), nullable=False)
name = db.Column(db.String(150), nullable=False) name = db.Column(db.String(150), nullable=False)
energy_100 = db.Column(db.Integer, nullable=False) energy_100 = db.Column(db.Float, nullable=False)
protein_100 = db.Column(db.Float, nullable=False) protein_100 = db.Column(db.Float, nullable=False)
carbs_100 = db.Column(db.Float, nullable=False) carbs_100 = db.Column(db.Float, nullable=False)
sugar_100 = db.Column(db.Float) sugar_100 = db.Column(db.Float)
@@ -68,7 +77,7 @@ class FoodItem(db.Model):
self, self,
name: str, name: str,
owner_id: int, owner_id: int,
energy: int, energy: float,
protein: float, protein: float,
carbs: float, carbs: float,
fat: float, fat: float,