mirror of
https://github.com/hay-kot/mealie.git
synced 2025-08-22 06:23:34 -07:00
Bulk assign
This commit is contained in:
parent
f4485827d8
commit
b9b0a67409
10 changed files with 91 additions and 33 deletions
|
@ -19,6 +19,11 @@
|
||||||
## Features and Improvements
|
## Features and Improvements
|
||||||
|
|
||||||
### General
|
### General
|
||||||
|
- New Toolbox Page!
|
||||||
|
- Bulk assign categories and tags by keyword search
|
||||||
|
- Title case all Categories or Tags with 1 click
|
||||||
|
- Create/Rename/Delete Operations for Tags/Categories
|
||||||
|
- Remove Unused Categories or Tags with 1 click
|
||||||
- More localization
|
- More localization
|
||||||
- Start date for Week is now selectable
|
- Start date for Week is now selectable
|
||||||
- Languages are now managed through Crowdin
|
- Languages are now managed through Crowdin
|
||||||
|
|
|
@ -39,6 +39,16 @@ const apiReq = {
|
||||||
processResponse(response);
|
processResponse(response);
|
||||||
return response;
|
return response;
|
||||||
},
|
},
|
||||||
|
patch: async function(url, data) {
|
||||||
|
let response = await axios.patch(url, data).catch(function(error) {
|
||||||
|
if (error.response) {
|
||||||
|
processResponse(error.response);
|
||||||
|
return response;
|
||||||
|
} else return;
|
||||||
|
});
|
||||||
|
processResponse(response);
|
||||||
|
return response;
|
||||||
|
},
|
||||||
|
|
||||||
get: async function(url, data) {
|
get: async function(url, data) {
|
||||||
let response = await axios.get(url, data).catch(function(error) {
|
let response = await axios.get(url, data).catch(function(error) {
|
||||||
|
|
|
@ -71,6 +71,12 @@ export const recipeAPI = {
|
||||||
return response.data;
|
return response.data;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
async patch(data) {
|
||||||
|
let response = await apiReq.patch(recipeURLs.update(data.slug), data);
|
||||||
|
store.dispatch("requestRecentRecipes");
|
||||||
|
return response.data;
|
||||||
|
},
|
||||||
|
|
||||||
async delete(recipeSlug) {
|
async delete(recipeSlug) {
|
||||||
await apiReq.delete(recipeURLs.delete(recipeSlug));
|
await apiReq.delete(recipeURLs.delete(recipeSlug));
|
||||||
store.dispatch("requestRecentRecipes");
|
store.dispatch("requestRecentRecipes");
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
:width="modalWidth + 'px'"
|
:width="modalWidth + 'px'"
|
||||||
:content-class="top ? 'top-dialog' : undefined"
|
:content-class="top ? 'top-dialog' : undefined"
|
||||||
>
|
>
|
||||||
<v-card class="pb-2" :loading="loading">
|
<v-card class="pb-10" :loading="loading" height="100%">
|
||||||
<v-app-bar dark :color="color" class="mt-n1 mb-2">
|
<v-app-bar dark :color="color" class="mt-n1 mb-2">
|
||||||
<v-icon large left v-if="!loading">
|
<v-icon large left v-if="!loading">
|
||||||
{{ titleIcon }}
|
{{ titleIcon }}
|
||||||
|
|
|
@ -62,6 +62,7 @@
|
||||||
import CardSection from "@/components/UI/CardSection";
|
import CardSection from "@/components/UI/CardSection";
|
||||||
import CategoryTagSelector from "@/components/FormHelpers/CategoryTagSelector";
|
import CategoryTagSelector from "@/components/FormHelpers/CategoryTagSelector";
|
||||||
import BaseDialog from "@/components/UI/Dialogs/BaseDialog";
|
import BaseDialog from "@/components/UI/Dialogs/BaseDialog";
|
||||||
|
import { api } from "@/api";
|
||||||
export default {
|
export default {
|
||||||
props: {
|
props: {
|
||||||
isTags: {
|
isTags: {
|
||||||
|
@ -113,9 +114,15 @@ export default {
|
||||||
this.tagsToAssign = [];
|
this.tagsToAssign = [];
|
||||||
},
|
},
|
||||||
assignAll() {
|
assignAll() {
|
||||||
console.log("Categories", this.catsToAssign);
|
this.loading = true;
|
||||||
console.log("Tags", this.tagsToAssign);
|
this.results.forEach(async element => {
|
||||||
console.log("results", this.results);
|
element.recipeCategory = element.recipeCategory.concat(
|
||||||
|
this.catsToAssign
|
||||||
|
);
|
||||||
|
element.tags = element.tags.concat(this.tagsToAssign);
|
||||||
|
await api.recipes.patch(element);
|
||||||
|
});
|
||||||
|
this.loading = false;
|
||||||
},
|
},
|
||||||
closeDialog() {
|
closeDialog() {
|
||||||
this.$refs.assignDialog.close();
|
this.$refs.assignDialog.close();
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
from logging import getLogger
|
||||||
|
|
||||||
from mealie.db.db_base import BaseDocument
|
from mealie.db.db_base import BaseDocument
|
||||||
from mealie.db.models.group import Group
|
from mealie.db.models.group import Group
|
||||||
from mealie.db.models.mealplan import MealPlanModel
|
from mealie.db.models.mealplan import MealPlanModel
|
||||||
|
@ -16,12 +18,13 @@ from mealie.schema.theme import SiteTheme
|
||||||
from mealie.schema.user import GroupInDB, UserInDB
|
from mealie.schema.user import GroupInDB, UserInDB
|
||||||
from sqlalchemy.orm.session import Session
|
from sqlalchemy.orm.session import Session
|
||||||
|
|
||||||
|
logger = getLogger()
|
||||||
|
|
||||||
|
|
||||||
class _Recipes(BaseDocument):
|
class _Recipes(BaseDocument):
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
self.primary_key = "slug"
|
self.primary_key = "slug"
|
||||||
self.sql_model: RecipeModel = RecipeModel
|
self.sql_model: RecipeModel = RecipeModel
|
||||||
self.orm_mode = True
|
|
||||||
self.schema: Recipe = Recipe
|
self.schema: Recipe = Recipe
|
||||||
|
|
||||||
def update_image(self, session: Session, slug: str, extension: str = None) -> str:
|
def update_image(self, session: Session, slug: str, extension: str = None) -> str:
|
||||||
|
@ -36,23 +39,26 @@ class _Categories(BaseDocument):
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
self.primary_key = "slug"
|
self.primary_key = "slug"
|
||||||
self.sql_model = Category
|
self.sql_model = Category
|
||||||
self.orm_mode = True
|
|
||||||
self.schema = RecipeCategoryResponse
|
self.schema = RecipeCategoryResponse
|
||||||
|
|
||||||
|
def get_empty(self, session: Session):
|
||||||
|
return session.query(Category).filter(~Category.recipes.any()).all()
|
||||||
|
|
||||||
|
|
||||||
class _Tags(BaseDocument):
|
class _Tags(BaseDocument):
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
self.primary_key = "slug"
|
self.primary_key = "slug"
|
||||||
self.sql_model = Tag
|
self.sql_model = Tag
|
||||||
self.orm_mode = True
|
|
||||||
self.schema = RecipeTagResponse
|
self.schema = RecipeTagResponse
|
||||||
|
|
||||||
|
def get_empty(self, session: Session):
|
||||||
|
return session.query(Tag).filter(~Tag.recipes.any()).all()
|
||||||
|
|
||||||
|
|
||||||
class _Meals(BaseDocument):
|
class _Meals(BaseDocument):
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
self.primary_key = "uid"
|
self.primary_key = "uid"
|
||||||
self.sql_model = MealPlanModel
|
self.sql_model = MealPlanModel
|
||||||
self.orm_mode = True
|
|
||||||
self.schema = MealPlanInDB
|
self.schema = MealPlanInDB
|
||||||
|
|
||||||
|
|
||||||
|
@ -60,7 +66,6 @@ class _Settings(BaseDocument):
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
self.primary_key = "id"
|
self.primary_key = "id"
|
||||||
self.sql_model = SiteSettings
|
self.sql_model = SiteSettings
|
||||||
self.orm_mode = True
|
|
||||||
self.schema = SiteSettingsSchema
|
self.schema = SiteSettingsSchema
|
||||||
|
|
||||||
|
|
||||||
|
@ -68,7 +73,6 @@ class _Themes(BaseDocument):
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
self.primary_key = "name"
|
self.primary_key = "name"
|
||||||
self.sql_model = SiteThemeModel
|
self.sql_model = SiteThemeModel
|
||||||
self.orm_mode = True
|
|
||||||
self.schema = SiteTheme
|
self.schema = SiteTheme
|
||||||
|
|
||||||
|
|
||||||
|
@ -76,7 +80,6 @@ class _Users(BaseDocument):
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
self.primary_key = "id"
|
self.primary_key = "id"
|
||||||
self.sql_model = User
|
self.sql_model = User
|
||||||
self.orm_mode = True
|
|
||||||
self.schema = UserInDB
|
self.schema = UserInDB
|
||||||
|
|
||||||
def update_password(self, session, id, password: str):
|
def update_password(self, session, id, password: str):
|
||||||
|
@ -91,7 +94,6 @@ class _Groups(BaseDocument):
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
self.primary_key = "id"
|
self.primary_key = "id"
|
||||||
self.sql_model = Group
|
self.sql_model = Group
|
||||||
self.orm_mode = True
|
|
||||||
self.schema = GroupInDB
|
self.schema = GroupInDB
|
||||||
|
|
||||||
def get_meals(self, session: Session, match_value: str, match_key: str = "name") -> list[MealPlanInDB]:
|
def get_meals(self, session: Session, match_value: str, match_key: str = "name") -> list[MealPlanInDB]:
|
||||||
|
@ -116,7 +118,6 @@ class _SignUps(BaseDocument):
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
self.primary_key = "token"
|
self.primary_key = "token"
|
||||||
self.sql_model = SignUp
|
self.sql_model = SignUp
|
||||||
self.orm_mode = True
|
|
||||||
self.schema = SignUpOut
|
self.schema = SignUpOut
|
||||||
|
|
||||||
|
|
||||||
|
@ -124,7 +125,6 @@ class _CustomPages(BaseDocument):
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
self.primary_key = "id"
|
self.primary_key = "id"
|
||||||
self.sql_model = CustomPage
|
self.sql_model = CustomPage
|
||||||
self.orm_mode = True
|
|
||||||
self.schema = CustomPageOut
|
self.schema = CustomPageOut
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -12,7 +12,6 @@ class BaseDocument:
|
||||||
self.primary_key: str
|
self.primary_key: str
|
||||||
self.store: str
|
self.store: str
|
||||||
self.sql_model: SqlAlchemyBase
|
self.sql_model: SqlAlchemyBase
|
||||||
self.orm_mode = False
|
|
||||||
self.schema: BaseModel
|
self.schema: BaseModel
|
||||||
|
|
||||||
# TODO: Improve Get All Query Functionality
|
# TODO: Improve Get All Query Functionality
|
||||||
|
@ -138,3 +137,4 @@ class BaseDocument:
|
||||||
|
|
||||||
session.delete(result)
|
session.delete(result)
|
||||||
session.commit()
|
session.commit()
|
||||||
|
|
||||||
|
|
|
@ -18,6 +18,18 @@ async def get_all_recipe_categories(session: Session = Depends(generate_session)
|
||||||
return db.categories.get_all_limit_columns(session, ["slug", "name"])
|
return db.categories.get_all_limit_columns(session, ["slug", "name"])
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/empty")
|
||||||
|
def get_empty_categories(session: Session = Depends(generate_session)):
|
||||||
|
""" Returns a list of categories that do not contain any recipes"""
|
||||||
|
return db.categories.get_empty(session)
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/{category}", response_model=RecipeCategoryResponse)
|
||||||
|
def get_all_recipes_by_category(category: str, session: Session = Depends(generate_session)):
|
||||||
|
""" Returns a list of recipes associated with the provided category. """
|
||||||
|
return db.categories.get(session, category)
|
||||||
|
|
||||||
|
|
||||||
@router.post("")
|
@router.post("")
|
||||||
async def create_recipe_category(
|
async def create_recipe_category(
|
||||||
category: CategoryIn, session: Session = Depends(generate_session), current_user=Depends(get_current_user)
|
category: CategoryIn, session: Session = Depends(generate_session), current_user=Depends(get_current_user)
|
||||||
|
@ -27,12 +39,6 @@ async def create_recipe_category(
|
||||||
return db.categories.create(session, category.dict())
|
return db.categories.create(session, category.dict())
|
||||||
|
|
||||||
|
|
||||||
@router.get("/{category}", response_model=RecipeCategoryResponse)
|
|
||||||
def get_all_recipes_by_category(category: str, session: Session = Depends(generate_session)):
|
|
||||||
""" Returns a list of recipes associated with the provided category. """
|
|
||||||
return db.categories.get(session, category)
|
|
||||||
|
|
||||||
|
|
||||||
@router.put("/{category}", response_model=RecipeCategoryResponse)
|
@router.put("/{category}", response_model=RecipeCategoryResponse)
|
||||||
async def update_recipe_category(
|
async def update_recipe_category(
|
||||||
category: str,
|
category: str,
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
import shutil
|
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
|
|
||||||
import requests
|
|
||||||
from fastapi import APIRouter, Depends, File, Form, HTTPException
|
from fastapi import APIRouter, Depends, File, Form, HTTPException
|
||||||
from fastapi.responses import FileResponse
|
from fastapi.responses import FileResponse
|
||||||
from mealie.db.database import db
|
from mealie.db.database import db
|
||||||
|
@ -13,10 +11,7 @@ from mealie.services.image.image import IMG_OPTIONS, delete_image, read_image, r
|
||||||
from mealie.services.scraper.scraper import create_from_url
|
from mealie.services.scraper.scraper import create_from_url
|
||||||
from sqlalchemy.orm.session import Session
|
from sqlalchemy.orm.session import Session
|
||||||
|
|
||||||
router = APIRouter(
|
router = APIRouter(prefix="/api/recipes", tags=["Recipe CRUD"])
|
||||||
prefix="/api/recipes",
|
|
||||||
tags=["Recipe CRUD"],
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@router.post("/create", status_code=201, response_model=str)
|
@router.post("/create", status_code=201, response_model=str)
|
||||||
|
@ -69,6 +64,29 @@ def update_recipe(
|
||||||
return recipe.slug
|
return recipe.slug
|
||||||
|
|
||||||
|
|
||||||
|
@router.patch("/{recipe_slug}")
|
||||||
|
def update_recipe(
|
||||||
|
recipe_slug: str,
|
||||||
|
data: dict,
|
||||||
|
session: Session = Depends(generate_session),
|
||||||
|
current_user=Depends(get_current_user),
|
||||||
|
):
|
||||||
|
""" Updates a recipe by existing slug and data. """
|
||||||
|
|
||||||
|
existing_entry: Recipe = db.recipes.get(session, recipe_slug)
|
||||||
|
|
||||||
|
entry_dict = existing_entry.dict()
|
||||||
|
entry_dict.update(data)
|
||||||
|
updated_entry = Recipe(**entry_dict) # ! Surely there's a better way?
|
||||||
|
|
||||||
|
recipe: Recipe = db.recipes.update(session, recipe_slug, updated_entry.dict())
|
||||||
|
|
||||||
|
if recipe_slug != recipe.slug:
|
||||||
|
rename_image(original_slug=recipe_slug, new_slug=recipe.slug)
|
||||||
|
|
||||||
|
return recipe
|
||||||
|
|
||||||
|
|
||||||
@router.delete("/{recipe_slug}")
|
@router.delete("/{recipe_slug}")
|
||||||
def delete_recipe(
|
def delete_recipe(
|
||||||
recipe_slug: str,
|
recipe_slug: str,
|
||||||
|
|
|
@ -20,6 +20,18 @@ async def get_all_recipe_tags(session: Session = Depends(generate_session)):
|
||||||
return db.tags.get_all_limit_columns(session, ["slug", "name"])
|
return db.tags.get_all_limit_columns(session, ["slug", "name"])
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/empty")
|
||||||
|
def get_empty_tags(session: Session = Depends(generate_session)):
|
||||||
|
""" Returns a list of tags that do not contain any recipes"""
|
||||||
|
return db.tags.get_empty(session)
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/{tag}", response_model=RecipeTagResponse)
|
||||||
|
def get_all_recipes_by_tag(tag: str, session: Session = Depends(generate_session)):
|
||||||
|
""" Returns a list of recipes associated with the provided tag. """
|
||||||
|
return db.tags.get(session, tag)
|
||||||
|
|
||||||
|
|
||||||
@router.post("")
|
@router.post("")
|
||||||
async def create_recipe_tag(
|
async def create_recipe_tag(
|
||||||
tag: TagIn, session: Session = Depends(generate_session), current_user=Depends(get_current_user)
|
tag: TagIn, session: Session = Depends(generate_session), current_user=Depends(get_current_user)
|
||||||
|
@ -29,12 +41,6 @@ async def create_recipe_tag(
|
||||||
return db.tags.create(session, tag.dict())
|
return db.tags.create(session, tag.dict())
|
||||||
|
|
||||||
|
|
||||||
@router.get("/{tag}", response_model=RecipeTagResponse)
|
|
||||||
def get_all_recipes_by_tag(tag: str, session: Session = Depends(generate_session)):
|
|
||||||
""" Returns a list of recipes associated with the provided tag. """
|
|
||||||
return db.tags.get(session, tag)
|
|
||||||
|
|
||||||
|
|
||||||
@router.put("/{tag}", response_model=RecipeTagResponse)
|
@router.put("/{tag}", response_model=RecipeTagResponse)
|
||||||
async def update_recipe_tag(
|
async def update_recipe_tag(
|
||||||
tag: str, new_tag: TagIn, session: Session = Depends(generate_session), current_user=Depends(get_current_user)
|
tag: str, new_tag: TagIn, session: Session = Depends(generate_session), current_user=Depends(get_current_user)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue