mirror of
https://github.com/mealie-recipes/mealie.git
synced 2025-07-09 03:04:54 -04:00
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:
parent
d26cb570ba
commit
2007bcfe28
@ -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,
|
||||||
|
@ -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 : [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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)))
|
||||||
|
|
||||||
|
@ -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:
|
||||||
|
Loading…
x
Reference in New Issue
Block a user