From cfaac2e0601cf69cc32a1028e50b51ffbc65cf53 Mon Sep 17 00:00:00 2001 From: Hayden <64056131+hay-kot@users.noreply.github.com> Date: Fri, 1 Apr 2022 09:50:31 -0800 Subject: [PATCH] feat: additional cookbook features (tags, tools, and public) (#1116) * migration: add public, tags, and tools * generate frontend types * add help icon * start replacement for tool-tag-category selector * add help icon utility * use generator types * add support for cookbook features * add UI elements for cookbook features * fix tests * fix type error --- ...9.55_59eb59135381_add_tags_to_cookbooks.py | 57 +++++++++ .../api/class-interfaces/group-cookbooks.ts | 20 +--- .../Domain/Recipe/RecipeOrganizerSelector.vue | 109 ++++++++++++++++++ frontend/components/global/HelpIcon.vue | 28 +++++ frontend/composables/use-group-cookbooks.ts | 8 +- frontend/layouts/default.vue | 4 +- frontend/pages/cookbooks/_slug.vue | 18 +-- frontend/pages/group/cookbooks.vue | 40 ++++++- frontend/types/api-types/admin.ts | 1 + frontend/types/api-types/cookbook.ts | 52 ++++++--- frontend/types/api-types/group.ts | 1 + frontend/types/api-types/meal-plan.ts | 1 + frontend/types/api-types/user.ts | 1 + frontend/utils/icons/icon-type.ts | 1 + frontend/utils/icons/icons.ts | 2 + mealie/db/models/group/cookbook.py | 13 ++- mealie/db/models/recipe/tag.py | 7 ++ mealie/db/models/recipe/tool.py | 7 ++ mealie/repos/repository_recipes.py | 50 ++++++-- mealie/routes/groups/controller_cookbooks.py | 28 ++--- mealie/schema/cookbook/cookbook.py | 12 +- .../user_group_tests/test_group_cookbooks.py | 9 +- .../backup_v2_tests/test_alchemy_exporter.py | 2 +- 23 files changed, 374 insertions(+), 97 deletions(-) create mode 100644 alembic/versions/2022-03-31-19.19.55_59eb59135381_add_tags_to_cookbooks.py create mode 100644 frontend/components/Domain/Recipe/RecipeOrganizerSelector.vue create mode 100644 frontend/components/global/HelpIcon.vue diff --git a/alembic/versions/2022-03-31-19.19.55_59eb59135381_add_tags_to_cookbooks.py b/alembic/versions/2022-03-31-19.19.55_59eb59135381_add_tags_to_cookbooks.py new file mode 100644 index 000000000000..c0c99246cd49 --- /dev/null +++ b/alembic/versions/2022-03-31-19.19.55_59eb59135381_add_tags_to_cookbooks.py @@ -0,0 +1,57 @@ +"""add tags to cookbooks + +Revision ID: 59eb59135381 +Revises: f1a2dbee5fe9 +Create Date: 2022-03-31 19:19:55.428965 + +""" +import sqlalchemy as sa + +import mealie.db.migration_types +from alembic import op + +# revision identifiers, used by Alembic. +revision = "59eb59135381" +down_revision = "f1a2dbee5fe9" +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.create_table( + "cookbooks_to_tags", + sa.Column("cookbook_id", mealie.db.migration_types.GUID(), nullable=True), + sa.Column("tag_id", mealie.db.migration_types.GUID(), nullable=True), + sa.ForeignKeyConstraint( + ["cookbook_id"], + ["cookbooks.id"], + ), + sa.ForeignKeyConstraint( + ["tag_id"], + ["tags.id"], + ), + ) + op.create_table( + "cookbooks_to_tools", + sa.Column("cookbook_id", mealie.db.migration_types.GUID(), nullable=True), + sa.Column("tool_id", mealie.db.migration_types.GUID(), nullable=True), + sa.ForeignKeyConstraint( + ["cookbook_id"], + ["cookbooks.id"], + ), + sa.ForeignKeyConstraint( + ["tool_id"], + ["tools.id"], + ), + ) + op.add_column("cookbooks", sa.Column("public", sa.Boolean(), nullable=True)) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_column("cookbooks", "public") + op.drop_table("cookbooks_to_tools") + op.drop_table("cookbooks_to_tags") + # ### end Alembic commands ### diff --git a/frontend/api/class-interfaces/group-cookbooks.ts b/frontend/api/class-interfaces/group-cookbooks.ts index fcaa85aecc01..853c14cff7a9 100644 --- a/frontend/api/class-interfaces/group-cookbooks.ts +++ b/frontend/api/class-interfaces/group-cookbooks.ts @@ -1,32 +1,18 @@ import { BaseCRUDAPI } from "../_base"; -import { CategoryBase } from "~/types/api-types/recipe"; -import { RecipeCategory } from "~/types/api-types/user"; +import { CreateCookBook, RecipeCookBook, UpdateCookBook } from "~/types/api-types/cookbook"; const prefix = "/api"; -export interface CreateCookBook { - name: string; -} - -export interface CookBook extends CreateCookBook { - id: number; - slug: string; - description: string; - position: number; - group_id: number; - categories: RecipeCategory[] | CategoryBase[]; -} - const routes = { cookbooks: `${prefix}/groups/cookbooks`, cookbooksId: (id: number) => `${prefix}/groups/cookbooks/${id}`, }; -export class CookbookAPI extends BaseCRUDAPI { +export class CookbookAPI extends BaseCRUDAPI { baseRoute: string = routes.cookbooks; itemRoute = routes.cookbooksId; - async updateAll(payload: CookBook[]) { + async updateAll(payload: UpdateCookBook[]) { return await this.requests.put(this.baseRoute, payload); } } diff --git a/frontend/components/Domain/Recipe/RecipeOrganizerSelector.vue b/frontend/components/Domain/Recipe/RecipeOrganizerSelector.vue new file mode 100644 index 000000000000..5d924dce7f0b --- /dev/null +++ b/frontend/components/Domain/Recipe/RecipeOrganizerSelector.vue @@ -0,0 +1,109 @@ + + + diff --git a/frontend/components/global/HelpIcon.vue b/frontend/components/global/HelpIcon.vue new file mode 100644 index 000000000000..c96682e93557 --- /dev/null +++ b/frontend/components/global/HelpIcon.vue @@ -0,0 +1,28 @@ + + + + + diff --git a/frontend/composables/use-group-cookbooks.ts b/frontend/composables/use-group-cookbooks.ts index 4e8a12981bc4..3a773211d52f 100644 --- a/frontend/composables/use-group-cookbooks.ts +++ b/frontend/composables/use-group-cookbooks.ts @@ -1,9 +1,9 @@ import { useAsync, ref, Ref } from "@nuxtjs/composition-api"; import { useAsyncKey } from "./use-utils"; import { useUserApi } from "~/composables/api"; -import { CookBook } from "~/api/class-interfaces/group-cookbooks"; +import { ReadCookBook, RecipeCookBook, UpdateCookBook } from "~/types/api-types/cookbook"; -let cookbookStore: Ref | null = null; +let cookbookStore: Ref | null = null; export const useCookbook = function () { function getOne(id: string | number) { @@ -60,13 +60,13 @@ export const useCookbooks = function () { loading.value = false; }, - async updateOne(updateData: CookBook) { + async updateOne(updateData: UpdateCookBook) { if (!updateData.id) { return; } loading.value = true; - const { data } = await api.cookbooks.updateOne(updateData.id, updateData); + const { data } = await api.cookbooks.updateOne(updateData.id, updateData as RecipeCookBook); if (data && cookbookStore?.value) { this.refreshAll(); } diff --git a/frontend/layouts/default.vue b/frontend/layouts/default.vue index 2ce9c981c61d..44b6a53e14c8 100644 --- a/frontend/layouts/default.vue +++ b/frontend/layouts/default.vue @@ -41,7 +41,7 @@ {{ $globals.icons.translate }} - {{ $t('sidebar.language') }} + {{ $t("sidebar.language") }} @@ -103,7 +103,7 @@ export default defineComponent({ return { icon: $globals.icons.pages, title: cookbook.name, - to: `/cookbooks/${cookbook.slug}`, + to: `/cookbooks/${cookbook.slug as string}`, }; }); }); diff --git a/frontend/pages/cookbooks/_slug.vue b/frontend/pages/cookbooks/_slug.vue index f4ba2a96098f..853ea8152233 100644 --- a/frontend/pages/cookbooks/_slug.vue +++ b/frontend/pages/cookbooks/_slug.vue @@ -9,16 +9,10 @@ {{ book.description }} - - - {{ cat.name }} - - - - - - - + + + + @@ -26,6 +20,7 @@ import { defineComponent, useRoute, ref, useMeta } from "@nuxtjs/composition-api"; import RecipeCardSection from "@/components/Domain/Recipe/RecipeCardSection.vue"; import { useCookbook } from "~/composables/use-group-cookbooks"; + export default defineComponent({ components: { RecipeCardSection }, setup() { @@ -51,6 +46,3 @@ export default defineComponent({ head: {}, // Must include for useMeta }); - - \ No newline at end of file diff --git a/frontend/pages/group/cookbooks.vue b/frontend/pages/group/cookbooks.vue index 0be319130fee..4f22ea6794b9 100644 --- a/frontend/pages/group/cookbooks.vue +++ b/frontend/pages/group/cookbooks.vue @@ -5,7 +5,9 @@ - Arrange and edit your cookbooks here. + Cookbooks are another way to organize recipes by creating cross sections of recipes and tags. Creating a cookbook + will add an entry to the side-bar and all the recipes with the tags and categories chosen will be displayed in the + cookbook. @@ -31,10 +33,24 @@ - + - + + + + + + @@ -42,12 +58,12 @@ :buttons="[ { icon: $globals.icons.delete, - text: $t('general.delete'), + text: $tc('general.delete'), event: 'delete', }, { icon: $globals.icons.save, - text: $t('general.save'), + text: $tc('general.save'), event: 'save', }, ]" @@ -66,15 +82,27 @@ import { defineComponent } from "@nuxtjs/composition-api"; import draggable from "vuedraggable"; import { useCookbooks } from "@/composables/use-group-cookbooks"; +import RecipeOrganizerSelector from "~/components/Domain/Recipe/RecipeOrganizerSelector.vue"; +import { useCategories, useTags, useTools } from "~/composables/recipes"; export default defineComponent({ - components: { draggable }, + components: { draggable, RecipeOrganizerSelector }, setup() { const { cookbooks, actions } = useCookbooks(); + const { tools } = useTools(); + const { allCategories, useAsyncGetAll: getAllCategories } = useCategories(); + const { allTags, useAsyncGetAll: getAllTags } = useTags(); + + getAllCategories(); + getAllTags(); + return { + allCategories, + allTags, cookbooks, actions, + tools, }; }, head() { diff --git a/frontend/types/api-types/admin.ts b/frontend/types/api-types/admin.ts index 680b76bcb079..fd443f8bd0fa 100644 --- a/frontend/types/api-types/admin.ts +++ b/frontend/types/api-types/admin.ts @@ -124,6 +124,7 @@ export interface RecipeIngredient { food?: IngredientFood | CreateIngredientFood; disableAmount?: boolean; quantity?: number; + originalText?: string; referenceId?: string; } export interface IngredientUnit { diff --git a/frontend/types/api-types/cookbook.ts b/frontend/types/api-types/cookbook.ts index e12f29270050..30384778d897 100644 --- a/frontend/types/api-types/cookbook.ts +++ b/frontend/types/api-types/cookbook.ts @@ -15,22 +15,46 @@ export interface CreateCookBook { description?: string; slug?: string; position?: number; + public?: boolean; categories?: CategoryBase[]; + tags?: TagBase[]; + tools?: RecipeTool[]; +} +export interface TagBase { + name: string; + id: string; + slug: string; +} +export interface RecipeTool { + id: string; + name: string; + slug: string; + onHand?: boolean; } export interface ReadCookBook { name: string; description?: string; slug?: string; position?: number; + public?: boolean; categories?: CategoryBase[]; + tags?: TagBase[]; + tools?: RecipeTool[]; groupId: string; id: string; } -export interface RecipeCategoryResponse { +export interface RecipeCookBook { name: string; + description?: string; + slug?: string; + position?: number; + public?: boolean; + categories?: CategoryBase[]; + tags?: TagBase[]; + tools?: RecipeTool[]; + groupId: string; id: string; - slug: string; - recipes?: RecipeSummary[]; + recipes: RecipeSummary[]; } export interface RecipeSummary { id?: string; @@ -64,12 +88,6 @@ export interface RecipeTag { name: string; slug: string; } -export interface RecipeTool { - id: string; - name: string; - slug: string; - onHand?: boolean; -} export interface RecipeIngredient { title?: string; note?: string; @@ -77,6 +95,7 @@ export interface RecipeIngredient { food?: IngredientFood | CreateIngredientFood; disableAmount?: boolean; quantity?: number; + originalText?: string; referenceId?: string; } export interface IngredientUnit { @@ -110,21 +129,15 @@ export interface CreateIngredientFood { description?: string; labelId?: string; } -export interface RecipeCookBook { - name: string; - description?: string; - slug?: string; - position?: number; - categories: RecipeCategoryResponse[]; - groupId: string; - id: string; -} export interface SaveCookBook { name: string; description?: string; slug?: string; position?: number; + public?: boolean; categories?: CategoryBase[]; + tags?: TagBase[]; + tools?: RecipeTool[]; groupId: string; } export interface UpdateCookBook { @@ -132,7 +145,10 @@ export interface UpdateCookBook { description?: string; slug?: string; position?: number; + public?: boolean; categories?: CategoryBase[]; + tags?: TagBase[]; + tools?: RecipeTool[]; groupId: string; id: string; } diff --git a/frontend/types/api-types/group.ts b/frontend/types/api-types/group.ts index f42d2b13dc1e..8d25c0d735f4 100644 --- a/frontend/types/api-types/group.ts +++ b/frontend/types/api-types/group.ts @@ -279,6 +279,7 @@ export interface RecipeIngredient { food?: IngredientFood | CreateIngredientFood; disableAmount?: boolean; quantity?: number; + originalText?: string; referenceId?: string; } export interface CreateIngredientUnit { diff --git a/frontend/types/api-types/meal-plan.ts b/frontend/types/api-types/meal-plan.ts index 85938c57850a..9aa07b8ca7fa 100644 --- a/frontend/types/api-types/meal-plan.ts +++ b/frontend/types/api-types/meal-plan.ts @@ -140,6 +140,7 @@ export interface RecipeIngredient { food?: IngredientFood | CreateIngredientFood; disableAmount?: boolean; quantity?: number; + originalText?: string; referenceId?: string; } export interface IngredientUnit { diff --git a/frontend/types/api-types/user.ts b/frontend/types/api-types/user.ts index 3070b448bd86..e29739814219 100644 --- a/frontend/types/api-types/user.ts +++ b/frontend/types/api-types/user.ts @@ -153,6 +153,7 @@ export interface RecipeIngredient { food?: IngredientFood | CreateIngredientFood; disableAmount?: boolean; quantity?: number; + originalText?: string; referenceId?: string; } export interface IngredientUnit { diff --git a/frontend/utils/icons/icon-type.ts b/frontend/utils/icons/icon-type.ts index 7e8f56a36237..39684c325db2 100644 --- a/frontend/utils/icons/icon-type.ts +++ b/frontend/utils/icons/icon-type.ts @@ -5,6 +5,7 @@ export interface Icon { // General chart: string; wrench: string; + help: string; bowlMixOutline: string; foods: string; units: string; diff --git a/frontend/utils/icons/icons.ts b/frontend/utils/icons/icons.ts index 4931164617ba..b4037ead1c37 100644 --- a/frontend/utils/icons/icons.ts +++ b/frontend/utils/icons/icons.ts @@ -107,6 +107,7 @@ import { mdiBowlMixOutline, mdiWrench, mdiChartLine, + mdiHelpCircleOutline, } from "@mdi/js"; export const icons = { @@ -118,6 +119,7 @@ export const icons = { // General bowlMixOutline: mdiBowlMixOutline, + help: mdiHelpCircleOutline, foods: mdiFoodApple, units: mdiBeakerOutline, alert: mdiAlert, diff --git a/mealie/db/models/group/cookbook.py b/mealie/db/models/group/cookbook.py index 89b0832be403..845b86e217ab 100644 --- a/mealie/db/models/group/cookbook.py +++ b/mealie/db/models/group/cookbook.py @@ -1,8 +1,10 @@ -from sqlalchemy import Column, ForeignKey, Integer, String, orm +from sqlalchemy import Boolean, Column, ForeignKey, Integer, String, orm from .._model_base import BaseMixins, SqlAlchemyBase from .._model_utils import auto_init, guid from ..recipe.category import Category, cookbooks_to_categories +from ..recipe.tag import Tag, cookbooks_to_tags +from ..recipe.tool import Tool, cookbooks_to_tools class CookBook(SqlAlchemyBase, BaseMixins): @@ -10,14 +12,17 @@ class CookBook(SqlAlchemyBase, BaseMixins): id = Column(guid.GUID, primary_key=True, default=guid.GUID.generate) position = Column(Integer, nullable=False, default=1) + group_id = Column(guid.GUID, ForeignKey("groups.id")) + group = orm.relationship("Group", back_populates="cookbooks") + name = Column(String, nullable=False) slug = Column(String, nullable=False) description = Column(String, default="") + public = Column(Boolean, default=False) categories = orm.relationship(Category, secondary=cookbooks_to_categories, single_parent=True) - - group_id = Column(guid.GUID, ForeignKey("groups.id")) - group = orm.relationship("Group", back_populates="cookbooks") + tags = orm.relationship(Tag, secondary=cookbooks_to_tags, single_parent=True) + tools = orm.relationship(Tool, secondary=cookbooks_to_tools, single_parent=True) @auto_init() def __init__(self, **_) -> None: diff --git a/mealie/db/models/recipe/tag.py b/mealie/db/models/recipe/tag.py index 4de8af92771f..17dff25d7637 100644 --- a/mealie/db/models/recipe/tag.py +++ b/mealie/db/models/recipe/tag.py @@ -23,6 +23,13 @@ plan_rules_to_tags = sa.Table( sa.Column("tag_id", guid.GUID, sa.ForeignKey("tags.id")), ) +cookbooks_to_tags = sa.Table( + "cookbooks_to_tags", + SqlAlchemyBase.metadata, + sa.Column("cookbook_id", guid.GUID, sa.ForeignKey("cookbooks.id")), + sa.Column("tag_id", guid.GUID, sa.ForeignKey("tags.id")), +) + class Tag(SqlAlchemyBase, BaseMixins): __tablename__ = "tags" diff --git a/mealie/db/models/recipe/tool.py b/mealie/db/models/recipe/tool.py index 06f97c6e2242..fd61787dc312 100644 --- a/mealie/db/models/recipe/tool.py +++ b/mealie/db/models/recipe/tool.py @@ -12,6 +12,13 @@ recipes_to_tools = Table( Column("tool_id", GUID, ForeignKey("tools.id")), ) +cookbooks_to_tools = Table( + "cookbooks_to_tools", + SqlAlchemyBase.metadata, + Column("cookbook_id", GUID, ForeignKey("cookbooks.id")), + Column("tool_id", GUID, ForeignKey("tools.id")), +) + class Tool(SqlAlchemyBase, BaseMixins): __tablename__ = "tools" diff --git a/mealie/repos/repository_recipes.py b/mealie/repos/repository_recipes.py index a14c345d9733..3986b818e168 100644 --- a/mealie/repos/repository_recipes.py +++ b/mealie/repos/repository_recipes.py @@ -13,8 +13,10 @@ from mealie.db.models.recipe.ingredient import RecipeIngredient 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.recipe import Recipe -from mealie.schema.recipe.recipe import RecipeCategory, RecipeSummary, RecipeTag +from mealie.schema.recipe.recipe import RecipeCategory, RecipeSummary, RecipeTag, RecipeTool +from mealie.schema.recipe.recipe_category import CategoryBase, TagBase from .repository_generic import RepositoryGeneric @@ -123,6 +125,40 @@ class RepositoryRecipes(RepositoryGeneric[Recipe, RecipeModel]): .all() ] + def _category_tag_filters( + self, + categories: list[CategoryBase] | None = None, + tags: list[TagBase] | None = None, + tools: list[RecipeTool] | None = None, + ) -> list: + fltr = [ + RecipeModel.group_id == self.group_id, + ] + + if categories: + cat_ids = [x.id for x in categories] + fltr.extend(RecipeModel.recipe_category.any(Category.id.is_(cat_id)) for cat_id in cat_ids) + + if tags: + tag_ids = [x.id for x in tags] + fltr.extend(RecipeModel.tags.any(Tag.id.is_(tag_id)) for tag_id in tag_ids) # type:ignore + + if tools: + tool_ids = [x.id for x in tools] + fltr.extend(RecipeModel.tools.any(Tool.id.is_(tool_id)) for tool_id in tool_ids) + + return fltr + + def by_category_and_tags( + self, + categories: list[CategoryBase] | None = None, + tags: list[TagBase] | None = None, + tools: list[RecipeTool] | None = None, + ) -> list[Recipe]: + fltr = self._category_tag_filters(categories, tags, tools) + + return [self.schema.from_orm(x) for x in self.session.query(RecipeModel).filter(*fltr).all()] + def get_random_by_categories_and_tags( self, categories: list[RecipeCategory], tags: list[RecipeTag] ) -> list[Recipe]: @@ -135,17 +171,7 @@ class RepositoryRecipes(RepositoryGeneric[Recipe, RecipeModel]): # See Also: # - https://stackoverflow.com/questions/60805/getting-random-row-through-sqlalchemy - filters = [ - RecipeModel.group_id == self.group_id, - ] - - if categories: - cat_ids = [x.id for x in categories] - filters.extend(RecipeModel.recipe_category.any(Category.id.is_(cat_id)) for cat_id in cat_ids) - - if tags: - tag_ids = [x.id for x in tags] - filters.extend(RecipeModel.tags.any(Tag.id.is_(tag_id)) for tag_id in tag_ids) + filters = self._category_tag_filters(categories, tags) # type: ignore return [ self.schema.from_orm(x) diff --git a/mealie/routes/groups/controller_cookbooks.py b/mealie/routes/groups/controller_cookbooks.py index b5ed2e839edc..01ccf20f3d26 100644 --- a/mealie/routes/groups/controller_cookbooks.py +++ b/mealie/routes/groups/controller_cookbooks.py @@ -8,15 +8,10 @@ from mealie.routes._base import BaseUserController, controller from mealie.routes._base.mixins import CrudMixins from mealie.schema import mapper from mealie.schema.cookbook import CreateCookBook, ReadCookBook, RecipeCookBook, SaveCookBook, UpdateCookBook -from mealie.schema.recipe.recipe_category import RecipeCategoryResponse router = APIRouter(prefix="/groups/cookbooks", tags=["Groups: Cookbooks"]) -class CookBookRecipeResponse(RecipeCookBook): - categories: list[RecipeCategoryResponse] - - @controller(router) class GroupCookbookController(BaseUserController): @cached_property @@ -37,13 +32,13 @@ class GroupCookbookController(BaseUserController): self.registered_exceptions, ) - @router.get("", response_model=list[RecipeCookBook]) + @router.get("", response_model=list[ReadCookBook]) def get_all(self): items = self.repo.get_all() items.sort(key=lambda x: x.position) return items - @router.post("", response_model=RecipeCookBook, status_code=201) + @router.post("", response_model=ReadCookBook, status_code=201) def create_one(self, data: CreateCookBook): data = mapper.cast(data, SaveCookBook, group_id=self.group_id) return self.mixins.create_one(data) @@ -58,20 +53,25 @@ class GroupCookbookController(BaseUserController): return updated - @router.get("/{item_id}", response_model=CookBookRecipeResponse) + @router.get("/{item_id}", response_model=RecipeCookBook) def get_one(self, item_id: UUID4 | str): match_attr = "slug" if isinstance(item_id, str) else "id" - book = self.repo.get_one(item_id, match_attr, override_schema=CookBookRecipeResponse) + cookbook = self.repo.get_one(item_id, match_attr) - if book is None: + if cookbook is None: raise HTTPException(status_code=404) - return book + return cookbook.cast( + RecipeCookBook, + recipes=self.repos.recipes.by_group(self.group_id).by_category_and_tags( + cookbook.categories, cookbook.tags, cookbook.tools + ), + ) - @router.put("/{item_id}", response_model=RecipeCookBook) + @router.put("/{item_id}", response_model=ReadCookBook) def update_one(self, item_id: str, data: CreateCookBook): - return self.mixins.update_one(data, item_id) + return self.mixins.update_one(data, item_id) # type: ignore - @router.delete("/{item_id}", response_model=RecipeCookBook) + @router.delete("/{item_id}", response_model=ReadCookBook) def delete_one(self, item_id: str): return self.mixins.delete_one(item_id) diff --git a/mealie/schema/cookbook/cookbook.py b/mealie/schema/cookbook/cookbook.py index 1eb80f818332..5089fb6a0f8c 100644 --- a/mealie/schema/cookbook/cookbook.py +++ b/mealie/schema/cookbook/cookbook.py @@ -2,19 +2,23 @@ from pydantic import UUID4, validator from slugify import slugify from mealie.schema._mealie import MealieModel +from mealie.schema.recipe.recipe import RecipeSummary, RecipeTool -from ..recipe.recipe_category import CategoryBase, RecipeCategoryResponse +from ..recipe.recipe_category import CategoryBase, TagBase class CreateCookBook(MealieModel): name: str description: str = "" - slug: str = None + slug: str | None = None position: int = 1 + public: bool = False categories: list[CategoryBase] = [] + tags: list[TagBase] = [] + tools: list[RecipeTool] = [] @validator("slug", always=True, pre=True) - def validate_slug(slug: str, values): + def validate_slug(slug: str, values): # type: ignore name: str = values["name"] calc_slug: str = slugify(name) @@ -42,7 +46,7 @@ class ReadCookBook(UpdateCookBook): class RecipeCookBook(ReadCookBook): group_id: UUID4 - categories: list[RecipeCategoryResponse] + recipes: list[RecipeSummary] class Config: orm_mode = True diff --git a/tests/integration_tests/user_group_tests/test_group_cookbooks.py b/tests/integration_tests/user_group_tests/test_group_cookbooks.py index 78146af80f21..53019c3df790 100644 --- a/tests/integration_tests/user_group_tests/test_group_cookbooks.py +++ b/tests/integration_tests/user_group_tests/test_group_cookbooks.py @@ -9,7 +9,6 @@ from pydantic import UUID4 from mealie.repos.repository_factory import AllRepositories from mealie.schema.cookbook.cookbook import ReadCookBook, SaveCookBook from tests import utils -from tests.utils.assertion_helpers import assert_ignore_keys from tests.utils.factories import random_string from tests.utils.fixture_schemas import TestUser @@ -69,7 +68,13 @@ def test_read_cookbook(api_client: TestClient, unique_user: TestUser, cookbooks: sample = random.choice(cookbooks) response = api_client.get(Routes.item(sample.id), headers=unique_user.token) assert response.status_code == 200 - assert_ignore_keys(response.json(), sample.data) + + page_data = response.json() + + assert page_data["id"] == str(sample.id) + assert page_data["slug"] == sample.slug + assert page_data["name"] == sample.name + assert page_data["groupId"] == str(unique_user.group_id) def test_update_cookbook(api_client: TestClient, unique_user: TestUser, cookbooks: list[TestCookbook]): diff --git a/tests/unit_tests/services_tests/backup_v2_tests/test_alchemy_exporter.py b/tests/unit_tests/services_tests/backup_v2_tests/test_alchemy_exporter.py index 8b0e5e6f0111..484a31e2750d 100644 --- a/tests/unit_tests/services_tests/backup_v2_tests/test_alchemy_exporter.py +++ b/tests/unit_tests/services_tests/backup_v2_tests/test_alchemy_exporter.py @@ -4,7 +4,7 @@ from mealie.core.config import get_app_settings from mealie.services.backups_v2.alchemy_exporter import AlchemyExporter ALEMBIC_VERSIONS = [ - {"version_num": "f1a2dbee5fe9"}, + {"version_num": "59eb59135381"}, ]