feat: added "cookbook" filter to recipe pagination to serve frontend (#1609)

* added cookbook filter to recipe pagination

* fixed wrong filter var

* restored cookbook sorting

* reverted unnecessary var change
This commit is contained in:
Michael Genson 2022-09-10 11:59:30 -05:00 committed by GitHub
parent d26cb570ba
commit 2007bcfe28
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 80 additions and 9 deletions

View File

@ -161,6 +161,10 @@ export default defineComponent({
type: Array as () => Recipe[], type: Array as () => Recipe[],
default: () => [], default: () => [],
}, },
cookbookSlug: {
type: String,
default: null,
},
categorySlug: { categorySlug: {
type: String, type: String,
default: null, default: null,
@ -213,6 +217,8 @@ export default defineComponent({
const hasMore = ref(true); const hasMore = ref(true);
const ready = ref(false); const ready = ref(false);
const loading = ref(false); const loading = ref(false);
const cookbook = ref<string>(props.cookbookSlug);
const category = ref<string>(props.categorySlug); const category = ref<string>(props.categorySlug);
const tag = ref<string>(props.tagSlug); const tag = ref<string>(props.tagSlug);
const tool = ref<string>(props.toolSlug); const tool = ref<string>(props.toolSlug);
@ -227,6 +233,7 @@ export default defineComponent({
perPage.value*2, perPage.value*2,
preferences.value.orderBy, preferences.value.orderBy,
preferences.value.orderDirection, preferences.value.orderDirection,
cookbook.value,
category.value, category.value,
tag.value, tag.value,
tool.value, tool.value,
@ -253,6 +260,7 @@ export default defineComponent({
perPage.value, perPage.value,
preferences.value.orderBy, preferences.value.orderBy,
preferences.value.orderDirection, preferences.value.orderDirection,
cookbook.value,
category.value, category.value,
tag.value, tag.value,
tool.value, tool.value,
@ -314,6 +322,7 @@ export default defineComponent({
perPage.value, perPage.value,
preferences.value.orderBy, preferences.value.orderBy,
preferences.value.orderDirection, preferences.value.orderDirection,
cookbook.value,
category.value, category.value,
tag.value, tag.value,
tool.value, tool.value,

View File

@ -11,8 +11,17 @@ export const useLazyRecipes = function () {
const recipes = ref<Recipe[]>([]); const recipes = ref<Recipe[]>([]);
async function fetchMore(page: number, perPage: number, orderBy: string | null = null, orderDirection = "desc", category: string | null = null, tag: string | null = null, tool: string | null = null) { async function fetchMore(
const { data } = await api.recipes.getAll(page, perPage, { orderBy, orderDirection, "categories": category, "tags": tag, "tools": tool }); page: number,
perPage: number,
orderBy: string | null = null,
orderDirection = "desc",
cookbook: string | null = null,
category: string | null = null,
tag: string | null = null,
tool: string | null = null
) {
const { data } = await api.recipes.getAll(page, perPage, { orderBy, orderDirection, cookbook, "categories": category, "tags": tag, "tools": tool });
return data ? data.items : []; return data ? data.items : [];
} }

View File

@ -11,25 +11,35 @@
</v-card> </v-card>
<v-container class="pa-0"> <v-container class="pa-0">
<RecipeCardSection class="mb-5 mx-1" :recipes="book.recipes" /> <RecipeCardSection
class="mb-5 mx-1"
:recipes="recipes"
:cookbook-slug="slug"
@sortRecipes="assignSorted"
@replaceRecipes="replaceRecipes"
@appendRecipes="appendRecipes"
@delete="removeRecipe"
/>
</v-container> </v-container>
</v-container> </v-container>
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent, useRoute, ref, useMeta } from "@nuxtjs/composition-api"; import { defineComponent, useRoute, ref, useMeta } from "@nuxtjs/composition-api";
import { useLazyRecipes } from "~/composables/recipes";
import RecipeCardSection from "@/components/Domain/Recipe/RecipeCardSection.vue"; import RecipeCardSection from "@/components/Domain/Recipe/RecipeCardSection.vue";
import { useCookbook } from "~/composables/use-group-cookbooks"; import { useCookbook } from "~/composables/use-group-cookbooks";
export default defineComponent({ export default defineComponent({
components: { RecipeCardSection }, components: { RecipeCardSection },
setup() { setup() {
const { recipes, appendRecipes, assignSorted, removeRecipe, replaceRecipes } = useLazyRecipes();
const route = useRoute(); const route = useRoute();
const slug = route.value.params.slug; const slug = route.value.params.slug;
const { getOne } = useCookbook(); const { getOne } = useCookbook();
const tab = ref(null); const tab = ref(null);
const book = getOne(slug); const book = getOne(slug);
useMeta(() => { useMeta(() => {
@ -40,7 +50,13 @@ export default defineComponent({
return { return {
book, book,
slug,
tab, tab,
appendRecipes,
assignSorted,
recipes,
removeRecipe,
replaceRecipes,
}; };
}, },
head: {}, // Must include for useMeta head: {}, // Must include for useMeta

View File

@ -14,6 +14,7 @@ from mealie.db.models.recipe.recipe import RecipeModel
from mealie.db.models.recipe.settings import RecipeSettings from mealie.db.models.recipe.settings import RecipeSettings
from mealie.db.models.recipe.tag import Tag from mealie.db.models.recipe.tag import Tag
from mealie.db.models.recipe.tool import Tool from mealie.db.models.recipe.tool import Tool
from mealie.schema.cookbook.cookbook import ReadCookBook
from mealie.schema.recipe import Recipe from mealie.schema.recipe import Recipe
from mealie.schema.recipe.recipe import RecipeCategory, RecipePagination, RecipeSummary, RecipeTag, RecipeTool from mealie.schema.recipe.recipe import RecipeCategory, RecipePagination, RecipeSummary, RecipeTag, RecipeTool
from mealie.schema.recipe.recipe_category import CategoryBase, TagBase from mealie.schema.recipe.recipe_category import CategoryBase, TagBase
@ -134,6 +135,7 @@ class RepositoryRecipes(RepositoryGeneric[Recipe, RecipeModel]):
pagination: PaginationQuery, pagination: PaginationQuery,
override=None, override=None,
load_food=False, load_food=False,
cookbook: Optional[ReadCookBook] = None,
categories: Optional[list[UUID4 | str]] = None, categories: Optional[list[UUID4 | str]] = None,
tags: Optional[list[UUID4 | str]] = None, tags: Optional[list[UUID4 | str]] = None,
tools: Optional[list[UUID4 | str]] = None, tools: Optional[list[UUID4 | str]] = None,
@ -154,6 +156,18 @@ class RepositoryRecipes(RepositoryGeneric[Recipe, RecipeModel]):
fltr = self._filter_builder() fltr = self._filter_builder()
q = q.filter_by(**fltr) q = q.filter_by(**fltr)
if cookbook:
cb_filters = self._category_tag_filters(
cookbook.categories,
cookbook.tags,
cookbook.tools,
cookbook.require_all_categories,
cookbook.require_all_tags,
cookbook.require_all_tools,
)
q = q.filter(*cb_filters)
if categories: if categories:
for category in categories: for category in categories:
if isinstance(category, UUID): if isinstance(category, UUID):
@ -241,7 +255,7 @@ class RepositoryRecipes(RepositoryGeneric[Recipe, RecipeModel]):
tool_ids = [x.id for x in tools] tool_ids = [x.id for x in tools]
if require_all_tools: if require_all_tools:
fltr.extend(RecipeModel.tags.any(Tag.id == tag_id) for tag_id in tag_ids) fltr.extend(RecipeModel.tools.any(Tool.id == tool_id) for tool_id in tool_ids)
else: else:
fltr.append(RecipeModel.tools.any(Tool.id.in_(tool_ids))) fltr.append(RecipeModel.tools.any(Tool.id.in_(tool_ids)))

View File

@ -4,7 +4,7 @@ from typing import Optional
from zipfile import ZipFile from zipfile import ZipFile
import sqlalchemy import sqlalchemy
from fastapi import BackgroundTasks, Depends, File, Form, HTTPException, Query, status from fastapi import BackgroundTasks, Depends, File, Form, HTTPException, Query, Request, status
from fastapi.datastructures import UploadFile from fastapi.datastructures import UploadFile
from fastapi.encoders import jsonable_encoder from fastapi.encoders import jsonable_encoder
from fastapi.responses import JSONResponse from fastapi.responses import JSONResponse
@ -16,11 +16,14 @@ from mealie.core import exceptions
from mealie.core.dependencies import temporary_zip_path from mealie.core.dependencies import temporary_zip_path
from mealie.core.dependencies.dependencies import temporary_dir, validate_recipe_token from mealie.core.dependencies.dependencies import temporary_dir, validate_recipe_token
from mealie.core.security import create_recipe_slug_token from mealie.core.security import create_recipe_slug_token
from mealie.db.models.group.cookbook import CookBook
from mealie.pkgs import cache from mealie.pkgs import cache
from mealie.repos.repository_generic import RepositoryGeneric
from mealie.repos.repository_recipes import RepositoryRecipes from mealie.repos.repository_recipes import RepositoryRecipes
from mealie.routes._base import BaseCrudController, controller from mealie.routes._base import BaseCrudController, controller
from mealie.routes._base.mixins import HttpRepo from mealie.routes._base.mixins import HttpRepo
from mealie.routes._base.routers import MealieCrudRoute, UserAPIRouter from mealie.routes._base.routers import MealieCrudRoute, UserAPIRouter
from mealie.schema.cookbook.cookbook import ReadCookBook
from mealie.schema.recipe import Recipe, RecipeImageTypes, ScrapeRecipe from mealie.schema.recipe import Recipe, RecipeImageTypes, ScrapeRecipe
from mealie.schema.recipe.recipe import ( from mealie.schema.recipe.recipe import (
CreateRecipe, CreateRecipe,
@ -54,6 +57,10 @@ class BaseRecipeController(BaseCrudController):
def repo(self) -> RepositoryRecipes: def repo(self) -> RepositoryRecipes:
return self.repos.recipes.by_group(self.group_id) return self.repos.recipes.by_group(self.group_id)
@cached_property
def cookbooks_repo(self) -> RepositoryGeneric[ReadCookBook, CookBook]:
return self.repos.cookbooks.by_group(self.group_id)
@cached_property @cached_property
def service(self) -> RecipeService: def service(self) -> RecipeService:
return RecipeService(self.repos, self.user, self.group) return RecipeService(self.repos, self.user, self.group)
@ -219,24 +226,40 @@ class RecipeController(BaseRecipeController):
@router.get("", response_model=RecipePagination) @router.get("", response_model=RecipePagination)
def get_all( def get_all(
self, self,
q: RecipePaginationQuery = Depends(RecipePaginationQuery), request: Request,
q: RecipePaginationQuery = Depends(),
cookbook: Optional[UUID4 | str] = Query(None),
categories: Optional[list[UUID4 | str]] = Query(None), categories: Optional[list[UUID4 | str]] = Query(None),
tags: Optional[list[UUID4 | str]] = Query(None), tags: Optional[list[UUID4 | str]] = Query(None),
tools: Optional[list[UUID4 | str]] = Query(None), tools: Optional[list[UUID4 | str]] = Query(None),
): ):
cookbook_data: Optional[ReadCookBook] = None
if cookbook:
cb_match_attr = "slug" if isinstance(cookbook, str) else "id"
cookbook_data = self.cookbooks_repo.get_one(cookbook, cb_match_attr)
if cookbook is None:
raise HTTPException(status_code=404, detail="cookbook not found")
pagination_response = self.repo.page_all( pagination_response = self.repo.page_all(
pagination=q, pagination=q,
load_food=q.load_food, load_food=q.load_food,
cookbook=cookbook_data,
categories=categories, categories=categories,
tags=tags, tags=tags,
tools=tools, tools=tools,
) )
pagination_response.set_pagination_guides(router.url_path_for("get_all"), q.dict()) # merge default pagination with the request's query params
query_params = q.dict() | {**request.query_params}
pagination_response.set_pagination_guides(
router.url_path_for("get_all"),
{k: v for k, v in query_params.items() if v is not None},
)
new_items = [] new_items = []
for item in pagination_response.items: for item in pagination_response.items:
# Pydantic/FastAPI can't seem to serialize the ingredient field on thier own. # Pydantic/FastAPI can't seem to serialize the ingredient field on their own.
new_item = item.__dict__ new_item = item.__dict__
if q.load_food: if q.load_food: