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:
vabene1111
2021-08-22 13:14:23 +02:00
74 changed files with 1599 additions and 463 deletions

View File

@@ -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)

View File

@@ -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,)

View File

@@ -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'))

View File

@@ -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({})

View File

@@ -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})