informaniac
4 years ago
commit
4ef9c7f677
21 changed files with 1102 additions and 0 deletions
@ -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 |
@ -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'``` |
||||||
|
|
@ -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 |
||||||
|
|
@ -0,0 +1,9 @@ |
|||||||
|
class Config(object): |
||||||
|
pass |
||||||
|
|
||||||
|
class ProdConfig(object): |
||||||
|
pass |
||||||
|
|
||||||
|
class DevConfig(object): |
||||||
|
DEBUG = True |
||||||
|
DATABASE = 're.db' |
@ -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) |
||||||
|
|
@ -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() |
@ -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 |
||||||
|
); |
@ -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 |
After Width: | Height: | Size: 193 KiB |
@ -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/<int:id>', 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/<int:id>/bearbeiten', methods=['GET']) |
||||||
|
def edit_recipe(id): |
||||||
|
pass |
||||||
|
|
||||||
|
@app.route('/rezept/<int:id>/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() |
@ -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; |
||||||
|
} |
@ -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); |
||||||
|
} |
@ -0,0 +1,105 @@ |
|||||||
|
{% extends "layout.html" %} |
||||||
|
|
||||||
|
{% macro input_text(id, labeltext) -%} |
||||||
|
<div id="input_field_for_{{id}}" class="input-field" xmlns:p="http://www.w3.org/1999/html"> |
||||||
|
<input id="{{id}}" name="{{id}}" type="text" class="validate" /> |
||||||
|
<label for="{{id}}">{{labeltext}}</label> |
||||||
|
</div> |
||||||
|
{%- endmacro %} |
||||||
|
|
||||||
|
{% macro input_text_pat(id, labeltext, pattern) -%} |
||||||
|
<div id="input_field_for_{{id}}" class="input-field"> |
||||||
|
<input id="{{id}}" name="{{id}}" type="text" class="validate" pattern="{{pattern}}"/> |
||||||
|
<label for="{{id}}">{{labeltext}}</label> |
||||||
|
</div> |
||||||
|
{%- endmacro %} |
||||||
|
|
||||||
|
{% macro input_text_req(id, labeltext) -%} |
||||||
|
<div id="input_field_for_{{id}}" class="input-field"> |
||||||
|
<input id="{{id}}" name="{{id}}" type="text" class="validate" required /> |
||||||
|
<label for="{{id}}">{{labeltext}}<sup>*</sup></label> |
||||||
|
</div> |
||||||
|
{%- endmacro %} |
||||||
|
|
||||||
|
{% macro input_text_req_pat(id, labeltext, pattern) -%} |
||||||
|
<div id="input_field_for_{{id}}" class="input-field"> |
||||||
|
<input id="{{id}}" name="{{id}}" type="text" class="validate" required pattern="{{pattern}}" /> |
||||||
|
<label for="{{id}}">{{labeltext}}<sup>*</sup></label> |
||||||
|
</div> |
||||||
|
{%- endmacro %} |
||||||
|
|
||||||
|
{% macro checkbox(id, text) -%} |
||||||
|
<p id="checkbox_for_{{id}}"> |
||||||
|
<label> |
||||||
|
<input type="checkbox" id="{{id}}" name="{{id}}" /> |
||||||
|
<span>{{text}}</span> |
||||||
|
</label> |
||||||
|
</p> |
||||||
|
{%- endmacro %} |
||||||
|
{% macro section(title) -%} |
||||||
|
<h5>{{ title }}</h5><hr/> |
||||||
|
{%- endmacro %} |
||||||
|
|
||||||
|
{% block content %} |
||||||
|
<form action="/process_recipe" method="POST"> |
||||||
|
{{ section("Name und Beschreibung") }} |
||||||
|
{{ input_text_req("recipe_name", "Rezeptname") }} |
||||||
|
{{ input_text("description", "Kurzbeschreibung") }} |
||||||
|
<div id="input_field_for_additional_text" class="input-field"> |
||||||
|
<textarea id="additional_text" name="additional_text" class="materialize-textarea"></textarea> |
||||||
|
<label for="additional_text">Lange Beschreibung</label> |
||||||
|
</div> |
||||||
|
|
||||||
|
{{ section("Zutaten") }} |
||||||
|
{{ input_text_req_pat("servings", "Anzahl Portionen", "[0-9]+([-][0-9]+)?") }} |
||||||
|
<div id="input_fields_for_ingredients" class="container"> |
||||||
|
<div class="row"> |
||||||
|
<div class="col s1"> |
||||||
|
<input class="center-align validate" type="text" name="amount[]" id="first_amount" /> |
||||||
|
<label for="first_amount" class="center-align">Menge</label> |
||||||
|
</div> |
||||||
|
<div class="col s1"> |
||||||
|
<input class="center-align validate" type="text" name="measure[]" id="first_measure" /> |
||||||
|
<label for="first_measure" class="center-align">Maß</label> |
||||||
|
</div> |
||||||
|
<div class="col s10"> |
||||||
|
<input class="validate" type="text" name="ingredient[]" id="first_ingredient" /> |
||||||
|
<label for="first_ingredient">Zutat</label> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
<button id="add_ingredients_button" class="btn-small right" type="button" onclick="add_more_ingredients()"> |
||||||
|
<i class="material-icons">add</i> |
||||||
|
</button> |
||||||
|
</div> |
||||||
|
<br> |
||||||
|
{{ section("Zubereitung") }} |
||||||
|
<div id="input_field_for_preparation_time"> |
||||||
|
{{ input_text_pat("preparation_time", "Zubereitungszeit in Minuten", "[0-9]*") }} |
||||||
|
</div> |
||||||
|
<div id="input_fields_for_preparation" class="row"> |
||||||
|
<div> |
||||||
|
<input type="text" name="preparation[]" id="first_preparation" class="validate" required /> |
||||||
|
<label for="first_preparation">Zubereitungsschritte<sup>*</sup></label> |
||||||
|
</div> |
||||||
|
<button id="add_preparation_button" class="btn-small right" type="button" onclick="add_more_preparation()"> |
||||||
|
<i class="material-icons">add</i> |
||||||
|
</button> |
||||||
|
</div> |
||||||
|
|
||||||
|
{{ 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") }} |
||||||
|
|
||||||
|
<button class="btn waves-effect waves-light"> |
||||||
|
Rezept speichern |
||||||
|
<i class="material-icons left">send</i> |
||||||
|
</button> |
||||||
|
</form> |
||||||
|
<br> |
||||||
|
{% endblock %} |
||||||
|
|
||||||
|
{% block scripts %} |
||||||
|
<script type="text/javascript" src="/static/js/new_recipe.js"></script> |
||||||
|
{% endblock %} |
@ -0,0 +1,7 @@ |
|||||||
|
{% extends "layout.html" %} |
||||||
|
|
||||||
|
{% block content %} |
||||||
|
<h3>Gah!</h3> |
||||||
|
<h5>There has been an error</h5> |
||||||
|
<p>{{error}}</p> |
||||||
|
{% endblock %} |
@ -0,0 +1,27 @@ |
|||||||
|
{% extends "layout.html" %} |
||||||
|
{% block content %} |
||||||
|
<div class="row"> |
||||||
|
<div class="col s12 m6 l4"> |
||||||
|
<div class="card light-blue darken-3"> |
||||||
|
<div class="card-content white-text"> |
||||||
|
<span class="card-title">Alle Rezepte auflisten</span> |
||||||
|
<p>Durchstöbere die Rezeptdatenbank</p> |
||||||
|
</div> |
||||||
|
<div class="card-action"> |
||||||
|
<a href="/rezepte">Zu den Rezepten</a> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
<div class="col s12 m6 l4"> |
||||||
|
<div class="card light-blue darken-3"> |
||||||
|
<div class="card-content white-text"> |
||||||
|
<span class="card-title">Neues Rezept anlegen</span> |
||||||
|
<p>Ein neues Rezept in die Datenbank eintragen</p> |
||||||
|
</div> |
||||||
|
<div class="card-action"> |
||||||
|
<a href="/neues_rezept">Rezept anlegen</a> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
{% endblock %} |
@ -0,0 +1,59 @@ |
|||||||
|
<!DOCTYPE html> |
||||||
|
<html> |
||||||
|
<head> |
||||||
|
<!-- |
||||||
|
IMPORT MATERIALIZE CSS ICON FONT FROM GOOGLE. CHOOSE YOUR POISON |
||||||
|
1. replace the font completely and edit the corresponding icons in this repository |
||||||
|
|
||||||
|
2. create a static css file in static/css/ and include a local copy of the Material Icons Font |
||||||
|
if you decide to do this, you can download the stylesheet from the |
||||||
|
fonts.googleapis.com that is linked in option 3 |
||||||
|
--!> |
||||||
|
<!--link type="text/css" rel="stylesheet" href="/static/css/font.css" media="screen,projection"/--!> |
||||||
|
<!-- 3. Import Google Icon Font from Google itself. This is not recommended, especially if you are developing offline.--> |
||||||
|
<!--link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet"/--!> |
||||||
|
<!--Import materialize.css--> |
||||||
|
<link type="text/css" rel="stylesheet" href="/static/css/materialize.min.css" media="screen,projection"/> |
||||||
|
<link type="text/css" rel="stylesheet" href="/static/css/main.css" media="screen,projection"/> |
||||||
|
|
||||||
|
<!--Let browser know website is optimized for mobile--> |
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"/> |
||||||
|
</head> |
||||||
|
|
||||||
|
<body> |
||||||
|
<header> |
||||||
|
<nav> |
||||||
|
<div class="nav-wrapper light-green darken-3"> |
||||||
|
<a href="/" class="brand-logo"> ReDab</a> |
||||||
|
<ul id="nav-mobile" class="right hide-on-med-and-down"> |
||||||
|
<li><a href="/rezepte">Alle Rezepte</a></li> |
||||||
|
</ul> |
||||||
|
</div> |
||||||
|
</nav> |
||||||
|
</header> |
||||||
|
<main> |
||||||
|
{% block content %}{% endblock %} |
||||||
|
<div class="fixed-action-btn"> |
||||||
|
<a class="btn-floating btn-large red" href="/neues_rezept"> |
||||||
|
<i class="large material-icons">add</i> |
||||||
|
</a> |
||||||
|
</div> |
||||||
|
</main> |
||||||
|
<footer class="page-footer light-green darken-3"> |
||||||
|
<div class="footer-copyright"> |
||||||
|
<div class="container"> |
||||||
|
ReDab - <b>Re</b>cipe <b>Da</b>ta <b>B</b>ase. <b>Re</b>zept<b>da</b>ten<b>b</b>ank.<br> |
||||||
|
<div class="badges"> |
||||||
|
<i>Refreshing Memories</i>: Development started by informaniac at 35C3. |
||||||
|
<i>Bun intended</i>: Serves with Buns (and bunintended bugs) from EH19. |
||||||
|
<i>Error 451</i>: Gulasch recipe from GPN19 is unavailable for legal reasons. |
||||||
|
<i>Gesellschaftsspiele</i>: 'A la carte' was not available at MRMCD19. |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</footer> |
||||||
|
<!--JavaScript at end of body for optimized loading--> |
||||||
|
<script type="text/javascript" src="/static/js/materialize.min.js"></script> |
||||||
|
{% block scripts %}{% endblock %} |
||||||
|
</body> |
||||||
|
</html> |
@ -0,0 +1,105 @@ |
|||||||
|
{% extends "layout.html" %} |
||||||
|
|
||||||
|
{% macro input_text(id, labeltext) -%} |
||||||
|
<div id="input_field_for_{{id}}" class="input-field" xmlns:p="http://www.w3.org/1999/html"> |
||||||
|
<input id="{{id}}" name="{{id}}" type="text" class="validate" /> |
||||||
|
<label for="{{id}}">{{labeltext}}</label> |
||||||
|
</div> |
||||||
|
{%- endmacro %} |
||||||
|
|
||||||
|
{% macro input_text_pat(id, labeltext, pattern) -%} |
||||||
|
<div id="input_field_for_{{id}}" class="input-field"> |
||||||
|
<input id="{{id}}" name="{{id}}" type="text" class="validate" pattern="{{pattern}}"/> |
||||||
|
<label for="{{id}}">{{labeltext}}</label> |
||||||
|
</div> |
||||||
|
{%- endmacro %} |
||||||
|
|
||||||
|
{% macro input_text_req(id, labeltext) -%} |
||||||
|
<div id="input_field_for_{{id}}" class="input-field"> |
||||||
|
<input id="{{id}}" name="{{id}}" type="text" class="validate" required /> |
||||||
|
<label for="{{id}}">{{labeltext}}<sup>*</sup></label> |
||||||
|
</div> |
||||||
|
{%- endmacro %} |
||||||
|
|
||||||
|
{% macro input_text_req_pat(id, labeltext, pattern) -%} |
||||||
|
<div id="input_field_for_{{id}}" class="input-field"> |
||||||
|
<input id="{{id}}" name="{{id}}" type="text" class="validate" required pattern="{{pattern}}" /> |
||||||
|
<label for="{{id}}">{{labeltext}}<sup>*</sup></label> |
||||||
|
</div> |
||||||
|
{%- endmacro %} |
||||||
|
|
||||||
|
{% macro checkbox(id, text) -%} |
||||||
|
<p id="checkbox_for_{{id}}"> |
||||||
|
<label> |
||||||
|
<input type="checkbox" id="{{id}}" name="{{id}}" /> |
||||||
|
<span>{{text}}</span> |
||||||
|
</label> |
||||||
|
</p> |
||||||
|
{%- endmacro %} |
||||||
|
{% macro section(title) -%} |
||||||
|
<h5>{{ title }}</h5><hr/> |
||||||
|
{%- endmacro %} |
||||||
|
|
||||||
|
{% block content %} |
||||||
|
<form action="/process_recipe" method="POST"> |
||||||
|
{{ section("Name und Beschreibung") }} |
||||||
|
{{ input_text_req("recipe_name", "Rezeptname") }} |
||||||
|
{{ input_text("description", "Kurzbeschreibung") }} |
||||||
|
<div id="input_field_for_additional_text" class="input-field"> |
||||||
|
<textarea id="additional_text" name="additional_text" class="materialize-textarea"></textarea> |
||||||
|
<label for="additional_text">Lange Beschreibung</label> |
||||||
|
</div> |
||||||
|
|
||||||
|
{{ section("Zutaten") }} |
||||||
|
{{ input_text_req_pat("servings", "Anzahl Portionen", "[0-9]+([-][0-9]+)?") }} |
||||||
|
<div id="input_fields_for_ingredients"> |
||||||
|
<div class="row"> |
||||||
|
<div class="col s2 m1"> |
||||||
|
<input class="center-align validate" type="text" name="amount[]" id="first_amount" /> |
||||||
|
<label for="first_amount" class="center-align">Menge</label> |
||||||
|
</div> |
||||||
|
<div class="col s2 m1"> |
||||||
|
<input class="center-align validate" type="text" name="measure[]" id="first_measure" /> |
||||||
|
<label for="first_measure" class="center-align">Maß</label> |
||||||
|
</div> |
||||||
|
<div class="col s8 m10"> |
||||||
|
<input class="validate" type="text" name="ingredient[]" id="first_ingredient" /> |
||||||
|
<label for="first_ingredient">Zutat</label> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
<button id="add_ingredients_button" class="btn-small right" type="button" onclick="add_more_ingredients()"> |
||||||
|
<i class="material-icons">add</i> |
||||||
|
</button> |
||||||
|
</div> |
||||||
|
<br> |
||||||
|
{{ section("Zubereitung") }} |
||||||
|
<div id="input_field_for_preparation_time"> |
||||||
|
{{ input_text_pat("preparation_time", "Zubereitungszeit in Minuten", "[0-9]*") }} |
||||||
|
</div> |
||||||
|
<div id="input_fields_for_preparation" class="row"> |
||||||
|
<div> |
||||||
|
<input type="text" name="preparation[]" id="first_preparation" class="validate" required /> |
||||||
|
<label for="first_preparation">Zubereitungsschritte<sup>*</sup></label> |
||||||
|
</div> |
||||||
|
<button id="add_preparation_button" class="btn-small right" type="button" onclick="add_more_preparation()"> |
||||||
|
<i class="material-icons">add</i> |
||||||
|
</button> |
||||||
|
</div> |
||||||
|
|
||||||
|
{{ 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") }} |
||||||
|
|
||||||
|
<button class="btn waves-effect waves-light"> |
||||||
|
Rezept speichern |
||||||
|
<i class="material-icons left">send</i> |
||||||
|
</button> |
||||||
|
</form> |
||||||
|
<br> |
||||||
|
{% endblock %} |
||||||
|
|
||||||
|
{% block scripts %} |
||||||
|
<script type="text/javascript" src="/static/js/new_recipe.js"></script> |
||||||
|
{% endblock %} |
@ -0,0 +1,43 @@ |
|||||||
|
{% extends "layout.html" %} |
||||||
|
|
||||||
|
{% block content %} |
||||||
|
<div class="container"> |
||||||
|
<h4>{{recipe.name}}</h4> |
||||||
|
<hr> |
||||||
|
<h5>{{recipe.description}}</h5> |
||||||
|
<p>{{recipe.additional_text}}</p> |
||||||
|
{% if recipe.preparation_time != 0 %} |
||||||
|
<i class="material-icons">schedule</i>{{recipe.preparation_time}} Minuten |
||||||
|
{% endif %} |
||||||
|
<hr> |
||||||
|
<h5>Zutaten</h5> |
||||||
|
<ul class="collection blue-grey darken-3"> |
||||||
|
{% for ingredient in recipe.ingredients %} |
||||||
|
<li class="collection-item blue-grey darken-3"> |
||||||
|
{{ingredient.amount}} {{ingredient.measure}} {{ingredient.ingredient}} |
||||||
|
</li> |
||||||
|
{% endfor %} |
||||||
|
</ul> |
||||||
|
<hr> |
||||||
|
<h5>Zubereitung</h5> |
||||||
|
<ol class="collection blue-grey darken-3"> |
||||||
|
{% for preparation_step in recipe.preparation %} |
||||||
|
<li class="collection-item blue-grey darken-3">{{preparation_step}}</li> |
||||||
|
{% endfor %} |
||||||
|
</ol> |
||||||
|
<hr> |
||||||
|
<h5>Bewertung</h5> |
||||||
|
<i class="material-icons">thumb_up</i> {{recipe.upvotes}}<br> |
||||||
|
<i class="material-icons">thumb_down</i> {{recipe.downvotes}} |
||||||
|
<hr> |
||||||
|
<h5>Verbesserungen</h5> |
||||||
|
<ul class="collection blue-grey darken-3"> |
||||||
|
{% for enhancement in recipe.enhancements %} |
||||||
|
<li class="collection_item blue-grey darken-3">{{enhancement}}</li> |
||||||
|
{% endfor %} |
||||||
|
</ul> |
||||||
|
<hr> |
||||||
|
<h5>Quelle</h5> |
||||||
|
{{recipe.source}} |
||||||
|
</div> |
||||||
|
{% endblock %} |
@ -0,0 +1,58 @@ |
|||||||
|
{% extends "layout.html" %} |
||||||
|
|
||||||
|
{% macro create_recipe_row(recipe) -%} |
||||||
|
<tr> |
||||||
|
<td>{{recipe.id}}</td> |
||||||
|
<td> |
||||||
|
<a href="/rezept/{{recipe.id}}"> |
||||||
|
{{recipe.name}} |
||||||
|
<a> |
||||||
|
</td> |
||||||
|
<td>{{recipe.description}}</td> |
||||||
|
<td class="center-align">{{recipe.servings}}</td> |
||||||
|
<td class="center-align"> |
||||||
|
<i class="material-icons contains-meat-or-meat-products"> |
||||||
|
{% if recipe.contains_meat_or_meat_products %} |
||||||
|
clear |
||||||
|
{% endif %} |
||||||
|
</i> |
||||||
|
</td> |
||||||
|
<td class="center-align"> |
||||||
|
<i class="material-icons contains-fish"> |
||||||
|
{% if recipe.contains_fish %} |
||||||
|
clear |
||||||
|
{% endif %} |
||||||
|
</i> |
||||||
|
</td> |
||||||
|
<td class="center-align"> |
||||||
|
<i class="material-icons contains-animal-products"> |
||||||
|
{% if recipe.contains_animal_products %} |
||||||
|
clear |
||||||
|
{% endif %} |
||||||
|
</i> |
||||||
|
</td> |
||||||
|
<td> |
||||||
|
{% if recipe.preparation_time != 0 %} |
||||||
|
{{recipe.preparation_time}} Minuten |
||||||
|
{% endif %} |
||||||
|
</td> |
||||||
|
</tr> |
||||||
|
{%- endmacro %} |
||||||
|
|
||||||
|
{% block content %} |
||||||
|
<table class="highlight striped"> |
||||||
|
<tr> |
||||||
|
<th>#</th> |
||||||
|
<th>name</th> |
||||||
|
<th>beschreibung</th> |
||||||
|
<th class="center-align">portionen</th> |
||||||
|
<th class="center-align">fleisch</th> |
||||||
|
<th class="center-align">fisch</th> |
||||||
|
<th>tierprodukte</th> |
||||||
|
<th><i class="material-icons">schedule</i></th> |
||||||
|
</tr> |
||||||
|
{% for recipe in recipes -%} |
||||||
|
{{ create_recipe_row(recipe) }} |
||||||
|
{%- endfor %} |
||||||
|
</table> |
||||||
|
{% endblock %} |
Loading…
Reference in new issue