refactor: Reanmed templates
- fix: Added browse subcommand for products
This commit is contained in:
parent
d479b4f621
commit
6af9bef334
132
grocy/cli.py
132
grocy/cli.py
@ -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):
|
||||||
|
@ -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:
|
||||||
|
@ -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
27
grocy/meta.py
Normal 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
|
@ -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
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -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
28
grocy/schema.py
Normal 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
|
@ -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)
|
||||||
|
@ -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
39
models.json
Normal 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"
|
2
setup.py
2
setup.py
@ -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
1
templates/debug.yml
Normal file
@ -0,0 +1 @@
|
|||||||
|
{{ grocy | tojson(indent=2) | safe }}
|
20
templates/product/add.yml
Normal file
20
templates/product/add.yml
Normal 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
|
0
templates/product/table.yml
Normal file
0
templates/product/table.yml
Normal file
18
templates/recipe/edit.yml
Normal file
18
templates/recipe/edit.yml
Normal 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
20
templates/recipe/view.yml
Normal 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 }}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -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 %}
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user