feat: Added templates for macro, list, and view for recipe

- feat: Added list and view commands for recipe
This commit is contained in:
Aerex
2019-08-04 23:59:28 -05:00
parent 6af9bef334
commit 05089108a9
11 changed files with 282 additions and 118 deletions

View File

@@ -23,6 +23,7 @@ CONFIG_DIR = click.get_app_dir(APP_NAME)
PROJ_DIR = path.join(path.dirname(path.realpath(__file__)))
TEMPLATE_LOADER = Environment(loader=FileSystemLoader('templates'), trim_blocks=True, lstrip_blocks=True)
class GrocyGroup(click.Group):
def parse_args(self, ctx, args):
is_subcommand = True if args[0] in self.commands else False
@@ -62,7 +63,7 @@ def main():
@main.command()
@click.pass_context
@click.pass_context
def stock(ctx):
logger = logging.getLogger('cli.stock')
if ctx.invoked_subcommand is None:
@@ -97,6 +98,7 @@ def view(ctx, product_id):
logger.error(e)
raise e
@product.command()
@click.option('--name', '-n', 'name')
@click.argument('product_id', required=False)
@@ -124,7 +126,7 @@ def edit(product_id, name):
if edited_products is None:
return
parsed_edited_products = Util.load_yaml(edited_products)
parsed_edited_products = util.load_yaml(edited_products)
schema = entity.schema
for index, edited_product in enumerate(parsed_edited_products):
@@ -154,6 +156,8 @@ def list(name, template):
# Convert name args to a single string
string_name_arg = ' '.join(name) if type(name) == list else name
products = product_entity.find({'name': string_name_arg})
if len(products) == 0:
return
else:
products = product_entity.get()
@@ -193,16 +197,23 @@ def add(template):
logger = logging.getLogger('cli.product.add')
meta = Meta()
# Get product_groups
meta.add(type='entities', name='product_groups')
entity = Entity(name='product_groups')
product_groups = entity.get()
meta.add(type='entities', name='product_groups', valid_values=product_groups)
# Get locations
meta.add(type='entities', name='locations')
entity = Entity(name='locations')
locations = entity.get()
meta.add(type='entities', name='locations', valid_values=locations)
# Get quantity_units
meta.add(type='entities', name='quantity_units')
entity = Entity(name='quantity_units')
quantity_units = entity.get()
meta.add(type='entities', name='quantity_units', valid_values=quantity_units)
data = { 'meta': meta.generate() }
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')
new_product = click.edit(loaded_template.render(grocy=data), extension='.yml')
if not new_product:
return
@@ -221,7 +232,8 @@ def add(template):
@click.pass_context
def recipe(ctx):
pass
@recipe.command()
@click.pass_context
@click.argument('recipe_id', required=False)
@@ -234,43 +246,158 @@ def view(ctx, recipe_id, template):
data = {'fields': {}}
if recipe_id:
entity = Entity(name='recipes')
meta = Meta()
data['fields'] = entity.get(id=recipe_id)
recipe = Recipe(id=recipe_id)
data['fields']['fulfillment'] = recipe.fulfillment
# Change html markup to plain text
html_markup_description = data['fields']['description']
html_markup_description = data['fields']['description']
plain_text_description = html2text(html_markup_description)
data['fields']['description'] = plain_text_description
data['fields']['description'] = plain_text_description
recipe = Recipe(id=recipe_id)
data['fields']['fulfillment'] = recipe.get_fulfillments()
meta.add(type='recipes', name='ingredients', valid_values=recipe.ingredients)
data['meta'] = meta.generate()
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
##
#
@recipe.command()
@click.option('--name', '-n', 'name')
@click.option('-t', 'template')
def list(name, template):
logger = logging.getLogger('cli.recipe.list')
cfg = Configuration()
cfg.load()
try:
entity = Entity(name='recipes')
if name:
# Convert name args to a single string
string_name_arg = ' '.join(name) if type(name) == list else name
recipe_entities = entity.find({'name': string_name_arg})
if len(recipe_entities) == 0:
return 0
else:
recipe_entities = entity.get()
data = {'recipes': []}
for recipe_entity in recipe_entities:
entry = {'fields': recipe_entity, 'meta': []}
meta = Meta(include_fa_icons=False)
recipe = Recipe(id=recipe_entity['id'])
entry['fields']['fulfillment'] = recipe.fulfillment
entry['fields']['description'] = recipe.generate_plain_text_description(recipe_entity['description'])
meta.add(type='recipes', name='ingredients', valid_values=recipe.ingredients)
entry['meta'].append(meta.generate())
data['recipes'].append(entry)
meta = Meta()
data['meta'] = meta.generate()
if template:
loaded_template = cfg.templates(template)
else:
loaded_template = cfg.templates('recipe/list')
click.echo(loaded_template.render(grocy=data))
except Exception as e:
logger.error(e)
raise e
@recipe.command()
@click.option('--fullscreen', '-f', 'fullscreen', is_flag=True)
@click.argument('recipe_id')
def browse(fullscreen, recipe_id):
logger = logging.getLogger('cli.recipe.browse')
try:
cfg = Configuration()
cfg.load()
url = '{domain}/recipes?recipe={recipe_id}'.format(domain=cfg.domain, recipe_id=recipe_id)
if fullscreen:
url += '#fullscreen'
click.launch(url, wait=False)
except Exception as e:
logger.error(e)
@recipe.command()
@click.option('--name', '-n', 'name')
@click.argument('recipe_id', required=False)
@click.option('-t', 'template')
def edit(recipe_id, name, template):
logger = logging.getLogger('cli.recipe.edit')
try:
cfg = Configuration()
util = Util(cfg=cfg)
cfg.load()
loaded_template = cfg.templates('recipe/edit')
if template:
loaded_template = cfg.templates(template)
else:
loaded_template = cfg.templates('recipe/list')
entity = Entity(name='recipes')
if recipe_id:
recipe_entity = entity.get(id=recipe_id)
recipe = Recipe(id=recipe_id)
entry = {'fields': recipe_entity}
meta = Meta()
entry['fields']['fulfillment'] = recipe.fulfillment
entry['fields']['description'] = recipe.generate_plain_text_description(entity['description'])
entry['meta'] = meta.generate()
edited_recipe = click.edit(loaded_template.render(grocy=entry))
edited_recipe.update()
return
elif name:
# Convert name args to a single string
string_name_arg = ' '.join(name) if type(name) == list else name
recipe_entities = entity.find({'name': string_name_arg})
if len(recipe_entities) == 0:
click.echo('Could not find recipe')
return
data = {'recipes': []}
for recipe_entity in recipe_entities:
entry = {'fields': recipe_entity, 'meta': []}
meta = Meta(include_fa_icons=False)
recipe = Recipe(id=recipe_entity['id'])
entry['fields']['fulfillment'] = recipe.fulfillment
entry['fields']['description'] = recipe.generate_plain_text_description(recipe_entity['description'])
meta.add(type='recipes', name='ingredients', valid_values=recipe.ingredients)
entry['meta'].append(meta.generate())
data['recipes'].append(entry)
meta = Meta()
data['meta'] = meta.generate()
edited_recipes = click.edit(loaded_template.render(grocy=data))
if edited_recipes is None:
return
parsed_edited_recipes = util.load_yaml(edited_recipes)
for index, edited_recipe in enumerate(parsed_edited_recipes):
edited_recipe['id'] = recipe_entities[index]['id']
# Util.verify_integrity(edited_recipe, schema)
entity.update(edited_recipe, id=recipe[index]['id'])
else:
raise click.BadParameter('Missing RECIPE_ID or QUERY')
except Exception as e:
logger.error(e)
raise e
#@main.command()
#def shopping():
# logger = logging.getLogger('cli.shopping')
@@ -337,27 +464,6 @@ def view(ctx, recipe_id, template):
# raise e
#
#
#@recipe.command('edit')
#@click.argument('recipe_id')
#def edit(recipe_id):
# logger = logging.getLogger('cli.recipe.edit')
# try:
# if recipe_id:
# entity = Entity(name='recipes')
# recipe = entity.get(id=recipe_id)
# loaded_template = TEMPLATE_LOADER.get_template('recipe_edit.yml')
# edited_recipe = click.edit(loaded_template.render(recipe))
# if edited_recipe is not None:
# parsed_edited_recipe = yaml.safe_load(edited_recipe)
# parsed_edited_recipe['description'] = markdown(parsed_edited_recipe['description'])
# recipe.__dict__.update(parsed_edited_recipe)
# recipe.update()
# except Exception as e:
# logger.error(e)
# logger.error('Could not edit recipe {}'.format(recipe_id))
# raise e
#
#
#@recipe.command('create')
#@click.pass_context
#def create(ctx):

View File

@@ -99,7 +99,7 @@ class Configuration(object):
def templates(self, name):
try:
TEMPLATE_LOADER = Environment(loader=FileSystemLoader([self.USER_TEMPLATE_DIR, self.PROJ_TEMPLATE_DIR]),
trim_blocks=False, lstrip_blocks=True)
trim_blocks=False, lstrip_blocks=True, keep_trailing_newline=False)
return TEMPLATE_LOADER.get_template('{}.{}'.format(name, self.TEMPLATE_EXT))
except Exception as e:

View File

@@ -2,7 +2,6 @@ from grocy.request import Request
import re
from copy import deepcopy
from grocy.conf import Configuration
import logging
@@ -51,7 +50,7 @@ class Entity(object):
found_entities.append(entity)
if len(found_entities) == 0:
return None
return []
return found_entities
except Exception as e:

View File

@@ -5,23 +5,21 @@ from fontawesome import icons as fa_icons
class Meta(object):
def __init__(self):
self.meta = {'meta': {'entities': {}, 'fa_icons': fa_icons}}
def __init__(self, include_fa_icons=True):
self.meta = {}
if include_fa_icons:
self.meta['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 add(self, type, name=None, ids=[], valid_values=None):
if type not in self.meta:
self.meta[type] = {}
if name and name not in self.meta[type]:
self.meta[type][name] = {}
#if name:
# schema = get_schema(name)
#if type == 'entities':
#self.meta[type][name]['properties'] = schema['properties']
self.meta[type][name]['valid_values'] = valid_values
def generate(self):
return self.meta

View File

@@ -1,5 +1,8 @@
from grocy.request import Request
from html2text import html2text
from grocy.stock import Stock
from grocy.conf import Configuration
from copy import deepcopy
from grocy.entity import Entity
import logging
@@ -7,17 +10,87 @@ import logging
class Recipe(object):
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'
GET_RECIPES_POS_FULFILLMENT_URL_TEMPLATE = '{domain}/api/recipes/{recipeId}/pos/fulfillment'
GET_RECIPE_POS_FULFILLMENT_URL_TEMPLATE = '{domain}/api/recipes/{recipeId}/pos/{recipePosId}/fulfillment'
def __init__(self, id=None):
self.conf = Configuration()
self.conf.load()
self.id = id
def get_ingredient_requirements(self, recipe_id=None):
logger = logging.getLogger('recipe.get_ingredient_requirements')
@property
def ingredients(self):
logger = logging.getLogger('recipes.ingredients')
stock = Stock()
ingredients_detail = []
products = Entity(name='products').get()
locations = Entity(name='locations').get()
quantity_units = Entity(name='quantity_units').get()
for recipe_pos in self.pos_fulfillment:
if recipe_pos['recipe_id'] == self.id:
data = {}
product = next((p for p in products if p['id'] == recipe_pos['product_id']), None)
product['quantity_unit'] = next((q for q in quantity_units if q['id'] == recipe_pos['qu_id']), None)
product['location'] = next((l for l in locations if l['id'] == product['location_id']), None)
product['group'] = recipe_pos['ingredient_group']
# Remove extraneous properties
recipe_pos.pop('recipe_id', None)
recipe_pos.pop('ingredient_group', None)
recipe_pos.pop('id', None)
recipe_pos.pop('product_id', None)
recipe_pos.pop('recipe_pos_id', None)
recipe_pos.pop('qu_id', None)
product.pop('qu_id_purchase', None)
product.pop('qu_id_stock', None)
product.pop('location_id', None)
product['location'].pop('row_created_timestamp', None)
product['quantity_unit'].pop('row_created_timestamp', None)
product.pop('row_created_timestamp', None)
# Merge recipe pos and products
product.update(recipe_pos)
data.update(product)
ingredients_detail.append(data)
return ingredients_detail
@property
def pos_fulfillment(self):
logger = logging.getLogger('recipe.pos_fulfillment')
try:
if self.id is None:
raise Exception('recipe id is required')
url = self.GET_RECIPES_POS_FULFILLMENT_URL_TEMPLATE.format(domain=self.conf.domain, recipeId=self.id)
request = Request('get', url)
return request.send()
except Exception as e:
logger.error(e)
raise e
def generate_plain_text_description(self, description):
if not description:
raise Exception('Missing description')
logger = logging.getLogger('recipe.generate_plain_text_description')
try:
plain_text_description = html2text(description)
return plain_text_description
except Exception as e:
logger.error(e)
raise e
@property
def fulfillment(self):
logger = logging.getLogger('recipe.fulfillment')
url = self.GET_RECIPE_FULFILLMENT_URL_TEMPLATE.format(domain=self.conf.domain, recipeId=self.id)
request = Request('get', url)
try:
return request.send()
except Exception as e:
logger.error(e)
raise e
def get_fulfillments(self):
logger = logging.getLogger('recipe.get_requirements')
@@ -31,24 +104,3 @@ class Recipe(object):
except Exception as e:
logger.error(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

@@ -5,11 +5,12 @@ import logging
SCHEMA_URL_TEMPLATE = '{domain}/api/openapi/specification'
SCHEMA_MODEL_MAP = {
'products': 'Product',
'product_details': 'ProductDetailsResponse',
'stock': 'StockEntry',
'product_groups': 'ProductGroup',
'locations': 'Location',
'quantity_units': 'QuantityUnit',
'recipe_requirements': 'RecipeFulfilmentResponse'
'recipe_fulfillment': 'RecipeFulfillmentResponse'
}

View File

@@ -11,8 +11,7 @@ class Util(object):
self.cfg = cfg
yaml.SafeLoader.add_constructor("tag:yaml.org,2002:python/unicode", _yaml_constructor)
def load_yaml(data):
def load_yaml(self, data):
generator = yaml.safe_load_all(data)
data_list = list(generator)
return data_list