Fix after rebase

This commit is contained in:
smilerz
2021-04-16 09:02:27 -05:00
parent ca84da68c4
commit 987be4b04d
20 changed files with 69 additions and 676 deletions

View File

@ -7,8 +7,7 @@ from .models import (Comment, CookLog, Food, Ingredient, InviteLink, Keyword,
RecipeBook, RecipeBookEntry, RecipeImport, ShareLink,
ShoppingList, ShoppingListEntry, ShoppingListRecipe,
Space, Step, Storage, Sync, SyncLog, Unit, UserPreference,
ViewLog, Supermarket, SupermarketCategory, SupermarketCategoryRelation,
ImportLog, TelegramBot, BookmarkletImport)
ViewLog, Supermarket, SupermarketCategory, SupermarketCategoryRelation, ImportLog, TelegramBot)
class CustomUserAdmin(UserAdmin):
@ -223,13 +222,6 @@ class ImportLogAdmin(admin.ModelAdmin):
admin.site.register(ImportLog, ImportLogAdmin)
class BookmarkletImportAdmin(admin.ModelAdmin):
list_display = ('id', 'url', 'created_by', 'created_at',)
admin.site.register(BookmarkletImport, BookmarkletImportAdmin)
class TelegramBotAdmin(admin.ModelAdmin):
list_display = ('id', 'name', 'created_by',)

View File

@ -61,6 +61,7 @@ with scopes_disabled():
model = Recipe
fields = ['name', 'keywords', 'foods', 'internal']
class FoodFilter(django_filters.FilterSet):
name = django_filters.CharFilter(lookup_expr='icontains')
@ -68,6 +69,7 @@ with scopes_disabled():
model = Food
fields = ['name']
class ShoppingListFilter(django_filters.FilterSet):
def __init__(self, data=None, *args, **kwargs):

View File

@ -75,9 +75,11 @@ def get_recipe_from_source(text, url, space):
images = []
text = unquote(text)
# text = normalize_string(text)
try:
parse_list.append(remove_graph(json.loads(text)))
if not url and 'url' in parse_list[0]:
url = parse_list[0]['url']
scrape = text_scraper("<script type='application/ld+json'>" + text + "</script>", url=url)
except JSONDecodeError:
soup = BeautifulSoup(text, "html.parser")
@ -85,6 +87,8 @@ def get_recipe_from_source(text, url, space):
images += get_images_from_source(soup, url)
for el in soup.find_all('script', type='application/ld+json'):
el = remove_graph(el)
if not url and 'url' in el:
url = el['url']
if type(el) == list:
for le in el:
parse_list.append(le)
@ -97,15 +101,6 @@ def get_recipe_from_source(text, url, space):
parse_list.append(le)
elif type(el) == dict:
parse_list.append(el)
# if a url was not provided, try to find one in the first document
if not url and len(parse_list) > 0:
if 'url' in parse_list[0]:
url = parse_list[0]['url']
if type(text) == dict:
scrape = text_scraper("<script type='application/ld+json'>" + text + "</script>", url=url)
elif type(text) == str:
scrape = text_scraper(text, url=url)
recipe_json = helper.get_from_scraper(scrape, space)

View File

@ -294,8 +294,8 @@ def parse_keywords(keyword_json, space):
# keywords as list
for kw in keyword_json:
kw = normalize_string(kw)
if len(kw) != 0:
if k := Keyword.objects.filter(name=kw, space=space).first():
if len(k) != 0:
keywords.append({'id': str(k.id), 'text': str(k)})
else:
keywords.append({'id': random.randrange(1111111, 9999999, 1), 'text': kw})

View File

@ -1,34 +0,0 @@
# Generated by Django 3.1.7 on 2021-03-29 11:05
import cookbook.models
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('cookbook', '0119_auto_20210411_2101'),
]
operations = [
migrations.AlterField(
model_name='userpreference',
name='use_fractions',
field=models.BooleanField(default=True),
),
migrations.CreateModel(
name='BookmarkletImport',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('html', models.TextField()),
('url', models.CharField(blank=True, max_length=128, null=True)),
('created_at', models.DateTimeField(auto_now_add=True)),
('created_by', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
('space', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='cookbook.space')),
],
bases=(models.Model, cookbook.models.PermissionModelMixin),
),
]

View File

@ -671,13 +671,3 @@ class ImportLog(models.Model, PermissionModelMixin):
def __str__(self):
return f"{self.created_at}:{self.type}"
class BookmarkletImport(models.Model, PermissionModelMixin):
html = models.TextField()
url = models.CharField(max_length=128, null=True, blank=True)
created_at = models.DateTimeField(auto_now_add=True)
created_by = models.ForeignKey(User, on_delete=models.CASCADE)
objects = ScopedManager(space='space')
space = models.ForeignKey(Space, on_delete=models.CASCADE)

View File

@ -14,8 +14,7 @@ from cookbook.models import (Comment, CookLog, Food, Ingredient, Keyword,
RecipeBook, RecipeBookEntry, RecipeImport,
ShareLink, ShoppingList, ShoppingListEntry,
ShoppingListRecipe, Step, Storage, Sync, SyncLog,
Unit, UserPreference, ViewLog, SupermarketCategory,
Supermarket, SupermarketCategoryRelation, ImportLog, BookmarkletImport)
Unit, UserPreference, ViewLog, SupermarketCategory, Supermarket, SupermarketCategoryRelation, ImportLog)
from cookbook.templatetags.custom_tags import markdown
@ -467,20 +466,6 @@ class ImportLogSerializer(serializers.ModelSerializer):
read_only_fields = ('created_by',)
# CORS, REST and Scopes aren't currently working
# Scopes are evaluating before REST has authenticated the user assiging a None space
# I've made the change below to fix the bookmarklet, other serializers likely need a similar/better fix
class BookmarkletImportSerializer(serializers.ModelSerializer):
def create(self, validated_data):
validated_data['created_by'] = self.context['request'].user
validated_data['space'] = self.context['request'].user.userpreference.space
return super().create(validated_data)
class Meta:
model = BookmarkletImport
fields = ('id', 'url', 'html', 'created_by', 'created_at')
read_only_fields = ('created_by', 'space')
# Export/Import Serializers
class KeywordExportSerializer(KeywordSerializer):

View File

@ -1,47 +0,0 @@
(function(){
var v = "1.3.2";
if (window.jQuery === undefined || window.jQuery.fn.jquery < v) {
var done = false;
var script = document.createElement("script");
script.src = "https://ajax.googleapis.com/ajax/libs/jquery/" + v + "/jquery.min.js";
script.onload = script.onreadystatechange = function(){
if (!done && (!this.readyState || this.readyState == "loaded" || this.readyState == "complete")) {
done = true;
initBookmarklet();
}
};
document.getElementsByTagName("head")[0].appendChild(script);
} else {
initBookmarklet();
}
function initBookmarklet() {
(window.bookmarkletTandoor = function() {
let recipe = document.documentElement.innerHTML
let windowName = "ImportRecipe"
let url = localStorage.getItem('importURL')
let redirect = localStorage.getItem('redirectURL')
let token = localStorage.getItem('token')
let params = { 'html' : recipe,'url': window.location.href};
const xhr = new XMLHttpRequest();
xhr.open('POST', url, true);
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.setRequestHeader('Authorization', 'Token ' + token);
// listen for `onload` event
xhr.onload = () => {
// process response
if (xhr.readyState == 4 && xhr.status == 201) {
// parse JSON data
window.open(redirect.concat('?id=', JSON.parse(xhr.response).id) )
} else {
console.error('Error!');
}
};
xhr.send(JSON.stringify(params));
}
)();
}
})();

View File

@ -13,7 +13,7 @@
<link rel="shortcut icon" type="image/x-icon" href="{% static 'assets/favicon.svg' %}">
<link rel="shortcut icon" href="{% static 'assets/favicon.svg' %}">
<link rel="icon" type="image/png" href="{% static 'assets/.svg' %}" sizes="32x32">
<link rel="icon" type="image/png" href="{% static 'assets/favicon-32x32.png' %}" sizes="32x32">
<link rel="icon" type="image/png" href="{% static 'assets/favicon-16x16.png' %}" sizes="16x16">
<link rel="mask-icon" href="{% static 'assets/safari-pinned-tab.svg' %}" color="#161616">
@ -29,7 +29,6 @@
<meta name="theme-color" content="#ffffff">
<!-- Bootstrap 4 -->
<link id="id_main_css" href="{% theme_url request %}" rel="stylesheet">
<script src="{% static 'js/jquery-3.5.1.min.js' %}"></script>

View File

@ -1,385 +0,0 @@
<!--I PROBLABLY DON'T NEED THIS??-->
{% 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="html_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: {
html_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_manual_recipe_from_json' %}", {'html_text': this.html_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 %}

View File

@ -1,7 +1,6 @@
{% extends "base.html" %}
{% load i18n %}
{% load static %}
{% load custom_tags %}
{% block title %}{% trans 'URL Import' %}{% endblock %}
@ -21,15 +20,9 @@
{% endblock %}
{% block content %}
<div id="app">
<div class="row px-3" style="justify-content:space-between;">
<h2> {% trans 'Import' %} </h2>
<a class="btn btn-outline-info btn-sm"
style="height:50%"
href="{% bookmarklet request %}"
title="{% trans 'Drag me to your bookmarks to import recipes from anywhere' %}">
<img src="{% static 'assets/favicon-16x16.png' %}">{% trans 'Bookmark Me!' %} </a>
</div>
<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" @click="mode='url'">URL</a>
<a class="nav-link" href="#nav-app" data-toggle="tab" role="tab" aria-controls="nav-app" @click="mode='app'">App</a>
@ -41,7 +34,7 @@
<div class="tab-content" id="nav-tabContent">
<!-- Import URL -->
<div class="tab-pane fade show active" role="tabpanel">
<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" autocomplete="off" checked> Automatic
@ -605,6 +598,7 @@
<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({
@ -625,7 +619,7 @@
recipe_data: undefined,
error: undefined,
loading: false,
preview: undefined,
preview: false,
preview_type: 'json',
all_keywords: false,
importing_recipe: false,
@ -651,12 +645,7 @@
this.searchKeywords('')
this.searchUnits('')
this.searchIngredients('')
let uri = window.location.search.substring(1);
let params = new URLSearchParams(uri);
q = params.get("id")
if (q) {
this.loadBookmarklet(q)
}
},
methods: {
makeToast: function (title, message, variant = null) {
@ -706,41 +695,6 @@
this.makeToast(gettext('Error'), msg, 'danger')
})
},
loadBookmarklet: function(id_bkmk) {
let uri = window.location.search.substring(1);
let params = new URLSearchParams(uri);
q = params.get("id")
console.log(q)
this.error = undefined
this.loading = true
this.$http.get("{% url 'api:bookmarkletimport-list' %}?id=" + id_bkmk ).then((response) => {
console.log(response.data)
this.automatic = false
this.source_data = response.data[0]['html']
this.remote_url = response.data[0]['url']
this.loadSource()
}).catch((err) => {
this.error = err.data
this.loading = false
console.log(err)
this.makeToast(gettext('Error'), gettext('There was an error loading bookmarklet!') + err.bodyText, 'danger')
})
},
deleteBookmarklet: function(id_bkmk) {
this.error = undefined
this.$http.delete("{% url 'api:bookmarkletimport-list' %}" + id_bkmk +"/").then((response) => {
}).catch((err) => {
this.error = err.data
console.log(err)
this.makeToast(gettext('Error'), gettext('There was an error deleting bookmarklet!') + err.bodyText, 'danger')
})
},
loadRecipeManual: function () {
this.error = undefined
this.preview = false
this.recipe_data = this.recipe_json
},
showRecipe: function() {
this.preview = false
this.recipe_data = this.recipe_json
@ -761,12 +715,6 @@
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) => {
let uri = window.location.search.substring(1);
let params = new URLSearchParams(uri);
q = params.get("id")
if (q) {
this.deleteBookmarklet(q)
}
window.location.href = response.data
}).catch((err) => {
console.log(err);

View File

@ -1,5 +1,4 @@
import bleach
import re
import markdown as md
from bleach_allowlist import markdown_attrs, markdown_tags
from cookbook.helper.mdx_attributes import MarkdownFormatExtension
@ -7,10 +6,8 @@ from cookbook.helper.mdx_urlize import UrlizeExtension
from cookbook.models import Space, get_model_name
from django import template
from django.db.models import Avg
from django.templatetags.static import static
from django.urls import NoReverseMatch, reverse
from recipes import settings
from rest_framework.authtoken.models import Token
from gettext import gettext as _
register = template.Library()
@ -109,29 +106,3 @@ def message_of_the_day():
@register.simple_tag
def is_debug():
return settings.DEBUG
@register.simple_tag
def bookmarklet(request):
if request.is_secure():
prefix = "https://"
else:
prefix = "http://"
server = prefix + request.get_host()
# TODO is it safe to store the token in clear text in a bookmark?
if (api_token := Token.objects.filter(user=request.user).first()) is None:
api_token = Token.objects.create(user=request.user)
bookmark = "javascript: \
(function(){ \
if(window.bookmarkletTandoor!==undefined){ \
bookmarkletTandoor(); \
} else { \
localStorage.setItem('importURL', '" + server + reverse('api:bookmarkletimport-list') + "'); \
localStorage.setItem('redirectURL', '" + server + reverse('data_import_url') + "'); \
localStorage.setItem('token', '" + api_token.__str__() + "'); \
document.body.appendChild(document.createElement(\'script\')).src=\'" \
+ server + static('js/bookmarklet.js') + "? \
r=\'+Math.floor(Math.random()*999999999);}})();"
return re.sub(r"[\n\t\s]*", "", bookmark)

View File

@ -128,10 +128,8 @@ ALLRECIPES = {
}
AMERICAS_TEST_KITCHEN = {
"file": [
'americastestkitchen.html',
'cookscountry.html'
],
"file": ['americastestkitchen.html'],
'url': 'http://www.americastestkitchen.com/recipes/1771-pasta-with-chicken-broccoli-and-sun-dried-tomatoes',
"name": "Pasta with Chicken, Broccoli, and Sun-dried Tomatoes",
"description": "This restaurant-chain classic can be as off-putting as a bad horror movie: drab colors, tough meat, and a main character—the pasta—with no bite.",
"servings": 4,
@ -140,13 +138,12 @@ AMERICAS_TEST_KITCHEN = {
"image": "https://res.cloudinary.com/hksqkdlah/image/upload/ar_1:1,c_fill,dpr_2.0,f_auto,fl_lossy.progressive.strip_profile,g_faces:auto,q_auto:low,w_150/33255_sfs-pasta-with-chicken-broccoli-sun-dried-tomatoes-15",
"keywords": [
{
"id": "21",
"text": "main courses"
}
],
"recipeIngredient": [
{
"amount": 4,
"amount": 4.0,
"unit": {
"text": "tablespoons"
},
@ -157,7 +154,7 @@ AMERICAS_TEST_KITCHEN = {
"original": "4 tablespoons unsalted butter"
},
{
"amount": 1,
"amount": 1.0,
"unit": {
"text": "pound"
},
@ -168,7 +165,7 @@ AMERICAS_TEST_KITCHEN = {
"original": "1 pound boneless, skinless chicken breast, trimmed of fat and cut crosswise into 1/4-inch slices"
},
{
"amount": 1,
"amount": 1.0,
"unit": {
"text": "small"
},
@ -190,7 +187,7 @@ AMERICAS_TEST_KITCHEN = {
"original": " Table salt"
},
{
"amount": 6,
"amount": 6.0,
"unit": {
"text": "medium"
},
@ -212,7 +209,7 @@ AMERICAS_TEST_KITCHEN = {
"original": "1/4 teaspoon red pepper flakes"
},
{
"amount": 2,
"amount": 2.0,
"unit": {
"text": "teaspoons"
},
@ -223,7 +220,7 @@ AMERICAS_TEST_KITCHEN = {
"original": "2 teaspoons chopped fresh thyme leaves"
},
{
"amount": 2,
"amount": 2.0,
"unit": {
"text": "teaspoons"
},
@ -234,7 +231,7 @@ AMERICAS_TEST_KITCHEN = {
"original": "2 teaspoons all-purpose flour"
},
{
"amount": 1,
"amount": 1.0,
"unit": {
"text": "cup"
},
@ -245,7 +242,7 @@ AMERICAS_TEST_KITCHEN = {
"original": "1 cup dry white wine"
},
{
"amount": 2,
"amount": 2.0,
"unit": {
"text": "cups"
},
@ -256,7 +253,7 @@ AMERICAS_TEST_KITCHEN = {
"original": "2 cups low-sodium chicken broth"
},
{
"amount": 1,
"amount": 1.0,
"unit": {
"text": "bunch"
},
@ -278,7 +275,7 @@ AMERICAS_TEST_KITCHEN = {
"original": "1/2 pound penne pasta, ziti, cavatappi, or campanelle"
},
{
"amount": 2,
"amount": 2.0,
"unit": {
"text": "ounces"
},
@ -289,7 +286,7 @@ AMERICAS_TEST_KITCHEN = {
"original": "2 ounces grated Asiago cheese(1 cup), plus extra for serving"
},
{
"amount": 1,
"amount": 1.0,
"unit": {
"text": "jar"
},
@ -300,7 +297,7 @@ AMERICAS_TEST_KITCHEN = {
"original": "1 jar oil-packed sun-dried tomato(7 to 8 1/2 ounces), rinsed, patted dry, and cut into 1/4-inch strips (about 1 cup)"
},
{
"amount": 1,
"amount": 1.0,
"unit": {
"text": "tablespoon"
},
@ -1705,7 +1702,7 @@ MADAME_DESSERT = {
"servings": 6,
"prepTime": 0,
"cookTime": 20,
"image": "https://madamedessert.de/wp-content/uploads/2020/02/Madame-Dessert_Schokopudding-Schokoladenpudding-mit-echter-Schokolade-0238-scaled.jpg",
"image": "https://assets.madamedessert.de/wp-content/uploads/2020/02/25163328/Madame-Dessert_Schokopudding-Schokoladenpudding-mit-echter-Schokolade-0238-scaled.jpg",
"keywords": [
{
"id": 7588432,
@ -1795,7 +1792,7 @@ MADAME_DESSERT = {
"original": "1 TL Vanilleextrakt"
},
{
"amount": 125,
"amount": 150,
"unit": {
"text": "g",
"id": 24254
@ -1805,7 +1802,7 @@ MADAME_DESSERT = {
"id": 42645
},
"note": "",
"original": "125 g Zucker"
"original": "150 g Zucker"
},
{
"amount": 30,

File diff suppressed because one or more lines are too long

View File

@ -36,7 +36,6 @@ router.register(r'recipe-book', api.RecipeBookViewSet)
router.register(r'recipe-book-entry', api.RecipeBookEntryViewSet)
router.register(r'supermarket', api.SupermarketViewSet)
router.register(r'import-log', api.ImportLogViewSet)
router.register(r'bookmarklet-import', api.BookmarkletImportViewSet)
urlpatterns = [
path('', views.index, name='index'),
@ -60,6 +59,7 @@ urlpatterns = [
path('test2/', views.test2, name='view_test2'),
path('import/', import_export.import_recipe, name='view_import'),
path('import-response/<int:pk>/', import_export.import_response, name='view_import_response'),
path('export/', import_export.export_recipe, name='view_export'),
path('view/recipe/<int:pk>', views.recipe_view, name='view_recipe'),

View File

@ -13,11 +13,9 @@ from django.core import management
from django.core.exceptions import FieldError, ValidationError
from django.core.files import File
from django.db.models import Q
from django.http import FileResponse, HttpResponse, JsonResponse, HttpResponseRedirect
from django.shortcuts import redirect, render, get_object_or_404
from django.urls import reverse
from django.http import FileResponse, HttpResponse, JsonResponse
from django.shortcuts import redirect, get_object_or_404
from django.utils.translation import gettext as _
from django.views.decorators.csrf import csrf_exempt
from icalendar import Calendar, Event
from rest_framework import decorators, viewsets, status
from rest_framework.exceptions import APIException, PermissionDenied, NotFound, MethodNotAllowed
@ -29,14 +27,14 @@ from cookbook.helper.ingredient_parser import parse
from cookbook.helper.permission_helper import (CustomIsAdmin, CustomIsGuest,
CustomIsOwner, CustomIsShare,
CustomIsShared, CustomIsUser,
group_required)
group_required, share_link_valid)
from cookbook.helper.recipe_html_import import get_recipe_from_source
from cookbook.helper.recipe_url_import import get_from_scraper
from cookbook.models import (CookLog, Food, Ingredient, Keyword, MealPlan,
MealType, Recipe, RecipeBook, ShoppingList,
ShoppingListEntry, ShoppingListRecipe, Step,
Storage, Sync, SyncLog, Unit, UserPreference,
ViewLog, RecipeBookEntry, Supermarket, ImportLog, BookmarkletImport)
ViewLog, RecipeBookEntry, Supermarket, ImportLog)
from cookbook.provider.dropbox import Dropbox
from cookbook.provider.local import Local
from cookbook.provider.nextcloud import Nextcloud
@ -51,9 +49,7 @@ from cookbook.serializer import (FoodSerializer, IngredientSerializer,
StorageSerializer, SyncLogSerializer,
SyncSerializer, UnitSerializer,
UserNameSerializer, UserPreferenceSerializer,
ViewLogSerializer, CookLogSerializer,
RecipeBookEntrySerializer, RecipeOverviewSerializer,
SupermarketSerializer, ImportLogSerializer, BookmarkletImportSerializer)
ViewLogSerializer, CookLogSerializer, RecipeBookEntrySerializer, RecipeOverviewSerializer, SupermarketSerializer, ImportLogSerializer)
from recipes.settings import DEMO
from recipe_scrapers import scrape_me, WebsiteNotImplementedError, NoSchemaFoundInWildMode
@ -414,15 +410,6 @@ class ImportLogViewSet(viewsets.ModelViewSet):
return self.queryset.filter(space=self.request.space).all()
class BookmarkletImportViewSet(viewsets.ModelViewSet):
queryset = BookmarkletImport.objects
serializer_class = BookmarkletImportSerializer
permission_classes = [CustomIsUser]
def get_queryset(self):
return self.queryset.filter(space=self.request.space).all()
# -------------- non django rest api views --------------------
def get_recipe_provider(recipe):

View File

@ -93,7 +93,9 @@ def batch_edit(request):
msg = ngettext(
'Batch edit done. %(count)d recipe was updated.',
'Batch edit done. %(count)d Recipes where updated.',
count) % {'count': count, }
count) % {
'count': count,
}
messages.add_message(request, messages.SUCCESS, msg)
return redirect('data_batch_edit')
@ -186,12 +188,7 @@ def import_url(request):
return HttpResponse(reverse('view_recipe', args=[recipe.pk]))
elif 'id' in request.GET:
context = {'bookmarklet': request.GET.get('id')}
else:
context = {}
return render(request, 'url_import.html', context)
return render(request, 'url_import.html', {})
class Object(object):

View File

@ -107,3 +107,7 @@ def export_recipe(request):
return render(request, 'export.html', {'form': form})
@group_required('user')
def import_response(request, pk):
return render(request, 'import_response.html', {'pk': pk})

View File

@ -12,7 +12,6 @@ https://docs.djangoproject.com/en/2.0/ref/settings/
import ast
import os
from corsheaders.defaults import default_headers
from django.contrib import messages
from django.utils.translation import gettext_lazy as _
from dotenv import load_dotenv
@ -75,7 +74,6 @@ INSTALLED_APPS = [
'django.contrib.staticfiles',
'django.contrib.postgres',
'django_tables2',
'corsheaders',
'django_filters',
'crispy_forms',
'emoji_picker',
@ -97,7 +95,6 @@ SOCIALACCOUNT_PROVIDERS = ast.literal_eval(
os.getenv('SOCIALACCOUNT_PROVIDERS') if os.getenv('SOCIALACCOUNT_PROVIDERS') else '{}')
MIDDLEWARE = [
'corsheaders.middleware.CorsMiddleware',
'django.middleware.security.SecurityMiddleware',
'whitenoise.middleware.WhiteNoiseMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
@ -251,17 +248,5 @@ STATICFILES_STORAGE = 'whitenoise.storage.CompressedManifestStaticFilesStorage'
TEST_RUNNER = "cookbook.helper.CustomTestRunner.CustomTestRunner"
# settings for cross site origin (CORS)
# all origins allowed to support bookmarklet
# all of this may or may not work with nginx or other web servers
# TODO make this user configureable - enable or disable bookmarklets
# TODO since token auth is enabled - this all should be https by default
CORS_ORIGIN_ALLOW_ALL = True
# django rest_framework requires authentication header
#CORS_ALLOW_HEADERS = list(default_headers) + ['authentication',]
# enable CORS only for bookmarklet api and only for posts, get and options
CORS_URLS_REGEX = r'^/api/bookmarklet-import.*$'
CORS_ALLOW_METHODS = ['GET', 'OPTIONS', 'POST']
# future versions of django will make undeclared default django.db.models.BigAutoField which will force migrations on all models
DEFAULT_AUTO_FIELD = 'django.db.models.AutoField'

View File

@ -31,8 +31,7 @@ Jinja2==2.11.3
django-webpack-loader==0.7.0
django-js-reverse==0.9.1
django-allauth==0.44.0
recipe-scrapers==12.2.1
recipe-scrapers==12.2.2
django-scopes==1.2.0
pytest==6.2.2
pytest-django==4.1.0
django-cors-headers==3.7.0
pytest==6.2.3
pytest-django==4.2.0