Removed old log for new log

This commit is contained in:
2025-08-14 12:29:02 +02:00
parent 4f1b5a5667
commit f7f6d23562
12 changed files with 42 additions and 618 deletions

4
app.py
View File

@@ -11,7 +11,6 @@ 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
@@ -30,14 +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

@@ -1,187 +0,0 @@
from flask import (
Blueprint,
redirect,
url_for,
render_template,
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 sqlalchemy.sql.elements import BinaryExpression
from typing import cast
bp = Blueprint(
"add_meal",
__name__,
url_prefix="/add_meal",
template_folder="templates",
)
@bp.before_request
def login_required():
if not current_user.is_authenticated:
return redirect(url_for("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"))
@bp.route("/get_barcode", methods=["GET"])
def step2():
return render_template("scan_barcode.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("/")
# Check if input is a barcode
if input.isdigit():
item = current_user.food_items.filter_by(barcode=input).first()
else:
item = current_user.food_items.filter_by(name=input).first()
if item is None:
# Does not exist, add item
return redirect(url_for("add_meal.step3_alt1", input=input))
# Track item to add and continue to next step
session["item_id"] = item.id
return redirect(url_for("add_meal.step4"))
@bp.route("/step3_alt1/<string:input>", methods=["GET"])
def step3_alt1(input: str):
form = FoodItemForm()
if input.isdigit():
form.barcode.data = input
else:
form.name.data = input
return render_template("add_item.html", form=form)
@bp.route("/step3_alt1/<string:input>", methods=["POST"])
def step3_alt1_post(input: str):
form = FoodItemForm()
if form.validate_on_submit():
# Form has valid input
barcode = form.barcode.data
name = form.name.data
assert name
assert form.energy.data is not None
assert form.protein.data is not None
assert form.carbs.data is not None
assert form.fat.data is not None
# Check if name or barcode already exists
name_filter = cast(BinaryExpression, FoodItem.name == name)
barcode_filter = cast(BinaryExpression, FoodItem.barcode == barcode)
filter_exp = or_(name_filter, barcode_filter)
item = current_user.food_items.filter(filter_exp).first()
if item is None:
# Item does not exist, add to DB
barcode = (
barcode if barcode else None
) # Turn empty strings into None
db.session.add(
FoodItem(
name=name,
owner_id=current_user.id,
energy=form.energy.data,
protein=form.protein.data,
carbs=form.carbs.data,
fat=form.fat.data,
barcode=barcode,
saturated_fat=form.saturated_fat.data,
sugar=form.sugar.data,
)
)
db.session.commit()
print("[DEBUG] New FoodItem Added")
input = barcode if barcode else name # update input
else:
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))
else:
print("[DEBUG] Form Invalid")
return redirect(url_for("add_meal.step3_alt1", input=input))
@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 form.validate_on_submit():
assert form.amount.data
db.session.add(
FoodLog(
food_item_id=item.id,
user_id=current_user.id,
amount=form.amount.data,
part_of_day=session["meal_type"],
date_=day,
)
)
db.session.commit()
session.pop("meal_type")
session.pop("item_id")
return redirect(url_for("user.daily_log", offset=offset))
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)
@bp.route("/query", methods=["GET"])
def query():
q = request.args.get("q", "").strip().lower()
if not q:
return jsonify([])
words = q.split()
filters = [
FoodItem.name.ilike(f"%{word}%") for word in words # type: ignore
]
results = current_user.food_items.filter(and_(*filters)).all()
return jsonify([item.name for item in results])

View File

@@ -1,49 +0,0 @@
{% extends "base.html" %}
{% block content %}
<form method="POST">
{{ form.hidden_tag() }}
<div class="mb-3">
{{ form.barcode.label(class="form-label") }}
{{ form.barcode(class="form-control-plaintext", readonly=true) }}
</div>
<div class="mb-3">
{{ form.name.label(class="form-label") }}
{{ form.name(class="form-control") }}
</div>
<div class="mb-3">
{{ form.energy.label(class="form-label") }}
{{ form.energy(class="form-control") }}
</div>
<div class="mb-3">
{{ form.fat.label(class="form-label") }}
{{ 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 class="mb-3">
{{ form.carbs.label(class="form-label") }}
{{ form.carbs(class="form-control") }}
</div>
<div class="mb-3">
{{ form.sugar.label(class="form-label") }}
{{ form.sugar(class="form-control") }}
</div>
<div class="mb-3">
{{ form.protein.label(class="form-label") }}
{{ form.protein(class="form-control") }}
</div>
{{ form.submit(class="btn btn-primary") }}
</form>
{% endblock%}

View File

@@ -1,134 +0,0 @@
{% extends "base.html" %}
{% block content %}
<div class="container py-5">
<!-- Header -->
<div class="text-center mb-4">
<h1 class="fw-bold">Barcode Scanner</h1>
<p class="text-muted">Use your camera to scan barcodes</p>
</div>
<!-- Video preview -->
<div class="d-flex justify-content-center mb-4">
<video id="video" class="border rounded shadow-sm" style="width: 100%; max-width: 500px;" autoplay
muted></video>
</div>
<!-- Start/Stop buttons -->
<div class="d-flex justify-content-center gap-3 mb-4">
<button id="startButton" class="btn btn-primary px-4">Start Scanning</button>
<button id="stopButton" class="btn btn-danger px-4">Stop</button>
</div>
<!-- Search box and suggestions -->
<div class="d-flex justify-content-center mt-4">
<div class="w-100 position-relative" style="max-width: 500px;">
<!-- Input group (search + go button) -->
<div class="input-group">
<input type="text" id="search-box" class="form-control" placeholder="Search..." autocomplete="off">
<button id="go-button" class="btn btn-success">Go</button>
</div>
<!-- Suggestions -->
<ul id="suggestions" class="list-group position-absolute w-100 mt-1" style="z-index: 1000;"></ul>
</div>
</div>
<!-- Suggestions list -->
<div class="d-flex justify-content-center">
<ul id="suggestions" class="list-group position-absolute mt-1 w-100" style="max-width: 500px; z-index: 1000;">
</ul>
</div>
</div>
<script>
const searchBox = document.getElementById('search-box');
const suggestionsBox = document.getElementById('suggestions');
const goButton = document.getElementById('go-button');
searchBox.addEventListener('input', function () {
const query = searchBox.value;
if (query.length < 2) {
suggestionsBox.innerHTML = '';
return;
}
fetch(`{{url_for("add_meal.query")}}?q=${encodeURIComponent(query)}`)
.then(response => response.json())
.then(data => {
suggestionsBox.innerHTML = '';
data.forEach(item => {
const li = document.createElement('li');
li.textContent = item;
// Apply Bootstrap classes
li.classList.add('list-group-item', 'list-group-item-action', 'cursor-pointer');
// Add click behavior
li.addEventListener('click', () => {
searchBox.value = item;
suggestionsBox.innerHTML = '';
});
// Add to suggestions box
suggestionsBox.appendChild(li);
});
});
});
// ✅ Redirect when the "Go" button is clicked
goButton.addEventListener('click', () => {
const value = searchBox.value.trim();
if (value) {
const baseURL = "{{url_for('add_meal.step3', input='!')}}";
window.location.href = baseURL.replace("!", encodeURIComponent(value));
}
});
</script>
<script type="module">
import { BrowserMultiFormatReader } from 'https://cdn.jsdelivr.net/npm/@zxing/library@0.21.3/+esm';
// constants
const codeReader = new BrowserMultiFormatReader();
const videoElement = document.getElementById('video');
// Start scanning for barcode
document.getElementById('startButton').addEventListener('click', async () => {
console.log('[DEBUG] Start button clicked')
try {
await navigator.mediaDevices.getUserMedia({ video: true });
} catch (err) {
alert("No camera found or no camera permission");
console.error("Could not access the camera:", err);
return;
}
console.log('[DEBUG] Permission given and at least one device present');
const devices = await codeReader.listVideoInputDevices();
console.log('[DEBUG] Cameras found:', devices);
const rearCamera = devices.find(device => device.label.toLowerCase().includes('back'))
|| devices.find(device => device.label.toLowerCase().includes('rear'))
|| devices[0]; // fallback
const selectedDeviceId = rearCamera?.deviceId;
await codeReader.decodeFromVideoDevice(selectedDeviceId, videoElement, async (result, err) => {
if (result) {
// Result found, this should post the barcode
const codeText = result.getText();
const baseURL = "{{url_for('add_meal.step3', input='!')}}";
window.location.href = baseURL.replace("!", encodeURIComponent(codeText));
}
})
})
document.getElementById('stopButton').addEventListener('click', () => {
codeReader.reset();
});
</script>
{% endblock %}

View File

@@ -1,23 +0,0 @@
{% extends "base.html" %}
{% block content %}
<p>{{ tod }}</p>
<p>{{ item.name }}</p>
<form method="POST">
{{ form.hidden_tag() }}
<div class="mb-3">
<div class="form-control-plaintext">
{{item_id}}
</div>
</div>
<div class="mb-3">
{{ form.amount.label(class="form-label") }}
{{ form.amount(class="form-control") }}
</div>
{{ form.submit(class="btn btn-primary") }}
</form>
{% endblock%}

View File

@@ -126,10 +126,10 @@ def add_new_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_v2.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_v2.step3_alt1", input=input))
return redirect(url_for("add_meal_v2.add_new", input=input))
@date_present

View File

@@ -178,6 +178,7 @@ def daily_log2():
macros=macros,
logs=logs_today,
today=today.strftime("%d/%m/%Y"),
min=min,
)
@@ -191,6 +192,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

@@ -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>
@@ -34,7 +34,7 @@
{% 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>
<span>{{ log.food_item.energy_100 * log.amount / 100 }} kcal</span>
</div>
{% endfor %}
</div>
@@ -52,7 +52,7 @@
</a>
<!-- Center Button (highlighted) -->
<a id="set_link_date" href="{{ url_for('add_meal_v2.get_barcode', 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>

View File

@@ -14,7 +14,7 @@ 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:

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

@@ -134,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)