Merge pull request #5 from StefBuwalda/development

Switch to new daily log as main log
This commit is contained in:
Stef
2025-08-14 12:46:41 +02:00
committed by GitHub
19 changed files with 209 additions and 375 deletions

6
app.py
View File

@@ -11,8 +11,8 @@ 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 application.auth.routes import bp as auth_bp
from application.add_meal_v2.routes import bp as add_meal_v2_bp
from typing import Optional
# Config
@@ -29,13 +29,13 @@ def load_user(user_id: int):
# Register blueprints
app.register_blueprint(admin_bp)
app.register_blueprint(user_bp)
app.register_blueprint(add_meal_bp)
app.register_blueprint(auth_bp)
app.register_blueprint(add_meal_v2_bp)
# Routes
def default_return(next_page: Optional[str] = None):
return redirect(url_for("user.daily_log"))
return redirect(url_for("user.daily_log2"))
if next_page:
return redirect(next_page)
if current_user.is_admin:

View File

@@ -6,50 +6,52 @@ from flask import (
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 datetime import datetime
from sqlalchemy.sql.elements import BinaryExpression
from typing import cast
bp = Blueprint(
"add_meal",
"add_meal_v2",
__name__,
url_prefix="/add_meal",
url_prefix="/add_meal_v2",
template_folder="templates",
)
def date_present(func):
def check_date():
if "selected_date" not in session:
return redirect(url_for("user.daily_log2"))
def item_selected(func):
def check_item():
if check_item():
if "item_id" not in session:
return redirect(url_for("add_meal_v2.get_barcode"))
@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"))
@date_present
@bp.route("/get_barcode", methods=["GET"])
def step2():
return render_template("scan_barcode.html")
def get_barcode():
return render_template("scan_barcode_v2.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("/")
@date_present
@bp.route("/add_existing/<string:input>", methods=["GET"])
def add_existing(input: str):
# Check if input is a barcode
if input.isdigit():
item = current_user.food_items.filter_by(barcode=input).first()
@@ -59,15 +61,16 @@ def step3(input: str):
if item is None:
# Does not exist, add item
return redirect(url_for("add_meal.step3_alt1", input=input))
return redirect(url_for("add_meal_v2.add_new", input=input))
# Track item to add and continue to next step
session["item_id"] = item.id
return redirect(url_for("add_meal.step4"))
return redirect(url_for("add_meal_v2.step4"))
@bp.route("/step3_alt1/<string:input>", methods=["GET"])
def step3_alt1(input: str):
@date_present
@bp.route("/add_new/<string:input>", methods=["GET"])
def add_new(input: str):
form = FoodItemForm()
if input.isdigit():
@@ -77,8 +80,9 @@ def step3_alt1(input: str):
return render_template("add_item.html", form=form)
@bp.route("/step3_alt1/<string:input>", methods=["POST"])
def step3_alt1_post(input: str):
@date_present
@bp.route("/add_new/<string:input>", methods=["POST"])
def add_new_post(input: str):
form = FoodItemForm()
if form.validate_on_submit():
@@ -122,25 +126,20 @@ def step3_alt1_post(input: str):
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))
return redirect(url_for("add_meal_v2.add_existing", input=input))
else:
print("[DEBUG] Form Invalid")
return redirect(url_for("add_meal.step3_alt1", input=input))
return redirect(url_for("add_meal_v2.add_new", input=input))
@date_present
@item_selected
@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 not item:
return redirect(url_for("add_meal_v2.get_barcode"))
if form.validate_on_submit():
assert form.amount.data
@@ -149,29 +148,20 @@ def step4():
food_item_id=item.id,
user_id=current_user.id,
amount=form.amount.data,
part_of_day=session["meal_type"],
date_=day,
date_=datetime.strptime(
session["selected_date"], "%Y-%m-%d"
).date(),
)
)
db.session.commit()
session.pop("meal_type")
session.pop("item_id")
return redirect(url_for("user.daily_log", offset=offset))
session.pop("selected_date")
return redirect(url_for("user.daily_log2"))
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)
return render_template("step4.html", tod="idk", item=item, form=form)
@date_present
@bp.route("/query", methods=["GET"])
def query():
q = request.args.get("q", "").strip().lower()

View File

@@ -85,7 +85,7 @@
goButton.addEventListener('click', () => {
const value = searchBox.value.trim();
if (value) {
const baseURL = "{{url_for('add_meal.step3', input='!')}}";
const baseURL = "{{url_for('add_meal_v2.add_existing', input='!')}}";
window.location.href = baseURL.replace("!", encodeURIComponent(value));
}
});
@@ -121,7 +121,7 @@
if (result) {
// Result found, this should post the barcode
const codeText = result.getText();
const baseURL = "{{url_for('add_meal.step3', input='!')}}";
const baseURL = "{{url_for('add_meal_v2.add_existing', input='!')}}";
window.location.href = baseURL.replace("!", encodeURIComponent(codeText));
}
})

View File

@@ -1,8 +1,8 @@
from flask import Blueprint, request, render_template, redirect, url_for
from flask import Blueprint, 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.utils import default_return, is_valid_timezone
from application import db
bp = Blueprint(
@@ -19,12 +19,16 @@ def login():
form = LoginForm()
if form.validate_on_submit():
assert form.timezone.data
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
tz = form.timezone.data
if is_valid_timezone(tz):
user.set_timezone(tz)
db.session.commit()
login_user(user) # Log in the user
return default_return(next_page=next_page)
return default_return()
else:
pass
# invalid user

View File

@@ -3,7 +3,10 @@
{% 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>
<h3 class="mb-1 text-center">Login</h3>
<p class="text-center text-muted small mb-4">
Your timezone will be saved to show times correctly.
</p>
<form method="post">
{{ form.hidden_tag() }}
@@ -27,10 +30,21 @@
{% endif %}
</div>
{{ form.timezone(id="timezone") }}
<div class="d-grid">
{{ form.submit(class="btn btn-primary btn-lg") }}
</div>
</form>
</div>
</div>
{% endblock%}
{% endblock %}
{% block scripts %}
<script>
document.addEventListener('DOMContentLoaded', () => {
const tzField = document.getElementById('timezone');
tzField.value = Intl.DateTimeFormat().resolvedOptions().timeZone;
});
</script>
{% endblock %}

View File

@@ -22,9 +22,6 @@
<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</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{{ url_for('user.daily_log2') }}">Daily Log (new)</a>
</li>
@@ -83,6 +80,9 @@
</script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
{% block scripts %}
{% endblock %}
</body>
</html>

View File

@@ -1,36 +0,0 @@
{% 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" novalidate>
{{ 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

@@ -11,10 +11,10 @@ from flask_login import current_user
from application import db
from forms import FoodItemForm
from models import FoodItem, FoodLog
from datetime import datetime, timezone, timedelta
from datetime import datetime
from application.utils import login_required
from typing import cast
from numpy import array
from zoneinfo import ZoneInfo
user_bp = Blueprint(
"user",
@@ -119,50 +119,33 @@ def edit_food_item(id: int):
return redirect(url_for("user.dashboard"))
@user_bp.route("/", methods=["GET"])
@user_bp.route("/<offset>", methods=["GET"])
def daily_log(offset: int = 0):
try:
offset = int(offset)
except ValueError:
abort(400) # or handle invalid input
today = datetime.now(timezone.utc).date()
day = today + timedelta(days=offset)
session["offset"] = offset
logs_today = current_user.food_logs.filter_by(date_=day).all()
logs = [[], [], [], []]
calories: float = 0
protein: float = 0
carbs: float = 0
fat: float = 0
for log in logs_today:
logs[log.part_of_day].append(log)
calories += log.amount * log.food_item.energy_100 / 100
protein += log.amount * log.food_item.protein_100 / 100
carbs += log.amount * log.food_item.carbs_100 / 100
fat += log.amount * log.food_item.fat_100 / 100
return render_template(
"daily_log.html",
date=(day.strftime("%d/%m/%y")),
logs=logs,
calories=calories,
protein=protein,
carbs=carbs,
fat=fat,
offset=offset,
)
@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()
# Get today's date according to user's timezone
today = datetime.now(ZoneInfo(current_user.timezone)).date()
# Save date in session
session["selected_date"] = today.isoformat()
# Get logs from today
logs_today = current_user.food_logs.filter_by(
date_=today.isoformat()
).all()
# calculate macros
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 += array(log.food_item.macros()) / 100 * log.amount
macros = macro_arr_to_json(macros.tolist())
return render_template("daily_log2.html", macros=macros, logs=logs_today)
# Render HTML
return render_template(
"daily_log2.html",
macros=macros,
logs=logs_today,
today=today.strftime("%d/%m/%Y"),
min=min,
)
@user_bp.route("/remove_log/<int:id>", methods=["POST"])
@@ -175,6 +158,4 @@ def remove_log(id: int):
# Delete log
db.session.delete(log)
db.session.commit()
if "offset" in session:
return redirect(url_for("user.daily_log", offset=session["offset"]))
return redirect(url_for("user.daily_log"))
return redirect(url_for("user.daily_log2"))

View File

@@ -1,211 +0,0 @@
{% extends "base.html" %}
{% block title %}
Food Nutritional Info
{% endblock %}
{% block content %}
<!-- Daily Overview Section -->
<div class="container">
<div class="mb-4 p-3 border rounded d-flex align-items-center justify-content-between">
<!-- Previous Day Button -->
<form method="get" action="{{url_for('user.daily_log', offset=offset - 1)}}" class="m-0">
<button type="submit" class="btn btn-outline-primary d-flex align-items-center justify-content-center"
style="width: 40px; height: 40px;">
&laquo;
</button>
</form>
<!-- Main Content -->
<div class="flex-grow-1 text-center">
<h2>Daily Overview ({{date}})</h2>
<!-- Row 1 -->
<div class="row justify-content-center text-center mb-2">
<div class="col-auto">
<strong>Calories:</strong> {{ '%.0f' % calories }} kcal
</div>
<div class="col-auto">
<strong>Protein:</strong> {{ '%.1f' % protein }} g
</div>
</div>
<!-- Row 2 -->
<div class="row justify-content-center text-center">
<div class="col-auto">
<strong>Carbs:</strong> {{ '%.1f' % carbs }} g
</div>
<div class="col-auto">
<strong>Fat:</strong> {{ '%.1f' % fat }} g
</div>
</div>
</div>
<!-- Next Day Button -->
<form method="get" action="{{url_for('user.daily_log', offset=offset + 1)}}" class="m-0">
<button type="submit" class="btn btn-outline-primary d-flex align-items-center justify-content-center"
style="width: 40px; height: 40px;">
&raquo;
</button>
</form>
</div>
<div class="p-3 border rounded">
<div class="text-center">
<h2>Eaten today</h2>
</div>
<div class="p-3 mb-2 border rounded">
<!-- Header row (centered vertically for consistency) -->
<div class="row align-items-center mb-2">
<div class="col">
<h4 class="mb-0">Breakfast</h4>
</div>
<div class="col-auto text-end" style="min-width: 80px;">
<h4 class="mb-0">Amount</h4>
</div>
<div class="col-auto text-end" style="min-width: 80px;">
<a href="{{ url_for('add_meal.step1', meal_type=0) }}"
class="btn btn-sm btn-primary px-3 py-1">Add</a>
</div>
</div>
<!-- Data rows -->
<div>
{% for log in logs[0] %}
<div class="row mb-2">
<div class="col text-wrap">
{{ log.food_item.name }}
</div>
<div class="col-auto text-end align-self-start" style="min-width: 80px;">
{{ "{:g}".format(log.amount) }}
</div>
<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">
<button type="submit" class="btn btn-sm btn-danger px-3 py-1"
title="Delete">&times;</button>
</form>
</div>
</div>
{% endfor %}
</div>
</div>
<div class="p-3 mb-2 border rounded">
<!-- Header row (centered vertically for consistency) -->
<div class="row align-items-center mb-2">
<div class="col">
<h4 class="mb-0">Lunch</h4>
</div>
<div class="col-auto text-end" style="min-width: 80px;">
<h4 class="mb-0">Amount</h4>
</div>
<div class="col-auto text-end" style="min-width: 80px;">
<a href="{{ url_for('add_meal.step1', meal_type=1) }}"
class="btn btn-sm btn-primary px-3 py-1">Add</a>
</div>
</div>
<!-- Data rows -->
<div>
{% for log in logs[1] %}
<div class="row mb-2">
<div class="col text-wrap">
{{ log.food_item.name }}
</div>
<div class="col-auto text-end align-self-start" style="min-width: 80px;">
{{ "{:g}".format(log.amount) }}
</div>
<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">
<button type="submit" class="btn btn-sm btn-danger px-3 py-1"
title="Delete">&times;</button>
</form>
</div>
</div>
{% endfor %}
</div>
</div>
<div class="p-3 mb-2 border rounded">
<!-- Header row (centered vertically for consistency) -->
<div class="row align-items-center mb-2">
<div class="col">
<h4 class="mb-0">Dinner</h4>
</div>
<div class="col-auto text-end" style="min-width: 80px;">
<h4 class="mb-0">Amount</h4>
</div>
<div class="col-auto text-end" style="min-width: 80px;">
<a href="{{ url_for('add_meal.step1', meal_type=2) }}"
class="btn btn-sm btn-primary px-3 py-1">Add</a>
</div>
</div>
<!-- Data rows -->
<div>
{% for log in logs[2] %}
<div class="row mb-2">
<div class="col text-wrap">
{{ log.food_item.name }}
</div>
<div class="col-auto text-end align-self-start" style="min-width: 80px;">
{{ "{:g}".format(log.amount) }}
</div>
<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">
<button type="submit" class="btn btn-sm btn-danger px-3 py-1"
title="Delete">&times;</button>
</form>
</div>
</div>
{% endfor %}
</div>
</div>
<div class="p-3 mb-2 border rounded">
<!-- Header row (centered vertically for consistency) -->
<div class="row align-items-center mb-2">
<div class="col">
<h4 class="mb-0">Snacks</h4>
</div>
<div class="col-auto text-end" style="min-width: 80px;">
<h4 class="mb-0">Amount</h4>
</div>
<div class="col-auto text-end" style="min-width: 80px;">
<a href="{{ url_for('add_meal.step1', meal_type=3) }}"
class="btn btn-sm btn-primary px-3 py-1">Add</a>
</div>
</div>
<!-- Data rows -->
<div>
{% for log in logs[3] %}
<div class="row mb-2">
<div class="col text-wrap">
{{ log.food_item.name }}
</div>
<div class="col-auto text-end align-self-start" style="min-width: 80px;">
{{ "{:g}".format(log.amount) }}
</div>
<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"
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"
title="Delete">&times;</button>
</form>
</div>
</div>
{% endfor %}
</div>
</div>
</div>
{% endblock%}

View File

@@ -6,7 +6,7 @@
<link rel="stylesheet" href="{{ url_for('static', filename='css/macros.css') }}">
<div class="container my-4">
<h2 class="mb-3">Daily Calorie Dashboard</h2>
<h2 class="mb-3">Daily Calorie Dashboard ({{ today }})</h2>
<!-- Macro Summary -->
<div class="card p-3 mb-3">
@@ -17,10 +17,10 @@
<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 }}
{{ (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 }}
{{ min(macro.current, macro.target) }}{{ macro.unit }}
</div>
</div>
</div>
@@ -32,9 +32,9 @@
<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">
<div class="list-group-item item-row d-flex justify-content-between align-items-center">
<span>({{ log.amount }}g) {{ log.food_item.name }}</span>
<span>{{ log.food_item.energy_100 }} kcal</span>
<span>{{ log.food_item.energy_100 * log.amount / 100 }} kcal</span>
</div>
{% endfor %}
</div>
@@ -47,18 +47,18 @@
<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">
<a href="" class="btn card flex-fill me-2 rounded-pill">
Previous
</a>
<!-- Center Button (highlighted) -->
<a href="{{ url_for('add_meal.step1', meal_type=0) }}"
<a id="set_link_date" href="{{ url_for('add_meal_v2.get_barcode') }}"
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">
<a href="" class="btn card flex-fill ms-2 rounded-pill">
Next
</a>
</div>

View File

@@ -1,6 +1,7 @@
from flask_login import current_user
from flask import redirect, url_for, flash
from typing import Optional
from zoneinfo import ZoneInfo
def login_required():
@@ -13,9 +14,18 @@ def login_required():
def default_return(next_page: Optional[str] = None):
return redirect(url_for("user.daily_log"))
return redirect(url_for("user.daily_log2"))
if next_page:
return redirect(next_page)
if current_user.is_admin:
return redirect(url_for("admin.food_items"))
return redirect(url_for("dashboard"))
def is_valid_timezone(tz: str) -> bool:
try:
ZoneInfo(tz)
except Exception:
print(Exception)
return False
return True

View File

@@ -4,13 +4,20 @@ from wtforms import (
PasswordField,
SubmitField,
FloatField,
HiddenField,
)
from wtforms.validators import DataRequired, InputRequired, Optional
class SelectDateForm(FlaskForm):
date = HiddenField(validators=[DataRequired()])
submit = SubmitField(" Add Item")
class LoginForm(FlaskForm):
username = StringField("Username", validators=[DataRequired()])
password = PasswordField("Password", validators=[DataRequired()])
timezone = HiddenField("Timezone", validators=[DataRequired()])
submit = SubmitField("Log in")

View File

@@ -0,0 +1,32 @@
"""empty message
Revision ID: 65eaeafb0904
Revises: 9eb23abd5294
Create Date: 2025-08-14 12:28:56.157288
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = '65eaeafb0904'
down_revision = '9eb23abd5294'
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('food_log', schema=None) as batch_op:
batch_op.drop_column('part_of_day')
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('food_log', schema=None) as batch_op:
batch_op.add_column(sa.Column('part_of_day', sa.INTEGER(), nullable=False))
# ### end Alembic commands ###

View File

@@ -0,0 +1,40 @@
"""empty message
Revision ID: 9eb23abd5294
Revises: 101002a6ef17
Create Date: 2025-08-14 05:40:27.342711
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = "9eb23abd5294"
down_revision = "101002a6ef17"
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(
"timezone",
sa.String(length=64),
nullable=False,
server_default="UTC",
)
)
# ### 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("timezone")
# ### end Alembic commands ###

View File

@@ -4,6 +4,7 @@ from application import db
from typing import Optional
from forms import FoodItemForm
from datetime import datetime, timezone, date
from application.utils import is_valid_timezone
class User(UserMixin, db.Model):
@@ -11,6 +12,7 @@ class User(UserMixin, db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(150), unique=True, nullable=False)
password = db.Column(db.String, nullable=False)
timezone = db.Column(db.String(64), nullable=False, default="UTC")
is_admin = db.Column(db.Boolean, nullable=False, default=False)
must_change_password = db.Column(db.Boolean, nullable=False, default=False)
@@ -39,6 +41,10 @@ class User(UserMixin, db.Model):
def set_pw_change(self, change: bool) -> None:
self.must_change_password = change
def set_timezone(self, tz: str) -> None:
if is_valid_timezone(tz):
self.timezone = tz
class Unit(db.Model):
__tablename__ = "unit"
@@ -128,7 +134,6 @@ class FoodLog(db.Model):
food_item_id = db.Column(
db.Integer, db.ForeignKey("food_item.id"), nullable=False
)
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.Float, nullable=False)
@@ -137,7 +142,6 @@ class FoodLog(db.Model):
food_item_id: int,
user_id: int,
amount: float,
part_of_day: int,
date_: Optional[date] = None,
):
super().__init__()
@@ -146,4 +150,3 @@ class FoodLog(db.Model):
self.amount = amount
if date_ is not None:
self.date_ = date_
self.part_of_day = part_of_day

10
seed.py
View File

@@ -22,10 +22,10 @@ with app.app_context():
)
FoodLog.query.delete()
db.session.add(FoodLog(1, 1, 200, 0))
db.session.add(FoodLog(1, 1, 200, 1))
db.session.add(FoodLog(1, 1, 200, 2))
db.session.add(FoodLog(1, 1, 200, 3))
db.session.add(FoodLog(1, 1, 100, 1))
db.session.add(FoodLog(1, 1, 200))
db.session.add(FoodLog(1, 1, 200))
db.session.add(FoodLog(1, 1, 200))
db.session.add(FoodLog(1, 1, 200))
db.session.add(FoodLog(1, 1, 100))
db.session.commit()