Merge branch 'search_and_keywords' of https://github.com/smilerz/recipes into smilerz-search_and_keywords
# Conflicts: # cookbook/admin.py # cookbook/helper/recipe_search.py # cookbook/integration/chowdown.py # cookbook/integration/integration.py # cookbook/management/commands/rebuildindex.py # cookbook/managers.py # cookbook/migrations/0142_build_full_text_index.py # cookbook/models.py # cookbook/schemas.py # cookbook/serializer.py # cookbook/static/vue/css/keyword_list_view.css # cookbook/static/vue/js/chunk-vendors.js # cookbook/static/vue/js/keyword_list_view.js # cookbook/tests/api/test_api_keyword.py # cookbook/views/api.py # cookbook/views/data.py # cookbook/views/views.py # recipes/settings.py # vue/package.json # vue/src/apps/KeywordListView/KeywordListView.vue # vue/src/components/KeywordCard.vue # vue/src/locales/en.json # vue/src/utils/openapi/api.ts # vue/yarn.lock
This commit is contained in:
commit
43dbb1d973
@ -18,8 +18,6 @@ from .models import (Comment, CookLog, Food, Ingredient, InviteLink, Keyword,
|
||||
|
||||
from cookbook.managers import DICTIONARY
|
||||
|
||||
from cookbook.managers import DICTIONARY
|
||||
|
||||
|
||||
class CustomUserAdmin(UserAdmin):
|
||||
def has_add_permission(self, request, obj=None):
|
||||
|
@ -32,7 +32,7 @@ def rescale_image_png(image_object, base_width=720):
|
||||
def get_filetype(name):
|
||||
try:
|
||||
return os.path.splitext(name)[1]
|
||||
except:
|
||||
except Exception:
|
||||
return '.jpeg'
|
||||
|
||||
|
||||
|
@ -3,8 +3,6 @@ Source: https://djangosnippets.org/snippets/1703/
|
||||
"""
|
||||
from django.conf import settings
|
||||
from django.core.cache import caches
|
||||
from django.views.generic.detail import SingleObjectTemplateResponseMixin
|
||||
from django.views.generic.edit import ModelFormMixin
|
||||
|
||||
from cookbook.models import ShareLink
|
||||
from django.contrib import messages
|
||||
@ -64,7 +62,7 @@ def is_object_owner(user, obj):
|
||||
return False
|
||||
try:
|
||||
return obj.get_owner() == user
|
||||
except:
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
|
||||
|
@ -4,7 +4,7 @@ from recipes import settings
|
||||
from django.contrib.postgres.search import (
|
||||
SearchQuery, SearchRank, TrigramSimilarity
|
||||
)
|
||||
from django.db.models import Q, Subquery, Case, When, Value
|
||||
from django.db.models import Count, Q, Subquery, Case, When, Value
|
||||
from django.utils import translation
|
||||
|
||||
from cookbook.managers import DICTIONARY
|
||||
@ -14,6 +14,7 @@ from cookbook.models import Food, Keyword, ViewLog
|
||||
def search_recipes(request, queryset, params):
|
||||
search_prefs = request.user.searchpreference
|
||||
search_string = params.get('query', '')
|
||||
search_ratings = params.getlist('ratings', [])
|
||||
search_keywords = params.getlist('keywords', [])
|
||||
search_foods = params.getlist('foods', [])
|
||||
search_books = params.getlist('books', [])
|
||||
@ -24,8 +25,10 @@ def search_recipes(request, queryset, params):
|
||||
|
||||
search_internal = params.get('internal', None)
|
||||
search_random = params.get('random', False)
|
||||
search_new = params.get('new', False)
|
||||
search_last_viewed = int(params.get('last_viewed', 0))
|
||||
|
||||
# TODO update this to concat with full search queryset qs1 | qs2
|
||||
if search_last_viewed > 0:
|
||||
last_viewed_recipes = ViewLog.objects.filter(
|
||||
created_by=request.user, space=request.space,
|
||||
@ -34,13 +37,16 @@ def search_recipes(request, queryset, params):
|
||||
|
||||
return queryset.filter(pk__in=last_viewed_recipes[len(last_viewed_recipes) - min(len(last_viewed_recipes), search_last_viewed):])
|
||||
|
||||
queryset = queryset.annotate(
|
||||
new_recipe=Case(When(
|
||||
created_at__gte=(datetime.now() - timedelta(days=7)), then=Value(100)),
|
||||
default=Value(0), )).order_by('-new_recipe', 'name')
|
||||
orderby = []
|
||||
if search_new == 'true':
|
||||
queryset = queryset.annotate(
|
||||
new_recipe=Case(When(created_at__gte=(datetime.now() - timedelta(days=7)), then=Value(100)),
|
||||
default=Value(0), ))
|
||||
orderby += ['new_recipe']
|
||||
else:
|
||||
queryset = queryset
|
||||
|
||||
search_type = search_prefs.search or 'plain'
|
||||
search_sort = None
|
||||
if len(search_string) > 0:
|
||||
unaccent_include = search_prefs.unaccent.values_list('field', flat=True)
|
||||
|
||||
@ -106,22 +112,30 @@ def search_recipes(request, queryset, params):
|
||||
else:
|
||||
query_filter = f
|
||||
|
||||
# TODO this is kind of a dumb method to sort. create settings to choose rank vs most often made, date created or rating
|
||||
# TODO add order by user settings - only do search rank and annotation if rank order is configured
|
||||
search_rank = (
|
||||
SearchRank('name_search_vector', search_query, cover_density=True)
|
||||
+ SearchRank('desc_search_vector', search_query, cover_density=True)
|
||||
+ SearchRank('steps__search_vector', search_query, cover_density=True)
|
||||
)
|
||||
queryset = queryset.filter(query_filter).annotate(rank=search_rank)
|
||||
orderby += ['-rank']
|
||||
else:
|
||||
queryset = queryset.filter(name__icontains=search_string)
|
||||
queryset = queryset.filter(query_filter)
|
||||
|
||||
if len(search_keywords) > 0:
|
||||
# TODO creating setting to include descendants of keywords a setting
|
||||
if search_keywords_or == 'true':
|
||||
# when performing an 'or' search all descendants are included in the OR condition
|
||||
# so descendants are appended to filter all at once
|
||||
for kw in Keyword.objects.filter(pk__in=search_keywords):
|
||||
search_keywords += list(kw.get_descendants().values_list('pk', flat=True))
|
||||
queryset = queryset.filter(keywords__id__in=search_keywords)
|
||||
else:
|
||||
for k in search_keywords:
|
||||
queryset = queryset.filter(keywords__id=k)
|
||||
# when performing an 'and' search returned recipes should include a parent OR any of its descedants
|
||||
# AND other keywords selected so filters are appended using keyword__id__in the list of keywords and descendants
|
||||
for kw in Keyword.objects.filter(pk__in=search_keywords):
|
||||
queryset = queryset.filter(keywords__id__in=list(kw.get_descendants_and_self().values_list('pk', flat=True)))
|
||||
|
||||
if len(search_foods) > 0:
|
||||
if search_foods_or == 'true':
|
||||
@ -144,11 +158,122 @@ def search_recipes(request, queryset, params):
|
||||
|
||||
if search_random == 'true':
|
||||
queryset = queryset.order_by("?")
|
||||
elif search_sort == 'rank':
|
||||
queryset = queryset.order_by('-rank')
|
||||
|
||||
else:
|
||||
# TODO add order by user settings
|
||||
orderby += ['name']
|
||||
queryset = queryset.order_by(*orderby)
|
||||
return queryset
|
||||
|
||||
|
||||
# this returns a list of keywords in the queryset and how many times it appears
|
||||
# Keyword.objects.filter(recipe__in=queryset).annotate(kw_count=Count('recipe'))
|
||||
def get_facet(qs, params):
|
||||
facets = {}
|
||||
ratings = params.getlist('ratings', [])
|
||||
keyword_list = params.getlist('keywords', [])
|
||||
ingredient_list = params.getlist('ingredient', [])
|
||||
book_list = params.getlist('book', [])
|
||||
|
||||
# this returns a list of keywords in the queryset and how many times it appears
|
||||
kws = Keyword.objects.filter(recipe__in=qs).annotate(kw_count=Count('recipe'))
|
||||
# custom django-tree function annotates a queryset to make building a tree easier.
|
||||
# see https://django-treebeard.readthedocs.io/en/latest/api.html#treebeard.models.Node.get_annotated_list_qs for details
|
||||
kw_a = annotated_qs(kws, root=True, fill=True)
|
||||
|
||||
# TODO add rating facet
|
||||
facets['Ratings'] = []
|
||||
facets['Keywords'] = fill_annotated_parents(kw_a, keyword_list)
|
||||
# TODO add food facet
|
||||
facets['Ingredients'] = []
|
||||
# TODO add book facet
|
||||
facets['Books'] = []
|
||||
|
||||
return facets
|
||||
|
||||
|
||||
def fill_annotated_parents(annotation, filters):
|
||||
tree_list = []
|
||||
parent = []
|
||||
i = 0
|
||||
level = -1
|
||||
for r in annotation:
|
||||
expand = False
|
||||
|
||||
annotation[i][1]['id'] = r[0].id
|
||||
annotation[i][1]['name'] = r[0].name
|
||||
annotation[i][1]['count'] = getattr(r[0], 'kw_count', 0)
|
||||
annotation[i][1]['isDefaultExpanded'] = False
|
||||
|
||||
if str(r[0].id) in filters:
|
||||
expand = True
|
||||
if r[1]['level'] < level:
|
||||
parent = parent[:r[1]['level'] - level]
|
||||
parent[-1] = i
|
||||
level = r[1]['level']
|
||||
elif r[1]['level'] > level:
|
||||
parent.extend([i])
|
||||
level = r[1]['level']
|
||||
else:
|
||||
parent[-1] = i
|
||||
j = 0
|
||||
|
||||
while j < level:
|
||||
# this causes some double counting when a recipe has both a child and an ancestor
|
||||
annotation[parent[j]][1]['count'] += getattr(r[0], 'kw_count', 0)
|
||||
if expand:
|
||||
annotation[parent[j]][1]['isDefaultExpanded'] = True
|
||||
j += 1
|
||||
if level == 0:
|
||||
tree_list.append(annotation[i][1])
|
||||
elif level > 0:
|
||||
annotation[parent[level - 1]][1].setdefault('children', []).append(annotation[i][1])
|
||||
i += 1
|
||||
return tree_list
|
||||
|
||||
|
||||
def annotated_qs(qs, root=False, fill=False):
|
||||
"""
|
||||
Gets an annotated list from a queryset.
|
||||
|
||||
:param root:
|
||||
|
||||
Will backfill in annotation to include all parents to root node.
|
||||
|
||||
:param fill:
|
||||
|
||||
Will fill in gaps in annotation where nodes between children
|
||||
and ancestors are not included in the queryset.
|
||||
"""
|
||||
|
||||
result, info = [], {}
|
||||
start_depth, prev_depth = (None, None)
|
||||
nodes_list = list(qs.values_list('pk', flat=True))
|
||||
for node in qs.order_by('path'):
|
||||
node_queue = [node]
|
||||
while len(node_queue) > 0:
|
||||
dirty = False
|
||||
current_node = node_queue[-1]
|
||||
depth = current_node.get_depth()
|
||||
parent_id = current_node.parent
|
||||
if root and depth > 1 and parent_id not in nodes_list:
|
||||
parent_id = current_node.parent
|
||||
nodes_list.append(parent_id)
|
||||
node_queue.append(current_node.__class__.objects.get(pk=parent_id))
|
||||
dirty = True
|
||||
|
||||
if fill and depth > 1 and prev_depth and depth > prev_depth and parent_id not in nodes_list:
|
||||
nodes_list.append(parent_id)
|
||||
node_queue.append(current_node.__class__.objects.get(pk=parent_id))
|
||||
dirty = True
|
||||
|
||||
if not dirty:
|
||||
working_node = node_queue.pop()
|
||||
if start_depth is None:
|
||||
start_depth = depth
|
||||
open = (depth and (prev_depth is None or depth > prev_depth))
|
||||
if prev_depth is not None and depth < prev_depth:
|
||||
info['close'] = list(range(0, prev_depth - depth))
|
||||
info = {'open': open, 'close': [], 'level': depth - start_depth}
|
||||
result.append((working_node, info,))
|
||||
prev_depth = depth
|
||||
if start_depth and start_depth > 0:
|
||||
info['close'] = list(range(0, prev_depth - start_depth + 1))
|
||||
return result
|
||||
|
@ -1,4 +1,3 @@
|
||||
from django.shortcuts import redirect
|
||||
from django.urls import reverse
|
||||
from django_scopes import scope, scopes_disabled
|
||||
|
||||
|
@ -30,7 +30,6 @@ def text_scraper(text, url=None):
|
||||
url=None
|
||||
):
|
||||
self.wild_mode = False
|
||||
# self.exception_handling = None # TODO add new method here, old one was deprecated
|
||||
self.meta_http_equiv = False
|
||||
self.soup = BeautifulSoup(page_data, "html.parser")
|
||||
self.url = url
|
||||
|
@ -6,6 +6,7 @@ from cookbook.helper.mdx_urlize import UrlizeExtension
|
||||
from jinja2 import Template, TemplateSyntaxError, UndefinedError
|
||||
from gettext import gettext as _
|
||||
|
||||
|
||||
class IngredientObject(object):
|
||||
amount = ""
|
||||
unit = ""
|
||||
|
@ -1,11 +1,6 @@
|
||||
import json
|
||||
import re
|
||||
from io import BytesIO
|
||||
from zipfile import ZipFile
|
||||
|
||||
from cookbook.helper.ingredient_parser import parse, get_food, get_unit
|
||||
from cookbook.integration.integration import Integration
|
||||
from cookbook.models import Recipe, Step, Food, Unit, Ingredient, Keyword
|
||||
from cookbook.models import Recipe, Step, Ingredient
|
||||
|
||||
|
||||
class Pepperplate(Integration):
|
||||
|
@ -1,10 +1,8 @@
|
||||
import re
|
||||
|
||||
from django.utils.translation import gettext as _
|
||||
|
||||
from cookbook.helper.ingredient_parser import parse, get_food, get_unit
|
||||
from cookbook.integration.integration import Integration
|
||||
from cookbook.models import Recipe, Step, Food, Unit, Ingredient
|
||||
from cookbook.models import Recipe, Step, Ingredient
|
||||
|
||||
|
||||
class ChefTap(Integration):
|
||||
|
@ -1,4 +1,3 @@
|
||||
import json
|
||||
import re
|
||||
from io import BytesIO
|
||||
from zipfile import ZipFile
|
||||
@ -6,7 +5,7 @@ from zipfile import ZipFile
|
||||
from cookbook.helper.image_processing import get_filetype
|
||||
from cookbook.helper.ingredient_parser import parse, get_food, get_unit
|
||||
from cookbook.integration.integration import Integration
|
||||
from cookbook.models import Recipe, Step, Food, Unit, Ingredient, Keyword
|
||||
from cookbook.models import Recipe, Step, Ingredient, Keyword
|
||||
|
||||
|
||||
class Chowdown(Integration):
|
||||
@ -52,7 +51,7 @@ class Chowdown(Integration):
|
||||
|
||||
for k in tags.split(','):
|
||||
print(f'adding keyword {k.strip()}')
|
||||
keyword, created = Keyword.get_or_create(name=k.strip(), space=self.request.space)
|
||||
keyword, created = Keyword.objects.get_or_create(name=k.strip(), space=self.request.space)
|
||||
recipe.keywords.add(keyword)
|
||||
|
||||
step = Step.objects.create(
|
||||
|
@ -1,7 +1,5 @@
|
||||
import datetime
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import traceback
|
||||
import uuid
|
||||
from io import BytesIO, StringIO
|
||||
@ -16,7 +14,7 @@ from django_scopes import scope
|
||||
from cookbook.forms import ImportExportBase
|
||||
from cookbook.helper.image_processing import get_filetype
|
||||
from cookbook.models import Keyword, Recipe
|
||||
from recipes.settings import DEBUG
|
||||
from recipes.settings import DATABASES, DEBUG
|
||||
|
||||
|
||||
class Integration:
|
||||
@ -33,8 +31,29 @@ class Integration:
|
||||
"""
|
||||
self.request = request
|
||||
self.export_type = export_type
|
||||
# TODO add all import keywords under the importer root node
|
||||
self.keyword = Keyword.objects.first()
|
||||
name = f'Import {export_type}'
|
||||
description = f'Imported by {request.user.get_user_name()} at {date_format(datetime.datetime.now(), "DATETIME_FORMAT")}. Type: {export_type}'
|
||||
icon = '📥'
|
||||
count = Keyword.objects.filter(name__icontains=name, space=request.space).count()
|
||||
if count != 0:
|
||||
pk = Keyword.objects.filter(name__icontains=name, space=request.space).order_by('id').first().id
|
||||
name = name + " " + str(pk)
|
||||
|
||||
if DATABASES['default']['ENGINE'] in ['django.db.backends.postgresql_psycopg2', 'django.db.backends.postgresql']:
|
||||
parent = Keyword.objects.get_or_create(name='Import', space=request.space)
|
||||
parent.add_child(
|
||||
name=name,
|
||||
description=description,
|
||||
icon=icon,
|
||||
space=request.space
|
||||
)
|
||||
else:
|
||||
self.keyword = Keyword.objects.create(
|
||||
name=name,
|
||||
description=description,
|
||||
icon=icon,
|
||||
space=request.space
|
||||
)
|
||||
|
||||
def do_export(self, recipes):
|
||||
"""
|
||||
@ -181,7 +200,7 @@ class Integration:
|
||||
except BadZipFile:
|
||||
il.msg += 'ERROR ' + _(
|
||||
'Importer expected a .zip file. Did you choose the correct importer type for your data ?') + '\n'
|
||||
except:
|
||||
except Exception as e:
|
||||
msg = 'ERROR ' + _(
|
||||
'An unexpected error occurred during the import. Please make sure you have uploaded a valid file.') + '\n'
|
||||
self.handle_exception(e, log=il, message=msg)
|
||||
|
@ -6,7 +6,7 @@ from zipfile import ZipFile
|
||||
from cookbook.helper.image_processing import get_filetype
|
||||
from cookbook.helper.ingredient_parser import parse, get_food, get_unit
|
||||
from cookbook.integration.integration import Integration
|
||||
from cookbook.models import Recipe, Step, Food, Unit, Ingredient
|
||||
from cookbook.models import Recipe, Step, Ingredient
|
||||
|
||||
|
||||
class Mealie(Integration):
|
||||
@ -23,7 +23,8 @@ class Mealie(Integration):
|
||||
name=recipe_json['name'].strip(), description=description,
|
||||
created_by=self.request.user, internal=True, space=self.request.space)
|
||||
|
||||
# TODO parse times (given in PT2H3M )
|
||||
# TODO parse times (given in PT2H3M )
|
||||
# @vabene check recipe_url_import.iso_duration_to_minutes I think it does what you are looking for
|
||||
|
||||
ingredients_added = False
|
||||
for s in recipe_json['recipe_instructions']:
|
||||
@ -50,7 +51,7 @@ class Mealie(Integration):
|
||||
step.ingredients.add(Ingredient.objects.create(
|
||||
food=f, unit=u, amount=amount, note=note, space=self.request.space,
|
||||
))
|
||||
except:
|
||||
except Exception:
|
||||
pass
|
||||
recipe.steps.add(step)
|
||||
|
||||
@ -59,7 +60,7 @@ class Mealie(Integration):
|
||||
import_zip = ZipFile(f['file'])
|
||||
try:
|
||||
self.import_recipe_image(recipe, BytesIO(import_zip.read(f'recipes/{recipe_json["slug"]}/images/min-original.webp')), filetype=get_filetype(f'recipes/{recipe_json["slug"]}/images/original'))
|
||||
except:
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return recipe
|
||||
|
@ -1,11 +1,8 @@
|
||||
import json
|
||||
import re
|
||||
from io import BytesIO
|
||||
from zipfile import ZipFile
|
||||
|
||||
from cookbook.helper.ingredient_parser import parse, get_food, get_unit
|
||||
from cookbook.integration.integration import Integration
|
||||
from cookbook.models import Recipe, Step, Food, Unit, Ingredient, Keyword
|
||||
from cookbook.models import Recipe, Step, Ingredient, Keyword
|
||||
|
||||
|
||||
class MealMaster(Integration):
|
||||
|
@ -6,7 +6,7 @@ from zipfile import ZipFile
|
||||
from cookbook.helper.image_processing import get_filetype
|
||||
from cookbook.helper.ingredient_parser import parse, get_food, get_unit
|
||||
from cookbook.integration.integration import Integration
|
||||
from cookbook.models import Recipe, Step, Food, Unit, Ingredient
|
||||
from cookbook.models import Recipe, Step, Ingredient
|
||||
|
||||
|
||||
class NextcloudCookbook(Integration):
|
||||
@ -25,6 +25,7 @@ class NextcloudCookbook(Integration):
|
||||
servings=recipe_json['recipeYield'], space=self.request.space)
|
||||
|
||||
# TODO parse times (given in PT2H3M )
|
||||
# @vabene check recipe_url_import.iso_duration_to_minutes I think it does what you are looking for
|
||||
# TODO parse keywords
|
||||
|
||||
ingredients_added = False
|
||||
|
@ -1,11 +1,8 @@
|
||||
import json
|
||||
import re
|
||||
|
||||
from django.utils.translation import gettext as _
|
||||
|
||||
from cookbook.helper.ingredient_parser import parse, get_food, get_unit
|
||||
from cookbook.helper.ingredient_parser import get_food, get_unit
|
||||
from cookbook.integration.integration import Integration
|
||||
from cookbook.models import Recipe, Step, Food, Unit, Ingredient
|
||||
from cookbook.models import Recipe, Step, Ingredient
|
||||
|
||||
|
||||
class OpenEats(Integration):
|
||||
|
@ -16,7 +16,7 @@ class Paprika(Integration):
|
||||
raise NotImplementedError('Method not implemented in storage integration')
|
||||
|
||||
def get_recipe_from_file(self, file):
|
||||
with gzip.open(file, 'r') as recipe_zip:
|
||||
with gzip.open(file, 'r') as recipe_zip:
|
||||
recipe_json = json.loads(recipe_zip.read().decode("utf-8"))
|
||||
|
||||
recipe = Recipe.objects.create(
|
||||
|
@ -1,16 +1,14 @@
|
||||
import re
|
||||
import json
|
||||
import base64
|
||||
import requests
|
||||
from io import BytesIO
|
||||
from zipfile import ZipFile
|
||||
import imghdr
|
||||
from django.utils.translation import gettext as _
|
||||
|
||||
from cookbook.helper.image_processing import get_filetype
|
||||
from cookbook.helper.ingredient_parser import parse, get_food, get_unit
|
||||
from cookbook.integration.integration import Integration
|
||||
from cookbook.models import Recipe, Step, Food, Unit, Ingredient, Keyword
|
||||
from cookbook.models import Recipe, Step, Ingredient, Keyword
|
||||
|
||||
|
||||
class RecetteTek(Integration):
|
||||
@ -108,7 +106,7 @@ class RecetteTek(Integration):
|
||||
recipe.keywords.add(k)
|
||||
recipe.save()
|
||||
except Exception as e:
|
||||
pass
|
||||
print(recipe.name, ': failed to parse keywords ', str(e))
|
||||
|
||||
# TODO: Parse Nutritional Information
|
||||
|
||||
@ -123,7 +121,7 @@ class RecetteTek(Integration):
|
||||
else:
|
||||
if file['originalPicture'] != '':
|
||||
response = requests.get(file['originalPicture'])
|
||||
if imghdr.what(BytesIO(response.content)) != None:
|
||||
if imghdr.what(BytesIO(response.content)) is not None:
|
||||
self.import_recipe_image(recipe, BytesIO(response.content), filetype=get_filetype(file['originalPicture']))
|
||||
else:
|
||||
raise Exception("Original image failed to download.")
|
||||
|
@ -3,12 +3,10 @@ from bs4 import BeautifulSoup
|
||||
from io import BytesIO
|
||||
from zipfile import ZipFile
|
||||
|
||||
from django.utils.translation import gettext as _
|
||||
|
||||
from cookbook.helper.ingredient_parser import parse, get_food, get_unit
|
||||
from cookbook.helper.recipe_url_import import parse_servings, iso_duration_to_minutes
|
||||
from cookbook.integration.integration import Integration
|
||||
from cookbook.models import Recipe, Step, Food, Unit, Ingredient, Keyword
|
||||
from cookbook.models import Recipe, Step, Ingredient, Keyword
|
||||
|
||||
|
||||
class RecipeKeeper(Integration):
|
||||
@ -61,7 +59,6 @@ class RecipeKeeper(Integration):
|
||||
if file.find("span", {"itemprop": "recipeSource"}).text != '':
|
||||
step.instruction += "\n\nImported from: " + file.find("span", {"itemprop": "recipeSource"}).text
|
||||
step.save()
|
||||
source_url_added = True
|
||||
|
||||
recipe.steps.add(step)
|
||||
|
||||
@ -72,7 +69,7 @@ class RecipeKeeper(Integration):
|
||||
import_zip = ZipFile(f['file'])
|
||||
self.import_recipe_image(recipe, BytesIO(import_zip.read(file.find("img", class_="recipe-photo").get("src"))), filetype='.jpeg')
|
||||
except Exception as e:
|
||||
pass
|
||||
print(recipe.name, ': failed to import image ', str(e))
|
||||
|
||||
return recipe
|
||||
|
||||
|
@ -1,9 +1,7 @@
|
||||
import base64
|
||||
import json
|
||||
from io import BytesIO
|
||||
|
||||
import requests
|
||||
from rest_framework.renderers import JSONRenderer
|
||||
|
||||
from cookbook.helper.ingredient_parser import parse, get_food, get_unit
|
||||
from cookbook.integration.integration import Integration
|
||||
|
@ -1,11 +1,6 @@
|
||||
import json
|
||||
import re
|
||||
from io import BytesIO
|
||||
from zipfile import ZipFile
|
||||
|
||||
from cookbook.helper.ingredient_parser import parse, get_food, get_unit
|
||||
from cookbook.integration.integration import Integration
|
||||
from cookbook.models import Recipe, Step, Food, Unit, Ingredient, Keyword
|
||||
from cookbook.models import Recipe, Step, Ingredient, Keyword
|
||||
|
||||
|
||||
class RezKonv(Integration):
|
||||
|
@ -2,7 +2,7 @@ from django.utils.translation import gettext as _
|
||||
|
||||
from cookbook.helper.ingredient_parser import parse, get_food, get_unit
|
||||
from cookbook.integration.integration import Integration
|
||||
from cookbook.models import Recipe, Step, Food, Unit, Ingredient
|
||||
from cookbook.models import Recipe, Step, Ingredient
|
||||
|
||||
|
||||
class Safron(Integration):
|
||||
|
@ -13,7 +13,7 @@ msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2021-06-12 20:30+0200\n"
|
||||
"PO-Revision-Date: 2021-06-22 09:12+0000\n"
|
||||
"PO-Revision-Date: 2021-07-19 16:40+0000\n"
|
||||
"Last-Translator: Jesse <jesse.kamps@pm.me>\n"
|
||||
"Language-Team: Dutch <http://translate.tandoor.dev/projects/tandoor/"
|
||||
"recipes-backend/nl/>\n"
|
||||
@ -22,7 +22,7 @@ msgstr ""
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=n != 1;\n"
|
||||
"X-Generator: Weblate 4.6.2\n"
|
||||
"X-Generator: Weblate 4.7.1\n"
|
||||
|
||||
#: .\cookbook\filters.py:23 .\cookbook\templates\base.html:98
|
||||
#: .\cookbook\templates\forms\edit_internal_recipe.html:246
|
||||
@ -453,7 +453,7 @@ msgstr "Bewerken"
|
||||
#: .\cookbook\templates\meal_plan.html:277
|
||||
#: .\cookbook\templates\recipes_table.html:90
|
||||
msgid "Delete"
|
||||
msgstr "Verwijderen"
|
||||
msgstr "Verwijder"
|
||||
|
||||
#: .\cookbook\templates\404.html:5
|
||||
msgid "404 Error"
|
||||
@ -1629,7 +1629,7 @@ msgid ""
|
||||
"To join an existing space either enter your invite token or click on the "
|
||||
"invite link the space owner send you."
|
||||
msgstr ""
|
||||
"Om aan te sluiten bij een bestaande ruimte moet je je uitnodigingstoken "
|
||||
"Om je aan te sluiten bij een bestaande ruimte moet je jouw uitnodigingstoken "
|
||||
"invoeren of op de uitnodingslink klikken die je ontvangen hebt."
|
||||
|
||||
#: .\cookbook\templates\no_space_info.html:48
|
||||
@ -1976,7 +1976,7 @@ msgstr "Waarschuwing"
|
||||
#: .\cookbook\templates\system.html:49 .\cookbook\templates\system.html:64
|
||||
#: .\cookbook\templates\system.html:80 .\cookbook\templates\system.html:95
|
||||
msgid "Ok"
|
||||
msgstr "Ok"
|
||||
msgstr "Oké"
|
||||
|
||||
#: .\cookbook\templates\system.html:51
|
||||
msgid ""
|
||||
@ -2098,6 +2098,7 @@ msgstr "Bekijk recept gegevens"
|
||||
#: .\cookbook\templates\url_import.html:147
|
||||
msgid "Drag recipe attributes from the right into the appropriate box below."
|
||||
msgstr ""
|
||||
"Sleep eigenschappen van het recept van rechts naar het juiste vlak beneden."
|
||||
|
||||
#: .\cookbook\templates\url_import.html:156
|
||||
#: .\cookbook\templates\url_import.html:173
|
||||
@ -2110,82 +2111,73 @@ msgstr ""
|
||||
#: .\cookbook\templates\url_import.html:300
|
||||
#: .\cookbook\templates\url_import.html:351
|
||||
msgid "Clear Contents"
|
||||
msgstr ""
|
||||
msgstr "Wis inhoud"
|
||||
|
||||
#: .\cookbook\templates\url_import.html:158
|
||||
msgid "Text dragged here will be appended to the name."
|
||||
msgstr ""
|
||||
msgstr "Hierheen gesleepte tekst wordt aan de naam toegevoegd."
|
||||
|
||||
#: .\cookbook\templates\url_import.html:175
|
||||
msgid "Text dragged here will be appended to the description."
|
||||
msgstr ""
|
||||
msgstr "Hierheen gesleepte tekst wordt aan de beschrijving toegevoegd."
|
||||
|
||||
#: .\cookbook\templates\url_import.html:192
|
||||
msgid "Keywords dragged here will be appended to current list"
|
||||
msgstr ""
|
||||
msgstr "Hierheen gesleepte Etiketten worden aan de huidige lijst toegevoegd"
|
||||
|
||||
#: .\cookbook\templates\url_import.html:207
|
||||
msgid "Image"
|
||||
msgstr ""
|
||||
msgstr "Afbeelding"
|
||||
|
||||
#: .\cookbook\templates\url_import.html:239
|
||||
#, fuzzy
|
||||
#| msgid "Preparation Time"
|
||||
msgid "Prep Time"
|
||||
msgstr "Bereidingstijd"
|
||||
msgstr "Voorbereidingstijd"
|
||||
|
||||
#: .\cookbook\templates\url_import.html:254
|
||||
#, fuzzy
|
||||
#| msgid "Time"
|
||||
msgid "Cook Time"
|
||||
msgstr "Tijd"
|
||||
msgstr "Kooktijd"
|
||||
|
||||
#: .\cookbook\templates\url_import.html:275
|
||||
msgid "Ingredients dragged here will be appended to current list."
|
||||
msgstr ""
|
||||
msgstr "Hierheen gesleepte Ingrediënten worden aan de huidige lijst toegevoegd."
|
||||
|
||||
#: .\cookbook\templates\url_import.html:302
|
||||
msgid ""
|
||||
"Recipe instructions dragged here will be appended to current instructions."
|
||||
msgstr ""
|
||||
"Hierheen gesleepte Recept instructies worden aan de huidige lijst toegevoegd."
|
||||
|
||||
#: .\cookbook\templates\url_import.html:325
|
||||
#, fuzzy
|
||||
#| msgid "Discovered Recipes"
|
||||
msgid "Discovered Attributes"
|
||||
msgstr "Ontdekte recepten"
|
||||
msgstr "Ontdekte Eigenschappen"
|
||||
|
||||
#: .\cookbook\templates\url_import.html:327
|
||||
msgid ""
|
||||
"Drag recipe attributes from below into the appropriate box on the left. "
|
||||
"Click any node to display its full properties."
|
||||
msgstr ""
|
||||
"Sleep recept eigenschappen van beneden naar de juiste doos aan de "
|
||||
"linkerzijde. Klik er op om alle eigenschappen te zien."
|
||||
|
||||
#: .\cookbook\templates\url_import.html:344
|
||||
#, fuzzy
|
||||
#| msgid "Show as header"
|
||||
msgid "Show Blank Field"
|
||||
msgstr "Laat als kop zien"
|
||||
msgstr "Toon Leeg Veld"
|
||||
|
||||
#: .\cookbook\templates\url_import.html:349
|
||||
msgid "Blank Field"
|
||||
msgstr ""
|
||||
msgstr "Leeg Veld"
|
||||
|
||||
#: .\cookbook\templates\url_import.html:353
|
||||
msgid "Items dragged to Blank Field will be appended."
|
||||
msgstr ""
|
||||
msgstr "Naar Leeg Veld gesleepte items worden toegevoegd."
|
||||
|
||||
#: .\cookbook\templates\url_import.html:400
|
||||
#, fuzzy
|
||||
#| msgid "Delete Step"
|
||||
msgid "Delete Text"
|
||||
msgstr "Verwijder stap"
|
||||
msgstr "Verwijder tekst"
|
||||
|
||||
#: .\cookbook\templates\url_import.html:413
|
||||
#, fuzzy
|
||||
#| msgid "Delete Recipe"
|
||||
msgid "Delete image"
|
||||
msgstr "Verwijder recept"
|
||||
msgstr "Verwijder afbeelding"
|
||||
|
||||
#: .\cookbook\templates\url_import.html:429
|
||||
msgid "Recipe Name"
|
||||
@ -2261,7 +2253,7 @@ msgstr "Er is een fout opgetreden bij het synchroniseren met Opslag"
|
||||
|
||||
#: .\cookbook\views\api.py:649
|
||||
msgid "Nothing to do."
|
||||
msgstr ""
|
||||
msgstr "Niks te doen."
|
||||
|
||||
#: .\cookbook\views\api.py:664
|
||||
msgid "The requested site provided malformed data and cannot be read."
|
||||
@ -2282,26 +2274,24 @@ msgstr ""
|
||||
"te importeren."
|
||||
|
||||
#: .\cookbook\views\api.py:694
|
||||
#, fuzzy
|
||||
#| msgid "The requested page could not be found."
|
||||
msgid "No useable data could be found."
|
||||
msgstr "De opgevraagde pagina kon niet gevonden worden."
|
||||
msgstr "Er is geen bruikbare data gevonden."
|
||||
|
||||
#: .\cookbook\views\api.py:710
|
||||
msgid "I couldn't find anything to do."
|
||||
msgstr ""
|
||||
msgstr "Ik kon niks vinden om te doen."
|
||||
|
||||
#: .\cookbook\views\data.py:30 .\cookbook\views\data.py:121
|
||||
#: .\cookbook\views\edit.py:50 .\cookbook\views\import_export.py:67
|
||||
#: .\cookbook\views\new.py:32
|
||||
msgid "You have reached the maximum number of recipes for your space."
|
||||
msgstr ""
|
||||
msgstr "Je hebt het maximaal aantal recepten voor jouw ruimte bereikt."
|
||||
|
||||
#: .\cookbook\views\data.py:34 .\cookbook\views\data.py:125
|
||||
#: .\cookbook\views\edit.py:54 .\cookbook\views\import_export.py:71
|
||||
#: .\cookbook\views\new.py:36
|
||||
msgid "You have more users than allowed in your space."
|
||||
msgstr ""
|
||||
msgstr "Je hebt meer gebruikers dan toegestaan in jouw ruimte."
|
||||
|
||||
#: .\cookbook\views\data.py:103
|
||||
#, python-format
|
||||
@ -2409,57 +2399,64 @@ msgstr "Er is een fout opgetreden bij het importeren van dit recept!"
|
||||
|
||||
#: .\cookbook\views\new.py:226
|
||||
msgid "Hello"
|
||||
msgstr ""
|
||||
msgstr "Hallo"
|
||||
|
||||
#: .\cookbook\views\new.py:226
|
||||
msgid "You have been invited by "
|
||||
msgstr ""
|
||||
msgstr "Je bent uitgenodigd door "
|
||||
|
||||
#: .\cookbook\views\new.py:227
|
||||
msgid " to join their Tandoor Recipes space "
|
||||
msgstr ""
|
||||
msgstr " om zijn/haar Tandoor Recepten ruimte "
|
||||
|
||||
#: .\cookbook\views\new.py:228
|
||||
msgid "Click the following link to activate your account: "
|
||||
msgstr ""
|
||||
msgstr "Klik om de volgende link om je account te activeren: "
|
||||
|
||||
#: .\cookbook\views\new.py:229
|
||||
msgid ""
|
||||
"If the link does not work use the following code to manually join the space: "
|
||||
msgstr ""
|
||||
"Als de linkt niet werkt, gebruik dan de volgende code om handmatig tot de "
|
||||
"ruimte toe te treden: "
|
||||
|
||||
#: .\cookbook\views\new.py:230
|
||||
msgid "The invitation is valid until "
|
||||
msgstr ""
|
||||
msgstr "De uitnodiging is geldig tot "
|
||||
|
||||
#: .\cookbook\views\new.py:231
|
||||
msgid ""
|
||||
"Tandoor Recipes is an Open Source recipe manager. Check it out on GitHub "
|
||||
msgstr ""
|
||||
"Tandoor Recepten is een Open Source recepten manager. Bekijk het op GitHub "
|
||||
|
||||
#: .\cookbook\views\new.py:234
|
||||
msgid "Tandoor Recipes Invite"
|
||||
msgstr ""
|
||||
msgstr "Tandoor Recepten uitnodiging"
|
||||
|
||||
#: .\cookbook\views\new.py:241
|
||||
msgid "Invite link successfully send to user."
|
||||
msgstr ""
|
||||
msgstr "Uitnodigingslink succesvol verstuurd naar gebruiker."
|
||||
|
||||
#: .\cookbook\views\new.py:244
|
||||
msgid ""
|
||||
"You have send to many emails, please share the link manually or wait a few "
|
||||
"hours."
|
||||
msgstr ""
|
||||
"Je hebt te veel e-mails verstuurd, deel de link handmatig of wacht enkele "
|
||||
"uren."
|
||||
|
||||
#: .\cookbook\views\new.py:246
|
||||
msgid "Email to user could not be send, please share link manually."
|
||||
msgstr ""
|
||||
msgstr "E-mail aan gebruiker kon niet verzonden worden, deel de link handmatig."
|
||||
|
||||
#: .\cookbook\views\views.py:125
|
||||
msgid ""
|
||||
"You have successfully created your own recipe space. Start by adding some "
|
||||
"recipes or invite other people to join you."
|
||||
msgstr ""
|
||||
"Je hebt je eigen recepten ruimte succesvol aangemaakt. Start met het "
|
||||
"toevoegen van recepten of nodig anderen uit om je te vergezellen."
|
||||
|
||||
#: .\cookbook\views\views.py:173
|
||||
msgid "You do not have the required permissions to perform this action!"
|
||||
@ -2493,14 +2490,12 @@ msgid "Malformed Invite Link supplied!"
|
||||
msgstr "Onjuiste uitnodigingslink opgegeven!"
|
||||
|
||||
#: .\cookbook\views\views.py:441
|
||||
#, fuzzy
|
||||
#| msgid "You are not logged in and therefore cannot view this page!"
|
||||
msgid "You are already member of a space and therefore cannot join this one."
|
||||
msgstr "Je bent niet ingelogd en kan deze pagina daarom niet bekijken!"
|
||||
msgstr "Je bent al lid van een ruimte en kan daardoor niet toetreden tot deze."
|
||||
|
||||
#: .\cookbook\views\views.py:452
|
||||
msgid "Successfully joined space."
|
||||
msgstr ""
|
||||
msgstr "Succesvol toegetreden tot ruimte."
|
||||
|
||||
#: .\cookbook\views\views.py:458
|
||||
msgid "Invite Link not valid or already used!"
|
||||
|
@ -27,5 +27,5 @@ class Command(BaseCommand):
|
||||
Step.objects.all().update(search_vector=SearchVector('instruction__unaccent', weight='B', config=language))
|
||||
|
||||
self.stdout.write(self.style.SUCCESS(_('Recipe index rebuild complete.')))
|
||||
except:
|
||||
except Exception:
|
||||
self.stdout.write(self.style.ERROR(_('Recipe index rebuild failed.')))
|
||||
|
@ -1,12 +1,11 @@
|
||||
from django.contrib.postgres.aggregates import StringAgg
|
||||
from django.contrib.postgres.search import (
|
||||
SearchQuery, SearchRank, SearchVector, TrigramSimilarity,
|
||||
SearchQuery, SearchRank, SearchVector,
|
||||
)
|
||||
from django.db import models
|
||||
from django.db.models import Q
|
||||
from django.utils import translation
|
||||
|
||||
# TODO move this somewhere else and delete this file
|
||||
DICTIONARY = {
|
||||
# TODO find custom dictionaries - maybe from here https://www.postgresql.org/message-id/CAF4Au4x6X_wSXFwsQYE8q5o0aQZANrvYjZJ8uOnsiHDnOVPPEg%40mail.gmail.com
|
||||
# 'hy': 'Armenian',
|
||||
@ -22,8 +21,6 @@ DICTIONARY = {
|
||||
}
|
||||
|
||||
|
||||
# TODO add search highlighting
|
||||
# TODO add language support
|
||||
# TODO add schedule index rebuild
|
||||
class RecipeSearchManager(models.Manager):
|
||||
def search(self, search_text, space):
|
||||
|
24
cookbook/migrations/0141_auto_20210713_1042.py
Normal file
24
cookbook/migrations/0141_auto_20210713_1042.py
Normal file
@ -0,0 +1,24 @@
|
||||
# Generated by Django 3.2.5 on 2021-07-13 08:42
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('cookbook', '0140_userpreference_created_at'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='step',
|
||||
name='step_recipe',
|
||||
field=models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.PROTECT, to='cookbook.recipe'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='step',
|
||||
name='type',
|
||||
field=models.CharField(choices=[('TEXT', 'Text'), ('TIME', 'Time'), ('FILE', 'File'), ('RECIPE', 'Recipe')], default='TEXT', max_length=16),
|
||||
),
|
||||
]
|
@ -27,7 +27,7 @@ def set_default_search_vector(apps, schema_editor):
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
('cookbook', '0141_keyword_to_tree'),
|
||||
('cookbook', '0141_auto_20210713_1042'),
|
||||
]
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
|
70
cookbook/migrations/0146_keyword_to_tree.py
Normal file
70
cookbook/migrations/0146_keyword_to_tree.py
Normal file
@ -0,0 +1,70 @@
|
||||
# Generated by Django 3.1.7 on 2021-03-30 19:42
|
||||
|
||||
from treebeard.mp_tree import MP_Node
|
||||
from django.db import migrations, models
|
||||
from django_scopes import scopes_disabled
|
||||
# update if needed
|
||||
steplen = MP_Node.steplen
|
||||
alphabet = MP_Node.alphabet
|
||||
node_order_by = ["name"]
|
||||
|
||||
|
||||
def update_paths(apps, schema_editor):
|
||||
with scopes_disabled():
|
||||
Node = apps.get_model("cookbook", "Keyword")
|
||||
nodes = Node.objects.all().order_by(*node_order_by)
|
||||
for i, node in enumerate(nodes, 1):
|
||||
# for default values, this resolves to: "{:04d}".format(i)
|
||||
node.path = f"{{:{alphabet[0]}{steplen}d}}".format(i)
|
||||
if nodes:
|
||||
Node.objects.bulk_update(nodes, ["path"])
|
||||
|
||||
|
||||
def backwards(apps, schema_editor):
|
||||
"""nothing to do"""
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('cookbook', '0145_alter_userpreference_use_fractions'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='keyword',
|
||||
name='depth',
|
||||
field=models.PositiveIntegerField(default=1),
|
||||
preserve_default=False,
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='keyword',
|
||||
name='numchild',
|
||||
field=models.PositiveIntegerField(default=0),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='keyword',
|
||||
name='path',
|
||||
field=models.CharField(default="", max_length=255, unique=False),
|
||||
preserve_default=False,
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='userpreference',
|
||||
name='use_fractions',
|
||||
field=models.BooleanField(default=True),
|
||||
),
|
||||
migrations.RunPython(update_paths, backwards),
|
||||
migrations.AlterField(
|
||||
model_name="keyword",
|
||||
name="path",
|
||||
field=models.CharField(max_length=255, unique=True),
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='keyword',
|
||||
unique_together=set(),
|
||||
),
|
||||
migrations.AddConstraint(
|
||||
model_name='keyword',
|
||||
constraint=models.UniqueConstraint(fields=('space', 'name'), name='unique_name_per_space'),
|
||||
),
|
||||
]
|
66
cookbook/migrations/0147_auto_20210813_1829.py
Normal file
66
cookbook/migrations/0147_auto_20210813_1829.py
Normal file
@ -0,0 +1,66 @@
|
||||
# Generated by Django 3.2.5 on 2021-08-13 16:29
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('cookbook', '0146_keyword_to_tree'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveConstraint(
|
||||
model_name='keyword',
|
||||
name='unique_name_per_space',
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='userpreference',
|
||||
name='use_fractions',
|
||||
field=models.BooleanField(default=False),
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='food',
|
||||
unique_together=set(),
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='recipebookentry',
|
||||
unique_together=set(),
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='supermarket',
|
||||
unique_together=set(),
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='supermarketcategory',
|
||||
unique_together=set(),
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='unit',
|
||||
unique_together=set(),
|
||||
),
|
||||
migrations.AddConstraint(
|
||||
model_name='food',
|
||||
constraint=models.UniqueConstraint(fields=('space', 'name'), name='f_unique_name_per_space'),
|
||||
),
|
||||
migrations.AddConstraint(
|
||||
model_name='keyword',
|
||||
constraint=models.UniqueConstraint(fields=('space', 'name'), name='kw_unique_name_per_space'),
|
||||
),
|
||||
migrations.AddConstraint(
|
||||
model_name='recipebookentry',
|
||||
constraint=models.UniqueConstraint(fields=('recipe', 'book'), name='rbe_unique_name_per_space'),
|
||||
),
|
||||
migrations.AddConstraint(
|
||||
model_name='supermarket',
|
||||
constraint=models.UniqueConstraint(fields=('space', 'name'), name='sm_unique_name_per_space'),
|
||||
),
|
||||
migrations.AddConstraint(
|
||||
model_name='supermarketcategory',
|
||||
constraint=models.UniqueConstraint(fields=('space', 'name'), name='smc_unique_name_per_space'),
|
||||
),
|
||||
migrations.AddConstraint(
|
||||
model_name='unit',
|
||||
constraint=models.UniqueConstraint(fields=('space', 'name'), name='u_unique_name_per_space'),
|
||||
),
|
||||
]
|
31
cookbook/migrations/0148_fix_leading_trailing_spaces.py
Normal file
31
cookbook/migrations/0148_fix_leading_trailing_spaces.py
Normal file
@ -0,0 +1,31 @@
|
||||
from django.db import migrations, models
|
||||
from django_scopes import scopes_disabled
|
||||
models = ["Keyword", "Food", "Unit"]
|
||||
|
||||
def update_paths(apps, schema_editor):
|
||||
with scopes_disabled():
|
||||
for model in models:
|
||||
Node = apps.get_model("cookbook", model)
|
||||
nodes = Node.objects.all().filter(name__startswith=" ")
|
||||
for i in nodes:
|
||||
i.name = "_" + i.name
|
||||
i.save()
|
||||
nodes = Node.objects.all().filter(name__endswith=" ")
|
||||
for i in nodes:
|
||||
i.name = i.name + "_"
|
||||
i.save()
|
||||
|
||||
|
||||
def backwards(apps, schema_editor):
|
||||
"""nothing to do"""
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('cookbook', '0147_auto_20210813_1829'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(update_paths, backwards),
|
||||
]
|
@ -37,8 +37,20 @@ def get_model_name(model):
|
||||
return ('_'.join(re.findall('[A-Z][^A-Z]*', model.__name__))).lower()
|
||||
|
||||
|
||||
class PermissionModelMixin:
|
||||
class TreeManager(MP_NodeManager):
|
||||
def get_or_create(self, **kwargs):
|
||||
# model.Manager get_or_create() is not compatible with MP_Tree
|
||||
kwargs['name'] = kwargs['name'].strip()
|
||||
q = self.filter(name__iexact=kwargs['name'], space=kwargs['space'])
|
||||
if len(q) != 0:
|
||||
return q[0], False
|
||||
else:
|
||||
with scopes_disabled():
|
||||
node = self.model.add_root(**kwargs)
|
||||
return node, True
|
||||
|
||||
|
||||
class PermissionModelMixin:
|
||||
@staticmethod
|
||||
def get_space_key():
|
||||
return ('space',)
|
||||
@ -217,8 +229,9 @@ class SupermarketCategory(models.Model, PermissionModelMixin):
|
||||
return self.name
|
||||
|
||||
class Meta:
|
||||
# TODO according to this https://docs.djangoproject.com/en/3.1/ref/models/options/#unique-together should not be used
|
||||
unique_together = (('space', 'name'),)
|
||||
constraints = [
|
||||
models.UniqueConstraint(fields=['space', 'name'], name='smc_unique_name_per_space')
|
||||
]
|
||||
|
||||
|
||||
class Supermarket(models.Model, PermissionModelMixin):
|
||||
@ -233,8 +246,9 @@ class Supermarket(models.Model, PermissionModelMixin):
|
||||
return self.name
|
||||
|
||||
class Meta:
|
||||
# TODO according to this https://docs.djangoproject.com/en/3.1/ref/models/options/#unique-together should not be used
|
||||
unique_together = (('space', 'name'),)
|
||||
constraints = [
|
||||
models.UniqueConstraint(fields=['space', 'name'], name='sm_unique_name_per_space')
|
||||
]
|
||||
|
||||
|
||||
class SupermarketCategoryRelation(models.Model, PermissionModelMixin):
|
||||
@ -265,7 +279,7 @@ class SyncLog(models.Model, PermissionModelMixin):
|
||||
|
||||
|
||||
class Keyword(ExportModelOperationsMixin('keyword'), MP_Node, PermissionModelMixin):
|
||||
# TODO create get_or_create method
|
||||
# TODO add find and fix problem functions
|
||||
node_order_by = ['name']
|
||||
name = models.CharField(max_length=64)
|
||||
icon = models.CharField(max_length=16, blank=True, null=True)
|
||||
@ -274,7 +288,7 @@ class Keyword(ExportModelOperationsMixin('keyword'), MP_Node, PermissionModelMix
|
||||
updated_at = models.DateTimeField(auto_now=True)
|
||||
|
||||
space = models.ForeignKey(Space, on_delete=models.CASCADE)
|
||||
objects = ScopedManager(space='space', _manager_class=MP_NodeManager)
|
||||
objects = ScopedManager(space='space', _manager_class=TreeManager)
|
||||
|
||||
_full_name_separator = ' > '
|
||||
|
||||
@ -291,19 +305,6 @@ class Keyword(ExportModelOperationsMixin('keyword'), MP_Node, PermissionModelMix
|
||||
return self.get_parent().id
|
||||
return None
|
||||
|
||||
@classmethod
|
||||
def get_or_create(self, **kwargs):
|
||||
# an attempt to mimic get_or_create functionality with Keywords
|
||||
# function attempts to get the keyword,
|
||||
# if the length of the return is 0 will add a root node
|
||||
kwargs['name'] = kwargs['name'].strip()
|
||||
q = self.get_tree().filter(name=kwargs['name'], space=kwargs['space'])
|
||||
if len(q) != 0:
|
||||
return q[0], False
|
||||
else:
|
||||
kw = Keyword.add_root(**kwargs)
|
||||
return kw, True
|
||||
|
||||
@property
|
||||
def full_name(self):
|
||||
"""
|
||||
@ -337,6 +338,7 @@ class Keyword(ExportModelOperationsMixin('keyword'), MP_Node, PermissionModelMix
|
||||
def get_num_children(self):
|
||||
return self.get_children().count()
|
||||
|
||||
# use self.objects.get_or_create() instead
|
||||
@classmethod
|
||||
def add_root(self, **kwargs):
|
||||
with scopes_disabled():
|
||||
@ -344,7 +346,7 @@ class Keyword(ExportModelOperationsMixin('keyword'), MP_Node, PermissionModelMix
|
||||
|
||||
class Meta:
|
||||
constraints = [
|
||||
models.UniqueConstraint(fields=['space', 'name'], name='unique_name_per_space')
|
||||
models.UniqueConstraint(fields=['space', 'name'], name='kw_unique_name_per_space')
|
||||
]
|
||||
indexes = (Index(fields=['id', 'name']),)
|
||||
|
||||
@ -360,8 +362,9 @@ class Unit(ExportModelOperationsMixin('unit'), models.Model, PermissionModelMixi
|
||||
return self.name
|
||||
|
||||
class Meta:
|
||||
# TODO according to this https://docs.djangoproject.com/en/3.1/ref/models/options/#unique-together should not be used
|
||||
unique_together = (('space', 'name'),)
|
||||
constraints = [
|
||||
models.UniqueConstraint(fields=['space', 'name'], name='u_unique_name_per_space')
|
||||
]
|
||||
|
||||
|
||||
class Food(ExportModelOperationsMixin('food'), models.Model, PermissionModelMixin):
|
||||
@ -378,8 +381,9 @@ class Food(ExportModelOperationsMixin('food'), models.Model, PermissionModelMixi
|
||||
return self.name
|
||||
|
||||
class Meta:
|
||||
# TODO according to this https://docs.djangoproject.com/en/3.1/ref/models/options/#unique-together should not be used
|
||||
unique_together = (('space', 'name'),)
|
||||
constraints = [
|
||||
models.UniqueConstraint(fields=['space', 'name'], name='f_unique_name_per_space')
|
||||
]
|
||||
indexes = (Index(fields=['id', 'name']),)
|
||||
|
||||
|
||||
@ -407,10 +411,11 @@ class Step(ExportModelOperationsMixin('step'), models.Model, PermissionModelMixi
|
||||
TEXT = 'TEXT'
|
||||
TIME = 'TIME'
|
||||
FILE = 'FILE'
|
||||
RECIPE = 'RECIPE'
|
||||
|
||||
name = models.CharField(max_length=128, default='', blank=True)
|
||||
type = models.CharField(
|
||||
choices=((TEXT, _('Text')), (TIME, _('Time')), (FILE, _('File')),),
|
||||
choices=((TEXT, _('Text')), (TIME, _('Time')), (FILE, _('File')), (RECIPE, _('Recipe')),),
|
||||
default=TEXT,
|
||||
max_length=16
|
||||
)
|
||||
@ -421,6 +426,7 @@ class Step(ExportModelOperationsMixin('step'), models.Model, PermissionModelMixi
|
||||
file = models.ForeignKey('UserFile', on_delete=models.PROTECT, null=True, blank=True)
|
||||
show_as_header = models.BooleanField(default=True)
|
||||
search_vector = SearchVectorField(null=True)
|
||||
step_recipe = models.ForeignKey('Recipe', default=None, blank=True, null=True, on_delete=models.PROTECT)
|
||||
|
||||
space = models.ForeignKey(Space, on_delete=models.CASCADE)
|
||||
objects = ScopedManager(space='space')
|
||||
@ -561,8 +567,9 @@ class RecipeBookEntry(ExportModelOperationsMixin('book_entry'), models.Model, Pe
|
||||
return None
|
||||
|
||||
class Meta:
|
||||
# TODO according to this https://docs.djangoproject.com/en/3.1/ref/models/options/#unique-together should not be used
|
||||
unique_together = (('recipe', 'book'),)
|
||||
constraints = [
|
||||
models.UniqueConstraint(fields=['recipe', 'book'], name='rbe_unique_name_per_space')
|
||||
]
|
||||
|
||||
|
||||
class MealType(models.Model, PermissionModelMixin):
|
||||
|
@ -1,6 +1,5 @@
|
||||
import io
|
||||
import os
|
||||
import tempfile
|
||||
from datetime import datetime
|
||||
from os import listdir
|
||||
from os.path import isfile, join
|
||||
|
@ -4,7 +4,6 @@ from rest_framework.schemas.utils import is_list_view
|
||||
|
||||
# TODO move to separate class to cleanup
|
||||
class RecipeSchema(AutoSchema):
|
||||
|
||||
def get_path_parameters(self, path, method):
|
||||
if not is_list_view(path, method, self.view):
|
||||
return super(RecipeSchema, self).get_path_parameters(path, method)
|
||||
@ -55,10 +54,14 @@ class RecipeSchema(AutoSchema):
|
||||
"description": 'true or false. returns the results in randomized order.',
|
||||
'schema': {'type': 'string', },
|
||||
})
|
||||
parameters.append({
|
||||
"name": 'new', "in": "query", "required": False,
|
||||
"description": 'true or false. returns new results first in search results',
|
||||
'schema': {'type': 'string', },
|
||||
})
|
||||
return parameters
|
||||
|
||||
|
||||
# TODO move to separate class to cleanup
|
||||
class TreeSchema(AutoSchema):
|
||||
|
||||
def get_path_parameters(self, path, method):
|
||||
|
@ -47,7 +47,7 @@ class CustomDecimalField(serializers.Field):
|
||||
class SpaceFilterSerializer(serializers.ListSerializer):
|
||||
|
||||
def to_representation(self, data):
|
||||
if (type(data) == QuerySet and data.query.is_sliced) or type(data) == MP_NodeQuerySet:
|
||||
if (type(data) == QuerySet and data.query.is_sliced):
|
||||
# if query is sliced it came from api request not nested serializer
|
||||
return super().to_representation(data)
|
||||
if self.child.Meta.model == User:
|
||||
@ -209,29 +209,30 @@ class KeywordSerializer(UniqueFieldsMixin, serializers.ModelSerializer):
|
||||
return str(obj)
|
||||
|
||||
def get_image(self, obj):
|
||||
recipes = obj.recipe_set.all().exclude(image__isnull=True).exclude(image__exact='')
|
||||
recipes = obj.recipe_set.all().filter(space=obj.space).exclude(image__isnull=True).exclude(image__exact='')
|
||||
if len(recipes) == 0:
|
||||
recipes = Recipe.objects.filter(keywords__in=Keyword.get_tree(obj)).exclude(image__isnull=True).exclude(image__exact='') # if no recipes found - check whole tree
|
||||
recipes = Recipe.objects.filter(keywords__in=obj.get_tree(), space=obj.space).exclude(image__isnull=True).exclude(image__exact='') # if no recipes found - check whole tree
|
||||
if len(recipes) != 0:
|
||||
return random.choice(recipes).image.url
|
||||
else:
|
||||
return None
|
||||
|
||||
def count_recipes(self, obj):
|
||||
return obj.recipe_set.all().count()
|
||||
return obj.recipe_set.filter(space=self.context['request'].space).all().count()
|
||||
|
||||
def create(self, validated_data):
|
||||
# since multi select tags dont have id's
|
||||
# duplicate names might be routed to create
|
||||
validated_data['name'] = validated_data['name'].strip()
|
||||
validated_data['space'] = self.context['request'].space
|
||||
obj, created = Keyword.get_or_create(**validated_data)
|
||||
obj, created = Keyword.objects.get_or_create(**validated_data)
|
||||
return obj
|
||||
|
||||
class Meta:
|
||||
# list_serializer_class = SpaceFilterSerializer
|
||||
model = Keyword
|
||||
fields = ('id', 'name', 'icon', 'label', 'description', 'image', 'parent', 'numchild', 'numrecipe', 'created_at', 'updated_at')
|
||||
read_only_fields = ('id', 'numchild',)
|
||||
read_only_fields = ('id', 'numchild', 'parent', 'image')
|
||||
|
||||
|
||||
class UnitSerializer(UniqueFieldsMixin, serializers.ModelSerializer):
|
||||
@ -264,7 +265,7 @@ class SupermarketCategorySerializer(UniqueFieldsMixin, WritableNestedModelSerial
|
||||
fields = ('id', 'name')
|
||||
|
||||
|
||||
class SupermarketCategoryRelationSerializer(SpacedModelSerializer):
|
||||
class SupermarketCategoryRelationSerializer(WritableNestedModelSerializer):
|
||||
category = SupermarketCategorySerializer()
|
||||
|
||||
class Meta:
|
||||
@ -284,7 +285,9 @@ class FoodSerializer(UniqueFieldsMixin, WritableNestedModelSerializer):
|
||||
supermarket_category = SupermarketCategorySerializer(allow_null=True, required=False)
|
||||
|
||||
def create(self, validated_data):
|
||||
obj, created = Food.objects.get_or_create(name=validated_data['name'].strip(), space=self.context['request'].space)
|
||||
validated_data['name'] = validated_data['name'].strip()
|
||||
validated_data['space'] = self.context['request'].space
|
||||
obj, created = Food.objects.get_or_create(validated_data)
|
||||
return obj
|
||||
|
||||
def update(self, instance, validated_data):
|
||||
@ -318,6 +321,7 @@ class StepSerializer(WritableNestedModelSerializer):
|
||||
ingredients_markdown = serializers.SerializerMethodField('get_ingredients_markdown')
|
||||
ingredients_vue = serializers.SerializerMethodField('get_ingredients_vue')
|
||||
file = UserFileViewSerializer(allow_null=True, required=False)
|
||||
step_recipe_data = serializers.SerializerMethodField('get_step_recipe_data')
|
||||
|
||||
def create(self, validated_data):
|
||||
validated_data['space'] = self.context['request'].space
|
||||
@ -329,11 +333,27 @@ class StepSerializer(WritableNestedModelSerializer):
|
||||
def get_ingredients_markdown(self, obj):
|
||||
return obj.get_instruction_render()
|
||||
|
||||
def get_step_recipe_data(self, obj):
|
||||
# check if root type is recipe to prevent infinite recursion
|
||||
# can be improved later to allow multi level embedding
|
||||
if obj.step_recipe and type(self.parent.root) == RecipeSerializer:
|
||||
return StepRecipeSerializer(obj.step_recipe).data
|
||||
|
||||
class Meta:
|
||||
model = Step
|
||||
fields = (
|
||||
'id', 'name', 'type', 'instruction', 'ingredients', 'ingredients_markdown',
|
||||
'ingredients_vue', 'time', 'order', 'show_as_header', 'file',
|
||||
'ingredients_vue', 'time', 'order', 'show_as_header', 'file', 'step_recipe', 'step_recipe_data'
|
||||
)
|
||||
|
||||
|
||||
class StepRecipeSerializer(WritableNestedModelSerializer):
|
||||
steps = StepSerializer(many=True)
|
||||
|
||||
class Meta:
|
||||
model = Recipe
|
||||
fields = (
|
||||
'id', 'name', 'steps',
|
||||
)
|
||||
|
||||
|
||||
@ -439,9 +459,11 @@ class RecipeBookEntrySerializer(serializers.ModelSerializer):
|
||||
|
||||
def create(self, validated_data):
|
||||
book = validated_data['book']
|
||||
recipe = validated_data['recipe']
|
||||
if not book.get_owner() == self.context['request'].user:
|
||||
raise NotFound(detail=None, code=None)
|
||||
return super().create(validated_data)
|
||||
obj, created = RecipeBookEntry.objects.get_or_create(book=book, recipe=recipe)
|
||||
return obj
|
||||
|
||||
class Meta:
|
||||
model = RecipeBookEntry
|
||||
|
File diff suppressed because one or more lines are too long
@ -1 +1 @@
|
||||
.shake[data-v-88855b04]{-webkit-animation:shake-data-v-88855b04 .82s cubic-bezier(.36,.07,.19,.97) both;animation:shake-data-v-88855b04 .82s cubic-bezier(.36,.07,.19,.97) both;transform:translateZ(0);-webkit-backface-visibility:hidden;backface-visibility:hidden;perspective:1000px}@-webkit-keyframes shake-data-v-88855b04{10%,90%{transform:translate3d(-1px,0,0)}20%,80%{transform:translate3d(2px,0,0)}30%,50%,70%{transform:translate3d(-4px,0,0)}40%,60%{transform:translate3d(4px,0,0)}}@keyframes shake-data-v-88855b04{10%,90%{transform:translate3d(-1px,0,0)}20%,80%{transform:translate3d(2px,0,0)}30%,50%,70%{transform:translate3d(-4px,0,0)}40%,60%{transform:translate3d(4px,0,0)}}
|
||||
.shake[data-v-54d4941f]{-webkit-animation:shake-data-v-54d4941f .82s cubic-bezier(.36,.07,.19,.97) both;animation:shake-data-v-54d4941f .82s cubic-bezier(.36,.07,.19,.97) both;transform:translateZ(0);-webkit-backface-visibility:hidden;backface-visibility:hidden;perspective:1000px}@-webkit-keyframes shake-data-v-54d4941f{10%,90%{transform:translate3d(-1px,0,0)}20%,80%{transform:translate3d(2px,0,0)}30%,50%,70%{transform:translate3d(-4px,0,0)}40%,60%{transform:translate3d(4px,0,0)}}@keyframes shake-data-v-54d4941f{10%,90%{transform:translate3d(-1px,0,0)}20%,80%{transform:translate3d(2px,0,0)}30%,50%,70%{transform:translate3d(-4px,0,0)}40%,60%{transform:translate3d(4px,0,0)}}
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -97,6 +97,9 @@
|
||||
<a class="dropdown-item" href="{% url 'list_food' %}"><i
|
||||
class="fas fa-leaf fa-fw"></i> {% trans 'Ingredients' %}
|
||||
</a>
|
||||
<a class="dropdown-item" href="{% url 'view_supermarket' %}"><i
|
||||
class="fas fa-store-alt fa-fw"></i> {% trans 'Supermarket' %}
|
||||
</a>
|
||||
</div>
|
||||
</li>
|
||||
<li class="nav-item dropdown {% if request.resolver_match.url_name in 'list_keyword,data_batch_edit' %}active{% endif %}">
|
||||
|
@ -202,6 +202,7 @@
|
||||
<option value="TEXT">{% trans 'Text' %}</option>
|
||||
<option value="TIME">{% trans 'Time' %}</option>
|
||||
<option value="FILE">{% trans 'File' %}</option>
|
||||
<option value="RECIPE">{% trans 'Recipe' %}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
@ -214,7 +215,7 @@
|
||||
:id="'id_step_' + step.id + '_time'">
|
||||
</div>
|
||||
|
||||
<div class="col-md-9">
|
||||
<div class="col-md-9" v-if="step.type === 'FILE'">
|
||||
<label :for="'id_step_' + step.id + '_file'">{% trans 'File' %}</label>
|
||||
<multiselect
|
||||
v-tabindex
|
||||
@ -235,6 +236,28 @@
|
||||
@search-change="searchFiles">
|
||||
</multiselect>
|
||||
</div>
|
||||
|
||||
<div class="col-md-9" v-if="step.type === 'RECIPE'">
|
||||
<label :for="'id_step_' + step.id + '_recipe'">{% trans 'Recipe' %}</label>
|
||||
<multiselect
|
||||
v-tabindex
|
||||
ref="step_recipe"
|
||||
v-model="step.step_recipe"
|
||||
:options="recipes.map(recipe => recipe.id)"
|
||||
:close-on-select="true"
|
||||
:clear-on-select="true"
|
||||
:allow-empty="true"
|
||||
:preserve-search="true"
|
||||
placeholder="{% trans 'Select Recipe' %}"
|
||||
select-label="{% trans 'Select' %}"
|
||||
:id="'id_step_' + step.id + '_recipe'"
|
||||
:custom-label="opt => recipes.find(x => x.id == opt).name"
|
||||
|
||||
:multiple="false"
|
||||
:loading="recipes_loading"
|
||||
@search-change="searchRecipes">
|
||||
</multiselect>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<template v-if="step.type == 'TEXT'">
|
||||
@ -524,6 +547,8 @@
|
||||
units_loading: false,
|
||||
files: [],
|
||||
files_loading: false,
|
||||
recipes: [],
|
||||
recipes_loading: false,
|
||||
message: '',
|
||||
},
|
||||
directives: {
|
||||
@ -550,6 +575,7 @@
|
||||
this.searchFoods('')
|
||||
this.searchKeywords('')
|
||||
this.searchFiles('')
|
||||
this.searchRecipes('')
|
||||
|
||||
this._keyListener = function (e) {
|
||||
if (e.code === "Space" && e.ctrlKey) {
|
||||
@ -722,6 +748,16 @@
|
||||
this.makeToast(gettext('Error'), gettext('There was an error loading a resource!') + err.bodyText, 'danger')
|
||||
})
|
||||
},
|
||||
searchRecipes: function (query) {
|
||||
this.recipes_loading = true
|
||||
this.$http.get("{% url 'api:recipe-list' %}" + '?query=' + query + '&limit=10').then((response) => {
|
||||
this.recipes = response.data.results
|
||||
this.recipes_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 'api:unit-list' %}" + '?query=' + query + '&limit=10').then((response) => {
|
||||
|
@ -73,6 +73,7 @@
|
||||
}
|
||||
|
||||
$('#id_log_rating').on("input", () => {
|
||||
let rating = $('#id_log_rating')
|
||||
$('#id_rating_show').html(rating.val() + '/5')
|
||||
});
|
||||
|
||||
|
@ -32,7 +32,7 @@
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">{% trans 'Join an existing space.' %}</h5>
|
||||
<p class="card-text">{% trans 'To join an existing space either enter your invite token or click on the invite link the space owner send you.' %}</p>
|
||||
<p class="card-text" style="height: 64px">{% trans 'To join an existing space either enter your invite token or click on the invite link the space owner send you.' %}</p>
|
||||
|
||||
<form method="POST" action="{% url 'view_no_space' %}">
|
||||
{% csrf_token %}
|
||||
@ -49,7 +49,7 @@
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">{% trans 'Create your own recipe space.' %}</h5>
|
||||
<p class="card-text">{% trans 'Start your own recipe space and invite other users to it.' %}</p>
|
||||
<p class="card-text" style="height: 64px">{% trans 'Start your own recipe space and invite other users to it.' %}</p>
|
||||
<form method="POST" action="{% url 'view_no_space' %}">
|
||||
{% csrf_token %}
|
||||
{{ create_form | crispy }}
|
||||
|
File diff suppressed because one or more lines are too long
@ -7,6 +7,15 @@ from django_scopes import scopes_disabled
|
||||
from cookbook.models import Keyword
|
||||
from cookbook.tests.conftest import get_random_recipe
|
||||
|
||||
# ------------------ IMPORTANT -------------------
|
||||
#
|
||||
# if changing any capabilities associated with keywords
|
||||
# you will need to ensure that it is tested against both
|
||||
# SqlLite and PostgresSQL
|
||||
# adding load_env() to settings.py will enable Postgress access
|
||||
#
|
||||
# ------------------ IMPORTANT -------------------
|
||||
|
||||
LIST_URL = 'api:keyword-list'
|
||||
DETAIL_URL = 'api:keyword-detail'
|
||||
MOVE_URL = 'api:keyword-move'
|
||||
@ -16,7 +25,7 @@ MERGE_URL = 'api:keyword-merge'
|
||||
# TODO are there better ways to manage these fixtures?
|
||||
@pytest.fixture()
|
||||
def obj_1(space_1):
|
||||
return Keyword.add_root(name='test_1', space=space_1)
|
||||
return Keyword.objects.get_or_create(name='test_1', space=space_1)[0]
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
@ -31,12 +40,12 @@ def obj_1_1_1(obj_1_1, space_1):
|
||||
|
||||
@pytest.fixture
|
||||
def obj_2(space_1):
|
||||
return Keyword.add_root(name='test_2', space=space_1)
|
||||
return Keyword.objects.get_or_create(name='test_2', space=space_1)[0]
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def obj_3(space_2):
|
||||
return Keyword.add_root(name='test_3', space=space_2)
|
||||
return Keyword.objects.get_or_create(name='test_3', space=space_2)[0]
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
@ -158,7 +167,6 @@ def test_add(arg, request, u1_s2):
|
||||
assert r.status_code == 404
|
||||
|
||||
|
||||
@pytest.mark.django_db(transaction=True)
|
||||
def test_add_duplicate(u1_s1, u1_s2, obj_1, obj_3):
|
||||
assert json.loads(u1_s1.get(reverse(LIST_URL)).content)['count'] == 1
|
||||
assert json.loads(u1_s2.get(reverse(LIST_URL)).content)['count'] == 1
|
||||
@ -246,13 +254,13 @@ def test_move(u1_s1, obj_1, obj_1_1, obj_1_1_1, obj_2, obj_3, space_1):
|
||||
r = u1_s1.put(
|
||||
reverse(MOVE_URL, args=[obj_1.id, 9999])
|
||||
)
|
||||
assert r.status_code == 400
|
||||
assert r.status_code == 404
|
||||
|
||||
# attempt to move to wrong space
|
||||
r = u1_s1.put(
|
||||
reverse(MOVE_URL, args=[obj_1_1.id, obj_3.id])
|
||||
)
|
||||
assert r.status_code == 400
|
||||
assert r.status_code == 404
|
||||
|
||||
# run diagnostic to find problems - none should be found
|
||||
with scopes_disabled():
|
||||
@ -318,13 +326,13 @@ def test_merge(
|
||||
r = u1_s1.put(
|
||||
reverse(MERGE_URL, args=[obj_1_1.id, 9999])
|
||||
)
|
||||
assert r.status_code == 400
|
||||
assert r.status_code == 404
|
||||
|
||||
# attempt to move to wrong space
|
||||
r = u1_s1.put(
|
||||
reverse(MERGE_URL, args=[obj_2.id, obj_3.id])
|
||||
)
|
||||
assert r.status_code == 400
|
||||
assert r.status_code == 404
|
||||
|
||||
# attempt to merge with child
|
||||
r = u1_s1.put(
|
||||
|
@ -4,12 +4,16 @@ import pytest
|
||||
from django.urls import reverse
|
||||
from django_scopes import scopes_disabled
|
||||
|
||||
from cookbook.models import Food, Ingredient, Step, Recipe
|
||||
from cookbook.models import Recipe
|
||||
|
||||
LIST_URL = 'api:recipe-list'
|
||||
DETAIL_URL = 'api:recipe-detail'
|
||||
|
||||
|
||||
# TODO need to add extensive tests against recipe search to go through all of the combinations of parameters
|
||||
# probably needs to include a far more extensive set of initial recipes to effectively test results
|
||||
# and to ensure that all parts of the code are exercised.
|
||||
# TODO should probably consider adding code coverage plugin to the test suite
|
||||
@pytest.mark.parametrize("arg", [
|
||||
['a_u', 403],
|
||||
['g1_s1', 200],
|
||||
|
@ -11,7 +11,7 @@ LIST_URL = 'api:recipebookentry-list'
|
||||
DETAIL_URL = 'api:recipebookentry-detail'
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
@pytest.fixture
|
||||
def obj_1(space_1, u1_s1, recipe_1_s1):
|
||||
b = RecipeBook.objects.create(name='test_1', created_by=auth.get_user(u1_s1), space=space_1)
|
||||
|
||||
@ -100,7 +100,7 @@ def test_add_duplicate(u1_s1, obj_1):
|
||||
{'book': obj_1.book.pk, 'recipe': obj_1.recipe.pk},
|
||||
content_type='application/json'
|
||||
)
|
||||
assert r.status_code == 400
|
||||
assert r.status_code == 201
|
||||
|
||||
|
||||
def test_delete(u1_s1, u1_s2, obj_1):
|
||||
|
@ -7,7 +7,7 @@ from django.contrib import auth
|
||||
from django.contrib.auth.models import User, Group
|
||||
from django_scopes import scopes_disabled
|
||||
|
||||
from cookbook.models import Space, Recipe, Step, Ingredient, Food, Unit, Storage
|
||||
from cookbook.models import Space, Recipe, Step, Ingredient, Food, Unit
|
||||
|
||||
|
||||
# hack from https://github.com/raphaelm/django-scopes to disable scopes for all fixtures
|
||||
|
@ -36,6 +36,7 @@ router.register(r'recipe-book', api.RecipeBookViewSet)
|
||||
router.register(r'recipe-book-entry', api.RecipeBookEntryViewSet)
|
||||
router.register(r'supermarket', api.SupermarketViewSet)
|
||||
router.register(r'supermarket-category', api.SupermarketCategoryViewSet)
|
||||
router.register(r'supermarket-category-relation', api.SupermarketCategoryRelationViewSet)
|
||||
router.register(r'import-log', api.ImportLogViewSet)
|
||||
router.register(r'bookmarklet-import', api.BookmarkletImportViewSet)
|
||||
router.register(r'user-file', api.UserFileViewSet)
|
||||
|
@ -4,9 +4,9 @@ import re
|
||||
import uuid
|
||||
|
||||
import requests
|
||||
from PIL import Image
|
||||
from annoying.decorators import ajax_request
|
||||
from annoying.functions import get_object_or_None
|
||||
from collections import OrderedDict
|
||||
from django.contrib import messages
|
||||
from django.contrib.auth.models import User
|
||||
from django.contrib.postgres.search import TrigramSimilarity
|
||||
@ -37,13 +37,13 @@ from cookbook.helper.permission_helper import (CustomIsAdmin, CustomIsGuest,
|
||||
group_required)
|
||||
from cookbook.helper.recipe_html_import import get_recipe_from_source
|
||||
|
||||
from cookbook.helper.recipe_search import search_recipes
|
||||
from cookbook.helper.recipe_search import search_recipes, get_facet
|
||||
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, SupermarketCategory, UserFile, ShareLink)
|
||||
ViewLog, RecipeBookEntry, Supermarket, ImportLog, BookmarkletImport, SupermarketCategory, UserFile, ShareLink, SupermarketCategoryRelation)
|
||||
from cookbook.provider.dropbox import Dropbox
|
||||
from cookbook.provider.local import Local
|
||||
from cookbook.provider.nextcloud import Nextcloud
|
||||
@ -61,7 +61,7 @@ from cookbook.serializer import (FoodSerializer, IngredientSerializer,
|
||||
UserNameSerializer, UserPreferenceSerializer,
|
||||
ViewLogSerializer, CookLogSerializer, RecipeBookEntrySerializer,
|
||||
RecipeOverviewSerializer, SupermarketSerializer, ImportLogSerializer,
|
||||
BookmarkletImportSerializer, SupermarketCategorySerializer, UserFileSerializer)
|
||||
BookmarkletImportSerializer, SupermarketCategorySerializer, UserFileSerializer, SupermarketCategoryRelationSerializer)
|
||||
|
||||
|
||||
class StandardFilterMixin(ViewSetMixin):
|
||||
@ -144,18 +144,18 @@ class TreeMixin(FuzzyFilterMixin):
|
||||
except self.model.DoesNotExist:
|
||||
self.queryset = self.model.objects.none()
|
||||
if root == 0:
|
||||
self.queryset = self.model.get_root_nodes().filter(space=self.request.space)
|
||||
self.queryset = self.model.get_root_nodes() | self.model.objects.filter(depth=0)
|
||||
else:
|
||||
self.queryset = self.model.objects.get(id=root).get_children().filter(space=self.request.space)
|
||||
self.queryset = self.model.objects.get(id=root).get_children()
|
||||
elif tree:
|
||||
if tree.isnumeric():
|
||||
try:
|
||||
self.queryset = self.model.objects.get(id=int(tree)).get_descendants_and_self().filter(space=self.request.space)
|
||||
self.queryset = self.model.objects.get(id=int(tree)).get_descendants_and_self()
|
||||
except Keyword.DoesNotExist:
|
||||
self.queryset = self.model.objects.none()
|
||||
else:
|
||||
return super().get_queryset()
|
||||
return self.queryset
|
||||
return self.queryset.filter(space=self.request.space)
|
||||
|
||||
@decorators.action(detail=True, url_path='move/(?P<parent>[^/.]+)', methods=['PUT'],)
|
||||
@decorators.renderer_classes((TemplateHTMLRenderer, JSONRenderer))
|
||||
@ -166,7 +166,7 @@ class TreeMixin(FuzzyFilterMixin):
|
||||
child = self.model.objects.get(pk=pk, space=self.request.space)
|
||||
except (self.model.DoesNotExist):
|
||||
content = {'error': True, 'msg': _(f'No {self.basename} with id {child} exists')}
|
||||
return Response(content, status=status.HTTP_400_BAD_REQUEST)
|
||||
return Response(content, status=status.HTTP_404_NOT_FOUND)
|
||||
|
||||
parent = int(parent)
|
||||
# parent 0 is root of the tree
|
||||
@ -184,7 +184,7 @@ class TreeMixin(FuzzyFilterMixin):
|
||||
parent = self.model.objects.get(pk=parent, space=self.request.space)
|
||||
except (self.model.DoesNotExist):
|
||||
content = {'error': True, 'msg': _(f'No {self.basename} with id {parent} exists')}
|
||||
return Response(content, status=status.HTTP_400_BAD_REQUEST)
|
||||
return Response(content, status=status.HTTP_404_NOT_FOUND)
|
||||
|
||||
try:
|
||||
with scopes_disabled():
|
||||
@ -204,7 +204,7 @@ class TreeMixin(FuzzyFilterMixin):
|
||||
source = self.model.objects.get(pk=pk, space=self.request.space)
|
||||
except (self.model.DoesNotExist):
|
||||
content = {'error': True, 'msg': _(f'No {self.basename} with id {pk} exists')}
|
||||
return Response(content, status=status.HTTP_400_BAD_REQUEST)
|
||||
return Response(content, status=status.HTTP_404_NOT_FOUND)
|
||||
|
||||
if int(target) == source.id:
|
||||
content = {'error': True, 'msg': _('Cannot merge with the same object!')}
|
||||
@ -215,14 +215,14 @@ class TreeMixin(FuzzyFilterMixin):
|
||||
target = self.model.objects.get(pk=target, space=self.request.space)
|
||||
except (self.model.DoesNotExist):
|
||||
content = {'error': True, 'msg': _(f'No {self.basename} with id {target} exists')}
|
||||
return Response(content, status=status.HTTP_400_BAD_REQUEST)
|
||||
return Response(content, status=status.HTTP_404_NOT_FOUND)
|
||||
|
||||
try:
|
||||
if target in source.get_descendants_and_self():
|
||||
content = {'error': True, 'msg': _('Cannot merge with child object!')}
|
||||
return Response(content, status=status.HTTP_403_FORBIDDEN)
|
||||
########################################################################
|
||||
# this needs abstracted to update steps instead of recipes for food merge
|
||||
# TODO this needs abstracted to update steps instead of recipes for food merge
|
||||
########################################################################
|
||||
recipes = Recipe.objects.filter(**{"%ss" % self.basename: source}, space=self.request.space)
|
||||
|
||||
@ -322,9 +322,18 @@ class SupermarketCategoryViewSet(viewsets.ModelViewSet, StandardFilterMixin):
|
||||
return super().get_queryset()
|
||||
|
||||
|
||||
class KeywordViewSet(viewsets.ModelViewSet, TreeMixin):
|
||||
# TODO check if fuzzyfilter is conflicting - may also need to create 'tree filter' mixin
|
||||
class SupermarketCategoryRelationViewSet(viewsets.ModelViewSet, StandardFilterMixin):
|
||||
queryset = SupermarketCategoryRelation.objects
|
||||
serializer_class = SupermarketCategoryRelationSerializer
|
||||
permission_classes = [CustomIsUser]
|
||||
pagination_class = DefaultPagination
|
||||
|
||||
def get_queryset(self):
|
||||
self.queryset = self.queryset.filter(supermarket__space=self.request.space)
|
||||
return super().get_queryset()
|
||||
|
||||
|
||||
class KeywordViewSet(viewsets.ModelViewSet, TreeMixin):
|
||||
queryset = Keyword.objects
|
||||
model = Keyword
|
||||
serializer_class = KeywordSerializer
|
||||
@ -438,6 +447,19 @@ class RecipePagination(PageNumberPagination):
|
||||
page_size_query_param = 'page_size'
|
||||
max_page_size = 100
|
||||
|
||||
def paginate_queryset(self, queryset, request, view=None):
|
||||
self.facets = get_facet(queryset, request.query_params)
|
||||
return super().paginate_queryset(queryset, request, view)
|
||||
|
||||
def get_paginated_response(self, data):
|
||||
return Response(OrderedDict([
|
||||
('count', self.page.paginator.count),
|
||||
('next', self.get_next_link()),
|
||||
('previous', self.get_previous_link()),
|
||||
('results', data),
|
||||
('facets', self.facets)
|
||||
]))
|
||||
|
||||
|
||||
class RecipeViewSet(viewsets.ModelViewSet):
|
||||
queryset = Recipe.objects
|
||||
@ -679,7 +701,7 @@ def share_link(request, pk):
|
||||
def log_cooking(request, recipe_id):
|
||||
recipe = get_object_or_None(Recipe, id=recipe_id)
|
||||
if recipe:
|
||||
log = CookLog.objects.create(created_by=request.user, recipe=recipe)
|
||||
log = CookLog.objects.create(created_by=request.user, recipe=recipe, space=request.space)
|
||||
servings = request.GET['s'] if 's' in request.GET else None
|
||||
if servings and re.match(r'^([1-9])+$', servings):
|
||||
log.servings = int(servings)
|
||||
|
@ -13,7 +13,7 @@ from django.urls import reverse
|
||||
from django.utils.translation import gettext as _
|
||||
from django.utils.translation import ngettext
|
||||
from django_tables2 import RequestConfig
|
||||
from PIL import Image, UnidentifiedImageError
|
||||
from PIL import UnidentifiedImageError
|
||||
from requests.exceptions import MissingSchema
|
||||
|
||||
from cookbook.forms import BatchEditForm, SyncForm
|
||||
@ -150,12 +150,8 @@ def import_url(request):
|
||||
|
||||
all_keywords = Keyword.get_tree()
|
||||
for kw in data['keywords']:
|
||||
q = all_keywords.filter(name=kw['text'], space=request.space)
|
||||
if len(q) != 0:
|
||||
recipe.keywords.add(q[0])
|
||||
elif data['all_keywords']:
|
||||
k = Keyword.add_root(name=kw['text'], space=request.space)
|
||||
recipe.keywords.add(k)
|
||||
k, created = Keyword.objects.get_or_create(name=kw['text'], space=request.space)
|
||||
recipe.keywords.add(k)
|
||||
|
||||
for ing in data['recipeIngredient']:
|
||||
ingredient = Ingredient(space=request.space,)
|
||||
|
@ -63,7 +63,7 @@ def get_integration(request, export_type):
|
||||
|
||||
@group_required('user')
|
||||
def import_recipe(request):
|
||||
if request.space.max_recipes != 0 and Recipe.objects.filter(space=request.space).count() >= request.space.max_recipes: # TODO move to central helper function
|
||||
if request.space.max_recipes != 0 and Recipe.objects.filter(space=request.space).count() >= request.space.max_recipes: # TODO move to central helper function
|
||||
messages.add_message(request, messages.WARNING, _('You have reached the maximum number of recipes for your space.'))
|
||||
return HttpResponseRedirect(reverse('index'))
|
||||
|
||||
|
@ -3,12 +3,12 @@ import json
|
||||
import requests
|
||||
from django.db.models import Q
|
||||
from django.http import JsonResponse
|
||||
from django.shortcuts import render, get_object_or_404
|
||||
from django.shortcuts import get_object_or_404
|
||||
from django.views.decorators.csrf import csrf_exempt
|
||||
|
||||
from cookbook.helper.ingredient_parser import parse, get_unit, get_food
|
||||
from cookbook.helper.permission_helper import group_required
|
||||
from cookbook.models import TelegramBot, ShoppingList, ShoppingListEntry, Food, Unit
|
||||
from cookbook.models import TelegramBot, ShoppingList, ShoppingListEntry
|
||||
|
||||
|
||||
@group_required('user')
|
||||
@ -58,7 +58,7 @@ def hook(request, token):
|
||||
)
|
||||
)
|
||||
return JsonResponse({'data': data['message']['text']})
|
||||
except:
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return JsonResponse({})
|
||||
|
@ -15,6 +15,8 @@ from django.db.models import Avg, Q
|
||||
from django.db.models import Sum
|
||||
from django.http import HttpResponseRedirect
|
||||
from django.http import JsonResponse
|
||||
from django.db.models import Avg, Q, Sum
|
||||
from django.http import HttpResponseRedirect, JsonResponse
|
||||
from django.shortcuts import get_object_or_404, render, redirect
|
||||
from django.urls import reverse, reverse_lazy
|
||||
from django.utils import timezone
|
||||
@ -27,6 +29,8 @@ from cookbook.filters import RecipeFilter
|
||||
from cookbook.forms import (CommentForm, Recipe, User,
|
||||
UserCreateForm, UserNameForm, UserPreference,
|
||||
UserPreferenceForm, SpaceJoinForm, SpaceCreateForm, SearchPreferenceForm)
|
||||
UserPreferenceForm, SpaceJoinForm, SpaceCreateForm,
|
||||
SearchPreferenceForm)
|
||||
from cookbook.helper.ingredient_parser import parse
|
||||
from cookbook.helper.permission_helper import group_required, share_link_valid, has_group_permission
|
||||
from cookbook.models import (Comment, CookLog, InviteLink, MealPlan,
|
||||
@ -122,6 +126,7 @@ def no_space(request):
|
||||
max_users=settings.SPACE_DEFAULT_MAX_USERS,
|
||||
allow_sharing=settings.SPACE_DEFAULT_ALLOW_SHARING,
|
||||
)
|
||||
|
||||
request.user.userpreference.space = created_space
|
||||
request.user.userpreference.save()
|
||||
request.user.groups.add(Group.objects.filter(name='admin').get())
|
||||
@ -141,7 +146,7 @@ def no_space(request):
|
||||
if 'signup_token' in request.session:
|
||||
return HttpResponseRedirect(reverse('view_invite', args=[request.session.pop('signup_token', '')]))
|
||||
|
||||
create_form = SpaceCreateForm()
|
||||
create_form = SpaceCreateForm(initial={'name': f'{request.user.username}\'s Space'})
|
||||
join_form = SpaceJoinForm()
|
||||
|
||||
return render(request, 'no_space_info.html', {'create_form': create_form, 'join_form': join_form})
|
||||
|
@ -7,16 +7,32 @@ These intructions are inspired from a standard django/gunicorn/postgresql instru
|
||||
|
||||
## Prerequisites
|
||||
|
||||
*Optional*: create a virtual env and activate it
|
||||
Setup user: `sudo useradd recipes`
|
||||
|
||||
Get the last version from the repository: `git clone https://github.com/vabene1111/recipes.git -b master`
|
||||
|
||||
Install postgresql requirements: `sudo apt install libpq-dev postgresql`
|
||||
Install project requirements: `pip3.9 install -r requirements.txt`
|
||||
Move it to the `/var/www` directory: `mv recipes /var/www`
|
||||
|
||||
Change to the directory: `cd /var/www/recipes`
|
||||
|
||||
Give the user permissions: `chown -R recipes:www-data /var/www/recipes`
|
||||
|
||||
Create virtual env: `python3.9 -m venv /var/www/recipes`
|
||||
|
||||
### Install postgresql requirements
|
||||
|
||||
`sudo apt install libpq-dev postgresql`
|
||||
|
||||
###Install project requirements
|
||||
|
||||
Using binaries from the virtual env:
|
||||
|
||||
`/var/www/recipes/bin/pip3.9 install -r requirements.txt`
|
||||
|
||||
|
||||
## Setup postgresql
|
||||
|
||||
Run `sudo -u postgres psql`
|
||||
`sudo -u postgres psql`
|
||||
|
||||
In the psql console:
|
||||
|
||||
@ -37,12 +53,18 @@ ALTER USER djangouser WITH SUPERUSER;
|
||||
|
||||
Download the `.env` configuration file and **edit it accordingly**.
|
||||
```shell
|
||||
wget https://raw.githubusercontent.com/vabene1111/recipes/develop/.env.template -O .env
|
||||
wget https://raw.githubusercontent.com/vabene1111/recipes/develop/.env.template -O /var/www/recipes/.env
|
||||
```
|
||||
|
||||
Things to edit:
|
||||
- `SECRET_KEY`: use something secure.
|
||||
- `POSTGRES_HOST`: probably 127.0.0.1.
|
||||
- `POSTGRES_PASSWORD`: the password we set earlier when setting up djangodb.
|
||||
- `STATIC_URL`, `MEDIA_URL`: these will be in `/var/www/recipes`, under `/staticfiles/` and `/mediafiles/` respectively.
|
||||
|
||||
## Initialize the application
|
||||
|
||||
Execute `export $(cat .env |grep "^[^#]" | xargs)` to load variables from `.env`
|
||||
Execute `export $(cat /var/www/recipes/.env |grep "^[^#]" | xargs)` to load variables from `/var/www/recipes/.env`
|
||||
|
||||
Execute `/python3.9 manage.py migrate`
|
||||
|
||||
@ -67,10 +89,11 @@ After=network.target
|
||||
Type=simple
|
||||
Restart=always
|
||||
RestartSec=3
|
||||
User=recipes
|
||||
Group=www-data
|
||||
WorkingDirectory=/media/data/recipes
|
||||
EnvironmentFile=/media/data/recipes/.env
|
||||
ExecStart=/opt/.pyenv/versions/3.9/bin/gunicorn --error-logfile /tmp/gunicorn_err.log --log-level debug --capture-output --bind unix:/media/data/recipes/recipes.sock recipes.wsgi:application
|
||||
WorkingDirectory=/var/www/recipes
|
||||
EnvironmentFile=/var/www/recipes/.env
|
||||
ExecStart=/var/www/recipes/bin/gunicorn --error-logfile /tmp/gunicorn_err.log --log-level debug --capture-output --bind unix:/var/www/recipes/recipes.sock recipes.wsgi:application
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
@ -80,11 +103,11 @@ WantedBy=multi-user.target
|
||||
|
||||
*Note2*: Fix the path in the `ExecStart` line to where you gunicorn and recipes are
|
||||
|
||||
Finally, run `sudo systemctl enable gunicorn_recipes.service` and `sudo systemctl start gunicorn_recipes.service`. You can check that the service is correctly started with `systemctl status gunicorn_recipes.service`
|
||||
Finally, run `sudo systemctl enable gunicorn_recipes` and `sudo systemctl start gunicorn_recipes`. You can check that the service is correctly started with `systemctl status gunicorn_recipes`
|
||||
|
||||
### nginx
|
||||
|
||||
Now we tell nginx to listen to a new port and forward that to gunicorn. `sudo nano /etc/nginx/sites-available/recipes.conf`
|
||||
Now we tell nginx to listen to a new port and forward that to gunicorn. `sudo nano /etc/nginx/conf.d/recipes.conf`
|
||||
|
||||
And enter these lines:
|
||||
|
||||
@ -95,20 +118,21 @@ server {
|
||||
#error_log /var/log/nginx/error.log;
|
||||
|
||||
# serve media files
|
||||
location /static {
|
||||
alias /media/data/recipes/staticfiles;
|
||||
location /staticfiles {
|
||||
alias /var/www/recipes/staticfiles;
|
||||
}
|
||||
|
||||
location /media {
|
||||
alias /media/data/recipes/mediafiles;
|
||||
location /mediafiles {
|
||||
alias /var/www/recipes/mediafiles;
|
||||
}
|
||||
|
||||
location / {
|
||||
proxy_pass http://unix:/media/data/recipes/recipes.sock;
|
||||
proxy_set_header Host $http_host;
|
||||
proxy_pass http://unix:/var/www/recipes/recipes.sock;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
*Note*: Enter the correct path in static and proxy_pass lines.
|
||||
|
||||
Enable the website `sudo ln -s /etc/nginx/sites-available/recipes.conf /etc/nginx/sites-enabled` and restart nginx : `sudo systemctl restart nginx.service`
|
||||
Reload nginx : `sudo systemctl reload nginx`
|
||||
|
@ -2,7 +2,7 @@ server {
|
||||
listen 80;
|
||||
server_name localhost;
|
||||
|
||||
client_max_body_size 16M;
|
||||
client_max_body_size 128M;
|
||||
|
||||
# serve media files
|
||||
location /media/ {
|
||||
|
@ -15,18 +15,15 @@ import os
|
||||
import re
|
||||
|
||||
from django.contrib import messages
|
||||
from django.contrib.staticfiles.storage import staticfiles_storage
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from dotenv import load_dotenv
|
||||
from webpack_loader.loader import WebpackLoader
|
||||
load_dotenv()
|
||||
# from dotenv import load_dotenv
|
||||
# load_dotenv()
|
||||
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
|
||||
# Get vars from .env files
|
||||
SECRET_KEY = os.getenv('SECRET_KEY') if os.getenv('SECRET_KEY') else 'INSECURE_STANDARD_KEY_SET_IN_ENV'
|
||||
|
||||
DEBUG = bool(int(os.getenv('DEBUG', True)))
|
||||
DEMO = bool(int(os.getenv('DEMO', False)))
|
||||
|
||||
SOCIAL_DEFAULT_ACCESS = bool(int(os.getenv('SOCIAL_DEFAULT_ACCESS', False)))
|
||||
SOCIAL_DEFAULT_GROUP = os.getenv('SOCIAL_DEFAULT_GROUP', 'guest')
|
||||
@ -279,6 +276,16 @@ else:
|
||||
# }
|
||||
# }
|
||||
|
||||
# SQLite testing DB
|
||||
# DATABASES = {
|
||||
# 'default': {
|
||||
# 'ENGINE': 'django.db.backends.sqlite3',
|
||||
# 'OPTIONS': ast.literal_eval(os.getenv('DB_OPTIONS')) if os.getenv('DB_OPTIONS') else {},
|
||||
# 'NAME': 'db.sqlite3',
|
||||
# 'CONN_MAX_AGE': 600,
|
||||
# }
|
||||
# }
|
||||
|
||||
CACHES = {
|
||||
'default': {
|
||||
'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
|
||||
|
@ -1,4 +1,4 @@
|
||||
Django==3.2.4
|
||||
Django==3.2.5
|
||||
cryptography==3.4.7
|
||||
django-annoying==0.10.6
|
||||
django-autocomplete-light==3.8.2
|
||||
@ -9,19 +9,19 @@ django-filter==2.4.0
|
||||
django-tables2==2.4.0
|
||||
djangorestframework==3.12.4
|
||||
drf-writable-nested==0.6.3
|
||||
bleach==3.3.0
|
||||
bleach==3.3.1
|
||||
bleach-allowlist==1.0.3
|
||||
gunicorn==20.1.0
|
||||
lxml==4.6.3
|
||||
Markdown==3.3.4
|
||||
Pillow==8.2.0
|
||||
Pillow==8.3.1
|
||||
psycopg2-binary==2.9.1
|
||||
python-dotenv==0.18.0
|
||||
requests==2.25.1
|
||||
simplejson==3.17.2
|
||||
requests==2.26.0
|
||||
simplejson==3.17.3
|
||||
six==1.16.0
|
||||
webdavclient3==3.14.5
|
||||
whitenoise==5.2.0
|
||||
whitenoise==5.3.0
|
||||
icalendar==4.0.7
|
||||
pyyaml==5.4.1
|
||||
uritemplate==3.0.1
|
||||
@ -30,14 +30,14 @@ microdata==0.7.1
|
||||
Jinja2==3.0.1
|
||||
django-webpack-loader==1.1.0
|
||||
django-js-reverse==0.9.1
|
||||
django-allauth==0.44.0
|
||||
recipe-scrapers==13.3.0
|
||||
django-allauth==0.45.0
|
||||
recipe-scrapers==13.3.4
|
||||
django-scopes==1.2.0
|
||||
pytest==6.2.4
|
||||
pytest-django==4.4.0
|
||||
django-cors-headers==3.7.0
|
||||
django-treebeard==4.5.1
|
||||
django-storages==1.11.1
|
||||
boto3==1.17.102
|
||||
boto3==1.18.4
|
||||
django-prometheus==2.1.0
|
||||
django-hCaptcha==0.1.0
|
||||
|
@ -10,6 +10,7 @@
|
||||
"dependencies": {
|
||||
"@babel/eslint-parser": "^7.13.14",
|
||||
"@kevinfaguiar/vue-twemoji-picker": "^5.7.4",
|
||||
"@riophae/vue-treeselect": "^0.4.0",
|
||||
"axios": "^0.21.1",
|
||||
"bootstrap-vue": "^2.21.2",
|
||||
"core-js": "^3.14.0",
|
||||
|
@ -6,7 +6,7 @@
|
||||
|
||||
</div>
|
||||
<div class="col-xl-8 col-12">
|
||||
<!-- TODO only show scollbars in split mode, but this doesn't interact well with infinite scroll, maybe a different componenet? -->
|
||||
<!-- TODO only show scollbars in split mode, but this doesn't interact well with infinite scroll, maybe a different component? -->
|
||||
<div class="container-fluid d-flex flex-column flex-grow-1" :class="{'vh-100' : show_split}">
|
||||
<!-- <div class="container-fluid d-flex flex-column flex-grow-1 vh-100"> -->
|
||||
<!-- expanded options box -->
|
||||
@ -450,10 +450,11 @@ export default {
|
||||
let parent = {}
|
||||
let pageSize = 200
|
||||
let keyword = String(kw.id)
|
||||
console.log(apiClient.listRecipes)
|
||||
|
||||
apiClient.listRecipes(
|
||||
undefined, keyword, undefined, undefined, undefined, undefined,
|
||||
undefined, undefined, undefined, undefined, pageSize
|
||||
undefined, undefined, undefined, undefined, undefined, pageSize, undefined
|
||||
).then(result => {
|
||||
if (col == 'left') {
|
||||
parent = this.findKeyword(this.keywords, kw.id)
|
||||
|
@ -10,13 +10,20 @@
|
||||
<b-input class="form-control form-control-lg form-control-borderless form-control-search" v-model="settings.search_input"
|
||||
v-bind:placeholder="$t('Search')"></b-input>
|
||||
<b-input-group-append>
|
||||
<b-button v-b-toggle.collapse_advanced_search
|
||||
v-bind:class="{'btn-primary': !isAdvancedSettingsSet(), 'btn-danger': isAdvancedSettingsSet()}"
|
||||
class="shadow-none btn"><i
|
||||
class="fas fa-caret-down" v-if="!settings.advanced_search_visible"></i><i
|
||||
class="fas fa-caret-up"
|
||||
v-if="settings.advanced_search_visible"></i>
|
||||
<b-button variant="light"
|
||||
v-b-tooltip.hover :title="$t('Random Recipes')"
|
||||
@click="openRandom()">
|
||||
<i class="fas fa-dice-five" style="font-size: 1.5em"></i>
|
||||
</b-button>
|
||||
<b-button v-b-toggle.collapse_advanced_search
|
||||
v-b-tooltip.hover :title="$t('Advanced Settings')"
|
||||
v-bind:variant="!isAdvancedSettingsSet() ? 'primary' : 'danger'"
|
||||
>
|
||||
<!-- consider changing this icon to a filter -->
|
||||
<i class="fas fa-caret-down" v-if="!settings.advanced_search_visible"></i>
|
||||
<i class="fas fa-caret-up" v-if="settings.advanced_search_visible"></i>
|
||||
</b-button>
|
||||
|
||||
</b-input-group-append>
|
||||
</b-input-group>
|
||||
</div>
|
||||
@ -73,6 +80,21 @@
|
||||
></b-form-input>
|
||||
</b-form-group>
|
||||
|
||||
<b-form-group
|
||||
v-bind:label="$t('Recipes_per_page')"
|
||||
label-for="popover-input-page-count"
|
||||
label-cols="6"
|
||||
class="mb-3">
|
||||
<b-form-input
|
||||
type="number"
|
||||
v-model="settings.page_count"
|
||||
id="popover-input-page-count"
|
||||
size="sm"
|
||||
></b-form-input>
|
||||
</b-form-group>
|
||||
|
||||
|
||||
|
||||
<b-form-group
|
||||
v-bind:label="$t('Meal_Plan')"
|
||||
label-for="popover-input-2"
|
||||
@ -85,6 +107,24 @@
|
||||
size="sm"
|
||||
></b-form-checkbox>
|
||||
</b-form-group>
|
||||
|
||||
<b-form-group
|
||||
v-bind:label="$t('Sort_by_new')"
|
||||
label-for="popover-input-3"
|
||||
label-cols="6"
|
||||
class="mb-3">
|
||||
<b-form-checkbox
|
||||
switch
|
||||
v-model="settings.sort_by_new"
|
||||
id="popover-input-3"
|
||||
size="sm"
|
||||
></b-form-checkbox>
|
||||
</b-form-group>
|
||||
</div>
|
||||
<div class="row" style="margin-top: 1vh">
|
||||
<div class="col-12">
|
||||
<a :href="resolveDjangoUrl('view_settings') + '#search'">{{ $t('Advanced Search Settings') }}</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row" style="margin-top: 1vh">
|
||||
<div class="col-12">
|
||||
@ -103,12 +143,16 @@
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<b-input-group class="mt-2">
|
||||
<generic-multiselect @change="genericSelectChanged" parent_variable="search_keywords"
|
||||
<!-- <generic-multiselect @change="genericSelectChanged" parent_variable="search_keywords"
|
||||
:initial_selection="settings.search_keywords"
|
||||
search_function="listKeywords" label="label"
|
||||
:tree_api="true"
|
||||
style="flex-grow: 1; flex-shrink: 1; flex-basis: 0"
|
||||
v-bind:placeholder="$t('Keywords')"></generic-multiselect>
|
||||
v-bind:placeholder="$t('Keywords')"></generic-multiselect> -->
|
||||
<treeselect v-model="settings.search_keywords" :options="facets.Keywords" :flat="true"
|
||||
searchNested multiple :placeholder="$t('Keywords')" :normalizer="normalizer"
|
||||
@input="refreshData(false)"
|
||||
style="flex-grow: 1; flex-shrink: 1; flex-basis: 0"/>
|
||||
<b-input-group-append>
|
||||
<b-input-group-text>
|
||||
<b-form-checkbox v-model="settings.search_keywords_or" name="check-button"
|
||||
@ -179,7 +223,7 @@
|
||||
<div class="row">
|
||||
<div class="col col-md-12 text-right" style="margin-top: 2vh">
|
||||
<span class="text-muted">
|
||||
{{ $t('Page') }} {{ settings.pagination_page }}/{{ pagination_count }} <a href="#" @click="resetSearch"><i
|
||||
{{ $t('Page') }} {{ settings.pagination_page }}/{{ Math.ceil(pagination_count/settings.page_count) }} <a href="#" @click="resetSearch"><i
|
||||
class="fas fa-times-circle"></i> {{ $t('Reset') }}</a>
|
||||
</span>
|
||||
</div>
|
||||
@ -187,10 +231,10 @@
|
||||
|
||||
<div class="row">
|
||||
<div class="col col-md-12">
|
||||
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));grid-gap: 1rem;">
|
||||
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));grid-gap: 0.8rem;" >
|
||||
|
||||
<template
|
||||
v-if="settings.search_input === '' && settings.search_keywords.length === 0 && settings.search_foods.length === 0 && settings.search_books.length === 0">
|
||||
v-if="settings.search_input === '' && settings.search_keywords.length === 0 && settings.search_foods.length === 0 && settings.search_books.length === 0 && this.settings.pagination_page === 1 && !random_search">
|
||||
<recipe-card v-bind:key="`mp_${m.id}`" v-for="m in meal_plans" :recipe="m.recipe"
|
||||
:meal_plan="m" :footer_text="m.meal_type_name"
|
||||
footer_icon="far fa-calendar-alt"></recipe-card>
|
||||
@ -204,12 +248,12 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row" style="margin-top: 2vh">
|
||||
<div class="row" style="margin-top: 2vh" v-if="!random_search">
|
||||
<div class="col col-md-12">
|
||||
<b-pagination pills
|
||||
v-model="settings.pagination_page"
|
||||
:total-rows="pagination_count"
|
||||
per-page="25"
|
||||
:per-page="settings.page_count"
|
||||
@change="pageChange"
|
||||
align="center">
|
||||
|
||||
@ -223,8 +267,6 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</template>
|
||||
|
||||
<script>
|
||||
@ -246,6 +288,8 @@ import LoadingSpinner from "@/components/LoadingSpinner";
|
||||
import {ApiApiFactory} from "@/utils/openapi/api.ts";
|
||||
import RecipeCard from "@/components/RecipeCard";
|
||||
import GenericMultiselect from "@/components/GenericMultiselect";
|
||||
import Treeselect from '@riophae/vue-treeselect'
|
||||
import '@riophae/vue-treeselect/dist/vue-treeselect.css'
|
||||
|
||||
Vue.use(BootstrapVue)
|
||||
|
||||
@ -254,10 +298,11 @@ let SETTINGS_COOKIE_NAME = 'search_settings'
|
||||
export default {
|
||||
name: 'RecipeSearchView',
|
||||
mixins: [ResolveUrlMixin],
|
||||
components: {GenericMultiselect, RecipeCard},
|
||||
components: {GenericMultiselect, RecipeCard, Treeselect},
|
||||
data() {
|
||||
return {
|
||||
recipes: [],
|
||||
facets: [],
|
||||
meal_plans: [],
|
||||
last_viewed_recipes: [],
|
||||
|
||||
@ -275,11 +320,13 @@ export default {
|
||||
advanced_search_visible: false,
|
||||
show_meal_plan: true,
|
||||
recently_viewed: 5,
|
||||
|
||||
sort_by_new: true,
|
||||
pagination_page: 1,
|
||||
page_count: 25,
|
||||
},
|
||||
|
||||
pagination_count: 0,
|
||||
random_search: false,
|
||||
}
|
||||
|
||||
},
|
||||
@ -339,15 +386,18 @@ export default {
|
||||
'settings.search_input': _debounce(function () {
|
||||
this.refreshData(false)
|
||||
}, 300),
|
||||
'settings.page_count': _debounce(function () {
|
||||
this.refreshData(false)
|
||||
}, 300),
|
||||
},
|
||||
methods: {
|
||||
refreshData: function (page_load) {
|
||||
refreshData: function (random) {
|
||||
this.random_search = random
|
||||
let apiClient = new ApiApiFactory()
|
||||
|
||||
apiClient.listRecipes(
|
||||
this.settings.search_input,
|
||||
this.settings.search_keywords.map(function (A) {
|
||||
return A["id"];
|
||||
}),
|
||||
this.settings.search_keywords,
|
||||
this.settings.search_foods.map(function (A) {
|
||||
return A["id"];
|
||||
}),
|
||||
@ -359,14 +409,20 @@ export default {
|
||||
this.settings.search_books_or,
|
||||
|
||||
this.settings.search_internal,
|
||||
undefined,
|
||||
random,
|
||||
this.settings.sort_by_new,
|
||||
this.settings.pagination_page,
|
||||
this.settings.page_count
|
||||
).then(result => {
|
||||
window.scrollTo(0, 0);
|
||||
this.pagination_count = result.data.count
|
||||
this.recipes = result.data.results
|
||||
this.facets = result.data.facets
|
||||
})
|
||||
},
|
||||
openRandom: function () {
|
||||
this.refreshData(true)
|
||||
},
|
||||
loadMealPlan: function () {
|
||||
let apiClient = new ApiApiFactory()
|
||||
|
||||
@ -389,7 +445,7 @@ export default {
|
||||
let apiClient = new ApiApiFactory()
|
||||
if (this.settings.recently_viewed > 0) {
|
||||
apiClient.listRecipes(undefined, undefined, undefined, undefined, undefined, undefined,
|
||||
undefined, undefined, undefined, undefined, undefined, {query: {last_viewed: this.settings.recently_viewed}}).then(result => {
|
||||
undefined, undefined, undefined, this.settings.sort_by_new, 1, this.settings.recently_viewed, {query: {last_viewed: this.settings.recently_viewed}}).then(result => {
|
||||
this.last_viewed_recipes = result.data.results
|
||||
})
|
||||
} else {
|
||||
@ -411,10 +467,18 @@ export default {
|
||||
},
|
||||
pageChange: function (page) {
|
||||
this.settings.pagination_page = page
|
||||
this.refreshData()
|
||||
this.refreshData(false)
|
||||
},
|
||||
isAdvancedSettingsSet() {
|
||||
return ((this.settings.search_keywords.length + this.settings.search_foods.length + this.settings.search_books.length) > 0)
|
||||
},
|
||||
normalizer(node) {
|
||||
return {
|
||||
id: node.id,
|
||||
label: node.name + ' (' + node.count + ')',
|
||||
children: node.children,
|
||||
isDefaultExpanded: node.isDefaultExpanded
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -8,7 +8,7 @@
|
||||
<h2>{{ $t('Supermarket') }}</h2>
|
||||
|
||||
<multiselect v-model="selected_supermarket" track-by="id" label="name"
|
||||
:options="supermarkets">
|
||||
:options="supermarkets" @input="selectedSupermarketChanged">
|
||||
</multiselect>
|
||||
|
||||
<b-button class="btn btn-primary btn-block" style="margin-top: 1vh" v-b-modal.modal-supermarket>
|
||||
@ -25,12 +25,14 @@
|
||||
<div class="row">
|
||||
<div class="col col-md-6">
|
||||
<h4>{{ $t('Categories') }}
|
||||
<button class="btn btn-success btn-sm">{{ $t('New') }}</button>
|
||||
<button class="btn btn-success btn-sm" @click="selected_category = {new:true, name:''}"
|
||||
v-b-modal.modal-category>{{ $t('New') }}
|
||||
</button>
|
||||
</h4>
|
||||
|
||||
<draggable :list="categories" group="supermarket_categories"
|
||||
<draggable :list="selectable_categories" group="supermarket_categories"
|
||||
:empty-insert-threshold="10">
|
||||
<div v-for="c in categories" :key="c.id">
|
||||
<div v-for="c in selectable_categories" :key="c.id">
|
||||
<button class="btn btn-block btn-sm btn-primary" style="margin-top: 0.5vh">{{ c.name }}</button>
|
||||
|
||||
</div>
|
||||
@ -41,9 +43,9 @@
|
||||
<div class="col col-md-6">
|
||||
<h4>{{ $t('Selected') }} {{ $t('Categories') }}</h4>
|
||||
|
||||
<draggable :list="selected_categories" group="supermarket_categories"
|
||||
:empty-insert-threshold="10">
|
||||
<div v-for="c in selected_categories" :key="c.id">
|
||||
<draggable :list="supermarket_categories" group="supermarket_categories"
|
||||
:empty-insert-threshold="10" @change="selectedCategoriesChanged">
|
||||
<div v-for="c in supermarket_categories" :key="c.id">
|
||||
<button class="btn btn-block btn-sm btn-primary" style="margin-top: 0.5vh">{{ c.name }}</button>
|
||||
|
||||
</div>
|
||||
@ -61,9 +63,9 @@
|
||||
</b-modal>
|
||||
|
||||
<b-modal id="modal-category" v-bind:title="$t('Category')" @ok="categoryModalOk()">
|
||||
<label v-if="selected_supermarket !== undefined">
|
||||
<label v-if="selected_category !== undefined">
|
||||
{{ $t('Name') }}
|
||||
<b-input v-model="selected_supermarket.name"></b-input>
|
||||
<b-input v-model="selected_category.name"></b-input>
|
||||
|
||||
</label>
|
||||
</b-modal>
|
||||
@ -107,12 +109,15 @@ export default {
|
||||
categories: [],
|
||||
|
||||
selected_supermarket: {},
|
||||
selected_categories: [],
|
||||
selected_category: {},
|
||||
|
||||
|
||||
selectable_categories: [],
|
||||
supermarket_categories: [],
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.$i18n.locale = window.CUSTOM_LOCALE
|
||||
console.log('LOADED')
|
||||
this.loadInitial()
|
||||
},
|
||||
methods: {
|
||||
@ -123,8 +128,45 @@ export default {
|
||||
})
|
||||
apiClient.listSupermarketCategorys().then(results => {
|
||||
this.categories = results.data
|
||||
this.selectable_categories = this.categories
|
||||
})
|
||||
},
|
||||
selectedCategoriesChanged: function (data) {
|
||||
let apiClient = new ApiApiFactory()
|
||||
|
||||
if ('removed' in data) {
|
||||
let relation = this.selected_supermarket.category_to_supermarket.filter((el) => el.category.id === data.removed.element.id)[0]
|
||||
apiClient.destroySupermarketCategoryRelation(relation.id)
|
||||
}
|
||||
|
||||
if ('added' in data) {
|
||||
apiClient.createSupermarketCategoryRelation({
|
||||
category: data.added.element,
|
||||
supermarket: this.selected_supermarket.id, order: 0
|
||||
}).then(results => {
|
||||
this.selected_supermarket.category_to_supermarket.push(results.data)
|
||||
})
|
||||
}
|
||||
|
||||
if ('moved' in data || 'added' in data) {
|
||||
this.supermarket_categories.forEach( (element,index) =>{
|
||||
let relation = this.selected_supermarket.category_to_supermarket.filter((el) => el.category.id === element.id)[0]
|
||||
console.log(relation)
|
||||
apiClient.partialUpdateSupermarketCategoryRelation(relation.id, {order: index})
|
||||
})
|
||||
}
|
||||
},
|
||||
selectedSupermarketChanged: function (supermarket, id) {
|
||||
this.supermarket_categories = []
|
||||
this.selectable_categories = this.categories
|
||||
|
||||
for (let i of supermarket.category_to_supermarket) {
|
||||
this.supermarket_categories.push(i.category)
|
||||
this.selectable_categories = this.selectable_categories.filter(function (el) {
|
||||
return el.id !== i.category.id
|
||||
});
|
||||
}
|
||||
},
|
||||
supermarketModalOk: function () {
|
||||
let apiClient = new ApiApiFactory()
|
||||
if (this.selected_supermarket.new) {
|
||||
@ -139,14 +181,13 @@ export default {
|
||||
},
|
||||
categoryModalOk: function () {
|
||||
let apiClient = new ApiApiFactory()
|
||||
if (this.selected_supermarket.new) {
|
||||
apiClient.createSupermarket({name: this.selected_supermarket.name}).then(results => {
|
||||
this.selected_supermarket = undefined
|
||||
if (this.selected_category.new) {
|
||||
apiClient.createSupermarketCategory({name: this.selected_category.name}).then(results => {
|
||||
this.selected_category = {}
|
||||
this.loadInitial()
|
||||
})
|
||||
} else {
|
||||
apiClient.partialUpdateSupermarket(this.selected_supermarket.id, {name: this.selected_supermarket.name})
|
||||
|
||||
apiClient.partialUpdateSupermarketCategory(this.selected_category.id, {name: this.selected_category.name})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,7 +2,7 @@
|
||||
<div row>
|
||||
<b-card no-body d-flex flex-column :class="{'border border-primary' : over, 'shake': isError}"
|
||||
refs="keywordCard"
|
||||
style="height: 10vh;" :style="{'cursor:grab' : draggle}"
|
||||
style="height: 10vh;" :style="{'cursor:grab' : draggable}"
|
||||
@dragover.prevent
|
||||
@dragenter.prevent
|
||||
:draggable="draggable"
|
||||
|
23
vue/src/components/PinnedRecipeBar.vue
Normal file
23
vue/src/components/PinnedRecipeBar.vue
Normal file
@ -0,0 +1,23 @@
|
||||
<template>
|
||||
<div>
|
||||
|
||||
<nav class="navbar navbar-expand-lg navbar-light bg-header"
|
||||
style="position: sticky; top: 0; left: 0; z-index: 1000;">
|
||||
|
||||
|
||||
<div class="collapse navbar-collapse" id="navbarText">
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "PinnedRecipeBar"
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
@ -1,9 +1,9 @@
|
||||
<template>
|
||||
|
||||
<div>
|
||||
<hr/>
|
||||
<hr />
|
||||
|
||||
<template v-if="step.type === 'TEXT'">
|
||||
<template v-if="step.type === 'TEXT' || step.type === 'RECIPE'">
|
||||
<div class="row" v-if="recipe.steps.length > 1">
|
||||
<div class="col col-md-8">
|
||||
<h5 class="text-primary">
|
||||
@ -22,15 +22,21 @@
|
||||
</div>
|
||||
<div class="col col-md-4" style="text-align: right">
|
||||
<b-button @click="details_visible = !details_visible" style="border: none; background: none"
|
||||
class="shadow-none d-print-none" :class="{ 'text-primary': details_visible, 'text-success': !details_visible}">
|
||||
class="shadow-none d-print-none"
|
||||
:class="{ 'text-primary': details_visible, 'text-success': !details_visible}">
|
||||
<i class="far fa-check-circle"></i>
|
||||
</b-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</template>
|
||||
|
||||
<template v-if="step.type === 'TEXT'">
|
||||
|
||||
<b-collapse id="collapse-1" v-model="details_visible">
|
||||
<div class="row">
|
||||
<div class="col col-md-4" v-if="step.ingredients.length > 0 && recipe.steps.length > 1">
|
||||
<div class="col col-md-4"
|
||||
v-if="step.ingredients.length > 0 && (recipe.steps.length > 1 || force_ingredients)">
|
||||
<table class="table table-sm">
|
||||
<!-- eslint-disable vue/no-v-for-template-key-on-child -->
|
||||
<template v-for="i in step.ingredients">
|
||||
@ -65,7 +71,8 @@
|
||||
|
||||
<div class="col-md-2" style="text-align: right">
|
||||
<b-button @click="details_visible = !details_visible" style="border: none; background: none"
|
||||
class="shadow-none d-print-none" :class="{ 'text-primary': details_visible, 'text-success': !details_visible}">
|
||||
class="shadow-none d-print-none"
|
||||
:class="{ 'text-primary': details_visible, 'text-success': !details_visible}">
|
||||
<i class="far fa-check-circle"></i>
|
||||
</b-button>
|
||||
</div>
|
||||
@ -89,13 +96,31 @@
|
||||
<img :src="step.file.file" style="max-width: 50vw; max-height: 50vh">
|
||||
</div>
|
||||
<div v-else>
|
||||
<a :href="step.file.file" target="_blank" rel="noreferrer nofollow">{{ $t('Download') }} {{ $t('File') }}</a>
|
||||
<a :href="step.file.file" target="_blank" rel="noreferrer nofollow">{{ $t('Download') }} {{
|
||||
$t('File')
|
||||
}}</a>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="card" v-if="step.type === 'RECIPE' && step.step_recipe_data !== null">
|
||||
<b-collapse id="collapse-1" v-model="details_visible">
|
||||
<div class="card-body">
|
||||
<h2 class="card-title">
|
||||
<a :href="resolveDjangoUrl('view_recipe',step.step_recipe_data.id)">{{ step.step_recipe_data.name }}</a>
|
||||
</h2>
|
||||
<div v-for="(sub_step, index) in step.step_recipe_data.steps" v-bind:key="`substep_${sub_step.id}`">
|
||||
<Step :recipe="step.step_recipe_data" :step="sub_step" :ingredient_factor="ingredient_factor" :index="index"
|
||||
:start_time="start_time" :force_ingredients="true"></Step>
|
||||
</div>
|
||||
</div>
|
||||
</b-collapse>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<div v-if="start_time !== ''">
|
||||
<b-popover
|
||||
:target="`id_reactive_popover_${step.id}`"
|
||||
@ -139,6 +164,8 @@ import {GettextMixin} from "@/utils/utils";
|
||||
import CompileComponent from "@/components/CompileComponent";
|
||||
import Vue from "vue";
|
||||
import moment from "moment";
|
||||
import Keywords from "@/components/Keywords";
|
||||
import {ResolveUrlMixin} from "@/utils/utils";
|
||||
|
||||
Vue.prototype.moment = moment
|
||||
|
||||
@ -146,6 +173,7 @@ export default {
|
||||
name: 'Step',
|
||||
mixins: [
|
||||
GettextMixin,
|
||||
ResolveUrlMixin,
|
||||
],
|
||||
components: {
|
||||
Ingredient,
|
||||
@ -157,6 +185,10 @@ export default {
|
||||
index: Number,
|
||||
recipe: Object,
|
||||
start_time: String,
|
||||
force_ingredients: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
@ -20,6 +20,8 @@
|
||||
"Add_to_Shopping": "Add to Shopping",
|
||||
"Add_to_Plan": "Add to Plan",
|
||||
"Step_start_time": "Step start time",
|
||||
"Sort_by_new": "Sort by new",
|
||||
"Recipes_per_page": "Recipes per Page",
|
||||
|
||||
"Meal_Plan": "Meal Plan",
|
||||
"Select_Book": "Select Book",
|
||||
|
@ -53,5 +53,26 @@
|
||||
"Supermarket": "Supermarkt",
|
||||
"Categories": "Categorieën",
|
||||
"Category": "Categorie",
|
||||
"Selected": "Geselecteerd"
|
||||
"Selected": "Geselecteerd",
|
||||
"Copy": "Kopie",
|
||||
"Link": "Link",
|
||||
"Sort_by_new": "Sorteer op nieuw",
|
||||
"Recipes_per_page": "Recepten per pagina",
|
||||
"Files": "Bestanden",
|
||||
"Size": "Grootte",
|
||||
"File": "Bestand",
|
||||
"err_fetching_resource": "Bij het ophalen van een hulpbron is een foutmelding opgetreden!",
|
||||
"err_creating_resource": "Bij het maken van een hulpbron is een foutmelding opgetreden!",
|
||||
"err_updating_resource": "Bij het updaten van een hulpbron is een foutmelding opgetreden!",
|
||||
"success_fetching_resource": "Hulpbron is succesvol opgehaald!",
|
||||
"success_creating_resource": "Hulpbron succesvol aangemaakt!",
|
||||
"success_updating_resource": "Hulpbron succesvol geüpdatet!",
|
||||
"Success": "Succes",
|
||||
"Download": "Download",
|
||||
"err_deleting_resource": "Bij het verwijderen van een hulpbron is een foutmelding opgetreden!",
|
||||
"success_deleting_resource": "Hulpbron succesvol verwijderd!",
|
||||
"Cancel": "Annuleer",
|
||||
"Delete": "Verwijder",
|
||||
"Ok": "Open",
|
||||
"Load_More": "Laad meer"
|
||||
}
|
||||
|
@ -1188,6 +1188,18 @@ export interface RecipeSteps {
|
||||
* @memberof RecipeSteps
|
||||
*/
|
||||
file?: StepFile | null;
|
||||
/**
|
||||
*
|
||||
* @type {number}
|
||||
* @memberof RecipeSteps
|
||||
*/
|
||||
step_recipe?: number | null;
|
||||
/**
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof RecipeSteps
|
||||
*/
|
||||
step_recipe_data?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1197,7 +1209,8 @@ export interface RecipeSteps {
|
||||
export enum RecipeStepsTypeEnum {
|
||||
Text = 'TEXT',
|
||||
Time = 'TIME',
|
||||
File = 'FILE'
|
||||
File = 'FILE',
|
||||
Recipe = 'RECIPE'
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1593,6 +1606,18 @@ export interface Step {
|
||||
* @memberof Step
|
||||
*/
|
||||
file?: StepFile | null;
|
||||
/**
|
||||
*
|
||||
* @type {number}
|
||||
* @memberof Step
|
||||
*/
|
||||
step_recipe?: number | null;
|
||||
/**
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof Step
|
||||
*/
|
||||
step_recipe_data?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1602,7 +1627,8 @@ export interface Step {
|
||||
export enum StepTypeEnum {
|
||||
Text = 'TEXT',
|
||||
Time = 'TIME',
|
||||
File = 'FILE'
|
||||
File = 'FILE',
|
||||
Recipe = 'RECIPE'
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1851,6 +1877,37 @@ export interface SupermarketCategory {
|
||||
*/
|
||||
name: string;
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @export
|
||||
* @interface SupermarketCategoryRelation
|
||||
*/
|
||||
export interface SupermarketCategoryRelation {
|
||||
/**
|
||||
*
|
||||
* @type {number}
|
||||
* @memberof SupermarketCategoryRelation
|
||||
*/
|
||||
id?: number;
|
||||
/**
|
||||
*
|
||||
* @type {ShoppingListSupermarketCategory}
|
||||
* @memberof SupermarketCategoryRelation
|
||||
*/
|
||||
category: ShoppingListSupermarketCategory;
|
||||
/**
|
||||
*
|
||||
* @type {number}
|
||||
* @memberof SupermarketCategoryRelation
|
||||
*/
|
||||
supermarket: number;
|
||||
/**
|
||||
*
|
||||
* @type {number}
|
||||
* @memberof SupermarketCategoryRelation
|
||||
*/
|
||||
order?: number;
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @export
|
||||
@ -2756,6 +2813,39 @@ export const ApiApiAxiosParamCreator = function (configuration?: Configuration)
|
||||
options: localVarRequestOptions,
|
||||
};
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @param {SupermarketCategoryRelation} [supermarketCategoryRelation]
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
createSupermarketCategoryRelation: async (supermarketCategoryRelation?: SupermarketCategoryRelation, options: any = {}): Promise<RequestArgs> => {
|
||||
const localVarPath = `/api/supermarket-category-relation/`;
|
||||
// use dummy base URL string because the URL constructor only accepts absolute URLs.
|
||||
const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
|
||||
let baseOptions;
|
||||
if (configuration) {
|
||||
baseOptions = configuration.baseOptions;
|
||||
}
|
||||
|
||||
const localVarRequestOptions = { method: 'POST', ...baseOptions, ...options};
|
||||
const localVarHeaderParameter = {} as any;
|
||||
const localVarQueryParameter = {} as any;
|
||||
|
||||
|
||||
|
||||
localVarHeaderParameter['Content-Type'] = 'application/json';
|
||||
|
||||
setSearchParams(localVarUrlObj, localVarQueryParameter, options.query);
|
||||
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
|
||||
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
|
||||
localVarRequestOptions.data = serializeDataIfNeeded(supermarketCategoryRelation, localVarRequestOptions, configuration)
|
||||
|
||||
return {
|
||||
url: toPathString(localVarUrlObj),
|
||||
options: localVarRequestOptions,
|
||||
};
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @param {Sync} [sync]
|
||||
@ -3528,6 +3618,39 @@ export const ApiApiAxiosParamCreator = function (configuration?: Configuration)
|
||||
|
||||
|
||||
|
||||
setSearchParams(localVarUrlObj, localVarQueryParameter, options.query);
|
||||
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
|
||||
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
|
||||
|
||||
return {
|
||||
url: toPathString(localVarUrlObj),
|
||||
options: localVarRequestOptions,
|
||||
};
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @param {string} id A unique integer value identifying this supermarket category relation.
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
destroySupermarketCategoryRelation: async (id: string, options: any = {}): Promise<RequestArgs> => {
|
||||
// verify required parameter 'id' is not null or undefined
|
||||
assertParamExists('destroySupermarketCategoryRelation', 'id', id)
|
||||
const localVarPath = `/api/supermarket-category-relation/{id}/`
|
||||
.replace(`{${"id"}}`, encodeURIComponent(String(id)));
|
||||
// use dummy base URL string because the URL constructor only accepts absolute URLs.
|
||||
const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
|
||||
let baseOptions;
|
||||
if (configuration) {
|
||||
baseOptions = configuration.baseOptions;
|
||||
}
|
||||
|
||||
const localVarRequestOptions = { method: 'DELETE', ...baseOptions, ...options};
|
||||
const localVarHeaderParameter = {} as any;
|
||||
const localVarQueryParameter = {} as any;
|
||||
|
||||
|
||||
|
||||
setSearchParams(localVarUrlObj, localVarQueryParameter, options.query);
|
||||
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
|
||||
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
|
||||
@ -4070,12 +4193,13 @@ export const ApiApiAxiosParamCreator = function (configuration?: Configuration)
|
||||
* @param {string} [booksOr] If recipe should be in all (AND) or any (OR) any of the provided books.
|
||||
* @param {string} [internal] true or false. If only internal recipes should be returned or not.
|
||||
* @param {string} [random] true or false. returns the results in randomized order.
|
||||
* @param {string} [_new] true or false. returns new results first in search results
|
||||
* @param {number} [page] A page number within the paginated result set.
|
||||
* @param {number} [pageSize] Number of results to return per page.
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
listRecipes: async (query?: string, keywords?: string, foods?: string, books?: string, keywordsOr?: string, foodsOr?: string, booksOr?: string, internal?: string, random?: string, page?: number, pageSize?: number, options: any = {}): Promise<RequestArgs> => {
|
||||
listRecipes: async (query?: string, keywords?: string, foods?: string, books?: string, keywordsOr?: string, foodsOr?: string, booksOr?: string, internal?: string, random?: string, _new?: string, page?: number, pageSize?: number, options: any = {}): Promise<RequestArgs> => {
|
||||
const localVarPath = `/api/recipe/`;
|
||||
// use dummy base URL string because the URL constructor only accepts absolute URLs.
|
||||
const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
|
||||
@ -4124,6 +4248,10 @@ export const ApiApiAxiosParamCreator = function (configuration?: Configuration)
|
||||
localVarQueryParameter['random'] = random;
|
||||
}
|
||||
|
||||
if (_new !== undefined) {
|
||||
localVarQueryParameter['new'] = _new;
|
||||
}
|
||||
|
||||
if (page !== undefined) {
|
||||
localVarQueryParameter['page'] = page;
|
||||
}
|
||||
@ -4279,6 +4407,35 @@ export const ApiApiAxiosParamCreator = function (configuration?: Configuration)
|
||||
|
||||
|
||||
|
||||
setSearchParams(localVarUrlObj, localVarQueryParameter, options.query);
|
||||
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
|
||||
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
|
||||
|
||||
return {
|
||||
url: toPathString(localVarUrlObj),
|
||||
options: localVarRequestOptions,
|
||||
};
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
listSupermarketCategoryRelations: async (options: any = {}): Promise<RequestArgs> => {
|
||||
const localVarPath = `/api/supermarket-category-relation/`;
|
||||
// use dummy base URL string because the URL constructor only accepts absolute URLs.
|
||||
const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
|
||||
let baseOptions;
|
||||
if (configuration) {
|
||||
baseOptions = configuration.baseOptions;
|
||||
}
|
||||
|
||||
const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options};
|
||||
const localVarHeaderParameter = {} as any;
|
||||
const localVarQueryParameter = {} as any;
|
||||
|
||||
|
||||
|
||||
setSearchParams(localVarUrlObj, localVarQueryParameter, options.query);
|
||||
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
|
||||
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
|
||||
@ -5297,6 +5454,43 @@ export const ApiApiAxiosParamCreator = function (configuration?: Configuration)
|
||||
options: localVarRequestOptions,
|
||||
};
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @param {string} id A unique integer value identifying this supermarket category relation.
|
||||
* @param {SupermarketCategoryRelation} [supermarketCategoryRelation]
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
partialUpdateSupermarketCategoryRelation: async (id: string, supermarketCategoryRelation?: SupermarketCategoryRelation, options: any = {}): Promise<RequestArgs> => {
|
||||
// verify required parameter 'id' is not null or undefined
|
||||
assertParamExists('partialUpdateSupermarketCategoryRelation', 'id', id)
|
||||
const localVarPath = `/api/supermarket-category-relation/{id}/`
|
||||
.replace(`{${"id"}}`, encodeURIComponent(String(id)));
|
||||
// use dummy base URL string because the URL constructor only accepts absolute URLs.
|
||||
const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
|
||||
let baseOptions;
|
||||
if (configuration) {
|
||||
baseOptions = configuration.baseOptions;
|
||||
}
|
||||
|
||||
const localVarRequestOptions = { method: 'PATCH', ...baseOptions, ...options};
|
||||
const localVarHeaderParameter = {} as any;
|
||||
const localVarQueryParameter = {} as any;
|
||||
|
||||
|
||||
|
||||
localVarHeaderParameter['Content-Type'] = 'application/json';
|
||||
|
||||
setSearchParams(localVarUrlObj, localVarQueryParameter, options.query);
|
||||
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
|
||||
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
|
||||
localVarRequestOptions.data = serializeDataIfNeeded(supermarketCategoryRelation, localVarRequestOptions, configuration)
|
||||
|
||||
return {
|
||||
url: toPathString(localVarUrlObj),
|
||||
options: localVarRequestOptions,
|
||||
};
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @param {string} id A unique integer value identifying this sync.
|
||||
@ -6089,6 +6283,39 @@ export const ApiApiAxiosParamCreator = function (configuration?: Configuration)
|
||||
|
||||
|
||||
|
||||
setSearchParams(localVarUrlObj, localVarQueryParameter, options.query);
|
||||
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
|
||||
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
|
||||
|
||||
return {
|
||||
url: toPathString(localVarUrlObj),
|
||||
options: localVarRequestOptions,
|
||||
};
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @param {string} id A unique integer value identifying this supermarket category relation.
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
retrieveSupermarketCategoryRelation: async (id: string, options: any = {}): Promise<RequestArgs> => {
|
||||
// verify required parameter 'id' is not null or undefined
|
||||
assertParamExists('retrieveSupermarketCategoryRelation', 'id', id)
|
||||
const localVarPath = `/api/supermarket-category-relation/{id}/`
|
||||
.replace(`{${"id"}}`, encodeURIComponent(String(id)));
|
||||
// use dummy base URL string because the URL constructor only accepts absolute URLs.
|
||||
const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
|
||||
let baseOptions;
|
||||
if (configuration) {
|
||||
baseOptions = configuration.baseOptions;
|
||||
}
|
||||
|
||||
const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options};
|
||||
const localVarHeaderParameter = {} as any;
|
||||
const localVarQueryParameter = {} as any;
|
||||
|
||||
|
||||
|
||||
setSearchParams(localVarUrlObj, localVarQueryParameter, options.query);
|
||||
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
|
||||
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
|
||||
@ -6995,6 +7222,43 @@ export const ApiApiAxiosParamCreator = function (configuration?: Configuration)
|
||||
options: localVarRequestOptions,
|
||||
};
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @param {string} id A unique integer value identifying this supermarket category relation.
|
||||
* @param {SupermarketCategoryRelation} [supermarketCategoryRelation]
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
updateSupermarketCategoryRelation: async (id: string, supermarketCategoryRelation?: SupermarketCategoryRelation, options: any = {}): Promise<RequestArgs> => {
|
||||
// verify required parameter 'id' is not null or undefined
|
||||
assertParamExists('updateSupermarketCategoryRelation', 'id', id)
|
||||
const localVarPath = `/api/supermarket-category-relation/{id}/`
|
||||
.replace(`{${"id"}}`, encodeURIComponent(String(id)));
|
||||
// use dummy base URL string because the URL constructor only accepts absolute URLs.
|
||||
const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
|
||||
let baseOptions;
|
||||
if (configuration) {
|
||||
baseOptions = configuration.baseOptions;
|
||||
}
|
||||
|
||||
const localVarRequestOptions = { method: 'PUT', ...baseOptions, ...options};
|
||||
const localVarHeaderParameter = {} as any;
|
||||
const localVarQueryParameter = {} as any;
|
||||
|
||||
|
||||
|
||||
localVarHeaderParameter['Content-Type'] = 'application/json';
|
||||
|
||||
setSearchParams(localVarUrlObj, localVarQueryParameter, options.query);
|
||||
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
|
||||
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
|
||||
localVarRequestOptions.data = serializeDataIfNeeded(supermarketCategoryRelation, localVarRequestOptions, configuration)
|
||||
|
||||
return {
|
||||
url: toPathString(localVarUrlObj),
|
||||
options: localVarRequestOptions,
|
||||
};
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @param {string} id A unique integer value identifying this sync.
|
||||
@ -7392,6 +7656,16 @@ export const ApiApiFp = function(configuration?: Configuration) {
|
||||
const localVarAxiosArgs = await localVarAxiosParamCreator.createSupermarketCategory(supermarketCategory, options);
|
||||
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @param {SupermarketCategoryRelation} [supermarketCategoryRelation]
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
async createSupermarketCategoryRelation(supermarketCategoryRelation?: SupermarketCategoryRelation, options?: any): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<SupermarketCategoryRelation>> {
|
||||
const localVarAxiosArgs = await localVarAxiosParamCreator.createSupermarketCategoryRelation(supermarketCategoryRelation, options);
|
||||
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @param {Sync} [sync]
|
||||
@ -7625,6 +7899,16 @@ export const ApiApiFp = function(configuration?: Configuration) {
|
||||
const localVarAxiosArgs = await localVarAxiosParamCreator.destroySupermarketCategory(id, options);
|
||||
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @param {string} id A unique integer value identifying this supermarket category relation.
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
async destroySupermarketCategoryRelation(id: string, options?: any): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<void>> {
|
||||
const localVarAxiosArgs = await localVarAxiosParamCreator.destroySupermarketCategoryRelation(id, options);
|
||||
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @param {string} id A unique integer value identifying this sync.
|
||||
@ -7792,13 +8076,14 @@ export const ApiApiFp = function(configuration?: Configuration) {
|
||||
* @param {string} [booksOr] If recipe should be in all (AND) or any (OR) any of the provided books.
|
||||
* @param {string} [internal] true or false. If only internal recipes should be returned or not.
|
||||
* @param {string} [random] true or false. returns the results in randomized order.
|
||||
* @param {string} [_new] true or false. returns new results first in search results
|
||||
* @param {number} [page] A page number within the paginated result set.
|
||||
* @param {number} [pageSize] Number of results to return per page.
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
async listRecipes(query?: string, keywords?: string, foods?: string, books?: string, keywordsOr?: string, foodsOr?: string, booksOr?: string, internal?: string, random?: string, page?: number, pageSize?: number, options?: any): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<InlineResponse2001>> {
|
||||
const localVarAxiosArgs = await localVarAxiosParamCreator.listRecipes(query, keywords, foods, books, keywordsOr, foodsOr, booksOr, internal, random, page, pageSize, options);
|
||||
async listRecipes(query?: string, keywords?: string, foods?: string, books?: string, keywordsOr?: string, foodsOr?: string, booksOr?: string, internal?: string, random?: string, _new?: string, page?: number, pageSize?: number, options?: any): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<InlineResponse2001>> {
|
||||
const localVarAxiosArgs = await localVarAxiosParamCreator.listRecipes(query, keywords, foods, books, keywordsOr, foodsOr, booksOr, internal, random, _new, page, pageSize, options);
|
||||
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
|
||||
},
|
||||
/**
|
||||
@ -7846,6 +8131,15 @@ export const ApiApiFp = function(configuration?: Configuration) {
|
||||
const localVarAxiosArgs = await localVarAxiosParamCreator.listStorages(options);
|
||||
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
async listSupermarketCategoryRelations(options?: any): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<Array<SupermarketCategoryRelation>>> {
|
||||
const localVarAxiosArgs = await localVarAxiosParamCreator.listSupermarketCategoryRelations(options);
|
||||
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @param {*} [options] Override http request option.
|
||||
@ -8149,6 +8443,17 @@ export const ApiApiFp = function(configuration?: Configuration) {
|
||||
const localVarAxiosArgs = await localVarAxiosParamCreator.partialUpdateSupermarketCategory(id, supermarketCategory, options);
|
||||
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @param {string} id A unique integer value identifying this supermarket category relation.
|
||||
* @param {SupermarketCategoryRelation} [supermarketCategoryRelation]
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
async partialUpdateSupermarketCategoryRelation(id: string, supermarketCategoryRelation?: SupermarketCategoryRelation, options?: any): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<SupermarketCategoryRelation>> {
|
||||
const localVarAxiosArgs = await localVarAxiosParamCreator.partialUpdateSupermarketCategoryRelation(id, supermarketCategoryRelation, options);
|
||||
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @param {string} id A unique integer value identifying this sync.
|
||||
@ -8387,6 +8692,16 @@ export const ApiApiFp = function(configuration?: Configuration) {
|
||||
const localVarAxiosArgs = await localVarAxiosParamCreator.retrieveSupermarketCategory(id, options);
|
||||
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @param {string} id A unique integer value identifying this supermarket category relation.
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
async retrieveSupermarketCategoryRelation(id: string, options?: any): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<SupermarketCategoryRelation>> {
|
||||
const localVarAxiosArgs = await localVarAxiosParamCreator.retrieveSupermarketCategoryRelation(id, options);
|
||||
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @param {string} id A unique integer value identifying this sync.
|
||||
@ -8655,6 +8970,17 @@ export const ApiApiFp = function(configuration?: Configuration) {
|
||||
const localVarAxiosArgs = await localVarAxiosParamCreator.updateSupermarketCategory(id, supermarketCategory, options);
|
||||
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @param {string} id A unique integer value identifying this supermarket category relation.
|
||||
* @param {SupermarketCategoryRelation} [supermarketCategoryRelation]
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
async updateSupermarketCategoryRelation(id: string, supermarketCategoryRelation?: SupermarketCategoryRelation, options?: any): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<SupermarketCategoryRelation>> {
|
||||
const localVarAxiosArgs = await localVarAxiosParamCreator.updateSupermarketCategoryRelation(id, supermarketCategoryRelation, options);
|
||||
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @param {string} id A unique integer value identifying this sync.
|
||||
@ -8885,6 +9211,15 @@ export const ApiApiFactory = function (configuration?: Configuration, basePath?:
|
||||
createSupermarketCategory(supermarketCategory?: SupermarketCategory, options?: any): AxiosPromise<SupermarketCategory> {
|
||||
return localVarFp.createSupermarketCategory(supermarketCategory, options).then((request) => request(axios, basePath));
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @param {SupermarketCategoryRelation} [supermarketCategoryRelation]
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
createSupermarketCategoryRelation(supermarketCategoryRelation?: SupermarketCategoryRelation, options?: any): AxiosPromise<SupermarketCategoryRelation> {
|
||||
return localVarFp.createSupermarketCategoryRelation(supermarketCategoryRelation, options).then((request) => request(axios, basePath));
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @param {Sync} [sync]
|
||||
@ -9095,6 +9430,15 @@ export const ApiApiFactory = function (configuration?: Configuration, basePath?:
|
||||
destroySupermarketCategory(id: string, options?: any): AxiosPromise<void> {
|
||||
return localVarFp.destroySupermarketCategory(id, options).then((request) => request(axios, basePath));
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @param {string} id A unique integer value identifying this supermarket category relation.
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
destroySupermarketCategoryRelation(id: string, options?: any): AxiosPromise<void> {
|
||||
return localVarFp.destroySupermarketCategoryRelation(id, options).then((request) => request(axios, basePath));
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @param {string} id A unique integer value identifying this sync.
|
||||
@ -9246,13 +9590,14 @@ export const ApiApiFactory = function (configuration?: Configuration, basePath?:
|
||||
* @param {string} [booksOr] If recipe should be in all (AND) or any (OR) any of the provided books.
|
||||
* @param {string} [internal] true or false. If only internal recipes should be returned or not.
|
||||
* @param {string} [random] true or false. returns the results in randomized order.
|
||||
* @param {string} [_new] true or false. returns new results first in search results
|
||||
* @param {number} [page] A page number within the paginated result set.
|
||||
* @param {number} [pageSize] Number of results to return per page.
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
listRecipes(query?: string, keywords?: string, foods?: string, books?: string, keywordsOr?: string, foodsOr?: string, booksOr?: string, internal?: string, random?: string, page?: number, pageSize?: number, options?: any): AxiosPromise<InlineResponse2001> {
|
||||
return localVarFp.listRecipes(query, keywords, foods, books, keywordsOr, foodsOr, booksOr, internal, random, page, pageSize, options).then((request) => request(axios, basePath));
|
||||
listRecipes(query?: string, keywords?: string, foods?: string, books?: string, keywordsOr?: string, foodsOr?: string, booksOr?: string, internal?: string, random?: string, _new?: string, page?: number, pageSize?: number, options?: any): AxiosPromise<InlineResponse2001> {
|
||||
return localVarFp.listRecipes(query, keywords, foods, books, keywordsOr, foodsOr, booksOr, internal, random, _new, page, pageSize, options).then((request) => request(axios, basePath));
|
||||
},
|
||||
/**
|
||||
*
|
||||
@ -9294,6 +9639,14 @@ export const ApiApiFactory = function (configuration?: Configuration, basePath?:
|
||||
listStorages(options?: any): AxiosPromise<Array<Storage>> {
|
||||
return localVarFp.listStorages(options).then((request) => request(axios, basePath));
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
listSupermarketCategoryRelations(options?: any): AxiosPromise<Array<SupermarketCategoryRelation>> {
|
||||
return localVarFp.listSupermarketCategoryRelations(options).then((request) => request(axios, basePath));
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @param {*} [options] Override http request option.
|
||||
@ -9568,6 +9921,16 @@ export const ApiApiFactory = function (configuration?: Configuration, basePath?:
|
||||
partialUpdateSupermarketCategory(id: string, supermarketCategory?: SupermarketCategory, options?: any): AxiosPromise<SupermarketCategory> {
|
||||
return localVarFp.partialUpdateSupermarketCategory(id, supermarketCategory, options).then((request) => request(axios, basePath));
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @param {string} id A unique integer value identifying this supermarket category relation.
|
||||
* @param {SupermarketCategoryRelation} [supermarketCategoryRelation]
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
partialUpdateSupermarketCategoryRelation(id: string, supermarketCategoryRelation?: SupermarketCategoryRelation, options?: any): AxiosPromise<SupermarketCategoryRelation> {
|
||||
return localVarFp.partialUpdateSupermarketCategoryRelation(id, supermarketCategoryRelation, options).then((request) => request(axios, basePath));
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @param {string} id A unique integer value identifying this sync.
|
||||
@ -9783,6 +10146,15 @@ export const ApiApiFactory = function (configuration?: Configuration, basePath?:
|
||||
retrieveSupermarketCategory(id: string, options?: any): AxiosPromise<SupermarketCategory> {
|
||||
return localVarFp.retrieveSupermarketCategory(id, options).then((request) => request(axios, basePath));
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @param {string} id A unique integer value identifying this supermarket category relation.
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
retrieveSupermarketCategoryRelation(id: string, options?: any): AxiosPromise<SupermarketCategoryRelation> {
|
||||
return localVarFp.retrieveSupermarketCategoryRelation(id, options).then((request) => request(axios, basePath));
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @param {string} id A unique integer value identifying this sync.
|
||||
@ -10026,6 +10398,16 @@ export const ApiApiFactory = function (configuration?: Configuration, basePath?:
|
||||
updateSupermarketCategory(id: string, supermarketCategory?: SupermarketCategory, options?: any): AxiosPromise<SupermarketCategory> {
|
||||
return localVarFp.updateSupermarketCategory(id, supermarketCategory, options).then((request) => request(axios, basePath));
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @param {string} id A unique integer value identifying this supermarket category relation.
|
||||
* @param {SupermarketCategoryRelation} [supermarketCategoryRelation]
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
updateSupermarketCategoryRelation(id: string, supermarketCategoryRelation?: SupermarketCategoryRelation, options?: any): AxiosPromise<SupermarketCategoryRelation> {
|
||||
return localVarFp.updateSupermarketCategoryRelation(id, supermarketCategoryRelation, options).then((request) => request(axios, basePath));
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @param {string} id A unique integer value identifying this sync.
|
||||
@ -10287,6 +10669,17 @@ export class ApiApi extends BaseAPI {
|
||||
return ApiApiFp(this.configuration).createSupermarketCategory(supermarketCategory, options).then((request) => request(this.axios, this.basePath));
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {SupermarketCategoryRelation} [supermarketCategoryRelation]
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
* @memberof ApiApi
|
||||
*/
|
||||
public createSupermarketCategoryRelation(supermarketCategoryRelation?: SupermarketCategoryRelation, options?: any) {
|
||||
return ApiApiFp(this.configuration).createSupermarketCategoryRelation(supermarketCategoryRelation, options).then((request) => request(this.axios, this.basePath));
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {Sync} [sync]
|
||||
@ -10543,6 +10936,17 @@ export class ApiApi extends BaseAPI {
|
||||
return ApiApiFp(this.configuration).destroySupermarketCategory(id, options).then((request) => request(this.axios, this.basePath));
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} id A unique integer value identifying this supermarket category relation.
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
* @memberof ApiApi
|
||||
*/
|
||||
public destroySupermarketCategoryRelation(id: string, options?: any) {
|
||||
return ApiApiFp(this.configuration).destroySupermarketCategoryRelation(id, options).then((request) => request(this.axios, this.basePath));
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} id A unique integer value identifying this sync.
|
||||
@ -10726,14 +11130,15 @@ export class ApiApi extends BaseAPI {
|
||||
* @param {string} [booksOr] If recipe should be in all (AND) or any (OR) any of the provided books.
|
||||
* @param {string} [internal] true or false. If only internal recipes should be returned or not.
|
||||
* @param {string} [random] true or false. returns the results in randomized order.
|
||||
* @param {string} [_new] true or false. returns new results first in search results
|
||||
* @param {number} [page] A page number within the paginated result set.
|
||||
* @param {number} [pageSize] Number of results to return per page.
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
* @memberof ApiApi
|
||||
*/
|
||||
public listRecipes(query?: string, keywords?: string, foods?: string, books?: string, keywordsOr?: string, foodsOr?: string, booksOr?: string, internal?: string, random?: string, page?: number, pageSize?: number, options?: any) {
|
||||
return ApiApiFp(this.configuration).listRecipes(query, keywords, foods, books, keywordsOr, foodsOr, booksOr, internal, random, page, pageSize, options).then((request) => request(this.axios, this.basePath));
|
||||
public listRecipes(query?: string, keywords?: string, foods?: string, books?: string, keywordsOr?: string, foodsOr?: string, booksOr?: string, internal?: string, random?: string, _new?: string, page?: number, pageSize?: number, options?: any) {
|
||||
return ApiApiFp(this.configuration).listRecipes(query, keywords, foods, books, keywordsOr, foodsOr, booksOr, internal, random, _new, page, pageSize, options).then((request) => request(this.axios, this.basePath));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -10786,6 +11191,16 @@ export class ApiApi extends BaseAPI {
|
||||
return ApiApiFp(this.configuration).listStorages(options).then((request) => request(this.axios, this.basePath));
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
* @memberof ApiApi
|
||||
*/
|
||||
public listSupermarketCategoryRelations(options?: any) {
|
||||
return ApiApiFp(this.configuration).listSupermarketCategoryRelations(options).then((request) => request(this.axios, this.basePath));
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {*} [options] Override http request option.
|
||||
@ -11118,6 +11533,18 @@ export class ApiApi extends BaseAPI {
|
||||
return ApiApiFp(this.configuration).partialUpdateSupermarketCategory(id, supermarketCategory, options).then((request) => request(this.axios, this.basePath));
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} id A unique integer value identifying this supermarket category relation.
|
||||
* @param {SupermarketCategoryRelation} [supermarketCategoryRelation]
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
* @memberof ApiApi
|
||||
*/
|
||||
public partialUpdateSupermarketCategoryRelation(id: string, supermarketCategoryRelation?: SupermarketCategoryRelation, options?: any) {
|
||||
return ApiApiFp(this.configuration).partialUpdateSupermarketCategoryRelation(id, supermarketCategoryRelation, options).then((request) => request(this.axios, this.basePath));
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} id A unique integer value identifying this sync.
|
||||
@ -11379,6 +11806,17 @@ export class ApiApi extends BaseAPI {
|
||||
return ApiApiFp(this.configuration).retrieveSupermarketCategory(id, options).then((request) => request(this.axios, this.basePath));
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} id A unique integer value identifying this supermarket category relation.
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
* @memberof ApiApi
|
||||
*/
|
||||
public retrieveSupermarketCategoryRelation(id: string, options?: any) {
|
||||
return ApiApiFp(this.configuration).retrieveSupermarketCategoryRelation(id, options).then((request) => request(this.axios, this.basePath));
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} id A unique integer value identifying this sync.
|
||||
@ -11672,6 +12110,18 @@ export class ApiApi extends BaseAPI {
|
||||
return ApiApiFp(this.configuration).updateSupermarketCategory(id, supermarketCategory, options).then((request) => request(this.axios, this.basePath));
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} id A unique integer value identifying this supermarket category relation.
|
||||
* @param {SupermarketCategoryRelation} [supermarketCategoryRelation]
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
* @memberof ApiApi
|
||||
*/
|
||||
public updateSupermarketCategoryRelation(id: string, supermarketCategoryRelation?: SupermarketCategoryRelation, options?: any) {
|
||||
return ApiApiFp(this.configuration).updateSupermarketCategoryRelation(id, supermarketCategoryRelation, options).then((request) => request(this.axios, this.basePath));
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} id A unique integer value identifying this sync.
|
||||
|
@ -79,7 +79,10 @@ module.exports = {
|
||||
priority: 1
|
||||
},
|
||||
},
|
||||
});
|
||||
},
|
||||
// TODO make this conditional on .env DEBUG = TRUE
|
||||
// config.optimization.minimize(false)
|
||||
);
|
||||
|
||||
//TODO somehow remov them as they are also added to the manifest config of the service worker
|
||||
/*
|
||||
|
@ -1063,6 +1063,20 @@
|
||||
resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.9.2.tgz#adea7b6953cbb34651766b0548468e743c6a2353"
|
||||
integrity sha512-VZMYa7+fXHdwIq1TDhSXoVmSPEGM/aa+6Aiq3nVVJ9bXr24zScr+NlKFKC3iPljA7ho/GAZr+d2jOf5GIRC30Q==
|
||||
|
||||
"@riophae/vue-treeselect@^0.4.0":
|
||||
version "0.4.0"
|
||||
resolved "https://registry.yarnpkg.com/@riophae/vue-treeselect/-/vue-treeselect-0.4.0.tgz#0baed5a794cffc580b63591f35c125e51c0df241"
|
||||
integrity sha512-J4atYmBqXQmiPFK/0B5sXKjtnGc21mBJEiyKIDZwk0Q9XuynVFX6IJ4EpaLmUgL5Tve7HAS7wkiGGSti6Uaxcg==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.3.1"
|
||||
babel-helper-vue-jsx-merge-props "^2.0.3"
|
||||
easings-css "^1.0.0"
|
||||
fuzzysearch "^1.0.3"
|
||||
is-promise "^2.1.0"
|
||||
lodash "^4.0.0"
|
||||
material-colors "^1.2.6"
|
||||
watch-size "^2.0.0"
|
||||
|
||||
"@rollup/plugin-babel@^5.2.0":
|
||||
version "5.3.0"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/plugin-babel/-/plugin-babel-5.3.0.tgz#9cb1c5146ddd6a4968ad96f209c50c62f92f9879"
|
||||
@ -2255,6 +2269,11 @@ babel-extract-comments@^1.0.0:
|
||||
dependencies:
|
||||
babylon "^6.18.0"
|
||||
|
||||
babel-helper-vue-jsx-merge-props@^2.0.3:
|
||||
version "2.0.3"
|
||||
resolved "https://registry.yarnpkg.com/babel-helper-vue-jsx-merge-props/-/babel-helper-vue-jsx-merge-props-2.0.3.tgz#22aebd3b33902328e513293a8e4992b384f9f1b6"
|
||||
integrity sha512-gsLiKK7Qrb7zYJNgiXKpXblxbV5ffSwR0f5whkPAaBAR4fhi6bwRZxX9wBlIc5M/v8CCkXUbXZL4N/nSE97cqg==
|
||||
|
||||
babel-loader@^8.1.0:
|
||||
version "8.2.2"
|
||||
resolved "https://registry.yarnpkg.com/babel-loader/-/babel-loader-8.2.2.tgz#9363ce84c10c9a40e6c753748e1441b60c8a0b81"
|
||||
@ -3804,6 +3823,11 @@ duplexify@^3.4.2, duplexify@^3.6.0:
|
||||
readable-stream "^2.0.0"
|
||||
stream-shift "^1.0.0"
|
||||
|
||||
easings-css@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/easings-css/-/easings-css-1.0.0.tgz#dde569003bb7a4a0c0b77878f5db3e0be5679c81"
|
||||
integrity sha512-7Uq7NdazNfVtr0RNmPAys8it0zKCuaqxJStYKEl72D3j4gbvXhhaM7iWNbqhA4C94ygCye6VuyhzBRQC4szeBg==
|
||||
|
||||
easy-stack@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/easy-stack/-/easy-stack-1.0.1.tgz#8afe4264626988cabb11f3c704ccd0c835411066"
|
||||
@ -4674,6 +4698,11 @@ functional-red-black-tree@^1.0.1:
|
||||
resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327"
|
||||
integrity sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=
|
||||
|
||||
fuzzysearch@^1.0.3:
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/fuzzysearch/-/fuzzysearch-1.0.3.tgz#dffc80f6d6b04223f2226aa79dd194231096d008"
|
||||
integrity sha1-3/yA9tawQiPyImqnndGUIxCW0Ag=
|
||||
|
||||
generic-names@^2.0.1:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/generic-names/-/generic-names-2.0.1.tgz#f8a378ead2ccaa7a34f0317b05554832ae41b872"
|
||||
@ -5579,6 +5608,11 @@ is-plain-object@^2.0.3, is-plain-object@^2.0.4:
|
||||
dependencies:
|
||||
isobject "^3.0.1"
|
||||
|
||||
is-promise@^2.1.0:
|
||||
version "2.2.2"
|
||||
resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-2.2.2.tgz#39ab959ccbf9a774cf079f7b40c7a26f763135f1"
|
||||
integrity sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==
|
||||
|
||||
is-regex@^1.0.4, is-regex@^1.1.3:
|
||||
version "1.1.3"
|
||||
resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.3.tgz#d029f9aff6448b93ebbe3f33dac71511fdcbef9f"
|
||||
@ -6052,7 +6086,7 @@ lodash.uniq@^4.5.0:
|
||||
resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773"
|
||||
integrity sha1-0CJTc662Uq3BvILklFM5qEJ1R3M=
|
||||
|
||||
lodash@^4.17.11, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.21, lodash@^4.17.3:
|
||||
lodash@^4.0.0, lodash@^4.17.11, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.21, lodash@^4.17.3:
|
||||
version "4.17.21"
|
||||
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
|
||||
integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
|
||||
@ -6137,6 +6171,11 @@ map-visit@^1.0.0:
|
||||
dependencies:
|
||||
object-visit "^1.0.0"
|
||||
|
||||
material-colors@^1.2.6:
|
||||
version "1.2.6"
|
||||
resolved "https://registry.yarnpkg.com/material-colors/-/material-colors-1.2.6.tgz#6d1958871126992ceecc72f4bcc4d8f010865f46"
|
||||
integrity sha512-6qE4B9deFBIa9YSpOc9O0Sgc43zTeVYbgDT5veRKSlB2+ZuHNoVVxA1L/ckMUayV9Ay9y7Z/SZCLcGteW9i7bg==
|
||||
|
||||
md5.js@^1.3.4:
|
||||
version "1.3.5"
|
||||
resolved "https://registry.yarnpkg.com/md5.js/-/md5.js-1.3.5.tgz#b5d07b8e3216e3e27cd728d72f70d1e6a342005f"
|
||||
@ -9509,6 +9548,11 @@ vuex@^3.6.0:
|
||||
resolved "https://registry.yarnpkg.com/vuex/-/vuex-3.6.2.tgz#236bc086a870c3ae79946f107f16de59d5895e71"
|
||||
integrity sha512-ETW44IqCgBpVomy520DT5jf8n0zoCac+sxWnn+hMe/CzaSejb/eVw2YToiXYX+Ex/AuHHia28vWTq4goAexFbw==
|
||||
|
||||
watch-size@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/watch-size/-/watch-size-2.0.0.tgz#096ee28d0365bd7ea03d9c8bf1f2f50a73be1474"
|
||||
integrity sha512-M92R89dNoTPWyCD+HuUEDdhaDnh9jxPGOwlDc0u51jAgmjUvzqaEMynXSr3BaWs+QdHYk4KzibPy1TFtjLmOZQ==
|
||||
|
||||
watchpack-chokidar2@^2.0.1:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/watchpack-chokidar2/-/watchpack-chokidar2-2.0.1.tgz#38500072ee6ece66f3769936950ea1771be1c957"
|
||||
|
Loading…
Reference in New Issue
Block a user