From e5e9ddd836fc2bb86f08b67444ca160af54e2e70 Mon Sep 17 00:00:00 2001 From: Aerex Date: Mon, 1 Apr 2019 00:16:32 -0500 Subject: [PATCH] feat: adding edit feature for recipe --- file.txt | 12 +++++++ grocy/cli.py | 51 +++++++++++++++++++++++++++--- grocy/commands/__init__.py | 1 + grocy/commands/battery.py | 64 ++++++++++++++++++++++++++++++++++++++ grocy/commands/chore.py | 2 +- grocy/commands/product.py | 3 ++ grocy/commands/recipe.py | 33 ++++++++++++++++++-- requirements.txt | 2 ++ sample.html | 26 ++++++++++++++++ templates/recipe.yml | 18 +++++++++++ 10 files changed, 205 insertions(+), 7 deletions(-) create mode 100644 file.txt create mode 100644 grocy/commands/battery.py create mode 100644 grocy/commands/product.py create mode 100644 sample.html create mode 100644 templates/recipe.yml diff --git a/file.txt b/file.txt new file mode 100644 index 0000000..06c46ea --- /dev/null +++ b/file.txt @@ -0,0 +1,12 @@ + 1. Preheat oven to 425 degrees F (220 degrees C). Lightly oil a large roasting pan. + 2. Place chicken pieces in large bowl. Season with salt, oregano, pepper, rosemary, and cayenne pepper. Add fresh lemon juice, olive oil, and garlic. Place potatoes in bowl with the chicken; stir together until chicken and potatoes are evenly coated with marinade. + 3. Transfer chicken pieces, skin side up, to prepared roasting pan, reserving marinade. Distribute potato pieces among chicken thighs. Drizzle with 2/3 cup chicken broth. Spoon remainder of marinade over chicken and potatoes. + 4. Place in preheated oven. Bake in the preheated oven for 20 minutes. Toss chicken and potatoes, keeping chicken skin side up; continue baking until chicken is browned and cooked through, about 25 minutes more. An instant-read thermometer inserted near the bone should read 165 degrees F (74 degrees C). Transfer chicken to serving platter and keep warm. + 5. Set oven to broil or highest heat setting. Toss potatoes once again in pan juices. Place pan under broiler and broil until potatoes are caramelized, about 3 minutes. Transfer potatoes to serving platter with chicken. + 6. Place roasting pan on stove over medium heat. Add a splash of broth and stir up browned bits from the bottom of the pan. Strain; spoon juices over chicken and potatoes. Top with chopped oregano. + +[](/"https://www.allrecipes.com/recipe/242352/greek-lemon-chicken-and- +potatoes//") + + + diff --git a/grocy/cli.py b/grocy/cli.py index 80be1bd..5f4e8a4 100644 --- a/grocy/cli.py +++ b/grocy/cli.py @@ -1,4 +1,6 @@ import click +from jinja2 import Environment, FileSystemLoader +from uuid import uuid4 from grocy import RestService from grocy.commands import * from pkg_resources import iter_entry_points @@ -13,9 +15,12 @@ logger = logging.getLogger(__name__) APP_NAME = 'grocy-cli' SAMPLE_CONFIG_FILE = 'sample.config.yml' +TMP_DIR = '/tmp/grocy' +TEMPLATE_DIR = '../templates' 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(TEMPLATE_DIR), trim_blocks=True, lstrip_blocks=True) def __validate_token(cfg): # Validate token @@ -36,6 +41,20 @@ def __create_config_file(): chmod(user_cfg_file, 0o664) return user_cfg_file +def open_editor(template, data): + if not path.exists(TMP_DIR): + makedirs(TMP_DIR) + tmp_filename_template = '{}/grocy-{}'.format(TMP_DIR, uuid()) + open_editork k + + + + + + + + + @click.group() @click.pass_context @@ -46,9 +65,10 @@ def main(ctx): 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) + 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'] = log_cfg __validate_token(cfg) ctx.ensure_object(dict) @@ -89,7 +109,8 @@ def shopping(ctx): shopping_list = shopping.get_list() click.echo(shopping_list) -@main.command() +" Recipe Commands " +@main.group() @click.pass_context def recipe(ctx): cfg = ctx.obj['cfg'] @@ -98,6 +119,28 @@ def recipe(ctx): recipes = receipe.get_list() click.echo(recipes) +@recipe.command('edit') +@click.argument('recipe_id') +@click.pass_context +def edit(ctx): + cfg = ctx.obj['cfg'] + logger = cfg['logger'] + + try: + if recipe_id: + receipe = Recipe(id=receipe_id, **cfg) + loaded_recipe = recipe.get(include_products=True) + loaded_template = TEMPLATE_LOADER.get_template('recipe.yml') + edited_recipe = click.edit(loaded_template.render(loaded_recipe)) + if edited_recipe is not None: + updated_recipe = Recipe(id=receipe_id, **edited_recipe) + updated_recipe.update() + except Exception as e: + logger.error(e) + logger.error('Could not edit recipe {}'.format(recipe_id) + + +" Recipe Commands " @main.command() @click.pass_context def chore(ctx): diff --git a/grocy/commands/__init__.py b/grocy/commands/__init__.py index b938150..abd65f1 100644 --- a/grocy/commands/__init__.py +++ b/grocy/commands/__init__.py @@ -3,3 +3,4 @@ from grocy.commands.recipe import Recipe from grocy.commands.chore import Chore from grocy.commands.task import Task from grocy.commands.shopping import Shopping +from grocy.commands.battery import Battery diff --git a/grocy/commands/battery.py b/grocy/commands/battery.py new file mode 100644 index 0000000..b9c3f80 --- /dev/null +++ b/grocy/commands/battery.py @@ -0,0 +1,64 @@ +import re +from datetime import datetime +from grocy import RestService +from tabulate import tabulate +from os import path + +class Battery(object): + GET_CURRENT_CHORES = '/chores/get-current' + GET_CHORE_BY_ID = '/get-object/chores/{0}' + def __init__(self, **entries): + self.__dict__.update(entries) + self._init_rest_service() + #self._set_default_table_formats() + #if not hasattr('tablefmt', self): + # self.tablefmt = None + #if not hasattr('colalign', self): + # self.colalign = None + + + + def _set_default_table_formats(self): + if not hasattr('formats', self): + self.tablefmt = None + self.colalign = None + elif not hasattr('table', self.formats): + self.tableformat = None + elif not hasattr('col', self.formats): + self.colalign = None + + + + def _init_rest_service(self): + if self.api.startswith == '/': + self.api = self.api[1:] + if self.api.endswith == '/': + self.api = self.api[1:-1] + self.rest_service = RestService(self.api, json=True) + self.rest_service.addHeader('Content-Type', 'application/json') + self.rest_service.addToken(self.token) + + + def get_list(self): + try: + get_current_chores = self.rest_service.get(Chore.GET_CURRENT_CHORES) + + table_headers = ['Name', 'Due'] + table_entries = [] + for chore in get_current_chores: + path = Chore.GET_CHORE_BY_ID.format(chore['chore_id']) + chore_info = self.rest_service.get(path) + if chore.get('next_estimated_execution_time') is None: + due_date = 'None' + elif re.match('2999',chore.get('next_estimated_execution_time')): + due_date = 'None' + else: + due_date = datetime.strptime(chore.get('next_estimated_execution_time'), '%Y-%m-%d') + + table_entry = [chore_info.get('name'), due_date] + table_entries.append(table_entry) + + except Exception as e: + raise e + # Generate stock overview table + return tabulate(table_entries, headers=table_headers) diff --git a/grocy/commands/chore.py b/grocy/commands/chore.py index ad93e19..8b5548c 100644 --- a/grocy/commands/chore.py +++ b/grocy/commands/chore.py @@ -5,7 +5,7 @@ from tabulate import tabulate from os import path class Chore(object): - GET_CURRENT_CHORES = '/chores/get-current' + GET_BATTERIES = '/chores/get-current' GET_CHORE_BY_ID = '/get-object/chores/{0}' def __init__(self, **entries): self.__dict__.update(entries) diff --git a/grocy/commands/product.py b/grocy/commands/product.py new file mode 100644 index 0000000..4390983 --- /dev/null +++ b/grocy/commands/product.py @@ -0,0 +1,3 @@ +class Product(object): + def __init__(self, **entries): + self.__dict__.update(entries) diff --git a/grocy/commands/recipe.py b/grocy/commands/recipe.py index 3bc1698..bdfb0b3 100644 --- a/grocy/commands/recipe.py +++ b/grocy/commands/recipe.py @@ -1,13 +1,18 @@ from grocy import RestService +from grocy.commands import products from tabulate import tabulate from os import path class Recipe(object): GET_RECIPES = '/get-objects/recipes' - GET_PRODUCT_BY_ID = '/get-object/products/{0}' - def __init__(self, **entries): + GET_RECIPE = '/get-object/recipes/{0}' + GET_PRODUCT = '/get-object/products/{0}' + GET_RECIPES_POS = '/get-object/recipes_pos' + def __init__(self, id, **entries): + self.id = id self.__dict__.update(entries) self._init_rest_service() + self.products = [] #self._set_default_table_formats() #if not hasattr('tablefmt', self): # self.tablefmt = None @@ -16,6 +21,17 @@ class Recipe(object): + def _get_products_by_recipe_id(self): + recipe_products = self.rest_service.get(Recipe.GET_RECIPES_POS) + products_for_recipe = [] + for recipe_product in recipe_products: + if recipe_product.get('recipe_id') == self.id: + ## TODO: need to find a better way to run a batch call to get only products for recipe + product = self.rest_service.get(Recipe.GET_PRODUCT.format(recipe_product.get('product_id')) + ## combined dict into single dict + product_recipie_info = {k: v for combined_dict in [product, recipe_product] for k, v in combined_dict.items()} + self.products.append(product_recipie_info) + def _set_default_table_formats(self): if not hasattr('formats', self): self.tablefmt = None @@ -50,3 +66,16 @@ class Recipe(object): raise e # Generate stock overview table return tabulate(table_entries, headers=table_headers) + + def get(self, include_products=False): + try: + recipe = self.rest_service.get(Recipe.GET_RECIPE.format(self.id)) + if include_products: + self.products = self._get_products_by_recipe_id() + + except Exception as e: + raise e + + return recipe.to_json() + + diff --git a/requirements.txt b/requirements.txt index 10d4352..749c48b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,6 @@ click +markdown +html2text colorama pyyaml requests diff --git a/sample.html b/sample.html new file mode 100644 index 0000000..2caee97 --- /dev/null +++ b/sample.html @@ -0,0 +1,26 @@ + + + + + + +
+
    +
  1. Preheat oven to 425 degrees F (220 degrees C). Lightly oil a large roasting pan.
  2. +
  3. Place chicken pieces in large bowl. Season with salt, oregano, pepper, rosemary, and cayenne pepper. Add fresh lemon juice, olive oil, and garlic. Place potatoes in bowl with the chicken; stir together until chicken and potatoes are evenly coated with marinade.
  4. +
  5. Transfer chicken pieces, skin side up, to prepared roasting pan, reserving marinade. Distribute potato pieces among chicken thighs. Drizzle with 2/3 cup chicken broth. Spoon remainder of marinade over chicken and potatoes.
  6. +
  7. Place in preheated oven. Bake in the preheated oven for 20 minutes. Toss chicken and potatoes, keeping chicken skin side up; continue baking until chicken is browned and cooked through, about 25 minutes more. An instant-read thermometer inserted near the bone should read 165 degrees F (74 degrees C). Transfer chicken to serving platter and keep warm.
  8. +
  9. Set oven to broil or highest heat setting. Toss potatoes once again in pan juices. Place pan under broiler and broil until potatoes are caramelized, about 3 minutes. Transfer potatoes to serving platter with chicken.
  10. +
  11. Place roasting pan on stove over medium heat. Add a splash of broth and stir up browned bits from the bottom of the pan. Strain; spoon juices over chicken and potatoes. Top with chopped oregano.
  12. +
+
+
+
+
+
+   +
+
+
+ + diff --git a/templates/recipe.yml b/templates/recipe.yml new file mode 100644 index 0000000..e43fe9e --- /dev/null +++ b/templates/recipe.yml @@ -0,0 +1,18 @@ +name: {{ name }} +description: {{ description }} +recipe_id: {{ recipe_id } +picture_file_name: {{ picture_file_name | null }} +base_serving: {{ base_serving | "1" }} +desired_serving: {{ desired_serving | "1" }} +not_checking_shopping_list: {{ not_checking_shopping_list | "1" }} +{% for product in products } +products: + - id: {{ product.id }} + amount: {{ product.amount }} + note: {{ product.note }} + qu_id: + only_check_single_unit_in_stock: {{ product.only_check_single_unit_in_stock }} + ingredient_group: {{ product.ingredient_group | null }} + not_checking_shopping_list: {{ product.not_checking_shopping_list }} +{% endfor } +