recipe url import ld json

This commit is contained in:
vabene1111 2020-06-22 21:16:31 +02:00
parent 743d7bf608
commit 9e748552b2
5 changed files with 259 additions and 13 deletions

View File

@ -1,10 +1,177 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>$Title$</title>
</head>
<body>
$END$
</body>
</html>
{% extends "base.html" %}
{% load i18n %}
{% load static %}
{% block title %}{% trans 'URL Import' %}{% endblock %}
{% block extra_head %}
{% include 'include/vue_base.html' %}
<script src="https://unpkg.com/vue-multiselect@2.1.0"></script>
<link rel="stylesheet" href="https://unpkg.com/vue-multiselect@2.1.0/dist/vue-multiselect.min.css">
{% endblock %}
{% block content %}
<div id="app">
<div class="row">
<div class="col-md-12">
<div class="input-group mb-3">
<input class="form-control" v-model="remote_url">
<div class="input-group-append">
<button @click="loadRecipe()" class="btn btn-outline-secondary" type="button"
id="button-addon2">Search
</button>
</div>
</div>
</div>
</div>
<br/>
<template v-if="recipe_data !== undefined">
<form>
<div class="form-group">
<label for="id_name">{% trans 'Recipe Name' %}</label>
<input id="id_name" class="form-control" v-model="recipe_data.name">
</div>
<div class="row">
<div class="col col-md-6">
<img v-bind:src="recipe_data.image" alt="{% trans 'Recipe Image' %}"
class="img-fluid img-responsive img-rounded">
</div>
<div>
<div class="form-group">
<label for="id_prep_time">{% trans 'Preparation time ca.' %}</label>
<input id="id_prep_time" class="form-control" v-model="recipe_data.prepTime">
</div>
<div class="form-group">
<label for="id_waiting_time">{% trans 'Waiting time ca.' %}</label>
<input id="id_waiting_time" class="form-control" v-model="recipe_data.cookTime">
</div>
</div>
</div>
<br/>
<div class="row">
<div class="col-md-12">
<table class="table table-responsive-sm table-sm">
<thead>
<tr>
<th>{% trans 'Amount' %}</th>
<th>{% trans 'Unit' %}</th>
<th>{% trans 'Ingredient' %}</th>
</tr>
</thead>
<tbody>
<tr v-for="i in recipe_data.recipeIngredient">
<td><input class="form-control" v-model="i.amount"></td>
<td><input class="form-control" v-model="i.unit"></td>
<td><input class="form-control" v-model="i.ingredient"></td>
</tr>
</tbody>
</table>
</div>
</div>
<div class="form-group">
<label for="id_instructions">{% trans 'Instructions' %}</label>
<textarea id="id_instructions" class="form-control" v-model="recipe_data.recipeInstructions"
rows="8"></textarea>
</div>
<div class="form-group">
<label for="id_keywords">{% trans 'Keywords' %}</label>
<multiselect
v-model="recipe_data.keywords"
:options="keywords"
:close-on-select="false"
:clear-on-select="true"
:hide-selected="true"
:preserve-search="true"
placeholder="Pick some"
label="text"
track-by="id"
id="id_keywords"
:multiple="true">
</multiselect>
</div>
</form>
</template>
[[recipe_data]]
</div>
<br/>
<br/>
<br/>
<br/>
<br/>
<br/>
<br/>
<br/>
<br/>
<script type="application/javascript">
let csrftoken = Cookies.get('csrftoken');
Vue.http.headers.common['X-CSRFToken'] = csrftoken;
Vue.component('vue-multiselect', window.VueMultiselect.default)
// micro data examples
// https://www.inspirationforall.de/pudding-selber-machen-vanillepudding-schokopudding-rezept/
// https://www.ichkoche.at/schokopudding-rezept-218012
// https://www.gutekueche.de/mamis-feiner-schokopudding-rezept-4274
// https://www.maizena.at/rezepte/schokopudding/13534
// https://kochkino.de/schokoladen-pudding/2159
// https://www.oetker.de/rezepte/r/schokopudding-mit-vanille-herzen
let app = new Vue({
components: {
Multiselect: window.VueMultiselect.default
},
delimiters: ['[[', ']]'],
el: '#app',
data: {
remote_url: 'https://www.chefkoch.de/rezepte/1716851280413039/Einfacher-Zwiebelkuchen.html',
keywords: [],
recipe_data: undefined,
},
mounted: function () {
this.loadRecipe();
this.getKeywords();
},
methods: {
loadRecipe: function () {
this.$http.get("{% url 'api_recipe_from_url' 12345 %}".replace(/12345/, this.remote_url)).then((response) => {
this.recipe_data = response.data;
}).catch((err) => {
console.log(err)
})
},
getKeywords: function () {
this.$http.get("{% url 'dal_keyword' %}").then((response) => {
this.keywords = response.data.results;
}).catch((err) => {
console.log(err)
})
}
}
});
</script>
{% endblock %}

View File

@ -53,12 +53,14 @@ urlpatterns = [
path('data/batch/import', data.batch_import, name='data_batch_import'),
path('data/sync/wait', data.sync_wait, name='data_sync_wait'),
path('data/statistics', data.statistics, name='data_stats'),
path('data/import/url', data.import_url, name='data_import_url'),
path('api/get_external_file_link/<int:recipe_id>/', api.get_external_file_link, name='api_get_external_file_link'),
path('api/get_recipe_file/<int:recipe_id>/', api.get_recipe_file, name='api_get_recipe_file'),
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:html_week>/', api.get_plan_ical, name='api_get_plan_ical'),
path('api/recipe-from-url/<path:url>/', api.recipe_from_url, name='api_recipe_from_url'),
path('dal/keyword/', dal.KeywordAutocomplete.as_view(), name='dal_keyword'),
path('dal/ingredient/', dal.IngredientsAutocomplete.as_view(), name='dal_ingredient'),

View File

@ -2,12 +2,14 @@ import io
import json
import re
import requests
from annoying.decorators import ajax_request
from annoying.functions import get_object_or_None
from bs4 import BeautifulSoup
from django.contrib import messages
from django.contrib.auth.models import User
from django.db.models import Q
from django.http import HttpResponse, FileResponse
from django.http import HttpResponse, FileResponse, JsonResponse
from django.shortcuts import redirect
from django.utils.translation import gettext as _
from icalendar import Calendar, Event
@ -16,7 +18,7 @@ from rest_framework.exceptions import APIException
from rest_framework.mixins import RetrieveModelMixin, UpdateModelMixin, ListModelMixin
from cookbook.helper.permission_helper import group_required, CustomIsOwner, CustomIsAdmin
from cookbook.models import Recipe, Sync, Storage, CookLog, MealPlan, MealType, ViewLog, UserPreference, RecipeBook
from cookbook.models import Recipe, Sync, Storage, CookLog, MealPlan, MealType, ViewLog, UserPreference, RecipeBook, Keyword
from cookbook.provider.dropbox import Dropbox
from cookbook.provider.nextcloud import Nextcloud
from cookbook.serializer import MealPlanSerializer, MealTypeSerializer, RecipeSerializer, ViewLogSerializer, UserNameSerializer, UserPreferenceSerializer, RecipeBookSerializer
@ -242,3 +244,72 @@ def get_plan_ical(request, html_week):
response["Content-Disposition"] = f'attachment; filename=meal_plan_{html_week}.ics'
return response
@group_required('user')
def recipe_from_url(request, url):
headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.106 Safari/537.36'}
response = requests.get(url, headers=headers)
if response.status_code == 403:
return JsonResponse({'error': _('The requested page refused to provide any information (Status Code 403).')})
soup = BeautifulSoup(response.text, "html.parser")
for ld in soup.find_all('script', type='application/ld+json'):
ld_json = json.loads(ld.string)
# recipes type might be wrapped in @graph type
if '@graph' in ld_json:
for x in ld_json['@graph']:
if '@type' in x and x['@type'] == 'Recipe':
ld_json = x
if '@type' in ld_json and ld_json['@type'] == 'Recipe':
if 'recipeIngredient' in ld_json:
ingredients = []
for x in ld_json['recipeIngredient']:
ingredient_split = x.split()
if len(ingredient_split) > 2:
ingredients.append({'amount': ingredient_split[0], 'unit': ingredient_split[1], 'ingredient': " ".join(ingredient_split[2:])})
if len(ingredient_split) == 2:
ingredients.append({'amount': ingredient_split[0], 'unit': '', 'ingredient': " ".join(ingredient_split[1:])})
if len(ingredient_split) == 1:
ingredients.append({'amount': 0, 'unit': '', 'ingredient': " ".join(ingredient_split)})
ld_json['recipeIngredient'] = ingredients
if 'keywords' in ld_json:
keywords = []
if type(ld_json['keywords']) == str:
ld_json['keywords'] = ld_json['keywords'].split(',')
for kw in ld_json['keywords']:
if k := Keyword.objects.filter(name=kw).first():
keywords.append({'id': str(k.id), 'text': str(k).strip()})
else:
keywords.append({'id': "null", 'text': kw.strip()})
ld_json['keywords'] = keywords
if 'recipeInstructions' in ld_json:
instructions = ''
if type(ld_json['recipeInstructions']) == list:
for i in ld_json['recipeInstructions']:
if type(i) == str:
instructions += i
else:
instructions += i['text'] + '\n\n'
ld_json['recipeInstructions'] = instructions
if 'image' in ld_json:
if (type(ld_json['image'])) == list:
if type(ld_json['image'][0]) == str:
ld_json['image'] = ld_json['image'][0]
elif 'url' in ld_json['image'][0]:
ld_json['image'] = ld_json['image'][0]['url']
return JsonResponse(ld_json)
return JsonResponse({'error': _('The requested site does not provide any recognized data format to import the recipe from.')})

View File

@ -88,6 +88,11 @@ def batch_edit(request):
return render(request, 'batch/edit.html', {'form': form})
@group_required('user')
def import_url(request):
return render(request, 'url_import.html', {})
class Object(object):
pass

View File

@ -22,4 +22,5 @@ webdavclient3==3.14.4
whitenoise==5.1.0
icalendar==4.0.6
pyyaml==5.3.1
uritemplate==3.0.1
uritemplate==3.0.1
beautifulsoup4==4.9.1