This commit is contained in:
smilerz 2021-08-25 15:22:47 -05:00
parent d7d552c5c5
commit 4714162c0b
5 changed files with 46 additions and 49 deletions

View File

@ -36,22 +36,21 @@ def search_recipes(request, queryset, params):
if search_last_viewed > 0: if search_last_viewed > 0:
last_viewed_recipes = ViewLog.objects.filter( last_viewed_recipes = ViewLog.objects.filter(
created_by=request.user, space=request.space, 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) ).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 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 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))) # 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')
orderby += ['-recent']
# 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 create options for live sorting
# TODO make days of new recipe a setting
if search_new == 'true': if search_new == 'true':
queryset = ( queryset = (
queryset.annotate(new_recipe=Case( 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'] orderby += ['-new_recipe']
@ -195,13 +194,12 @@ def get_facet(qs, params, space):
kw_a = annotated_qs(keywords, root=True, fill=True) 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 using an OR search, will annotate all keywords, otherwise, just those that appear in results
if search_keywords_or: if search_foods_or:
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(space=space).annotate(recipe_count=Count('ingredient'))
else: 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) food_a = annotated_qs(foods, root=True, fill=True)
# TODO add rating facet # TODO add rating facet
facets['Ratings'] = [] facets['Ratings'] = []
facets['Keywords'] = fill_annotated_parents(kw_a, keyword_list) facets['Keywords'] = fill_annotated_parents(kw_a, keyword_list)

View File

@ -749,7 +749,8 @@ class CookLog(ExportModelOperationsMixin('cook_log'), models.Model, PermissionMo
return self.recipe.name return self.recipe.name
class Meta(): 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): class ViewLog(ExportModelOperationsMixin('view_log'), models.Model, PermissionModelMixin):
@ -764,7 +765,8 @@ class ViewLog(ExportModelOperationsMixin('view_log'), models.Model, PermissionMo
return self.recipe.name return self.recipe.name
class Meta(): 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): class ImportLog(models.Model, PermissionModelMixin):

View File

@ -1,10 +1,9 @@
import json
import random import random
from datetime import timedelta from datetime import timedelta
from decimal import Decimal from decimal import Decimal
from gettext import gettext as _ from gettext import gettext as _
from django.contrib.auth.models import User 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 django.urls import reverse
from drf_writable_nested import (UniqueFieldsMixin, from drf_writable_nested import (UniqueFieldsMixin,
WritableNestedModelSerializer) WritableNestedModelSerializer)
@ -58,24 +57,6 @@ class SpaceFilterSerializer(serializers.ListSerializer):
return super().to_representation(data) 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): class SpacedModelSerializer(serializers.ModelSerializer):
def create(self, validated_data): def create(self, validated_data):
validated_data['space'] = self.context['request'].space validated_data['space'] = self.context['request'].space
@ -319,8 +300,6 @@ class RecipeSimpleSerializer(serializers.ModelSerializer):
class FoodSerializer(UniqueFieldsMixin, WritableNestedModelSerializer): 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') image = serializers.SerializerMethodField('get_image')
numrecipe = serializers.SerializerMethodField('count_recipes') numrecipe = serializers.SerializerMethodField('count_recipes')
@ -460,19 +439,21 @@ class RecipeBaseSerializer(WritableNestedModelSerializer):
pass pass
return None return None
# TODO make days of new recipe a setting def get_recipe_last_viewed(self, obj):
def is_recipe_new(self, obj): try:
if obj.created_at > (timezone.now() - timedelta(days=7)): last = obj.viewlog_set.filter(created_by=self.context['request'].user).last()
return True if last:
else: return last.created_at
return False except TypeError:
pass
return None
class RecipeOverviewSerializer(RecipeBaseSerializer): class RecipeOverviewSerializer(RecipeBaseSerializer):
keywords = KeywordLabelSerializer(many=True) keywords = KeywordLabelSerializer(many=True)
rating = serializers.SerializerMethodField('get_recipe_rating') rating = serializers.SerializerMethodField('get_recipe_rating')
last_cooked = serializers.SerializerMethodField('get_recipe_last_cooked') 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): def create(self, validated_data):
pass pass
@ -485,7 +466,7 @@ class RecipeOverviewSerializer(RecipeBaseSerializer):
fields = ( fields = (
'id', 'name', 'description', 'image', 'keywords', 'working_time', 'id', 'name', 'description', 'image', 'keywords', 'working_time',
'waiting_time', 'created_by', 'created_at', 'updated_at', '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'] read_only_fields = ['image', 'created_by', 'created_at']

View File

@ -12,7 +12,7 @@ from django.contrib.auth.models import User
from django.contrib.postgres.search import TrigramSimilarity from django.contrib.postgres.search import TrigramSimilarity
from django.core.exceptions import FieldError, ValidationError from django.core.exceptions import FieldError, ValidationError
from django.core.files import File 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.db.models.fields.related import ForeignObjectRel
from django.http import FileResponse, HttpResponse, JsonResponse from django.http import FileResponse, HttpResponse, JsonResponse
from django_scopes import scopes_disabled from django_scopes import scopes_disabled
@ -114,8 +114,11 @@ class FuzzyFilterMixin(ViewSetMixin):
) )
else: else:
# TODO have this check unaccent search settings or other search preferences? # 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 = self.queryset.filter(name__istartswith=query) | self.queryset.filter(name__icontains=query) 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) updated_at = self.request.query_params.get('updated_at', None)
if updated_at is not None: if updated_at is not None:

View File

@ -19,7 +19,7 @@
v-b-tooltip.hover :title="$t('Advanced Settings')" v-b-tooltip.hover :title="$t('Advanced Settings')"
v-bind:variant="!isAdvancedSettingsSet() ? 'primary' : 'danger'" v-bind:variant="!isAdvancedSettingsSet() ? 'primary' : 'danger'"
> >
<!-- consider changing this icon to a filter --> <!-- TODO consider changing this icon to a filter -->
<i class="fas fa-caret-down" v-if="!settings.advanced_search_visible"></i> <i class="fas fa-caret-down" v-if="!settings.advanced_search_visible"></i>
<i class="fas fa-caret-up" v-if="settings.advanced_search_visible"></i> <i class="fas fa-caret-up" v-if="settings.advanced_search_visible"></i>
</b-button> </b-button>
@ -270,7 +270,7 @@ Vue.use(VueCookies)
import {ResolveUrlMixin} from "@/utils/utils"; 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 {ApiApiFactory} from "@/utils/openapi/api.ts";
import RecipeCard from "@/components/RecipeCard"; import RecipeCard from "@/components/RecipeCard";
@ -320,7 +320,20 @@ export default {
mounted() { mounted() {
this.$nextTick(function () { this.$nextTick(function () {
if (this.$cookies.isKey(SETTINGS_COOKIE_NAME)) { 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); let urlParams = new URLSearchParams(window.location.search);