Bulk assign

This commit is contained in:
hay-kot 2021-04-26 16:09:43 -08:00
commit b9b0a67409
10 changed files with 91 additions and 33 deletions

View file

@ -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

View file

@ -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) {

View file

@ -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");

View file

@ -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 }}

View file

@ -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();

View file

@ -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

View file

@ -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()

View file

@ -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,

View file

@ -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,

View file

@ -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)