changed source import to match field structure of recipe model
first imports working
This commit is contained in:
@ -11,6 +11,7 @@ from cookbook.helper import recipe_url_import as helper
|
||||
from cookbook.helper.ingredient_parser import IngredientParser
|
||||
from cookbook.models import Keyword
|
||||
|
||||
|
||||
# from recipe_scrapers._utils import get_minutes ## temporary until/unless upstream incorporates get_minutes() PR
|
||||
|
||||
|
||||
@ -33,6 +34,7 @@ def get_from_scraper(scrape, request):
|
||||
description = ''
|
||||
|
||||
recipe_json['description'] = parse_description(description)
|
||||
recipe_json['internal'] = True
|
||||
|
||||
try:
|
||||
servings = scrape.yields() or None
|
||||
@ -51,20 +53,20 @@ def get_from_scraper(scrape, request):
|
||||
recipe_json['servings'] = max(servings, 1)
|
||||
|
||||
try:
|
||||
recipe_json['prepTime'] = get_minutes(scrape.schema.data.get("prepTime")) or 0
|
||||
recipe_json['working_time'] = get_minutes(scrape.schema.data.get("prepTime")) or 0
|
||||
except Exception:
|
||||
recipe_json['prepTime'] = 0
|
||||
recipe_json['working_time'] = 0
|
||||
try:
|
||||
recipe_json['cookTime'] = get_minutes(scrape.schema.data.get("cookTime")) or 0
|
||||
recipe_json['waiting_time'] = get_minutes(scrape.schema.data.get("cookTime")) or 0
|
||||
except Exception:
|
||||
recipe_json['cookTime'] = 0
|
||||
recipe_json['waiting_time'] = 0
|
||||
|
||||
if recipe_json['cookTime'] + recipe_json['prepTime'] == 0:
|
||||
if recipe_json['working_time'] + recipe_json['waiting_time'] == 0:
|
||||
try:
|
||||
recipe_json['prepTime'] = get_minutes(scrape.total_time()) or 0
|
||||
recipe_json['working_time'] = get_minutes(scrape.total_time()) or 0
|
||||
except Exception:
|
||||
try:
|
||||
get_minutes(scrape.schema.data.get("totalTime")) or 0
|
||||
recipe_json['working_time'] = get_minutes(scrape.schema.data.get("totalTime")) or 0
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
@ -101,54 +103,49 @@ def get_from_scraper(scrape, request):
|
||||
|
||||
ingredient_parser = IngredientParser(request, True)
|
||||
|
||||
ingredients = []
|
||||
recipe_json['steps'] = []
|
||||
|
||||
for i in parse_instructions(scrape.instructions()):
|
||||
recipe_json['steps'].append({'instruction': i, 'ingredients': [], })
|
||||
if len(recipe_json['steps']) == 0:
|
||||
recipe_json['steps'].append({'instruction': '', 'ingredients': [], })
|
||||
|
||||
try:
|
||||
for x in scrape.ingredients():
|
||||
try:
|
||||
amount, unit, ingredient, note = ingredient_parser.parse(x)
|
||||
ingredients.append(
|
||||
recipe_json['steps'][0]['ingredients'].append(
|
||||
{
|
||||
'amount': amount,
|
||||
'unit': {
|
||||
'text': unit,
|
||||
'id': random.randrange(10000, 99999)
|
||||
'name': unit,
|
||||
},
|
||||
'ingredient': {
|
||||
'text': ingredient,
|
||||
'id': random.randrange(10000, 99999)
|
||||
'food': {
|
||||
'name': ingredient,
|
||||
},
|
||||
'note': note,
|
||||
'original': x
|
||||
'original_text': x
|
||||
}
|
||||
)
|
||||
except Exception:
|
||||
ingredients.append(
|
||||
recipe_json['steps'][0]['ingredients'].append(
|
||||
{
|
||||
'amount': 0,
|
||||
'unit': {
|
||||
'text': '',
|
||||
'id': random.randrange(10000, 99999)
|
||||
'name': '',
|
||||
},
|
||||
'ingredient': {
|
||||
'text': x,
|
||||
'id': random.randrange(10000, 99999)
|
||||
'food': {
|
||||
'name': x,
|
||||
},
|
||||
'note': '',
|
||||
'original': x
|
||||
'original_text': x
|
||||
}
|
||||
)
|
||||
recipe_json['recipeIngredient'] = ingredients
|
||||
except Exception:
|
||||
recipe_json['recipeIngredient'] = ingredients
|
||||
|
||||
try:
|
||||
recipe_json['recipeInstructions'] = parse_instructions(scrape.instructions())
|
||||
except Exception:
|
||||
recipe_json['recipeInstructions'] = ""
|
||||
pass
|
||||
|
||||
if scrape.url:
|
||||
recipe_json['url'] = scrape.url
|
||||
recipe_json['recipeInstructions'] += "\n\nImported from " + scrape.url
|
||||
recipe_json['source_url'] = scrape.url
|
||||
return recipe_json
|
||||
|
||||
|
||||
@ -161,102 +158,46 @@ def parse_name(name):
|
||||
return normalize_string(name)
|
||||
|
||||
|
||||
def parse_ingredients(ingredients):
|
||||
# some pages have comma separated ingredients in a single array entry
|
||||
try:
|
||||
if type(ingredients[0]) == dict:
|
||||
return ingredients
|
||||
except (KeyError, IndexError):
|
||||
pass
|
||||
|
||||
if (len(ingredients) == 1 and type(ingredients) == list):
|
||||
ingredients = ingredients[0].split(',')
|
||||
elif type(ingredients) == str:
|
||||
ingredients = ingredients.split(',')
|
||||
|
||||
for x in ingredients:
|
||||
if '\n' in x:
|
||||
ingredients.remove(x)
|
||||
for i in x.split('\n'):
|
||||
ingredients.insert(0, i)
|
||||
|
||||
ingredient_list = []
|
||||
|
||||
for x in ingredients:
|
||||
if x.replace(' ', '') != '':
|
||||
x = x.replace('½', "0.5").replace('¼', "0.25").replace('¾', "0.75")
|
||||
try:
|
||||
amount, unit, ingredient, note = parse_single_ingredient(x)
|
||||
if ingredient:
|
||||
ingredient_list.append(
|
||||
{
|
||||
'amount': amount,
|
||||
'unit': {
|
||||
'text': unit,
|
||||
'id': random.randrange(10000, 99999)
|
||||
},
|
||||
'ingredient': {
|
||||
'text': ingredient,
|
||||
'id': random.randrange(10000, 99999)
|
||||
},
|
||||
'note': note,
|
||||
'original': x
|
||||
}
|
||||
)
|
||||
except Exception:
|
||||
ingredient_list.append(
|
||||
{
|
||||
'amount': 0,
|
||||
'unit': {
|
||||
'text': '',
|
||||
'id': random.randrange(10000, 99999)
|
||||
},
|
||||
'ingredient': {
|
||||
'text': x,
|
||||
'id': random.randrange(10000, 99999)
|
||||
},
|
||||
'note': '',
|
||||
'original': x
|
||||
}
|
||||
)
|
||||
|
||||
ingredients = ingredient_list
|
||||
else:
|
||||
ingredients = []
|
||||
return ingredients
|
||||
|
||||
|
||||
def parse_description(description):
|
||||
return normalize_string(description)
|
||||
|
||||
|
||||
def parse_instructions(instructions):
|
||||
instruction_text = ''
|
||||
|
||||
# flatten instructions if they are in a list
|
||||
if type(instructions) == list:
|
||||
for i in instructions:
|
||||
if type(i) == str:
|
||||
instruction_text += i
|
||||
else:
|
||||
if 'text' in i:
|
||||
instruction_text += i['text'] + '\n\n'
|
||||
elif 'itemListElement' in i:
|
||||
for ile in i['itemListElement']:
|
||||
if type(ile) == str:
|
||||
instruction_text += ile + '\n\n'
|
||||
elif 'text' in ile:
|
||||
instruction_text += ile['text'] + '\n\n'
|
||||
else:
|
||||
instruction_text += str(i)
|
||||
instructions = instruction_text
|
||||
|
||||
normalized_string = normalize_string(instructions)
|
||||
def clean_instruction_string(instruction):
|
||||
normalized_string = normalize_string(instruction)
|
||||
normalized_string = normalized_string.replace('\n', ' \n')
|
||||
normalized_string = normalized_string.replace(' \n \n', '\n\n')
|
||||
return normalized_string
|
||||
|
||||
|
||||
def parse_instructions(instructions):
|
||||
"""
|
||||
Convert arbitrary instructions object from website import and turn it into a flat list of strings
|
||||
:param instructions: any instructions object from import
|
||||
:return: list of strings (from one to many elements depending on website)
|
||||
"""
|
||||
instruction_list = []
|
||||
|
||||
if type(instructions) == list:
|
||||
for i in instructions:
|
||||
if type(i) == str:
|
||||
instruction_list.append(clean_instruction_string(i))
|
||||
else:
|
||||
if 'text' in i:
|
||||
instruction_list.append(clean_instruction_string(i['text']))
|
||||
elif 'itemListElement' in i:
|
||||
for ile in i['itemListElement']:
|
||||
if type(ile) == str:
|
||||
instruction_list.append(clean_instruction_string(ile))
|
||||
elif 'text' in ile:
|
||||
instruction_list.append(clean_instruction_string(ile['text']))
|
||||
else:
|
||||
instruction_list.append(clean_instruction_string(str(i)))
|
||||
else:
|
||||
instruction_list.append(clean_instruction_string(instructions))
|
||||
|
||||
return instruction_list
|
||||
|
||||
|
||||
def parse_image(image):
|
||||
# check if list of images is returned, take first if so
|
||||
if not image:
|
||||
@ -334,9 +275,9 @@ def parse_keywords(keyword_json, space):
|
||||
kw = normalize_string(kw)
|
||||
if len(kw) != 0:
|
||||
if k := Keyword.objects.filter(name=kw, space=space).first():
|
||||
keywords.append({'id': str(k.id), 'text': str(k)})
|
||||
keywords.append({'name': str(k)})
|
||||
else:
|
||||
keywords.append({'id': random.randrange(1111111, 9999999, 1), 'text': kw})
|
||||
keywords.append({'name': kw})
|
||||
|
||||
return keywords
|
||||
|
||||
@ -367,6 +308,7 @@ def normalize_string(string):
|
||||
unescaped_string = unescaped_string.replace("\xa0", " ").replace("\t", " ").strip()
|
||||
return unescaped_string
|
||||
|
||||
|
||||
# TODO deprecate when merged into recipe_scapers
|
||||
|
||||
|
||||
@ -408,9 +350,9 @@ def get_minutes(time_text):
|
||||
if "/" in (hours := matched.groupdict().get("hours") or ''):
|
||||
number = hours.split(" ")
|
||||
if len(number) == 2:
|
||||
minutes += 60*int(number[0])
|
||||
minutes += 60 * int(number[0])
|
||||
fraction = number[-1:][0].split("/")
|
||||
minutes += 60 * float(int(fraction[0])/int(fraction[1]))
|
||||
minutes += 60 * float(int(fraction[0]) / int(fraction[1]))
|
||||
else:
|
||||
minutes += 60 * float(hours)
|
||||
|
||||
|
23
cookbook/migrations/0172_auto_20220219_1655.py
Normal file
23
cookbook/migrations/0172_auto_20220219_1655.py
Normal file
@ -0,0 +1,23 @@
|
||||
# Generated by Django 3.2.12 on 2022-02-19 15:55
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('cookbook', '0171_alter_searchpreference_trigram_threshold'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='ingredient',
|
||||
name='original_text',
|
||||
field=models.CharField(blank=True, default=None, max_length=512, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='recipe',
|
||||
name='source_url',
|
||||
field=models.CharField(blank=True, default=None, max_length=1024, null=True),
|
||||
),
|
||||
]
|
@ -240,7 +240,7 @@ class Space(ExportModelOperationsMixin('space'), models.Model):
|
||||
max_users = models.IntegerField(default=0)
|
||||
allow_sharing = models.BooleanField(default=True)
|
||||
demo = models.BooleanField(default=False)
|
||||
food_inherit = models.ManyToManyField(FoodInheritField, blank=True)
|
||||
food_inherit = models.ManyToManyField(FoodInheritField, blank=True)
|
||||
show_facet_count = models.BooleanField(default=False)
|
||||
|
||||
def __str__(self):
|
||||
@ -336,7 +336,7 @@ class UserPreference(models.Model, PermissionModelMixin):
|
||||
default_delay = models.DecimalField(default=4, max_digits=8, decimal_places=4)
|
||||
shopping_recent_days = models.PositiveIntegerField(default=7)
|
||||
csv_delim = models.CharField(max_length=2, default=",")
|
||||
csv_prefix = models.CharField(max_length=10, blank=True,)
|
||||
csv_prefix = models.CharField(max_length=10, blank=True, )
|
||||
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
space = models.ForeignKey(Space, on_delete=models.CASCADE, null=True)
|
||||
@ -495,11 +495,11 @@ class Food(ExportModelOperationsMixin('food'), TreeModel, PermissionModelMixin):
|
||||
ignore_shopping = models.BooleanField(default=False) # inherited field
|
||||
onhand_users = models.ManyToManyField(User, blank=True)
|
||||
description = models.TextField(default='', blank=True)
|
||||
inherit_fields = models.ManyToManyField(FoodInheritField, blank=True)
|
||||
inherit_fields = models.ManyToManyField(FoodInheritField, blank=True)
|
||||
substitute = models.ManyToManyField("self", blank=True)
|
||||
substitute_siblings = models.BooleanField(default=False)
|
||||
substitute_children = models.BooleanField(default=False)
|
||||
child_inherit_fields = models.ManyToManyField(FoodInheritField, blank=True, related_name='child_inherit')
|
||||
child_inherit_fields = models.ManyToManyField(FoodInheritField, blank=True, related_name='child_inherit')
|
||||
|
||||
space = models.ForeignKey(Space, on_delete=models.CASCADE)
|
||||
objects = ScopedManager(space='space', _manager_class=TreeManager)
|
||||
@ -532,7 +532,7 @@ class Food(ExportModelOperationsMixin('food'), TreeModel, PermissionModelMixin):
|
||||
if food:
|
||||
# if child inherit fields is preset children should be set to that, otherwise inherit this foods inherited fields
|
||||
inherit = list((food.child_inherit_fields.all() or food.inherit_fields.all()).values('id', 'field'))
|
||||
tree_filter = Q(path__startswith=food.path, space=space, depth=food.depth+1)
|
||||
tree_filter = Q(path__startswith=food.path, space=space, depth=food.depth + 1)
|
||||
else:
|
||||
inherit = list(space.food_inherit.all().values('id', 'field'))
|
||||
tree_filter = Q(space=space)
|
||||
@ -663,9 +663,7 @@ class Recipe(ExportModelOperationsMixin('recipe'), models.Model, PermissionModel
|
||||
servings = models.IntegerField(default=1)
|
||||
servings_text = models.CharField(default='', blank=True, max_length=32)
|
||||
image = models.ImageField(upload_to='recipes/', blank=True, null=True)
|
||||
storage = models.ForeignKey(
|
||||
Storage, on_delete=models.PROTECT, blank=True, null=True
|
||||
)
|
||||
storage = models.ForeignKey(Storage, on_delete=models.PROTECT, blank=True, null=True)
|
||||
file_uid = models.CharField(max_length=256, default="", blank=True)
|
||||
file_path = models.CharField(max_length=512, default="", blank=True)
|
||||
link = models.CharField(max_length=512, null=True, blank=True)
|
||||
@ -675,7 +673,7 @@ class Recipe(ExportModelOperationsMixin('recipe'), models.Model, PermissionModel
|
||||
working_time = models.IntegerField(default=0)
|
||||
waiting_time = models.IntegerField(default=0)
|
||||
internal = models.BooleanField(default=False)
|
||||
nutrition = models.ForeignKey( NutritionInformation, blank=True, null=True, on_delete=models.CASCADE )
|
||||
nutrition = models.ForeignKey(NutritionInformation, blank=True, null=True, on_delete=models.CASCADE)
|
||||
|
||||
source_url = models.CharField(max_length=1024, default=None, blank=True, null=True)
|
||||
created_by = models.ForeignKey(User, on_delete=models.PROTECT)
|
||||
|
@ -483,7 +483,7 @@ class IngredientSerializer(WritableNestedModelSerializer):
|
||||
model = Ingredient
|
||||
fields = (
|
||||
'id', 'food', 'unit', 'amount', 'note', 'order',
|
||||
'is_header', 'no_amount'
|
||||
'is_header', 'no_amount', 'original_text'
|
||||
)
|
||||
|
||||
|
||||
|
@ -1138,10 +1138,10 @@ def recipe_from_source(request):
|
||||
}, status=400)
|
||||
else:
|
||||
return JsonResponse({
|
||||
'recipe_tree': recipe_tree,
|
||||
'recipe_json': recipe_json,
|
||||
'recipe_tree': recipe_tree,
|
||||
'recipe_html': recipe_html,
|
||||
'images': images,
|
||||
'recipe_images': images,
|
||||
})
|
||||
|
||||
else:
|
||||
|
@ -32,218 +32,21 @@
|
||||
<h6>Options</h6>
|
||||
<!-- preview column -->
|
||||
<div class="row">
|
||||
<div class="col col-md-6" v-if="recipe_json !== undefined">
|
||||
<div >
|
||||
<!-- start of preview card -->
|
||||
<div class="card card-border-primary">
|
||||
<div class="card-header">
|
||||
<h3>Recipe Preview</h3> <!-- TODO localize -->
|
||||
<div class='small text-muted'>Drag recipe attributes from the
|
||||
right into the appropriate box below.
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body p-2">
|
||||
<div class="card mb-2">
|
||||
<div class="card-header" v-b-toggle.collapse-name>
|
||||
<div class="row px-3" style="justify-content:space-between;">
|
||||
Name
|
||||
<i class="fas fa-eraser" style="cursor:pointer;"
|
||||
@click="recipe_json.name=''"
|
||||
title="{% trans 'Clear Contents' %}"></i>
|
||||
</div>
|
||||
<div class="small text-muted">Text dragged here will
|
||||
be appended to the name.
|
||||
</div>
|
||||
</div>
|
||||
<b-collapse id="collapse-name" visible class="mt-2">
|
||||
<div class="card-body drop-zone"
|
||||
@drop="replacePreview('name', $event)"
|
||||
@dragover.prevent @dragenter.prevent>
|
||||
<div class="card-text">{{recipe_json.name}}</div>
|
||||
</div>
|
||||
</b-collapse>
|
||||
</div>
|
||||
|
||||
<div class="card mb-2">
|
||||
<div class="card-header" v-b-toggle.collapse-description>
|
||||
<div class="row px-3" style="justify-content:space-between;">
|
||||
{% trans 'Description' %}
|
||||
<i class="fas fa-eraser" style="cursor:pointer;"
|
||||
@click="recipe_json.description=''"
|
||||
title="{% trans 'Clear Contents' %}"></i>
|
||||
</div>
|
||||
<div class="small text-muted">{% trans 'Text dragged here will
|
||||
be appended to the description.' %}
|
||||
</div>
|
||||
</div>
|
||||
<b-collapse id="collapse-description" visible class="mt-2">
|
||||
<div class="card-body drop-zone"
|
||||
@drop="replacePreview('description', $event)"
|
||||
@dragover.prevent @dragenter.prevent>
|
||||
<div class="card-text">{{recipe_json.description}}</div>
|
||||
</div>
|
||||
</b-collapse>
|
||||
</div>
|
||||
|
||||
<div class="card mb-2">
|
||||
<div class="card-header" v-b-toggle.collapse-kw>
|
||||
<div class="row px-3" style="justify-content:space-between;">
|
||||
{% trans 'Keywords' %}
|
||||
<i class="fas fa-eraser" style="cursor:pointer;"
|
||||
@click="recipe_json.keywords=[]"
|
||||
title="{% trans 'Clear Contents' %}"></i>
|
||||
</div>
|
||||
<div class="small text-muted">{% trans 'Keywords dragged here
|
||||
will be appended to current list' %}
|
||||
</div>
|
||||
</div>
|
||||
<b-collapse id="collapse-kw" visible class="mt-2">
|
||||
<div class="card-body drop-zone"
|
||||
@drop="replacePreview('keywords', $event)"
|
||||
@dragover.prevent @dragenter.prevent>
|
||||
<div v-for="kw in recipe_json.keywords" v-bind:key="kw.id">
|
||||
<div class="card-text">{{ kw.text }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</b-collapse>
|
||||
</div>
|
||||
|
||||
<div class="card mb-2">
|
||||
<div class="card-header" v-b-toggle.collapse-image
|
||||
style="display:flex; justify-content:space-between;">
|
||||
{% trans 'Image' %}
|
||||
<i class="fas fa-eraser" style="cursor:pointer;"
|
||||
@click="recipe_json.image=''"
|
||||
title="{% trans 'Clear Contents' %}"></i>
|
||||
</div>
|
||||
<b-collapse id="collapse-image" visible class="mt-2">
|
||||
<div class="card-body m-0 p-0 drop-zone"
|
||||
@drop="replacePreview('image', $event)"
|
||||
@dragover.prevent @dragenter.prevent>
|
||||
<img class="card-img" v-bind:src="recipe_json.image"
|
||||
alt="Recipe Image">
|
||||
</div>
|
||||
</b-collapse>
|
||||
</div>
|
||||
|
||||
<div class="row mb-2">
|
||||
<div class="col">
|
||||
<div class="card">
|
||||
<div class="card-header p-1"
|
||||
style="display:flex; justify-content:space-between;">
|
||||
{% trans 'Servings' %}
|
||||
<i class="fas fa-eraser" style="cursor:pointer;"
|
||||
@click="recipe_json.servings=''"
|
||||
title="{% trans 'Clear Contents' %}"></i>
|
||||
</div>
|
||||
<div class="card-body p-2 drop-zone"
|
||||
@drop="replacePreview('servings', $event)"
|
||||
@dragover.prevent @dragenter.prevent>
|
||||
<div class="card-text">{{recipe_json.servings}}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col">
|
||||
<div class="card">
|
||||
<div class="card-header p-1"
|
||||
style="display:flex; justify-content:space-between;">
|
||||
{% trans 'Prep Time' %}
|
||||
<i class="fas fa-eraser" style="cursor:pointer;"
|
||||
@click="recipe_json.prepTime=''"
|
||||
title="{% trans 'Clear Contents' %}"></i>
|
||||
</div>
|
||||
<div class="card-body p-2 drop-zone"
|
||||
@drop="replacePreview('prepTime', $event)"
|
||||
@dragover.prevent @dragenter.prevent>
|
||||
<div class="card-text">{{recipe_json.prepTime}}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col">
|
||||
<div class="card">
|
||||
<div class="card-header p-1"
|
||||
style="display:flex; justify-content:space-between;">
|
||||
{% trans 'Cook Time' %}
|
||||
<i class="fas fa-eraser" style="cursor:pointer;"
|
||||
@click="recipe_json.cookTime=''"
|
||||
title="{% trans 'Clear Contents' %}"></i>
|
||||
</div>
|
||||
<div class="card-body p-2 drop-zone"
|
||||
@drop="replacePreview('cookTime', $event)"
|
||||
@dragover.prevent @dragenter.prevent>
|
||||
<div class="card-text">{{recipe_json.cookTime}}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card mb-2">
|
||||
<div class="card-header" v-b-toggle.collapse-ing>
|
||||
<div class="row px-3"
|
||||
style="display:flex; justify-content:space-between;">
|
||||
{% trans 'Ingredients' %}
|
||||
<i class="fas fa-eraser" style="cursor:pointer;"
|
||||
@click="recipe_json.recipeIngredient=[]"
|
||||
title="{% trans 'Clear Contents' %}"></i>
|
||||
</div>
|
||||
<div class="small text-muted">{% trans 'Ingredients dragged here
|
||||
will be appended to current list.' %}
|
||||
</div>
|
||||
</div>
|
||||
<b-collapse id="collapse-ing" visible class="mt-2">
|
||||
<div class="card-body drop-zone"
|
||||
@drop="replacePreview('ingredients', $event)"
|
||||
@dragover.prevent @dragenter.prevent>
|
||||
<ul class="list-group list-group">
|
||||
<div v-for="i in recipe_json.recipeIngredient" v-bind:key="i.id">
|
||||
<li class="row border-light">
|
||||
<div class="col-sm-1 border">{{i.amount}}</div>
|
||||
<div class="col-sm border"> {{i.unit.text}}</div>
|
||||
<div class="col-sm border">
|
||||
{{i.ingredient.text}}
|
||||
</div>
|
||||
<div class="col-sm border">{{i.note}}</div>
|
||||
</li>
|
||||
</div>
|
||||
</ul>
|
||||
</div>
|
||||
</b-collapse>
|
||||
</div>
|
||||
|
||||
<div class="card mb-2">
|
||||
<div class="card-header" v-b-toggle.collapse-instructions>
|
||||
<div class="row px-3" style="justify-content:space-between;">
|
||||
{% trans 'Instructions' %}
|
||||
<i class="fas fa-eraser" style="cursor:pointer;"
|
||||
@click="recipe_json.recipeInstructions=''"
|
||||
title="{% trans 'Clear Contents' %}"></i>
|
||||
</div>
|
||||
<div class="small text-muted">{% trans 'Recipe instructions
|
||||
dragged here will be appended to current instructions.' %}
|
||||
</div>
|
||||
</div>
|
||||
<b-collapse id="collapse-instructions" visible class="mt-2">
|
||||
<div class="card-body drop-zone"
|
||||
@drop="replacePreview('instructions', $event)"
|
||||
@dragover.prevent @dragenter.prevent>
|
||||
<div class="card-text">{{recipe_json.recipeInstructions}}
|
||||
</div>
|
||||
</div>
|
||||
</b-collapse>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<br/>
|
||||
<!-- end of preview card -->
|
||||
<button @click="showRecipe()" class="btn btn-primary shadow-none" type="button"
|
||||
style="margin-bottom: 2vh"
|
||||
id="id_btn_json"><i class="fas fa-code"></i> {% trans 'Import' %}
|
||||
</button>
|
||||
</div>
|
||||
<div class="col col-md-12" v-if="recipe_json !== undefined">
|
||||
Images
|
||||
Keywords
|
||||
<ul>
|
||||
<li v-for="k in recipe_json.keywords" v-bind:key="k">{{k}}</li>
|
||||
</ul>
|
||||
Steps
|
||||
<ul>
|
||||
<li v-for="s in recipe_json.steps" v-bind:key="s">{{s}}</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<h6>Import</h6>
|
||||
<b-button @click="importRecipe()">Import</b-button>
|
||||
|
||||
</b-tab>
|
||||
<b-tab v-bind:title="$t('App')">
|
||||
@ -277,6 +80,7 @@ import 'bootstrap-vue/dist/bootstrap-vue.css'
|
||||
|
||||
import {resolveDjangoUrl, ResolveUrlMixin, StandardToasts, ToastMixin} from "@/utils/utils";
|
||||
import axios from "axios";
|
||||
import {ApiApiFactory} from "@/utils/openapi/api";
|
||||
|
||||
Vue.use(BootstrapVue)
|
||||
|
||||
@ -293,8 +97,8 @@ export default {
|
||||
website_url: '',
|
||||
recent_urls: [],
|
||||
source_data: '',
|
||||
recipe_data: undefined,
|
||||
recipe_json: undefined,
|
||||
recipe_data: undefined,
|
||||
recipe_tree: undefined,
|
||||
recipe_images: [],
|
||||
automatic: true,
|
||||
@ -309,6 +113,18 @@ export default {
|
||||
|
||||
},
|
||||
methods: {
|
||||
/**
|
||||
* Import recipe based on the data configured by the client
|
||||
*/
|
||||
importRecipe: function () {
|
||||
let apiFactory = new ApiApiFactory()
|
||||
apiFactory.createRecipe(this.recipe_json).then(response => {
|
||||
StandardToasts.makeStandardToast(StandardToasts.SUCCESS_CREATE)
|
||||
window.location = resolveDjangoUrl('edit_recipe', response.data.id)
|
||||
}).catch(err => {
|
||||
StandardToasts.makeStandardToast(StandardToasts.FAIL_CREATE)
|
||||
})
|
||||
},
|
||||
/**
|
||||
* Requests the recipe to be loaded form the source (url/data) from the server
|
||||
* Updates all variables to contain what they need to render either simple preview or manual mapping mode
|
||||
@ -340,7 +156,7 @@ export default {
|
||||
this.recipe_json = response.data['recipe_json'];
|
||||
this.recipe_tree = response.data['recipe_tree'];
|
||||
this.recipe_html = response.data['recipe_html'];
|
||||
this.recipe_images = response.data['images']; //todo change on backend as well after old view is deprecated
|
||||
this.recipe_images = response.data['recipe_images']; //todo change on backend as well after old view is deprecated
|
||||
if (this.automatic) {
|
||||
this.recipe_data = this.recipe_json;
|
||||
this.preview = false
|
||||
|
@ -308,6 +308,8 @@
|
||||
<draggable :list="step.ingredients" group="ingredients" :empty-insert-threshold="10" handle=".handle" @sort="sortIngredients(step)">
|
||||
<div v-for="(ingredient, index) in step.ingredients" :key="ingredient.id">
|
||||
<hr class="d-md-none" />
|
||||
<!-- TODO improve rendering/add switch to toggle on/off -->
|
||||
<div class="small text-muted" v-if="ingredient.original_text">{{ingredient.original_text}}</div>
|
||||
<div class="d-flex">
|
||||
<div class="flex-grow-0 handle align-self-start">
|
||||
<button type="button" class="btn btn-lg shadow-none pr-0 pl-1 pr-md-2 pl-md-2"><i class="fas fa-arrows-alt-v"></i></button>
|
||||
|
@ -746,6 +746,12 @@ export interface Ingredient {
|
||||
* @memberof Ingredient
|
||||
*/
|
||||
no_amount?: boolean;
|
||||
/**
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof Ingredient
|
||||
*/
|
||||
original_text?: string | null;
|
||||
}
|
||||
/**
|
||||
*
|
||||
@ -1905,6 +1911,12 @@ export interface RecipeIngredients {
|
||||
* @memberof RecipeIngredients
|
||||
*/
|
||||
no_amount?: boolean;
|
||||
/**
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof RecipeIngredients
|
||||
*/
|
||||
original_text?: string | null;
|
||||
}
|
||||
/**
|
||||
*
|
||||
|
Reference in New Issue
Block a user