import raw json/html
This commit is contained in:
parent
8f3f1c230c
commit
55ba568f3c
98
cookbook/helper/recipe_raw_import.py
Normal file
98
cookbook/helper/recipe_raw_import.py
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
|
||||||
|
# %%
|
||||||
|
import json
|
||||||
|
|
||||||
|
from bs4 import BeautifulSoup
|
||||||
|
# from cookbook.helper.ingredient_parser import parse as parse_ingredient
|
||||||
|
from cookbook.helper import recipe_url_import as helper
|
||||||
|
from django.http import JsonResponse
|
||||||
|
from django.utils.dateparse import parse_duration
|
||||||
|
|
||||||
|
|
||||||
|
# %%
|
||||||
|
|
||||||
|
# %%
|
||||||
|
def get_from_raw(raw_text):
|
||||||
|
def build_node(k, v):
|
||||||
|
if isinstance(v, dict):
|
||||||
|
node = {
|
||||||
|
'name': k,
|
||||||
|
'value': k,
|
||||||
|
'children': get_children_dict(v)
|
||||||
|
}
|
||||||
|
elif isinstance(v, list):
|
||||||
|
node = {
|
||||||
|
'name': k,
|
||||||
|
'value': k,
|
||||||
|
'children': get_children_list(v)
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
node = {
|
||||||
|
'name': k + ": " + str(v),
|
||||||
|
'value': str(v)
|
||||||
|
}
|
||||||
|
return node
|
||||||
|
|
||||||
|
def get_children_dict(children):
|
||||||
|
kid_list = []
|
||||||
|
for k, v in children.items():
|
||||||
|
kid_list.append(build_node(k, v))
|
||||||
|
return kid_list
|
||||||
|
|
||||||
|
def get_children_list(children):
|
||||||
|
kid_list = []
|
||||||
|
for kid in children:
|
||||||
|
if type(kid) == list:
|
||||||
|
node = {
|
||||||
|
'name': "unknown list",
|
||||||
|
'value': "unknown list",
|
||||||
|
'children': get_children_list(kid)
|
||||||
|
}
|
||||||
|
kid_list.append(node)
|
||||||
|
elif type(kid) == dict:
|
||||||
|
for k, v in kid.items():
|
||||||
|
kid_list.append(build_node(k, v))
|
||||||
|
else:
|
||||||
|
kid_list.append({
|
||||||
|
'name': kid,
|
||||||
|
'value': kid
|
||||||
|
})
|
||||||
|
return kid_list
|
||||||
|
|
||||||
|
recipe_items = ['recipeIngredient', 'keywords', 'recipeInstructions', 'image',
|
||||||
|
'cookTime', 'prepTime', 'servings', 'name']
|
||||||
|
extra_items = ['recipeYield', 'title', 'recipeCategory', 'recipeCuisine']
|
||||||
|
|
||||||
|
soup = BeautifulSoup(raw_text, "html.parser")
|
||||||
|
recipe_json = {}
|
||||||
|
recipe_tree = []
|
||||||
|
# first try finding ld+json as its most common
|
||||||
|
for ld in soup.find_all('script', type='application/ld+json'):
|
||||||
|
ld_json = helper.find_recipe_json(json.loads(ld.string), '')
|
||||||
|
for item in recipe_items:
|
||||||
|
if item in ld_json:
|
||||||
|
recipe_json[item] = ld_json[item]
|
||||||
|
recipe_items.remove(item)
|
||||||
|
del ld_json[item]
|
||||||
|
for k, v in ld_json.items():
|
||||||
|
if isinstance(v, dict):
|
||||||
|
node = {
|
||||||
|
'name': k,
|
||||||
|
'value': k,
|
||||||
|
'children': get_children_dict(v)
|
||||||
|
}
|
||||||
|
elif isinstance(v, list):
|
||||||
|
node = {
|
||||||
|
'name': k,
|
||||||
|
'value': k,
|
||||||
|
'children': get_children_list(v)
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
node = {
|
||||||
|
'name': k + ": " + str(v),
|
||||||
|
'value': str(v)
|
||||||
|
}
|
||||||
|
recipe_tree.append(node)
|
||||||
|
# TODO put recipe_tree and json_recipe in the JSON response
|
||||||
|
print(recipe_tree)
|
||||||
|
return recipe_json, recipe_tree
|
286
cookbook/templates/import_json.html
Normal file
286
cookbook/templates/import_json.html
Normal file
@ -0,0 +1,286 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
{% load crispy_forms_filters %}
|
||||||
|
{% load i18n %}
|
||||||
|
{% load static %}
|
||||||
|
|
||||||
|
{% block title %}{% trans 'Import Recipe' %}{% endblock %}
|
||||||
|
|
||||||
|
{% block extra_head %}
|
||||||
|
{% include 'include/vue_base.html' %}
|
||||||
|
|
||||||
|
<script src="{% static 'js/vue-multiselect.min.js' %}"></script>
|
||||||
|
<link rel="stylesheet" href="{% static 'css/vue-multiselect.min.css' %}">
|
||||||
|
<script src="{% static 'js/vue-jstree.js' %}"></script>
|
||||||
|
<style>
|
||||||
|
.tree-anchor {
|
||||||
|
width:95%;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div id="app">
|
||||||
|
<div v-if="!parsed">
|
||||||
|
<h2>{% trans 'Import From Source' %}</h2>
|
||||||
|
<div class="input-group input-group-lg">
|
||||||
|
<textarea class="form-control" v-model="raw_recipe" rows="12" style="font-size: 12px"></textarea>
|
||||||
|
</div>
|
||||||
|
<small class="text-muted">Simply paste a web page source or JSON document into this textarea and click import.</small>
|
||||||
|
<br>
|
||||||
|
<button @click="loadRecipe()" class="btn btn-success" type="button"
|
||||||
|
id="id_btn_import"><i class="fas fa-code"></i>{% trans 'Import' %}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<br/>
|
||||||
|
|
||||||
|
<div v-if="loading" class="text-center">
|
||||||
|
<br/>
|
||||||
|
<i class="fas fa-spinner fa-spin fa-8x"></i>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<template v-if="recipe_data !== undefined">
|
||||||
|
<p>this is the recipe form
|
||||||
|
</template>
|
||||||
|
<div class="col" v-if="recipe_tree !== undefined" id="manage_tree">
|
||||||
|
<v-jstree :data="recipe_tree"
|
||||||
|
text-field-name="name"
|
||||||
|
collapse:true
|
||||||
|
draggable>
|
||||||
|
|
||||||
|
<template scope="_">
|
||||||
|
<div class="container-fluid" >
|
||||||
|
<div class="col" @click.ctrl="customItemClickWithCtrl">
|
||||||
|
<div class="row clearfix" style="width:50%" >
|
||||||
|
<div class="col">
|
||||||
|
<i :class="_.vm.themeIconClasses" role="presentation" v-if="!_.model.loading"></i>
|
||||||
|
{% verbatim %}
|
||||||
|
[[_.model.name]]
|
||||||
|
{% endverbatim %}
|
||||||
|
</div>
|
||||||
|
<div class="col-es" style="align-right">
|
||||||
|
<button style="border: 0px; background-color: transparent; cursor: pointer;"
|
||||||
|
@click="deleteNode(_.vm, _.model, $event)"><i class="fas fa-minus-square" style="color:red"></i></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</v-jstree>
|
||||||
|
</div>
|
||||||
|
<template v-if="error !== undefined">
|
||||||
|
<p>something terrible happened
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block script %}
|
||||||
|
|
||||||
|
<script src="{% url 'javascript-catalog' %}"></script>
|
||||||
|
<script type="application/javascript">
|
||||||
|
let csrftoken = Cookies.get('csrftoken');
|
||||||
|
Vue.http.headers.common['X-CSRFToken'] = csrftoken;
|
||||||
|
|
||||||
|
//Vue.component('vue-multiselect', window.VueMultiselect.default)
|
||||||
|
|
||||||
|
let app = new Vue({
|
||||||
|
components: {
|
||||||
|
Multiselect: window.VueMultiselect.default
|
||||||
|
},
|
||||||
|
delimiters: ['[[', ']]'],
|
||||||
|
el: '#app',
|
||||||
|
data: {
|
||||||
|
raw_recipe: '',
|
||||||
|
keywords: [],
|
||||||
|
keywords_loading: false,
|
||||||
|
units: [],
|
||||||
|
units_loading: false,
|
||||||
|
ingredients: [],
|
||||||
|
ingredients_loading: false,
|
||||||
|
recipe_data: undefined,
|
||||||
|
recipe_tree: [],
|
||||||
|
error: undefined,
|
||||||
|
loading: false,
|
||||||
|
all_keywords: false,
|
||||||
|
importing_recipe: false,
|
||||||
|
parsed: false,
|
||||||
|
},
|
||||||
|
directives: {
|
||||||
|
tabindex: {
|
||||||
|
inserted(el) {
|
||||||
|
el.setAttribute('tabindex', 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted: function () {
|
||||||
|
this.searchKeywords('')
|
||||||
|
this.searchUnits('')
|
||||||
|
this.searchIngredients('')
|
||||||
|
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
makeToast: function (title, message, variant = null) {
|
||||||
|
//TODO remove duplicate function in favor of central one
|
||||||
|
this.$bvToast.toast(message, {
|
||||||
|
title: title,
|
||||||
|
variant: variant,
|
||||||
|
toaster: 'b-toaster-top-center',
|
||||||
|
solid: true
|
||||||
|
})
|
||||||
|
},
|
||||||
|
loadRecipe: function () {
|
||||||
|
this.recipe_data = undefined
|
||||||
|
this.recipe_tree = undefined
|
||||||
|
this.error = undefined
|
||||||
|
this.parsed = true
|
||||||
|
this.loading = true
|
||||||
|
this.$http.post("{% url 'api_recipe_from_raw' %}", {'raw_text': this.raw_recipe}, {emulateJSON: true}).then((response) => {
|
||||||
|
console.log(response.data)
|
||||||
|
this.recipe_data = response.data['recipe_data'];
|
||||||
|
this.recipe_tree = response.data['recipe_tree'];
|
||||||
|
this.loading = false
|
||||||
|
}).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.importing_recipe) {
|
||||||
|
this.makeToast(gettext('Error'), gettext('Already importing the selected recipe, please wait!'), 'danger')
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.importing_recipe = true
|
||||||
|
this.$set(this.recipe_data, 'all_keywords', this.all_keywords)
|
||||||
|
this.$http.post(`{% url 'data_import_url' %}`, this.recipe_data).then((response) => {
|
||||||
|
window.location.href = response.data
|
||||||
|
}).catch((err) => {
|
||||||
|
console.log(err);
|
||||||
|
this.makeToast(gettext('Error'), gettext('An error occurred while trying to import this recipe!') + err.bodyText, 'danger')
|
||||||
|
})
|
||||||
|
},
|
||||||
|
deleteIngredient: function (i) {
|
||||||
|
this.recipe_data.recipeIngredient = this.recipe_data.recipeIngredient.filter(item => item !== i)
|
||||||
|
},
|
||||||
|
addIngredient: function (i) {
|
||||||
|
this.recipe_data.recipeIngredient.push({
|
||||||
|
unit: {id: Math.random() * 1000, text: '{{ request.user.userpreference.default_unit }}'},
|
||||||
|
amount: 0,
|
||||||
|
ingredient: {id: Math.random() * 1000, text: ''}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
addIngredientType: function (tag, index) {
|
||||||
|
index = index.replace('ingredient_', '')
|
||||||
|
let new_ingredient = this.recipe_data.recipeIngredient[index]
|
||||||
|
new_ingredient.ingredient = {'id': Math.random() * 1000, 'text': tag}
|
||||||
|
this.ingredients.push(new_ingredient.ingredient)
|
||||||
|
this.recipe_data.recipeIngredient[index] = new_ingredient
|
||||||
|
},
|
||||||
|
addUnitType: function (tag, index) {
|
||||||
|
index = index.replace('unit_', '')
|
||||||
|
let new_unit = this.recipe_data.recipeIngredient[index]
|
||||||
|
new_unit.unit = {'id': Math.random() * 1000, 'text': tag}
|
||||||
|
this.units.push(new_unit.unit)
|
||||||
|
this.recipe_data.recipeIngredient[index] = new_unit
|
||||||
|
},
|
||||||
|
addKeyword: function (tag) {
|
||||||
|
let new_keyword = {'text':tag,'id':null}
|
||||||
|
this.recipe_data.keywords.push(new_keyword)
|
||||||
|
},
|
||||||
|
openUnitSelect: function (id) {
|
||||||
|
let index = id.replace('unit_', '')
|
||||||
|
if (this.recipe_data.recipeIngredient[index].unit !== null) {
|
||||||
|
this.$set(app.$refs.unit[index].$data, 'search', this.recipe_data.recipeIngredient[index].unit.text)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
openIngredientSelect: function (id) {
|
||||||
|
let index = id.replace('ingredient_', '')
|
||||||
|
this.$set(this.$refs.ingredient[index].$data, 'search', this.recipe_data.recipeIngredient[index].ingredient.text)
|
||||||
|
},
|
||||||
|
searchKeywords: function (query) {
|
||||||
|
this.keywords_loading = true
|
||||||
|
this.$http.get("{% url 'dal_keyword' %}" + '?q=' + query).then((response) => {
|
||||||
|
this.keywords = response.data.results;
|
||||||
|
this.keywords_loading = false
|
||||||
|
}).catch((err) => {
|
||||||
|
console.log(err)
|
||||||
|
this.makeToast(gettext('Error'), gettext('There was an error loading a resource!') + err.bodyText, 'danger')
|
||||||
|
})
|
||||||
|
},
|
||||||
|
searchUnits: function (query) {
|
||||||
|
this.units_loading = true
|
||||||
|
this.$http.get("{% url 'dal_unit' %}" + '?q=' + query).then((response) => {
|
||||||
|
this.units = response.data.results;
|
||||||
|
if (this.recipe_data !== undefined) {
|
||||||
|
for (let x of Array.from(this.recipe_data.recipeIngredient)) {
|
||||||
|
if (x.unit !== null && x.unit.text !== '') {
|
||||||
|
this.units = this.units.filter(item => item.text !== x.unit.text)
|
||||||
|
this.units.push(x.unit)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.units_loading = false
|
||||||
|
}).catch((err) => {
|
||||||
|
console.log(err)
|
||||||
|
this.makeToast(gettext('Error'), gettext('There was an error loading a resource!') + err.bodyText, 'danger')
|
||||||
|
})
|
||||||
|
},
|
||||||
|
searchIngredients: function (query) {
|
||||||
|
this.ingredients_loading = true
|
||||||
|
this.$http.get("{% url 'dal_food' %}" + '?q=' + query).then((response) => {
|
||||||
|
this.ingredients = response.data.results
|
||||||
|
if (this.recipe_data !== undefined) {
|
||||||
|
for (let x of Array.from(this.recipe_data.recipeIngredient)) {
|
||||||
|
if (x.ingredient.text !== '') {
|
||||||
|
this.ingredients = this.ingredients.filter(item => item.text !== x.ingredient.text)
|
||||||
|
this.ingredients.push(x.ingredient)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.ingredients_loading = false
|
||||||
|
}).catch((err) => {
|
||||||
|
console.log(err)
|
||||||
|
this.makeToast(gettext('Error'), gettext('There was an error loading a resource!') + err.bodyText, 'danger')
|
||||||
|
})
|
||||||
|
},
|
||||||
|
itemDrop (node, item, draggedItem , e) {
|
||||||
|
var sortBy = function(attr,rev) {
|
||||||
|
if (rev == undefined) {
|
||||||
|
rev = 1;
|
||||||
|
} else {
|
||||||
|
rev = (rev) ? 1 : -1;
|
||||||
|
}
|
||||||
|
return function (a, b) {
|
||||||
|
a = a[attr];
|
||||||
|
b = b[attr];
|
||||||
|
if (a < b) {
|
||||||
|
return rev * -1;
|
||||||
|
}
|
||||||
|
if (a > b) {
|
||||||
|
return rev * 1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
item.children.sort(sortBy('text', true))
|
||||||
|
this.$refs.tree.handleRecursionNodeChildren(draggedItem, function (childrenItem) {
|
||||||
|
childrenItem.selected = item.selected
|
||||||
|
})
|
||||||
|
console.log(node.model.text + ' drop !')
|
||||||
|
},
|
||||||
|
deleteNode: function (node ,item, e) {
|
||||||
|
e.stopPropagation()
|
||||||
|
var index = node.parentItem.indexOf(item)
|
||||||
|
node.parentItem.splice(index, 1)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{% endblock %}
|
384
cookbook/templates/import_json_working.html
Normal file
384
cookbook/templates/import_json_working.html
Normal file
@ -0,0 +1,384 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
{% load crispy_forms_filters %}
|
||||||
|
{% load i18n %}
|
||||||
|
{% load static %}
|
||||||
|
|
||||||
|
{% block title %}{% trans 'Import Recipe' %}{% endblock %}
|
||||||
|
|
||||||
|
{% block extra_head %}
|
||||||
|
{% include 'include/vue_base.html' %}
|
||||||
|
|
||||||
|
<script src="{% static 'js/vue-multiselect.min.js' %}"></script>
|
||||||
|
<link rel="stylesheet" href="{% static 'css/vue-multiselect.min.css' %}">
|
||||||
|
<script src="{% static 'js/vue-jstree.js' %}"></script>
|
||||||
|
<style>
|
||||||
|
.tree-anchor {
|
||||||
|
width:95%;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div id="app">
|
||||||
|
<div v-if="!parsed">
|
||||||
|
<h2>{% trans 'Import From Source' %}</h2>
|
||||||
|
<div class="input-group input-group-lg">
|
||||||
|
<textarea class="form-control" v-model="raw_recipe" rows="12" style="font-size: 12px"></textarea>
|
||||||
|
</div>
|
||||||
|
<small class="text-muted">Simply paste a web page source or JSON document into this textarea and click import.</small>
|
||||||
|
<br>
|
||||||
|
<button @click="loadRecipe()" class="btn btn-success" type="button"
|
||||||
|
id="id_btn_import"><i class="fas fa-code"></i>{% trans 'Import' %}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<br/>
|
||||||
|
|
||||||
|
<div v-if="loading" class="text-center">
|
||||||
|
<br/>
|
||||||
|
<i class="fas fa-spinner fa-spin fa-8x"></i>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<template v-if="recipe_data !== undefined">
|
||||||
|
<p>this is the recipe form
|
||||||
|
</template>
|
||||||
|
<div v-if="recipe_tree !== undefined" id="manage_tree">
|
||||||
|
<v-jstree :data="recipe_tree"
|
||||||
|
whole-row
|
||||||
|
collapse:true
|
||||||
|
draggable
|
||||||
|
@item-click="itemClick"
|
||||||
|
@item-drag-start="itemDragStart"
|
||||||
|
@item-drag-end="itemDragEnd"
|
||||||
|
@item-drop-before = "itemDropBefore"
|
||||||
|
@item-drop="itemDrop">
|
||||||
|
|
||||||
|
<template scope="_">
|
||||||
|
<div class="container-fluid" >
|
||||||
|
<div @click.ctrl="customItemClickWithCtrl">
|
||||||
|
<div class="row clearfix" style="width:80%; cursor: grab;" >
|
||||||
|
<i :class="_.vm.themeIconClasses" role="presentation" v-if="!_.model.loading"></i>
|
||||||
|
{% verbatim %}
|
||||||
|
[[_.model.name]]
|
||||||
|
{% endverbatim %}
|
||||||
|
<button style="border: 0px; background-color: transparent; cursor: pointer;"
|
||||||
|
@click="deleteNode(_.vm, _.model, $event)"><i class="fa fa-remove"></i></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</v-jstree>
|
||||||
|
</div>
|
||||||
|
<template v-if="error !== undefined">
|
||||||
|
<p>something terrible happened
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block script %}
|
||||||
|
|
||||||
|
<script src="{% url 'javascript-catalog' %}"></script>
|
||||||
|
<script type="application/javascript">
|
||||||
|
let csrftoken = Cookies.get('csrftoken');
|
||||||
|
Vue.http.headers.common['X-CSRFToken'] = csrftoken;
|
||||||
|
|
||||||
|
//Vue.component('vue-multiselect', window.VueMultiselect.default)
|
||||||
|
|
||||||
|
let app = new Vue({
|
||||||
|
components: {
|
||||||
|
Multiselect: window.VueMultiselect.default
|
||||||
|
},
|
||||||
|
delimiters: ['[[', ']]'],
|
||||||
|
el: '#app',
|
||||||
|
data: {
|
||||||
|
raw_recipe: '',
|
||||||
|
keywords: [],
|
||||||
|
keywords_loading: false,
|
||||||
|
units: [],
|
||||||
|
units_loading: false,
|
||||||
|
ingredients: [],
|
||||||
|
ingredients_loading: false,
|
||||||
|
recipe_data: undefined,
|
||||||
|
recipe_tree: [],
|
||||||
|
error: undefined,
|
||||||
|
loading: false,
|
||||||
|
all_keywords: false,
|
||||||
|
importing_recipe: false,
|
||||||
|
parsed: false,
|
||||||
|
searchText: '',
|
||||||
|
editingItem: {},
|
||||||
|
editingNode: null,
|
||||||
|
itemEvents: {
|
||||||
|
mouseover: function () {
|
||||||
|
console.log('mouseover')
|
||||||
|
},
|
||||||
|
contextmenu: function () {
|
||||||
|
console.log(arguments[2])
|
||||||
|
arguments[2].preventDefault()
|
||||||
|
console.log('contextmenu')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
directives: {
|
||||||
|
tabindex: {
|
||||||
|
inserted(el) {
|
||||||
|
el.setAttribute('tabindex', 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted: function () {
|
||||||
|
this.searchKeywords('')
|
||||||
|
this.searchUnits('')
|
||||||
|
this.searchIngredients('')
|
||||||
|
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
makeToast: function (title, message, variant = null) {
|
||||||
|
//TODO remove duplicate function in favor of central one
|
||||||
|
this.$bvToast.toast(message, {
|
||||||
|
title: title,
|
||||||
|
variant: variant,
|
||||||
|
toaster: 'b-toaster-top-center',
|
||||||
|
solid: true
|
||||||
|
})
|
||||||
|
},
|
||||||
|
loadRecipe: function () {
|
||||||
|
this.recipe_data = undefined
|
||||||
|
this.recipe_tree = undefined
|
||||||
|
this.error = undefined
|
||||||
|
this.parsed = true
|
||||||
|
this.loading = true
|
||||||
|
this.$http.post("{% url 'api_recipe_from_raw' %}", {'raw_text': this.raw_recipe}, {emulateJSON: true}).then((response) => {
|
||||||
|
console.log(response.data)
|
||||||
|
this.recipe_data = response.data['recipe_data'];
|
||||||
|
this.recipe_tree = response.data['recipe_tree'];
|
||||||
|
this.loading = false
|
||||||
|
}).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.importing_recipe) {
|
||||||
|
this.makeToast(gettext('Error'), gettext('Already importing the selected recipe, please wait!'), 'danger')
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.importing_recipe = true
|
||||||
|
this.$set(this.recipe_data, 'all_keywords', this.all_keywords)
|
||||||
|
this.$http.post(`{% url 'data_import_url' %}`, this.recipe_data).then((response) => {
|
||||||
|
window.location.href = response.data
|
||||||
|
}).catch((err) => {
|
||||||
|
console.log(err);
|
||||||
|
this.makeToast(gettext('Error'), gettext('An error occurred while trying to import this recipe!') + err.bodyText, 'danger')
|
||||||
|
})
|
||||||
|
},
|
||||||
|
deleteIngredient: function (i) {
|
||||||
|
this.recipe_data.recipeIngredient = this.recipe_data.recipeIngredient.filter(item => item !== i)
|
||||||
|
},
|
||||||
|
addIngredient: function (i) {
|
||||||
|
this.recipe_data.recipeIngredient.push({
|
||||||
|
unit: {id: Math.random() * 1000, text: '{{ request.user.userpreference.default_unit }}'},
|
||||||
|
amount: 0,
|
||||||
|
ingredient: {id: Math.random() * 1000, text: ''}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
addIngredientType: function (tag, index) {
|
||||||
|
index = index.replace('ingredient_', '')
|
||||||
|
let new_ingredient = this.recipe_data.recipeIngredient[index]
|
||||||
|
new_ingredient.ingredient = {'id': Math.random() * 1000, 'text': tag}
|
||||||
|
this.ingredients.push(new_ingredient.ingredient)
|
||||||
|
this.recipe_data.recipeIngredient[index] = new_ingredient
|
||||||
|
},
|
||||||
|
addUnitType: function (tag, index) {
|
||||||
|
index = index.replace('unit_', '')
|
||||||
|
let new_unit = this.recipe_data.recipeIngredient[index]
|
||||||
|
new_unit.unit = {'id': Math.random() * 1000, 'text': tag}
|
||||||
|
this.units.push(new_unit.unit)
|
||||||
|
this.recipe_data.recipeIngredient[index] = new_unit
|
||||||
|
},
|
||||||
|
addKeyword: function (tag) {
|
||||||
|
let new_keyword = {'text':tag,'id':null}
|
||||||
|
this.recipe_data.keywords.push(new_keyword)
|
||||||
|
},
|
||||||
|
openUnitSelect: function (id) {
|
||||||
|
let index = id.replace('unit_', '')
|
||||||
|
if (this.recipe_data.recipeIngredient[index].unit !== null) {
|
||||||
|
this.$set(app.$refs.unit[index].$data, 'search', this.recipe_data.recipeIngredient[index].unit.text)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
openIngredientSelect: function (id) {
|
||||||
|
let index = id.replace('ingredient_', '')
|
||||||
|
this.$set(this.$refs.ingredient[index].$data, 'search', this.recipe_data.recipeIngredient[index].ingredient.text)
|
||||||
|
},
|
||||||
|
searchKeywords: function (query) {
|
||||||
|
this.keywords_loading = true
|
||||||
|
this.$http.get("{% url 'dal_keyword' %}" + '?q=' + query).then((response) => {
|
||||||
|
this.keywords = response.data.results;
|
||||||
|
this.keywords_loading = false
|
||||||
|
}).catch((err) => {
|
||||||
|
console.log(err)
|
||||||
|
this.makeToast(gettext('Error'), gettext('There was an error loading a resource!') + err.bodyText, 'danger')
|
||||||
|
})
|
||||||
|
},
|
||||||
|
searchUnits: function (query) {
|
||||||
|
this.units_loading = true
|
||||||
|
this.$http.get("{% url 'dal_unit' %}" + '?q=' + query).then((response) => {
|
||||||
|
this.units = response.data.results;
|
||||||
|
if (this.recipe_data !== undefined) {
|
||||||
|
for (let x of Array.from(this.recipe_data.recipeIngredient)) {
|
||||||
|
if (x.unit !== null && x.unit.text !== '') {
|
||||||
|
this.units = this.units.filter(item => item.text !== x.unit.text)
|
||||||
|
this.units.push(x.unit)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.units_loading = false
|
||||||
|
}).catch((err) => {
|
||||||
|
console.log(err)
|
||||||
|
this.makeToast(gettext('Error'), gettext('There was an error loading a resource!') + err.bodyText, 'danger')
|
||||||
|
})
|
||||||
|
},
|
||||||
|
searchIngredients: function (query) {
|
||||||
|
this.ingredients_loading = true
|
||||||
|
this.$http.get("{% url 'dal_food' %}" + '?q=' + query).then((response) => {
|
||||||
|
this.ingredients = response.data.results
|
||||||
|
if (this.recipe_data !== undefined) {
|
||||||
|
for (let x of Array.from(this.recipe_data.recipeIngredient)) {
|
||||||
|
if (x.ingredient.text !== '') {
|
||||||
|
this.ingredients = this.ingredients.filter(item => item.text !== x.ingredient.text)
|
||||||
|
this.ingredients.push(x.ingredient)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.ingredients_loading = false
|
||||||
|
}).catch((err) => {
|
||||||
|
console.log(err)
|
||||||
|
this.makeToast(gettext('Error'), gettext('There was an error loading a resource!') + err.bodyText, 'danger')
|
||||||
|
})
|
||||||
|
},
|
||||||
|
itemClick (node) {
|
||||||
|
this.editingNode = node
|
||||||
|
this.editingItem = node.model
|
||||||
|
console.log(node.model.text + ' clicked !')
|
||||||
|
},
|
||||||
|
itemDragStart (node) {
|
||||||
|
console.log(node.model.text + ' drag start !')
|
||||||
|
},
|
||||||
|
itemDragEnd (node) {
|
||||||
|
console.log(node.model.text + ' drag end !')
|
||||||
|
},
|
||||||
|
itemDropBefore (node, item, draggedItem , e) {
|
||||||
|
if (!draggedItem) {
|
||||||
|
item.addChild({
|
||||||
|
text: "newNode",
|
||||||
|
value: "newNode"
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
itemDrop (node, item, draggedItem , e) {
|
||||||
|
var sortBy = function(attr,rev) {
|
||||||
|
if (rev == undefined) {
|
||||||
|
rev = 1;
|
||||||
|
} else {
|
||||||
|
rev = (rev) ? 1 : -1;
|
||||||
|
}
|
||||||
|
return function (a, b) {
|
||||||
|
a = a[attr];
|
||||||
|
b = b[attr];
|
||||||
|
if (a < b) {
|
||||||
|
return rev * -1;
|
||||||
|
}
|
||||||
|
if (a > b) {
|
||||||
|
return rev * 1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
item.children.sort(sortBy('text', true))
|
||||||
|
this.$refs.tree.handleRecursionNodeChildren(draggedItem, function (childrenItem) {
|
||||||
|
childrenItem.selected = item.selected
|
||||||
|
})
|
||||||
|
console.log(node.model.text + ' drop !')
|
||||||
|
},
|
||||||
|
inputKeyUp: function () {
|
||||||
|
var text = this.searchText
|
||||||
|
const patt = new RegExp(text);
|
||||||
|
this.$refs.tree.handleRecursionNodeChilds(this.$refs.tree, function (node) {
|
||||||
|
if (text !== '' && node.model !== undefined) {
|
||||||
|
const str = node.model.text
|
||||||
|
if (patt.test(str)) {
|
||||||
|
node.$el.querySelector('.tree-anchor').style.color = 'red'
|
||||||
|
} else {
|
||||||
|
node.$el.querySelector('.tree-anchor').style.color = '#000'
|
||||||
|
} // or other operations
|
||||||
|
} else {
|
||||||
|
node.$el.querySelector('.tree-anchor').style.color = '#000'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
addChildNode: function () {
|
||||||
|
if (this.editingItem.id !== undefined) {
|
||||||
|
this.editingItem.addChild({
|
||||||
|
text: "newNode",
|
||||||
|
value: "newNode"
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
removeNode: function () {
|
||||||
|
if (this.editingItem.id !== undefined) {
|
||||||
|
var index = this.editingNode.parentItem.indexOf(this.editingItem)
|
||||||
|
this.editingNode.parentItem.splice(index, 1)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
addBeforeNode: function () {
|
||||||
|
if (this.editingItem.id !== undefined) {
|
||||||
|
this.editingItem.addBefore({
|
||||||
|
text: "newNode",
|
||||||
|
value: "newNode"
|
||||||
|
}, this.editingNode)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
addAfterNode: function () {
|
||||||
|
if (this.editingItem.id !== undefined) {
|
||||||
|
this.editingItem.addAfter({
|
||||||
|
text: "newNode",
|
||||||
|
value: "newNode"
|
||||||
|
}, this.editingNode)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
openChildren: function () {
|
||||||
|
if (this.editingItem.id !== undefined) {
|
||||||
|
this.editingItem.openChildren()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
closeChildren: function () {
|
||||||
|
if (this.editingItem.id !== undefined) {
|
||||||
|
this.editingItem.closeChildren()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
refreshNode: function () {
|
||||||
|
this.asyncData = [
|
||||||
|
this.$refs.tree2.initializeLoading()
|
||||||
|
]
|
||||||
|
this.$refs.tree2.handleAsyncLoad(this.asyncData, this.$refs.tree2)
|
||||||
|
},
|
||||||
|
customItemClick: function (node ,item, e) {
|
||||||
|
e.stopPropagation()
|
||||||
|
var index = node.parentItem.indexOf(item)
|
||||||
|
node.parentItem.splice(index, 1)
|
||||||
|
},
|
||||||
|
customItemClickWithCtrl: function () {
|
||||||
|
console.log('click + ctrl')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{% endblock %}
|
@ -59,7 +59,7 @@ urlpatterns = [
|
|||||||
path('test2/', views.test2, name='view_test2'),
|
path('test2/', views.test2, name='view_test2'),
|
||||||
|
|
||||||
path('import/', import_export.import_recipe, name='view_import'),
|
path('import/', import_export.import_recipe, name='view_import'),
|
||||||
path('import-response/<int:pk>/', import_export.import_response, name='view_import_response'),
|
path('import/json/', import_export.import_json, name='view_json_import'),
|
||||||
path('export/', import_export.export_recipe, name='view_export'),
|
path('export/', import_export.export_recipe, name='view_export'),
|
||||||
|
|
||||||
path('view/recipe/<int:pk>', views.recipe_view, name='view_recipe'),
|
path('view/recipe/<int:pk>', views.recipe_view, name='view_recipe'),
|
||||||
@ -94,7 +94,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-json/', api.recipe_from_json, name='api_recipe_from_json'),
|
path('api/recipe-from-raw/', api.recipe_from_raw, name='api_recipe_from_raw'),
|
||||||
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'),
|
||||||
|
|
||||||
|
@ -35,7 +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_url_import import get_from_html
|
||||||
|
from cookbook.helper.recipe_raw_import import get_from_raw
|
||||||
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,
|
||||||
@ -696,6 +697,16 @@ def recipe_from_url(request):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@group_required('user')
|
||||||
|
def recipe_from_raw(request):
|
||||||
|
raw_text = request.POST['raw_text']
|
||||||
|
recipe_json, recipe_tree = get_from_raw(raw_text)
|
||||||
|
return JsonResponse({
|
||||||
|
'recipe_tree': recipe_tree,
|
||||||
|
'recipe_json': recipe_json
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
@group_required('admin')
|
@group_required('admin')
|
||||||
def get_backup(request):
|
def get_backup(request):
|
||||||
if not request.user.is_superuser:
|
if not request.user.is_superuser:
|
||||||
|
@ -109,5 +109,10 @@ def export_recipe(request):
|
|||||||
|
|
||||||
|
|
||||||
@group_required('user')
|
@group_required('user')
|
||||||
def import_response(request, pk):
|
def import_json(request):
|
||||||
return render(request, 'import_response.html', {'pk': pk})
|
if request.method == "POST":
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
pass
|
||||||
|
|
||||||
|
return render(request, 'import_json.html')
|
||||||
|
Loading…
Reference in New Issue
Block a user