From 4714162c0b88409acdb046200636a6c213f8d120 Mon Sep 17 00:00:00 2001 From: smilerz Date: Wed, 25 Aug 2021 15:22:47 -0500 Subject: [PATCH] WIP --- cookbook/helper/recipe_search.py | 20 ++++----- cookbook/models.py | 6 ++- cookbook/serializer.py | 41 +++++-------------- cookbook/views/api.py | 9 ++-- .../RecipeSearchView/RecipeSearchView.vue | 19 +++++++-- 5 files changed, 46 insertions(+), 49 deletions(-) diff --git a/cookbook/helper/recipe_search.py b/cookbook/helper/recipe_search.py index 80ad18d6..8bd3c041 100644 --- a/cookbook/helper/recipe_search.py +++ b/cookbook/helper/recipe_search.py @@ -36,22 +36,21 @@ 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) # TODO make recent days a setting + created_at__gte=datetime.now() - timedelta(days=14) ).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) - 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'] + # 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') - # TODO create setting for default ordering - most cooked, rating, + orderby = [] + # 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=7)), then=('pk')), default=Value(0),)) + When(created_at__gte=(datetime.now() - timedelta(days=81)), then=('pk')), default=Value(0),)) ) orderby += ['-new_recipe'] @@ -195,13 +194,12 @@ def get_facet(qs, params, space): 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_keywords_or: - foods = Food.objects.filter(ingredient__step__recipe__in=list(qs.values_list('id', flat=True),space=space)).annotate(recipe_count=Count('ingredient')) + if search_foods_or: + foods = Food.objects.filter(space=space).annotate(recipe_count=Count('ingredient')) else: - foods = Food.objects.filter(ingredient__step__recipe__in=list(qs.values_list('id', flat=True))).annotate(recipe_count=Count('ingredient')) + foods = Food.objects.filter(ingredient__step__recipe__in=list(qs.values_list('id', flat=True)), space=space).annotate(recipe_count=Count('ingredient')) food_a = annotated_qs(foods, root=True, fill=True) - # TODO add rating facet facets['Ratings'] = [] facets['Keywords'] = fill_annotated_parents(kw_a, keyword_list) diff --git a/cookbook/models.py b/cookbook/models.py index 1c237302..6f7ea147 100644 --- a/cookbook/models.py +++ b/cookbook/models.py @@ -749,7 +749,8 @@ class CookLog(ExportModelOperationsMixin('cook_log'), models.Model, PermissionMo return self.recipe.name class Meta(): - indexes = (Index(fields=['id', 'recipe', '-created_at', 'rating', 'created_by']),) + # TODO add created_by + indexes = (Index(fields=['id', 'recipe', '-created_at', 'rating']),) class ViewLog(ExportModelOperationsMixin('view_log'), models.Model, PermissionModelMixin): @@ -764,7 +765,8 @@ class ViewLog(ExportModelOperationsMixin('view_log'), models.Model, PermissionMo return self.recipe.name class Meta(): - indexes = (Index(fields=['recipe', '-created_at', 'created_by']),) + # TODO add created_by + indexes = (Index(fields=['recipe', '-created_at']),) class ImportLog(models.Model, PermissionModelMixin): diff --git a/cookbook/serializer.py b/cookbook/serializer.py index b5ef844d..6bc9ce94 100644 --- a/cookbook/serializer.py +++ b/cookbook/serializer.py @@ -1,10 +1,9 @@ -import json import random 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 Avg, Manager, QuerySet, Sum +from django.db.models import Avg, QuerySet, Sum from django.urls import reverse from drf_writable_nested import (UniqueFieldsMixin, WritableNestedModelSerializer) @@ -58,24 +57,6 @@ class SpaceFilterSerializer(serializers.ListSerializer): return super().to_representation(data) -# custom related field, sends details on read, accepts primary key on write -# class RelatedFieldAlternative(serializers.PrimaryKeyRelatedField): -# def __init__(self, **kwargs): -# self.serializer = kwargs.pop('serializer', None) -# if self.serializer is not None and not issubclass(self.serializer, serializers.Serializer): -# raise TypeError('"serializer" is not a valid serializer class') - -# super().__init__(**kwargs) - -# def use_pk_only_optimization(self): -# return False if self.serializer else True - -# def to_representation(self, instance): -# if self.serializer: -# return self.serializer(instance, context=self.context).data -# return super().to_representation(instance) - - class SpacedModelSerializer(serializers.ModelSerializer): def create(self, validated_data): validated_data['space'] = self.context['request'].space @@ -319,8 +300,6 @@ class RecipeSimpleSerializer(serializers.ModelSerializer): class FoodSerializer(UniqueFieldsMixin, WritableNestedModelSerializer): - # RelatedFieldAlternative adds details of related object on read, accepts PK on write - # this approach prevents adding *new* objects when updating Food, SupermarketCategory must be created elsewhere image = serializers.SerializerMethodField('get_image') numrecipe = serializers.SerializerMethodField('count_recipes') @@ -460,19 +439,21 @@ class RecipeBaseSerializer(WritableNestedModelSerializer): 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 + 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 class RecipeOverviewSerializer(RecipeBaseSerializer): keywords = KeywordLabelSerializer(many=True) rating = serializers.SerializerMethodField('get_recipe_rating') last_cooked = serializers.SerializerMethodField('get_recipe_last_cooked') - new = serializers.SerializerMethodField('is_recipe_new') + last_viewed = serializers.SerializerMethodField('get_recipe_last_viewed') def create(self, validated_data): pass @@ -485,7 +466,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', 'new' + 'internal', 'servings', 'servings_text', 'rating', 'last_cooked', 'last_viewed', ) read_only_fields = ['image', 'created_by', 'created_at'] diff --git a/cookbook/views/api.py b/cookbook/views/api.py index b68b17de..bacd9952 100644 --- a/cookbook/views/api.py +++ b/cookbook/views/api.py @@ -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 +from django.db.models import Q, Case, When, Value from django.db.models.fields.related import ForeignObjectRel from django.http import FileResponse, HttpResponse, JsonResponse from django_scopes import scopes_disabled @@ -114,8 +114,11 @@ class FuzzyFilterMixin(ViewSetMixin): ) else: # TODO have this check unaccent search settings or other search preferences? - # 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) + 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') + ) updated_at = self.request.query_params.get('updated_at', None) if updated_at is not None: diff --git a/vue/src/apps/RecipeSearchView/RecipeSearchView.vue b/vue/src/apps/RecipeSearchView/RecipeSearchView.vue index 327d1403..d7220aaf 100644 --- a/vue/src/apps/RecipeSearchView/RecipeSearchView.vue +++ b/vue/src/apps/RecipeSearchView/RecipeSearchView.vue @@ -19,7 +19,7 @@ v-b-tooltip.hover :title="$t('Advanced Settings')" v-bind:variant="!isAdvancedSettingsSet() ? 'primary' : 'danger'" > - + @@ -270,7 +270,7 @@ Vue.use(VueCookies) import {ResolveUrlMixin} from "@/utils/utils"; -import LoadingSpinner from "@/components/LoadingSpinner"; +import LoadingSpinner from "@/components/LoadingSpinner"; // is this deprecated? import {ApiApiFactory} from "@/utils/openapi/api.ts"; import RecipeCard from "@/components/RecipeCard"; @@ -320,7 +320,20 @@ export default { mounted() { this.$nextTick(function () { if (this.$cookies.isKey(SETTINGS_COOKIE_NAME)) { - this.settings = Object.assign({}, this.settings, this.$cookies.get(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) + } let urlParams = new URLSearchParams(window.location.search);