informaniac
4 years ago
commit
4ef9c7f677
21 changed files with 1102 additions and 0 deletions
@ -0,0 +1,310 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -0,0 +1,9 @@
|
||||
class Config(object): |
||||
pass |
||||
|
||||
class ProdConfig(object): |
||||
pass |
||||
|
||||
class DevConfig(object): |
||||
DEBUG = True |
||||
DATABASE = 're.db' |
@ -0,0 +1,44 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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