mirror of
https://github.com/StefBuwalda/cal_counter.git
synced 2025-10-29 19:00:00 +00:00
Refactor food item model and add barcode scanner page
Renamed FoodItems to FoodItem and Units to Unit in models.py, updated related imports and usage throughout the codebase. Added a barcode scanner test page using ZXing in the admin section. Improved food_items.html to display nutritional information in a table. Registered the admin blueprint in app.py and cleaned up blueprint registration in __init__.py. Updated seed.py to use the new FoodItem model.
This commit is contained in:
5
app.py
5
app.py
@@ -8,6 +8,7 @@ from flask_login import (
|
|||||||
from forms import LoginForm
|
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
|
||||||
|
|
||||||
# Config
|
# Config
|
||||||
app.config["SECRET_KEY"] = "Iman"
|
app.config["SECRET_KEY"] = "Iman"
|
||||||
@@ -20,6 +21,10 @@ def load_user(user_id: int):
|
|||||||
return db.session.get(User, user_id)
|
return db.session.get(User, user_id)
|
||||||
|
|
||||||
|
|
||||||
|
# Register blueprints
|
||||||
|
app.register_blueprint(admin_bp)
|
||||||
|
|
||||||
|
|
||||||
# Routes
|
# Routes
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ from flask import Flask
|
|||||||
from flask_login import LoginManager # type: ignore
|
from flask_login import LoginManager # type: ignore
|
||||||
from flask_sqlalchemy import SQLAlchemy
|
from flask_sqlalchemy import SQLAlchemy
|
||||||
from flask_migrate import Migrate
|
from flask_migrate import Migrate
|
||||||
from application.admin.routes import admin_bp
|
|
||||||
|
|
||||||
|
|
||||||
app = Flask(__name__) # Init Flask app
|
app = Flask(__name__) # Init Flask app
|
||||||
@@ -13,6 +12,3 @@ db = SQLAlchemy(app=app) # Init SQLAlchemy
|
|||||||
migrate = Migrate(app=app, db=db) # Init Migration
|
migrate = Migrate(app=app, db=db) # Init Migration
|
||||||
|
|
||||||
login_manager = LoginManager(app=app) # Init login manager
|
login_manager = LoginManager(app=app) # Init login manager
|
||||||
|
|
||||||
# Register blueprints
|
|
||||||
app.register_blueprint(admin_bp)
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
from flask import Blueprint, render_template, abort
|
from flask import Blueprint, render_template, abort
|
||||||
from flask_login import current_user
|
from flask_login import current_user
|
||||||
from models import FoodItems
|
from models import FoodItem
|
||||||
|
|
||||||
admin_bp = Blueprint(
|
admin_bp = Blueprint(
|
||||||
"admin",
|
"admin",
|
||||||
@@ -18,5 +18,10 @@ def admin_required():
|
|||||||
|
|
||||||
@admin_bp.route("/food_items", methods=["GET"])
|
@admin_bp.route("/food_items", methods=["GET"])
|
||||||
def food_items():
|
def food_items():
|
||||||
items = FoodItems.query.all()
|
items = FoodItem.query.all()
|
||||||
return render_template("food_items.html", items=items)
|
return render_template("food_items.html", items=items)
|
||||||
|
|
||||||
|
|
||||||
|
@admin_bp.route("/barcode_test", methods=["GET"])
|
||||||
|
def barcode_test():
|
||||||
|
return render_template("barcode_test.html")
|
||||||
|
|||||||
55
application/admin/templates/barcode_test.html
Normal file
55
application/admin/templates/barcode_test.html
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
{% block title %}
|
||||||
|
ZXing Barcode Scanner
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="container text-center">
|
||||||
|
<h1 class="mb-4">📷 ZXing Barcode Scanner</h1>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<video id="video" class="border rounded shadow-sm" width="100%" style="max-width: 500px;"></video>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<button id="startButton" class="btn btn-primary">Start Scanning</button>
|
||||||
|
<button id="stopButton" class="btn btn-danger ms-2">Stop</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<h5>Result:</h5>
|
||||||
|
<p id="result" class="fw-bold text-success"></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script type="module">
|
||||||
|
import { BrowserMultiFormatReader } from 'https://cdn.jsdelivr.net/npm/@zxing/library@0.21.3/+esm';
|
||||||
|
|
||||||
|
const codeReader = new BrowserMultiFormatReader();
|
||||||
|
const videoElement = document.getElementById('video');
|
||||||
|
const resultElement = document.getElementById('result');
|
||||||
|
|
||||||
|
document.getElementById('startButton').addEventListener('click', async () => {
|
||||||
|
await navigator.mediaDevices.getUserMedia({ video: true });
|
||||||
|
console.log('[DEBUG] Start button clicked');
|
||||||
|
const devices = await codeReader.listVideoInputDevices();
|
||||||
|
console.log('[DEBUG] Cameras found:', devices);
|
||||||
|
const selectedDeviceId = devices[0]?.deviceId;
|
||||||
|
if (!selectedDeviceId) {
|
||||||
|
alert('No camera found!');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
codeReader.decodeFromVideoDevice(selectedDeviceId, videoElement, (result, err, controls) => {
|
||||||
|
if (result) {
|
||||||
|
resultElement.textContent = result.getText();
|
||||||
|
controls.stop();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
document.getElementById('stopButton').addEventListener('click', () => {
|
||||||
|
codeReader.reset();
|
||||||
|
resultElement.textContent = '';
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
||||||
@@ -1,5 +1,40 @@
|
|||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block title %}
|
||||||
|
Food Nutritional Info
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
Hallo
|
<div class="container mt-5">
|
||||||
|
<h1 class="mb-4">Food Nutritional Information (per 100g)</h1>
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-bordered table-hover align-middle">
|
||||||
|
<thead class="table-dark">
|
||||||
|
<tr>
|
||||||
|
<th>Name</th>
|
||||||
|
<th>Energy (kcal)</th>
|
||||||
|
<th>Fats (g)</th>
|
||||||
|
<th>Saturated Fats (g)</th>
|
||||||
|
<th>Sugars (g)</th>
|
||||||
|
<th>Carbs (g)</th>
|
||||||
|
<th>Protein (g)</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for food in items %}
|
||||||
|
<tr>
|
||||||
|
<td>{{ food.name }}</td>
|
||||||
|
<td>{{ food.energy_100g }}</td>
|
||||||
|
<td>{{ food.fats_100g }}</td>
|
||||||
|
<td>{{ food.saturated_fats_100g }}</td>
|
||||||
|
<td>{{ food.sugar_100g }}</td>
|
||||||
|
<td>{{ food.carbs_100g }}</td>
|
||||||
|
<td>{{ food.protein_100g }}</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
{% endblock%}
|
{% endblock%}
|
||||||
@@ -10,7 +10,7 @@
|
|||||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body class="bg-light p-4">
|
||||||
|
|
||||||
<nav class="navbar navbar-expand-lg navbar-dark bg-dark mb-4">
|
<nav class="navbar navbar-expand-lg navbar-dark bg-dark mb-4">
|
||||||
<div class="container-fluid">
|
<div class="container-fluid">
|
||||||
|
|||||||
35
models.py
35
models.py
@@ -1,6 +1,7 @@
|
|||||||
from flask_login import UserMixin # type: ignore
|
from flask_login import UserMixin # type: ignore
|
||||||
from werkzeug.security import generate_password_hash, check_password_hash
|
from werkzeug.security import generate_password_hash, check_password_hash
|
||||||
from application import db
|
from application import db
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
|
||||||
class User(UserMixin, db.Model):
|
class User(UserMixin, db.Model):
|
||||||
@@ -25,21 +26,39 @@ class User(UserMixin, db.Model):
|
|||||||
self.password = generate_password_hash(password=password)
|
self.password = generate_password_hash(password=password)
|
||||||
|
|
||||||
|
|
||||||
class Units(db.Model):
|
class Unit(db.Model):
|
||||||
__tablename__ = "unit"
|
__tablename__ = "unit"
|
||||||
id = db.Column(db.Integer, primary_key=True)
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
symbol = db.Column(db.String(10), unique=True, nullable=False)
|
symbol = db.Column(db.String(10), unique=True, nullable=False)
|
||||||
name = db.Column(db.String(50), unique=True, nullable=False)
|
name = db.Column(db.String(50), unique=True, nullable=False)
|
||||||
|
|
||||||
|
|
||||||
class FoodItems(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)
|
||||||
name = db.Column(db.String(150), unique=True, nullable=False)
|
name = db.Column(db.String(150), unique=True, nullable=False)
|
||||||
|
|
||||||
energy_100g = db.Column(db.Float)
|
energy_100g = db.Column(db.Integer, nullable=False)
|
||||||
protein_100g = db.Column(db.Float)
|
protein_100g = db.Column(db.Float, nullable=False)
|
||||||
carbs_100g = db.Column(db.Float)
|
carbs_100g = db.Column(db.Integer, nullable=False)
|
||||||
sugar_100g = db.Column(db.Float)
|
sugar_100g = db.Column(db.Integer)
|
||||||
fats_100g = db.Column(db.Float)
|
fats_100g = db.Column(db.Integer, nullable=False)
|
||||||
saturated_fats_100g = db.Column(db.Float)
|
saturated_fats_100g = db.Column(db.Integer)
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
name: str,
|
||||||
|
energy: int,
|
||||||
|
protein: float,
|
||||||
|
carbs: int,
|
||||||
|
fats: int,
|
||||||
|
sugar: Optional[int] = False,
|
||||||
|
saturated_fats: Optional[int] = False,
|
||||||
|
):
|
||||||
|
self.name = name
|
||||||
|
self.energy_100g = energy
|
||||||
|
self.protein_100g = protein
|
||||||
|
self.carbs_100g = carbs
|
||||||
|
self.sugar_100g = sugar
|
||||||
|
self.fats_100g = fats
|
||||||
|
self.saturated_fats_100g = saturated_fats
|
||||||
|
|||||||
17
seed.py
17
seed.py
@@ -1,11 +1,22 @@
|
|||||||
from application import db, app
|
from application import db, app
|
||||||
from models import User, FoodItems
|
from models import User, FoodItem
|
||||||
|
|
||||||
with app.app_context():
|
with app.app_context():
|
||||||
User.query.delete()
|
User.query.delete()
|
||||||
db.session.add(User(username="admin", password="admin", is_admin=True))
|
db.session.add(User(username="admin", password="admin", is_admin=True))
|
||||||
db.session.add(User(username="user", password="user", is_admin=False))
|
db.session.add(User(username="user", password="user", is_admin=False))
|
||||||
|
|
||||||
|
FoodItem.query.delete()
|
||||||
|
db.session.add(
|
||||||
|
FoodItem(
|
||||||
|
name="AH Matcha cookie",
|
||||||
|
energy=430,
|
||||||
|
fats=19,
|
||||||
|
carbs=59,
|
||||||
|
protein=5.5,
|
||||||
|
saturated_fats=10,
|
||||||
|
sugar=35,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|||||||
Reference in New Issue
Block a user