filter by rating

This commit is contained in:
smilerz
2021-09-09 15:23:46 -05:00
parent 14974c55cf
commit 249557f8ef
13 changed files with 389 additions and 88 deletions

View File

@@ -1,14 +1,20 @@
from collections import Counter
from datetime import datetime, timedelta
from recipes import settings
from django.contrib.postgres.search import (
SearchQuery, SearchRank, TrigramSimilarity
)
from django.db.models import Count, Max, Q, Subquery, Case, When, Value
from django.db.models import Avg, Case, Count, Func, Max, Q, Subquery, Value, When
from django.utils import translation
from cookbook.managers import DICTIONARY
from cookbook.models import Food, Keyword, Recipe, ViewLog
from cookbook.models import Food, Keyword, ViewLog
class Round(Func):
function = 'ROUND'
template='%(function)s(%(expressions)s, 0)'
# TODO create extensive tests to make sure ORs ANDs and various filters, sorting, etc work as expected
@@ -16,12 +22,21 @@ from cookbook.models import Food, Keyword, Recipe, ViewLog
def search_recipes(request, queryset, params):
search_prefs = request.user.searchpreference
search_string = params.get('query', '')
search_ratings = params.getlist('ratings', [])
search_rating = int(params.get('rating', 0))
search_keywords = params.getlist('keywords', [])
search_foods = params.getlist('foods', [])
search_books = params.getlist('books', [])
search_units = params.get('units', None)
filtered_search = (
len(search_string) > 0
or search_rating != 0
or len(search_keywords) > 0
or len(search_foods) > 0
or len(search_books) > 0
or search_units is not None
)
# TODO I think default behavior should be 'AND' which is how most sites operate with facet/filters based on results
search_keywords_or = params.get('keywords_or', True)
search_foods_or = params.get('foods_or', True)
@@ -33,8 +48,8 @@ def search_recipes(request, queryset, params):
search_last_viewed = int(params.get('last_viewed', 0))
orderby = []
# TODO update this to concat with full search queryset qs1 | qs2
if search_last_viewed > 0:
# only sort by recent not otherwise filtering/sorting
if search_last_viewed > 0 and not filtered_search:
last_viewed_recipes = ViewLog.objects.filter(
created_by=request.user, space=request.space,
created_at__gte=datetime.now() - timedelta(days=14) # TODO make recent days a setting
@@ -54,6 +69,8 @@ def search_recipes(request, queryset, params):
queryset.annotate(new_recipe=Case(
When(created_at__gte=(datetime.now() - timedelta(days=7)), then=('pk')), default=Value(0), ))
)
# only sort by new recipes if not otherwise filtering/sorting
if not filtered_search:
orderby += ['-new_recipe']
search_type = search_prefs.search or 'plain'
@@ -135,8 +152,6 @@ def search_recipes(request, queryset, params):
if len(search_keywords) > 0:
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
# TODO creating setting to include descendants of keywords a setting
# for kw in Keyword.objects.filter(pk__in=search_keywords):
# search_keywords += list(kw.get_descendants().values_list('pk', flat=True))
@@ -163,7 +178,14 @@ def search_recipes(request, queryset, params):
else:
for k in search_books:
queryset = queryset.filter(recipebookentry__book__id=k)
if search_rating:
queryset = queryset.annotate(rating=Round(Avg(Case(When(cooklog__created_by=request.user, then='cooklog__rating'), default=Value(0)))))
if search_rating == -1:
queryset = queryset.filter(rating=0)
else:
queryset = queryset.filter(rating__gte=search_rating)
# probably only useful in Unit list view, so keeping it simple
if search_units:
queryset = queryset.filter(steps__ingredients__unit__id=search_units)
@@ -185,7 +207,6 @@ def search_recipes(request, queryset, params):
def get_facet(qs, request):
# 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', [])
@@ -209,10 +230,11 @@ def get_facet(qs, request):
foods = Food.objects.filter(ingredient__step__recipe__in=list(qs.values_list('id', flat=True)), space=request.space).annotate(recipe_count=Count('ingredient'))
food_a = annotated_qs(foods, root=True, fill=True)
rating_qs = qs.annotate(rating=Round(Avg(Case(When(cooklog__created_by=request.user, then='cooklog__rating'), default=Value(0)))))
# TODO add rating facet
facets['Ratings'] = []
facets['Ratings'] = dict(Counter(r.rating for r in rating_qs))
facets['Keywords'] = fill_annotated_parents(kw_a, keyword_list)
# TODO add food facet
facets['Foods'] = fill_annotated_parents(food_a, food_list)
# TODO add book facet
facets['Books'] = []
@@ -266,13 +288,11 @@ def fill_annotated_parents(annotation, filters):
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.
"""