refactor: Reanmed templates

- fix: Added browse subcommand for products
This commit is contained in:
Aerex 2019-07-07 13:58:01 -05:00
parent d479b4f621
commit 6af9bef334
21 changed files with 397 additions and 88 deletions

View File

@ -1,6 +1,8 @@
import click import click
from markdown import markdown from markdown import markdown
from html2text import html2text
from jinja2 import Environment, FileSystemLoader from jinja2 import Environment, FileSystemLoader
from grocy.meta import Meta
from grocy.conf import Configuration from grocy.conf import Configuration
from grocy.util import Util from grocy.util import Util
from grocy.recipe import Recipe from grocy.recipe import Recipe
@ -104,7 +106,7 @@ def edit(product_id, name):
cfg = Configuration() cfg = Configuration()
util = Util(cfg=cfg) util = Util(cfg=cfg)
cfg.load() cfg.load()
loaded_template = cfg.templates('product_edit') loaded_template = cfg.templates('product/edit')
entity = Entity(name='products') entity = Entity(name='products')
if product_id: if product_id:
product = entity.get(id=product_id) product = entity.get(id=product_id)
@ -138,7 +140,8 @@ def edit(product_id, name):
@product.command() @product.command()
@click.option('--name', '-n', 'name') @click.option('--name', '-n', 'name')
def list(name): @click.option('-t', 'template')
def list(name, template):
logger = logging.getLogger('cli.product.list') logger = logging.getLogger('cli.product.list')
cfg = Configuration() cfg = Configuration()
cfg.load() cfg.load()
@ -160,12 +163,114 @@ def list(name):
entries['locations'] = location_entity.get() entries['locations'] = location_entity.get()
entries['products'] = products entries['products'] = products
table = Table(entries=entries) table = Table(entries=entries)
click.echo(table.products) click.echo_via_pager(table.products)
except Exception as e: except Exception as e:
logger.error(e) logger.error(e)
raise e raise e
@product.command()
@click.argument('product_id')
def browse(product_id):
logger = logging.getLogger('cli.product.browse')
try:
cfg = Configuration()
cfg.load()
url = '{domain}/product/{product_id}'.format(domain=cfg.domain, product_id=product_id)
click.launch(url, wait=False)
except Exception as e:
logger.error(e)
raise e
@product.command()
@click.option('-t', 'template')
def add(template):
logger = logging.getLogger('cli.product.add')
try:
cfg = Configuration()
cfg.load()
logger = logging.getLogger('cli.product.add')
meta = Meta()
# Get product_groups
meta.add(type='entities', name='product_groups')
# Get locations
meta.add(type='entities', name='locations')
# Get quantity_units
meta.add(type='entities', name='quantity_units')
if template:
loaded_template = cfg.templates(template)
else:
loaded_template = cfg.templates('product/add')
new_product = click.edit(loaded_template.render(grocy=meta.generate()), extension='.yml')
if not new_product:
return
parsed_new_product = yaml.safe_load(new_product)
if template == 'debug':
click.echo(parsed_new_product)
return
entity = Entity(name='products')
entity.create(parsed_new_product)
except Exception as e:
logger.error(e)
raise e
@main.group()
@click.pass_context
def recipe(ctx):
pass
@recipe.command()
@click.pass_context
@click.argument('recipe_id', required=False)
@click.option('-t', 'template')
def view(ctx, recipe_id, template):
logger = logging.getLogger('cli.recipes.view')
try:
cfg = Configuration()
cfg.load()
data = {'fields': {}}
if recipe_id:
entity = Entity(name='recipes')
data['fields'] = entity.get(id=recipe_id)
# Change html markup to plain text
html_markup_description = data['fields']['description']
plain_text_description = html2text(html_markup_description)
data['fields']['description'] = plain_text_description
recipe = Recipe(id=recipe_id)
data['fields']['fulfillment'] = recipe.get_fulfillments()
if template:
loaded_template = cfg.templates(template)
else:
loaded_template = cfg.templates('recipe/view')
entity = Entity(name='recipes')
click.echo(loaded_template.render(grocy=data))
else:
click.echo(ctx.get_help())
except Exception as e:
logger.error(e)
raise e
#@recipe.command()
#def list():
# logger = logging.getLogger('cli.recipe')
# try:
# entity = Entity(name='recipes')
# recipes = entity.get()
# recipe = Recipe(
# ingredient_reqs = recipe.get_ingredient_requirements()
# table = Table(recipes=recipes, recipes_reqs=recipes_reqs, ingredient_reqs=ingredient_reqs)
# click.echo(table.recipe)
# except Exception as e:
# logger.error(e)
# raise e
##
#
#@main.command() #@main.command()
#def shopping(): #def shopping():
# logger = logging.getLogger('cli.shopping') # logger = logging.getLogger('cli.shopping')
@ -232,27 +337,6 @@ def list(name):
# raise e # raise e
# #
# #
#@main.group()
#@click.pass_context
#def recipe(ctx):
# pass
#
#
#@recipe.command('ls')
#def ls():
# logger = logging.getLogger('cli.recipe')
# try:
# entity = Entity(name='recipes')
# recipes = entity.get()
# recipe = Recipe()
# recipes_reqs = recipe.get_requirements()
# table = Table(recipes=recipes, recipes_reqs=recipes_reqs)
# click.echo(table.recipe)
# except Exception as e:
# logger.error(e)
# raise e
#
#
#@recipe.command('edit') #@recipe.command('edit')
#@click.argument('recipe_id') #@click.argument('recipe_id')
#def edit(recipe_id): #def edit(recipe_id):

View File

@ -20,7 +20,7 @@ class Configuration(object):
'level': 'DEBUG', 'level': 'DEBUG',
'file_location': None 'file_location': None
}, },
'api': 'https://demo-en.grocy.info/api', 'domain': 'https://demo-en.grocy.info',
'token': None, 'token': None,
'formats': { 'formats': {
'col': 'center', 'col': 'center',
@ -56,9 +56,9 @@ class Configuration(object):
self.logger_file_location = user_cfg_options['logger_file_location'] self.logger_file_location = user_cfg_options['logger_file_location']
cfg_json['logger']['file_location'] = self.logger_file_location cfg_json['logger']['file_location'] = self.logger_file_location
if user_cfg_options['api']: if user_cfg_options['domain']:
self.api = user_cfg_options['api'] self.domain = user_cfg_options['domain']
cfg_json['api'] = self.api cfg_json['domain'] = self.domain
if user_cfg_options['token']: if user_cfg_options['token']:
self.token = user_cfg_options['token'] self.token = user_cfg_options['token']
@ -99,7 +99,7 @@ class Configuration(object):
def templates(self, name): def templates(self, name):
try: try:
TEMPLATE_LOADER = Environment(loader=FileSystemLoader([self.USER_TEMPLATE_DIR, self.PROJ_TEMPLATE_DIR]), TEMPLATE_LOADER = Environment(loader=FileSystemLoader([self.USER_TEMPLATE_DIR, self.PROJ_TEMPLATE_DIR]),
trim_blocks=True, lstrip_blocks=True) trim_blocks=False, lstrip_blocks=True)
return TEMPLATE_LOADER.get_template('{}.{}'.format(name, self.TEMPLATE_EXT)) return TEMPLATE_LOADER.get_template('{}.{}'.format(name, self.TEMPLATE_EXT))
except Exception as e: except Exception as e:

View File

@ -1,16 +1,21 @@
from grocy.request import Request from grocy.request import Request
import re import re
from copy import deepcopy
from grocy.conf import Configuration from grocy.conf import Configuration
import logging import logging
class Entity(object): class Entity(object):
RESOURCE_URL_TEMPLATE = '{api}/objects/{entity}/{objectId}' RESOURCE_URL_TEMPLATE = '{domain}/api/objects/{entity}/{objectId}'
COLLECTION_URL_TEMPLATE = '{api}/objects/{entity}' COLLECTION_URL_TEMPLATE = '{domain}/api/objects/{entity}'
SCHEMA_URL_TEMPLATE = '{api}/openapi/specification' SCHEMA_URL_TEMPLATE = '{domain}/api/openapi/specification'
SCHEMA_MODEL_MAP = { SCHEMA_MODEL_MAP = {
'products': 'Product', 'products': 'Product',
'stock': 'StockEntry' 'stock': 'StockEntry',
'product_groups':'ProductGroup',
'locations': 'Location',
'quantity_units': 'QuantityUnit'
} }
def __init__(self, name, **props): def __init__(self, name, **props):
@ -19,12 +24,13 @@ class Entity(object):
self.name = name self.name = name
self.__dict__.update(**props) self.__dict__.update(**props)
def get(self, id=None): def get(self, id=None):
logger = logging.getLogger('entity.get') logger = logging.getLogger('entity.get')
if id: if id:
url = self.RESOURCE_URL_TEMPLATE.format(api=self.conf.api, entity=self.name, objectId=id) url = self.RESOURCE_URL_TEMPLATE.format(domain=self.conf.domain, entity=self.name, objectId=id)
else: else:
url = self.COLLECTION_URL_TEMPLATE.format(api=self.conf.api, entity=self.name) url = self.COLLECTION_URL_TEMPLATE.format(domain=self.conf.domain, entity=self.name)
request = Request('get', url) request = Request('get', url)
try: try:
@ -54,9 +60,15 @@ class Entity(object):
def create(self, entity): def create(self, entity):
logger = logging.getLogger('entity.add') logger = logging.getLogger('entity.add')
url = self.COLLECTION_URL_TEMPLATE.format(api=self.conf.api, entity=self.name) url = self.COLLECTION_URL_TEMPLATE.format(domain=self.conf.domain, entity=self.name)
request = Request('post', url, entity) for key, value in entity.items():
if type(value) == bool:
entity[key] = '1' if value else '0'
print('{}'.format(entity))
request = Request('post', url, resource=entity)
try: try:
return request.send() return request.send()
except Exception as e: except Exception as e:
@ -67,7 +79,7 @@ class Entity(object):
if id is None: if id is None:
raise Exception('id property is required to update entity') raise Exception('id property is required to update entity')
logger = logging.getLogger('entity.update') logger = logging.getLogger('entity.update')
url = self.RESOURCE_URL_TEMPLATE.format(api=self.conf.api, entity=self.name, objectId=id) url = self.RESOURCE_URL_TEMPLATE.format(domain=self.conf.domain, entity=self.name, objectId=id)
request = Request('put', url, resource=entity) request = Request('put', url, resource=entity)
try: try:
@ -80,7 +92,7 @@ class Entity(object):
def schema(self): def schema(self):
logger = logging.getLogger('entity.schema') logger = logging.getLogger('entity.schema')
try: try:
url = self.SCHEMA_URL_TEMPLATE.format(api=self.conf.api) url = self.SCHEMA_URL_TEMPLATE.format(domain=self.conf.domain)
request = Request('get', url) request = Request('get', url)
response = request.send() response = request.send()
schema_name = self.SCHEMA_MODEL_MAP[self.name] schema_name = self.SCHEMA_MODEL_MAP[self.name]

27
grocy/meta.py Normal file
View File

@ -0,0 +1,27 @@
from grocy.entity import Entity
from grocy.schema import get_schema
from grocy.recipe import Recipe
from fontawesome import icons as fa_icons
class Meta(object):
def __init__(self):
self.meta = {'meta': {'entities': {}, 'fa_icons': fa_icons}}
def add(self, type, name):
if type == 'entities':
entity = Entity(name=name)
resources = entity.get()
schema = get_schema(name)
elif type == 'recipes':
recipe = Recipe()
if name == 'fulfillments':
resources = recipe.get_fulfillments()
schema = get_schema(name='recipe_fulfilments')
self.meta['meta'][type][name] = {}
self.meta['meta'][type][name]['properties'] = schema['properties']
self.meta['meta'][type][name]['valid_values'] = resources
def generate(self):
return self.meta

View File

@ -1,18 +1,30 @@
from grocy.request import Request from grocy.request import Request
from grocy.conf import Configuration from grocy.conf import Configuration
from grocy.entity import Entity
import logging import logging
class Recipe(object): class Recipe(object):
GET_RECIPE_REQUIRMENTS_URL_TEMPLATE = '{api}/recipes/requirements' GET_RECIPES_FULFILLMENT_URL_TEMPLATE = '{domain}/api/recipes/fulfillment'
GET_RECIPE_FULFILLMENT_URL_TEMPLATE = '{domain}/api/recipes/{recipeId}/fulfillment'
GET_RECIPES_POS_FULFILLMENT_URL_TEMPLATE = '{domain}/api/recipes/pos/fulfillment'
GET_RECIPE_POS_FULFILLMENT_URL_TEMPLATE = '{domain}/api/recipes/{recipeId}/pos/{recipeId}/fulfillment'
def __init__(self): def __init__(self, id=None):
self.conf = Configuration() self.conf = Configuration()
self.conf.load() self.conf.load()
self.id = id
def get_requirements(self): def get_ingredient_requirements(self, recipe_id=None):
logger = logging.getLogger('recipe.get_ingredient_requirements')
def get_fulfillments(self):
logger = logging.getLogger('recipe.get_requirements') logger = logging.getLogger('recipe.get_requirements')
url = self.GET_RECIPE_REQUIRMENTS_URL_TEMPLATE.format(api=self.conf.api) if self.id:
url = self.GET_RECIPE_FULFILLMENT_URL_TEMPLATE.format(domain=self.conf.domain, recipeId=self.id)
else:
url = self.GET_RECIPES_FULFILLMENT_URL_TEMPLATE.format(domain=self.conf.domain)
request = Request('get', url) request = Request('get', url)
try: try:
return request.send() return request.send()
@ -20,3 +32,23 @@ class Recipe(object):
logger.error(e) logger.error(e)
raise e raise e
def get(self):
# Get list of available ingredients
if self.id:
entity = Entity(name='recipes')
recipe = entity.get(id=self.id)
if type(recipe) is list:
pass

View File

@ -1,11 +1,13 @@
from grocy.conf import Configuration from grocy.conf import Configuration
import json from requests import session
from requests import request
import requests
import logging import logging
import cachecontrol
sess = cachecontrol.CacheControl(session())
class Request(object): class Request(object):
def __init__(self, method, url, resource=None): def __init__(self, method, url, resource=None):
self.conf = Configuration() self.conf = Configuration()
self.conf.load() self.conf.load()
@ -22,13 +24,16 @@ class Request(object):
def send(self): def send(self):
logger = logging.getLogger('request.send') logger = logging.getLogger('request.send')
if self.resource: if self.resource:
r = request(method=self.method, url=self.url, headers=self.headers, json=self.resource) r = sess.request(method=self.method, url=self.url, headers=self.headers, json=self.resource)
print(r.text)
else: else:
r = request(method=self.method, url=self.url, headers=self.headers) r = sess.request(method=self.method, url=self.url, headers=self.headers)
if r.raise_for_status(): if r.raise_for_status():
logger.error(r.raise_for_status()) logger.error(r.raise_for_status())
logger.error(r.text)
raise r.raise_for_status() raise r.raise_for_status()
if r.status_code != 204: if r.status_code != 204:
return r.json() return r.json()

28
grocy/schema.py Normal file
View File

@ -0,0 +1,28 @@
from grocy.request import Request
from grocy.conf import Configuration
import logging
SCHEMA_URL_TEMPLATE = '{domain}/api/openapi/specification'
SCHEMA_MODEL_MAP = {
'products': 'Product',
'stock': 'StockEntry',
'product_groups': 'ProductGroup',
'locations': 'Location',
'quantity_units': 'QuantityUnit',
'recipe_requirements': 'RecipeFulfilmentResponse'
}
def get_schema(name):
logger = logging.getLogger('schema')
try:
cfg = Configuration()
cfg.load()
url = SCHEMA_URL_TEMPLATE.format(domain=cfg.domain)
request = Request('get', url)
response = request.send()
schema_name = SCHEMA_MODEL_MAP[name]
return response['components']['schemas'][schema_name]
except Exception as e:
logger.error(e)
raise e

View File

@ -4,8 +4,8 @@ import logging
class Stock(object): class Stock(object):
GET_STOCK_URL_TEMPLATE = '{api}/stock' GET_STOCK_URL_TEMPLATE = '{domain}/api/stock'
GET_STOCK_PRODUCT_DETAIL_TEMPLATE = '{api}/stock/products/{product_id}' GET_STOCK_PRODUCT_DETAIL_TEMPLATE = '{domain}/api/stock/products/{product_id}'
def __init__(self): def __init__(self):
self.conf = Configuration() self.conf = Configuration()
@ -22,10 +22,10 @@ class Stock(object):
@property @property
def products(self): def products(self):
logger = logging.getLogger('stock.products') logger = logging.getLogger('stock.products')
url = self.GET_STOCK_URL_TEMPLATE.format(api=self.conf.api) url = self.GET_STOCK_URL_TEMPLATE.format(domain=self.conf.domain)
return self.__get_resources(url, logger) return self.__get_resources(url, logger)
def get_product(self, product_id): def get_product(self, product_id):
logger = logging.getLogger('stock.get_product') logger = logging.getLogger('stock.get_product')
url = self.GET_STOCK_PRODUCT_DETAIL_TEMPLATE.format(api=self.conf.api, product_id=product_id) url = self.GET_STOCK_PRODUCT_DETAIL_TEMPLATE.format(domain=self.conf.domain, product_id=product_id)
return self.__get_resources(url, logger) return self.__get_resources(url, logger)

View File

@ -20,7 +20,7 @@ class Table(object):
def product(self): def product(self):
if not self.entry: if not self.entry:
raise Exception('Missing product') raise Exception('Missing product')
loaded_template = self.conf.templates('single_product') loaded_template = self.conf.templates('product/view')
return loaded_template.render(self.entry) return loaded_template.render(self.entry)
@property @property
@ -36,23 +36,26 @@ class Table(object):
table_entries = [] table_entries = []
try: try:
for entry in self.entries['products']: for entry in self.entries['products']:
print('{}'.format(entry))
table_entry = [] table_entry = []
product_group_name = '' if entry['product_group_id'] == '' else product_groups_map[entry['product_group_id']] product_group_name = 'N/A' if entry['product_group_id'] == '' else product_groups_map[entry['product_group_id']]
location_name = location_map[entry['location_id']] mini_stock_amount = entry['mini_stock_amount'] if 'mini_stock_amount' in entry else 'N/A'
quantity_unit_purchase_name = quantity_unit_map[entry['qu_id_purchase']] qu_factor_purchase_to_stock = entry['qu_factor_purchase_to_stock'] if 'qu_factor_purchase_to_stock' in entry else 'N/A'
quantity_unit_stock_name = quantity_unit_map[entry['qu_id_stock']] location_name = location_map[entry['location_id']] if 'location_id' in entry else 'N/A'
quantity_unit_purchase_name = quantity_unit_map[entry['qu_id_purchase']] if 'qu_id_purchase' in entry else 'N/A'
quantity_unit_stock_name = quantity_unit_map[entry['qu_id_stock']] if 'qu_id_stock' in entry else 'N/A'
table_entry.append(entry['id']) table_entry.append(entry['id'])
table_entry.append(entry['name'])
table_entry.append(location_name) table_entry.append(location_name)
table_entry.append(quantity_unit_purchase_name) table_entry.append(quantity_unit_purchase_name)
table_entry.append(quantity_unit_stock_name) table_entry.append(quantity_unit_stock_name)
table_entry.append(product_group_name) table_entry.append(product_group_name)
table_entry.append(entry['mini_stock_amount']) table_entry.append(mini_stock_amount)
table_entry.append(entry['qu_factor_purchase_to_stock']) table_entry.append(quantity_unit_stock_name)
table_entries.append(table_entry) table_entries.append(table_entry)
table_headers = ['ID', 'Name', 'Location', 'Min Stock Amount', table_headers = ['ID', 'Name', 'Location', 'Min\nStock Amount',
'QU Purchase', 'QU Stock', 'QU Factor', 'Product Group'] 'QU\nPurchase', 'QU\nStock', 'QU\nFactor', 'Product\nGroup']
return tabulate(table_entries, headers=table_headers) return tabulate(table_entries, headers=table_headers)
except Exception as e: except Exception as e:
raise e raise e
@ -89,7 +92,49 @@ class Table(object):
checkmark_glyph = '' checkmark_glyph = ''
times_glyph = '' times_glyph = ''
exclamation_glyph = '' exclamation_glyph = ''
logger = logging.getLogger('table.recipes') logger = logging.getLogger('table.recipe')
normal_recipes = [recipe for recipe in self.recipes if re.search(r'^normal', recipe['type'])]
recipes_req_map = {recipe_reqs['recipe_id']: recipe_reqs for recipe_reqs in self.recipes_reqs}
try:
table_entries = []
try:
for item in normal_recipes:
table_entry = []
table_entry.append(item['id'])
table_entry.append(item['name'])
table_entry.append(item['base_servings'])
recipe_reqs = recipes_req_map[item['id']]
missing_amount = int(recipe_reqs['missing_products_count'])
number_of_ingredients = 's' if missing_amount > 1 else ''
if recipe_reqs['need_fulfilled'] == '1':
table_entry.append(self.ENOUGH_IN_STOCK_MSG.format(glyph=checkmark_glyph))
elif recipe_reqs['need_fulfilled_with_shopping_list'] == '1':
missing_amount = recipe_reqs['missing_products_count']
table_entry.append(self.NOT_ENOUGH_BUT_IN_SHOPPING_LIST_MSG.format(glyph=exclamation_glyph,
missing_amount=missing_amount, s=number_of_ingredients))
else:
table_entry.append(self.NOT_ENOUGH_IN_STOCK_MSG.format(glyph=times_glyph,
missing_amount=missing_amount, s=number_of_ingredients))
table_entries.append(table_entry)
except Exception as e:
logger.error(e)
raise e
# Generate recipes overview table
table_headers = ['Id', 'Name', 'Servings', 'Requirements Fulfilled']
return tabulate(table_entries, headers=table_headers)
except Exception as e:
logger.error(e)
raise e
@property
def recipes(self):
checkmark_glyph = ''
times_glyph = ''
exclamation_glyph = ''
logger = logging.getLogger('table.recipe')
normal_recipes = [recipe for recipe in self.recipes if re.search(r'^normal', recipe['type'])] normal_recipes = [recipe for recipe in self.recipes if re.search(r'^normal', recipe['type'])]
recipes_req_map = {recipe_reqs['recipe_id']: recipe_reqs for recipe_reqs in self.recipes_reqs} recipes_req_map = {recipe_reqs['recipe_id']: recipe_reqs for recipe_reqs in self.recipes_reqs}
@ -160,3 +205,5 @@ class Table(object):
# Generate stock overview table # Generate stock overview table
return tabulate(table_entries, headers=table_headers) return tabulate(table_entries, headers=table_headers)

39
models.json Normal file
View File

@ -0,0 +1,39 @@
{
"id": "3",
"name": "Garbonza Beans",
"description": "",
"location": {
"id": "3",
"name": "Pantry",
"description": "",
"row_created_timestamp": "2018-10-21 03:24:25"
},
"quantity_unit": {
"stock": {
"id": "4",
"name": "Ib",
"description": "Pounds",
"row_created_timestamp": "2018-10-21 03:24:11",
"name_plural": "Ibs",
"plural_forms": null
},
"purchase": {
"id": "4",
"name": "Ib",
"description": "Pounds",
"row_created_timestamp": "2018-10-21 03:24:11",
"name_plural": "Ibs"
}
},
"qu_factor_purchase_to_stock": "1.0",
"barcode": "",
"min_stock_amount": "0",
"default_best_before_days": "0",
"row_created_timestamp": "2018-10-21 03:39:42",
"product_group_id": "",
"picture_file_name": null,
"default_best_before_days_after_open": "0",
"allow_partial_units_in_stock": "0",
"enable_tare_weight_handling": "0",
"tare_weight": "0.0",
"not_check_stock_fulfillment_for_recipes": "0"

View File

@ -22,7 +22,7 @@ setup(
packages=find_packages(), packages=find_packages(),
include_package_data=True, include_package_data=True,
zip_safe=False, zip_safe=False,
install_requires=['Click', 'pyyaml', 'requests', 'taskw', 'lockfile', 'tox', 'html2text', 'markdown'], install_requires=['Click', 'pyyaml', 'requests', 'taskw', 'lockfile', 'tox', 'html2text', 'markdown', 'cachecontrol', 'fontawesome'],
long_description=read('README.rst'), long_description=read('README.rst'),
tests_require=[ tests_require=[
"pytest_mock", "pytest_mock",

1
templates/debug.yml Normal file
View File

@ -0,0 +1 @@
{{ grocy | tojson(indent=2) | safe }}

20
templates/product/add.yml Normal file
View File

@ -0,0 +1,20 @@
# Legend:
# Location Name (id): {% for valid_value in grocy.meta.entities.locations.valid_values %}{{ valid_value.name }}({{ valid_value.id}}){% if not loop.last %}{{ ', '}}{% endif %}{% endfor %}
# Product Group (id): {% for valid_value in grocy.meta.entities.product_groups.valid_values %}{{ valid_value.name }}({{ valid_value.id}}){% if not loop.last %}{{ ', '}}{% endif %}{% endfor %}
# Quanity Units (id): {% for valid_value in grocy.meta.entities.quantity_units.valid_values %}{{ valid_value.name }}({{ valid_value.id}}){% if not loop.last %}{{ ', '}}{% endif %}{% endfor %}
name:
description: |-
barcode:
location_id:
min_stock_amount: 0
default_best_before_days: 0
product_group_id:
qu_id_purchase:
qu_id_stock:
default_best_before_days_after_open: 0
qu_factor_purchase_to_stock: 0
allow_partial_units_in_stock: False
enable_tare_weight_handling: False
tare_weight: 0
not_check_stock_fulfillment_for_recipes: False

View File

18
templates/recipe/edit.yml Normal file
View File

@ -0,0 +1,18 @@
name: {{ grocy.fields.name }}
description: |
{{ grocy.fields.description }}
base_servings: {{ grocy.fields.base_servings | default("1") }}
desired_servings: {{ grocy.fields.desired_servings | default("1") }}
not_check_shoppinglist: {{ grocy.fields.not_check_shoppinglist | default("1") }}
products: {% for ingredient in grocy.fields.ingredientsp%}
- id: {{ ingredient.id }}
name: {{ ingredient.name }}
description: {{ ingredient.description | default(null) }}
note: {{ ingredient.note }}
amount: {{ ingredient.amount }}
qu_id: {{ ingredient.qu_id }}
only_check_single_unit_in_stock: {{ ingredient.only_check_single_unit_in_stock }}
ingredient_group: {{ ingredient.ingredient_group | default(null) }}
not_check_stock_fulfillment: {{ ingredient.not_check_stock_fulfillment }}{% endfor %}

20
templates/recipe/view.yml Normal file
View File

@ -0,0 +1,20 @@
name: {{ grocy.fields.name }}
servings: {{ grocy.fields.base_servings }}
costs: {{ grocy.fields.fulfillment.costs }}
ingredients: {% for ingredient in grocy.fields.ingredients %}
- product_id: {{ ingredient.id }}
name: {{ ingredient.name}}
description: {{ ingredient.description | default(null) }}
note: {{ ingredient.note }}
amount: {{ ingredient.amount }}
qu_id: {{ ingredient.qu_id }}
only_check_single_unit_in_stock: {{ ingredient.only_check_single_unit_in_stock }}
ingredient_group: {{ ingredient.ingredient_group | default(null) }}
not_check_stock_fulfillment: {{ ingredient.not_check_stock_fulfillment }}{% endfor %}
description: |-
{{ grocy.fields.description }}

View File

@ -1,24 +0,0 @@
name: {{ name }}
description: |
{{ description }}
picture_file_name: {% if picture_file_name is not none %}
{{picture_file_name}}
{% else %}
{% endif %}
base_servings: {{ base_servings | default("1") }}
desired_servings: {{ desired_servings | default("1") }}
not_check_shoppinglist: {{ not_check_shoppinglist | default("1") }}
products: {% for product in products %}
- id: {{ product.id }}
name: {{ product.name }}
description: {{ product.description | default(null) }}
note: {{ recipes_pos[loop.index0].note }}
amount: {{ recipes_pos[loop.index0].amount }}
qu_id: {{ recipes_pos[loop.index0].qu_id }}
only_check_single_unit_in_stock: {{ recipes_pos[loop.index0].only_check_single_unit_in_stock }}
ingredient_group: {{ recipes_pos[loop.index0].ingredient_group | default(null) }}
not_check_stock_fulfillment: {{ recipes_pos[loop.index0].not_check_stock_fulfillment }}{% endfor %}