From a7dc23194e2ecef16af94b2f7bcb74c365a4be36 Mon Sep 17 00:00:00 2001 From: vabene1111 Date: Thu, 16 Sep 2021 17:52:11 +0200 Subject: [PATCH] converted ingredient parser to class and added automation beta hint --- cookbook/helper/ingredient_parser.py | 366 ++++++++++-------- cookbook/helper/recipe_html_import.py | 4 +- cookbook/helper/recipe_url_import.py | 9 +- cookbook/integration/cheftap.py | 9 +- cookbook/integration/chowdown.py | 9 +- cookbook/integration/cookbookapp.py | 9 +- cookbook/integration/domestica.py | 9 +- cookbook/integration/mealie.py | 13 +- cookbook/integration/mealmaster.py | 9 +- cookbook/integration/nextcloud_cookbook.py | 3 +- cookbook/integration/openeats.py | 7 +- cookbook/integration/paprika.py | 9 +- cookbook/integration/pepperplate.py | 9 +- cookbook/integration/plantoeat.py | 9 +- cookbook/integration/recettetek.py | 9 +- cookbook/integration/recipekeeper.py | 9 +- cookbook/integration/recipesage.py | 9 +- cookbook/integration/rezkonv.py | 9 +- cookbook/integration/safron.py | 11 +- .../tests/other/test_ingredient_parser.py | 8 +- cookbook/views/api.py | 10 +- cookbook/views/telegram.py | 11 +- cookbook/views/views.py | 3 +- vue/src/apps/ModelListView/ModelListView.vue | 29 +- vue/src/components/GenericContextMenu.vue | 2 +- vue/src/components/GenericHorizontalCard.vue | 2 +- vue/src/locales/en.json | 1 + vue/webpack-stats.json | 255 ++++++++---- 28 files changed, 505 insertions(+), 337 deletions(-) diff --git a/cookbook/helper/ingredient_parser.py b/cookbook/helper/ingredient_parser.py index a6172852..4df440f8 100644 --- a/cookbook/helper/ingredient_parser.py +++ b/cookbook/helper/ingredient_parser.py @@ -5,190 +5,212 @@ import unicodedata from cookbook.models import Unit, Food -def parse_fraction(x): - if len(x) == 1 and 'fraction' in unicodedata.decomposition(x): - frac_split = unicodedata.decomposition(x[-1:]).split() - return (float((frac_split[1]).replace('003', '')) - / float((frac_split[3]).replace('003', ''))) - else: - frac_split = x.split('/') - if not len(frac_split) == 2: - raise ValueError - try: - return int(frac_split[0]) / int(frac_split[1]) - except ZeroDivisionError: - raise ValueError +class IngredientParser: + request = None + ignore_rules = False + food_aliases = [] + unit_aliases = [] + def __init__(self, request, cache_mode, ignore_automations=False): + """ + Initialize ingredient parser + :param request: request context (to control caching, rule ownership, etc.) + :param cache_mode: defines if all rules should be loaded on initialization (good when parser is used many times) or if they should be retrieved every time (good when parser is not used many times in a row) + :param ignore_automations: ignore automation rules, allows to use ingredient parser without database access/request (request can be None) + """ + self.request = request + self.ignore_rules = ignore_automations + if cache_mode: + self.food_aliases = [] + self.unit_aliases = [] -def parse_amount(x): - amount = 0 - unit = '' - note = '' - - did_check_frac = False - end = 0 - while (end < len(x) and (x[end] in string.digits - or ( - (x[end] == '.' or x[end] == ',' or x[end] == '/') - and end + 1 < len(x) - and x[end + 1] in string.digits - ))): - end += 1 - if end > 0: - if "/" in x[:end]: - amount = parse_fraction(x[:end]) - else: - amount = float(x[:end].replace(',', '.')) - else: - amount = parse_fraction(x[0]) - end += 1 - did_check_frac = True - if end < len(x): - if did_check_frac: - unit = x[end:] + def parse_fraction(self, x): + if len(x) == 1 and 'fraction' in unicodedata.decomposition(x): + frac_split = unicodedata.decomposition(x[-1:]).split() + return (float((frac_split[1]).replace('003', '')) + / float((frac_split[3]).replace('003', ''))) else: + frac_split = x.split('/') + if not len(frac_split) == 2: + raise ValueError try: - amount += parse_fraction(x[end]) - unit = x[end + 1:] - except ValueError: - unit = x[end:] + return int(frac_split[0]) / int(frac_split[1]) + except ZeroDivisionError: + raise ValueError - if unit.startswith('(') or unit.startswith('-'): # i dont know any unit that starts with ( or - so its likely an alternative like 1L (500ml) Water or 2-3 + def parse_amount(self, x): + amount = 0 unit = '' - note = x - return amount, unit, note + note = '' - -def parse_ingredient_with_comma(tokens): - ingredient = '' - note = '' - start = 0 - # search for first occurrence of an argument ending in a comma - while start < len(tokens) and not tokens[start].endswith(','): - start += 1 - if start == len(tokens): - # no token ending in a comma found -> use everything as ingredient - ingredient = ' '.join(tokens) - else: - ingredient = ' '.join(tokens[:start + 1])[:-1] - note = ' '.join(tokens[start + 1:]) - return ingredient, note - - -def parse_ingredient(tokens): - ingredient = '' - note = '' - if tokens[-1].endswith(')'): - # Check if the matching opening bracket is in the same token - if (not tokens[-1].startswith('(')) and ('(' in tokens[-1]): - return parse_ingredient_with_comma(tokens) - # last argument ends with closing bracket -> look for opening bracket - start = len(tokens) - 1 - while not tokens[start].startswith('(') and not start == 0: - start -= 1 - if start == 0: - # the whole list is wrapped in brackets -> assume it is an error (e.g. assumed first argument was the unit) # noqa: E501 - raise ValueError - elif start < 0: - # no opening bracket anywhere -> just ignore the last bracket - ingredient, note = parse_ingredient_with_comma(tokens) - else: - # opening bracket found -> split in ingredient and note, remove brackets from note # noqa: E501 - note = ' '.join(tokens[start:])[1:-1] - ingredient = ' '.join(tokens[:start]) - else: - ingredient, note = parse_ingredient_with_comma(tokens) - return ingredient, note - - -def parse(x): - # initialize default values - amount = 0 - unit = '' - ingredient = '' - note = '' - unit_note = '' - - # if the string contains parenthesis early on remove it and place it at the end - # because its likely some kind of note - if re.match('(.){1,6}\s\((.[^\(\)])+\)\s', x): - match = re.search('\((.[^\(])+\)', x) - x = x[:match.start()] + x[match.end():] + ' ' + x[match.start():match.end()] - - tokens = x.split() - if len(tokens) == 1: - # there only is one argument, that must be the ingredient - ingredient = tokens[0] - else: - try: - # try to parse first argument as amount - amount, unit, unit_note = parse_amount(tokens[0]) - # only try to parse second argument as amount if there are at least - # three arguments if it already has a unit there can't be - # a fraction for the amount - if len(tokens) > 2: - try: - if not unit == '': - # a unit is already found, no need to try the second argument for a fraction - # probably not the best method to do it, but I didn't want to make an if check and paste the exact same thing in the else as already is in the except # noqa: E501 - raise ValueError - # try to parse second argument as amount and add that, in case of '2 1/2' or '2 ½' - amount += parse_fraction(tokens[1]) - # assume that units can't end with a comma - if len(tokens) > 3 and not tokens[2].endswith(','): - # try to use third argument as unit and everything else as ingredient, use everything as ingredient if it fails # noqa: E501 - try: - ingredient, note = parse_ingredient(tokens[3:]) - unit = tokens[2] - except ValueError: - ingredient, note = parse_ingredient(tokens[2:]) - else: - ingredient, note = parse_ingredient(tokens[2:]) - except ValueError: - # assume that units can't end with a comma - if not tokens[1].endswith(','): - # try to use second argument as unit and everything else as ingredient, use everything as ingredient if it fails # noqa: E501 - try: - ingredient, note = parse_ingredient(tokens[2:]) - if unit == '': - unit = tokens[1] - else: - note = tokens[1] - except ValueError: - ingredient, note = parse_ingredient(tokens[1:]) - else: - ingredient, note = parse_ingredient(tokens[1:]) + did_check_frac = False + end = 0 + while (end < len(x) and (x[end] in string.digits + or ( + (x[end] == '.' or x[end] == ',' or x[end] == '/') + and end + 1 < len(x) + and x[end + 1] in string.digits + ))): + end += 1 + if end > 0: + if "/" in x[:end]: + amount = self.parse_fraction(x[:end]) else: - # only two arguments, first one is the amount - # which means this is the ingredient - ingredient = tokens[1] - except ValueError: + amount = float(x[:end].replace(',', '.')) + else: + amount = self.parse_fraction(x[0]) + end += 1 + did_check_frac = True + if end < len(x): + if did_check_frac: + unit = x[end:] + else: + try: + amount += self.parse_fraction(x[end]) + unit = x[end + 1:] + except ValueError: + unit = x[end:] + + if unit.startswith('(') or unit.startswith('-'): # i dont know any unit that starts with ( or - so its likely an alternative like 1L (500ml) Water or 2-3 + unit = '' + note = x + return amount, unit, note + + def parse_ingredient_with_comma(self, tokens): + ingredient = '' + note = '' + start = 0 + # search for first occurrence of an argument ending in a comma + while start < len(tokens) and not tokens[start].endswith(','): + start += 1 + if start == len(tokens): + # no token ending in a comma found -> use everything as ingredient + ingredient = ' '.join(tokens) + else: + ingredient = ' '.join(tokens[:start + 1])[:-1] + note = ' '.join(tokens[start + 1:]) + return ingredient, note + + def parse_ingredient(self, tokens): + ingredient = '' + note = '' + if tokens[-1].endswith(')'): + # Check if the matching opening bracket is in the same token + if (not tokens[-1].startswith('(')) and ('(' in tokens[-1]): + return self.parse_ingredient_with_comma(tokens) + # last argument ends with closing bracket -> look for opening bracket + start = len(tokens) - 1 + while not tokens[start].startswith('(') and not start == 0: + start -= 1 + if start == 0: + # the whole list is wrapped in brackets -> assume it is an error (e.g. assumed first argument was the unit) # noqa: E501 + raise ValueError + elif start < 0: + # no opening bracket anywhere -> just ignore the last bracket + ingredient, note = self.parse_ingredient_with_comma(tokens) + else: + # opening bracket found -> split in ingredient and note, remove brackets from note # noqa: E501 + note = ' '.join(tokens[start:])[1:-1] + ingredient = ' '.join(tokens[:start]) + else: + ingredient, note = self.parse_ingredient_with_comma(tokens) + return ingredient, note + + def parse(self, x): + # initialize default values + amount = 0 + unit = '' + ingredient = '' + note = '' + unit_note = '' + + # if the string contains parenthesis early on remove it and place it at the end + # because its likely some kind of note + if re.match('(.){1,6}\s\((.[^\(\)])+\)\s', x): + match = re.search('\((.[^\(])+\)', x) + x = x[:match.start()] + x[match.end():] + ' ' + x[match.start():match.end()] + + tokens = x.split() + if len(tokens) == 1: + # there only is one argument, that must be the ingredient + ingredient = tokens[0] + else: try: - # can't parse first argument as amount - # -> no unit -> parse everything as ingredient - ingredient, note = parse_ingredient(tokens) + # try to parse first argument as amount + amount, unit, unit_note = self.parse_amount(tokens[0]) + # only try to parse second argument as amount if there are at least + # three arguments if it already has a unit there can't be + # a fraction for the amount + if len(tokens) > 2: + try: + if not unit == '': + # a unit is already found, no need to try the second argument for a fraction + # probably not the best method to do it, but I didn't want to make an if check and paste the exact same thing in the else as already is in the except # noqa: E501 + raise ValueError + # try to parse second argument as amount and add that, in case of '2 1/2' or '2 ½' + amount += self.parse_fraction(tokens[1]) + # assume that units can't end with a comma + if len(tokens) > 3 and not tokens[2].endswith(','): + # try to use third argument as unit and everything else as ingredient, use everything as ingredient if it fails # noqa: E501 + try: + ingredient, note = self.parse_ingredient(tokens[3:]) + unit = tokens[2] + except ValueError: + ingredient, note = self.parse_ingredient(tokens[2:]) + else: + ingredient, note = self.parse_ingredient(tokens[2:]) + except ValueError: + # assume that units can't end with a comma + if not tokens[1].endswith(','): + # try to use second argument as unit and everything else as ingredient, use everything as ingredient if it fails # noqa: E501 + try: + ingredient, note = self.parse_ingredient(tokens[2:]) + if unit == '': + unit = tokens[1] + else: + note = tokens[1] + except ValueError: + ingredient, note = self.parse_ingredient(tokens[1:]) + else: + ingredient, note = self.parse_ingredient(tokens[1:]) + else: + # only two arguments, first one is the amount + # which means this is the ingredient + ingredient = tokens[1] except ValueError: - ingredient = ' '.join(tokens[1:]) + try: + # can't parse first argument as amount + # -> no unit -> parse everything as ingredient + ingredient, note = self.parse_ingredient(tokens) + except ValueError: + ingredient = ' '.join(tokens[1:]) - if unit_note not in note: - note += ' ' + unit_note - return amount, unit.strip(), ingredient.strip(), note.strip() + if unit_note not in note: + note += ' ' + unit_note + return amount, unit.strip(), ingredient.strip(), note.strip() - -# small utility functions to prevent emtpy unit/food creation -def get_unit(unit, space): - if not unit: + def get_unit(self, unit): + """ + Get or create a unit for given space respecting possible automations + :param unit: string unit + :return: None if unit passed is invalid, Unit object otherwise + """ + if not unit: + return None + if len(unit) > 0: + u, created = Unit.objects.get_or_create(name=unit, space=self.request.space) + return u return None - if len(unit) > 0: - u, created = Unit.objects.get_or_create(name=unit, space=space) - return u - return None - -def get_food(food, space): - if not food: + def get_food(self, food): + """ + Get or create a food for given space respecting possible automations + :param food: string food + :return: None if food passed is invalid, Food object otherwise + """ + if not food: + return None + if len(food) > 0: + f, created = Food.objects.get_or_create(name=food, space=self.request.space) + return f return None - if len(food) > 0: - f, created = Food.objects.get_or_create(name=food, space=space) - return f - return None diff --git a/cookbook/helper/recipe_html_import.py b/cookbook/helper/recipe_html_import.py index 3b06dc80..7b779add 100644 --- a/cookbook/helper/recipe_html_import.py +++ b/cookbook/helper/recipe_html_import.py @@ -10,7 +10,7 @@ from recipe_scrapers._utils import get_host_name, normalize_string from urllib.parse import unquote -def get_recipe_from_source(text, url, space): +def get_recipe_from_source(text, url, request): def build_node(k, v): if isinstance(v, dict): node = { @@ -103,7 +103,7 @@ def get_recipe_from_source(text, url, space): parse_list.append(el) scrape = text_scraper(text, url=url) - recipe_json = helper.get_from_scraper(scrape, space) + recipe_json = helper.get_from_scraper(scrape, request) for el in parse_list: temp_tree = [] diff --git a/cookbook/helper/recipe_url_import.py b/cookbook/helper/recipe_url_import.py index df24409a..2ea889bf 100644 --- a/cookbook/helper/recipe_url_import.py +++ b/cookbook/helper/recipe_url_import.py @@ -3,14 +3,14 @@ import re from isodate import parse_duration as iso_parse_duration from isodate.isoerror import ISO8601Error -from cookbook.helper.ingredient_parser import parse as parse_single_ingredient +from cookbook.helper.ingredient_parser import IngredientParser from cookbook.models import Keyword from django.utils.dateparse import parse_duration from html import unescape from recipe_scrapers._utils import get_minutes -def get_from_scraper(scrape, space): +def get_from_scraper(scrape, request): # converting the scrape_me object to the existing json format based on ld+json recipe_json = {} try: @@ -91,15 +91,16 @@ def get_from_scraper(scrape, space): except Exception: pass try: - recipe_json['keywords'] = parse_keywords(list(set(map(str.casefold, keywords))), space) + recipe_json['keywords'] = parse_keywords(list(set(map(str.casefold, keywords))), request.space) except AttributeError: recipe_json['keywords'] = keywords + ingredient_parser = IngredientParser(request, True) try: ingredients = [] for x in scrape.ingredients(): try: - amount, unit, ingredient, note = parse_single_ingredient(x) + amount, unit, ingredient, note = ingredient_parser.parse(x) ingredients.append( { 'amount': amount, diff --git a/cookbook/integration/cheftap.py b/cookbook/integration/cheftap.py index c4330628..f83203dc 100644 --- a/cookbook/integration/cheftap.py +++ b/cookbook/integration/cheftap.py @@ -1,6 +1,6 @@ import re -from cookbook.helper.ingredient_parser import parse, get_food, get_unit +from cookbook.helper.ingredient_parser import IngredientParser from cookbook.integration.integration import Integration from cookbook.models import Recipe, Step, Ingredient @@ -42,11 +42,12 @@ class ChefTap(Integration): step.instruction += '\n' + source_url step.save() + ingredient_parser = IngredientParser(self.request, True) for ingredient in ingredients: if len(ingredient.strip()) > 0: - amount, unit, ingredient, note = parse(ingredient) - f = get_food(ingredient, self.request.space) - u = get_unit(unit, self.request.space) + amount, unit, ingredient, note = ingredient_parser.parse(ingredient) + f = ingredient_parser.get_food(ingredient) + u = ingredient_parser.get_unit(unit) step.ingredients.add(Ingredient.objects.create( food=f, unit=u, amount=amount, note=note, space=self.request.space, )) diff --git a/cookbook/integration/chowdown.py b/cookbook/integration/chowdown.py index 331a2ed8..8a16ae0e 100644 --- a/cookbook/integration/chowdown.py +++ b/cookbook/integration/chowdown.py @@ -3,7 +3,7 @@ from io import BytesIO from zipfile import ZipFile from cookbook.helper.image_processing import get_filetype -from cookbook.helper.ingredient_parser import parse, get_food, get_unit +from cookbook.helper.ingredient_parser import IngredientParser from cookbook.integration.integration import Integration from cookbook.models import Recipe, Step, Ingredient, Keyword @@ -58,10 +58,11 @@ class Chowdown(Integration): instruction='\n'.join(directions) + '\n\n' + '\n'.join(descriptions), space=self.request.space, ) + ingredient_parser = IngredientParser(self.request, True) for ingredient in ingredients: - amount, unit, ingredient, note = parse(ingredient) - f = get_food(ingredient, self.request.space) - u = get_unit(unit, self.request.space) + amount, unit, ingredient, note = ingredient_parser.parse(ingredient) + f = ingredient_parser.get_food(ingredient) + u = ingredient_parser.get_unit(unit) step.ingredients.add(Ingredient.objects.create( food=f, unit=u, amount=amount, note=note, space=self.request.space, )) diff --git a/cookbook/integration/cookbookapp.py b/cookbook/integration/cookbookapp.py index f5225f77..f7dc55d7 100644 --- a/cookbook/integration/cookbookapp.py +++ b/cookbook/integration/cookbookapp.py @@ -6,7 +6,7 @@ from io import BytesIO import yaml -from cookbook.helper.ingredient_parser import parse, get_food, get_unit +from cookbook.helper.ingredient_parser import IngredientParser from cookbook.integration.integration import Integration from cookbook.models import Recipe, Step, Ingredient, Keyword from gettext import gettext as _ @@ -53,11 +53,12 @@ class CookBookApp(Integration): step.save() recipe.steps.add(step) + ingredient_parser = IngredientParser(self.request, True) for ingredient in recipe_yml['ingredients'].split('\n'): if ingredient.strip() != '': - amount, unit, ingredient, note = parse(ingredient) - f = get_food(ingredient, self.request.space) - u = get_unit(unit, self.request.space) + amount, unit, ingredient, note = ingredient_parser.parse(ingredient) + f = ingredient_parser.get_food(ingredient) + u = ingredient_parser.get_unit(unit) step.ingredients.add(Ingredient.objects.create( food=f, unit=u, amount=amount, note=note, space=self.request.space, )) diff --git a/cookbook/integration/domestica.py b/cookbook/integration/domestica.py index da55e7c3..f580063d 100644 --- a/cookbook/integration/domestica.py +++ b/cookbook/integration/domestica.py @@ -2,7 +2,7 @@ import base64 import json from io import BytesIO -from cookbook.helper.ingredient_parser import parse, get_food, get_unit +from cookbook.helper.ingredient_parser import IngredientParser from cookbook.integration.integration import Integration from cookbook.models import Recipe, Step, Ingredient @@ -34,11 +34,12 @@ class Domestica(Integration): if file['source'] != '': step.instruction += '\n' + file['source'] + ingredient_parser = IngredientParser(self.request, True) for ingredient in file['ingredients'].split('\n'): if len(ingredient.strip()) > 0: - amount, unit, ingredient, note = parse(ingredient) - f = get_food(ingredient, self.request.space) - u = get_unit(unit, self.request.space) + amount, unit, ingredient, note = ingredient_parser.parse(ingredient) + f = ingredient_parser.get_food(ingredient) + u = ingredient_parser.get_unit(unit) step.ingredients.add(Ingredient.objects.create( food=f, unit=u, amount=amount, note=note, space=self.request.space, )) diff --git a/cookbook/integration/mealie.py b/cookbook/integration/mealie.py index 528adb0d..15117595 100644 --- a/cookbook/integration/mealie.py +++ b/cookbook/integration/mealie.py @@ -4,7 +4,7 @@ from io import BytesIO from zipfile import ZipFile from cookbook.helper.image_processing import get_filetype -from cookbook.helper.ingredient_parser import parse, get_food, get_unit +from cookbook.helper.ingredient_parser import IngredientParser from cookbook.integration.integration import Integration from cookbook.models import Recipe, Step, Ingredient @@ -37,17 +37,18 @@ class Mealie(Integration): if len(recipe_json['description'].strip()) > 500: step.instruction = recipe_json['description'].strip() + '\n\n' + step.instruction + ingredient_parser = IngredientParser(self.request, True) for ingredient in recipe_json['recipe_ingredient']: try: if ingredient['food']: - f = get_food(ingredient['food'], self.request.space) - u = get_unit(ingredient['unit'], self.request.space) + f = ingredient_parser.get_food(ingredient['food']) + u = ingredient_parser.get_unit(ingredient['unit']) amount = ingredient['quantity'] note = ingredient['note'] else: - amount, unit, ingredient, note = parse(ingredient['note']) - f = get_food(ingredient, self.request.space) - u = get_unit(unit, self.request.space) + amount, unit, ingredient, note = ingredient_parser.parse(ingredient['note']) + f = ingredient_parser.get_food(ingredient) + u = ingredient_parser.get_unit(unit) step.ingredients.add(Ingredient.objects.create( food=f, unit=u, amount=amount, note=note, space=self.request.space, )) diff --git a/cookbook/integration/mealmaster.py b/cookbook/integration/mealmaster.py index b0a59c97..7b067b35 100644 --- a/cookbook/integration/mealmaster.py +++ b/cookbook/integration/mealmaster.py @@ -1,6 +1,6 @@ import re -from cookbook.helper.ingredient_parser import parse, get_food, get_unit +from cookbook.helper.ingredient_parser import IngredientParser from cookbook.integration.integration import Integration from cookbook.models import Recipe, Step, Ingredient, Keyword @@ -42,11 +42,12 @@ class MealMaster(Integration): instruction='\n'.join(directions) + '\n\n', space=self.request.space, ) + ingredient_parser = IngredientParser(self.request, True) for ingredient in ingredients: if len(ingredient.strip()) > 0: - amount, unit, ingredient, note = parse(ingredient) - f = get_food(ingredient, self.request.space) - u = get_unit(unit, self.request.space) + amount, unit, ingredient, note = ingredient_parser.parse(ingredient) + f = ingredient_parser.get_food(ingredient) + u = ingredient_parser.get_unit(unit) step.ingredients.add(Ingredient.objects.create( food=f, unit=u, amount=amount, note=note, space=self.request.space, )) diff --git a/cookbook/integration/nextcloud_cookbook.py b/cookbook/integration/nextcloud_cookbook.py index 00a379d4..882f1329 100644 --- a/cookbook/integration/nextcloud_cookbook.py +++ b/cookbook/integration/nextcloud_cookbook.py @@ -4,7 +4,7 @@ from io import BytesIO from zipfile import ZipFile from cookbook.helper.image_processing import get_filetype -from cookbook.helper.ingredient_parser import parse, get_food, get_unit +from cookbook.helper.ingredient_parser import IngredientParser from cookbook.integration.integration import Integration from cookbook.models import Recipe, Step, Ingredient @@ -39,6 +39,7 @@ class NextcloudCookbook(Integration): ingredients_added = True + ingredient_parser = IngredientParser(self.request, True) for ingredient in recipe_json['recipeIngredient']: amount, unit, ingredient, note = parse(ingredient) f = get_food(ingredient, self.request.space) diff --git a/cookbook/integration/openeats.py b/cookbook/integration/openeats.py index 02751110..d948d90a 100644 --- a/cookbook/integration/openeats.py +++ b/cookbook/integration/openeats.py @@ -1,6 +1,6 @@ import json -from cookbook.helper.ingredient_parser import get_food, get_unit +from cookbook.helper.ingredient_parser import IngredientParser from cookbook.integration.integration import Integration from cookbook.models import Recipe, Step, Ingredient @@ -23,9 +23,10 @@ class OpenEats(Integration): step = Step.objects.create(instruction=instructions, space=self.request.space,) + ingredient_parser = IngredientParser(self.request, True) for ingredient in file['ingredients']: - f = get_food(ingredient['food'], self.request.space) - u = get_unit(ingredient['unit'], self.request.space) + f = ingredient_parser.get_food(ingredient['food']) + u = ingredient_parser.get_unit(ingredient['unit']) step.ingredients.add(Ingredient.objects.create( food=f, unit=u, amount=ingredient['amount'], space=self.request.space, )) diff --git a/cookbook/integration/paprika.py b/cookbook/integration/paprika.py index 92d4bbe0..dcd5bfbe 100644 --- a/cookbook/integration/paprika.py +++ b/cookbook/integration/paprika.py @@ -4,7 +4,7 @@ import json import re from io import BytesIO -from cookbook.helper.ingredient_parser import parse, get_food, get_unit +from cookbook.helper.ingredient_parser import IngredientParser from cookbook.integration.integration import Integration from cookbook.models import Recipe, Step, Ingredient, Keyword from gettext import gettext as _ @@ -66,12 +66,13 @@ class Paprika(Integration): keyword, created = Keyword.objects.get_or_create(name=c.strip(), space=self.request.space) recipe.keywords.add(keyword) + ingredient_parser = IngredientParser(self.request, True) try: for ingredient in recipe_json['ingredients'].split('\n'): if len(ingredient.strip()) > 0: - amount, unit, ingredient, note = parse(ingredient) - f = get_food(ingredient, self.request.space) - u = get_unit(unit, self.request.space) + amount, unit, ingredient, note = ingredient_parser.parse(ingredient) + f = ingredient_parser.get_food(ingredient) + u = ingredient_parser.get_unit(unit) step.ingredients.add(Ingredient.objects.create( food=f, unit=u, amount=amount, note=note, space=self.request.space, )) diff --git a/cookbook/integration/pepperplate.py b/cookbook/integration/pepperplate.py index f9ae868a..4acc2d7b 100644 --- a/cookbook/integration/pepperplate.py +++ b/cookbook/integration/pepperplate.py @@ -1,4 +1,4 @@ -from cookbook.helper.ingredient_parser import parse, get_food, get_unit +from cookbook.helper.ingredient_parser import IngredientParser from cookbook.integration.integration import Integration from cookbook.models import Recipe, Step, Ingredient @@ -38,11 +38,12 @@ class Pepperplate(Integration): instruction='\n'.join(directions) + '\n\n', space=self.request.space, ) + ingredient_parser = IngredientParser(self.request, True) for ingredient in ingredients: if len(ingredient.strip()) > 0: - amount, unit, ingredient, note = parse(ingredient) - f = get_food(ingredient, self.request.space) - u = get_unit(unit, self.request.space) + amount, unit, ingredient, note = ingredient_parser.parse(ingredient) + f = ingredient_parser.get_food(ingredient) + u = ingredient_parser.get_unit(unit) step.ingredients.add(Ingredient.objects.create( food=f, unit=u, amount=amount, note=note, space=self.request.space, )) diff --git a/cookbook/integration/plantoeat.py b/cookbook/integration/plantoeat.py index 32e871f9..f679bcb0 100644 --- a/cookbook/integration/plantoeat.py +++ b/cookbook/integration/plantoeat.py @@ -2,7 +2,7 @@ from io import BytesIO import requests -from cookbook.helper.ingredient_parser import parse, get_food, get_unit +from cookbook.helper.ingredient_parser import IngredientParser from cookbook.integration.integration import Integration from cookbook.models import Recipe, Step, Ingredient, Keyword @@ -53,11 +53,12 @@ class Plantoeat(Integration): keyword, created = Keyword.objects.get_or_create(name=k.strip(), space=self.request.space) recipe.keywords.add(keyword) + ingredient_parser = IngredientParser(self.request, True) for ingredient in ingredients: if len(ingredient.strip()) > 0: - amount, unit, ingredient, note = parse(ingredient) - f = get_food(ingredient, self.request.space) - u = get_unit(unit, self.request.space) + amount, unit, ingredient, note = ingredient_parser.parse(ingredient) + f = ingredient_parser.get_food(ingredient) + u = ingredient_parser.get_unit(unit) step.ingredients.add(Ingredient.objects.create( food=f, unit=u, amount=amount, note=note, space=self.request.space, )) diff --git a/cookbook/integration/recettetek.py b/cookbook/integration/recettetek.py index 92cf7a61..be1c5ae0 100644 --- a/cookbook/integration/recettetek.py +++ b/cookbook/integration/recettetek.py @@ -6,7 +6,7 @@ from zipfile import ZipFile import imghdr from cookbook.helper.image_processing import get_filetype -from cookbook.helper.ingredient_parser import parse, get_food, get_unit +from cookbook.helper.ingredient_parser import IngredientParser from cookbook.integration.integration import Integration from cookbook.models import Recipe, Step, Ingredient, Keyword @@ -55,11 +55,12 @@ class RecetteTek(Integration): try: # Process the ingredients. Assumes 1 ingredient per line. + ingredient_parser = IngredientParser(self.request, True) for ingredient in file['ingredients'].split('\n'): if len(ingredient.strip()) > 0: - amount, unit, ingredient, note = parse(ingredient) - f = get_food(ingredient, self.request.space) - u = get_unit(unit, self.request.space) + amount, unit, ingredient, note = ingredient_parser.parse(ingredient) + f = ingredient_parser.get_food(ingredient) + u = ingredient_parser.get_unit(unit) step.ingredients.add(Ingredient.objects.create( food=f, unit=u, amount=amount, note=note, space=self.request.space, )) diff --git a/cookbook/integration/recipekeeper.py b/cookbook/integration/recipekeeper.py index 746fc435..0de2ff8e 100644 --- a/cookbook/integration/recipekeeper.py +++ b/cookbook/integration/recipekeeper.py @@ -3,7 +3,7 @@ from bs4 import BeautifulSoup from io import BytesIO from zipfile import ZipFile -from cookbook.helper.ingredient_parser import parse, get_food, get_unit +from cookbook.helper.ingredient_parser import IngredientParser from cookbook.helper.recipe_url_import import parse_servings, iso_duration_to_minutes from cookbook.integration.integration import Integration from cookbook.models import Recipe, Step, Ingredient, Keyword @@ -41,12 +41,13 @@ class RecipeKeeper(Integration): step = Step.objects.create(instruction='', space=self.request.space,) + ingredient_parser = IngredientParser(self.request, True) for ingredient in file.find("div", {"itemprop": "recipeIngredients"}).findChildren("p"): if ingredient.text == "": continue - amount, unit, ingredient, note = parse(ingredient.text.strip()) - f = get_food(ingredient, self.request.space) - u = get_unit(unit, self.request.space) + amount, unit, ingredient, note = ingredient_parser.parse(ingredient.text.strip()) + f = ingredient_parser.get_food(ingredient) + u = ingredient_parser.get_unit(unit) step.ingredients.add(Ingredient.objects.create( food=f, unit=u, amount=amount, note=note, space=self.request.space, )) diff --git a/cookbook/integration/recipesage.py b/cookbook/integration/recipesage.py index 3490d88e..9c5f70ac 100644 --- a/cookbook/integration/recipesage.py +++ b/cookbook/integration/recipesage.py @@ -3,7 +3,7 @@ from io import BytesIO import requests -from cookbook.helper.ingredient_parser import parse, get_food, get_unit +from cookbook.helper.ingredient_parser import IngredientParser from cookbook.integration.integration import Integration from cookbook.models import Recipe, Step, Ingredient @@ -31,6 +31,7 @@ class RecipeSage(Integration): except Exception as e: print('failed to parse yield or time ', str(e)) + ingredient_parser = IngredientParser(self.request,True) ingredients_added = False for s in file['recipeInstructions']: step = Step.objects.create( @@ -40,9 +41,9 @@ class RecipeSage(Integration): ingredients_added = True for ingredient in file['recipeIngredient']: - amount, unit, ingredient, note = parse(ingredient) - f = get_food(ingredient, self.request.space) - u = get_unit(unit, self.request.space) + amount, unit, ingredient, note = ingredient_parser.parse(ingredient) + f = ingredient_parser.get_food(ingredient) + u = ingredient_parser.get_unit(unit) step.ingredients.add(Ingredient.objects.create( food=f, unit=u, amount=amount, note=note, space=self.request.space, )) diff --git a/cookbook/integration/rezkonv.py b/cookbook/integration/rezkonv.py index d4fcbd43..4ee8eb19 100644 --- a/cookbook/integration/rezkonv.py +++ b/cookbook/integration/rezkonv.py @@ -1,4 +1,4 @@ -from cookbook.helper.ingredient_parser import parse, get_food, get_unit +from cookbook.helper.ingredient_parser import IngredientParser from cookbook.integration.integration import Integration from cookbook.models import Recipe, Step, Ingredient, Keyword @@ -41,11 +41,12 @@ class RezKonv(Integration): instruction='\n'.join(directions) + '\n\n', space=self.request.space, ) + ingredient_parser = IngredientParser(self.request, True) for ingredient in ingredients: if len(ingredient.strip()) > 0: - amount, unit, ingredient, note = parse(ingredient) - f = get_food(ingredient, self.request.space) - u = get_unit(unit, self.request.space) + amount, unit, ingredient, note = ingredient_parser.parse(ingredient) + f = ingredient_parser.get_food(ingredient) + u = ingredient_parser.get_unit(unit) step.ingredients.add(Ingredient.objects.create( food=f, unit=u, amount=amount, note=note, space=self.request.space, )) diff --git a/cookbook/integration/safron.py b/cookbook/integration/safron.py index 5a5e4332..fa7a793e 100644 --- a/cookbook/integration/safron.py +++ b/cookbook/integration/safron.py @@ -1,6 +1,6 @@ from django.utils.translation import gettext as _ -from cookbook.helper.ingredient_parser import parse, get_food, get_unit +from cookbook.helper.ingredient_parser import IngredientParser from cookbook.integration.integration import Integration from cookbook.models import Recipe, Step, Ingredient @@ -43,12 +43,13 @@ class Safron(Integration): recipe = Recipe.objects.create(name=title, description=description, created_by=self.request.user, internal=True, space=self.request.space, ) - step = Step.objects.create(instruction='\n'.join(directions), space=self.request.space,) + step = Step.objects.create(instruction='\n'.join(directions), space=self.request.space, ) + ingredient_parser = IngredientParser(self.request, True) for ingredient in ingredients: - amount, unit, ingredient, note = parse(ingredient) - f = get_food(ingredient, self.request.space) - u = get_unit(unit, self.request.space) + amount, unit, ingredient, note = ingredient_parser.parse(ingredient) + f = ingredient_parser.get_food(ingredient) + u = ingredient_parser.get_unit(unit) step.ingredients.add(Ingredient.objects.create( food=f, unit=u, amount=amount, note=note, space=self.request.space, )) diff --git a/cookbook/tests/other/test_ingredient_parser.py b/cookbook/tests/other/test_ingredient_parser.py index d79671e8..5d61fa87 100644 --- a/cookbook/tests/other/test_ingredient_parser.py +++ b/cookbook/tests/other/test_ingredient_parser.py @@ -1,4 +1,4 @@ -from cookbook.helper.ingredient_parser import parse +from cookbook.helper.ingredient_parser import IngredientParser def test_ingredient_parser(): @@ -58,14 +58,16 @@ def test_ingredient_parser(): "2L Wasser": (2, "L", "Wasser", ""), "1 (16 ounce) package dry lentils, rinsed": (1, "package", "dry lentils, rinsed", "16 ounce"), "2-3 c Water": (2, "c", "Water", "2-3"), - "Pane (raffermo o secco) 80 g": (0, "", "Pane 80 g", "raffermo o secco"), #TODO this is actually not a good result but currently expected + "Pane (raffermo o secco) 80 g": (0, "", "Pane 80 g", "raffermo o secco"), # TODO this is actually not a good result but currently expected } # for German you could say that if an ingredient does not have # an amount # and it starts with a lowercase letter, then that # is a unit ("etwas", "evtl.") does not apply to English tho + ingredient_parser = IngredientParser(None, False, ignore_automations=True) + count = 0 for key, val in expectations.items(): count += 1 - parsed = parse(key) + parsed = ingredient_parser.parse(key) assert val == parsed diff --git a/cookbook/views/api.py b/cookbook/views/api.py index 63784a3c..7e071cf1 100644 --- a/cookbook/views/api.py +++ b/cookbook/views/api.py @@ -31,7 +31,7 @@ from rest_framework.viewsets import ViewSetMixin from treebeard.exceptions import PathOverflow, InvalidMoveToDescendant, InvalidPosition from cookbook.helper.image_processing import handle_image -from cookbook.helper.ingredient_parser import parse +from cookbook.helper.ingredient_parser import IngredientParser from cookbook.helper.permission_helper import (CustomIsAdmin, CustomIsGuest, CustomIsOwner, CustomIsShare, CustomIsShared, CustomIsUser, @@ -853,11 +853,11 @@ def recipe_from_source(request): }, status=400) else: - return JsonResponse({"recipe_json": get_from_scraper(scrape, request.space)}) + return JsonResponse({"recipe_json": get_from_scraper(scrape, request)}) elif (mode == 'source') or (mode == 'url' and auto == 'false'): if not data or data == 'undefined': data = requests.get(url, headers=HEADERS).content - recipe_json, recipe_tree, recipe_html, images = get_recipe_from_source(data, url, request.space) + recipe_json, recipe_tree, recipe_html, images = get_recipe_from_source(data, url, request) if len(recipe_tree) == 0 and len(recipe_json) == 0: return JsonResponse( { @@ -893,7 +893,9 @@ def get_backup(request): @group_required('user') def ingredient_from_string(request): text = request.POST['text'] - amount, unit, food, note = parse(text) + + ingredient_parser = IngredientParser(request, False) + amount, unit, food, note = ingredient_parser.parse(text) return JsonResponse( { diff --git a/cookbook/views/telegram.py b/cookbook/views/telegram.py index 143552aa..9c73ba7b 100644 --- a/cookbook/views/telegram.py +++ b/cookbook/views/telegram.py @@ -6,7 +6,7 @@ from django.http import JsonResponse from django.shortcuts import get_object_or_404 from django.views.decorators.csrf import csrf_exempt -from cookbook.helper.ingredient_parser import parse, get_unit, get_food +from cookbook.helper.ingredient_parser import IngredientParser from cookbook.helper.permission_helper import group_required from cookbook.models import TelegramBot, ShoppingList, ShoppingListEntry @@ -49,9 +49,12 @@ def hook(request, token): if not sl: sl = ShoppingList.objects.create(created_by=tb.created_by, space=tb.space) - amount, unit, ingredient, note = parse(data['message']['text']) - f = get_food(ingredient, tb.space) - u = get_unit(unit, tb.space) + request.space = tb.space # TODO this is likely a bad idea. Verify and test + request.user = tb.created_by + ingredient_parser = IngredientParser(request, False) + amount, unit, ingredient, note = ingredient_parser.parse(data['message']['text']) + f = ingredient_parser.get_food(ingredient) + u = ingredient_parser.get_unit(unit) sl.entries.add( ShoppingListEntry.objects.create( food=f, unit=u, amount=amount diff --git a/cookbook/views/views.py b/cookbook/views/views.py index bc21b24d..b6c8526c 100644 --- a/cookbook/views/views.py +++ b/cookbook/views/views.py @@ -26,10 +26,9 @@ from cookbook.forms import (CommentForm, Recipe, User, UserCreateForm, UserNameForm, UserPreference, UserPreferenceForm, SpaceJoinForm, SpaceCreateForm, SearchPreferenceForm) -from cookbook.helper.ingredient_parser import parse from cookbook.helper.permission_helper import group_required, share_link_valid, has_group_permission from cookbook.models import (Comment, CookLog, InviteLink, MealPlan, - RecipeBook, RecipeBookEntry, ViewLog, ShoppingList, Space, Keyword, RecipeImport, Unit, + ViewLog, ShoppingList, Space, Keyword, RecipeImport, Unit, Food, UserFile, ShareLink) from cookbook.tables import (CookLogTable, RecipeTable, RecipeTableSmall, ViewLogTable, InviteLinkTable) diff --git a/vue/src/apps/ModelListView/ModelListView.vue b/vue/src/apps/ModelListView/ModelListView.vue index 3ce186c5..f746d297 100644 --- a/vue/src/apps/ModelListView/ModelListView.vue +++ b/vue/src/apps/ModelListView/ModelListView.vue @@ -8,11 +8,22 @@ :show="show_modal" @finish-action="finishAction"/> +
+ +
+
+ + BETA + {{ $t('warning_feature_beta') }} + +
+
+

@@ -38,11 +49,11 @@
-
+ :item=i + :model="this_model" + @item-action="startAction($event, 'left')" + @finish-action="finishAction"/> +

- {{$t('Merge')}} & {{$t('Automate')}} + {{$t('Merge')}} & {{$t('Automate')}} BETA diff --git a/vue/src/components/GenericHorizontalCard.vue b/vue/src/components/GenericHorizontalCard.vue index 4b493b40..232c12eb 100644 --- a/vue/src/components/GenericHorizontalCard.vue +++ b/vue/src/components/GenericHorizontalCard.vue @@ -86,7 +86,7 @@ {{$t('Merge')}}: - {{$t('Merge')}} & {{$t('Automate')}}: {{$t('create_rule')}} + {{$t('Merge')}} & {{$t('Automate')}}: {{$t('create_rule')}} BETA {{$t('Cancel')}} diff --git a/vue/src/locales/en.json b/vue/src/locales/en.json index 187a0d83..0a1155d5 100644 --- a/vue/src/locales/en.json +++ b/vue/src/locales/en.json @@ -1,4 +1,5 @@ { + "warning_feature_beta": "This feature is currently in a BETA (testing) state. Please expect bugs and possibly breaking changes in the future (possibly loosing feature related data) when using this feature.", "err_fetching_resource": "There was an error fetching a resource!", "err_creating_resource": "There was an error creating a resource!", "err_updating_resource": "There was an error updating a resource!", diff --git a/vue/webpack-stats.json b/vue/webpack-stats.json index 04e4b01c..60f27948 100644 --- a/vue/webpack-stats.json +++ b/vue/webpack-stats.json @@ -1,183 +1,294 @@ { "status": "done", "assets": { - "../../templates/sw.js": { - "name": "../../templates/sw.js", - "path": "..\\..\\templates\\sw.js" - }, - "css/chunk-vendors.css": { - "name": "css/chunk-vendors.css", - "path": "css\\chunk-vendors.css" - }, "js/chunk-vendors.js": { "name": "js/chunk-vendors.js", - "path": "js\\chunk-vendors.js" - }, - "css/cookbook_view.css": { - "name": "css/cookbook_view.css", - "path": "css\\cookbook_view.css" + "path": "js\\chunk-vendors.js", + "publicPath": "http://localhost:8080/js/chunk-vendors.js" }, "js/cookbook_view.js": { "name": "js/cookbook_view.js", - "path": "js\\cookbook_view.js" - }, - "css/edit_internal_recipe.css": { - "name": "css/edit_internal_recipe.css", - "path": "css\\edit_internal_recipe.css" + "path": "js\\cookbook_view.js", + "publicPath": "http://localhost:8080/js/cookbook_view.js" }, "js/edit_internal_recipe.js": { "name": "js/edit_internal_recipe.js", - "path": "js\\edit_internal_recipe.js" + "path": "js\\edit_internal_recipe.js", + "publicPath": "http://localhost:8080/js/edit_internal_recipe.js" }, "js/import_response_view.js": { "name": "js/import_response_view.js", - "path": "js\\import_response_view.js" - }, - "css/meal_plan_view.css": { - "name": "css/meal_plan_view.css", - "path": "css\\meal_plan_view.css" + "path": "js\\import_response_view.js", + "publicPath": "http://localhost:8080/js/import_response_view.js" }, "js/meal_plan_view.js": { "name": "js/meal_plan_view.js", - "path": "js\\meal_plan_view.js" - }, - "css/model_list_view.css": { - "name": "css/model_list_view.css", - "path": "css\\model_list_view.css" + "path": "js\\meal_plan_view.js", + "publicPath": "http://localhost:8080/js/meal_plan_view.js" }, "js/model_list_view.js": { "name": "js/model_list_view.js", - "path": "js\\model_list_view.js" + "path": "js\\model_list_view.js", + "publicPath": "http://localhost:8080/js/model_list_view.js" }, "js/offline_view.js": { "name": "js/offline_view.js", - "path": "js\\offline_view.js" - }, - "css/recipe_search_view.css": { - "name": "css/recipe_search_view.css", - "path": "css\\recipe_search_view.css" + "path": "js\\offline_view.js", + "publicPath": "http://localhost:8080/js/offline_view.js" }, "js/recipe_search_view.js": { "name": "js/recipe_search_view.js", - "path": "js\\recipe_search_view.js" - }, - "css/recipe_view.css": { - "name": "css/recipe_view.css", - "path": "css\\recipe_view.css" + "path": "js\\recipe_search_view.js", + "publicPath": "http://localhost:8080/js/recipe_search_view.js" }, "js/recipe_view.js": { "name": "js/recipe_view.js", - "path": "js\\recipe_view.js" + "path": "js\\recipe_view.js", + "publicPath": "http://localhost:8080/js/recipe_view.js" }, "js/supermarket_view.js": { "name": "js/supermarket_view.js", - "path": "js\\supermarket_view.js" + "path": "js\\supermarket_view.js", + "publicPath": "http://localhost:8080/js/supermarket_view.js" }, "js/user_file_view.js": { "name": "js/user_file_view.js", - "path": "js\\user_file_view.js" + "path": "js\\user_file_view.js", + "publicPath": "http://localhost:8080/js/user_file_view.js" }, "recipe_search_view.html": { "name": "recipe_search_view.html", - "path": "recipe_search_view.html" + "path": "recipe_search_view.html", + "publicPath": "http://localhost:8080/recipe_search_view.html" }, "recipe_view.html": { "name": "recipe_view.html", - "path": "recipe_view.html" + "path": "recipe_view.html", + "publicPath": "http://localhost:8080/recipe_view.html" }, "offline_view.html": { "name": "offline_view.html", - "path": "offline_view.html" + "path": "offline_view.html", + "publicPath": "http://localhost:8080/offline_view.html" }, "import_response_view.html": { "name": "import_response_view.html", - "path": "import_response_view.html" + "path": "import_response_view.html", + "publicPath": "http://localhost:8080/import_response_view.html" }, "supermarket_view.html": { "name": "supermarket_view.html", - "path": "supermarket_view.html" + "path": "supermarket_view.html", + "publicPath": "http://localhost:8080/supermarket_view.html" }, "user_file_view.html": { "name": "user_file_view.html", - "path": "user_file_view.html" + "path": "user_file_view.html", + "publicPath": "http://localhost:8080/user_file_view.html" }, "model_list_view.html": { "name": "model_list_view.html", - "path": "model_list_view.html" + "path": "model_list_view.html", + "publicPath": "http://localhost:8080/model_list_view.html" }, "edit_internal_recipe.html": { "name": "edit_internal_recipe.html", - "path": "edit_internal_recipe.html" + "path": "edit_internal_recipe.html", + "publicPath": "http://localhost:8080/edit_internal_recipe.html" }, "cookbook_view.html": { "name": "cookbook_view.html", - "path": "cookbook_view.html" + "path": "cookbook_view.html", + "publicPath": "http://localhost:8080/cookbook_view.html" }, "meal_plan_view.html": { "name": "meal_plan_view.html", - "path": "meal_plan_view.html" + "path": "meal_plan_view.html", + "publicPath": "http://localhost:8080/meal_plan_view.html" }, "manifest.json": { "name": "manifest.json", - "path": "manifest.json" + "path": "manifest.json", + "publicPath": "http://localhost:8080/manifest.json" + }, + "model_list_view.7cf8cfb04383ac21dae2.hot-update.js": { + "name": "model_list_view.7cf8cfb04383ac21dae2.hot-update.js", + "path": "model_list_view.7cf8cfb04383ac21dae2.hot-update.js", + "publicPath": "http://localhost:8080/model_list_view.7cf8cfb04383ac21dae2.hot-update.js" + }, + "7cf8cfb04383ac21dae2.hot-update.json": { + "name": "7cf8cfb04383ac21dae2.hot-update.json", + "path": "7cf8cfb04383ac21dae2.hot-update.json", + "publicPath": "http://localhost:8080/7cf8cfb04383ac21dae2.hot-update.json" + }, + "model_list_view.65f25fe27091809f7fcf.hot-update.js": { + "name": "model_list_view.65f25fe27091809f7fcf.hot-update.js", + "path": "model_list_view.65f25fe27091809f7fcf.hot-update.js", + "publicPath": "http://localhost:8080/model_list_view.65f25fe27091809f7fcf.hot-update.js" + }, + "65f25fe27091809f7fcf.hot-update.json": { + "name": "65f25fe27091809f7fcf.hot-update.json", + "path": "65f25fe27091809f7fcf.hot-update.json", + "publicPath": "http://localhost:8080/65f25fe27091809f7fcf.hot-update.json" + }, + "model_list_view.032182ad6990812f8035.hot-update.js": { + "name": "model_list_view.032182ad6990812f8035.hot-update.js", + "path": "model_list_view.032182ad6990812f8035.hot-update.js", + "publicPath": "http://localhost:8080/model_list_view.032182ad6990812f8035.hot-update.js" + }, + "032182ad6990812f8035.hot-update.json": { + "name": "032182ad6990812f8035.hot-update.json", + "path": "032182ad6990812f8035.hot-update.json", + "publicPath": "http://localhost:8080/032182ad6990812f8035.hot-update.json" + }, + "model_list_view.cbedf3e2489989eec8dd.hot-update.js": { + "name": "model_list_view.cbedf3e2489989eec8dd.hot-update.js", + "path": "model_list_view.cbedf3e2489989eec8dd.hot-update.js", + "publicPath": "http://localhost:8080/model_list_view.cbedf3e2489989eec8dd.hot-update.js" + }, + "cbedf3e2489989eec8dd.hot-update.json": { + "name": "cbedf3e2489989eec8dd.hot-update.json", + "path": "cbedf3e2489989eec8dd.hot-update.json", + "publicPath": "http://localhost:8080/cbedf3e2489989eec8dd.hot-update.json" + }, + "model_list_view.87a9208c092e31885f51.hot-update.js": { + "name": "model_list_view.87a9208c092e31885f51.hot-update.js", + "path": "model_list_view.87a9208c092e31885f51.hot-update.js", + "publicPath": "http://localhost:8080/model_list_view.87a9208c092e31885f51.hot-update.js" + }, + "87a9208c092e31885f51.hot-update.json": { + "name": "87a9208c092e31885f51.hot-update.json", + "path": "87a9208c092e31885f51.hot-update.json", + "publicPath": "http://localhost:8080/87a9208c092e31885f51.hot-update.json" + }, + "cookbook_view.307bb5ac629ff8f5dba5.hot-update.js": { + "name": "cookbook_view.307bb5ac629ff8f5dba5.hot-update.js", + "path": "cookbook_view.307bb5ac629ff8f5dba5.hot-update.js", + "publicPath": "http://localhost:8080/cookbook_view.307bb5ac629ff8f5dba5.hot-update.js" + }, + "edit_internal_recipe.307bb5ac629ff8f5dba5.hot-update.js": { + "name": "edit_internal_recipe.307bb5ac629ff8f5dba5.hot-update.js", + "path": "edit_internal_recipe.307bb5ac629ff8f5dba5.hot-update.js", + "publicPath": "http://localhost:8080/edit_internal_recipe.307bb5ac629ff8f5dba5.hot-update.js" + }, + "import_response_view.307bb5ac629ff8f5dba5.hot-update.js": { + "name": "import_response_view.307bb5ac629ff8f5dba5.hot-update.js", + "path": "import_response_view.307bb5ac629ff8f5dba5.hot-update.js", + "publicPath": "http://localhost:8080/import_response_view.307bb5ac629ff8f5dba5.hot-update.js" + }, + "meal_plan_view.307bb5ac629ff8f5dba5.hot-update.js": { + "name": "meal_plan_view.307bb5ac629ff8f5dba5.hot-update.js", + "path": "meal_plan_view.307bb5ac629ff8f5dba5.hot-update.js", + "publicPath": "http://localhost:8080/meal_plan_view.307bb5ac629ff8f5dba5.hot-update.js" + }, + "model_list_view.307bb5ac629ff8f5dba5.hot-update.js": { + "name": "model_list_view.307bb5ac629ff8f5dba5.hot-update.js", + "path": "model_list_view.307bb5ac629ff8f5dba5.hot-update.js", + "publicPath": "http://localhost:8080/model_list_view.307bb5ac629ff8f5dba5.hot-update.js" + }, + "offline_view.307bb5ac629ff8f5dba5.hot-update.js": { + "name": "offline_view.307bb5ac629ff8f5dba5.hot-update.js", + "path": "offline_view.307bb5ac629ff8f5dba5.hot-update.js", + "publicPath": "http://localhost:8080/offline_view.307bb5ac629ff8f5dba5.hot-update.js" + }, + "recipe_search_view.307bb5ac629ff8f5dba5.hot-update.js": { + "name": "recipe_search_view.307bb5ac629ff8f5dba5.hot-update.js", + "path": "recipe_search_view.307bb5ac629ff8f5dba5.hot-update.js", + "publicPath": "http://localhost:8080/recipe_search_view.307bb5ac629ff8f5dba5.hot-update.js" + }, + "recipe_view.307bb5ac629ff8f5dba5.hot-update.js": { + "name": "recipe_view.307bb5ac629ff8f5dba5.hot-update.js", + "path": "recipe_view.307bb5ac629ff8f5dba5.hot-update.js", + "publicPath": "http://localhost:8080/recipe_view.307bb5ac629ff8f5dba5.hot-update.js" + }, + "supermarket_view.307bb5ac629ff8f5dba5.hot-update.js": { + "name": "supermarket_view.307bb5ac629ff8f5dba5.hot-update.js", + "path": "supermarket_view.307bb5ac629ff8f5dba5.hot-update.js", + "publicPath": "http://localhost:8080/supermarket_view.307bb5ac629ff8f5dba5.hot-update.js" + }, + "user_file_view.307bb5ac629ff8f5dba5.hot-update.js": { + "name": "user_file_view.307bb5ac629ff8f5dba5.hot-update.js", + "path": "user_file_view.307bb5ac629ff8f5dba5.hot-update.js", + "publicPath": "http://localhost:8080/user_file_view.307bb5ac629ff8f5dba5.hot-update.js" + }, + "307bb5ac629ff8f5dba5.hot-update.json": { + "name": "307bb5ac629ff8f5dba5.hot-update.json", + "path": "307bb5ac629ff8f5dba5.hot-update.json", + "publicPath": "http://localhost:8080/307bb5ac629ff8f5dba5.hot-update.json" + }, + "model_list_view.e2073a90ad8ff66f374c.hot-update.js": { + "name": "model_list_view.e2073a90ad8ff66f374c.hot-update.js", + "path": "model_list_view.e2073a90ad8ff66f374c.hot-update.js", + "publicPath": "http://localhost:8080/model_list_view.e2073a90ad8ff66f374c.hot-update.js" + }, + "e2073a90ad8ff66f374c.hot-update.json": { + "name": "e2073a90ad8ff66f374c.hot-update.json", + "path": "e2073a90ad8ff66f374c.hot-update.json", + "publicPath": "http://localhost:8080/e2073a90ad8ff66f374c.hot-update.json" + }, + "model_list_view.1b14899ea7152cc46f4e.hot-update.js": { + "name": "model_list_view.1b14899ea7152cc46f4e.hot-update.js", + "path": "model_list_view.1b14899ea7152cc46f4e.hot-update.js", + "publicPath": "http://localhost:8080/model_list_view.1b14899ea7152cc46f4e.hot-update.js" + }, + "1b14899ea7152cc46f4e.hot-update.json": { + "name": "1b14899ea7152cc46f4e.hot-update.json", + "path": "1b14899ea7152cc46f4e.hot-update.json", + "publicPath": "http://localhost:8080/1b14899ea7152cc46f4e.hot-update.json" + }, + "model_list_view.7a2ce4182c97623ab7e8.hot-update.js": { + "name": "model_list_view.7a2ce4182c97623ab7e8.hot-update.js", + "path": "model_list_view.7a2ce4182c97623ab7e8.hot-update.js", + "publicPath": "http://localhost:8080/model_list_view.7a2ce4182c97623ab7e8.hot-update.js" + }, + "7a2ce4182c97623ab7e8.hot-update.json": { + "name": "7a2ce4182c97623ab7e8.hot-update.json", + "path": "7a2ce4182c97623ab7e8.hot-update.json", + "publicPath": "http://localhost:8080/7a2ce4182c97623ab7e8.hot-update.json" } }, "chunks": { "recipe_search_view": [ - "css/chunk-vendors.css", "js/chunk-vendors.js", - "css/recipe_search_view.css", "js/recipe_search_view.js" ], "recipe_view": [ - "css/chunk-vendors.css", "js/chunk-vendors.js", - "css/recipe_view.css", "js/recipe_view.js" ], "offline_view": [ - "css/chunk-vendors.css", "js/chunk-vendors.js", "js/offline_view.js" ], "import_response_view": [ - "css/chunk-vendors.css", "js/chunk-vendors.js", "js/import_response_view.js" ], "supermarket_view": [ - "css/chunk-vendors.css", "js/chunk-vendors.js", "js/supermarket_view.js" ], "user_file_view": [ - "css/chunk-vendors.css", "js/chunk-vendors.js", "js/user_file_view.js" ], "model_list_view": [ - "css/chunk-vendors.css", "js/chunk-vendors.js", - "css/model_list_view.css", - "js/model_list_view.js" + "js/model_list_view.js", + "model_list_view.7a2ce4182c97623ab7e8.hot-update.js" ], "edit_internal_recipe": [ - "css/chunk-vendors.css", "js/chunk-vendors.js", - "css/edit_internal_recipe.css", "js/edit_internal_recipe.js" ], "cookbook_view": [ - "css/chunk-vendors.css", "js/chunk-vendors.js", - "css/cookbook_view.css", "js/cookbook_view.js" ], "meal_plan_view": [ - "css/chunk-vendors.css", "js/chunk-vendors.js", - "css/meal_plan_view.css", "js/meal_plan_view.js" ] - } + }, + "publicPath": "http://localhost:8080/" } \ No newline at end of file