feat: Added templates for macro, list, and view for recipe
- feat: Added list and view commands for recipe
This commit is contained in:
200
grocy/cli.py
200
grocy/cli.py
@@ -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):
|
||||
|
@@ -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:
|
||||
|
@@ -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:
|
||||
|
@@ -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
|
||||
|
102
grocy/recipe.py
102
grocy/recipe.py
@@ -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
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
@@ -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'
|
||||
}
|
||||
|
||||
|
||||
|
@@ -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
|
||||
|
Reference in New Issue
Block a user