Browse Source

initial commit

master
informaniac 4 years ago
commit
4ef9c7f677
  1. 310
      .gitignore
  2. 28
      README.md
  3. 10
      TODO
  4. 9
      config.py
  5. 44
      database.py
  6. 11
      ingredient.py
  7. 17
      initdb.sql
  8. 0
      re.db
  9. 21
      recipe.py
  10. BIN
      redab.png
  11. 158
      redab.py
  12. 2
      requirements.txt
  13. 29
      static/css/main.css
  14. 59
      static/js/new_recipe.js
  15. 105
      templates/edit_recipe.html
  16. 7
      templates/error.html
  17. 27
      templates/index.html
  18. 59
      templates/layout.html
  19. 105
      templates/new_recipe.html
  20. 43
      templates/recipe.html
  21. 58
      templates/recipes.html

310
.gitignore vendored

@ -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

28
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'```

10
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

9
config.py

@ -0,0 +1,9 @@
class Config(object):
pass
class ProdConfig(object):
pass
class DevConfig(object):
DEBUG = True
DATABASE = 're.db'

44
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)

11
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()

17
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
);

21
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

BIN
redab.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 193 KiB

158
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/<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()

2
requirements.txt

@ -0,0 +1,2 @@
flask
Jinja2

29
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;
}

59
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);
}

105
templates/edit_recipe.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" 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 %}

7
templates/error.html

@ -0,0 +1,7 @@
{% extends "layout.html" %}
{% block content %}
<h3>Gah!</h3>
<h5>There has been an error</h5>
<p>{{error}}</p>
{% endblock %}

27
templates/index.html

@ -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 %}

59
templates/layout.html

@ -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">&nbsp;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>

105
templates/new_recipe.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 %}

43
templates/recipe.html

@ -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 %}

58
templates/recipes.html

@ -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…
Cancel
Save