commit 4ef9c7f677a498e04494b59f349aaf4769d1e4d5 Author: informaniac Date: Tue Jul 28 10:23:25 2020 +0200 initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..178eebe --- /dev/null +++ b/.gitignore @@ -0,0 +1,310 @@ + +# Created by https://www.gitignore.io/api/osx,venv,linux,python,windows,jetbrains+all +# Edit at https://www.gitignore.io/?templates=osx,venv,linux,python,windows,jetbrains+all + +### JetBrains+all ### +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm +# 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 + +# 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/modules.xml +# .idea/*.iml +# .idea/modules + +# 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 + +# 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 + +# JetBrains templates +**___jb_tmp___ + +### JetBrains+all Patch ### +# Ignores the whole .idea folder and all .iml files +# See https://github.com/joeblau/gitignore.io/issues/186 and https://github.com/joeblau/gitignore.io/issues/360 + +.idea/ + +# Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-249601023 + +*.iml +modules.xml +.idea/misc.xml +*.ipr + +# Sonarlint plugin +.idea/sonarlint + +### Linux ### +*~ + +# temporary files which can be created if a process still has a handle open of a deleted file +.fuse_hidden* + +# KDE directory preferences +.directory + +# Linux trash folder which might appear on any partition or disk +.Trash-* + +# .nfs files are created when an open file is removed but is still being accessed +.nfs* + +### OSX ### +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +### Python ### +# 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/ +pip-wheel-metadata/ +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 +.hypothesis/ +.pytest_cache/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +.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 + +# celery beat schedule file +celerybeat-schedule + +# SageMath parsed files +*.sage.py + +# Environments +.env +.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/ + +### venv ### +# Virtualenv +# http://iamzed.com/2009/05/07/a-primer-on-virtualenv/ +[Bb]in +[Ii]nclude +[Ll]ib +[Ll]ib64 +[Ll]ocal +[Ss]cripts +pyvenv.cfg +pip-selfcheck.json + +### Windows ### +# Windows thumbnail cache files +Thumbs.db +ehthumbs.db +ehthumbs_vista.db + +# Dump file +*.stackdump + +# Folder config file +[Dd]esktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msix +*.msm +*.msp + +# Windows shortcuts +*.lnk + +# End of https://www.gitignore.io/api/osx,venv,linux,python,windows,jetbrains+all + + +# ignore materialize.css files +static/css/materialize.css +static/css/materialize.min.css + +# ignore materialize .js files +static/js/materialize.js +static/js/materialize.min.js + +# ignore my static font files +static/font/* +static/css/font.css diff --git a/README.md b/README.md new file mode 100644 index 0000000..176da7f --- /dev/null +++ b/README.md @@ -0,0 +1,28 @@ +# ReDab Private Repository + +* extract materialize.css into 'static' folder +* create virtual environment `virtualenv -p python3 venv` +* activate virtual environment `source venv/bin/activate` +* install requirements `pip install -r requirements.txt` +* set environment variables +`export FLASK_APP='redab'` and +`export FLASK_ENV='production'` +* start server `flask run` +* navigate to `localhost:5000` + +## initialize the database +if this is your first run, you should initialize the database. this must be done while the server is running. +* if you opened another terminal, move to the project folder and set environment variables +`export FLASK_APP='redab'` and +`export FLASK_ENV='production'` +* run `flask init-db` + +## development +if you plan to develop, i suggest to remove the database file from the git working tree as a `.gitignore` entry will be ignored since the file is already tracked in the repository. + +```git update-index --skip-worktree re.db``` + +you will also want to use the development features of flask, such as auto-reload. to do this, just set the flask environment to `development` + +```export FLASK_ENV='development'``` + diff --git a/TODO b/TODO new file mode 100644 index 0000000..48055a1 --- /dev/null +++ b/TODO @@ -0,0 +1,10 @@ +better practice +main.css: body width is 70%. Add a container div inside the body to reduce width. dont reduce width for mobile + +responsiveness +replace table on page that lists all recipes + +feature +edit recipes +search recipes + diff --git a/config.py b/config.py new file mode 100644 index 0000000..e22dd02 --- /dev/null +++ b/config.py @@ -0,0 +1,9 @@ +class Config(object): + pass + +class ProdConfig(object): + pass + +class DevConfig(object): + DEBUG = True + DATABASE = 're.db' diff --git a/database.py b/database.py new file mode 100644 index 0000000..0d1c5ee --- /dev/null +++ b/database.py @@ -0,0 +1,44 @@ +import sqlite3 + +import click +from flask import current_app, g +from flask.cli import with_appcontext + + +def get_db(): + if 'db' not in g: + g.db = sqlite3.connect( + current_app.config['DATABASE'], + detect_types=sqlite3.PARSE_DECLTYPES + ) + g.db.row_factory = sqlite3.Row + + return g.db + + +def close_db(e=None): + db = g.pop('db', None) + + if db is not None: + db.close() + + +def init_db(): + db = get_db() + + with current_app.open_resource('initdb.sql') as f: + db.executescript(f.read().decode('utf8')) + + +@click.command('init-db') +@with_appcontext +def init_db_command(): + """Clear the existing data and create new tables.""" + init_db() + click.echo('Initialized the database.') + + +def init_app(app): + app.teardown_appcontext(close_db) + app.cli.add_command(init_db_command) + diff --git a/ingredient.py b/ingredient.py new file mode 100644 index 0000000..4c85417 --- /dev/null +++ b/ingredient.py @@ -0,0 +1,11 @@ +#!venv/bin/python3 + + +class Ingredient(object): + def __init__(self, amount, measure, ingredient): + self.amount = amount + self.measure = measure + self.ingredient = ingredient + + def __str__(self): + return (self.amount + ' ' + (self.measure + ' ' + self.ingredient).strip()).strip() diff --git a/initdb.sql b/initdb.sql new file mode 100644 index 0000000..1f01175 --- /dev/null +++ b/initdb.sql @@ -0,0 +1,17 @@ +CREATE TABLE IF NOT EXISTS recipes ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT NOT NULL, + description TEXT DEFAULT '', + additional_text TEXT DEFAULT '', + servings TEXT NOT NULL, + ingredients TEXT NOT NULL, + preparation TEXT NOT NULL, + preparation_time INT DEFAULT 0, + upvotes INT DEFAULT 0, + downvotes INT DEFAULT 0, + contains_meat_or_meat_products BOOLEAN NOT NULL, + contains_fish BOOLEAN NOT NULL, + contains_animal_products BOOLEAN NOT NULL, + enhancements TEXT DEFAULT '{}', + source TEXT +); diff --git a/re.db b/re.db new file mode 100644 index 0000000..e69de29 diff --git a/recipe.py b/recipe.py new file mode 100644 index 0000000..09f2da3 --- /dev/null +++ b/recipe.py @@ -0,0 +1,21 @@ +#!venv/bin/python3 + +import json + + +class Recipe(object): + def __init__(self, name, description, additional_text, servings, ingredients, preparation, preparation_time, upvotes, downvotes, contains_meat_or_meat_products, contains_fish, contains_animal_products, enhancements, source): + self.name = name + self.description = description + self.additional_text = additional_text + self.servings = servings + self.ingredients = json.loads(ingredients) + self.preparation = json.loads(preparation) + self.preparation_time = preparation_time + self.upvotes = upvotes + self.downvotes = downvotes + self.contains_meat_or_meat_products = contains_meat_or_meat_products + self.contains_fish = contains_fish + self.contains_animal_products = contains_animal_products + self.enhancements = json.loads(enhancements) + self.source = source diff --git a/redab.png b/redab.png new file mode 100644 index 0000000..b782a02 Binary files /dev/null and b/redab.png differ diff --git a/redab.py b/redab.py new file mode 100755 index 0000000..31ab11f --- /dev/null +++ b/redab.py @@ -0,0 +1,158 @@ +#!venv/bin/python3 + +from flask import Flask, render_template, request +from config import DevConfig +from ingredient import Ingredient +from recipe import Recipe +import json + + +def create_app(test_config=None): + app = Flask(__name__) + if test_config is not None: + app.config.from_object(test_config) + else: + app.config.from_object(DevConfig) + + import database + database.init_app(app) + + @app.route('/', methods=['GET']) + def index(): + return render_template("index.html") + + @app.route('/debug', methods=['GET']) + def debug(): + return repr(get_all_recipes()) + + @app.route('/rezepte', methods=['GET']) + def recipes(): + return render_template("recipes.html", recipes=get_all_recipes()) + + @app.route('/neues_rezept', methods=['GET']) + def new_recipe(): + return render_template("new_recipe.html") + + @app.route('/process_recipe', methods=['POST']) + def process_recipe(): + recipe_name = request.form.get("recipe_name").strip() + description = request.form.get("description").strip() + additional_text = request.form.get("additional_text").strip() + servings = request.form.get("servings").strip() + ingredients = request.form.getlist("ingredient[]") + measures = request.form.getlist("measure[]") + amounts = request.form.getlist("amount[]") + preparation = request.form.getlist("preparation[]") + preparation_time = request.form.get("preparation_time") + contains_meat_or_meat_products = request.form.get("contains_meat_or_meatproducts") is not None + contains_fish = request.form.get("contains_fish") is not None + contains_animal_products = request.form.get("contains_animal_products") is not None + source = request.form.get("source").strip() + if recipe_name is None or servings is None \ + or len(ingredients) == 0 or len(amounts) == 0 or len(measures) == 0 \ + or len(ingredients) != len(amounts) != len(measures) \ + or len(preparation) == 0: + # TODO: error + return "error" + amounts, measures, ingredients, preparation = sanitize_lists(amounts, measures, ingredients, preparation) + if len(measures) == 0 or len(amounts) == 0 or len(ingredients) == 0 or len(preparation) == 0: + # TODO: error + return "error after sanitizing" + merged_ingredients = merge_ingredients(amounts, measures, ingredients) + merged_ingredients_json = json.dumps([o.__dict__ for o in merged_ingredients]) + preparation_json = json.dumps(preparation) + db = database.get_db() + # TODO: Injection-safe? + db.execute("""INSERT INTO + recipes(name, description, additional_text, servings, ingredients, preparation, + preparation_time, contains_meat_or_meat_products, contains_fish, + contains_animal_products, source) + VALUES (?,?,?,?,?,?,?,?,?,?,?)""", + (recipe_name, description, additional_text, servings, merged_ingredients_json, + preparation_json, preparation_time, contains_meat_or_meat_products, + contains_fish, contains_animal_products, source)) + db.commit() + database.close_db() + # TODO: change return value + return repr(request.form) + + @app.route('/rezept/', methods=['GET']) + def get_recipe(id): + recipe = get_recipe_object_by_id(id) + if recipe is not None: + return render_template("recipe.html", recipe=recipe) + else: + return render_template("error.html", error="Das Rezept existiert (noch) nicht.") + + @app.route('/rezept//bearbeiten', methods=['GET']) + def edit_recipe(id): + pass + + @app.route('/rezept//process_edit', methods=['POST']) + def change_recipe(id): + pass + + return app + + +def sanitize_lists(amounts, measures, ingredients, preparation): + for i in range(len(ingredients)-1, -1, -1): + amounts[i] = amounts[i].strip() + measures[i] = measures[i].strip() + ingredients[i] = ingredients[i].strip() + if amounts[i] == "" and measures[i] == "" and ingredients[i] == "": + del amounts[i] + del measures[i] + del ingredients[i] + for i in range(len(preparation)-1, -1, -1): + preparation[i] = preparation[i].strip() + if preparation[i] == "": + del preparation[i] + return amounts, measures, ingredients, preparation + + +def merge_ingredients(amounts, measures, ingredients): + merged_ingredients = [] + for i in range(len(ingredients)): + merged_ingredients.append(Ingredient(amounts[i], measures[i], ingredients[i])) + return merged_ingredients + + +def get_all_recipes(): + import database + db = database.get_db() + answer = db.execute("SELECT * FROM recipes") + recipes = answer.fetchall() + database.close_db() + return recipes + + +def get_recipe_object_by_id(id): + import database + db = database.get_db() + # TODO: injection safe? + print("trying to query recipe with id: " + str(id)) + answer = db.execute("SELECT * FROM recipes WHERE id=?", (str(id))) + recipe = answer.fetchone() + database.close_db() + if recipe is None: + return None + return Recipe(recipe['name'], + recipe['description'], + recipe['additional_text'], + recipe['servings'], + recipe['ingredients'], + recipe['preparation'], + recipe['preparation_time'], + recipe['upvotes'], + recipe['downvotes'], + recipe['contains_meat_or_meat_products'], + recipe['contains_fish'], + recipe['contains_animal_products'], + recipe['enhancements'], + recipe['source']) + + +if __name__ == '__main__': + redab_app = create_app() + redab_app.run() diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..8e481ff --- /dev/null +++ b/requirements.txt @@ -0,0 +1,2 @@ +flask +Jinja2 \ No newline at end of file diff --git a/static/css/main.css b/static/css/main.css new file mode 100644 index 0000000..417148b --- /dev/null +++ b/static/css/main.css @@ -0,0 +1,29 @@ +body { + display: flex; + min-height: 100vh; + flex-direction: column; + width: 70%; + margin-left: 15%; + background-color: #3C3C3C; + color: #EFEFEF; +} + +main { + flex: 1 0 auto; +} + +i.contains-meat-or-meat-products { + color: rgb(200,0,0); +} + +i.contains-fish { + color: rgb(0,0,200); +} + +i.contains-animal-products { + color: rgb(0,200,0); +} + +div.badges { + font-size: .5em; +} diff --git a/static/js/new_recipe.js b/static/js/new_recipe.js new file mode 100644 index 0000000..ba96632 --- /dev/null +++ b/static/js/new_recipe.js @@ -0,0 +1,59 @@ +function build_ingredient_row() { + var ingredient_row_outer_div = document.createElement("div"); + ingredient_row_outer_div.setAttribute("class", "row"); + + var ingredient_row_amount_div = document.createElement("div"); + ingredient_row_amount_div.setAttribute("class", "col s2 m1"); + var ingredient_amount_input = document.createElement("input"); + ingredient_amount_input.setAttribute("class", "center-align"); + ingredient_amount_input.setAttribute("type", "text"); + ingredient_amount_input.setAttribute("name", "amount[]"); + ingredient_row_amount_div.appendChild(ingredient_amount_input); + + var ingredient_row_measure_div = document.createElement("div"); + ingredient_row_measure_div.setAttribute("class", "col s2 m1"); + var ingredient_measure_input = document.createElement("input"); + ingredient_measure_input.setAttribute("class", "center-align"); + ingredient_measure_input.setAttribute("type", "text"); + ingredient_measure_input.setAttribute("name", "measure[]"); + ingredient_row_measure_div.appendChild(ingredient_measure_input); + + var ingredient_row_ingredient_div = document.createElement("div"); + ingredient_row_ingredient_div.setAttribute("class", "col s8 m10"); + var ingredient_ingredient_input = document.createElement("input"); + ingredient_ingredient_input.setAttribute("type", "text"); + ingredient_ingredient_input.setAttribute("name", "ingredient[]"); + ingredient_row_ingredient_div.appendChild(ingredient_ingredient_input); + + ingredient_row_outer_div.appendChild(ingredient_row_amount_div); + ingredient_row_outer_div.appendChild(ingredient_row_measure_div); + ingredient_row_outer_div.appendChild(ingredient_row_ingredient_div); + + return ingredient_row_outer_div; +} + + +function add_more_ingredients() { + var ingredients_node = document.getElementById("input_fields_for_ingredients"); + var ingredients_button_node = document.getElementById("add_ingredients_button"); + ingredients_node.insertBefore(build_ingredient_row(), ingredients_button_node); + ingredients_node.insertBefore(build_ingredient_row(), ingredients_button_node); + ingredients_node.insertBefore(build_ingredient_row(), ingredients_button_node); +} + + +function build_preparation_input() { + var preparation_input = document.createElement("input"); + preparation_input.setAttribute("type", "text"); + preparation_input.setAttribute("name", "preparation[]"); + return preparation_input; +} + + +function add_more_preparation() { + var preparations_node = document.getElementById("input_fields_for_preparation"); + var preparations_button_node = document.getElementById("add_preparation_button"); + preparations_node.insertBefore(build_preparation_input(), preparations_button_node); + preparations_node.insertBefore(build_preparation_input(), preparations_button_node); + preparations_node.insertBefore(build_preparation_input(), preparations_button_node); +} diff --git a/templates/edit_recipe.html b/templates/edit_recipe.html new file mode 100644 index 0000000..2cfef2c --- /dev/null +++ b/templates/edit_recipe.html @@ -0,0 +1,105 @@ +{% extends "layout.html" %} + +{% macro input_text(id, labeltext) -%} +
+ + +
+{%- endmacro %} + +{% macro input_text_pat(id, labeltext, pattern) -%} +
+ + +
+{%- endmacro %} + +{% macro input_text_req(id, labeltext) -%} +
+ + +
+{%- endmacro %} + +{% macro input_text_req_pat(id, labeltext, pattern) -%} +
+ + +
+{%- endmacro %} + +{% macro checkbox(id, text) -%} +

+ +

+{%- endmacro %} +{% macro section(title) -%} +
{{ title }}

+{%- endmacro %} + +{% block content %} +
+ {{ section("Name und Beschreibung") }} + {{ input_text_req("recipe_name", "Rezeptname") }} + {{ input_text("description", "Kurzbeschreibung") }} +
+ + +
+ + {{ section("Zutaten") }} + {{ input_text_req_pat("servings", "Anzahl Portionen", "[0-9]+([-][0-9]+)?") }} +
+
+
+ + +
+
+ + +
+
+ + +
+
+ +
+
+ {{ section("Zubereitung") }} +
+ {{ input_text_pat("preparation_time", "Zubereitungszeit in Minuten", "[0-9]*") }} +
+
+
+ + +
+ +
+ + {{ section("Ernährungskategorie") }} + {{ checkbox("contains_meat_or_meat_products", "Enthält Fleisch oder Fleischprodukte") }} + {{ checkbox("contains_fish", "Enthält Fisch") }} + {{ checkbox("contains_animal_products", "Enthält tierische Produkte") }} + {{ input_text("source", "Quelle") }} + + +
+
+{% endblock %} + +{% block scripts %} + +{% endblock %} diff --git a/templates/error.html b/templates/error.html new file mode 100644 index 0000000..2064630 --- /dev/null +++ b/templates/error.html @@ -0,0 +1,7 @@ +{% extends "layout.html" %} + +{% block content %} +

Gah!

+
There has been an error
+

{{error}}

+{% endblock %} diff --git a/templates/index.html b/templates/index.html new file mode 100644 index 0000000..d4d958d --- /dev/null +++ b/templates/index.html @@ -0,0 +1,27 @@ +{% extends "layout.html" %} +{% block content %} +
+
+
+
+ Alle Rezepte auflisten +

Durchstöbere die Rezeptdatenbank

+
+ +
+
+
+
+
+ Neues Rezept anlegen +

Ein neues Rezept in die Datenbank eintragen

+
+ +
+
+
+{% endblock %} diff --git a/templates/layout.html b/templates/layout.html new file mode 100644 index 0000000..0ea5eb4 --- /dev/null +++ b/templates/layout.html @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + +
+ +
+
+ {% block content %}{% endblock %} + +
+
+ +
+ + + {% block scripts %}{% endblock %} + + diff --git a/templates/new_recipe.html b/templates/new_recipe.html new file mode 100644 index 0000000..3db23eb --- /dev/null +++ b/templates/new_recipe.html @@ -0,0 +1,105 @@ +{% extends "layout.html" %} + +{% macro input_text(id, labeltext) -%} +
+ + +
+{%- endmacro %} + +{% macro input_text_pat(id, labeltext, pattern) -%} +
+ + +
+{%- endmacro %} + +{% macro input_text_req(id, labeltext) -%} +
+ + +
+{%- endmacro %} + +{% macro input_text_req_pat(id, labeltext, pattern) -%} +
+ + +
+{%- endmacro %} + +{% macro checkbox(id, text) -%} +

+ +

+{%- endmacro %} +{% macro section(title) -%} +
{{ title }}

+{%- endmacro %} + +{% block content %} +
+ {{ section("Name und Beschreibung") }} + {{ input_text_req("recipe_name", "Rezeptname") }} + {{ input_text("description", "Kurzbeschreibung") }} +
+ + +
+ + {{ section("Zutaten") }} + {{ input_text_req_pat("servings", "Anzahl Portionen", "[0-9]+([-][0-9]+)?") }} +
+
+
+ + +
+
+ + +
+
+ + +
+
+ +
+
+ {{ section("Zubereitung") }} +
+ {{ input_text_pat("preparation_time", "Zubereitungszeit in Minuten", "[0-9]*") }} +
+
+
+ + +
+ +
+ + {{ section("Ernährungskategorie") }} + {{ checkbox("contains_meat_or_meat_products", "Enthält Fleisch oder Fleischprodukte") }} + {{ checkbox("contains_fish", "Enthält Fisch") }} + {{ checkbox("contains_animal_products", "Enthält tierische Produkte") }} + {{ input_text("source", "Quelle") }} + + +
+
+{% endblock %} + +{% block scripts %} + +{% endblock %} diff --git a/templates/recipe.html b/templates/recipe.html new file mode 100644 index 0000000..c4bb0df --- /dev/null +++ b/templates/recipe.html @@ -0,0 +1,43 @@ +{% extends "layout.html" %} + +{% block content %} +
+

{{recipe.name}}

+
+
{{recipe.description}}
+

{{recipe.additional_text}}

+ {% if recipe.preparation_time != 0 %} + schedule{{recipe.preparation_time}} Minuten + {% endif %} +
+
Zutaten
+
    + {% for ingredient in recipe.ingredients %} +
  • + {{ingredient.amount}} {{ingredient.measure}} {{ingredient.ingredient}} +
  • + {% endfor %} +
+
+
Zubereitung
+
    + {% for preparation_step in recipe.preparation %} +
  1. {{preparation_step}}
  2. + {% endfor %} +
+
+
Bewertung
+ thumb_up {{recipe.upvotes}}
+ thumb_down {{recipe.downvotes}} +
+
Verbesserungen
+
    + {% for enhancement in recipe.enhancements %} +
  • {{enhancement}}
  • + {% endfor %} +
+
+
Quelle
+ {{recipe.source}} +
+{% endblock %} diff --git a/templates/recipes.html b/templates/recipes.html new file mode 100644 index 0000000..29c5f26 --- /dev/null +++ b/templates/recipes.html @@ -0,0 +1,58 @@ +{% extends "layout.html" %} + +{% macro create_recipe_row(recipe) -%} + + {{recipe.id}} + + + {{recipe.name}} + + + {{recipe.description}} + {{recipe.servings}} + + + {% if recipe.contains_meat_or_meat_products %} + clear + {% endif %} + + + + + {% if recipe.contains_fish %} + clear + {% endif %} + + + + + {% if recipe.contains_animal_products %} + clear + {% endif %} + + + + {% if recipe.preparation_time != 0 %} + {{recipe.preparation_time}} Minuten + {% endif %} + + +{%- endmacro %} + +{% block content %} + + + + + + + + + + + + {% for recipe in recipes -%} + {{ create_recipe_row(recipe) }} + {%- endfor %} +
#namebeschreibungportionenfleischfischtierprodukteschedule
+{% endblock %}