diff --git a/cookbook/helper/recipe_search.py b/cookbook/helper/recipe_search.py index 0704b206..df1426a4 100644 --- a/cookbook/helper/recipe_search.py +++ b/cookbook/helper/recipe_search.py @@ -1,3 +1,4 @@ +import json from collections import Counter from datetime import timedelta @@ -12,7 +13,7 @@ from cookbook.filters import RecipeFilter from cookbook.helper.HelperFunctions import Round, str2bool from cookbook.helper.permission_helper import has_group_permission from cookbook.managers import DICTIONARY -from cookbook.models import CookLog, Food, Keyword, Recipe, SearchPreference, ViewLog +from cookbook.models import CookLog, CustomFilter, Food, Keyword, Recipe, SearchPreference, ViewLog from recipes import settings @@ -24,7 +25,14 @@ class RecipeSearch(): def __init__(self, request, **params): self._request = request self._queryset = None - self._params = {**params} + if filter := params.get('filter', None): + try: + self._params = {**json.loads(CustomFilter.objects.get(id=filter).search)} + except CustomFilter.DoesNotExist: + self._params = {**(params or {})} + else: + self._params = {**(params or {})} + self._query = self._params.get('query', {}) or {} if self._request.user.is_authenticated: self._search_prefs = request.user.searchpreference else: @@ -53,12 +61,11 @@ class RecipeSearch(): self._units = self._params.get('units', None) # TODO add created by # TODO image exists - self._sort_order = self._params.get('sort_order', None) - self._books_or = str2bool(self._params.get('books_or', True)) + self._sort_order = self._params.get('sort_order', None) or self._query.get('sort_order', 0) self._internal = str2bool(self._params.get('internal', False)) self._random = str2bool(self._params.get('random', False)) self._new = str2bool(self._params.get('new', False)) - self._last_viewed = int(self._params.get('last_viewed', 0)) + self._last_viewed = int(self._params.get('last_viewed', 0) or self._query.get('last_viewed', 0)) self._timescooked = self._params.get('timescooked', None) self._lastcooked = self._params.get('lastcooked', None) self._makenow = self._params.get('makenow', None) diff --git a/cookbook/migrations/0168_auto_20220121_1427.py b/cookbook/migrations/0168_auto_20220121_1427.py index 733ef8b4..67b13e32 100644 --- a/cookbook/migrations/0168_auto_20220121_1427.py +++ b/cookbook/migrations/0168_auto_20220121_1427.py @@ -33,4 +33,9 @@ class Migration(migrations.Migration): model_name='customfilter', constraint=models.UniqueConstraint(fields=('space', 'name'), name='cf_unique_name_per_space'), ), + migrations.AddField( + model_name='recipebook', + name='filter', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='cookbook.customfilter'), + ), ] diff --git a/cookbook/models.py b/cookbook/models.py index db6df086..8b913c90 100644 --- a/cookbook/models.py +++ b/cookbook/models.py @@ -725,6 +725,7 @@ class RecipeBook(ExportModelOperationsMixin('book'), models.Model, PermissionMod icon = models.CharField(max_length=16, blank=True, null=True) shared = models.ManyToManyField(User, blank=True, related_name='shared_with') created_by = models.ForeignKey(User, on_delete=models.CASCADE) + filter = models.ForeignKey('cookbook.CustomFilter', null=True, blank=True, on_delete=models.SET_NULL) space = models.ForeignKey(Space, on_delete=models.CASCADE) objects = ScopedManager(space='space') diff --git a/cookbook/serializer.py b/cookbook/serializer.py index f4fe8261..a269b372 100644 --- a/cookbook/serializer.py +++ b/cookbook/serializer.py @@ -604,8 +604,22 @@ class CommentSerializer(serializers.ModelSerializer): fields = '__all__' +class CustomFilterSerializer(SpacedModelSerializer, WritableNestedModelSerializer): + shared = UserNameSerializer(many=True, required=False) + + def create(self, validated_data): + validated_data['created_by'] = self.context['request'].user + return super().create(validated_data) + + class Meta: + model = CustomFilter + fields = ('id', 'name', 'search', 'shared', 'created_by') + read_only_fields = ('created_by',) + + class RecipeBookSerializer(SpacedModelSerializer, WritableNestedModelSerializer): shared = UserNameSerializer(many=True) + filter = CustomFilterSerializer(required=False) def create(self, validated_data): validated_data['created_by'] = self.context['request'].user @@ -613,8 +627,8 @@ class RecipeBookSerializer(SpacedModelSerializer, WritableNestedModelSerializer) class Meta: model = RecipeBook - fields = ('id', 'name', 'description', 'icon', 'shared', 'created_by') - read_only_fields = ('created_by',) + fields = ('id', 'name', 'description', 'icon', 'shared', 'created_by', 'filter') + read_only_fields = ('created_by', ) class RecipeBookEntrySerializer(serializers.ModelSerializer): @@ -976,16 +990,3 @@ class FoodShoppingUpdateSerializer(serializers.ModelSerializer): class Meta: model = Recipe fields = ['id', 'amount', 'unit', 'delete', ] - - -class CustomFilterSerializer(SpacedModelSerializer, WritableNestedModelSerializer): - shared = UserNameSerializer(many=True, required=False) - - def create(self, validated_data): - validated_data['created_by'] = self.context['request'].user - return super().create(validated_data) - - class Meta: - model = CustomFilter - fields = ('id', 'name', 'search', 'shared', 'created_by') - read_only_fields = ('created_by',) diff --git a/cookbook/views/api.py b/cookbook/views/api.py index 832293e9..5dd5bfed 100644 --- a/cookbook/views/api.py +++ b/cookbook/views/api.py @@ -526,7 +526,6 @@ class RecipeBookEntryViewSet(viewsets.ModelViewSet, viewsets.GenericViewSet): book_id = self.request.query_params.get('book', None) if book_id is not None: queryset = queryset.filter(book__pk=book_id) - return queryset @@ -669,8 +668,10 @@ class RecipeViewSet(viewsets.ModelViewSet): if not (share and self.detail): self.queryset = self.queryset.filter(space=self.request.space) - # self.queryset = search_recipes(self.request, self.queryset, self.request.GET) - params = {x: self.request.GET.get(x) if len({**self.request.GET}[x]) == 1 else self.request.GET.getlist(x) for x in list(self.request.GET)} + if filter := (self.request.GET.get('query', {}) or {}).get('filter', None): + params = {'filter': filter} + else: + params = {x: self.request.GET.get(x) if len({**self.request.GET}[x]) == 1 else self.request.GET.getlist(x) for x in list(self.request.GET)} search = RecipeSearch(self.request, **params) self.queryset = search.get_queryset(self.queryset).prefetch_related('cooklog_set') return self.queryset diff --git a/vue/src/apps/CookbookView/CookbookView.vue b/vue/src/apps/CookbookView/CookbookView.vue index 6363c52f..bcdb7004 100644 --- a/vue/src/apps/CookbookView/CookbookView.vue +++ b/vue/src/apps/CookbookView/CookbookView.vue @@ -6,11 +6,7 @@
- + @@ -48,13 +44,7 @@ - +
diff --git a/vue/src/apps/RecipeSearchView/RecipeSearchView.vue b/vue/src/apps/RecipeSearchView/RecipeSearchView.vue index 05291029..c1df9db2 100644 --- a/vue/src/apps/RecipeSearchView/RecipeSearchView.vue +++ b/vue/src/apps/RecipeSearchView/RecipeSearchView.vue @@ -903,6 +903,7 @@ export default { } if (filter) { + // this can be simplified by calling recipe API {query: {filter: filter_id}} but you lose loading all of the params into the UI filter = JSON.parse(filter.search) let fields = ["keywords", "foods", "books"] let operators = ["_or", "_and", "_or_not", "_and_not"] diff --git a/vue/src/components/CookbookEditCard.vue b/vue/src/components/CookbookEditCard.vue index a2feebe0..616dc8ba 100644 --- a/vue/src/components/CookbookEditCard.vue +++ b/vue/src/components/CookbookEditCard.vue @@ -1,130 +1,149 @@ - - \ No newline at end of file + diff --git a/vue/src/locales/en.json b/vue/src/locales/en.json index fda58356..4f562c0e 100644 --- a/vue/src/locales/en.json +++ b/vue/src/locales/en.json @@ -325,5 +325,7 @@ "date_created": "Date Created", "show_sortby": "Show Sort By", "search_rank": "Search Rank", - "make_now": "Make Now" + "make_now": "Make Now", + "recipe_filter": "Recipe Filter", + "book_filter_help": "Include recipes from recipe filter instead of assigning each recipe" } diff --git a/vue/src/utils/models.js b/vue/src/utils/models.js index cec052af..f63775af 100644 --- a/vue/src/utils/models.js +++ b/vue/src/utils/models.js @@ -249,7 +249,7 @@ export class Models { name: "Recipe_Book", apiName: "RecipeBook", create: { - params: [["name", "description", "icon"]], + params: [["name", "description", "icon", "filter"]], form: { name: { form_field: true, @@ -271,6 +271,13 @@ export class Models { field: "icon", label: i18n.t("Icon"), }, + filter: { + form_field: true, + type: "lookup", + field: "filter", + label: i18n.t("Custom Filter"), + list: "CUSTOM_FILTER", + }, }, }, } diff --git a/vue/src/utils/openapi/api.ts b/vue/src/utils/openapi/api.ts index 68d998f3..a21b4e71 100644 --- a/vue/src/utils/openapi/api.ts +++ b/vue/src/utils/openapi/api.ts @@ -1555,6 +1555,12 @@ export interface RecipeBook { * @memberof RecipeBook */ created_by?: string; + /** + * + * @type {RecipeBookFilter} + * @memberof RecipeBook + */ + filter?: RecipeBookFilter; } /** * @@ -1593,6 +1599,43 @@ export interface RecipeBookEntry { */ recipe_content?: string; } +/** + * + * @export + * @interface RecipeBookFilter + */ +export interface RecipeBookFilter { + /** + * + * @type {number} + * @memberof RecipeBookFilter + */ + id?: number; + /** + * + * @type {string} + * @memberof RecipeBookFilter + */ + name: string; + /** + * + * @type {string} + * @memberof RecipeBookFilter + */ + search: string; + /** + * + * @type {Array} + * @memberof RecipeBookFilter + */ + shared?: Array; + /** + * + * @type {string} + * @memberof RecipeBookFilter + */ + created_by?: string; +} /** * * @export