Squashed commit of the following:

commit 75e46b8023
Author: smilerz <smilerz@gmail.com>
Date:   Wed Aug 25 21:27:31 2021 -0500

    fix #883

commit 2a1e1953ee
Author: smilerz <smilerz@gmail.com>
Date:   Wed Aug 25 21:08:35 2021 -0500

    consolidated recently viewed into FTS queryset

commit aa2d0eafb1
Author: smilerz <smilerz@gmail.com>
Date:   Wed Aug 25 16:43:52 2021 -0500

    fixed cookie reactivity

commit b74fdb3825
Author: smilerz <smilerz@gmail.com>
Date:   Wed Aug 25 16:37:43 2021 -0500

    exact match appears first on keyword/food/unit searches

commit b00c189fd6
Author: smilerz <smilerz@gmail.com>
Date:   Wed Aug 25 16:36:14 2021 -0500

    add pagination reset on search query change
This commit is contained in:
smilerz 2021-08-25 22:19:42 -05:00
parent 07a67684ab
commit ba07e98cae
6 changed files with 40 additions and 52 deletions

View File

@ -36,21 +36,22 @@ def search_recipes(request, queryset, params):
if search_last_viewed > 0:
last_viewed_recipes = ViewLog.objects.filter(
created_by=request.user, space=request.space,
created_at__gte=datetime.now() - timedelta(days=14)
created_at__gte=datetime.now() - timedelta(days=14) # TODO make recent days a setting
).order_by('-pk').values_list('recipe__pk', flat=True)
last_viewed_recipes = list(dict.fromkeys(last_viewed_recipes))[:search_last_viewed] # removes duplicates from list prior to slicing
return queryset.annotate(last_view=Max('viewlog__pk')).annotate(new=Case(When(pk__in=last_viewed_recipes, then=('last_view')), default=Value(0))).filter(new__gt=0).order_by('-new')
# return queryset.annotate(last_view=Max('viewlog__pk')).annotate(new=Case(When(pk__in=last_viewed_recipes, then=('last_view')), default=Value(0))).filter(new__gt=0).order_by('-new')
# queryset that only annotates most recent view (higher pk = lastest view)
# TODO queryset.annotate(last_view=Max('viewlog__pk')).annotate(new=Case(When(pk__in=last_viewed_recipes, then=Value(100)), default=Value(0))).order_by('-new')
queryset = queryset.annotate(last_view=Max('viewlog__pk')).annotate(recent=Case(When(pk__in=last_viewed_recipes, then=('last_view')), default=Value(0)))
orderby += ['-recent']
orderby = []
# TODO create setting for default ordering - most cooked, rating,
# TODO create setting for default ordering - most cooked, rating,
# TODO create options for live sorting
# TODO make days of new recipe a setting
if search_new == 'true':
queryset = (
queryset.annotate(new_recipe=Case(
When(created_at__gte=(datetime.now() - timedelta(days=81)), then=('pk')), default=Value(0),))
When(created_at__gte=(datetime.now() - timedelta(days=7)), then=('pk')), default=Value(0),))
)
orderby += ['-new_recipe']
@ -173,31 +174,31 @@ def search_recipes(request, queryset, params):
return queryset
def get_facet(qs, params, space):
def get_facet(qs, request):
# NOTE facet counts for tree models include self AND descendants
facets = {}
ratings = params.getlist('ratings', [])
keyword_list = params.getlist('keywords', [])
food_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)
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)
# 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=space).annotate(recipe_count=Count('recipe'))
keywords = Keyword.objects.filter(space=request.space).annotate(recipe_count=Count('recipe'))
else:
keywords = Keyword.objects.filter(recipe__in=qs, space=space).annotate(recipe_count=Count('recipe'))
keywords = Keyword.objects.filter(recipe__in=qs, space=request.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)
# if using an OR search, will annotate all keywords, otherwise, just those that appear in results
if search_foods_or:
foods = Food.objects.filter(space=space).annotate(recipe_count=Count('ingredient'))
foods = Food.objects.filter(space=request.space).annotate(recipe_count=Count('ingredient'))
else:
foods = Food.objects.filter(ingredient__step__recipe__in=list(qs.values_list('id', flat=True)), space=space).annotate(recipe_count=Count('ingredient'))
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)
# TODO add rating facet

View File

@ -749,8 +749,7 @@ class CookLog(ExportModelOperationsMixin('cook_log'), models.Model, PermissionMo
return self.recipe.name
class Meta():
# TODO add created_by
indexes = (Index(fields=['id', 'recipe', '-created_at', 'rating']),)
indexes = (Index(fields=['id', 'recipe', '-created_at', 'rating', 'created_by']),)
class ViewLog(ExportModelOperationsMixin('view_log'), models.Model, PermissionModelMixin):
@ -765,8 +764,7 @@ class ViewLog(ExportModelOperationsMixin('view_log'), models.Model, PermissionMo
return self.recipe.name
class Meta():
# TODO add created_by
indexes = (Index(fields=['recipe', '-created_at']),)
indexes = (Index(fields=['recipe', '-created_at', 'created_by']),)
class ImportLog(models.Model, PermissionModelMixin):

View File

@ -5,6 +5,7 @@ from gettext import gettext as _
from django.contrib.auth.models import User
from django.db.models import Avg, QuerySet, Sum
from django.urls import reverse
from django.utils import timezone
from drf_writable_nested import (UniqueFieldsMixin,
WritableNestedModelSerializer)
from rest_framework import serializers
@ -439,21 +440,19 @@ class RecipeBaseSerializer(WritableNestedModelSerializer):
pass
return None
def get_recipe_last_viewed(self, obj):
try:
last = obj.viewlog_set.filter(created_by=self.context['request'].user).last()
if last:
return last.created_at
except TypeError:
pass
return None
# TODO make days of new recipe a setting
def is_recipe_new(self, obj):
if obj.created_at > (timezone.now() - timedelta(days=7)):
return True
else:
return False
class RecipeOverviewSerializer(RecipeBaseSerializer):
keywords = KeywordLabelSerializer(many=True)
rating = serializers.SerializerMethodField('get_recipe_rating')
last_cooked = serializers.SerializerMethodField('get_recipe_last_cooked')
last_viewed = serializers.SerializerMethodField('get_recipe_last_viewed')
new = serializers.SerializerMethodField('is_recipe_new')
def create(self, validated_data):
pass
@ -466,7 +465,7 @@ class RecipeOverviewSerializer(RecipeBaseSerializer):
fields = (
'id', 'name', 'description', 'image', 'keywords', 'working_time',
'waiting_time', 'created_by', 'created_at', 'updated_at',
'internal', 'servings', 'servings_text', 'rating', 'last_cooked', 'last_viewed',
'internal', 'servings', 'servings_text', 'rating', 'last_cooked', 'new'
)
read_only_fields = ['image', 'created_by', 'created_at']

File diff suppressed because one or more lines are too long

View File

@ -12,7 +12,7 @@ from django.contrib.auth.models import User
from django.contrib.postgres.search import TrigramSimilarity
from django.core.exceptions import FieldError, ValidationError
from django.core.files import File
from django.db.models import Q, Case, When, Value
from django.db.models import Case, Q, Value, When
from django.db.models.fields.related import ForeignObjectRel
from django.http import FileResponse, HttpResponse, JsonResponse
from django_scopes import scopes_disabled
@ -483,7 +483,7 @@ class RecipePagination(PageNumberPagination):
max_page_size = 100
def paginate_queryset(self, queryset, request, view=None):
self.facets = get_facet(queryset, request.query_params, request.space)
self.facets = get_facet(queryset, request)
return super().paginate_queryset(queryset, request, view)
def get_paginated_response(self, data):

View File

@ -226,11 +226,14 @@
<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>
</template>
<recipe-card v-for="r in recipes" v-bind:key="r.id" :recipe="r"
<recipe-card v-for="r in recipes" v-bind:key="`rv_${r.id}`" :recipe="r"
:footer_text="isRecentOrNew(r)[0]"
:footer_icon="isRecentOrNew(r)[1]">
</recipe-card>
</recipe-card>
</template>
<!-- <recipe-card v-for="r in recipes" v-bind:key="r.id" :recipe="r"></recipe-card> -->
</div>
</div>
</div>
@ -320,20 +323,7 @@ export default {
mounted() {
this.$nextTick(function () {
if (this.$cookies.isKey(SETTINGS_COOKIE_NAME)) {
let cookie_val = this.$cookies.get(SETTINGS_COOKIE_NAME)
for (let i of Object.keys(cookie_val)) {
this.$set(this.settings, i, cookie_val[i])
}
// @vabene - I think you need Vue.set but am not sure what you were observing to validate
//TODO i have no idea why the above code does not suffice to update the
//TODO pagination UI element as $set should update all values reactively but it does not
setTimeout(function () {
this.$set(this.settings, 'pagination_page', 0)
}.bind(this), 50)
setTimeout(function () {
this.$set(this.settings, 'pagination_page', cookie_val['pagination_page'])
}.bind(this), 51)
this.settings = Object.assign({}, this.settings, this.$cookies.get(SETTINGS_COOKIE_NAME))
}
let urlParams = new URLSearchParams(window.location.search);