feat: Added recipe list command
This commit is contained in:
parent
a1c593b118
commit
0a1ecddf3d
@ -76,10 +76,6 @@ class RestService(object):
|
||||
|
||||
r = requests.post(url, json=json.dumps(json_payload), headers=self.headers)
|
||||
|
||||
#if r.raise_for_status():
|
||||
# logger.error(r.raise_for_status())
|
||||
# raise r.raise_for_status()
|
||||
|
||||
if self.json:
|
||||
return r.json()
|
||||
|
||||
@ -89,4 +85,4 @@ class RestService(object):
|
||||
self.headers[type] = value
|
||||
|
||||
def addToken(self, value):
|
||||
self.headers[RestService.API_KEY_HEADER] = value;
|
||||
self.headers[RestService.API_KEY_HEADER] = value
|
||||
|
185
grocy/cli.py
185
grocy/cli.py
@ -1,20 +1,16 @@
|
||||
import click
|
||||
from markdown import markdown
|
||||
from dataclasses import asdict, replace
|
||||
from jinja2 import Environment, FileSystemLoader
|
||||
from uuid import uuid4
|
||||
from grocy import RestService
|
||||
from grocy.models import (Stock,
|
||||
Battery, Shopping, Recipe)
|
||||
from pkg_resources import iter_entry_points
|
||||
from grocy.conf import Configuration
|
||||
from grocy.recipe import Recipe
|
||||
from grocy.table import Table
|
||||
from grocy.entity import Entity
|
||||
from grocy.stock import Stock
|
||||
import yaml
|
||||
from sys import exit
|
||||
from shutil import copy
|
||||
from os import path, chmod, makedirs
|
||||
from taskw import TaskWarriorShellout
|
||||
from os import path
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
APP_NAME = 'grocy-cli'
|
||||
SAMPLE_CONFIG_FILE = 'sample.config.yml'
|
||||
@ -22,103 +18,136 @@ TMP_DIR = '/tmp/grocy'
|
||||
CONFIG_FILE = 'config.yml'
|
||||
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)
|
||||
TEMPLATE_LOADER = Environment(loader=FileSystemLoader('templates'), trim_blocks=True, lstrip_blocks=True)
|
||||
|
||||
|
||||
def __validate_token(cfg):
|
||||
# Validate token
|
||||
if hasattr(cfg, 'token') or cfg['token'] is None:
|
||||
click.echo('No token was found. Please add your token to the config file', err=True)
|
||||
logger.error('No token was found. Please add your token')
|
||||
#logger.error('No token was found. Please add your token')
|
||||
exit(1)
|
||||
|
||||
def __create_config_file():
|
||||
user_cfg_file = path.join(CONFIG_DIR, CONFIG_FILE)
|
||||
sample_cfg_file = path.join(PROJ_DIR, '..', SAMPLE_CONFIG_FILE)
|
||||
if not path.exists(CONFIG_DIR):
|
||||
click.echo('Config {} director does not exist, create...'.format(CONFIG_DIR))
|
||||
makedirs(CONFIG_DIR)
|
||||
|
||||
copy(sample_cfg_file, user_cfg_file)
|
||||
click.echo('Copying sample config to {}'.format(sample_cfg_file, user_cfg_file))
|
||||
chmod(user_cfg_file, 0o664)
|
||||
return user_cfg_file
|
||||
|
||||
@click.group()
|
||||
@click.pass_context
|
||||
def main(ctx):
|
||||
cfg = get_config_file()
|
||||
# Get logger
|
||||
if 'logger' in cfg:
|
||||
log_cfg = cfg['logger']
|
||||
else:
|
||||
log_cfg = path.join(click.get_app_dir(APP_NAME), 'grocy.log')
|
||||
log_level = 'DEBUG' if 'level' not in log_cfg else log_cfg['level']
|
||||
log_filename = 'log' if 'file_location' not in log_cfg else log_cfg['file_location']
|
||||
logging.basicConfig(level=log_level, filename=log_filename)
|
||||
cfg['logger'] = logger
|
||||
|
||||
__validate_token(cfg)
|
||||
ctx.ensure_object(dict)
|
||||
ctx.obj['cfg'] = cfg
|
||||
|
||||
|
||||
def get_config_file():
|
||||
no_config_msg = 'A config file was not found'
|
||||
no_config_msg+= ' and will be created under {}. Is that Ok?'
|
||||
no_config_msg = no_config_msg.format(click.get_app_dir(APP_NAME))
|
||||
cfg_file = path.join(click.get_app_dir(APP_NAME), 'config.yml')
|
||||
if not path.exists(cfg_file):
|
||||
def main():
|
||||
cfg = Configuration()
|
||||
if not cfg.exists:
|
||||
no_config_msg = 'A config file was not found. A sample configuration file'
|
||||
no_config_msg += ' will be created under {}. Is that Ok?'.format(cfg.CONFIG_DIR)
|
||||
create_config_app_dir = click.confirm(no_config_msg)
|
||||
user_cfg_options = {}
|
||||
if create_config_app_dir:
|
||||
cfg_file = __create_config_file()
|
||||
user_cfg_options['logger_level'] = click.prompt('Enter logger level',
|
||||
default='DEBUG')
|
||||
user_cfg_options['logger_file_location'] = click.prompt('Enter location for logger',
|
||||
default=path.expanduser('~/.config/grocy/log'))
|
||||
user_cfg_options['api'] = click.prompt('Enter the grocy api url',
|
||||
default='https://demo-en.grocy.info/api')
|
||||
user_cfg_options['token'] = click.prompt('Enter the grocy token ',
|
||||
default='')
|
||||
user_cfg_options['col_format'] = click.prompt('Enter the col position for rendering tables',
|
||||
default='col')
|
||||
user_cfg_options['table_format'] = click.prompt('Enter the table format',
|
||||
default='simple')
|
||||
cfg.create(user_cfg_options)
|
||||
else:
|
||||
exit(0)
|
||||
fd = open(cfg_file)
|
||||
parse_cfg_file = yaml.safe_load(fd)
|
||||
else:
|
||||
cfg.load()
|
||||
|
||||
return parse_cfg_file
|
||||
logging.basicConfig(level=cfg.logger_level, filename=cfg.logger_file_location)
|
||||
|
||||
|
||||
@main.command()
|
||||
@click.pass_context
|
||||
def stock(ctx):
|
||||
cfg = ctx.obj['cfg']
|
||||
logger = logging.getLogger('cli.stock')
|
||||
if ctx.invoked_subcommand is None:
|
||||
try:
|
||||
entity = Stock()
|
||||
stocks = entity.get()
|
||||
table = Table(stocks=stocks)
|
||||
click.echo(table.stock)
|
||||
except Exception as e:
|
||||
logger.error(e)
|
||||
raise e
|
||||
|
||||
stock = Stock(**cfg)
|
||||
stock_entries = stock.get_entries()
|
||||
click.echo(stock_entries)
|
||||
|
||||
@main.command()
|
||||
@click.pass_context
|
||||
def shopping(ctx):
|
||||
cfg = ctx.obj['cfg']
|
||||
def shopping():
|
||||
logger = logging.getLogger('cli.shopping')
|
||||
try:
|
||||
entity = Entity(name='shopping_list')
|
||||
shopping_list = entity.get()
|
||||
table = Table(shopping_list=shopping_list)
|
||||
click.echo(table.shopping_list)
|
||||
except Exception as e:
|
||||
logger.error(e)
|
||||
raise e
|
||||
|
||||
|
||||
@main.group()
|
||||
def ingredient(ctx):
|
||||
pass
|
||||
|
||||
|
||||
@ingredient.command('add')
|
||||
@click.option('-r', '--recipe_id')
|
||||
def add(ctx, recipe_id):
|
||||
logger = logging.getLogger('cli.ingredient.add')
|
||||
|
||||
try:
|
||||
loaded_template = TEMPLATE_LOADER.get_template('ingredient_add.yml')
|
||||
|
||||
if recipe_id:
|
||||
entity = Entity(name='recipes')
|
||||
recipe = entity.get(id=recipe_id)
|
||||
new_ingredient = click.edit(loaded_template.render(recipe))
|
||||
parsed_new_ingredient = yaml.safe_load(new_ingredient)
|
||||
else:
|
||||
new_ingredient = click.edit(loaded_template.render())
|
||||
parsed_new_ingredient = yaml.safe_load(new_ingredient)
|
||||
if parsed_new_ingredient['recipe_id']:
|
||||
raise Exception('Recipe id is not defined')
|
||||
|
||||
if new_ingredient:
|
||||
entity = Entity(name='ingredients', **parsed_new_ingredient)
|
||||
entity.create()
|
||||
except Exception as e:
|
||||
logger.error(e)
|
||||
|
||||
shopping = Shopping(**cfg)
|
||||
shopping_list = shopping.get_list()
|
||||
click.echo(shopping_list)
|
||||
|
||||
@main.group()
|
||||
@click.pass_context
|
||||
def recipe(ctx):
|
||||
if ctx.invoked_subcommand is None:
|
||||
cfg = ctx.obj['cfg']
|
||||
pass
|
||||
|
||||
receipe = Recipe(id=None, **cfg)
|
||||
recipes = receipe.get_list()
|
||||
click.echo(recipes)
|
||||
|
||||
@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')
|
||||
@click.argument('recipe_id')
|
||||
@click.pass_context
|
||||
def edit(ctx, recipe_id):
|
||||
cfg = ctx.obj['cfg']
|
||||
logger = cfg['logger']
|
||||
|
||||
def edit(recipe_id):
|
||||
logger = logging.getLogger('cli.recipe.edit')
|
||||
try:
|
||||
if recipe_id:
|
||||
recipe = Recipe(id=recipe_id, **cfg)
|
||||
recipe.get(include_products=True)
|
||||
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.toJSON()))
|
||||
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'])
|
||||
@ -127,16 +156,16 @@ def edit(ctx, recipe_id):
|
||||
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):
|
||||
cfg = ctx.obj['cfg']
|
||||
logger = cfg['logger']
|
||||
logger = logging.getLogger('cli.recipe.create')
|
||||
|
||||
try:
|
||||
recipe = Recipe(**cfg)
|
||||
recipe = Entity(name='recipes')
|
||||
loaded_template = TEMPLATE_LOADER.get_template('recipe_add.yml')
|
||||
new_recipe = click.edit(loaded_template.render())
|
||||
if new_recipe is not None:
|
||||
@ -146,6 +175,8 @@ def create(ctx):
|
||||
recipe.create()
|
||||
except Exception as e:
|
||||
logger.error(e)
|
||||
raise e
|
||||
|
||||
|
||||
#@main.command()
|
||||
#@click.pass_context
|
||||
|
85
grocy/conf.py
Normal file
85
grocy/conf.py
Normal file
@ -0,0 +1,85 @@
|
||||
from os import path, chmod, makedirs
|
||||
from yaml import safe_load, dump
|
||||
from copy import deepcopy
|
||||
|
||||
|
||||
class Configuration(object):
|
||||
# TODO: Figure out how to handle windows config
|
||||
CONFIG_DIR = path.expanduser('~/.config/grocy')
|
||||
CONFIG_FILE = CONFIG_DIR + '/config.yml'
|
||||
API_KEY_HEADER = 'GROCY-API-KEY'
|
||||
DEFAULT_CFG = {
|
||||
'logger': {
|
||||
'level': 'DEBUG',
|
||||
'file_location': None
|
||||
},
|
||||
'api': 'https://demo-en.grocy.info/api',
|
||||
'token': None,
|
||||
'formats': {
|
||||
'col': 'center',
|
||||
'table': 'simple'
|
||||
}
|
||||
}
|
||||
|
||||
def update(self, props):
|
||||
# Set nested fields (e.g. logger, formats)
|
||||
if props['logger']:
|
||||
for prop in props['logger']:
|
||||
prop_attr = 'logger_{prop}'.format(prop=prop)
|
||||
prop_value = props['logger'][prop]
|
||||
setattr(self, prop_attr, prop_value)
|
||||
if props['formats']:
|
||||
for prop in props['formats']:
|
||||
prop_attr = '{prop}_format'.format(prop=prop)
|
||||
prop_value = props['formats'][prop]
|
||||
setattr(self, prop_attr, prop_value)
|
||||
|
||||
for prop, value in props.items():
|
||||
if not prop == 'logger' and prop in props:
|
||||
setattr(self, prop, value)
|
||||
|
||||
def create(self, user_cfg_options={}):
|
||||
cfg_json = deepcopy(self.DEFAULT_CFG)
|
||||
|
||||
if user_cfg_options['logger_level']:
|
||||
self.logger_level = user_cfg_options['logger_level']
|
||||
cfg_json['logger']['level'] = self.logger_level
|
||||
|
||||
if 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
|
||||
|
||||
if user_cfg_options['api']:
|
||||
self.api = user_cfg_options['api']
|
||||
cfg_json['api'] = self.api
|
||||
|
||||
if user_cfg_options['token']:
|
||||
self.token = user_cfg_options['token']
|
||||
cfg_json['token'] = self.token
|
||||
|
||||
if user_cfg_options['col_format']:
|
||||
self.col_format = user_cfg_options['col_format']
|
||||
cfg_json['formats']['col'] = self.col_format
|
||||
|
||||
if user_cfg_options['table_format']:
|
||||
self.table_format = user_cfg_options['table_format']
|
||||
cfg_json['formats']['table'] = self.table_format
|
||||
|
||||
# Create new configuration
|
||||
makedirs(self.CONFIG_DIR)
|
||||
with open(self.CONFIG_FILE, 'x') as fd:
|
||||
dump_cfg = dump(cfg_json, default_flow_style=False, allow_unicode=True, encoding=None)
|
||||
fd.write(dump_cfg)
|
||||
chmod(self.CONFIG_DIR, 0o755)
|
||||
|
||||
@property
|
||||
def exists(self):
|
||||
return path.exists(self.CONFIG_FILE)
|
||||
|
||||
def load(self):
|
||||
if self.exists:
|
||||
with open(self.CONFIG_FILE) as fd:
|
||||
data = safe_load(fd)
|
||||
self.update(data)
|
||||
else:
|
||||
self.create()
|
39
grocy/entity.py
Normal file
39
grocy/entity.py
Normal file
@ -0,0 +1,39 @@
|
||||
from grocy.request import Request
|
||||
from grocy.conf import Configuration
|
||||
import logging
|
||||
|
||||
|
||||
class Entity(object):
|
||||
RESOURCE_URL_TEMPLATE = '{api}/objects/{entity}/{objectId}'
|
||||
COLLECTION_URL_TEMPLATE = '{api}/objects/{entity}'
|
||||
|
||||
def __init__(self, name, **props):
|
||||
self.conf = Configuration()
|
||||
self.conf.load()
|
||||
self.name = name
|
||||
self.__dict__.update(**props)
|
||||
|
||||
def get(self, id=None):
|
||||
logger = logging.getLogger('entity.get')
|
||||
if id:
|
||||
url = self.RESOURCE_URL_TEMPLATE.format(api=self.conf.api, entity=self.name, objectId=id)
|
||||
else:
|
||||
url = self.COLLECTION_URL_TEMPLATE.format(api=self.conf.api, entity=self.name)
|
||||
|
||||
request = Request('get', url)
|
||||
try:
|
||||
return request.send()
|
||||
except Exception as e:
|
||||
logger.error(e)
|
||||
raise e
|
||||
|
||||
def create(self, entity):
|
||||
logger = logging.getLogger('entity.add')
|
||||
url = self.RESOURCE_URL_TEMPLATE
|
||||
|
||||
request = Request('post', url)
|
||||
try:
|
||||
return request.send()
|
||||
except Exception as e:
|
||||
logger.error(e)
|
||||
raise e
|
23
grocy/models.py
Normal file
23
grocy/models.py
Normal file
@ -0,0 +1,23 @@
|
||||
class Product(dict):
|
||||
def __init__(self, **entries):
|
||||
self.fields = [
|
||||
'location_id',
|
||||
'name',
|
||||
'description',
|
||||
'qu_id_purchase',
|
||||
'qu_id_stock',
|
||||
'qu_factor_purchase_to_stock',
|
||||
'barcode',
|
||||
'min_stock_amount',
|
||||
'default_best_before_days',
|
||||
'default_best_before_days_after_open',
|
||||
'tare_weight', 'enable_tare_weight_handling', 'picture_file_name','product_group_id',
|
||||
'allow_partial_units_in_stock']
|
||||
self.__dict__.update(entries)
|
||||
def toJSON(self):
|
||||
obj = {}
|
||||
for attr, value in self.__dict__.items():
|
||||
if attr in self.fields and value is not None:
|
||||
obj[attr] = value
|
||||
return obj
|
||||
|
@ -17,14 +17,3 @@ class Product(Schema):
|
||||
obj[attr] = value
|
||||
return obj
|
||||
|
||||
#id:str = ''
|
||||
#location_id:str = ''
|
||||
#name:str = ''
|
||||
#description:str = ''
|
||||
#qu_id_purchase:str = ''
|
||||
#qu_id_stock:str = ''
|
||||
#qu_factor_purchase_to_stock:float = 0
|
||||
#barcode:str = ''
|
||||
#min_stock_amount:int = 0
|
||||
#default_best_before_days:int = 0
|
||||
#default_best_before_days_after_open:int = 0
|
||||
|
@ -101,6 +101,17 @@ class Recipe(Schema):
|
||||
except Exception as e:
|
||||
raise e
|
||||
|
||||
def addIngredient(self, ingredient):
|
||||
ingredient['recipe_id'] = self.id
|
||||
hasProduct = False
|
||||
if 'product_id' in ingredient and ingredient['product_id'] is not None:
|
||||
hasProduct = Product.exists(ingredient['product_id'])
|
||||
if hasProduct:
|
||||
self.rest_service.post('recipes_pos', ingredient)
|
||||
else
|
||||
raise Exception('No product was given')
|
||||
|
||||
|
||||
def create(self):
|
||||
created_recipe = {
|
||||
'description': self.description,
|
||||
@ -110,6 +121,7 @@ class Recipe(Schema):
|
||||
'not_check_shoppinglist': self.not_check_shoppinglist
|
||||
}
|
||||
self.rest_service.post('recipes', created_recipe)
|
||||
|
||||
def get(self, include_products=False):
|
||||
try:
|
||||
recipe = self.rest_service.get('recipes', id=self.id)
|
||||
|
@ -1,8 +1,8 @@
|
||||
from grocy import RestService
|
||||
import logging
|
||||
|
||||
class Schema(object):
|
||||
|
||||
class Schema(object):
|
||||
def _init_rest_service(self):
|
||||
if hasattr(self, 'api'):
|
||||
if self.api.startswith == '/':
|
||||
|
22
grocy/recipe.py
Normal file
22
grocy/recipe.py
Normal file
@ -0,0 +1,22 @@
|
||||
from grocy.request import Request
|
||||
from grocy.conf import Configuration
|
||||
import logging
|
||||
|
||||
|
||||
class Recipe(object):
|
||||
GET_RECIPE_REQUIRMENTS_URL_TEMPLATE = '{api}/recipes/requirements'
|
||||
|
||||
def __init__(self):
|
||||
self.conf = Configuration()
|
||||
self.conf.load()
|
||||
|
||||
def get_requirements(self):
|
||||
logger = logging.getLogger('recipe.get_requirements')
|
||||
url = self.GET_RECIPE_REQUIRMENTS_URL_TEMPLATE.format(api=self.conf.api)
|
||||
request = Request('get', url)
|
||||
try:
|
||||
return request.send()
|
||||
except Exception as e:
|
||||
logger.error(e)
|
||||
raise e
|
||||
|
25
grocy/request.py
Normal file
25
grocy/request.py
Normal file
@ -0,0 +1,25 @@
|
||||
from grocy.conf import Configuration
|
||||
from requests import request
|
||||
import logging
|
||||
|
||||
|
||||
class Request(object):
|
||||
def __init__(self, method, url):
|
||||
self.conf = Configuration()
|
||||
self.conf.load()
|
||||
self.url = url
|
||||
self.method = method
|
||||
self.headers = {
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json'
|
||||
}
|
||||
if self.conf.token:
|
||||
self.headers[self.conf.API_KEY_HEADER] = self.conf.token
|
||||
|
||||
def send(self):
|
||||
logger = logging.getLogger('request.send')
|
||||
r = request(method=self.method, url=self.url, headers=self.headers)
|
||||
if r.raise_for_status():
|
||||
logger.error(r.raise_for_status())
|
||||
raise r.raise_for_status()
|
||||
return r.json()
|
22
grocy/stock.py
Normal file
22
grocy/stock.py
Normal file
@ -0,0 +1,22 @@
|
||||
from grocy.request import Request
|
||||
from grocy.conf import Configuration
|
||||
import logging
|
||||
|
||||
|
||||
class Stock(object):
|
||||
GET_STOCK_URL_TEMPLATE = '{api}/stock'
|
||||
|
||||
def __init__(self):
|
||||
self.conf = Configuration()
|
||||
self.conf.load()
|
||||
|
||||
def get(self):
|
||||
logger = logging.getLogger('stock.get')
|
||||
url = self.GET_STOCK_URL_TEMPLATE.format(api=self.conf.api)
|
||||
request = Request('get', url)
|
||||
try:
|
||||
return request.send()
|
||||
except Exception as e:
|
||||
logger.error(e)
|
||||
raise e
|
||||
|
120
grocy/table.py
Normal file
120
grocy/table.py
Normal file
@ -0,0 +1,120 @@
|
||||
from grocy.entity import Entity
|
||||
import re
|
||||
from grocy.conf import Configuration
|
||||
import logging
|
||||
from tabulate import tabulate
|
||||
|
||||
|
||||
class Table(object):
|
||||
NOT_ENOUGH_IN_STOCK_MSG = '{glyph} Not enough in stock, {missing_amount} ingredient{s} missing'
|
||||
ENOUGH_IN_STOCK_MSG = '{glyph} Enough in stock'
|
||||
NOT_ENOUGH_BUT_IN_SHOPPING_LIST_MSG = '{not_enough}, but already on list'.format(not_enough=NOT_ENOUGH_IN_STOCK_MSG)
|
||||
|
||||
def __init__(self, **entries):
|
||||
self.__dict__.update(entries)
|
||||
self.conf = Configuration()
|
||||
self.conf.load()
|
||||
|
||||
@property
|
||||
def stock(self):
|
||||
logger = logging.getLogger('table.stock')
|
||||
entity = Entity(name='products')
|
||||
products = entity.get()
|
||||
products_map = {product['id']: product for product in products}
|
||||
try:
|
||||
# Get product names from ids and replace
|
||||
table_entries = []
|
||||
try:
|
||||
for item in self.stocks:
|
||||
product = products_map[item['product_id']]
|
||||
item['product_id'] = product['name']
|
||||
table_entry = list(dict.values(item))
|
||||
table_entries.append(table_entry)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(e)
|
||||
raise e
|
||||
|
||||
# Generate stock overview table
|
||||
table_headers = ['Product', 'Amount', 'Best Before Date', 'Amount Opened']
|
||||
return tabulate(table_entries, headers=table_headers)
|
||||
except Exception as e:
|
||||
logger.error(e)
|
||||
raise e
|
||||
|
||||
@property
|
||||
def recipe(self):
|
||||
checkmark_glyph = ''
|
||||
times_glyph = ''
|
||||
exclamation_glyph = ''
|
||||
logger = logging.getLogger('table.recipes')
|
||||
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 shopping_list(self):
|
||||
logger = logging.getLogger('table.shopping_list')
|
||||
try:
|
||||
table_headers = ['Group', 'Product', 'Amount']
|
||||
# Get product names and location from ids and replace
|
||||
product_ids = [entry['product_id'] for entry in self.shopping_list]
|
||||
products = []
|
||||
location_ids = []
|
||||
table_entries = []
|
||||
for index in range(len(product_ids)):
|
||||
product_id = product_ids[index]
|
||||
entity = Entity(name='products')
|
||||
product = entity.get(id=product_id)
|
||||
|
||||
entity = Entity(name='quantity_units')
|
||||
quantity_unit = entity.get(product['qu_id_purchase'])
|
||||
|
||||
min_amount = '{} {}'.format(product['min_stock_amount'], quantity_unit['name'])
|
||||
if product['product_group_id'] == '':
|
||||
product_group_name = 'Uncategorized'
|
||||
else:
|
||||
entity = Entity(name='product_groups')
|
||||
product_group = product.get(id=product['product_group_id'])
|
||||
product_group_name = product_group['name']
|
||||
shopping_item = [product_group_name, product['name'], min_amount]
|
||||
table_entries.append(shopping_item)
|
||||
except Exception as e:
|
||||
logger.error(e)
|
||||
raise e
|
||||
|
||||
# Generate stock overview table
|
||||
return tabulate(table_entries, headers=table_headers)
|
4
setup.cfg
Normal file
4
setup.cfg
Normal file
@ -0,0 +1,4 @@
|
||||
[pycodestyle]
|
||||
exclude = .git,*.egg-info
|
||||
ignore = E241,E128,E226,E722,W504
|
||||
max-line-length = 120
|
8
templates/ingredient_add.yml
Normal file
8
templates/ingredient_add.yml
Normal file
@ -0,0 +1,8 @@
|
||||
product_id:
|
||||
amount:
|
||||
note:
|
||||
qu_id:
|
||||
only_check_single_unit_in_stock:
|
||||
ingredient_group:
|
||||
not_check_stock_fulfillment:
|
||||
variable_amount:
|
Loading…
Reference in New Issue
Block a user