mirror of
https://github.com/mealie-recipes/mealie.git
synced 2025-05-24 01:12:54 -04:00
feat: bulk recipe settings update (#1557)
* extract switches from menu component * implement bulk updater for settings * fix browser cache api calls issue * add frontend for bulk settings modifications
This commit is contained in:
parent
5cfff75dbe
commit
7adcc86d03
@ -1,11 +1,10 @@
|
|||||||
import { BaseAPI } from "../_base";
|
import { BaseAPI } from "../_base";
|
||||||
import { AssignCategories, AssignTags, DeleteRecipes, ExportRecipes } from "~/types/api-types/recipe";
|
import { AssignCategories, AssignSettings, AssignTags, DeleteRecipes, ExportRecipes } from "~/types/api-types/recipe";
|
||||||
import { GroupDataExport } from "~/types/api-types/group";
|
import { GroupDataExport } from "~/types/api-types/group";
|
||||||
|
|
||||||
// Many bulk actions return nothing
|
// Many bulk actions return nothing
|
||||||
// eslint-disable-next-line @typescript-eslint/no-empty-interface
|
// eslint-disable-next-line @typescript-eslint/no-empty-interface
|
||||||
interface BulkActionResponse {
|
interface BulkActionResponse {}
|
||||||
}
|
|
||||||
|
|
||||||
const prefix = "/api";
|
const prefix = "/api";
|
||||||
|
|
||||||
@ -15,6 +14,7 @@ const routes = {
|
|||||||
bulkCategorize: prefix + "/recipes/bulk-actions/categorize",
|
bulkCategorize: prefix + "/recipes/bulk-actions/categorize",
|
||||||
bulkTag: prefix + "/recipes/bulk-actions/tag",
|
bulkTag: prefix + "/recipes/bulk-actions/tag",
|
||||||
bulkDelete: prefix + "/recipes/bulk-actions/delete",
|
bulkDelete: prefix + "/recipes/bulk-actions/delete",
|
||||||
|
bulkSettings: prefix + "/recipes/bulk-actions/settings",
|
||||||
};
|
};
|
||||||
|
|
||||||
export class BulkActionsAPI extends BaseAPI {
|
export class BulkActionsAPI extends BaseAPI {
|
||||||
@ -26,6 +26,10 @@ export class BulkActionsAPI extends BaseAPI {
|
|||||||
return await this.requests.post<BulkActionResponse>(routes.bulkCategorize, payload);
|
return await this.requests.post<BulkActionResponse>(routes.bulkCategorize, payload);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async bulkSetSettings(payload: AssignSettings) {
|
||||||
|
return await this.requests.post<BulkActionResponse>(routes.bulkSettings, payload);
|
||||||
|
}
|
||||||
|
|
||||||
async bulkTag(payload: AssignTags) {
|
async bulkTag(payload: AssignTags) {
|
||||||
return await this.requests.post<BulkActionResponse>(routes.bulkTag, payload);
|
return await this.requests.post<BulkActionResponse>(routes.bulkTag, payload);
|
||||||
}
|
}
|
||||||
|
@ -17,17 +17,7 @@
|
|||||||
</v-card-title>
|
</v-card-title>
|
||||||
<v-divider class="mx-2"></v-divider>
|
<v-divider class="mx-2"></v-divider>
|
||||||
<v-card-text class="mt-n5 pt-6 pb-2">
|
<v-card-text class="mt-n5 pt-6 pb-2">
|
||||||
<v-switch
|
<RecipeSettingsSwitches v-model="value" :is-owner="isOwner" />
|
||||||
v-for="(itemValue, key) in value"
|
|
||||||
:key="key"
|
|
||||||
v-model="value[key]"
|
|
||||||
xs
|
|
||||||
dense
|
|
||||||
:disabled="key == 'locked' && !isOwner"
|
|
||||||
class="my-1"
|
|
||||||
:label="labels[key]"
|
|
||||||
hide-details
|
|
||||||
></v-switch>
|
|
||||||
</v-card-text>
|
</v-card-text>
|
||||||
</v-card>
|
</v-card>
|
||||||
</v-menu>
|
</v-menu>
|
||||||
@ -35,9 +25,11 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent, useContext } from "@nuxtjs/composition-api";
|
import { defineComponent } from "@nuxtjs/composition-api";
|
||||||
|
import RecipeSettingsSwitches from "./RecipeSettingsSwitches.vue";
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
|
components: { RecipeSettingsSwitches },
|
||||||
props: {
|
props: {
|
||||||
value: {
|
value: {
|
||||||
type: Object,
|
type: Object,
|
||||||
@ -48,22 +40,6 @@ export default defineComponent({
|
|||||||
required: false,
|
required: false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
setup() {
|
|
||||||
const { i18n } = useContext();
|
|
||||||
const labels = {
|
|
||||||
public: i18n.t("recipe.public-recipe"),
|
|
||||||
showNutrition: i18n.t("recipe.show-nutrition-values"),
|
|
||||||
showAssets: i18n.t("asset.show-assets"),
|
|
||||||
landscapeView: i18n.t("recipe.landscape-view-coming-soon"),
|
|
||||||
disableComments: i18n.t("recipe.disable-comments"),
|
|
||||||
disableAmount: i18n.t("recipe.disable-amount"),
|
|
||||||
locked: i18n.t("recipe.locked"),
|
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
|
||||||
labels,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
51
frontend/components/Domain/Recipe/RecipeSettingsSwitches.vue
Normal file
51
frontend/components/Domain/Recipe/RecipeSettingsSwitches.vue
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<v-switch
|
||||||
|
v-for="(_, key) in value"
|
||||||
|
:key="key"
|
||||||
|
v-model="value[key]"
|
||||||
|
xs
|
||||||
|
dense
|
||||||
|
:disabled="key == 'locked' && !isOwner"
|
||||||
|
class="my-1"
|
||||||
|
:label="labels[key]"
|
||||||
|
hide-details
|
||||||
|
></v-switch>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { defineComponent, useContext } from "@nuxtjs/composition-api";
|
||||||
|
import { RecipeSettings } from "~/types/api-types/recipe";
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
props: {
|
||||||
|
value: {
|
||||||
|
type: Object as () => RecipeSettings,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
isOwner: {
|
||||||
|
type: Boolean,
|
||||||
|
required: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
setup() {
|
||||||
|
const { i18n } = useContext();
|
||||||
|
const labels: Record<keyof RecipeSettings, string> = {
|
||||||
|
public: i18n.tc("recipe.public-recipe"),
|
||||||
|
showNutrition: i18n.tc("recipe.show-nutrition-values"),
|
||||||
|
showAssets: i18n.tc("asset.show-assets"),
|
||||||
|
landscapeView: i18n.tc("recipe.landscape-view-coming-soon"),
|
||||||
|
disableComments: i18n.tc("recipe.disable-comments"),
|
||||||
|
disableAmount: i18n.tc("recipe.disable-amount"),
|
||||||
|
locked: i18n.tc("recipe.locked"),
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
labels,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped></style>
|
@ -55,6 +55,15 @@
|
|||||||
</v-virtual-scroll>
|
</v-virtual-scroll>
|
||||||
</v-card>
|
</v-card>
|
||||||
</v-card-text>
|
</v-card-text>
|
||||||
|
<v-card-text v-else-if="dialog.mode == MODES.updateSettings" class="px-12">
|
||||||
|
<p>Settings chosen here, excluding the locked option, will be applied to all selected recipes.</p>
|
||||||
|
<div class="mx-auto">
|
||||||
|
<RecipeSettingsSwitches v-model="recipeSettings" />
|
||||||
|
</div>
|
||||||
|
<p class="text-center mb-0">
|
||||||
|
<i>{{ selected.length }} recipe(s) settings will be updated.</i>
|
||||||
|
</p>
|
||||||
|
</v-card-text>
|
||||||
</BaseDialog>
|
</BaseDialog>
|
||||||
<section>
|
<section>
|
||||||
<!-- Recipe Data Table -->
|
<!-- Recipe Data Table -->
|
||||||
@ -100,6 +109,7 @@
|
|||||||
@tag-selected="openDialog(MODES.tag)"
|
@tag-selected="openDialog(MODES.tag)"
|
||||||
@categorize-selected="openDialog(MODES.category)"
|
@categorize-selected="openDialog(MODES.category)"
|
||||||
@delete-selected="openDialog(MODES.delete)"
|
@delete-selected="openDialog(MODES.delete)"
|
||||||
|
@update-settings="openDialog(MODES.updateSettings)"
|
||||||
>
|
>
|
||||||
</BaseOverflowButton>
|
</BaseOverflowButton>
|
||||||
|
|
||||||
@ -152,20 +162,22 @@ import RecipeDataTable from "~/components/Domain/Recipe/RecipeDataTable.vue";
|
|||||||
import RecipeOrganizerSelector from "~/components/Domain/Recipe/RecipeOrganizerSelector.vue";
|
import RecipeOrganizerSelector from "~/components/Domain/Recipe/RecipeOrganizerSelector.vue";
|
||||||
import { useUserApi } from "~/composables/api";
|
import { useUserApi } from "~/composables/api";
|
||||||
import { useRecipes, allRecipes } from "~/composables/recipes";
|
import { useRecipes, allRecipes } from "~/composables/recipes";
|
||||||
import { Recipe } from "~/types/api-types/recipe";
|
import { Recipe, RecipeSettings } from "~/types/api-types/recipe";
|
||||||
import GroupExportData from "~/components/Domain/Group/GroupExportData.vue";
|
import GroupExportData from "~/components/Domain/Group/GroupExportData.vue";
|
||||||
import { GroupDataExport } from "~/types/api-types/group";
|
import { GroupDataExport } from "~/types/api-types/group";
|
||||||
import { MenuItem } from "~/components/global/BaseOverflowButton.vue";
|
import { MenuItem } from "~/components/global/BaseOverflowButton.vue";
|
||||||
|
import RecipeSettingsSwitches from "~/components/Domain/Recipe/RecipeSettingsSwitches.vue";
|
||||||
|
|
||||||
const MODES = {
|
enum MODES {
|
||||||
tag: "tag",
|
tag = "tag",
|
||||||
category: "category",
|
category = "category",
|
||||||
export: "export",
|
export = "export",
|
||||||
delete: "delete",
|
delete = "delete",
|
||||||
};
|
updateSettings = "updateSettings",
|
||||||
|
}
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
components: { RecipeDataTable, RecipeOrganizerSelector, GroupExportData },
|
components: { RecipeDataTable, RecipeOrganizerSelector, GroupExportData, RecipeSettingsSwitches },
|
||||||
scrollToTop: true,
|
scrollToTop: true,
|
||||||
setup() {
|
setup() {
|
||||||
const { getAllRecipes, refreshRecipes } = useRecipes(true, true);
|
const { getAllRecipes, refreshRecipes } = useRecipes(true, true);
|
||||||
@ -217,6 +229,11 @@ export default defineComponent({
|
|||||||
text: "Categorize",
|
text: "Categorize",
|
||||||
event: "categorize-selected",
|
event: "categorize-selected",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
icon: $globals.icons.cog,
|
||||||
|
text: "Update Settings",
|
||||||
|
event: "update-settings",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
icon: $globals.icons.delete,
|
icon: $globals.icons.delete,
|
||||||
text: "Delete",
|
text: "Delete",
|
||||||
@ -307,6 +324,29 @@ export default defineComponent({
|
|||||||
resetAll();
|
resetAll();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const recipeSettings = reactive<RecipeSettings>({
|
||||||
|
public: false,
|
||||||
|
showNutrition: false,
|
||||||
|
showAssets: false,
|
||||||
|
landscapeView: false,
|
||||||
|
disableComments: false,
|
||||||
|
disableAmount: false,
|
||||||
|
locked: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
async function updateSettings() {
|
||||||
|
loading.value = true;
|
||||||
|
|
||||||
|
const recipes = selected.value.map((x: Recipe) => x.slug ?? "");
|
||||||
|
|
||||||
|
const { response, data } = await api.bulk.bulkSetSettings({ recipes, settings: recipeSettings });
|
||||||
|
|
||||||
|
console.log(response, data);
|
||||||
|
|
||||||
|
await refreshRecipes();
|
||||||
|
resetAll();
|
||||||
|
}
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// Dialog Management
|
// Dialog Management
|
||||||
|
|
||||||
@ -322,26 +362,29 @@ export default defineComponent({
|
|||||||
icon: $globals.icons.tags,
|
icon: $globals.icons.tags,
|
||||||
});
|
});
|
||||||
|
|
||||||
function openDialog(mode: string) {
|
function openDialog(mode: MODES) {
|
||||||
const titles = {
|
const titles: Record<MODES, string> = {
|
||||||
[MODES.tag]: "Tag Recipes",
|
[MODES.tag]: "Tag Recipes",
|
||||||
[MODES.category]: "Categorize Recipes",
|
[MODES.category]: "Categorize Recipes",
|
||||||
[MODES.export]: "Export Recipes",
|
[MODES.export]: "Export Recipes",
|
||||||
[MODES.delete]: "Delete Recipes",
|
[MODES.delete]: "Delete Recipes",
|
||||||
|
[MODES.updateSettings]: "Update Settings",
|
||||||
};
|
};
|
||||||
|
|
||||||
const callbacks = {
|
const callbacks: Record<MODES, () => Promise<void>> = {
|
||||||
[MODES.tag]: tagSelected,
|
[MODES.tag]: tagSelected,
|
||||||
[MODES.category]: categorizeSelected,
|
[MODES.category]: categorizeSelected,
|
||||||
[MODES.export]: exportSelected,
|
[MODES.export]: exportSelected,
|
||||||
[MODES.delete]: deleteSelected,
|
[MODES.delete]: deleteSelected,
|
||||||
|
[MODES.updateSettings]: updateSettings,
|
||||||
};
|
};
|
||||||
|
|
||||||
const icons = {
|
const icons: Record<MODES, string> = {
|
||||||
[MODES.tag]: $globals.icons.tags,
|
[MODES.tag]: $globals.icons.tags,
|
||||||
[MODES.category]: $globals.icons.tags,
|
[MODES.category]: $globals.icons.tags,
|
||||||
[MODES.export]: $globals.icons.database,
|
[MODES.export]: $globals.icons.database,
|
||||||
[MODES.delete]: $globals.icons.delete,
|
[MODES.delete]: $globals.icons.delete,
|
||||||
|
[MODES.updateSettings]: $globals.icons.cog,
|
||||||
};
|
};
|
||||||
|
|
||||||
dialog.mode = mode;
|
dialog.mode = mode;
|
||||||
@ -352,6 +395,7 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
recipeSettings,
|
||||||
selectAll,
|
selectAll,
|
||||||
loading,
|
loading,
|
||||||
actions,
|
actions,
|
||||||
|
@ -18,6 +18,19 @@ export interface CategoryBase {
|
|||||||
id: string;
|
id: string;
|
||||||
slug: string;
|
slug: string;
|
||||||
}
|
}
|
||||||
|
export interface AssignSettings {
|
||||||
|
recipes: string[];
|
||||||
|
settings: RecipeSettings;
|
||||||
|
}
|
||||||
|
export interface RecipeSettings {
|
||||||
|
public?: boolean;
|
||||||
|
showNutrition?: boolean;
|
||||||
|
showAssets?: boolean;
|
||||||
|
landscapeView?: boolean;
|
||||||
|
disableComments?: boolean;
|
||||||
|
disableAmount?: boolean;
|
||||||
|
locked?: boolean;
|
||||||
|
}
|
||||||
export interface AssignTags {
|
export interface AssignTags {
|
||||||
recipes: string[];
|
recipes: string[];
|
||||||
tags: TagBase[];
|
tags: TagBase[];
|
||||||
@ -214,15 +227,6 @@ export interface RecipeStep {
|
|||||||
text: string;
|
text: string;
|
||||||
ingredientReferences?: IngredientReferences[];
|
ingredientReferences?: IngredientReferences[];
|
||||||
}
|
}
|
||||||
export interface RecipeSettings {
|
|
||||||
public?: boolean;
|
|
||||||
showNutrition?: boolean;
|
|
||||||
showAssets?: boolean;
|
|
||||||
landscapeView?: boolean;
|
|
||||||
disableComments?: boolean;
|
|
||||||
disableAmount?: boolean;
|
|
||||||
locked?: boolean;
|
|
||||||
}
|
|
||||||
export interface RecipeAsset {
|
export interface RecipeAsset {
|
||||||
name: string;
|
name: string;
|
||||||
icon: string;
|
icon: string;
|
||||||
|
@ -5,8 +5,6 @@
|
|||||||
/* Do not modify it by hand - just update the pydantic models and then re-run the script
|
/* Do not modify it by hand - just update the pydantic models and then re-run the script
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export type OrderDirection = "asc" | "desc";
|
|
||||||
|
|
||||||
export interface ErrorResponse {
|
export interface ErrorResponse {
|
||||||
message: string;
|
message: string;
|
||||||
error?: boolean;
|
error?: boolean;
|
||||||
@ -15,13 +13,6 @@ export interface ErrorResponse {
|
|||||||
export interface FileTokenResponse {
|
export interface FileTokenResponse {
|
||||||
fileToken: string;
|
fileToken: string;
|
||||||
}
|
}
|
||||||
export interface PaginationQuery {
|
|
||||||
page?: number;
|
|
||||||
perPage?: number;
|
|
||||||
orderBy?: string;
|
|
||||||
orderDirection?: OrderDirection & string;
|
|
||||||
queryFilter?: string;
|
|
||||||
}
|
|
||||||
export interface SuccessResponse {
|
export interface SuccessResponse {
|
||||||
message: string;
|
message: string;
|
||||||
error?: boolean;
|
error?: boolean;
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import contextlib
|
||||||
import json
|
import json
|
||||||
from collections.abc import Callable
|
from collections.abc import Callable
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
@ -31,16 +32,15 @@ class MealieCrudRoute(APIRoute):
|
|||||||
original_route_handler = super().get_route_handler()
|
original_route_handler = super().get_route_handler()
|
||||||
|
|
||||||
async def custom_route_handler(request: Request) -> Response:
|
async def custom_route_handler(request: Request) -> Response:
|
||||||
try:
|
with contextlib.suppress(JSONDecodeError):
|
||||||
response = await original_route_handler(request)
|
response = await original_route_handler(request)
|
||||||
response_body = json.loads(response.body)
|
response_body = json.loads(response.body)
|
||||||
if type(response_body) == dict:
|
if type(response_body) == dict:
|
||||||
if last_modified := response_body.get("updateAt"):
|
if last_modified := response_body.get("updateAt"):
|
||||||
response.headers["last-modified"] = last_modified
|
response.headers["last-modified"] = last_modified
|
||||||
|
|
||||||
except JSONDecodeError:
|
# Force no-cache for all responses to prevent browser from caching API calls
|
||||||
pass
|
response.headers["Cache-Control"] = "no-cache, no-store, must-revalidate"
|
||||||
|
|
||||||
return response
|
return response
|
||||||
|
|
||||||
return custom_route_handler
|
return custom_route_handler
|
||||||
|
@ -7,7 +7,13 @@ from mealie.core.dependencies.dependencies import temporary_zip_path
|
|||||||
from mealie.core.security import create_file_token
|
from mealie.core.security import create_file_token
|
||||||
from mealie.routes._base import BaseUserController, controller
|
from mealie.routes._base import BaseUserController, controller
|
||||||
from mealie.schema.group.group_exports import GroupDataExport
|
from mealie.schema.group.group_exports import GroupDataExport
|
||||||
from mealie.schema.recipe.recipe_bulk_actions import AssignCategories, AssignTags, DeleteRecipes, ExportRecipes
|
from mealie.schema.recipe.recipe_bulk_actions import (
|
||||||
|
AssignCategories,
|
||||||
|
AssignSettings,
|
||||||
|
AssignTags,
|
||||||
|
DeleteRecipes,
|
||||||
|
ExportRecipes,
|
||||||
|
)
|
||||||
from mealie.schema.response.responses import SuccessResponse
|
from mealie.schema.response.responses import SuccessResponse
|
||||||
from mealie.services.recipe.recipe_bulk_service import RecipeBulkActionsService
|
from mealie.services.recipe.recipe_bulk_service import RecipeBulkActionsService
|
||||||
|
|
||||||
@ -25,6 +31,10 @@ class RecipeBulkActionsController(BaseUserController):
|
|||||||
def bulk_tag_recipes(self, tag_data: AssignTags):
|
def bulk_tag_recipes(self, tag_data: AssignTags):
|
||||||
self.service.assign_tags(tag_data.recipes, tag_data.tags)
|
self.service.assign_tags(tag_data.recipes, tag_data.tags)
|
||||||
|
|
||||||
|
@router.post("/settings")
|
||||||
|
def bulk_settings_recipes(self, settings_data: AssignSettings):
|
||||||
|
self.service.set_settings(settings_data.recipes, settings_data.settings)
|
||||||
|
|
||||||
@router.post("/categorize")
|
@router.post("/categorize")
|
||||||
def bulk_categorize_recipes(self, assign_cats: AssignCategories):
|
def bulk_categorize_recipes(self, assign_cats: AssignCategories):
|
||||||
self.service.assign_categories(assign_cats.recipes, assign_cats.categories)
|
self.service.assign_categories(assign_cats.recipes, assign_cats.categories)
|
||||||
|
@ -2,6 +2,7 @@ import enum
|
|||||||
|
|
||||||
from mealie.schema._mealie import MealieModel
|
from mealie.schema._mealie import MealieModel
|
||||||
from mealie.schema.recipe.recipe_category import CategoryBase, TagBase
|
from mealie.schema.recipe.recipe_category import CategoryBase, TagBase
|
||||||
|
from mealie.schema.recipe.recipe_settings import RecipeSettings
|
||||||
|
|
||||||
|
|
||||||
class ExportTypes(str, enum.Enum):
|
class ExportTypes(str, enum.Enum):
|
||||||
@ -24,5 +25,9 @@ class AssignTags(ExportBase):
|
|||||||
tags: list[TagBase]
|
tags: list[TagBase]
|
||||||
|
|
||||||
|
|
||||||
|
class AssignSettings(ExportBase):
|
||||||
|
settings: RecipeSettings
|
||||||
|
|
||||||
|
|
||||||
class DeleteRecipes(ExportBase):
|
class DeleteRecipes(ExportBase):
|
||||||
pass
|
pass
|
||||||
|
@ -4,6 +4,7 @@ from mealie.repos.repository_factory import AllRepositories
|
|||||||
from mealie.schema.group.group_exports import GroupDataExport
|
from mealie.schema.group.group_exports import GroupDataExport
|
||||||
from mealie.schema.recipe import CategoryBase
|
from mealie.schema.recipe import CategoryBase
|
||||||
from mealie.schema.recipe.recipe_category import TagBase
|
from mealie.schema.recipe.recipe_category import TagBase
|
||||||
|
from mealie.schema.recipe.recipe_settings import RecipeSettings
|
||||||
from mealie.schema.user.user import GroupInDB, PrivateUser
|
from mealie.schema.user.user import GroupInDB, PrivateUser
|
||||||
from mealie.services._base_service import BaseService
|
from mealie.services._base_service import BaseService
|
||||||
from mealie.services.exporter import Exporter, RecipeExporter
|
from mealie.services.exporter import Exporter, RecipeExporter
|
||||||
@ -47,6 +48,22 @@ class RecipeBulkActionsService(BaseService):
|
|||||||
|
|
||||||
return exports_deleted
|
return exports_deleted
|
||||||
|
|
||||||
|
def set_settings(self, recipes: list[str], settings: RecipeSettings) -> None:
|
||||||
|
for slug in recipes:
|
||||||
|
recipe = self.repos.recipes.get_one(slug)
|
||||||
|
|
||||||
|
if recipe is None:
|
||||||
|
self.logger.error(f"Failed to set settings for recipe {slug}, no recipe found")
|
||||||
|
|
||||||
|
settings.locked = recipe.settings.locked
|
||||||
|
recipe.settings = settings
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.repos.recipes.update(slug, recipe)
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.error(f"Failed to set settings for recipe {slug}")
|
||||||
|
self.logger.error(e)
|
||||||
|
|
||||||
def assign_tags(self, recipes: list[str], tags: list[TagBase]) -> None:
|
def assign_tags(self, recipes: list[str], tags: list[TagBase]) -> None:
|
||||||
for slug in recipes:
|
for slug in recipes:
|
||||||
recipe = self.repos.recipes.get_one(slug)
|
recipe = self.repos.recipes.get_one(slug)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user