forked from svitvojimilioni/kuhkal
init
This commit is contained in:
commit
04dbe6c309
428
.gitignore
vendored
Normal file
428
.gitignore
vendored
Normal file
@ -0,0 +1,428 @@
|
||||
# Created by https://www.toptal.com/developers/gitignore/api/python,flask,vim,visualstudiocode,pycharm
|
||||
# Edit at https://www.toptal.com/developers/gitignore?templates=python,flask,vim,visualstudiocode,pycharm
|
||||
|
||||
### Flask ###
|
||||
instance/*
|
||||
!instance/.gitignore
|
||||
.webassets-cache
|
||||
.env
|
||||
|
||||
### Flask.Python Stack ###
|
||||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
|
||||
# C extensions
|
||||
*.so
|
||||
|
||||
# Distribution / packaging
|
||||
.Python
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
lib/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
wheels/
|
||||
share/python-wheels/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
MANIFEST
|
||||
|
||||
# PyInstaller
|
||||
# Usually these files are written by a python script from a template
|
||||
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||
*.manifest
|
||||
*.spec
|
||||
|
||||
# Installer logs
|
||||
pip-log.txt
|
||||
pip-delete-this-directory.txt
|
||||
|
||||
# Unit test / coverage reports
|
||||
htmlcov/
|
||||
.tox/
|
||||
.nox/
|
||||
.coverage
|
||||
.coverage.*
|
||||
.cache
|
||||
nosetests.xml
|
||||
coverage.xml
|
||||
*.cover
|
||||
*.py,cover
|
||||
.hypothesis/
|
||||
.pytest_cache/
|
||||
cover/
|
||||
|
||||
# Translations
|
||||
*.mo
|
||||
*.pot
|
||||
|
||||
# Django stuff:
|
||||
*.log
|
||||
local_settings.py
|
||||
db.sqlite3
|
||||
db.sqlite3-journal
|
||||
|
||||
# Flask stuff:
|
||||
instance/
|
||||
|
||||
# Scrapy stuff:
|
||||
.scrapy
|
||||
|
||||
# Sphinx documentation
|
||||
docs/_build/
|
||||
|
||||
# PyBuilder
|
||||
.pybuilder/
|
||||
target/
|
||||
|
||||
# Jupyter Notebook
|
||||
.ipynb_checkpoints
|
||||
|
||||
# IPython
|
||||
profile_default/
|
||||
ipython_config.py
|
||||
|
||||
# pyenv
|
||||
# For a library or package, you might want to ignore these files since the code is
|
||||
# intended to run in multiple environments; otherwise, check them in:
|
||||
# .python-version
|
||||
|
||||
# pipenv
|
||||
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
||||
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
||||
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
||||
# install all needed dependencies.
|
||||
#Pipfile.lock
|
||||
|
||||
# poetry
|
||||
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
||||
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
||||
# commonly ignored for libraries.
|
||||
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
||||
#poetry.lock
|
||||
|
||||
# pdm
|
||||
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
||||
#pdm.lock
|
||||
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
|
||||
# in version control.
|
||||
# https://pdm.fming.dev/#use-with-ide
|
||||
.pdm.toml
|
||||
|
||||
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
||||
__pypackages__/
|
||||
|
||||
# Celery stuff
|
||||
celerybeat-schedule
|
||||
celerybeat.pid
|
||||
|
||||
# SageMath parsed files
|
||||
*.sage.py
|
||||
|
||||
# Environments
|
||||
.venv
|
||||
env/
|
||||
venv/
|
||||
ENV/
|
||||
env.bak/
|
||||
venv.bak/
|
||||
|
||||
# Spyder project settings
|
||||
.spyderproject
|
||||
.spyproject
|
||||
|
||||
# Rope project settings
|
||||
.ropeproject
|
||||
|
||||
# mkdocs documentation
|
||||
/site
|
||||
|
||||
# mypy
|
||||
.mypy_cache/
|
||||
.dmypy.json
|
||||
dmypy.json
|
||||
|
||||
# Pyre type checker
|
||||
.pyre/
|
||||
|
||||
# pytype static type analyzer
|
||||
.pytype/
|
||||
|
||||
# Cython debug symbols
|
||||
cython_debug/
|
||||
|
||||
# PyCharm
|
||||
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
||||
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
||||
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
||||
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
||||
#.idea/
|
||||
|
||||
### PyCharm ###
|
||||
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
|
||||
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
|
||||
|
||||
# User-specific stuff
|
||||
.idea/**/workspace.xml
|
||||
.idea/**/tasks.xml
|
||||
.idea/**/usage.statistics.xml
|
||||
.idea/**/dictionaries
|
||||
.idea/**/shelf
|
||||
|
||||
# AWS User-specific
|
||||
.idea/**/aws.xml
|
||||
|
||||
# Generated files
|
||||
.idea/**/contentModel.xml
|
||||
|
||||
# Sensitive or high-churn files
|
||||
.idea/**/dataSources/
|
||||
.idea/**/dataSources.ids
|
||||
.idea/**/dataSources.local.xml
|
||||
.idea/**/sqlDataSources.xml
|
||||
.idea/**/dynamic.xml
|
||||
.idea/**/uiDesigner.xml
|
||||
.idea/**/dbnavigator.xml
|
||||
|
||||
# Gradle
|
||||
.idea/**/gradle.xml
|
||||
.idea/**/libraries
|
||||
|
||||
# Gradle and Maven with auto-import
|
||||
# When using Gradle or Maven with auto-import, you should exclude module files,
|
||||
# since they will be recreated, and may cause churn. Uncomment if using
|
||||
# auto-import.
|
||||
# .idea/artifacts
|
||||
# .idea/compiler.xml
|
||||
# .idea/jarRepositories.xml
|
||||
# .idea/modules.xml
|
||||
# .idea/*.iml
|
||||
# .idea/modules
|
||||
# *.iml
|
||||
# *.ipr
|
||||
|
||||
# CMake
|
||||
cmake-build-*/
|
||||
|
||||
# Mongo Explorer plugin
|
||||
.idea/**/mongoSettings.xml
|
||||
|
||||
# File-based project format
|
||||
*.iws
|
||||
|
||||
# IntelliJ
|
||||
out/
|
||||
|
||||
# mpeltonen/sbt-idea plugin
|
||||
.idea_modules/
|
||||
|
||||
# JIRA plugin
|
||||
atlassian-ide-plugin.xml
|
||||
|
||||
# Cursive Clojure plugin
|
||||
.idea/replstate.xml
|
||||
|
||||
# SonarLint plugin
|
||||
.idea/sonarlint/
|
||||
|
||||
# Crashlytics plugin (for Android Studio and IntelliJ)
|
||||
com_crashlytics_export_strings.xml
|
||||
crashlytics.properties
|
||||
crashlytics-build.properties
|
||||
fabric.properties
|
||||
|
||||
# Editor-based Rest Client
|
||||
.idea/httpRequests
|
||||
|
||||
# Android studio 3.1+ serialized cache file
|
||||
.idea/caches/build_file_checksums.ser
|
||||
|
||||
### PyCharm Patch ###
|
||||
# Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721
|
||||
|
||||
# *.iml
|
||||
# modules.xml
|
||||
# .idea/misc.xml
|
||||
# *.ipr
|
||||
|
||||
# Sonarlint plugin
|
||||
# https://plugins.jetbrains.com/plugin/7973-sonarlint
|
||||
.idea/**/sonarlint/
|
||||
|
||||
# SonarQube Plugin
|
||||
# https://plugins.jetbrains.com/plugin/7238-sonarqube-community-plugin
|
||||
.idea/**/sonarIssues.xml
|
||||
|
||||
# Markdown Navigator plugin
|
||||
# https://plugins.jetbrains.com/plugin/7896-markdown-navigator-enhanced
|
||||
.idea/**/markdown-navigator.xml
|
||||
.idea/**/markdown-navigator-enh.xml
|
||||
.idea/**/markdown-navigator/
|
||||
|
||||
# Cache file creation bug
|
||||
# See https://youtrack.jetbrains.com/issue/JBR-2257
|
||||
.idea/$CACHE_FILE$
|
||||
|
||||
# CodeStream plugin
|
||||
# https://plugins.jetbrains.com/plugin/12206-codestream
|
||||
.idea/codestream.xml
|
||||
|
||||
# Azure Toolkit for IntelliJ plugin
|
||||
# https://plugins.jetbrains.com/plugin/8053-azure-toolkit-for-intellij
|
||||
.idea/**/azureSettings.xml
|
||||
|
||||
### Python ###
|
||||
# Byte-compiled / optimized / DLL files
|
||||
|
||||
# C extensions
|
||||
|
||||
# Distribution / packaging
|
||||
|
||||
# PyInstaller
|
||||
# Usually these files are written by a python script from a template
|
||||
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||
|
||||
# Installer logs
|
||||
|
||||
# Unit test / coverage reports
|
||||
|
||||
# Translations
|
||||
|
||||
# Django stuff:
|
||||
|
||||
# Flask stuff:
|
||||
|
||||
# Scrapy stuff:
|
||||
|
||||
# Sphinx documentation
|
||||
|
||||
# PyBuilder
|
||||
|
||||
# Jupyter Notebook
|
||||
|
||||
# IPython
|
||||
|
||||
# pyenv
|
||||
# For a library or package, you might want to ignore these files since the code is
|
||||
# intended to run in multiple environments; otherwise, check them in:
|
||||
# .python-version
|
||||
|
||||
# pipenv
|
||||
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
||||
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
||||
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
||||
# install all needed dependencies.
|
||||
|
||||
# poetry
|
||||
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
||||
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
||||
# commonly ignored for libraries.
|
||||
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
||||
|
||||
# pdm
|
||||
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
||||
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
|
||||
# in version control.
|
||||
# https://pdm.fming.dev/#use-with-ide
|
||||
|
||||
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
||||
|
||||
# Celery stuff
|
||||
|
||||
# SageMath parsed files
|
||||
|
||||
# Environments
|
||||
|
||||
# Spyder project settings
|
||||
|
||||
# Rope project settings
|
||||
|
||||
# mkdocs documentation
|
||||
|
||||
# mypy
|
||||
|
||||
# Pyre type checker
|
||||
|
||||
# pytype static type analyzer
|
||||
|
||||
# Cython debug symbols
|
||||
|
||||
# PyCharm
|
||||
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
||||
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
||||
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
||||
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
||||
|
||||
### Vim ###
|
||||
# Swap
|
||||
[._]*.s[a-v][a-z]
|
||||
!*.svg # comment out if you don't need vector files
|
||||
[._]*.sw[a-p]
|
||||
[._]s[a-rt-v][a-z]
|
||||
[._]ss[a-gi-z]
|
||||
[._]sw[a-p]
|
||||
|
||||
# Session
|
||||
Session.vim
|
||||
Sessionx.vim
|
||||
|
||||
# Temporary
|
||||
.netrwhist
|
||||
*~
|
||||
# Auto-generated tag files
|
||||
tags
|
||||
# Persistent undo
|
||||
[._]*.un~
|
||||
|
||||
### VisualStudioCode ###
|
||||
.vscode/*
|
||||
!.vscode/settings.json
|
||||
!.vscode/tasks.json
|
||||
!.vscode/launch.json
|
||||
!.vscode/extensions.json
|
||||
!.vscode/*.code-snippets
|
||||
|
||||
# Local History for Visual Studio Code
|
||||
.history/
|
||||
|
||||
# Built Visual Studio Code Extensions
|
||||
*.vsix
|
||||
|
||||
### VisualStudioCode Patch ###
|
||||
# Ignore all local history of files
|
||||
.history
|
||||
.ionide
|
||||
|
||||
# Support for Project snippet scope
|
||||
.vscode/*.code-snippets
|
||||
|
||||
# Ignore code-workspaces
|
||||
*.code-workspace
|
||||
|
||||
# End of https://www.toptal.com/developers/gitignore/api/python,flask,vim,visualstudiocode,pycharm
|
||||
|
||||
### venv ###
|
||||
# Virtualenv
|
||||
# http://iamzed.com/2009/05/07/a-primer-on-virtualenv/
|
||||
.Python
|
||||
[Bb]in
|
||||
[Ii]nclude
|
||||
[Ll]ib
|
||||
[Ll]ib64
|
||||
[Ll]ocal
|
||||
[Ss]cripts
|
||||
pyvenv.cfg
|
||||
.venv
|
||||
pip-selfcheck.json
|
||||
*.db
|
||||
|
||||
.idea
|
11
kuhkal/__init__.py
Normal file
11
kuhkal/__init__.py
Normal file
@ -0,0 +1,11 @@
|
||||
from flask import Flask
|
||||
from flask_sqlalchemy import SQLAlchemy
|
||||
|
||||
app = Flask(__name__)
|
||||
app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///kuhinja.db"
|
||||
app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False
|
||||
|
||||
db = SQLAlchemy(app)
|
||||
|
||||
from . import routes
|
||||
|
26
kuhkal/models.py
Normal file
26
kuhkal/models.py
Normal file
@ -0,0 +1,26 @@
|
||||
from . import db
|
||||
|
||||
class RecipeIngredientAssoc(db.Model):
|
||||
__tablename__ = "RecipeIngredientAssoc"
|
||||
recipe_id = db.Column(db.Integer, db.ForeignKey("recipe.id"), primary_key=True)
|
||||
ingredient_id = db.Column(
|
||||
db.Integer, db.ForeignKey("ingredient.id"), primary_key=True
|
||||
)
|
||||
ingredient_ammount = db.Column(db.Numeric(10, 2))
|
||||
ingredients = db.relationship("Ingredient", back_populates="recipe")
|
||||
recipe = db.relationship("Recipe", back_populates="ingredients")
|
||||
|
||||
|
||||
class Recipe(db.Model):
|
||||
__tablename__ = "recipe"
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
name = db.Column(db.String(120), unique=True, nullable=False)
|
||||
ingredients = db.relationship("RecipeIngredientAssoc", back_populates="recipe")
|
||||
|
||||
|
||||
class Ingredient(db.Model):
|
||||
__tablename__ = "ingredient"
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
name = db.Column(db.String(120), unique=True, nullable=False)
|
||||
price = db.Column(db.Numeric(10, 2))
|
||||
recipe = db.relationship("RecipeIngredientAssoc", back_populates="ingredients")
|
206
kuhkal/routes.py
Normal file
206
kuhkal/routes.py
Normal file
@ -0,0 +1,206 @@
|
||||
from flask import render_template, request, redirect, jsonify
|
||||
|
||||
from . import app
|
||||
from .models import Recipe, Ingredient, RecipeIngredientAssoc
|
||||
from . import db
|
||||
from .utils import get_all_recipes
|
||||
|
||||
|
||||
@app.get("/")
|
||||
def index():
|
||||
data = {"recipes": get_all_recipes()}
|
||||
return render_template("pages/index.html", data=data)
|
||||
|
||||
|
||||
@app.route("/ingredients")
|
||||
@app.route("/sastojci")
|
||||
def show_ingredients():
|
||||
ingredients = db.session.query(Ingredient).all()
|
||||
data = {"ingredients": ingredients}
|
||||
return render_template("pages/ingredients.html", data=data)
|
||||
|
||||
|
||||
@app.get("/ingredients/new")
|
||||
def show_new_ingredient_form():
|
||||
return render_template("pages/add_ingredient.html")
|
||||
|
||||
|
||||
@app.post("/ingredients/new")
|
||||
def add_new_ingredient():
|
||||
name = request.form["name"]
|
||||
price = request.form["price"]
|
||||
ingredient = Ingredient(name=name, price=price)
|
||||
try:
|
||||
db.session.add(ingredient)
|
||||
db.session.commit()
|
||||
except Exception as e:
|
||||
return "greska, vec postoji sastojak"
|
||||
|
||||
return "napravljeno"
|
||||
|
||||
|
||||
@app.get("/ingredients/edit/<int:id>")
|
||||
def show_edit_ingredient_form(id):
|
||||
ingredient = Ingredient.query.get(id)
|
||||
return render_template("pages/edit_ingredient.html", ingredient=ingredient)
|
||||
|
||||
|
||||
@app.post("/ingredients/edit/<int:id>")
|
||||
def edit_ingredient(id):
|
||||
old_ingredient = Ingredient.query.get(id)
|
||||
name = request.form["name"]
|
||||
price = request.form["price"]
|
||||
if old_ingredient.name != name:
|
||||
old_ingredient.name = name
|
||||
if old_ingredient.price != price:
|
||||
old_ingredient.price = price
|
||||
|
||||
db.session.flush()
|
||||
db.session.commit()
|
||||
return "updated"
|
||||
|
||||
|
||||
@app.get("/ingredients/delete/<int:id>")
|
||||
def delete_ingredient(id):
|
||||
Ingredient.query.filter_by(id=id).delete()
|
||||
db.session.commit()
|
||||
return "Deleted"
|
||||
|
||||
|
||||
# Recipes CRUD
|
||||
@app.get("/recipes/<int:id>")
|
||||
def show_recipe(id: int):
|
||||
recipe = Recipe.query.get(id)
|
||||
ingredients = []
|
||||
for ingredient in recipe.ingredients:
|
||||
ingr = Ingredient.query.get(ingredient.ingredient_id)
|
||||
ingr_dict = {
|
||||
"name": ingr.name,
|
||||
"price": ingr.price,
|
||||
"ammount": ingredient.ingredient_ammount,
|
||||
}
|
||||
ingredients.append(ingr_dict)
|
||||
data = {"recipe": recipe, "ingredients": ingredients}
|
||||
return render_template("pages/show_recipe.html", data=data)
|
||||
|
||||
|
||||
@app.get("/recipes/new")
|
||||
def create_recipe():
|
||||
ingredients = Ingredient.query.all()
|
||||
data = {"ingredients": ingredients}
|
||||
return render_template("pages/create_recipe.html", data=data)
|
||||
|
||||
|
||||
@app.post("/recipes/new")
|
||||
def create_recipe_in_db():
|
||||
recipe = Recipe(name=request.form["recipe_name"])
|
||||
ingredient_ids = list(map(int, request.form.getlist("ingredients")))
|
||||
ingredients = map(Ingredient.query.get, ingredient_ids)
|
||||
for ing in ingredients:
|
||||
assoc = RecipeIngredientAssoc()
|
||||
assoc.ingredient = ing
|
||||
assoc.ingredient_id = ing.id
|
||||
assoc.ingredient_name = ing.name
|
||||
assoc.ingredient_price = ing.price
|
||||
assoc.recipe = recipe
|
||||
assoc.recipe_id = recipe.id
|
||||
ammount = float(request.form[f"{ing.id}_ammount"])
|
||||
assoc.ingredient_ammount = ammount
|
||||
db.session.add(recipe)
|
||||
db.session.commit()
|
||||
return "created"
|
||||
|
||||
|
||||
@app.get("/recipes/edit/<int:id>")
|
||||
def edit_recipe_form(id):
|
||||
recipe = Recipe.query.get(id)
|
||||
ingredients_inside = []
|
||||
for ingredient in recipe.ingredients:
|
||||
ingr = Ingredient.query.get(ingredient.ingredient_id)
|
||||
ingr_dict = {
|
||||
"name": ingr.name,
|
||||
"price": ingr.price,
|
||||
"ammount": ingredient.ingredient_ammount,
|
||||
"id": ingr.id,
|
||||
}
|
||||
ingredients_inside.append(ingr_dict)
|
||||
all_ingredients = Ingredient.query.all()
|
||||
ingredients_rest = [
|
||||
ingr for ingr in all_ingredients if ingr not in ingredients_inside
|
||||
]
|
||||
data = {
|
||||
"recipe": recipe,
|
||||
"ingredients_inside": ingredients_inside,
|
||||
"ingredients_rest": ingredients_rest,
|
||||
}
|
||||
return render_template("pages/edit_recipe.html", data=data)
|
||||
|
||||
|
||||
@app.post("/recipes/edit/<int:id>")
|
||||
def edit_recipe_in_db(id):
|
||||
old_recipe = Recipe.query.get(id)
|
||||
ingredient_ids = list(map(int, request.form.getlist("ingredients")))
|
||||
ingredients = map(Ingredient.query.get, ingredient_ids)
|
||||
ingredient_inside_ids = []
|
||||
|
||||
for assoc in old_recipe.ingredients:
|
||||
ingredient_inside_ids.append(assoc.ingredient_id)
|
||||
if assoc.ingredient_id in ingredient_ids:
|
||||
ammount = int(request.form[f"{assoc.ingredient_id}_ammount"])
|
||||
assoc.ingredient_ammount = ammount
|
||||
db.session.add(assoc)
|
||||
|
||||
for ing_id in ingredient_ids:
|
||||
if ing_id not in ingredient_inside_ids:
|
||||
assoc = RecipeIngredientAssoc()
|
||||
ing = Ingredient.query.get(ing_id)
|
||||
assoc.ingredient = ing
|
||||
assoc.ingredient_id = ing.id
|
||||
assoc.ingredient_name = ing.name
|
||||
assoc.ingredient_price = ing.price
|
||||
assoc.recipe = old_recipe
|
||||
assoc.recipe_id = old_recipe.id
|
||||
ammount = int(request.form[f"{ing.id}_ammount"])
|
||||
assoc.ingredient_ammount = ammount
|
||||
|
||||
db.session.add(old_recipe)
|
||||
db.session.commit()
|
||||
return "ok"
|
||||
|
||||
|
||||
@app.get("/calculate")
|
||||
def calculator():
|
||||
data = {"recipes": Recipe.query.all()}
|
||||
return render_template("pages/calculate.html", data=data)
|
||||
|
||||
|
||||
@app.post("/calculate")
|
||||
def calculate():
|
||||
req_data = request.get_json()
|
||||
recipe_id = req_data["recipe_id"]
|
||||
recipe = Recipe.query.get(recipe_id)
|
||||
ingredients_inside = []
|
||||
ammount = req_data["ammount"]
|
||||
for ingredient in recipe.ingredients:
|
||||
ingr = Ingredient.query.get(ingredient.ingredient_id)
|
||||
ingr_dict = {
|
||||
"name": ingr.name,
|
||||
"price": ingr.price,
|
||||
"ammount": ingredient.ingredient_ammount,
|
||||
"calculated_ammount": float(ingredient.ingredient_ammount)
|
||||
* (int(ammount) / 10),
|
||||
"calculated_price": float(ingr.price) * (int(ammount) / 10),
|
||||
"id": ingr.id,
|
||||
}
|
||||
ingredients_inside.append(ingr_dict)
|
||||
price = calculate_price(ingredients_inside)
|
||||
data = {"ingredients": ingredients_inside, "price": price}
|
||||
|
||||
return jsonify(data)
|
||||
|
||||
|
||||
def calculate_price(ingredients: list) -> float:
|
||||
price = 0
|
||||
for ingredient in ingredients:
|
||||
price += ingredient["calculated_price"]
|
||||
return price
|
9
kuhkal/templates/includes/calculate_form.html
Normal file
9
kuhkal/templates/includes/calculate_form.html
Normal file
@ -0,0 +1,9 @@
|
||||
<form @submit.prevent method="POST">
|
||||
<select x-model.number="recipe_id" name="recipe_id">
|
||||
{% for recipe in data["recipes"] %}
|
||||
<option value="{{recipe.id}}">{{recipe.name}}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<input @input.debounce.300="getResults()" x-model.number="ammount" type="number" step="1" name="ammount" placeholder="Koliko obroka">
|
||||
<!--<button @click="getResults()" type="submit">Izracunaj</button> -->
|
||||
</form>
|
18
kuhkal/templates/includes/calculate_results.html
Normal file
18
kuhkal/templates/includes/calculate_results.html
Normal file
@ -0,0 +1,18 @@
|
||||
<div>
|
||||
|
||||
<table class="table">
|
||||
<thead>
|
||||
<th>Sastojak</th>
|
||||
<th>Kolicina</th>
|
||||
<th>Cena</th>
|
||||
</thead>
|
||||
<template x-for="ingredient in results['ingredients']">
|
||||
<tr>
|
||||
<td x-text="ingredient['name']"></td>
|
||||
<td x-text="ingredient['calculated_ammount']"></td>
|
||||
<td x-text="ingredient['calculated_price'] + ' RSD'"></td>
|
||||
</tr>
|
||||
</template>
|
||||
</table>
|
||||
<p>Cena: <b x-text="results['price']"></b> RSD</p>
|
||||
</div>
|
11
kuhkal/templates/includes/edit_ingredient_form.html
Normal file
11
kuhkal/templates/includes/edit_ingredient_form.html
Normal file
@ -0,0 +1,11 @@
|
||||
<form method="POST">
|
||||
<label for="ingredient_name">Naziv</label>
|
||||
<input id="ingredient_name" type="text" required name="name" value="{{ingredient.name}}">
|
||||
<br>
|
||||
<label for="ingredient_price">Cena</label>
|
||||
<input id="ingredient_price" type="number" required name="price" value="{{ingredient.price}}">
|
||||
<br>
|
||||
<button>
|
||||
Dodaj Sastojak
|
||||
</button>
|
||||
</form>
|
15
kuhkal/templates/includes/edit_recipe_form.html
Normal file
15
kuhkal/templates/includes/edit_recipe_form.html
Normal file
@ -0,0 +1,15 @@
|
||||
<form method="POST">
|
||||
<label for="recipe_name">Naziv: </label>
|
||||
<input type="text" name="recipe_name" value='{{data["recipe"].name}}'>
|
||||
<br>
|
||||
{% for ingredient in data["ingredients_inside"] %}
|
||||
<input checked type="checkbox" id="{{ingredient.name}}" name="ingredients" value="{{ingredient.id}}"> <label for="{{ingredient.name}}">{{ingredient.name}}</label> <input type="number" name="{{ingredient.id}}_ammount" placeholder="kg/L" value="{{ingredient.ammount}}">
|
||||
<br>
|
||||
{% endfor %}
|
||||
<hr>
|
||||
{% for ingredient in data["ingredients_rest"] %}
|
||||
<input type="checkbox" id="{{ingredient.name}}" name="ingredients" value="{{ingredient.id}}"> <label for="{{ingredient.name}}">{{ingredient.name}}</label> <input type="number" name="{{ingredient.id}}_ammount" placeholder="kg/L">
|
||||
<br>
|
||||
{% endfor %}
|
||||
<button>Dodaj recept</button>
|
||||
</form>
|
3
kuhkal/templates/includes/footer.html
Normal file
3
kuhkal/templates/includes/footer.html
Normal file
@ -0,0 +1,3 @@
|
||||
<footer>
|
||||
<hr>
|
||||
</footer>
|
13
kuhkal/templates/includes/header.html
Normal file
13
kuhkal/templates/includes/header.html
Normal file
@ -0,0 +1,13 @@
|
||||
<nav class="navbar navbar-expand-lg navbar-light bg-light">
|
||||
<a class="navbar-brand" href="#">SK Digitron</a>
|
||||
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<div class="collapse navbar-collapse" id="navbarSupportedContent">
|
||||
<ul class="navbar-nav mr-auto">
|
||||
<li class="nav-item"><a class="nav-link" href="/calculate">Digitron</a></li>
|
||||
<li class="nav-item"><a class="nav-link" href="/">Recepti</a></li>
|
||||
<li class="nav-item"><a class="nav-link" href="/ingredients">Sastojci</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
11
kuhkal/templates/includes/new_ingredient_form.html
Normal file
11
kuhkal/templates/includes/new_ingredient_form.html
Normal file
@ -0,0 +1,11 @@
|
||||
<form method="POST">
|
||||
<label for="ingredient_name">Naziv</label>
|
||||
<input id="ingredient_name" type="text" required name="name" placeholder="Naziv...">
|
||||
<br>
|
||||
<label for="ingredient_price">Cena</label>
|
||||
<input id="ingredient_price" type="number" required name="price" placeholder="Cena...">
|
||||
<br>
|
||||
<button>
|
||||
Dodaj Sastojak
|
||||
</button>
|
||||
</form>
|
10
kuhkal/templates/includes/new_recipe_form.html
Normal file
10
kuhkal/templates/includes/new_recipe_form.html
Normal file
@ -0,0 +1,10 @@
|
||||
<form method="POST">
|
||||
<label for="recipe_name">Naziv: </label>
|
||||
<input type="text" name="recipe_name">
|
||||
<br>
|
||||
{% for ingredient in data["ingredients"] %}
|
||||
<input type="checkbox" id="{{ingredient.name}}" name="ingredients" value="{{ingredient.id}}"> <label for="{{ingredient.name}}">{{ingredient.name}}</label> <input step="0.01"type="number" name="{{ingredient.id}}_ammount" placeholder="kg/L">
|
||||
<br>
|
||||
{% endfor %}
|
||||
<button>Dodaj recept</button>
|
||||
</form>
|
17
kuhkal/templates/layouts/base.html
Normal file
17
kuhkal/templates/layouts/base.html
Normal file
@ -0,0 +1,17 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.0/dist/css/bootstrap.min.css" integrity="sha384-gH2yIJqKdNHPEq0n4Mqa/HGKIhSkIHeL5AyhkYV8i59U5AR6csBvApHHNl/vI1Bx" crossorigin="anonymous">
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.0/dist/js/bootstrap.min.js" integrity="sha384-ODmDIVzN+pFdexxHEHFBQH3/9/vQ9uori45z4JjnFsRydbmQbmL5t1tQ0culUzyK" crossorigin="anonymous"></script>
|
||||
<script src="//unpkg.com/alpinejs" defer></script>
|
||||
<title>Kuhinja Kalkulator</title>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
{% include "includes/header.html" %}
|
||||
{% block content %}
|
||||
{% endblock content %}
|
||||
{% include "includes/footer.html" %}
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
4
kuhkal/templates/pages/add_ingredient.html
Normal file
4
kuhkal/templates/pages/add_ingredient.html
Normal file
@ -0,0 +1,4 @@
|
||||
{% extends "layouts/base.html" %}
|
||||
{% block content %}
|
||||
{% include "includes/new_ingredient_form.html" %}
|
||||
{% endblock %}
|
24
kuhkal/templates/pages/calculate.html
Normal file
24
kuhkal/templates/pages/calculate.html
Normal file
@ -0,0 +1,24 @@
|
||||
{% extends "layouts/base.html" %}
|
||||
|
||||
{% block content %}
|
||||
<div x-data="{ results: {},
|
||||
recipe_id: 1,
|
||||
ammount:40,
|
||||
async getResults() {
|
||||
this.results = await (await fetch('http://localhost:5000/calculate', {
|
||||
method:'POST',
|
||||
body: JSON.stringify({
|
||||
recipe_id: this.recipe_id,
|
||||
ammount: this.ammount
|
||||
}),
|
||||
headers: {
|
||||
'Content-type': 'application/json; charset=UTF-8',
|
||||
},
|
||||
})).json();
|
||||
console.log(this.results);
|
||||
}
|
||||
} ">
|
||||
{% include "includes/calculate_form.html" %}
|
||||
{% include "includes/calculate_results.html" %}
|
||||
</div>
|
||||
{% endblock content %}
|
4
kuhkal/templates/pages/create_recipe.html
Normal file
4
kuhkal/templates/pages/create_recipe.html
Normal file
@ -0,0 +1,4 @@
|
||||
{% extends "layouts/base.html" %}
|
||||
{% block content %}
|
||||
{% include "includes/new_recipe_form.html" %}
|
||||
{% endblock content %}
|
4
kuhkal/templates/pages/edit_ingredient.html
Normal file
4
kuhkal/templates/pages/edit_ingredient.html
Normal file
@ -0,0 +1,4 @@
|
||||
{% extends "layouts/base.html" %}
|
||||
{% block content %}
|
||||
{% include "includes/edit_ingredient_form.html" %}
|
||||
{% endblock %}
|
4
kuhkal/templates/pages/edit_recipe.html
Normal file
4
kuhkal/templates/pages/edit_recipe.html
Normal file
@ -0,0 +1,4 @@
|
||||
{% extends "layouts/base.html" %}
|
||||
{% block content %}
|
||||
{% include "includes/edit_recipe_form.html" %}
|
||||
{% endblock content %}
|
27
kuhkal/templates/pages/index.html
Normal file
27
kuhkal/templates/pages/index.html
Normal file
@ -0,0 +1,27 @@
|
||||
{% extends "layouts/base.html" %}
|
||||
|
||||
{% block content %}
|
||||
<div class="row">
|
||||
{% for recipe in data["recipes"] %}
|
||||
<li>
|
||||
<h3>{{recipe.recipe.name}}</h3>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Sastojak</th>
|
||||
<th>Kolicina</th>
|
||||
<th>Cena</th>
|
||||
</tr>
|
||||
</thead>
|
||||
{% for i in recipe.ingredients %}
|
||||
<tr>
|
||||
<td>{{ i.name }} </td>
|
||||
<td>{{ i.ammount }}</td>
|
||||
<td>{{ i.price }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endblock content %}
|
19
kuhkal/templates/pages/ingredients.html
Normal file
19
kuhkal/templates/pages/ingredients.html
Normal file
@ -0,0 +1,19 @@
|
||||
{% extends "layouts/base.html" %}
|
||||
{% block content %}
|
||||
<a href="/ingredients/new" class="btn btn-danger">Dodaj sastojak</a>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<th>Naziv</th>
|
||||
<th>Cena</th>
|
||||
<th>Akcije</th>
|
||||
</thead>
|
||||
{% for ingredient in data["ingredients"] %}
|
||||
<tr>
|
||||
<td>{{ingredient.name}}</td>
|
||||
<td>{{ingredient.price}} RSD</td>
|
||||
<td><a href="/ingredients/edit/{{ingredient.id}}" class="btn btn-danger">Izmeni</a></td>
|
||||
<td><a href="/ingredients/delete/{{ingredient.id}}" class="btn btn-dark">Obrisi</a></td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
{% endblock content %}
|
10
kuhkal/templates/pages/show_recipe.html
Normal file
10
kuhkal/templates/pages/show_recipe.html
Normal file
@ -0,0 +1,10 @@
|
||||
{% extends "layouts/base.html" %}
|
||||
|
||||
{% block content %}
|
||||
<h3>{{data["recipe"].name}}<h3>
|
||||
<ul>
|
||||
{% for ingredient in data["ingredients"] %}
|
||||
<li>{{ingredient.name}}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endblock content %}
|
19
kuhkal/utils.py
Normal file
19
kuhkal/utils.py
Normal file
@ -0,0 +1,19 @@
|
||||
from .models import Recipe, Ingredient
|
||||
import typing as t
|
||||
from . import db
|
||||
|
||||
def get_all_recipes() -> t.List:
|
||||
recipes = db.session.query(Recipe).all()
|
||||
all_recipes = []
|
||||
for recipe in recipes:
|
||||
ingredients = []
|
||||
for ingredient in recipe.ingredients:
|
||||
ingr = Ingredient.query.get(ingredient.ingredient_id)
|
||||
ingr_dict = {
|
||||
"name": ingr.name,
|
||||
"price": ingr.price,
|
||||
"ammount": ingredient.ingredient_ammount,
|
||||
}
|
||||
ingredients.append(ingr_dict)
|
||||
all_recipes.append({"recipe": recipe, "ingredients": ingredients})
|
||||
return all_recipes
|
4
requirements.txt
Normal file
4
requirements.txt
Normal file
@ -0,0 +1,4 @@
|
||||
flask
|
||||
Flask-SQLAlchemy
|
||||
black
|
||||
bandit
|
4
run.py
Normal file
4
run.py
Normal file
@ -0,0 +1,4 @@
|
||||
from kuhkal import app
|
||||
|
||||
if __name__ == "__main__":
|
||||
app.run()
|
31
test_db.py
Normal file
31
test_db.py
Normal file
@ -0,0 +1,31 @@
|
||||
from kuhkal import models
|
||||
from kuhkal import db
|
||||
|
||||
def main():
|
||||
db.create_all()
|
||||
recipe = models.Recipe(name="Pasulj")
|
||||
ing_1 = models.Ingredient(name="Meso", price=30)
|
||||
ing_2 = models.Ingredient(name="Luk", price=38)
|
||||
db.session.add(ing_1)
|
||||
db.session.add(ing_2)
|
||||
db.session.commit()
|
||||
ingredients = [ing_2, ing_1]
|
||||
for ing in ingredients:
|
||||
assoc = models.RecipeIngredientAssoc()
|
||||
assoc.ingredient = ing
|
||||
assoc.ingredient_id = ing.id
|
||||
assoc.ingredient_name= ing.name
|
||||
assoc.ingredient_price= ing.price
|
||||
assoc.recipe = recipe
|
||||
assoc.recipe_id = recipe.id
|
||||
assoc.ingredient_ammount = 30
|
||||
for ing in recipe.ingredients:
|
||||
print(ing.ingredient.name)
|
||||
db.session.add(recipe)
|
||||
db.session.commit()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user