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