remove facets and treeselect

This commit is contained in:
smilerz
2023-09-07 13:20:51 -05:00
parent d33b0d2254
commit 9ee4be621b
39 changed files with 12918 additions and 21842 deletions

View File

@@ -3,7 +3,6 @@ import io
import json
import mimetypes
import pathlib
import random
import re
import threading
import traceback
@@ -15,7 +14,6 @@ from zipfile import ZipFile
import requests
import validators
from PIL import UnidentifiedImageError
from annoying.decorators import ajax_request
from annoying.functions import get_object_or_None
from django.contrib import messages
@@ -24,7 +22,7 @@ from django.contrib.postgres.search import TrigramSimilarity
from django.core.cache import caches
from django.core.exceptions import FieldError, ValidationError
from django.core.files import File
from django.db.models import Case, Count, Exists, OuterRef, ProtectedError, Q, Subquery, Value, When, Avg, Max
from django.db.models import Case, Count, Exists, OuterRef, ProtectedError, Q, Subquery, Value, When
from django.db.models.fields.related import ForeignObjectRel
from django.db.models.functions import Coalesce, Lower
from django.db.models.signals import post_save
@@ -36,6 +34,7 @@ from django.utils.translation import gettext as _
from django_scopes import scopes_disabled
from icalendar import Calendar, Event
from oauth2_provider.models import AccessToken
from PIL import UnidentifiedImageError
from recipe_scrapers import scrape_me
from recipe_scrapers._exceptions import NoSchemaFoundInWildMode
from requests.exceptions import MissingSchema
@@ -58,35 +57,41 @@ from cookbook.helper.HelperFunctions import str2bool
from cookbook.helper.image_processing import handle_image
from cookbook.helper.ingredient_parser import IngredientParser
from cookbook.helper.open_data_importer import OpenDataImporter
from cookbook.helper.permission_helper import (CustomIsAdmin, CustomIsOwner,
CustomIsOwnerReadOnly, CustomIsShared,
CustomIsSpaceOwner, CustomIsUser, group_required,
is_space_owner, switch_user_active_space, above_space_limit,
CustomRecipePermission, CustomUserPermission,
CustomTokenHasReadWriteScope, CustomTokenHasScope, has_group_permission, IsReadOnlyDRF)
from cookbook.helper.recipe_search import RecipeFacet, RecipeSearch
from cookbook.helper.recipe_url_import import get_from_youtube_scraper, get_images_from_soup, clean_dict
from cookbook.helper.permission_helper import (CustomIsAdmin, CustomIsOwner, CustomIsOwnerReadOnly,
CustomIsShared, CustomIsSpaceOwner, CustomIsUser,
CustomRecipePermission, CustomTokenHasReadWriteScope,
CustomTokenHasScope, CustomUserPermission,
IsReadOnlyDRF, above_space_limit, group_required,
has_group_permission, is_space_owner,
switch_user_active_space)
from cookbook.helper.recipe_search import RecipeSearch
from cookbook.helper.recipe_url_import import (clean_dict, get_from_youtube_scraper,
get_images_from_soup)
from cookbook.helper.scrapers.scrapers import text_scraper
from cookbook.helper.shopping_helper import RecipeShoppingEditor, shopping_helper
from cookbook.models import (Automation, BookmarkletImport, CookLog, CustomFilter, ExportLog, Food,
FoodInheritField, ImportLog, Ingredient, InviteLink, Keyword, MealPlan,
MealType, Recipe, RecipeBook, RecipeBookEntry, ShareLink, ShoppingList,
ShoppingListEntry, ShoppingListRecipe, Space, Step, Storage,
Supermarket, SupermarketCategory, SupermarketCategoryRelation, Sync,
SyncLog, Unit, UserFile, UserPreference, UserSpace, ViewLog, UnitConversion, PropertyType, Property)
MealType, Property, PropertyType, Recipe, RecipeBook, RecipeBookEntry,
ShareLink, ShoppingList, ShoppingListEntry, ShoppingListRecipe, Space,
Step, Storage, Supermarket, SupermarketCategory,
SupermarketCategoryRelation, Sync, SyncLog, Unit, UnitConversion,
UserFile, UserPreference, UserSpace, ViewLog)
from cookbook.provider.dropbox import Dropbox
from cookbook.provider.local import Local
from cookbook.provider.nextcloud import Nextcloud
from cookbook.schemas import FilterSchema, QueryParam, QueryParamAutoSchema, TreeSchema
from cookbook.serializer import (AutomationSerializer, BookmarkletImportListSerializer,
from cookbook.serializer import (AccessTokenSerializer, AutomationSerializer,
AutoMealPlanSerializer, BookmarkletImportListSerializer,
BookmarkletImportSerializer, CookLogSerializer,
CustomFilterSerializer, ExportLogSerializer,
FoodInheritFieldSerializer, FoodSerializer,
FoodShoppingUpdateSerializer, GroupSerializer, ImportLogSerializer,
IngredientSerializer, IngredientSimpleSerializer,
InviteLinkSerializer, KeywordSerializer, MealPlanSerializer,
MealTypeSerializer, RecipeBookEntrySerializer,
RecipeBookSerializer, RecipeFromSourceSerializer,
FoodShoppingUpdateSerializer, FoodSimpleSerializer,
GroupSerializer, ImportLogSerializer, IngredientSerializer,
IngredientSimpleSerializer, InviteLinkSerializer,
KeywordSerializer, MealPlanSerializer, MealTypeSerializer,
PropertySerializer, PropertyTypeSerializer,
RecipeBookEntrySerializer, RecipeBookSerializer,
RecipeExportSerializer, RecipeFromSourceSerializer,
RecipeImageSerializer, RecipeOverviewSerializer, RecipeSerializer,
RecipeShoppingUpdateSerializer, RecipeSimpleSerializer,
ShoppingListAutoSyncSerializer, ShoppingListEntrySerializer,
@@ -94,11 +99,9 @@ from cookbook.serializer import (AutomationSerializer, BookmarkletImportListSeri
SpaceSerializer, StepSerializer, StorageSerializer,
SupermarketCategoryRelationSerializer,
SupermarketCategorySerializer, SupermarketSerializer,
SyncLogSerializer, SyncSerializer, UnitSerializer,
UserFileSerializer, UserSerializer, UserPreferenceSerializer,
UserSpaceSerializer, ViewLogSerializer, AccessTokenSerializer, FoodSimpleSerializer,
RecipeExportSerializer, UnitConversionSerializer, PropertyTypeSerializer,
PropertySerializer, AutoMealPlanSerializer)
SyncLogSerializer, SyncSerializer, UnitConversionSerializer,
UnitSerializer, UserFileSerializer, UserPreferenceSerializer,
UserSerializer, UserSpaceSerializer, ViewLogSerializer)
from cookbook.views.import_export import get_integration
from recipes import settings
@@ -186,7 +189,8 @@ class FuzzyFilterMixin(ViewSetMixin, ExtendedRecipeMixin):
if query is not None and query not in ["''", '']:
if fuzzy and (settings.DATABASES['default']['ENGINE'] in ['django.db.backends.postgresql_psycopg2',
'django.db.backends.postgresql']):
if self.request.user.is_authenticated and any([self.model.__name__.lower() in x for x in self.request.user.searchpreference.unaccent.values_list('field', flat=True)]):
if self.request.user.is_authenticated and any(
[self.model.__name__.lower() in x for x in self.request.user.searchpreference.unaccent.values_list('field', flat=True)]):
self.queryset = self.queryset.annotate(trigram=TrigramSimilarity('name__unaccent', query))
else:
self.queryset = self.queryset.annotate(trigram=TrigramSimilarity('name', query))
@@ -367,7 +371,7 @@ class TreeMixin(MergeMixin, FuzzyFilterMixin, ExtendedRecipeMixin):
child.move(parent, f'{node_location}-child')
content = {'msg': _(f'{child.name} was moved successfully to parent {parent.name}')}
return Response(content, status=status.HTTP_200_OK)
except (PathOverflow, InvalidMoveToDescendant, InvalidPosition) as e:
except (PathOverflow, InvalidMoveToDescendant, InvalidPosition):
content = {'error': True, 'msg': _('An error occurred attempting to move ') + child.name}
return Response(content, status=status.HTTP_400_BAD_REQUEST)
@@ -775,8 +779,7 @@ class StepViewSet(viewsets.ModelViewSet):
permission_classes = [CustomIsUser & CustomTokenHasReadWriteScope]
pagination_class = DefaultPagination
query_params = [
QueryParam(name='recipe', description=_('ID of recipe a step is part of. For multiple repeat parameter.'),
qtype='int'),
QueryParam(name='recipe', description=_('ID of recipe a step is part of. For multiple repeat parameter.'), qtype='int'),
QueryParam(name='query', description=_('Query string matched (fuzzy) against object name.'), qtype='string'),
]
schema = QueryParamAutoSchema()
@@ -799,7 +802,6 @@ class RecipePagination(PageNumberPagination):
def paginate_queryset(self, queryset, request, view=None):
if queryset is None:
raise Exception
self.facets = RecipeFacet(request, queryset=queryset)
return super().paginate_queryset(queryset, request, view)
def get_paginated_response(self, data):
@@ -808,7 +810,6 @@ class RecipePagination(PageNumberPagination):
('next', self.get_next_link()),
('previous', self.get_previous_link()),
('results', data),
('facets', self.facets.get_facets(from_cache=True))
]))
@@ -820,63 +821,33 @@ class RecipeViewSet(viewsets.ModelViewSet):
pagination_class = RecipePagination
query_params = [
QueryParam(name='query', description=_(
'Query string matched (fuzzy) against recipe name. In the future also fulltext search.')),
QueryParam(name='keywords', description=_(
'ID of keyword a recipe should have. For multiple repeat parameter. Equivalent to keywords_or'),
qtype='int'),
QueryParam(name='keywords_or',
description=_('Keyword IDs, repeat for multiple. Return recipes with any of the keywords'),
qtype='int'),
QueryParam(name='keywords_and',
description=_('Keyword IDs, repeat for multiple. Return recipes with all of the keywords.'),
qtype='int'),
QueryParam(name='keywords_or_not',
description=_('Keyword IDs, repeat for multiple. Exclude recipes with any of the keywords.'),
qtype='int'),
QueryParam(name='keywords_and_not',
description=_('Keyword IDs, repeat for multiple. Exclude recipes with all of the keywords.'),
qtype='int'),
QueryParam(name='foods', description=_('ID of food a recipe should have. For multiple repeat parameter.'),
qtype='int'),
QueryParam(name='foods_or',
description=_('Food IDs, repeat for multiple. Return recipes with any of the foods'), qtype='int'),
QueryParam(name='foods_and',
description=_('Food IDs, repeat for multiple. Return recipes with all of the foods.'), qtype='int'),
QueryParam(name='foods_or_not',
description=_('Food IDs, repeat for multiple. Exclude recipes with any of the foods.'), qtype='int'),
QueryParam(name='foods_and_not',
description=_('Food IDs, repeat for multiple. Exclude recipes with all of the foods.'), qtype='int'),
QueryParam(name='query', description=_('Query string matched (fuzzy) against recipe name. In the future also fulltext search.')),
QueryParam(name='keywords', description=_('ID of keyword a recipe should have. For multiple repeat parameter. Equivalent to keywords_or'), qtype='int'),
QueryParam(name='keywords_or', description=_('Keyword IDs, repeat for multiple. Return recipes with any of the keywords'), qtype='int'),
QueryParam(name='keywords_and', description=_('Keyword IDs, repeat for multiple. Return recipes with all of the keywords.'), qtype='int'),
QueryParam(name='keywords_or_not', description=_('Keyword IDs, repeat for multiple. Exclude recipes with any of the keywords.'), qtype='int'),
QueryParam(name='keywords_and_not', description=_('Keyword IDs, repeat for multiple. Exclude recipes with all of the keywords.'), qtype='int'),
QueryParam(name='foods', description=_('ID of food a recipe should have. For multiple repeat parameter.'), qtype='int'),
QueryParam(name='foods_or', description=_('Food IDs, repeat for multiple. Return recipes with any of the foods'), qtype='int'),
QueryParam(name='foods_and', description=_('Food IDs, repeat for multiple. Return recipes with all of the foods.'), qtype='int'),
QueryParam(name='foods_or_not', description=_('Food IDs, repeat for multiple. Exclude recipes with any of the foods.'), qtype='int'),
QueryParam(name='foods_and_not', description=_('Food IDs, repeat for multiple. Exclude recipes with all of the foods.'), qtype='int'),
QueryParam(name='units', description=_('ID of unit a recipe should have.'), qtype='int'),
QueryParam(name='rating', description=_(
'Rating a recipe should have or greater. [0 - 5] Negative value filters rating less than.'), qtype='int'),
QueryParam(name='rating', description=_('Rating a recipe should have or greater. [0 - 5] Negative value filters rating less than.'), qtype='int'),
QueryParam(name='books', description=_('ID of book a recipe should be in. For multiple repeat parameter.')),
QueryParam(name='books_or',
description=_('Book IDs, repeat for multiple. Return recipes with any of the books'), qtype='int'),
QueryParam(name='books_and',
description=_('Book IDs, repeat for multiple. Return recipes with all of the books.'), qtype='int'),
QueryParam(name='books_or_not',
description=_('Book IDs, repeat for multiple. Exclude recipes with any of the books.'), qtype='int'),
QueryParam(name='books_and_not',
description=_('Book IDs, repeat for multiple. Exclude recipes with all of the books.'), qtype='int'),
QueryParam(name='internal',
description=_('If only internal recipes should be returned. [''true''/''<b>false</b>'']')),
QueryParam(name='random',
description=_('Returns the results in randomized order. [''true''/''<b>false</b>'']')),
QueryParam(name='new',
description=_('Returns new results first in search results. [''true''/''<b>false</b>'']')),
QueryParam(name='timescooked', description=_(
'Filter recipes cooked X times or more. Negative values returns cooked less than X times'), qtype='int'),
QueryParam(name='cookedon', description=_(
'Filter recipes last cooked on or after YYYY-MM-DD. Prepending ''-'' filters on or before date.')),
QueryParam(name='createdon', description=_(
'Filter recipes created on or after YYYY-MM-DD. Prepending ''-'' filters on or before date.')),
QueryParam(name='updatedon', description=_(
'Filter recipes updated on or after YYYY-MM-DD. Prepending ''-'' filters on or before date.')),
QueryParam(name='viewedon', description=_(
'Filter recipes lasts viewed on or after YYYY-MM-DD. Prepending ''-'' filters on or before date.')),
QueryParam(name='makenow',
description=_('Filter recipes that can be made with OnHand food. [''true''/''<b>false</b>'']')),
QueryParam(name='books_or', description=_('Book IDs, repeat for multiple. Return recipes with any of the books'), qtype='int'),
QueryParam(name='books_and', description=_('Book IDs, repeat for multiple. Return recipes with all of the books.'), qtype='int'),
QueryParam(name='books_or_not', description=_('Book IDs, repeat for multiple. Exclude recipes with any of the books.'), qtype='int'),
QueryParam(name='books_and_not', description=_('Book IDs, repeat for multiple. Exclude recipes with all of the books.'), qtype='int'),
QueryParam(name='internal', description=_('If only internal recipes should be returned. [''true''/''<b>false</b>'']')),
QueryParam(name='random', description=_('Returns the results in randomized order. [''true''/''<b>false</b>'']')),
QueryParam(name='new', description=_('Returns new results first in search results. [''true''/''<b>false</b>'']')),
QueryParam(name='timescooked', description=_('Filter recipes cooked X times or more. Negative values returns cooked less than X times'), qtype='int'),
QueryParam(name='cookedon', description=_('Filter recipes last cooked on or after YYYY-MM-DD. Prepending ''-'' filters on or before date.')),
QueryParam(name='createdon', description=_('Filter recipes created on or after YYYY-MM-DD. Prepending ''-'' filters on or before date.')),
QueryParam(name='updatedon', description=_('Filter recipes updated on or after YYYY-MM-DD. Prepending ''-'' filters on or before date.')),
QueryParam(name='viewedon', description=_('Filter recipes lasts viewed on or after YYYY-MM-DD. Prepending ''-'' filters on or before date.')),
QueryParam(name='makenow', description=_('Filter recipes that can be made with OnHand food. [''true''/''<b>false</b>'']')),
]
schema = QueryParamAutoSchema()
@@ -1095,17 +1066,10 @@ class ShoppingListEntryViewSet(viewsets.ModelViewSet):
serializer_class = ShoppingListEntrySerializer
permission_classes = [(CustomIsOwner | CustomIsShared) & CustomTokenHasReadWriteScope]
query_params = [
QueryParam(name='id',
description=_('Returns the shopping list entry with a primary key of id. Multiple values allowed.'),
qtype='int'),
QueryParam(
name='checked',
description=_(
'Filter shopping list entries on checked. [''true'', ''false'', ''both'', ''<b>recent</b>'']<br> - ''recent'' includes unchecked items and recently completed items.')
),
QueryParam(name='supermarket',
description=_('Returns the shopping list entries sorted by supermarket category order.'),
qtype='int'),
QueryParam(name='id', description=_('Returns the shopping list entry with a primary key of id. Multiple values allowed.'), qtype='int'),
QueryParam(name='checked', description=_('Filter shopping list entries on checked. [''true'', ''false'', ''both'', ''<b>recent</b>'']<br> - ''recent'' includes unchecked items and recently completed items.')
),
QueryParam(name='supermarket', description=_('Returns the shopping list entries sorted by supermarket category order.'), qtype='int'),
]
schema = QueryParamAutoSchema()
@@ -1344,12 +1308,10 @@ def recipe_from_source(request):
}, status=status.HTTP_400_BAD_REQUEST)
elif url and not data:
if re.match('^(https?://)?(www\.youtube\.com|youtu\.be)/.+$', url):
if re.match('^(https?://)?(www\\.youtube\\.com|youtu\\.be)/.+$', url):
if validators.url(url, public=True):
return Response({
'recipe_json': get_from_youtube_scraper(url, request),
# 'recipe_tree': '',
# 'recipe_html': '',
'recipe_images': [],
}, status=status.HTTP_200_OK)
if re.match(
@@ -1412,8 +1374,6 @@ def recipe_from_source(request):
if scrape:
return Response({
'recipe_json': helper.get_from_scraper(scrape, request),
# 'recipe_tree': recipe_tree,
# 'recipe_html': recipe_html,
'recipe_images': list(dict.fromkeys(get_images_from_soup(scrape.soup, url))),
}, status=status.HTTP_200_OK)
@@ -1437,7 +1397,7 @@ def reset_food_inheritance(request):
try:
Food.reset_inheritance(space=request.space)
return Response({'message': 'success', }, status=status.HTTP_200_OK)
except Exception as e:
except Exception:
traceback.print_exc()
return Response({}, status=status.HTTP_400_BAD_REQUEST)
@@ -1457,7 +1417,7 @@ def switch_active_space(request, space_id):
return Response(UserSpaceSerializer().to_representation(instance=user_space), status=status.HTTP_200_OK)
else:
return Response("not found", status=status.HTTP_404_NOT_FOUND)
except Exception as e:
except Exception:
traceback.print_exc()
return Response({}, status=status.HTTP_400_BAD_REQUEST)
@@ -1482,7 +1442,7 @@ def download_file(request, file_id):
response['Content-Disposition'] = 'attachment; filename="' + uf.name + '.zip"'
return response
except Exception as e:
except Exception:
traceback.print_exc()
return Response({}, status=status.HTTP_400_BAD_REQUEST)
@@ -1707,25 +1667,3 @@ def ingredient_from_string(request):
},
status=200
)
@group_required('user')
def get_facets(request):
key = request.GET.get('hash', None)
food = request.GET.get('food', None)
keyword = request.GET.get('keyword', None)
facets = RecipeFacet(request, hash_key=key)
if food:
results = facets.add_food_children(food)
elif keyword:
results = facets.add_keyword_children(keyword)
else:
results = facets.get_facets()
return JsonResponse(
{
'facets': results,
},
status=200
)