added api pagination for recipes
This commit is contained in:
parent
04488741c4
commit
e676b4bac3
@ -50,7 +50,7 @@ def search_recipes(queryset, params):
|
|||||||
if search_internal == 'true':
|
if search_internal == 'true':
|
||||||
queryset = queryset.filter(internal=True)
|
queryset = queryset.filter(internal=True)
|
||||||
|
|
||||||
if search_limit:
|
# if search_limit:
|
||||||
queryset = queryset[:int(search_limit)]
|
# queryset = queryset[:int(search_limit)]
|
||||||
|
|
||||||
return queryset
|
return queryset
|
||||||
|
File diff suppressed because one or more lines are too long
@ -4,6 +4,7 @@ import re
|
|||||||
import uuid
|
import uuid
|
||||||
|
|
||||||
import requests
|
import requests
|
||||||
|
import yaml
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
from annoying.decorators import ajax_request
|
from annoying.decorators import ajax_request
|
||||||
from annoying.functions import get_object_or_None
|
from annoying.functions import get_object_or_None
|
||||||
@ -19,8 +20,10 @@ from django.utils.translation import gettext as _
|
|||||||
from icalendar import Calendar, Event
|
from icalendar import Calendar, Event
|
||||||
from rest_framework import decorators, viewsets, status
|
from rest_framework import decorators, viewsets, status
|
||||||
from rest_framework.exceptions import APIException, PermissionDenied, NotFound, MethodNotAllowed
|
from rest_framework.exceptions import APIException, PermissionDenied, NotFound, MethodNotAllowed
|
||||||
|
from rest_framework.pagination import LimitOffsetPagination, PageNumberPagination
|
||||||
from rest_framework.parsers import MultiPartParser
|
from rest_framework.parsers import MultiPartParser
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
|
from rest_framework.schemas import AutoSchema
|
||||||
from rest_framework.viewsets import ViewSetMixin
|
from rest_framework.viewsets import ViewSetMixin
|
||||||
|
|
||||||
from cookbook.helper.ingredient_parser import parse
|
from cookbook.helper.ingredient_parser import parse
|
||||||
@ -49,7 +52,8 @@ from cookbook.serializer import (FoodSerializer, IngredientSerializer,
|
|||||||
StorageSerializer, SyncLogSerializer,
|
StorageSerializer, SyncLogSerializer,
|
||||||
SyncSerializer, UnitSerializer,
|
SyncSerializer, UnitSerializer,
|
||||||
UserNameSerializer, UserPreferenceSerializer,
|
UserNameSerializer, UserPreferenceSerializer,
|
||||||
ViewLogSerializer, CookLogSerializer, RecipeBookEntrySerializer, RecipeOverviewSerializer, SupermarketSerializer, ImportLogSerializer)
|
ViewLogSerializer, CookLogSerializer, RecipeBookEntrySerializer,
|
||||||
|
RecipeOverviewSerializer, SupermarketSerializer, ImportLogSerializer)
|
||||||
from recipes.settings import DEMO
|
from recipes.settings import DEMO
|
||||||
from recipe_scrapers import scrape_me, WebsiteNotImplementedError, NoSchemaFoundInWildMode
|
from recipe_scrapers import scrape_me, WebsiteNotImplementedError, NoSchemaFoundInWildMode
|
||||||
|
|
||||||
@ -248,7 +252,8 @@ class MealTypeViewSet(viewsets.ModelViewSet):
|
|||||||
permission_classes = [CustomIsOwner]
|
permission_classes = [CustomIsOwner]
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
queryset = self.queryset.order_by('order', 'id').filter(created_by=self.request.user).filter(space=self.request.space).all()
|
queryset = self.queryset.order_by('order', 'id').filter(created_by=self.request.user).filter(
|
||||||
|
space=self.request.space).all()
|
||||||
return queryset
|
return queryset
|
||||||
|
|
||||||
|
|
||||||
@ -270,29 +275,59 @@ class StepViewSet(viewsets.ModelViewSet):
|
|||||||
return self.queryset.filter(recipe__space=self.request.space)
|
return self.queryset.filter(recipe__space=self.request.space)
|
||||||
|
|
||||||
|
|
||||||
|
class RecipePagination(PageNumberPagination):
|
||||||
|
page_size = 25
|
||||||
|
page_size_query_param = 'page_size'
|
||||||
|
max_page_size = 100
|
||||||
|
|
||||||
|
|
||||||
|
from rest_framework.schemas.openapi import AutoSchema, SchemaGenerator
|
||||||
|
|
||||||
|
|
||||||
|
class RecipeSchema(AutoSchema):
|
||||||
|
|
||||||
|
def get_path_parameters(self, path, method):
|
||||||
|
parameters = super().get_path_parameters(path, method)
|
||||||
|
parameters.append(
|
||||||
|
{
|
||||||
|
"name": 'query',
|
||||||
|
"in": "path",
|
||||||
|
"required": True,
|
||||||
|
"description": 'description',
|
||||||
|
'schema': {
|
||||||
|
'type': 'string', # TODO: integer, pattern, ...
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
return parameters
|
||||||
|
|
||||||
|
|
||||||
class RecipeViewSet(viewsets.ModelViewSet):
|
class RecipeViewSet(viewsets.ModelViewSet):
|
||||||
"""
|
"""
|
||||||
list:
|
list:
|
||||||
optional parameters
|
optional parameters
|
||||||
|
- **query**: search recipes for a string contained in the recipe name (case in-sensitive)
|
||||||
- **query**: search recipes for a string contained
|
- **limit**: limits the amount of returned results
|
||||||
in the recipe name (case in-sensitive)
|
"""
|
||||||
- **limit**: limits the amount of returned results
|
|
||||||
"""
|
|
||||||
queryset = Recipe.objects
|
queryset = Recipe.objects
|
||||||
serializer_class = RecipeSerializer
|
serializer_class = RecipeSerializer
|
||||||
# TODO split read and write permission for meal plan guest
|
# TODO split read and write permission for meal plan guest
|
||||||
permission_classes = [CustomIsShare | CustomIsGuest]
|
permission_classes = [CustomIsShare | CustomIsGuest]
|
||||||
|
|
||||||
|
pagination_class = RecipePagination
|
||||||
|
|
||||||
|
# schema = RecipeSchema
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
share = self.request.query_params.get('share', None)
|
share = self.request.query_params.get('share', None)
|
||||||
if not (share and self.detail):
|
if not (share and self.detail):
|
||||||
self.queryset = self.queryset.filter(space=self.request.space)
|
self.queryset = self.queryset.filter(space=self.request.space)
|
||||||
|
|
||||||
return search_recipes(self.queryset, self.request.GET)
|
self.queryset = search_recipes(self.queryset, self.request.GET)
|
||||||
|
|
||||||
|
return super().get_queryset()
|
||||||
|
|
||||||
# TODO write extensive tests for permissions
|
# TODO write extensive tests for permissions
|
||||||
|
|
||||||
def get_serializer_class(self):
|
def get_serializer_class(self):
|
||||||
if self.action == 'list':
|
if self.action == 'list':
|
||||||
return RecipeOverviewSerializer
|
return RecipeOverviewSerializer
|
||||||
@ -341,7 +376,9 @@ class ShoppingListRecipeViewSet(viewsets.ModelViewSet):
|
|||||||
permission_classes = [CustomIsOwner | CustomIsShared]
|
permission_classes = [CustomIsOwner | CustomIsShared]
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
return self.queryset.filter(Q(shoppinglist__created_by=self.request.user) | Q(shoppinglist__shared=self.request.user)).filter(shoppinglist__space=self.request.space).all()
|
return self.queryset.filter(
|
||||||
|
Q(shoppinglist__created_by=self.request.user) | Q(shoppinglist__shared=self.request.user)).filter(
|
||||||
|
shoppinglist__space=self.request.space).all()
|
||||||
|
|
||||||
|
|
||||||
class ShoppingListEntryViewSet(viewsets.ModelViewSet):
|
class ShoppingListEntryViewSet(viewsets.ModelViewSet):
|
||||||
@ -350,7 +387,9 @@ class ShoppingListEntryViewSet(viewsets.ModelViewSet):
|
|||||||
permission_classes = [CustomIsOwner | CustomIsShared]
|
permission_classes = [CustomIsOwner | CustomIsShared]
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
return self.queryset.filter(Q(shoppinglist__created_by=self.request.user) | Q(shoppinglist__shared=self.request.user)).filter(shoppinglist__space=self.request.space).all()
|
return self.queryset.filter(
|
||||||
|
Q(shoppinglist__created_by=self.request.user) | Q(shoppinglist__shared=self.request.user)).filter(
|
||||||
|
shoppinglist__space=self.request.space).all()
|
||||||
|
|
||||||
|
|
||||||
class ShoppingListViewSet(viewsets.ModelViewSet):
|
class ShoppingListViewSet(viewsets.ModelViewSet):
|
||||||
@ -359,7 +398,8 @@ class ShoppingListViewSet(viewsets.ModelViewSet):
|
|||||||
permission_classes = [CustomIsOwner | CustomIsShared]
|
permission_classes = [CustomIsOwner | CustomIsShared]
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
return self.queryset.filter(Q(created_by=self.request.user) | Q(shared=self.request.user)).filter(space=self.request.space).distinct()
|
return self.queryset.filter(Q(created_by=self.request.user) | Q(shared=self.request.user)).filter(
|
||||||
|
space=self.request.space).distinct()
|
||||||
|
|
||||||
def get_serializer_class(self):
|
def get_serializer_class(self):
|
||||||
try:
|
try:
|
||||||
@ -562,7 +602,8 @@ def recipe_from_url_old(request):
|
|||||||
url = request.POST['url']
|
url = request.POST['url']
|
||||||
|
|
||||||
headers = {
|
headers = {
|
||||||
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.106 Safari/537.36' # noqa: E501
|
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.106 Safari/537.36'
|
||||||
|
# noqa: E501
|
||||||
}
|
}
|
||||||
try:
|
try:
|
||||||
response = requests.get(url, headers=headers)
|
response = requests.get(url, headers=headers)
|
||||||
|
@ -51,7 +51,6 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
|
|
||||||
@ -60,11 +59,12 @@
|
|||||||
|
|
||||||
<generic-multiselect @change="genericSelectChanged" parent_variable="search_keywords"
|
<generic-multiselect @change="genericSelectChanged" parent_variable="search_keywords"
|
||||||
:initial_selection="search_keywords"
|
:initial_selection="search_keywords"
|
||||||
search_function="listKeywords" label="label" style="flex-grow: 1; flex-shrink: 1; flex-basis: 0"
|
search_function="listKeywords" label="label"
|
||||||
|
style="flex-grow: 1; flex-shrink: 1; flex-basis: 0"
|
||||||
v-bind:placeholder="$t('Keywords')"></generic-multiselect>
|
v-bind:placeholder="$t('Keywords')"></generic-multiselect>
|
||||||
|
|
||||||
<b-input-group-append >
|
<b-input-group-append>
|
||||||
<b-input-group-text >
|
<b-input-group-text>
|
||||||
|
|
||||||
<b-form-checkbox v-model="search_keywords_or" name="check-button" @change="refreshData"
|
<b-form-checkbox v-model="search_keywords_or" name="check-button" @change="refreshData"
|
||||||
class="shadow-none" switch>
|
class="shadow-none" switch>
|
||||||
@ -83,10 +83,11 @@
|
|||||||
<b-input-group style="margin-top: 1vh">
|
<b-input-group style="margin-top: 1vh">
|
||||||
<generic-multiselect @change="genericSelectChanged" parent_variable="search_foods"
|
<generic-multiselect @change="genericSelectChanged" parent_variable="search_foods"
|
||||||
:initial_selection="search_foods"
|
:initial_selection="search_foods"
|
||||||
search_function="listFoods" label="name" style="flex-grow: 1; flex-shrink: 1; flex-basis: 0"
|
search_function="listFoods" label="name"
|
||||||
|
style="flex-grow: 1; flex-shrink: 1; flex-basis: 0"
|
||||||
v-bind:placeholder="$t('Ingredients')"></generic-multiselect>
|
v-bind:placeholder="$t('Ingredients')"></generic-multiselect>
|
||||||
<b-input-group-append >
|
<b-input-group-append>
|
||||||
<b-input-group-text >
|
<b-input-group-text>
|
||||||
<b-form-checkbox v-model="search_foods_or" name="check-button" @change="refreshData"
|
<b-form-checkbox v-model="search_foods_or" name="check-button" @change="refreshData"
|
||||||
class="shadow-none" switch>
|
class="shadow-none" switch>
|
||||||
<span class="text-uppercase" v-if="search_foods_or">{{ $t('or') }}</span>
|
<span class="text-uppercase" v-if="search_foods_or">{{ $t('or') }}</span>
|
||||||
@ -105,10 +106,11 @@
|
|||||||
<b-input-group style="margin-top: 1vh">
|
<b-input-group style="margin-top: 1vh">
|
||||||
<generic-multiselect @change="genericSelectChanged" parent_variable="search_books"
|
<generic-multiselect @change="genericSelectChanged" parent_variable="search_books"
|
||||||
:initial_selection="search_books"
|
:initial_selection="search_books"
|
||||||
search_function="listRecipeBooks" label="name" style="flex-grow: 1; flex-shrink: 1; flex-basis: 0"
|
search_function="listRecipeBooks" label="name"
|
||||||
|
style="flex-grow: 1; flex-shrink: 1; flex-basis: 0"
|
||||||
v-bind:placeholder="$t('Books')"></generic-multiselect>
|
v-bind:placeholder="$t('Books')"></generic-multiselect>
|
||||||
<b-input-group-append >
|
<b-input-group-append>
|
||||||
<b-input-group-text >
|
<b-input-group-text>
|
||||||
|
|
||||||
<b-form-checkbox v-model="search_books_or" name="check-button" @change="refreshData"
|
<b-form-checkbox v-model="search_books_or" name="check-button" @change="refreshData"
|
||||||
class="shadow-none" tyle="width: 100%" switch>
|
class="shadow-none" tyle="width: 100%" switch>
|
||||||
@ -139,6 +141,21 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="row" style="margin-top: 2vh">
|
||||||
|
<div class="col col-md-12">
|
||||||
|
<b-pagination
|
||||||
|
v-model="pagination_page"
|
||||||
|
:total-rows="pagination_count"
|
||||||
|
per-page="25"
|
||||||
|
@change="pageChange"
|
||||||
|
align="center">
|
||||||
|
|
||||||
|
</b-pagination>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-2 d-none d-md-block">
|
<div class="col-md-2 d-none d-md-block">
|
||||||
|
|
||||||
@ -185,6 +202,8 @@ export default {
|
|||||||
|
|
||||||
advanced_search_visible: true,
|
advanced_search_visible: true,
|
||||||
|
|
||||||
|
pagination_count: 0,
|
||||||
|
pagination_page: 1,
|
||||||
}
|
}
|
||||||
|
|
||||||
},
|
},
|
||||||
@ -213,10 +232,11 @@ export default {
|
|||||||
books_or: this.search_books_or,
|
books_or: this.search_books_or,
|
||||||
|
|
||||||
internal: this.search_internal,
|
internal: this.search_internal,
|
||||||
limit: 20,
|
page: this.pagination_page,
|
||||||
}
|
}
|
||||||
}).then(result => {
|
}).then(result => {
|
||||||
this.recipes = result.data
|
this.recipes = result.data.results
|
||||||
|
this.pagination_count = result.data.count
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
genericSelectChanged: function (obj) {
|
genericSelectChanged: function (obj) {
|
||||||
@ -230,6 +250,10 @@ export default {
|
|||||||
this.search_foods = []
|
this.search_foods = []
|
||||||
this.search_books = []
|
this.search_books = []
|
||||||
this.refreshData()
|
this.refreshData()
|
||||||
|
},
|
||||||
|
pageChange: function (page){
|
||||||
|
this.pagination_page = page
|
||||||
|
this.refreshData()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user