mirror of
https://github.com/mealie-recipes/mealie.git
synced 2025-07-09 03:04:54 -04:00
feat: support require_all property for cookbooks (#1130)
* add direction prop for icon position * add support for require_all properties on cookbook * update type annotations * add and - or filter support * update cookbook API * generate types * implement editor for additional options * update version number
This commit is contained in:
parent
c988de1921
commit
10784b6e24
@ -0,0 +1,45 @@
|
|||||||
|
"""add require_all for cookbook filters
|
||||||
|
|
||||||
|
Revision ID: 09dfc897ad62
|
||||||
|
Revises: 59eb59135381
|
||||||
|
Create Date: 2022-04-03 10:48:51.379968
|
||||||
|
|
||||||
|
"""
|
||||||
|
import sqlalchemy as sa
|
||||||
|
|
||||||
|
import mealie.db.migration_types # noqa: F401
|
||||||
|
from alembic import op
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = "09dfc897ad62"
|
||||||
|
down_revision = "59eb59135381"
|
||||||
|
branch_labels = None
|
||||||
|
depends_on = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.add_column("cookbooks", sa.Column("require_all_categories", sa.Boolean(), nullable=True))
|
||||||
|
op.add_column("cookbooks", sa.Column("require_all_tags", sa.Boolean(), nullable=True))
|
||||||
|
op.add_column("cookbooks", sa.Column("require_all_tools", sa.Boolean(), nullable=True))
|
||||||
|
|
||||||
|
# Set Defaults for Existing Cookbooks
|
||||||
|
op.execute(
|
||||||
|
"""
|
||||||
|
UPDATE cookbooks
|
||||||
|
SET require_all_categories = TRUE,
|
||||||
|
require_all_tags = TRUE,
|
||||||
|
require_all_tools = TRUE
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
# ### end Alembic commands ###
|
||||||
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.drop_column("cookbooks", "require_all_tools")
|
||||||
|
op.drop_column("cookbooks", "require_all_tags")
|
||||||
|
op.drop_column("cookbooks", "require_all_categories")
|
||||||
|
# ### end Alembic commands ###
|
@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
<v-menu top offset-y left open-on-hover>
|
<v-menu top offset-y :right="right" :left="!right" open-on-hover>
|
||||||
<template #activator="{ on, attrs }">
|
<template #activator="{ on, attrs }">
|
||||||
<v-btn :small="small" icon v-bind="attrs" v-on="on" @click.stop>
|
<v-btn :small="small" icon v-bind="attrs" v-on="on" @click.stop>
|
||||||
<v-icon :small="small"> {{ $globals.icons.help }} </v-icon>
|
<v-icon :small="small"> {{ $globals.icons.help }} </v-icon>
|
||||||
@ -24,6 +24,10 @@ export default defineComponent({
|
|||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
|
right: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
@ -41,16 +41,35 @@
|
|||||||
:items="allCategories || []"
|
:items="allCategories || []"
|
||||||
selector-type="category"
|
selector-type="category"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<RecipeOrganizerSelector v-model="cookbooks[index].tags" :items="allTags || []" selector-type="tag" />
|
<RecipeOrganizerSelector v-model="cookbooks[index].tags" :items="allTags || []" selector-type="tag" />
|
||||||
<RecipeOrganizerSelector v-model="cookbooks[index].tools" :items="tools || []" selector-type="tool" />
|
<RecipeOrganizerSelector v-model="cookbooks[index].tools" :items="tools || []" selector-type="tool" />
|
||||||
<v-switch v-model="cookbooks[index].public">
|
<v-switch v-model="cookbooks[index].public" hide-details single-line>
|
||||||
<template #label>
|
<template #label>
|
||||||
Public Cookbook
|
Public Cookbook
|
||||||
<HelpIcon class="ml-4">
|
<HelpIcon small right class="ml-2">
|
||||||
Public Cookbooks can be shared with non-mealie users and will be displayed on your groups page.
|
Public Cookbooks can be shared with non-mealie users and will be displayed on your groups page.
|
||||||
</HelpIcon>
|
</HelpIcon>
|
||||||
</template>
|
</template>
|
||||||
</v-switch>
|
</v-switch>
|
||||||
|
<div class="mt-4">
|
||||||
|
<h3 class="text-subtitle-1 d-flex align-center mb-0 pb-0">
|
||||||
|
Filter Options
|
||||||
|
<HelpIcon right small class="ml-2">
|
||||||
|
When require all is selected the cookbook will only include recipes that have all of the items
|
||||||
|
selected. This applies to each subset of selectors and not a cross section of the selected items.
|
||||||
|
</HelpIcon>
|
||||||
|
</h3>
|
||||||
|
<v-switch v-model="cookbooks[index].requireAllCategories" class="mt-0" hide-details single-line>
|
||||||
|
<template #label> Require All Categories </template>
|
||||||
|
</v-switch>
|
||||||
|
<v-switch v-model="cookbooks[index].requireAllTags" hide-details single-line>
|
||||||
|
<template #label> Require All Tags </template>
|
||||||
|
</v-switch>
|
||||||
|
<v-switch v-model="cookbooks[index].requireAllTools" hide-details single-line>
|
||||||
|
<template #label> Require All Tools </template>
|
||||||
|
</v-switch>
|
||||||
|
</div>
|
||||||
</v-card-text>
|
</v-card-text>
|
||||||
<v-card-actions>
|
<v-card-actions>
|
||||||
<v-spacer></v-spacer>
|
<v-spacer></v-spacer>
|
||||||
|
@ -19,6 +19,9 @@ export interface CreateCookBook {
|
|||||||
categories?: CategoryBase[];
|
categories?: CategoryBase[];
|
||||||
tags?: TagBase[];
|
tags?: TagBase[];
|
||||||
tools?: RecipeTool[];
|
tools?: RecipeTool[];
|
||||||
|
requireAllCategories?: boolean;
|
||||||
|
requireAllTags?: boolean;
|
||||||
|
requireAllTools?: boolean;
|
||||||
}
|
}
|
||||||
export interface TagBase {
|
export interface TagBase {
|
||||||
name: string;
|
name: string;
|
||||||
@ -40,6 +43,9 @@ export interface ReadCookBook {
|
|||||||
categories?: CategoryBase[];
|
categories?: CategoryBase[];
|
||||||
tags?: TagBase[];
|
tags?: TagBase[];
|
||||||
tools?: RecipeTool[];
|
tools?: RecipeTool[];
|
||||||
|
requireAllCategories?: boolean;
|
||||||
|
requireAllTags?: boolean;
|
||||||
|
requireAllTools?: boolean;
|
||||||
groupId: string;
|
groupId: string;
|
||||||
id: string;
|
id: string;
|
||||||
}
|
}
|
||||||
@ -52,6 +58,9 @@ export interface RecipeCookBook {
|
|||||||
categories?: CategoryBase[];
|
categories?: CategoryBase[];
|
||||||
tags?: TagBase[];
|
tags?: TagBase[];
|
||||||
tools?: RecipeTool[];
|
tools?: RecipeTool[];
|
||||||
|
requireAllCategories?: boolean;
|
||||||
|
requireAllTags?: boolean;
|
||||||
|
requireAllTools?: boolean;
|
||||||
groupId: string;
|
groupId: string;
|
||||||
id: string;
|
id: string;
|
||||||
recipes: RecipeSummary[];
|
recipes: RecipeSummary[];
|
||||||
@ -138,6 +147,9 @@ export interface SaveCookBook {
|
|||||||
categories?: CategoryBase[];
|
categories?: CategoryBase[];
|
||||||
tags?: TagBase[];
|
tags?: TagBase[];
|
||||||
tools?: RecipeTool[];
|
tools?: RecipeTool[];
|
||||||
|
requireAllCategories?: boolean;
|
||||||
|
requireAllTags?: boolean;
|
||||||
|
requireAllTools?: boolean;
|
||||||
groupId: string;
|
groupId: string;
|
||||||
}
|
}
|
||||||
export interface UpdateCookBook {
|
export interface UpdateCookBook {
|
||||||
@ -149,6 +161,9 @@ export interface UpdateCookBook {
|
|||||||
categories?: CategoryBase[];
|
categories?: CategoryBase[];
|
||||||
tags?: TagBase[];
|
tags?: TagBase[];
|
||||||
tools?: RecipeTool[];
|
tools?: RecipeTool[];
|
||||||
|
requireAllCategories?: boolean;
|
||||||
|
requireAllTags?: boolean;
|
||||||
|
requireAllTools?: boolean;
|
||||||
groupId: string;
|
groupId: string;
|
||||||
id: string;
|
id: string;
|
||||||
}
|
}
|
||||||
|
@ -229,7 +229,7 @@ export interface RecipeCommentOut {
|
|||||||
user: UserBase;
|
user: UserBase;
|
||||||
}
|
}
|
||||||
export interface UserBase {
|
export interface UserBase {
|
||||||
id: number;
|
id: string;
|
||||||
username?: string;
|
username?: string;
|
||||||
admin: boolean;
|
admin: boolean;
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
from collections.abc import Generator
|
||||||
|
|
||||||
import sqlalchemy as sa
|
import sqlalchemy as sa
|
||||||
from sqlalchemy.orm import sessionmaker
|
from sqlalchemy.orm import sessionmaker
|
||||||
from sqlalchemy.orm.session import Session
|
from sqlalchemy.orm.session import Session
|
||||||
@ -32,7 +34,7 @@ def create_session() -> Session:
|
|||||||
return SessionLocal()
|
return SessionLocal()
|
||||||
|
|
||||||
|
|
||||||
def generate_session() -> Session:
|
def generate_session() -> Generator[Session, None, None]:
|
||||||
global SessionLocal
|
global SessionLocal
|
||||||
db = SessionLocal()
|
db = SessionLocal()
|
||||||
try:
|
try:
|
||||||
|
@ -21,8 +21,13 @@ class CookBook(SqlAlchemyBase, BaseMixins):
|
|||||||
public = Column(Boolean, default=False)
|
public = Column(Boolean, default=False)
|
||||||
|
|
||||||
categories = orm.relationship(Category, secondary=cookbooks_to_categories, single_parent=True)
|
categories = orm.relationship(Category, secondary=cookbooks_to_categories, single_parent=True)
|
||||||
|
require_all_categories = Column(Boolean, default=True)
|
||||||
|
|
||||||
tags = orm.relationship(Tag, secondary=cookbooks_to_tags, single_parent=True)
|
tags = orm.relationship(Tag, secondary=cookbooks_to_tags, single_parent=True)
|
||||||
|
require_all_tags = Column(Boolean, default=True)
|
||||||
|
|
||||||
tools = orm.relationship(Tool, secondary=cookbooks_to_tools, single_parent=True)
|
tools = orm.relationship(Tool, secondary=cookbooks_to_tools, single_parent=True)
|
||||||
|
require_all_tools = Column(Boolean, default=True)
|
||||||
|
|
||||||
@auto_init()
|
@auto_init()
|
||||||
def __init__(self, **_) -> None:
|
def __init__(self, **_) -> None:
|
||||||
|
@ -20,7 +20,7 @@ from .note import Note
|
|||||||
from .nutrition import Nutrition
|
from .nutrition import Nutrition
|
||||||
from .settings import RecipeSettings
|
from .settings import RecipeSettings
|
||||||
from .shared import RecipeShareTokenModel
|
from .shared import RecipeShareTokenModel
|
||||||
from .tag import Tag, recipes_to_tags
|
from .tag import recipes_to_tags
|
||||||
from .tool import recipes_to_tools
|
from .tool import recipes_to_tools
|
||||||
|
|
||||||
|
|
||||||
@ -99,7 +99,7 @@ class RecipeModel(SqlAlchemyBase, BaseMixins):
|
|||||||
|
|
||||||
# Mealie Specific
|
# Mealie Specific
|
||||||
settings = orm.relationship("RecipeSettings", uselist=False, cascade="all, delete-orphan")
|
settings = orm.relationship("RecipeSettings", uselist=False, cascade="all, delete-orphan")
|
||||||
tags: list[Tag] = orm.relationship("Tag", secondary=recipes_to_tags, back_populates="recipes")
|
tags = orm.relationship("Tag", secondary=recipes_to_tags, back_populates="recipes")
|
||||||
notes: list[Note] = orm.relationship("Note", cascade="all, delete-orphan")
|
notes: list[Note] = orm.relationship("Note", cascade="all, delete-orphan")
|
||||||
rating = sa.Column(sa.Integer)
|
rating = sa.Column(sa.Integer)
|
||||||
org_url = sa.Column(sa.String)
|
org_url = sa.Column(sa.String)
|
||||||
|
@ -130,6 +130,9 @@ class RepositoryRecipes(RepositoryGeneric[Recipe, RecipeModel]):
|
|||||||
categories: list[CategoryBase] | None = None,
|
categories: list[CategoryBase] | None = None,
|
||||||
tags: list[TagBase] | None = None,
|
tags: list[TagBase] | None = None,
|
||||||
tools: list[RecipeTool] | None = None,
|
tools: list[RecipeTool] | None = None,
|
||||||
|
require_all_categories: bool = True,
|
||||||
|
require_all_tags: bool = True,
|
||||||
|
require_all_tools: bool = True,
|
||||||
) -> list:
|
) -> list:
|
||||||
fltr = [
|
fltr = [
|
||||||
RecipeModel.group_id == self.group_id,
|
RecipeModel.group_id == self.group_id,
|
||||||
@ -137,15 +140,25 @@ class RepositoryRecipes(RepositoryGeneric[Recipe, RecipeModel]):
|
|||||||
|
|
||||||
if categories:
|
if categories:
|
||||||
cat_ids = [x.id for x in 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 require_all_categories:
|
||||||
|
fltr.extend(RecipeModel.recipe_category.any(Category.id.is_(cat_id)) for cat_id in cat_ids)
|
||||||
|
else:
|
||||||
|
fltr.append(RecipeModel.recipe_category.any(Category.id.in_(cat_ids)))
|
||||||
|
|
||||||
if tags:
|
if tags:
|
||||||
tag_ids = [x.id for x in 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 require_all_tags:
|
||||||
|
fltr.extend(RecipeModel.tags.any(Tag.id.is_(tag_id)) for tag_id in tag_ids)
|
||||||
|
else:
|
||||||
|
fltr.append(RecipeModel.tags.any(Tag.id.in_(tag_ids)))
|
||||||
|
|
||||||
if tools:
|
if tools:
|
||||||
tool_ids = [x.id for x in 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)
|
|
||||||
|
if require_all_tools:
|
||||||
|
fltr.extend(RecipeModel.tools.any(Tool.id.is_(tool_id)) for tool_id in tool_ids)
|
||||||
|
else:
|
||||||
|
fltr.append(RecipeModel.tools.any(Tool.id.in_(tool_ids)))
|
||||||
|
|
||||||
return fltr
|
return fltr
|
||||||
|
|
||||||
@ -154,8 +167,13 @@ class RepositoryRecipes(RepositoryGeneric[Recipe, RecipeModel]):
|
|||||||
categories: list[CategoryBase] | None = None,
|
categories: list[CategoryBase] | None = None,
|
||||||
tags: list[TagBase] | None = None,
|
tags: list[TagBase] | None = None,
|
||||||
tools: list[RecipeTool] | None = None,
|
tools: list[RecipeTool] | None = None,
|
||||||
|
require_all_categories: bool = True,
|
||||||
|
require_all_tags: bool = True,
|
||||||
|
require_all_tools: bool = True,
|
||||||
) -> list[Recipe]:
|
) -> list[Recipe]:
|
||||||
fltr = self._category_tag_filters(categories, tags, tools)
|
fltr = self._category_tag_filters(
|
||||||
|
categories, tags, tools, require_all_categories, require_all_tags, require_all_tools
|
||||||
|
)
|
||||||
|
|
||||||
return [self.schema.from_orm(x) for x in self.session.query(RecipeModel).filter(*fltr).all()]
|
return [self.schema.from_orm(x) for x in self.session.query(RecipeModel).filter(*fltr).all()]
|
||||||
|
|
||||||
|
@ -64,7 +64,12 @@ class GroupCookbookController(BaseUserController):
|
|||||||
return cookbook.cast(
|
return cookbook.cast(
|
||||||
RecipeCookBook,
|
RecipeCookBook,
|
||||||
recipes=self.repos.recipes.by_group(self.group_id).by_category_and_tags(
|
recipes=self.repos.recipes.by_group(self.group_id).by_category_and_tags(
|
||||||
cookbook.categories, cookbook.tags, cookbook.tools
|
cookbook.categories,
|
||||||
|
cookbook.tags,
|
||||||
|
cookbook.tools,
|
||||||
|
cookbook.require_all_categories,
|
||||||
|
cookbook.require_all_tags,
|
||||||
|
cookbook.require_all_tools,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -16,6 +16,9 @@ class CreateCookBook(MealieModel):
|
|||||||
categories: list[CategoryBase] = []
|
categories: list[CategoryBase] = []
|
||||||
tags: list[TagBase] = []
|
tags: list[TagBase] = []
|
||||||
tools: list[RecipeTool] = []
|
tools: list[RecipeTool] = []
|
||||||
|
require_all_categories: bool = True
|
||||||
|
require_all_tags: bool = True
|
||||||
|
require_all_tools: bool = True
|
||||||
|
|
||||||
@validator("public", always=True, pre=True)
|
@validator("public", always=True, pre=True)
|
||||||
def validate_public(public: bool | None, values: dict) -> bool: # type: ignore
|
def validate_public(public: bool | None, values: dict) -> bool: # type: ignore
|
||||||
|
@ -4,7 +4,7 @@ from mealie.core.config import get_app_settings
|
|||||||
from mealie.services.backups_v2.alchemy_exporter import AlchemyExporter
|
from mealie.services.backups_v2.alchemy_exporter import AlchemyExporter
|
||||||
|
|
||||||
ALEMBIC_VERSIONS = [
|
ALEMBIC_VERSIONS = [
|
||||||
{"version_num": "59eb59135381"},
|
{"version_num": "09dfc897ad62"},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user