combined json import and source import
This commit is contained in:
parent
4015517c0a
commit
40a2f7ff90
@ -7,10 +7,7 @@ from bs4.element import Tag
|
||||
from cookbook.helper import recipe_url_import as helper
|
||||
|
||||
|
||||
# %%
|
||||
|
||||
# %%
|
||||
def get_from_raw(text, space):
|
||||
def get_recipe_from_source(text, space):
|
||||
def build_node(k, v):
|
||||
if isinstance(v, dict):
|
||||
node = {
|
||||
@ -113,17 +110,20 @@ def get_from_raw(text, space):
|
||||
if '@graph' in el:
|
||||
for x in el['@graph']:
|
||||
if '@type' in x and x['@type'] == 'Recipe':
|
||||
recipe_json = helper.find_recipe_json(x, None, space)
|
||||
recipe_tree += [{'name': 'ld+json', 'children': temp_tree}]
|
||||
el = x
|
||||
|
||||
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:
|
||||
recipe_tree += [{'name': 'json', 'children': 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
|
||||
|
||||
|
||||
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))
|
@ -15,7 +15,7 @@ from django.utils.translation import gettext as _
|
||||
from recipe_scrapers import _utils
|
||||
|
||||
|
||||
def get_from_html(html_text, url, space):
|
||||
def get_from_html_old(html_text, url, space):
|
||||
soup = BeautifulSoup(html_text, "html.parser")
|
||||
|
||||
# first try finding ld+json as its most common
|
||||
|
@ -25,72 +25,54 @@
|
||||
<h2> {% trans 'Import' %} </h2>
|
||||
<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" href="#nav-ldjson" data-toggle="tab" role="tab" aria-controls="nav-ldjson">ld+json</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-html" data-toggle="tab" role="tab" aria-controls="nav-html">HTML</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>
|
||||
<a class="nav-link" href="#nav-source" data-toggle="tab" role="tab" aria-controls="nav-source">Source</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-file" data-toggle="tab" role="tab" aria-controls="nav-file">File</a>
|
||||
</nav>
|
||||
|
||||
|
||||
<div class="tab-content" id="nav-tabContent">
|
||||
<!-- Import URL -->
|
||||
<div class="row tab-pane fade show active" id="nav-url" role="tabpanel">
|
||||
<div class="col-md-12">
|
||||
<div class="input-group mb-3">
|
||||
<input class="form-control" v-model="remote_url" placeholder="{% trans 'Enter website URL' %}">
|
||||
<div class="input-group-append">
|
||||
<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>
|
||||
<div class="tab-pane fade show active" id="nav-url" role="tabpanel">
|
||||
<div class="btn-group btn-group-toggle" data-toggle="buttons">
|
||||
<label class="btn btn-outline-info btn-sm active" @click="automatic=true">
|
||||
<input type="radio" name="auto" id="auto" autocomplete="off" checked> Automatic
|
||||
</label>
|
||||
|
||||
<!-- Automatically import LD+JSON -->
|
||||
<div class="row tab-pane fade show" id="nav-ldjson" 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 'Paste ld+json here to parse recipe automatically.' %}" style="font-size: 12px">
|
||||
</textarea>
|
||||
</div>
|
||||
<br>
|
||||
<button @click="loadRecipeJson()" class="btn btn-primary shadow-none" type="button"
|
||||
id="id_btn_json"><i class="fas fa-code"></i> {% trans 'Import' %}
|
||||
<label class="btn btn-outline-info btn-sm disabled" @click="automatic=false">
|
||||
<input type="radio" name="auto" id="manual" autocomplete="off"> Manual
|
||||
</label>
|
||||
</div>
|
||||
<div class="input-group mb-3">
|
||||
<input class="form-control" v-model="remote_url" placeholder="{% trans 'Enter website URL' %}">
|
||||
<div class="input-group-append">
|
||||
<button @click="loadRecipe()" class="btn btn-primary shadow-none" type="button"
|
||||
id="id_btn_search"><i class="fas fa-search"></i>
|
||||
</button>
|
||||
</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>
|
||||
|
||||
<!-- Manually import from HTML -->
|
||||
<div class="row tab-pane fade show" id="nav-html" 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 'Paste html source here to parse recipe manually.' %}" style="font-size: 12px">
|
||||
</textarea>
|
||||
</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>
|
||||
<!-- Import JSON or HTML -->
|
||||
<div class=" tab-pane fade show" id="nav-source" role="tabpanel">
|
||||
<div class="btn-group btn-group-toggle" data-toggle="buttons">
|
||||
<label class="btn btn-outline-info btn-sm active" @click="automatic=true">
|
||||
<input type="radio" name="auto" id="auto" autocomplete="off" checked> Automatic
|
||||
</label>
|
||||
|
||||
<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>
|
||||
|
||||
<br/>
|
||||
@ -101,9 +83,7 @@
|
||||
</div>
|
||||
|
||||
<!-- recipe preview before Import -->
|
||||
<div class="container-fluid" v-if="parsed" id="manage_tree">
|
||||
|
||||
<h2></h2>
|
||||
<div class="container-fluid" v-if="preview" id="manage_tree">
|
||||
<div class="row">
|
||||
<div class="col" style="max-width:50%">
|
||||
<div class="card card-border-primary" >
|
||||
@ -116,6 +96,7 @@
|
||||
<div class="card mb-2">
|
||||
<div class="card-header" style="display:flex; justify-content:space-between;">
|
||||
{% 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>
|
||||
</div>
|
||||
|
||||
@ -232,11 +213,13 @@
|
||||
</div>
|
||||
</div>
|
||||
<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' %}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- start of json tree -->
|
||||
<div class="col" style="max-width:50%">
|
||||
<div class="card card-border-primary">
|
||||
<div class="card-header">
|
||||
@ -274,9 +257,9 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- end of json tree -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- end of recipe preview before Import -->
|
||||
|
||||
<template v-if="recipe_data !== undefined">
|
||||
@ -518,7 +501,7 @@
|
||||
el: '#app',
|
||||
data: {
|
||||
remote_url: '',
|
||||
raw_data: undefined,
|
||||
source_data: undefined,
|
||||
keywords: [],
|
||||
keywords_loading: false,
|
||||
units: [],
|
||||
@ -528,11 +511,12 @@
|
||||
recipe_data: undefined,
|
||||
error: undefined,
|
||||
loading: false,
|
||||
parsed: false,
|
||||
preview: false,
|
||||
all_keywords: false,
|
||||
importing_recipe: false,
|
||||
recipe_json: undefined,
|
||||
recipe_tree: undefined,
|
||||
automatic: true
|
||||
},
|
||||
directives: {
|
||||
tabindex: {
|
||||
@ -561,6 +545,9 @@
|
||||
this.recipe_data = undefined
|
||||
this.error = undefined
|
||||
this.loading = true
|
||||
if (this.automatic) {
|
||||
console.log('true')
|
||||
}
|
||||
this.$http.post("{% url 'api_recipe_from_url' %}", {'url': this.remote_url}, {emulateJSON: true}).then((response) => {
|
||||
console.log(response.data)
|
||||
this.recipe_data = response.data;
|
||||
@ -578,13 +565,22 @@
|
||||
this.makeToast(gettext('Error'), msg, 'danger')
|
||||
})
|
||||
},
|
||||
loadRecipeJson: function () {
|
||||
loadSource: function() {
|
||||
this.recipe_data = undefined
|
||||
this.recipe_json = undefined
|
||||
this.recipe_tree = undefined
|
||||
this.error = undefined
|
||||
this.loading = true
|
||||
this.$http.post("{% url 'api_recipe_from_json' %}", {'json': 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)
|
||||
this.recipe_data = response.data;
|
||||
if (this.automatic) {
|
||||
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
|
||||
}).catch((err) => {
|
||||
this.error = err.data
|
||||
@ -599,15 +595,16 @@
|
||||
this.makeToast(gettext('Error'), msg, 'danger')
|
||||
})
|
||||
},
|
||||
loadRecipeRaw: function () {
|
||||
loadRecipeManual: function () {
|
||||
this.error = undefined
|
||||
this.preview = false
|
||||
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) => {
|
||||
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;
|
||||
this.recipe_data = response.data['recipe_json'];
|
||||
this.loading = false
|
||||
this.preview = false
|
||||
}).catch((err) => {
|
||||
this.error = err.data
|
||||
this.loading = false
|
||||
@ -615,41 +612,6 @@
|
||||
this.makeToast(gettext('Error'), gettext('There was an error loading a resource!') + err.bodyText, 'danger')
|
||||
})
|
||||
},
|
||||
loadRecipeJson: function () {
|
||||
this.recipe_data = undefined
|
||||
this.error = undefined
|
||||
this.loading = true
|
||||
this.$http.post("{% url 'api_recipe_from_json' %}", {'json': this.json_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')
|
||||
})
|
||||
},
|
||||
loadPreviewRaw: function () {
|
||||
this.recipe_json = undefined
|
||||
this.recipe_tree = undefined
|
||||
this.error = undefined
|
||||
|
||||
this.loading = true
|
||||
this.$http.post("{% url 'api_manual_recipe_from_json' %}", {'data': this.raw_data}, {emulateJSON: true}).then((response) => {
|
||||
console.log(response.data)
|
||||
this.recipe_json = response.data['recipe_json'];
|
||||
this.recipe_tree = response.data['recipe_tree'];
|
||||
this.loading = false
|
||||
this.parsed = true
|
||||
}).catch((err) => {
|
||||
this.error = err.data
|
||||
this.loading = false
|
||||
this.parsed = false
|
||||
console.log(err)
|
||||
this.makeToast(gettext('Error'), gettext('There was an error loading a resource!') + err.bodyText, 'danger')
|
||||
})
|
||||
},
|
||||
importRecipe: function () {
|
||||
if (this.recipe_data.name.length > 128) {
|
||||
this.makeToast(gettext('Error'), gettext('Recipe name is longer than 128 characters'), 'danger')
|
||||
@ -852,16 +814,6 @@
|
||||
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>
|
||||
|
@ -92,9 +92,8 @@ urlpatterns = [
|
||||
path('api/sync_all/', api.sync_all, name='api_sync'),
|
||||
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/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-json/', api.recipe_from_json, name='api_recipe_from_json'),
|
||||
path('api/recipe-from-url/', api.recipe_from_url, name='api_recipe_from_url'),
|
||||
path('api/recipe-from-source/', api.recipe_from_source, name='api_recipe_from_source'),
|
||||
path('api/backup/', api.get_backup, name='api_backup'),
|
||||
path('api/ingredient-from-string/', api.ingredient_from_string, name='api_ingredient_from_string'),
|
||||
|
||||
|
@ -28,8 +28,8 @@ from cookbook.helper.permission_helper import (CustomIsAdmin, CustomIsGuest,
|
||||
CustomIsOwner, CustomIsShare,
|
||||
CustomIsShared, CustomIsUser,
|
||||
group_required)
|
||||
from cookbook.helper.recipe_url_import import get_from_html, find_recipe_json
|
||||
from cookbook.helper.recipe_html_import import get_from_raw
|
||||
from cookbook.helper.recipe_html_import import get_recipe_from_source
|
||||
from cookbook.helper.recipe_url_import import get_from_scraper, find_recipe_json
|
||||
from cookbook.models import (CookLog, Food, Ingredient, Keyword, MealPlan,
|
||||
MealType, Recipe, RecipeBook, ShoppingList,
|
||||
ShoppingListEntry, ShoppingListRecipe, Step,
|
||||
@ -616,9 +616,11 @@ def recipe_from_url_old(request):
|
||||
|
||||
|
||||
@group_required('user')
|
||||
def manual_recipe_from_json(request):
|
||||
def recipe_from_source(request):
|
||||
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:
|
||||
return JsonResponse(
|
||||
{
|
||||
@ -628,10 +630,18 @@ def manual_recipe_from_json(request):
|
||||
status=400
|
||||
)
|
||||
else:
|
||||
return JsonResponse({
|
||||
'recipe_tree': recipe_tree,
|
||||
'recipe_json': recipe_json
|
||||
})
|
||||
if auto == "true":
|
||||
return JsonResponse({'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')
|
||||
|
Loading…
Reference in New Issue
Block a user