19 Commits

Author SHA1 Message Date
7ff345d3a2 Do not update pip 2025-08-14 04:07:15 +02:00
Stef
944ba17b31 Merge pull request #4 from StefBuwalda/development
Removed accidental packages from requirements.txt
2025-08-14 04:05:24 +02:00
8820fc07d7 Removed accidental packages from requirements.txt 2025-08-14 04:04:48 +02:00
Stef
072dd2c651 Merge pull request #3 from StefBuwalda/development
Update requirements.txt
2025-08-14 03:46:54 +02:00
8acb8453ea Update requirements.txt 2025-08-14 03:46:26 +02:00
Stef
fb160d364a Merge pull request #2 from StefBuwalda/development
Hopefully add caching to docker build action
2025-08-14 03:42:12 +02:00
fdf264b9c5 Update docker-ghcr.yml 2025-08-14 03:41:34 +02:00
91595ccc11 Caching pip downloads (hopefully) 2025-08-14 03:39:39 +02:00
6c01c6a923 Update Dockerfile 2025-08-14 03:34:31 +02:00
Stef
5a9e1f77c7 Merge pull request #1 from StefBuwalda/development
Adds a new daily log page made with AI assistance.
2025-08-14 03:18:58 +02:00
2454bc61cb Moved the bar width calculation from jinja to the dashboard values generation 2025-08-14 03:15:02 +02:00
85297daaaf Add new daily log dashboard and macro summary
Introduces a new route and template for an enhanced daily log dashboard with macro nutrient summary. Refactors FoodItem model to add type annotations and a macros() method, and updates form handling to default missing values to zero. Also adds a navigation link to the new dashboard in the base template.
2025-08-14 02:49:06 +02:00
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
16 changed files with 409 additions and 71 deletions

View File

@@ -14,9 +14,11 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
# Checkout code
- name: Checkout code - name: Checkout code
uses: actions/checkout@v4.2.2 uses: actions/checkout@v4.2.2
# Log in to GHCR
- name: Log in to GitHub Container Registry - name: Log in to GitHub Container Registry
uses: docker/login-action@v3.5.0 uses: docker/login-action@v3.5.0
with: with:
@@ -24,13 +26,22 @@ jobs:
username: ${{ github.actor }} username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }} password: ${{ secrets.GITHUB_TOKEN }}
# Set up Docker Buildx (needed for ARM64 cross-build)
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3.11.1 uses: docker/setup-buildx-action@v3.11.1
# Build and push Docker image with caching
- name: Build and push ARM64 image - name: Build and push ARM64 image
uses: docker/build-push-action@v6.18.0 uses: docker/build-push-action@v6.18.0
with: with:
context: . context: .
push: true push: true
platforms: linux/arm64 platforms: linux/arm64
tags: ghcr.io/stefbuwalda/cal_counter:arm64,ghcr.io/stefbuwalda/cal_counter:latest tags: |
ghcr.io/stefbuwalda/cal_counter:arm64
ghcr.io/stefbuwalda/cal_counter:latest
cache-from: type=registry,ref=ghcr.io/stefbuwalda/cal_counter:cache
cache-to: type=registry,ref=ghcr.io/stefbuwalda/cal_counter:cache,mode=max
build-args: |
PIP_NO_CACHE_DIR=1

View File

@@ -2,8 +2,13 @@ FROM python:3.12-slim
# Everything will be done in /app (Not in the main OS Image) # Everything will be done in /app (Not in the main OS Image)
WORKDIR /app WORKDIR /app
COPY . .
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt RUN pip install --no-cache-dir -r requirements.txt
COPY . .
RUN chmod +x ./entrypoint.sh RUN chmod +x ./entrypoint.sh
ENV FLASK_APP=app.py ENV FLASK_APP=app.py

37
app.py
View File

@@ -1,28 +1,24 @@
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,
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 +30,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 +46,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 +54,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"])

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

View File

@@ -25,6 +25,9 @@
<li class="nav-item"> <li class="nav-item">
<a class="nav-link" href="{{ url_for('user.daily_log') }}">Daily Log</a> <a class="nav-link" href="{{ url_for('user.daily_log') }}">Daily Log</a>
</li> </li>
<li class="nav-item">
<a class="nav-link" href="{{ url_for('user.daily_log2') }}">Daily Log (new)</a>
</li>
<li class="nav-item"> <li class="nav-item">
<a class="nav-link" href="{{ url_for('user.dashboard') }}">Dashboard</a> <a class="nav-link" href="{{ url_for('user.dashboard') }}">Dashboard</a>
</li> </li>
@@ -32,11 +35,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,9 @@ 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
from typing import cast
from numpy import array
user_bp = Blueprint( user_bp = Blueprint(
"user", "user",
@@ -20,10 +23,58 @@ user_bp = Blueprint(
) )
@user_bp.before_request def macro_arr_to_json(data: list[float]):
def login_required(): assert len(data) == 4
if not current_user.is_authenticated: cal = data[0]
return redirect(url_for("login")) pro = data[3]
car = data[2]
fat = data[1]
macros = [
{
"name": "Calories",
"current": cal,
"target": 2000,
"bar_width": 100 - abs(cal / 20 - 100),
"bar_width_overflow": max(0, cal / 20 - 100),
"unit": " kcal",
"color": "bg-calories",
"overflow_color": "bg-calories-dark",
},
{
"name": "Protein",
"current": pro,
"target": 150,
"bar_width": 100 - abs(pro / 1.5 - 100),
"bar_width_overflow": max(0, pro / 1.5 - 100),
"unit": "g",
"color": "bg-protein",
"overflow_color": "bg-protein-dark",
},
{
"name": "Carbs",
"current": car,
"target": 250,
"bar_width": 100 - abs(car / 2.5 - 100),
"bar_width_overflow": max(0, car / 2.5 - 100),
"unit": "g",
"color": "bg-carbs",
"overflow_color": "bg-carbs-dark",
},
{
"name": "Fat",
"current": fat,
"target": 70,
"bar_width": 100 - abs(fat / 0.7 - 100),
"bar_width_overflow": max(0, fat / 0.7 - 100),
"unit": "g",
"color": "bg-fat",
"overflow_color": "bg-fat-dark",
},
]
return macros
user_bp.before_request(login_required)
@user_bp.route("/dashboard", methods=["GET"]) @user_bp.route("/dashboard", methods=["GET"])
@@ -102,6 +153,18 @@ def daily_log(offset: int = 0):
) )
@user_bp.route("/daily_log2", methods=["GET"])
def daily_log2():
today = datetime.now(timezone.utc).date()
logs_today = current_user.food_logs.filter_by(date_=today).all()
macros = array((0.0, 0.0, 0.0, 0.0))
for log in logs_today:
item = cast(FoodItem, log.food_item)
macros += array(item.macros()) / 100 * log.amount
macros = macro_arr_to_json(macros.tolist())
return render_template("daily_log2.html", macros=macros, logs=logs_today)
@user_bp.route("/remove_log/<int:id>", methods=["POST"]) @user_bp.route("/remove_log/<int:id>", methods=["POST"])
def remove_log(id: int): def remove_log(id: int):
log = db.session.get(FoodLog, id) log = db.session.get(FoodLog, id)

View File

@@ -0,0 +1,67 @@
{% extends 'base.html' %}
{% block title %}Daily Calorie Dashboard{% endblock %}
{% block content %}
<link rel="stylesheet" href="{{ url_for('static', filename='css/macros.css') }}">
<div class="container my-4">
<h2 class="mb-3">Daily Calorie Dashboard</h2>
<!-- Macro Summary -->
<div class="card p-3 mb-3">
<h5>Macros</h5>
{% for macro in macros %}
<div class="mb-2">
<span class="macro-text">{{ macro.name }}: {{ macro.current }} / {{ macro.target }}</span>
<div class="progress rounded" style="height: 24px;">
<div class="progress-bar bg-danger macro-bar" role="progressbar"
style="width: {{ macro.bar_width_overflow }}%">
{{ macro.current - macro.target }}{{ macro.unit }}
</div>
<div class="progress-bar bg-success macro-bar" role="progressbar" style="width: {{ macro.bar_width }}%">
{{ macro.current }}{{ macro.unit }}
</div>
</div>
</div>
{% endfor %}
</div>
<!-- Items List -->
<div class="card p-3">
<h5>Items Eaten Today</h5>
<div class="list-group list-group-flush">
{% for log in logs %}
<div class="list-group-item item-row d-flex justify-content-between align-items-center bg-dark text-light">
<span>({{ log.amount }}g) {{ log.food_item.name }}</span>
<span>{{ log.food_item.energy_100 }} kcal</span>
</div>
{% endfor %}
</div>
</div>
</div>
<!-- Bottom Navigation Buttons -->
<div class="container-fluid fixed-bottom py-2">
<div class="d-flex p-3">
<!-- Left Button -->
<a href="" class="btn btn-outline-light flex-fill me-2 rounded-pill">
Previous
</a>
<!-- Center Button (highlighted) -->
<a href="{{ url_for('add_meal.step1', meal_type=0) }}"
class="btn btn-success flex-fill mx-2 fw-bold rounded-pill">
Add Item
</a>
<!-- Right Button -->
<a href="" class="btn btn-outline-light flex-fill ms-2 rounded-pill">
Next
</a>
</div>
</div>
{% endblock %}

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

@@ -14,6 +14,17 @@ class LoginForm(FlaskForm):
submit = SubmitField("Log in") 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()])

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

@@ -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"
@@ -40,17 +49,18 @@ class Unit(db.Model):
class FoodItem(db.Model): class FoodItem(db.Model):
__tablename__ = "food_item" __tablename__ = "food_item"
id = db.Column(db.Integer, primary_key=True) id = db.Column(db.Integer, primary_key=True)
barcode = db.Column(db.String) barcode = db.Column(db.String)
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.Float, nullable=False) energy_100: float = db.Column(db.Float, nullable=False)
protein_100 = db.Column(db.Float, nullable=False) protein_100: float = db.Column(db.Float, nullable=False)
carbs_100 = db.Column(db.Float, nullable=False) carbs_100: float = db.Column(db.Float, nullable=False)
sugar_100 = db.Column(db.Float) sugar_100: Optional[float] = db.Column(db.Float)
fat_100 = db.Column(db.Float, nullable=False) fat_100: float = db.Column(db.Float, nullable=False)
saturated_fat_100 = db.Column(db.Float) saturated_fat_100: Optional[float] = db.Column(db.Float)
food_logs = db.relationship( food_logs = db.relationship(
"FoodLog", "FoodLog",
@@ -90,26 +100,20 @@ class FoodItem(db.Model):
def updateFromForm(self, form: FoodItemForm): def updateFromForm(self, form: FoodItemForm):
self.name = form.name.data self.name = form.name.data
self.energy_100 = form.energy.data self.energy_100 = form.energy.data or 0
self.protein_100 = form.protein.data self.protein_100 = form.protein.data or 0
self.carbs_100 = form.carbs.data self.carbs_100 = form.carbs.data or 0
self.sugar_100 = form.sugar.data self.sugar_100 = form.sugar.data
self.fat_100 = form.fat.data self.fat_100 = form.fat.data or 0
self.saturated_fat_100 = form.saturated_fat.data self.saturated_fat_100 = form.saturated_fat.data
def to_dict(self): def macros(self) -> tuple[float, float, float, float]:
return { return (
"id": self.id, self.energy_100,
"barcode": self.barcode, self.fat_100,
"name": self.name, self.carbs_100,
"owner_id": self.owner_id, self.protein_100,
"energy_100": self.energy_100, )
"protein_100": self.protein_100,
"carbs_100": self.carbs_100,
"sugar_100": self.sugar_100,
"fat_100": self.fat_100,
"saturated_fat_100": self.saturated_fat_100,
}
class FoodLog(db.Model): class FoodLog(db.Model):

View File

@@ -1,4 +1,4 @@
alembic==1.16.1 alembic==1.16.4
blinker==1.9.0 blinker==1.9.0
click==8.2.1 click==8.2.1
colorama==0.4.6 colorama==0.4.6
@@ -7,12 +7,13 @@ Flask-Login==0.6.3
Flask-Migrate==4.1.0 Flask-Migrate==4.1.0
Flask-SQLAlchemy==3.1.1 Flask-SQLAlchemy==3.1.1
Flask-WTF==1.2.2 Flask-WTF==1.2.2
greenlet==3.2.2 greenlet==3.2.4
itsdangerous==2.2.0 itsdangerous==2.2.0
Jinja2==3.1.6 Jinja2==3.1.6
Mako==1.3.10 Mako==1.3.10
MarkupSafe==3.0.2 MarkupSafe==3.0.2
SQLAlchemy==2.0.41 numpy==2.3.2
typing_extensions==4.13.2 SQLAlchemy==2.0.43
typing_extensions==4.14.1
Werkzeug==3.1.3 Werkzeug==3.1.3
WTForms==3.2.1 WTForms==3.2.1