fixes keyword filter on OR search
This commit is contained in:
parent
7bab07bdaf
commit
2808e3033d
@ -1,5 +1,7 @@
|
||||
import django_filters
|
||||
from django.conf import settings
|
||||
from django.contrib.postgres.search import TrigramSimilarity
|
||||
from django.db.models import Q
|
||||
from django.utils.translation import gettext as _
|
||||
from django_scopes import scopes_disabled
|
||||
|
||||
|
@ -174,22 +174,22 @@ def search_recipes(request, queryset, params):
|
||||
return queryset
|
||||
|
||||
|
||||
def get_facet(qs, request):
|
||||
def get_facet(qs, params, space):
|
||||
# NOTE facet counts for tree models include self AND descendants
|
||||
facets = {}
|
||||
ratings = request.query_params.getlist('ratings', [])
|
||||
keyword_list = request.query_params.getlist('keywords', [])
|
||||
food_list = request.query_params.getlist('foods', [])
|
||||
book_list = request.query_params.getlist('book', [])
|
||||
search_keywords_or = request.query_params.get('keywords_or', True)
|
||||
search_foods_or = request.query_params.get('foods_or', True)
|
||||
search_books_or = request.query_params.get('books_or', True)
|
||||
ratings = params.getlist('ratings', [])
|
||||
keyword_list = params.getlist('keywords', [])
|
||||
ingredient_list = params.getlist('foods', [])
|
||||
book_list = params.getlist('book', [])
|
||||
search_keywords_or = params.get('keywords_or', True)
|
||||
search_foods_or = params.get('foods_or', True)
|
||||
search_books_or = params.get('books_or', True)
|
||||
|
||||
# if using an OR search, will annotate all keywords, otherwise, just those that appear in results
|
||||
if search_keywords_or:
|
||||
keywords = Keyword.objects.filter(space=request.space).annotate(recipe_count=Count('recipe'))
|
||||
keywords = Keyword.objects.filter(space=space).annotate(recipe_count=Count('recipe'))
|
||||
else:
|
||||
keywords = Keyword.objects.filter(recipe__in=qs, space=request.space).annotate(recipe_count=Count('recipe'))
|
||||
keywords = Keyword.objects.filter(recipe__in=qs, space=space).annotate(recipe_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(keywords, root=True, fill=True)
|
||||
|
@ -23,7 +23,7 @@ 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
|
||||
|
@ -37,16 +37,14 @@ def get_model_name(model):
|
||||
|
||||
|
||||
class TreeManager(MP_NodeManager):
|
||||
# model.Manager get_or_create() is not compatible with MP_Tree
|
||||
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:
|
||||
try:
|
||||
return self.get(name__iexact=kwargs['name'], space=kwargs['space']), False
|
||||
except self.model.DoesNotExist:
|
||||
with scopes_disabled():
|
||||
node = self.model.add_root(**kwargs)
|
||||
return node, True
|
||||
return self.model.add_root(**kwargs), True
|
||||
|
||||
|
||||
class PermissionModelMixin:
|
||||
|
@ -63,7 +63,6 @@ class RecipeSchema(AutoSchema):
|
||||
|
||||
|
||||
class TreeSchema(AutoSchema):
|
||||
|
||||
def get_path_parameters(self, path, method):
|
||||
if not is_list_view(path, method, self.view):
|
||||
return super(TreeSchema, self).get_path_parameters(path, method)
|
||||
@ -85,5 +84,4 @@ class TreeSchema(AutoSchema):
|
||||
"description": 'Return all self and children of {} with ID [int].'.format(api_name),
|
||||
'schema': {'type': 'int', },
|
||||
})
|
||||
|
||||
return parameters
|
||||
|
@ -3,8 +3,7 @@ from datetime import timedelta
|
||||
from decimal import Decimal
|
||||
from gettext import gettext as _
|
||||
from django.contrib.auth.models import User
|
||||
from django.db.models import QuerySet, Sum, Avg
|
||||
from django.utils import timezone
|
||||
from django.db.models import Avg, QuerySet, Sum
|
||||
from drf_writable_nested import (UniqueFieldsMixin,
|
||||
WritableNestedModelSerializer)
|
||||
from rest_framework import serializers
|
||||
@ -213,9 +212,8 @@ class KeywordSerializer(UniqueFieldsMixin, serializers.ModelSerializer):
|
||||
|
||||
def get_image(self, obj):
|
||||
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=obj.get_tree(), space=obj.space).exclude(
|
||||
image__isnull=True).exclude(image__exact='') # if no recipes found - check whole tree
|
||||
if len(recipes) == 0 and obj.has_children():
|
||||
recipes = Recipe.objects.filter(keywords__in=obj.get_descendants(), 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:
|
||||
@ -233,7 +231,6 @@ class KeywordSerializer(UniqueFieldsMixin, serializers.ModelSerializer):
|
||||
return obj
|
||||
|
||||
class Meta:
|
||||
# list_serializer_class = SpaceFilterSerializer
|
||||
model = Keyword
|
||||
fields = (
|
||||
'id', 'name', 'icon', 'label', 'description', 'image', 'parent', 'numchild', 'numrecipe', 'created_at',
|
||||
|
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
@ -22,7 +22,6 @@ MOVE_URL = 'api:keyword-move'
|
||||
MERGE_URL = 'api:keyword-merge'
|
||||
|
||||
|
||||
# TODO are there better ways to manage these fixtures?
|
||||
@pytest.fixture()
|
||||
def obj_1(space_1):
|
||||
return Keyword.objects.get_or_create(name='test_1', space=space_1)[0]
|
||||
@ -352,7 +351,6 @@ def test_merge(
|
||||
|
||||
|
||||
def test_root_filter(obj_1, obj_1_1, obj_1_1_1, obj_2, obj_3, u1_s1):
|
||||
|
||||
# should return root objects in the space (obj_1, obj_2), ignoring query filters
|
||||
response = json.loads(u1_s1.get(f'{reverse(LIST_URL)}?root=0').content)
|
||||
assert len(response['results']) == 2
|
||||
@ -367,7 +365,6 @@ def test_root_filter(obj_1, obj_1_1, obj_1_1_1, obj_2, obj_3, u1_s1):
|
||||
|
||||
|
||||
def test_tree_filter(obj_1, obj_1_1, obj_1_1_1, obj_2, obj_3, u1_s1):
|
||||
|
||||
with scopes_disabled():
|
||||
obj_2.move(obj_1, 'sorted-child')
|
||||
# should return full tree starting at obj_1 (obj_1_1_1, obj_2), ignoring query filters
|
||||
|
@ -109,9 +109,9 @@ urlpatterns = [
|
||||
path('api/ingredient-from-string/', api.ingredient_from_string, name='api_ingredient_from_string'),
|
||||
path('api/share-link/<int:pk>', api.share_link, name='api_share_link'),
|
||||
|
||||
path('dal/keyword/', dal.KeywordAutocomplete.as_view(), name='dal_keyword'),
|
||||
path('dal/food/', dal.IngredientsAutocomplete.as_view(), name='dal_food'),
|
||||
path('dal/unit/', dal.UnitAutocomplete.as_view(), name='dal_unit'),
|
||||
path('dal/keyword/', dal.KeywordAutocomplete.as_view(), name='dal_keyword'), # TODO is this deprecated?
|
||||
path('dal/food/', dal.IngredientsAutocomplete.as_view(), name='dal_food'), # TODO is this deprecated?
|
||||
path('dal/unit/', dal.UnitAutocomplete.as_view(), name='dal_unit'), # TODO is this deprecated?
|
||||
|
||||
path('telegram/setup/<int:pk>', telegram.setup_bot, name='telegram_setup'),
|
||||
path('telegram/remove/<int:pk>', telegram.remove_bot, name='telegram_remove'),
|
||||
@ -137,7 +137,7 @@ urlpatterns = [
|
||||
|
||||
generic_models = (
|
||||
Recipe, RecipeImport, Storage, RecipeBook, MealPlan, SyncLog, Sync,
|
||||
Comment, RecipeBookEntry, Keyword, Food, ShoppingList, InviteLink
|
||||
Comment, RecipeBookEntry, Food, ShoppingList, InviteLink
|
||||
)
|
||||
|
||||
for m in generic_models:
|
||||
|
@ -113,11 +113,8 @@ class FuzzyFilterMixin(ViewSetMixin):
|
||||
)
|
||||
else:
|
||||
# TODO have this check unaccent search settings or other search preferences?
|
||||
self.queryset = (
|
||||
self.queryset
|
||||
.annotate(exact=Case(When(name__iexact=query, then=(Value(100))), default=Value(0))) # put exact matches at the top of the result set
|
||||
.filter(name__icontains=query).order_by('-exact')
|
||||
)
|
||||
# TODO for some querysets exact matches are sorted beyond pagesize, need to find better solution
|
||||
self.queryset = self.queryset.filter(name__istartswith=query) | self.queryset.filter(name__icontains=query)
|
||||
|
||||
updated_at = self.request.query_params.get('updated_at', None)
|
||||
if updated_at is not None:
|
||||
@ -152,14 +149,14 @@ class TreeMixin(FuzzyFilterMixin):
|
||||
except self.model.DoesNotExist:
|
||||
self.queryset = self.model.objects.none()
|
||||
if root == 0:
|
||||
self.queryset = self.model.get_root_nodes() | self.model.objects.filter(depth=0)
|
||||
self.queryset = self.model.get_root_nodes()
|
||||
else:
|
||||
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()
|
||||
except Keyword.DoesNotExist:
|
||||
except self.model.DoesNotExist:
|
||||
self.queryset = self.model.objects.none()
|
||||
else:
|
||||
return super().get_queryset()
|
||||
@ -470,7 +467,7 @@ class RecipePagination(PageNumberPagination):
|
||||
max_page_size = 100
|
||||
|
||||
def paginate_queryset(self, queryset, request, view=None):
|
||||
self.facets = get_facet(queryset, request)
|
||||
self.facets = get_facet(queryset, request.query_params, request.space)
|
||||
return super().paginate_queryset(queryset, request, view)
|
||||
|
||||
def get_paginated_response(self, data):
|
||||
|
@ -148,7 +148,6 @@ def import_url(request):
|
||||
|
||||
recipe.steps.add(step)
|
||||
|
||||
all_keywords = Keyword.get_tree()
|
||||
for kw in data['keywords']:
|
||||
k, created = Keyword.objects.get_or_create(name=kw['text'], space=request.space)
|
||||
recipe.keywords.add(k)
|
||||
|
@ -56,9 +56,7 @@ def index(request):
|
||||
return HttpResponseRedirect(reverse('view_search'))
|
||||
|
||||
|
||||
# faceting
|
||||
# unaccent / likely will perform full table scan
|
||||
# create tests
|
||||
# TODO need to deprecate
|
||||
def search(request):
|
||||
if has_group_permission(request.user, ('guest',)):
|
||||
if request.user.userpreference.search_style == UserPreference.NEW:
|
||||
|
@ -8,7 +8,6 @@
|
||||
<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 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 -->
|
||||
<div class="row flex-shrink-0">
|
||||
<div class="col col-md-12">
|
||||
|
@ -27,7 +27,7 @@
|
||||
<script>
|
||||
|
||||
export default {
|
||||
name: 'KeywordContextMenu',
|
||||
name: 'GenericContextMenu',
|
||||
props: {
|
||||
show_edit: {type: Boolean, default: true},
|
||||
show_delete: {type: Boolean, default: true},
|
||||
|
@ -36,10 +36,7 @@ export default {
|
||||
search_function: String,
|
||||
label: String,
|
||||
parent_variable: {type: String, default: undefined},
|
||||
limit: {
|
||||
type: Number,
|
||||
default: 10,
|
||||
},
|
||||
limit: {type: Number, default: 10,},
|
||||
sticky_options: {type:Array, default(){return []}},
|
||||
initial_selection: {type:Array, default(){return []}},
|
||||
multiple: {type: Boolean, default: true},
|
||||
|
@ -80,7 +80,7 @@ module.exports = {
|
||||
},
|
||||
},
|
||||
},
|
||||
// TODO make this conditional on .env DEBUG = TRUE
|
||||
// TODO make this conditional on .env DEBUG = FALSE
|
||||
config.optimization.minimize(true)
|
||||
);
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user