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

View file

@ -11,8 +11,17 @@ export const useLazyRecipes = function () {
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) {
const { data } = await api.recipes.getAll(page, perPage, { orderBy, orderDirection, "categories": category, "tags": tag, "tools": tool });
async function fetchMore(
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 : [];
}

View file

@ -11,25 +11,35 @@
</v-card>
<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>
</template>
<script lang="ts">
import { defineComponent, useRoute, ref, useMeta } from "@nuxtjs/composition-api";
import { useLazyRecipes } from "~/composables/recipes";
import RecipeCardSection from "@/components/Domain/Recipe/RecipeCardSection.vue";
import { useCookbook } from "~/composables/use-group-cookbooks";
export default defineComponent({
components: { RecipeCardSection },
setup() {
const { recipes, appendRecipes, assignSorted, removeRecipe, replaceRecipes } = useLazyRecipes();
const route = useRoute();
const slug = route.value.params.slug;
const { getOne } = useCookbook();
const tab = ref(null);
const book = getOne(slug);
useMeta(() => {
@ -40,7 +50,13 @@ export default defineComponent({
return {
book,
slug,
tab,
appendRecipes,
assignSorted,
recipes,
removeRecipe,
replaceRecipes,
};
},
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.tag import Tag
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.recipe import RecipeCategory, RecipePagination, RecipeSummary, RecipeTag, RecipeTool
from mealie.schema.recipe.recipe_category import CategoryBase, TagBase
@ -134,6 +135,7 @@ class RepositoryRecipes(RepositoryGeneric[Recipe, RecipeModel]):
pagination: PaginationQuery,
override=None,
load_food=False,
cookbook: Optional[ReadCookBook] = None,
categories: Optional[list[UUID4 | str]] = None,
tags: Optional[list[UUID4 | str]] = None,
tools: Optional[list[UUID4 | str]] = None,
@ -154,6 +156,18 @@ class RepositoryRecipes(RepositoryGeneric[Recipe, RecipeModel]):
fltr = self._filter_builder()
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:
for category in categories:
if isinstance(category, UUID):
@ -241,7 +255,7 @@ class RepositoryRecipes(RepositoryGeneric[Recipe, RecipeModel]):
tool_ids = [x.id for x in 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:
fltr.append(RecipeModel.tools.any(Tool.id.in_(tool_ids)))

View file

@ -4,7 +4,7 @@ from typing import Optional
from zipfile import ZipFile
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.encoders import jsonable_encoder
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.dependencies import temporary_dir, validate_recipe_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.repos.repository_generic import RepositoryGeneric
from mealie.repos.repository_recipes import RepositoryRecipes
from mealie.routes._base import BaseCrudController, controller
from mealie.routes._base.mixins import HttpRepo
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.recipe import (
CreateRecipe,
@ -54,6 +57,10 @@ class BaseRecipeController(BaseCrudController):
def repo(self) -> RepositoryRecipes:
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
def service(self) -> RecipeService:
return RecipeService(self.repos, self.user, self.group)
@ -219,24 +226,40 @@ class RecipeController(BaseRecipeController):
@router.get("", response_model=RecipePagination)
def get_all(
self,
q: RecipePaginationQuery = Depends(RecipePaginationQuery),
request: Request,
q: RecipePaginationQuery = Depends(),
cookbook: Optional[UUID4 | str] = Query(None),
categories: Optional[list[UUID4 | str]] = Query(None),
tags: 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=q,
load_food=q.load_food,
cookbook=cookbook_data,
categories=categories,
tags=tags,
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 = []
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__
if q.load_food: