combined json import and source import

This commit is contained in:
smilerz
2021-03-21 13:13:56 -05:00
parent 6a13619bbd
commit 9cc6a1dc79
5 changed files with 110 additions and 139 deletions

View File

@@ -7,10 +7,7 @@ from bs4.element import Tag
from cookbook.helper import recipe_url_import as helper from cookbook.helper import recipe_url_import as helper
# %% def get_recipe_from_source(text, space):
# %%
def get_from_raw(text, space):
def build_node(k, v): def build_node(k, v):
if isinstance(v, dict): if isinstance(v, dict):
node = { node = {
@@ -113,17 +110,20 @@ def get_from_raw(text, space):
if '@graph' in el: if '@graph' in el:
for x in el['@graph']: for x in el['@graph']:
if '@type' in x and x['@type'] == 'Recipe': if '@type' in x and x['@type'] == 'Recipe':
recipe_json = helper.find_recipe_json(x, None, space) el = x
recipe_tree += [{'name': 'ld+json', 'children': temp_tree}]
if '@type' in el and el['@type'] == 'Recipe':
recipe_json = helper.find_recipe_json(el, None, space)
recipe_tree += [{'name': 'ld+json', 'children': temp_tree}]
else: else:
recipe_tree += [{'name': 'json', 'children': temp_tree}] recipe_tree += [{'name': 'json', 'children': temp_tree}]
temp_tree = [] temp_tree = []
# overide keyword structure from dict to list
kws = []
for kw in recipe_json['keywords']:
kws.append(kw['text'])
recipe_json['keywords'] = kws
return recipe_json, recipe_tree return recipe_json, recipe_tree
def get_from_html(text, space):
for s in soup.strings:
if ((s.parent.name not in INVISIBLE_ELEMS) and (len(s.strip()) > 0)):
print(s.parent.name, s, len(s))

View File

@@ -14,8 +14,8 @@ from recipe_scrapers._schemaorg import SchemaOrgException
from recipe_scrapers._utils import get_minutes from recipe_scrapers._utils import get_minutes
def get_from_scraper(scrape, space): def get_from_html_old(html_text, url, space):
# converting the scrape_me object to the existing json format based on ld+json soup = BeautifulSoup(html_text, "html.parser")
# first try finding ld+json as its most common # first try finding ld+json as its most common
for ld in soup.find_all('script', type='application/ld+json'): for ld in soup.find_all('script', type='application/ld+json'):

View File

@@ -25,72 +25,54 @@
<h2> {% trans 'Import' %} </h2> <h2> {% trans 'Import' %} </h2>
<nav class="nav nav-pills flex-sm-row" style="margin-bottom:10px"> <nav class="nav nav-pills flex-sm-row" style="margin-bottom:10px">
<a class="nav-link active" href="#nav-url" data-toggle="tab" role="tab" aria-controls="nav-url" aria-selected="true">URL</a> <a class="nav-link active" href="#nav-url" data-toggle="tab" role="tab" aria-controls="nav-url" aria-selected="true">URL</a>
<a class="nav-link" href="#nav-ldjson" data-toggle="tab" role="tab" aria-controls="nav-ldjson">ld+json</a> <a class="nav-link" href="#nav-source" data-toggle="tab" role="tab" aria-controls="nav-source">Source</a>
<a class="nav-link" href="#nav-json" data-toggle="tab" role="tab" aria-controls="nav-json">json</a> <a class="nav-link disabled" href="#nav-text" data-toggle="tab" role="tab" aria-controls="nav-text">Text</a>
<a class="nav-link disabled" href="#nav-html" data-toggle="tab" role="tab" aria-controls="nav-html">HTML</a> <a class="nav-link disabled" href="#nav-file" data-toggle="tab" role="tab" aria-controls="nav-file">File</a>
<a class="nav-link disabled" href="#nav-text" data-toggle="tab" role="tab" aria-controls="nav-text">text</a>
<a class="nav-link disabled" href="#nav-pdf" data-toggle="tab" role="tab" aria-controls="nav-pdf">PDF</a>
</nav> </nav>
<div class="tab-content" id="nav-tabContent"> <div class="tab-content" id="nav-tabContent">
<!-- Import URL --> <!-- Import URL -->
<div class="row tab-pane fade show active" id="nav-url" role="tabpanel"> <div class="tab-pane fade show active" id="nav-url" role="tabpanel">
<div class="col-md-12"> <div class="btn-group btn-group-toggle" data-toggle="buttons">
<div class="input-group mb-3"> <label class="btn btn-outline-info btn-sm active" @click="automatic=true">
<input class="form-control" v-model="remote_url" placeholder="{% trans 'Enter website URL' %}"> <input type="radio" name="auto" id="auto" autocomplete="off" checked> Automatic
<div class="input-group-append"> </label>
<button @click="loadRecipe()" class="btn btn-primary shadow-none" type="button"
id="id_btn_search"><i class="fas fa-search"></i>
</button>
</div>
</div>
</div>
</div>
<!-- Automatically import LD+JSON --> <label class="btn btn-outline-info btn-sm disabled" @click="automatic=false">
<div class="row tab-pane fade show" id="nav-ldjson" role="tabpanel"> <input type="radio" name="auto" id="manual" autocomplete="off"> Manual
<div class="col-md-12"> </label>
<div class="input-group input-group-lg"> </div>
<textarea class="form-control input-group-append" v-model="raw_data" rows=10 placeholder="{% trans 'Paste ld+json here to parse recipe automatically.' %}" style="font-size: 12px"> <div class="input-group mb-3">
</textarea> <input class="form-control" v-model="remote_url" placeholder="{% trans 'Enter website URL' %}">
</div> <div class="input-group-append">
<br> <button @click="loadRecipe()" class="btn btn-primary shadow-none" type="button"
<button @click="loadRecipeJson()" class="btn btn-primary shadow-none" type="button" id="id_btn_search"><i class="fas fa-search"></i>
id="id_btn_json"><i class="fas fa-code"></i> {% trans 'Import' %}
</button> </button>
</div> </div>
</div>
<!-- Manually import from JSON -->
<div class="row tab-pane fade show" id="nav-json" role="tabpanel">
<div class="col-md-12">
<div class="input-group input-group-lg">
<textarea class="form-control input-group-append" v-model="raw_data" rows=10
placeholder="{% trans 'To parse recipe manually: Paste JSON document here or a web page source that contains one or more JSON elements here.' %}" style="font-size: 12px">
</textarea>
</div>
<br>
<button @click="loadPreviewRaw()" class="btn btn-primary shadow-none" type="button"
id="id_btn_raw"><i class="fas fa-code"></i> {% trans 'Preview Import' %}
</button>
</div> </div>
</div> </div>
<!-- Manually import from HTML --> <!-- Import JSON or HTML -->
<div class="row tab-pane fade show" id="nav-html" role="tabpanel"> <div class=" tab-pane fade show" id="nav-source" role="tabpanel">
<div class="col-md-12"> <div class="btn-group btn-group-toggle" data-toggle="buttons">
<div class="input-group input-group-lg"> <label class="btn btn-outline-info btn-sm active" @click="automatic=true">
<textarea class="form-control input-group-append" v-model="raw_data" rows=10 placeholder="{% trans 'Paste html source here to parse recipe manually.' %}" style="font-size: 12px"> <input type="radio" name="auto" id="auto" autocomplete="off" checked> Automatic
</textarea> </label>
</div>
<br>
<button @click="loadPreviewRaw()" class="btn btn-primary shadow-none" type="button"
id="id_btn_HTML"><i class="fas fa-code"></i> {% trans 'Preview Import' %}
</button>
</div>
</div>
<label class="btn btn-outline-info btn-sm" @click="automatic=false">
<input type="radio" name="auto" id="manual" autocomplete="off"> Manual
</label>
</div>
<div class="input-group input-group-lg">
<textarea class="form-control input-group-append" v-model="source_data" rows=10 placeholder="{% trans 'Paste json or html source here to load recipe.' %}" style="font-size: 12px">
</textarea>
</div>
<br>
<button @click="loadSource()" class="btn btn-primary shadow-none" type="button"
id="id_btn_json"><i class="fas fa-code"></i> {% trans 'Import' %}
</button>
</div>
</div> </div>
<br/> <br/>
@@ -101,9 +83,7 @@
</div> </div>
<!-- recipe preview before Import --> <!-- recipe preview before Import -->
<div class="container-fluid" v-if="parsed" id="manage_tree"> <div class="container-fluid" v-if="preview" id="manage_tree">
<h2></h2>
<div class="row"> <div class="row">
<div class="col" style="max-width:50%"> <div class="col" style="max-width:50%">
<div class="card card-border-primary" > <div class="card card-border-primary" >
@@ -116,6 +96,7 @@
<div class="card mb-2"> <div class="card mb-2">
<div class="card-header" style="display:flex; justify-content:space-between;"> <div class="card-header" style="display:flex; justify-content:space-between;">
{% trans 'Name' %} {% trans 'Name' %}
<!-- this and subsequent delete commands should be fixed to identify which model attribute the element is referring to -->
<i class="fas fa-eraser" style="cursor:pointer;" @click="deletePreview('name')" title="{% trans 'Clear Contents'%}"></i> <i class="fas fa-eraser" style="cursor:pointer;" @click="deletePreview('name')" title="{% trans 'Clear Contents'%}"></i>
</div> </div>
@@ -232,11 +213,13 @@
</div> </div>
</div> </div>
<br/> <br/>
<button @click="loadRecipeRaw()" class="btn btn-primary shadow-none" type="button" <!-- end of preview card -->
<button @click="loadRecipeManual()" class="btn btn-primary shadow-none" type="button"
id="id_btn_json"><i class="fas fa-code"></i> {% trans 'Import' %} id="id_btn_json"><i class="fas fa-code"></i> {% trans 'Import' %}
</button> </button>
</div> </div>
<!-- start of json tree -->
<div class="col" style="max-width:50%"> <div class="col" style="max-width:50%">
<div class="card card-border-primary"> <div class="card card-border-primary">
<div class="card-header"> <div class="card-header">
@@ -274,9 +257,9 @@
</div> </div>
</div> </div>
</div> </div>
<!-- end of json tree -->
</div> </div>
</div> </div>
<!-- end of recipe preview before Import --> <!-- end of recipe preview before Import -->
<template v-if="recipe_data !== undefined"> <template v-if="recipe_data !== undefined">
@@ -518,7 +501,7 @@
el: '#app', el: '#app',
data: { data: {
remote_url: '', remote_url: '',
raw_data: undefined, source_data: undefined,
keywords: [], keywords: [],
keywords_loading: false, keywords_loading: false,
units: [], units: [],
@@ -528,11 +511,12 @@
recipe_data: undefined, recipe_data: undefined,
error: undefined, error: undefined,
loading: false, loading: false,
parsed: false, preview: false,
all_keywords: false, all_keywords: false,
importing_recipe: false, importing_recipe: false,
recipe_json: undefined, recipe_json: undefined,
recipe_tree: undefined, recipe_tree: undefined,
automatic: true
}, },
directives: { directives: {
tabindex: { tabindex: {
@@ -564,12 +548,10 @@
this.images = [] this.images = []
this.error = undefined this.error = undefined
this.loading = true this.loading = true
this.preview = false if (this.automatic) {
this.$http.post("{% url 'api_recipe_from_source' %}", { console.log('true')
'url': this.remote_url, }
'data': this.source_data, this.$http.post("{% url 'api_recipe_from_url' %}", {'url': this.remote_url}, {emulateJSON: true}).then((response) => {
'auto':this.automatic,
'mode':this.mode}, {emulateJSON: true}).then((response) => {
console.log(response.data) console.log(response.data)
this.recipe_json = response.data['recipe_json']; this.recipe_json = response.data['recipe_json'];
this.recipe_tree = response.data['recipe_tree']; this.recipe_tree = response.data['recipe_tree'];
@@ -595,53 +577,43 @@
this.makeToast(gettext('Error'), msg, 'danger') this.makeToast(gettext('Error'), msg, 'danger')
}) })
}, },
loadRecipeJson: function () { loadSource: function() {
this.recipe_data = undefined this.recipe_data = undefined
this.error = undefined
this.loading = true
this.$http.post("{% url 'api_recipe_from_json' %}", {'json': this.raw_data}, {emulateJSON: true}).then((response) => {
console.log(response.data)
this.recipe_data = response.data;
this.loading = false
}).catch((err) => {
this.error = err.data
this.loading = false
console.log(err)
this.makeToast(gettext('Error'), gettext('There was an error loading a resource!') + err.bodyText, 'danger')
})
},
loadRecipeRaw: function () {
this.error = undefined
this.loading = true
this.parsed = false
this.recipe_json['@type'] = "Recipe"
this.$http.post("{% url 'api_recipe_from_json' %}", {'json': JSON.stringify(this.recipe_json)}, {emulateJSON: true}).then((response) => {
console.log(response.data)
this.recipe_data = response.data;
this.loading = false
}).catch((err) => {
this.error = err.data
this.loading = false
console.log(err)
this.makeToast(gettext('Error'), gettext('There was an error loading a resource!') + err.bodyText, 'danger')
})
},
loadPreviewRaw: function () {
this.recipe_json = undefined this.recipe_json = undefined
this.recipe_tree = undefined this.recipe_tree = undefined
this.error = undefined this.error = undefined
this.loading = true this.loading = true
this.$http.post("{% url 'api_manual_recipe_from_json' %}", {'data': this.raw_data}, {emulateJSON: true}).then((response) => { this.$http.post("{% url 'api_recipe_from_source' %}", {'data': this.source_data, 'auto':this.automatic}, {emulateJSON: true}).then((response) => {
console.log(response.data) console.log(response.data)
this.recipe_json = response.data['recipe_json']; if (this.automatic) {
this.recipe_tree = response.data['recipe_tree']; this.recipe_data = response.data['recipe_json'];
this.preview = false
} else {
this.recipe_json = response.data['recipe_json'];
this.recipe_tree = response.data['recipe_tree'];
this.preview = true
}
this.loading = false this.loading = false
this.parsed = true
}).catch((err) => { }).catch((err) => {
this.error = err.data this.error = err.data
this.loading = false this.loading = false
this.parsed = false console.log(err)
this.makeToast(gettext('Error'), gettext('There was an error loading a resource!') + err.bodyText, 'danger')
})
},
loadRecipeManual: function () {
this.error = undefined
this.preview = false
this.loading = true
this.recipe_json['@type'] = "Recipe"
this.$http.post("{% url 'api_recipe_from_source' %}", {'data': JSON.stringify(this.recipe_json), 'auto':'true'}, {emulateJSON: true}).then((response) => {
console.log(response.data)
this.recipe_data = response.data['recipe_json'];
this.loading = false
this.preview = false
}).catch((err) => {
this.error = err.data
this.loading = false
console.log(err) console.log(err)
this.makeToast(gettext('Error'), gettext('There was an error loading a resource!') + err.bodyText, 'danger') this.makeToast(gettext('Error'), gettext('There was an error loading a resource!') + err.bodyText, 'danger')
}) })
@@ -869,16 +841,6 @@
break; break;
} }
}, },
parseIngredient: function(txt) {
this.$http.post('{% url 'api_ingredient_from_string' %}', {text: txt}, {emulateJSON: true}).then((response) => {
console.log(response)
let ing = [response.body.amount, response.body.unit, response.body.food, response.body.note]
return ing
}).catch((err) => {
console.log(err)
this.makeToast(gettext('Error'), gettext('Something went wrong.'), 'danger')
})
}
} }
}); });
</script> </script>

View File

@@ -93,8 +93,7 @@ urlpatterns = [
path('api/log_cooking/<int:recipe_id>/', api.log_cooking, name='api_log_cooking'), path('api/log_cooking/<int:recipe_id>/', api.log_cooking, name='api_log_cooking'),
path('api/plan-ical/<slug:from_date>/<slug:to_date>/', api.get_plan_ical, name='api_get_plan_ical'), path('api/plan-ical/<slug:from_date>/<slug:to_date>/', api.get_plan_ical, name='api_get_plan_ical'),
path('api/recipe-from-url/', api.recipe_from_url, name='api_recipe_from_url'), path('api/recipe-from-url/', api.recipe_from_url, name='api_recipe_from_url'),
path('api/recipe-from-html/', api.manual_recipe_from_json, name='api_manual_recipe_from_json'), path('api/recipe-from-source/', api.recipe_from_source, name='api_recipe_from_source'),
path('api/recipe-from-json/', api.recipe_from_json, name='api_recipe_from_json'),
path('api/backup/', api.get_backup, name='api_backup'), path('api/backup/', api.get_backup, name='api_backup'),
path('api/ingredient-from-string/', api.ingredient_from_string, name='api_ingredient_from_string'), path('api/ingredient-from-string/', api.ingredient_from_string, name='api_ingredient_from_string'),

View File

@@ -35,8 +35,8 @@ from cookbook.helper.permission_helper import (CustomIsAdmin, CustomIsGuest,
CustomIsOwner, CustomIsShare, CustomIsOwner, CustomIsShare,
CustomIsShared, CustomIsUser, CustomIsShared, CustomIsUser,
group_required) group_required)
from cookbook.helper.recipe_url_import import get_from_html, find_recipe_json from cookbook.helper.recipe_html_import import get_recipe_from_source
from cookbook.helper.recipe_html_import import get_from_raw from cookbook.helper.recipe_url_import import get_from_scraper, find_recipe_json
from cookbook.models import (CookLog, Food, Ingredient, Keyword, MealPlan, from cookbook.models import (CookLog, Food, Ingredient, Keyword, MealPlan,
MealType, Recipe, RecipeBook, ShoppingList, MealType, Recipe, RecipeBook, ShoppingList,
ShoppingListEntry, ShoppingListRecipe, Step, ShoppingListEntry, ShoppingListRecipe, Step,
@@ -706,9 +706,11 @@ def recipe_from_url(request):
@group_required('user') @group_required('user')
def manual_recipe_from_json(request): def recipe_from_source(request):
json_data = request.POST['data'] json_data = request.POST['data']
recipe_json, recipe_tree = get_from_raw(json_data, request.space) auto = request.POST['auto']
recipe_json, recipe_tree = get_recipe_from_source(json_data, request.space)
if len(recipe_tree) == 0 and len(recipe_json) == 0: if len(recipe_tree) == 0 and len(recipe_json) == 0:
return JsonResponse( return JsonResponse(
{ {
@@ -718,10 +720,18 @@ def manual_recipe_from_json(request):
status=400 status=400
) )
else: else:
return JsonResponse({ if auto == "true":
'recipe_tree': recipe_tree, return JsonResponse({'recipe_json': recipe_json})
'recipe_json': recipe_json else:
}) # overide keyword structure from dict to list
kws = []
for kw in recipe_json['keywords']:
kws.append(kw['text'])
recipe_json['keywords'] = kws
return JsonResponse({
'recipe_tree': recipe_tree,
'recipe_json': recipe_json
})
@group_required('admin') @group_required('admin')