diff --git a/cookbook/helper/recipe_search.py b/cookbook/helper/recipe_search.py index 8e8d07f4..e0f604ef 100644 --- a/cookbook/helper/recipe_search.py +++ b/cookbook/helper/recipe_search.py @@ -17,7 +17,6 @@ def search_recipes(queryset, params): search_books_or = params.get('books_or', True) search_internal = params.get('internal', None) - search_limit = params.get('limit', None) if settings.DATABASES['default']['ENGINE'] in ['django.db.backends.postgresql_psycopg2', 'django.db.backends.postgresql']: queryset = queryset.annotate(similarity=TrigramSimilarity('name', search_string), ).filter(Q(similarity__gt=0.1) | Q(name__unaccent__icontains=search_string)).order_by('-similarity') @@ -50,7 +49,4 @@ def search_recipes(queryset, params): if search_internal == 'true': queryset = queryset.filter(internal=True) - # if search_limit: - # queryset = queryset[:int(search_limit)] - return queryset diff --git a/cookbook/templates/meal_plan.html b/cookbook/templates/meal_plan.html index 17cd1678..741cb0ad 100644 --- a/cookbook/templates/meal_plan.html +++ b/cookbook/templates/meal_plan.html @@ -517,7 +517,7 @@ } this.$http.get(url).then((response) => { - this.recipes = response.data; + this.recipes = response.data.results; }).catch((err) => { console.log("getRecipes error: ", err); this.makeToast(gettext('Error'), gettext('There was an error loading a resource!') + err.bodyText, 'danger') @@ -530,7 +530,7 @@ } this.$http.get(url).then((response) => { - this.recipes = response.data; + this.recipes = response.data.results; }).catch((err) => { console.log("getRecipes error: ", err); this.makeToast(gettext('Error'), gettext('There was an error loading a resource!') + err.bodyText, 'danger') diff --git a/cookbook/templates/shopping_list.html b/cookbook/templates/shopping_list.html index bc816ca8..5c057441 100644 --- a/cookbook/templates/shopping_list.html +++ b/cookbook/templates/shopping_list.html @@ -834,7 +834,7 @@ } this.$http.get(url).then((response) => { - this.recipes = response.data; + this.recipes = response.data.results; }).catch((err) => { console.log("getRecipes error: ", err); this.makeToast(gettext('Error'), gettext('There was an error loading a resource!') + err.bodyText, 'danger') diff --git a/cookbook/tests/api/test_api_recipe.py b/cookbook/tests/api/test_api_recipe.py index 7506d8ef..f68db563 100644 --- a/cookbook/tests/api/test_api_recipe.py +++ b/cookbook/tests/api/test_api_recipe.py @@ -22,15 +22,15 @@ def test_list_permission(arg, request): def test_list_space(recipe_1_s1, u1_s1, u1_s2, space_2): - assert len(json.loads(u1_s1.get(reverse(LIST_URL)).content)) == 1 - assert len(json.loads(u1_s2.get(reverse(LIST_URL)).content)) == 0 + assert len(json.loads(u1_s1.get(reverse(LIST_URL)).content)['results']) == 1 + assert len(json.loads(u1_s2.get(reverse(LIST_URL)).content)['results']) == 0 with scopes_disabled(): recipe_1_s1.space = space_2 recipe_1_s1.save() - assert len(json.loads(u1_s1.get(reverse(LIST_URL)).content)) == 0 - assert len(json.loads(u1_s2.get(reverse(LIST_URL)).content)) == 1 + assert len(json.loads(u1_s1.get(reverse(LIST_URL)).content)['results']) == 0 + assert len(json.loads(u1_s2.get(reverse(LIST_URL)).content)['results']) == 1 @pytest.mark.parametrize("arg", [ diff --git a/cookbook/views/api.py b/cookbook/views/api.py index 76f147c3..4a7b7ed6 100644 --- a/cookbook/views/api.py +++ b/cookbook/views/api.py @@ -4,13 +4,11 @@ import re import uuid import requests -import yaml from PIL import Image from annoying.decorators import ajax_request from annoying.functions import get_object_or_None from django.contrib import messages from django.contrib.auth.models import User -from django.core import management from django.core.exceptions import FieldError, ValidationError from django.core.files import File from django.db.models import Q @@ -18,19 +16,21 @@ from django.http import FileResponse, HttpResponse, JsonResponse from django.shortcuts import redirect, get_object_or_404 from django.utils.translation import gettext as _ from icalendar import Calendar, Event -from rest_framework import decorators, viewsets, status -from rest_framework.exceptions import APIException, PermissionDenied, NotFound, MethodNotAllowed -from rest_framework.pagination import LimitOffsetPagination, PageNumberPagination +from recipe_scrapers import scrape_me, WebsiteNotImplementedError, NoSchemaFoundInWildMode +from rest_framework import decorators, viewsets +from rest_framework.exceptions import APIException, PermissionDenied +from rest_framework.pagination import PageNumberPagination from rest_framework.parsers import MultiPartParser from rest_framework.response import Response -from rest_framework.schemas import AutoSchema +from rest_framework.schemas.openapi import AutoSchema +from rest_framework.schemas.utils import is_list_view from rest_framework.viewsets import ViewSetMixin from cookbook.helper.ingredient_parser import parse from cookbook.helper.permission_helper import (CustomIsAdmin, CustomIsGuest, CustomIsOwner, CustomIsShare, CustomIsShared, CustomIsUser, - group_required, share_link_valid) + group_required) from cookbook.helper.recipe_search import search_recipes from cookbook.helper.recipe_url_import import get_from_html, get_from_scraper, find_recipe_json from cookbook.models import (CookLog, Food, Ingredient, Keyword, MealPlan, @@ -55,7 +55,6 @@ from cookbook.serializer import (FoodSerializer, IngredientSerializer, ViewLogSerializer, CookLogSerializer, RecipeBookEntrySerializer, RecipeOverviewSerializer, SupermarketSerializer, ImportLogSerializer) from recipes.settings import DEMO -from recipe_scrapers import scrape_me, WebsiteNotImplementedError, NoSchemaFoundInWildMode class StandardFilterMixin(ViewSetMixin): @@ -281,34 +280,58 @@ class RecipePagination(PageNumberPagination): max_page_size = 100 -from rest_framework.schemas.openapi import AutoSchema, SchemaGenerator - - +# TODO move to separate class to cleanup class RecipeSchema(AutoSchema): def get_path_parameters(self, path, method): + if not is_list_view(path, method, self.view): + return [] + parameters = super().get_path_parameters(path, method) - parameters.append( - { - "name": 'query', - "in": "path", - "required": True, - "description": 'description', - 'schema': { - 'type': 'string', # TODO: integer, pattern, ... - }, - } - ) + parameters.append({ + "name": 'query', "in": "query", "required": False, + "description": 'Query string matched (fuzzy) against recipe name. In the future also fulltext search.', + 'schema': {'type': 'string', }, + }) + parameters.append({ + "name": 'keywords', "in": "query", "required": False, + "description": 'Id of keyword a recipe should have. For multiple repeat parameter.', + 'schema': {'type': 'string', }, + }) + parameters.append({ + "name": 'foods', "in": "query", "required": False, + "description": 'Id of food a recipe should have. For multiple repeat parameter.', + 'schema': {'type': 'string', }, + }) + parameters.append({ + "name": 'books', "in": "query", "required": False, + "description": 'Id of book a recipe should have. For multiple repeat parameter.', + 'schema': {'type': 'string', }, + }) + parameters.append({ + "name": 'keywords_or', "in": "query", "required": False, + "description": 'If recipe should have all (AND) or any (OR) of the provided keywords.', + 'schema': {'type': 'string', }, + }) + parameters.append({ + "name": 'foods_or', "in": "query", "required": False, + "description": 'If recipe should have all (AND) or any (OR) any of the provided foods.', + 'schema': {'type': 'string', }, + }) + parameters.append({ + "name": 'books_or', "in": "query", "required": False, + "description": 'If recipe should be in all (AND) or any (OR) any of the provided books.', + 'schema': {'type': 'string', }, + }) + parameters.append({ + "name": 'internal', "in": "query", "required": False, + "description": 'true or false. If only internal recipes should be returned or not.', + 'schema': {'type': 'string', }, + }) return parameters class RecipeViewSet(viewsets.ModelViewSet): - """ - list: - optional parameters - - **query**: search recipes for a string contained in the recipe name (case in-sensitive) - - **limit**: limits the amount of returned results - """ queryset = Recipe.objects serializer_class = RecipeSerializer # TODO split read and write permission for meal plan guest @@ -316,7 +339,7 @@ class RecipeViewSet(viewsets.ModelViewSet): pagination_class = RecipePagination - # schema = RecipeSchema + schema = RecipeSchema() def get_queryset(self): share = self.request.query_params.get('share', None)