15 Commits

Author SHA1 Message Date
10c5f0fffd Quick fix for url name issues 2025-10-18 14:52:35 +02:00
b83d2e1bd9 Merge branch 'main' into development 2025-10-18 14:42:51 +02:00
312eda85df Can now set macro target 2025-10-10 19:50:18 +02:00
Stef
e934633370 Development (#12)
* Adjusted GUI of daily dashboard to better deal with float values

* Change password + New base (#10)

* created a new Base template to test with

* Changed out the base and added a new password page

* Password change works, UI needs redisgn

* Daily log now sums up amount per item in a day
2025-10-08 19:12:19 +02:00
Stef
7fb75d46af Merge branch 'main' into development 2025-10-08 19:10:34 +02:00
70eef5b9a2 Daily log now sums up amount per item in a day 2025-10-08 19:08:44 +02:00
Stef
88f553a08e New webpage structure, not yet finished. Change password implemented again (#11)
* Adjusted GUI of daily dashboard to better deal with float values

* Change password + New base (#10)

* created a new Base template to test with

* Changed out the base and added a new password page

* Password change works, UI needs redisgn
2025-10-08 15:38:40 +02:00
Stef
cef3d63ca2 Change password + New base (#10)
* created a new Base template to test with

* Changed out the base and added a new password page

* Password change works, UI needs redisgn
2025-10-08 15:28:50 +02:00
Stef
573437f4cf Adjusted GUI of daily dashboard to better deal with float values (#9) 2025-10-08 11:56:20 +02:00
b379b59eec Adjusted GUI of daily dashboard to better deal with float values 2025-10-08 11:55:56 +02:00
4bc319c32a Revert "Update requirements.txt"
This reverts commit 24a1757166.
2025-08-21 17:05:32 +02:00
24a1757166 Update requirements.txt 2025-08-21 16:55:52 +02:00
5373d373ca Update find_item.html 2025-08-14 17:40:27 +02:00
Stef
dca3ff7efe Merge pull request #8 from StefBuwalda/development
Update find_item.html
2025-08-14 17:38:25 +02:00
7cedd8f74d Update find_item.html 2025-08-14 17:38:10 +02:00
16 changed files with 393 additions and 103 deletions

12
app.py
View File

@@ -1,8 +1,4 @@
from flask import ( from flask import redirect, url_for, send_from_directory, render_template
redirect,
url_for,
send_from_directory,
)
from flask_login import ( from flask_login import (
login_required, login_required,
current_user, current_user,
@@ -54,6 +50,12 @@ def index():
return redirect(url_for("auth.login")) return redirect(url_for("auth.login"))
@app.route("/test")
@login_required
def text():
return render_template("newBase.html")
@app.route("/favicon.ico") @app.route("/favicon.ico")
def favicon(): def favicon():
return send_from_directory("static", "favicon.ico") return send_from_directory("static", "favicon.ico")

View File

@@ -49,8 +49,9 @@ def find_item():
return render_template("find_item.html") return render_template("find_item.html")
# TODO: Switch from this to query parameters / url args
@date_present @date_present
@bp.route("/select_item/<string:input>", methods=["GET"]) @bp.route("/select_item/<path:input>", methods=["GET"])
def select_item(input: str): def select_item(input: str):
# Check if input is a barcode # Check if input is a barcode
if input.isdigit(): if input.isdigit():
@@ -69,7 +70,7 @@ def select_item(input: str):
@date_present @date_present
@bp.route("/add_new_item/<string:input>", methods=["GET"]) @bp.route("/add_new_item/<path:input>", methods=["GET"])
def add_new_item(input: str): def add_new_item(input: str):
form = FoodItemForm() form = FoodItemForm()
@@ -81,7 +82,7 @@ def add_new_item(input: str):
@date_present @date_present
@bp.route("/add_new_item/<string:input>", methods=["POST"]) @bp.route("/add_new_item/<path:input>", methods=["POST"])
def post_new_item(input: str): def post_new_item(input: str):
form = FoodItemForm() form = FoodItemForm()

View File

@@ -2,10 +2,10 @@
{% block content %} {% block content %}
<div class="container d-flex flex-column justify-content-start align-items-center py-4" style="min-height: 100vh;"> <div class="container d-flex flex-column justify-content-start align-items-center py-4" style="min-height: 100vh;">
<div class="card shadow-sm w-100" style="max-width: 480px;"> <div class="card shadow-sm w-100">
<div class="card-body d-flex flex-column"> <div class="card-body d-flex flex-column">
<h5 class="card-title text-center mb-4">Item Scanner</h5> <h5 class="card-title text-center mb-4">Item Scanner</h5>
<video id="camera" autoplay class="w-100 mb-3" style="aspect-ratio: 4/3;" muted></video> <video id="camera" autoplay class="w-100 mb-3" muted></video>
<div class="mb-3"> <div class="mb-3">
<label for="manualSearch" class="form-label">Or search manually</label> <label for="manualSearch" class="form-label">Or search manually</label>

View File

@@ -35,8 +35,8 @@ def login():
return render_template("login.html", form=form) return render_template("login.html", form=form)
@bp.route("/change_password", methods=["GET", "POST"]) @bp.route("/change_pass", methods=["GET", "POST"])
def change_password(): def change_pass():
if not current_user.is_authenticated: if not current_user.is_authenticated:
return redirect(url_for("auth.login")) return redirect(url_for("auth.login"))
@@ -50,8 +50,9 @@ def change_password():
current_user.change_password(form.new_password.data) current_user.change_password(form.new_password.data)
current_user.set_pw_change(False) current_user.set_pw_change(False)
db.session.commit() db.session.commit()
logout_user()
return default_return() return default_return()
return render_template("change_password.html", form=form) return render_template("new_change_password.html", form=form)
@bp.route("/logout") @bp.route("/logout")

View File

@@ -0,0 +1,52 @@
{% extends "base.html" %}
{% block title %}Change Password{%endblock%}
{% block content %}
<style>
.card {
border-radius: 16px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
}
</style>
<div class="d-flex justify-content-center align-items-center">
<div class="card p-4" style="max-width: 400px; width: 90%">
<h1 class="text-center mb-4" style="font-size: 2rem;">Change Your Password</h1>
<form method="POST">
{{ form.hidden_tag() }}
<div class="mb-3">
<label for="current-password" class="form-label">Current Password</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">
<label for="new-password" class="form-label">New Password</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">
<label for="confirm-password" class="form-label">Confirm New Password</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>
{{ form.submit(class="btn btn-primary w-100", label="Update Password") }}
</form>
<p class="text-center text-muted mt-3" style="font-size: 0.85rem;">
Make sure your password is at least 8 characters long and includes a mix of letters and numbers.
</p>
</div>
</div>
{% endblock %}

View File

@@ -10,74 +10,16 @@
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.7/dist/css/bootstrap.min.css" rel="stylesheet"> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.7/dist/css/bootstrap.min.css" rel="stylesheet">
</head> </head>
<body class="bg-body-secondary"> <body class="bg-body-secondary" style="max-width: calc(100vh*9/16); margin: 0 auto;">
<nav class="navbar navbar-expand-lg navbar-dark bg-dark mb-4"> {% include "new_navbar.html" %}
<div class="container-fluid">
<a class="navbar-brand" href="{{ url_for('index') }}">CalCounter</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav"
aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav"> {% include "flash.html" %}
<div class="d-flex w-100">
<ul class="navbar-nav flex-grow-1">
<li class="nav-item">
<a class="nav-link" href="{{ url_for('user.daily_log') }}">Daily Log (new)</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{{ url_for('user.dashboard') }}">Dashboard</a>
</li>
</ul>
<ul class="navbar-nav">
{% if current_user.is_authenticated %}
<li class="nav-item">
<a class="nav-link" href="{{ url_for('auth.logout') }}">Logout</a>
</li>
{% else %}
<li class="nav-item">
<a class="nav-link" href="{{ url_for('auth.login') }}">Login</a>
</li>
{% endif %}
<li class="nav-item">
<button id="toggleTheme" class="btn btn-outline-light">Toggle Theme</button>
</li>
</ul>
</div>
</div>
</div>
</nav>
<div class="container">
{% with messages = get_flashed_messages() %}
{% if messages %}
<div class="alert alert-info">
{% for message in messages %}
<p>{{ message }}</p>
{% endfor %}
</div>
{% endif %}
{% endwith %}
{% block content %} {% block content %}
{% endblock %} {% endblock %}
</div>
<script> {% block footer %}
const html = document.documentElement; {% endblock %}
const savedTheme = localStorage.getItem("theme");
if (savedTheme) {
html.setAttribute("data-bs-theme", savedTheme);
}
document.getElementById("toggleTheme").addEventListener("click", () => {
const current = html.getAttribute("data-bs-theme");
const next = current === "dark" ? "light" : "dark";
html.setAttribute("data-bs-theme", next);
localStorage.setItem("theme", next);
});
</script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>

View File

@@ -0,0 +1,9 @@
{% with messages = get_flashed_messages() %}
{% if messages %}
<div class="alert alert-info">
{% for message in messages %}
<p>{{ message }}</p>
{% endfor %}
</div>
{% endif %}
{% endwith %}

View File

@@ -0,0 +1,48 @@
<nav class="navbar navbar-dark bg-dark mb-4">
<div class="container-fluid">
<a class="navbar-brand" href="{{ url_for('index') }}">CalCounter</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav"
aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<div class="d-flex w-100">
<ul class="navbar-nav flex-grow-1">
<li class="nav-item">
<a class="nav-link" href="{{ url_for('user.daily_log') }}">Daily Log (new)</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{{ url_for('user.dashboard') }}">Dashboard</a>
</li>
</ul>
<ul class="navbar-nav">
<li class="nav-item">
<a class="nav-link" href="{{ url_for('auth.logout') }}">Logout</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{{ url_for('auth.logout') }}">Set</a>
</li>
<li class="nav-item">
<button id="toggleTheme" class="btn btn-outline-light">Toggle Theme</button>
</li>
</ul>
</div>
</div>
</div>
</nav>
<script>
const html = document.documentElement;
const savedTheme = localStorage.getItem("theme");
if (savedTheme) {
html.setAttribute("data-bs-theme", savedTheme);
}
document.getElementById("toggleTheme").addEventListener("click", () => {
const current = html.getAttribute("data-bs-theme");
const next = current === "dark" ? "light" : "dark";
html.setAttribute("data-bs-theme", next);
localStorage.setItem("theme", next);
});
</script>

View File

@@ -0,0 +1,61 @@
<!-- Navbar -->
<nav class="navbar bg-body shadow-sm mb-2">
<div class="container">
<a class="navbar-brand" href="#">TaskManager</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav"
aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav me-auto">
<li class="nav-item">
<a class="nav-link" href="{{ url_for('user.daily_log') }}">Daily Overview</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{{ url_for('user.dashboard') }}">Dashboard</a>
</li>
</ul>
<ul class="navbar-nav ms-auto">
{% if current_user.is_authenticated %}
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" id="accountDropdown" role="button"
data-bs-toggle="dropdown" aria-expanded="false">
Account
</a>
<ul class="dropdown-menu dropdown-menu-end" aria-labelledby="accountDropdown">
<li><a class="dropdown-item" href='{{ url_for("auth.change_pass") }}'>Change Password</a></li>
<li><a class="dropdown-item" href='{{ url_for("user.set_macros") }}'>Set macros</a></li>
<li><a class="dropdown-item" href="#">Profile</a></li>
<li>
<hr class="dropdown-divider">
</li>
<li><a class="dropdown-item" href='{{ url_for("auth.logout") }}'>Logout</a></li>
</ul>
</li>
{% else %}
<li class="nav-item">
<a class="nav-link" href="#">Login</a>
</li>
{% endif %}
<li class="nav-item">
<button id="toggleTheme" class="btn btn-body-secondary bg-dark-subtle">Toggle Theme</button>
</li>
</ul>
</div>
</div>
</nav>
<script>
const html = document.documentElement;
const savedTheme = localStorage.getItem("theme");
if (savedTheme) {
html.setAttribute("data-bs-theme", savedTheme);
}
document.getElementById("toggleTheme").addEventListener("click", () => {
const current = html.getAttribute("data-bs-theme");
const next = current === "dark" ? "light" : "dark";
html.setAttribute("data-bs-theme", next);
localStorage.setItem("theme", next);
});
</script>

View File

@@ -9,12 +9,13 @@ from flask import (
) )
from flask_login import current_user from flask_login import current_user
from application import db from application import db
from forms import FoodItemForm from forms import FoodItemForm, MacroForm
from models import FoodItem, FoodLog from models import FoodItem, FoodLog
from datetime import datetime from datetime import datetime
from application.utils import login_required, macro_arr_to_json from application.utils import login_required, macro_arr_to_json
from numpy import array from numpy import array
from zoneinfo import ZoneInfo from zoneinfo import ZoneInfo
from sqlalchemy import func
user_bp = Blueprint( user_bp = Blueprint(
"user", "user",
@@ -40,14 +41,23 @@ def daily_log():
session["selected_date"] = today.isoformat() session["selected_date"] = today.isoformat()
# Get logs from today # Get logs from today
logs_today = current_user.food_logs.filter_by( logs_today = (
date_=today.isoformat() current_user.food_logs.join(
).all() FoodItem, FoodItem.id == FoodLog.food_item_id
)
.filter(FoodLog.date_ == today)
.group_by(FoodItem.id)
.with_entities(
FoodItem, # the full object
func.sum(FoodLog.amount).label("total_amount"),
)
.all()
)
# calculate macros # calculate macros
macros = array((0.0, 0.0, 0.0, 0.0)) macros = array((0.0, 0.0, 0.0, 0.0))
for log in logs_today: for food_item, amount in logs_today:
macros += array(log.food_item.macros()) / 100 * log.amount macros += array(food_item.macros()) / 100 * amount
macros = macro_arr_to_json(macros.tolist()) macros = macro_arr_to_json(macros.tolist())
# Render HTML # Render HTML
@@ -107,3 +117,20 @@ def remove_log(id: int):
db.session.delete(log) db.session.delete(log)
db.session.commit() db.session.commit()
return redirect(url_for("user.daily_log")) return redirect(url_for("user.daily_log"))
@user_bp.route("/set_macros", methods=["GET", "POST"])
def set_macros():
form = MacroForm()
if form.validate_on_submit():
current_user.set_macros(
form.protein.data,
form.carbohydrates.data,
form.fat.data,
form.calories.data,
)
db.session.commit()
return redirect(url_for("user.daily_log"))
return render_template("settings.html", form=form)

View File

@@ -11,14 +11,14 @@
<h5>Macros</h5> <h5>Macros</h5>
{% for macro in macros %} {% for macro in macros %}
<div class="mb-2"> <div class="mb-2">
<span class="macro-text">{{ macro.name }}: {{ macro.current }} / {{ macro.target }}</span> <span class="macro-text">{{ macro.name }}: {{ macro.current | int }} / {{ macro.target }}</span>
<div class="progress rounded" style="height: 24px;"> <div class="progress rounded" style="height: 24px;">
<div class="progress-bar bg-danger macro-bar" role="progressbar" <div class="progress-bar bg-danger macro-bar" role="progressbar"
style="width: {{ macro.bar_width_overflow }}%"> style="width: {{ macro.bar_width_overflow }}%">
{{ (macro.current - macro.target) }}{{ macro.unit }} {{ (macro.current - macro.target) | int}}{{ macro.unit }}
</div> </div>
<div class="progress-bar bg-success macro-bar" role="progressbar" style="width: {{ macro.bar_width }}%"> <div class="progress-bar bg-success macro-bar" role="progressbar" style="width: {{ macro.bar_width }}%">
{{ min(macro.current, macro.target) }}{{ macro.unit }} {{ min(macro.current, macro.target) | int }}{{ macro.unit }}
</div> </div>
</div> </div>
</div> </div>
@@ -29,16 +29,32 @@
<div class="card p-3"> <div class="card p-3">
<h5>Items Eaten Today</h5> <h5>Items Eaten Today</h5>
<div class="list-group list-group-flush"> <div class="list-group list-group-flush">
{% for log in logs %} {% for food_item, amount in logs %}
<div class="list-group-item item-row d-flex justify-content-between align-items-center"> <div class="list-group-item d-flex align-items-center">
<span>({{ log.amount }}g) {{ log.food_item.name }}</span> <!-- Weight: fixed width, right-aligned -->
<span>{{ log.food_item.energy_100 * log.amount / 100 }} kcal</span> <span class="text-end" style="width: 6ch; flex-shrink: 0;">
({{ amount | int }}g)
</span>
<!-- Food name: flexible, truncates if too long -->
<span class="text-truncate flex-grow-1"
style="min-width: 0; margin-left: 0.5rem; margin-right: 0.5rem;">
{{ food_item.name }}
</span>
<!-- kcal: fixed width, right-aligned, pushed to the right -->
<span class="d-inline-block text-end ms-auto" style="width: 9ch; flex-shrink: 0;">
{{ (food_item.energy_100 * amount / 100) | int }} kcal
</span>
</div> </div>
{% endfor %} {% endfor %}
</div> </div>
</div> </div>
</div> </div>
<!-- Bottom Navigation Buttons --> <!-- Bottom Navigation Buttons -->

View File

@@ -0,0 +1,30 @@
{% extends "base.html"%}
{% block title %} Daily Macro Settings {% endblock %}
{% block content %}
<div class="container py-5">
<h2 class="mb-4 text-center">Set Your Daily Macro Targets</h2>
<form action="{{ url_for('user.set_macros') }}" method="POST" class="card p-4 shadow-sm bg-body-secondary">
{{ form.hidden_tag() }}
<div class="mb-3">
<label for="protein" class="form-label">Protein (g)</label>
{{form.protein(class="form-control")}}
</div>
<div class="mb-3">
<label for="carbs" class="form-label">Carbohydrates (g)</label>
{{form.carbohydrates(class="form-control")}}
</div>
<div class="mb-3">
<label for="fat" class="form-label">Fat (g)</label>
{{form.fat(class="form-control")}}
</div>
<div class="mb-3">
<label for="calories" class="form-label">Calories (kcal)</label>
{{form.calories(class="form-control")}}
</div>
{{form.submit(class="btn btn-primary")}}
</form>
</div>
{% endblock %}

View File

@@ -41,9 +41,11 @@ def macro_arr_to_json(data: list[float]):
{ {
"name": "Calories", "name": "Calories",
"current": cal, "current": cal,
"target": 2000, "target": current_user.calories,
"bar_width": 100 - abs(cal / 20 - 100), "bar_width": 100 - abs(cal * 100 / current_user.calories - 100),
"bar_width_overflow": max(0, cal / 20 - 100), "bar_width_overflow": max(
0, cal * 100 / current_user.calories - 100
),
"unit": " kcal", "unit": " kcal",
"color": "bg-calories", "color": "bg-calories",
"overflow_color": "bg-calories-dark", "overflow_color": "bg-calories-dark",
@@ -51,9 +53,11 @@ def macro_arr_to_json(data: list[float]):
{ {
"name": "Protein", "name": "Protein",
"current": pro, "current": pro,
"target": 150, "target": current_user.protein,
"bar_width": 100 - abs(pro / 1.5 - 100), "bar_width": 100 - abs(pro * 100 / current_user.protein - 100),
"bar_width_overflow": max(0, pro / 1.5 - 100), "bar_width_overflow": max(
0, pro * 100 / current_user.protein - 100
),
"unit": "g", "unit": "g",
"color": "bg-protein", "color": "bg-protein",
"overflow_color": "bg-protein-dark", "overflow_color": "bg-protein-dark",
@@ -61,9 +65,12 @@ def macro_arr_to_json(data: list[float]):
{ {
"name": "Carbs", "name": "Carbs",
"current": car, "current": car,
"target": 250, "target": current_user.carbohydrates,
"bar_width": 100 - abs(car / 2.5 - 100), "bar_width": 100
"bar_width_overflow": max(0, car / 2.5 - 100), - abs(car * 100 / current_user.carbohydrates - 100),
"bar_width_overflow": max(
0, car * 100 / current_user.carbohydrates - 100
),
"unit": "g", "unit": "g",
"color": "bg-carbs", "color": "bg-carbs",
"overflow_color": "bg-carbs-dark", "overflow_color": "bg-carbs-dark",
@@ -71,9 +78,9 @@ def macro_arr_to_json(data: list[float]):
{ {
"name": "Fat", "name": "Fat",
"current": fat, "current": fat,
"target": 70, "target": current_user.fat,
"bar_width": 100 - abs(fat / 0.7 - 100), "bar_width": 100 - abs(fat * 100 / current_user.fat - 100),
"bar_width_overflow": max(0, fat / 0.7 - 100), "bar_width_overflow": max(0, fat * 100 / current_user.fat - 100),
"unit": "g", "unit": "g",
"color": "bg-fat", "color": "bg-fat",
"overflow_color": "bg-fat-dark", "overflow_color": "bg-fat-dark",

View File

@@ -68,6 +68,30 @@ class FoodItemForm(FlaskForm):
submit = SubmitField("Add Item") submit = SubmitField("Add Item")
class MacroForm(FlaskForm):
protein = FloatField(
"Protein (g)",
validators=[InputRequired()],
render_kw={"inputmode": "decimal"},
)
carbohydrates = FloatField(
"Carbohydrates (g)",
validators=[InputRequired()],
render_kw={"inputmode": "decimal"},
)
fat = FloatField(
"Fat (g)",
validators=[InputRequired()],
render_kw={"inputmode": "decimal"},
)
calories = FloatField(
"Calories (kcal)",
validators=[InputRequired()],
render_kw={"inputmode": "decimal"},
)
submit = SubmitField("Update macros")
class FoodLogForm(FlaskForm): class FoodLogForm(FlaskForm):
amount = FloatField("amount of food (g/ml)", validators=[DataRequired()]) amount = FloatField("amount of food (g/ml)", validators=[DataRequired()])
submit = SubmitField("Log Item") submit = SubmitField("Log Item")

View File

@@ -0,0 +1,56 @@
"""empty message
Revision ID: 21ec41b645e9
Revises: 65eaeafb0904
Create Date: 2025-10-10 19:26:21.718736
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = "21ec41b645e9"
down_revision = "65eaeafb0904"
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(
"protein", sa.Float(), nullable=False, server_default="100"
)
)
batch_op.add_column(
sa.Column(
"carbohydrates",
sa.Float(),
nullable=False,
server_default="250",
)
)
batch_op.add_column(
sa.Column("fat", sa.Float(), nullable=False, server_default="60")
)
batch_op.add_column(
sa.Column(
"calories", sa.Float(), nullable=False, server_default="2000"
)
)
# ### 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("calories")
batch_op.drop_column("fat")
batch_op.drop_column("carbohydrates")
batch_op.drop_column("protein")
# ### end Alembic commands ###

View File

@@ -16,6 +16,11 @@ class User(UserMixin, db.Model):
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) must_change_password = db.Column(db.Boolean, nullable=False, default=False)
protein = db.Column(db.Float, nullable=False)
carbohydrates = db.Column(db.Float, nullable=False)
fat = db.Column(db.Float, nullable=False)
calories = db.Column(db.Float, nullable=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")
@@ -32,6 +37,15 @@ class User(UserMixin, db.Model):
self.is_admin = is_admin self.is_admin = is_admin
self.must_change_password = must_change_password self.must_change_password = must_change_password
def set_macros(
self, protein: float, carbs: float, fat: float, calories: float
):
self.protein = protein
self.carbohydrates = carbs
self.fat = fat
self.calories = calories
return
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)