mirror of
https://github.com/hay-kot/mealie.git
synced 2025-08-22 22:43:34 -07:00
recipe databse refactor
This commit is contained in:
parent
96979ead19
commit
29d65fddc4
17 changed files with 337 additions and 204 deletions
|
@ -118,7 +118,7 @@
|
||||||
chips
|
chips
|
||||||
item-color="secondary"
|
item-color="secondary"
|
||||||
deletable-chips
|
deletable-chips
|
||||||
v-model="value.categories"
|
v-model="value.recipeCategory"
|
||||||
hide-selected
|
hide-selected
|
||||||
:items="categories"
|
:items="categories"
|
||||||
text="name"
|
text="name"
|
||||||
|
@ -359,7 +359,7 @@ export default {
|
||||||
this.value.notes.splice(index, 1);
|
this.value.notes.splice(index, 1);
|
||||||
},
|
},
|
||||||
removeCategory(index) {
|
removeCategory(index) {
|
||||||
this.value.categories.splice(index, 1);
|
this.value.recipeCategory.splice(index, 1);
|
||||||
},
|
},
|
||||||
removeTags(index) {
|
removeTags(index) {
|
||||||
this.value.tags.splice(index, 1);
|
this.value.tags.splice(index, 1);
|
||||||
|
|
|
@ -34,7 +34,7 @@
|
||||||
:description="recipeDetails.description"
|
:description="recipeDetails.description"
|
||||||
:instructions="recipeDetails.recipeInstructions"
|
:instructions="recipeDetails.recipeInstructions"
|
||||||
:tags="recipeDetails.tags"
|
:tags="recipeDetails.tags"
|
||||||
:categories="recipeDetails.categories"
|
:categories="recipeDetails.recipeCategory"
|
||||||
:notes="recipeDetails.notes"
|
:notes="recipeDetails.notes"
|
||||||
:rating="recipeDetails.rating"
|
:rating="recipeDetails.rating"
|
||||||
:yields="recipeDetails.recipeYield"
|
:yields="recipeDetails.recipeYield"
|
||||||
|
|
|
@ -2,7 +2,7 @@ from sqlalchemy.orm.session import Session
|
||||||
|
|
||||||
from db.db_base import BaseDocument
|
from db.db_base import BaseDocument
|
||||||
from db.models.mealplan import MealPlanModel
|
from db.models.mealplan import MealPlanModel
|
||||||
from db.models.recipe import Category, RecipeModel, Tag
|
from db.models.recipe.recipe import Category, RecipeModel, Tag
|
||||||
from db.models.settings import SiteSettingsModel
|
from db.models.settings import SiteSettingsModel
|
||||||
from db.models.sign_up import SignUp
|
from db.models.sign_up import SignUp
|
||||||
from db.models.theme import SiteThemeModel
|
from db.models.theme import SiteThemeModel
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
from db.models.mealplan import *
|
from db.models.mealplan import *
|
||||||
from db.models.recipe import *
|
from db.models.recipe.recipe import *
|
||||||
from db.models.settings import *
|
from db.models.settings import *
|
||||||
from db.models.theme import *
|
from db.models.theme import *
|
||||||
from db.models.users import *
|
from db.models.users import *
|
||||||
|
|
19
mealie/db/models/recipe/api_extras.py
Normal file
19
mealie/db/models/recipe/api_extras.py
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
from datetime import date
|
||||||
|
|
||||||
|
import sqlalchemy as sa
|
||||||
|
from db.models.model_base import SqlAlchemyBase
|
||||||
|
|
||||||
|
|
||||||
|
class ApiExtras(SqlAlchemyBase):
|
||||||
|
__tablename__ = "api_extras"
|
||||||
|
id = sa.Column(sa.Integer, primary_key=True)
|
||||||
|
parent_id = sa.Column(sa.String, sa.ForeignKey("recipes.id"))
|
||||||
|
key_name = sa.Column(sa.String, unique=True)
|
||||||
|
value = sa.Column(sa.String)
|
||||||
|
|
||||||
|
def __init__(self, key, value) -> None:
|
||||||
|
self.key_name = key
|
||||||
|
self.value = value
|
||||||
|
|
||||||
|
def dict(self):
|
||||||
|
return {self.key_name: self.value}
|
65
mealie/db/models/recipe/category.py
Normal file
65
mealie/db/models/recipe/category.py
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
import sqlalchemy as sa
|
||||||
|
import sqlalchemy.orm as orm
|
||||||
|
from db.models.model_base import SqlAlchemyBase
|
||||||
|
from fastapi.logger import logger
|
||||||
|
from slugify import slugify
|
||||||
|
from sqlalchemy.orm import validates
|
||||||
|
|
||||||
|
recipes2categories = sa.Table(
|
||||||
|
"recipes2categories",
|
||||||
|
SqlAlchemyBase.metadata,
|
||||||
|
sa.Column("recipe_id", sa.Integer, sa.ForeignKey("recipes.id")),
|
||||||
|
sa.Column("category_slug", sa.String, sa.ForeignKey("categories.slug")),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class Category(SqlAlchemyBase):
|
||||||
|
__tablename__ = "categories"
|
||||||
|
id = sa.Column(sa.Integer, primary_key=True)
|
||||||
|
name = sa.Column(sa.String, index=True, nullable=False)
|
||||||
|
slug = sa.Column(sa.String, index=True, unique=True, nullable=False)
|
||||||
|
recipes = orm.relationship(
|
||||||
|
"RecipeModel", secondary=recipes2categories, back_populates="recipeCategory"
|
||||||
|
)
|
||||||
|
|
||||||
|
@validates("name")
|
||||||
|
def validate_name(self, key, name):
|
||||||
|
assert not name == ""
|
||||||
|
return name
|
||||||
|
|
||||||
|
def __init__(self, name) -> None:
|
||||||
|
self.name = name.strip()
|
||||||
|
self.slug = slugify(name)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def create_if_not_exist(session, name: str = None):
|
||||||
|
test_slug = slugify(name)
|
||||||
|
try:
|
||||||
|
result = session.query(Category).filter(Category.slug == test_slug).one()
|
||||||
|
if result:
|
||||||
|
logger.info("Category exists, associating recipe")
|
||||||
|
return result
|
||||||
|
else:
|
||||||
|
logger.info("Category doesn't exists, creating tag")
|
||||||
|
return Category(name=name)
|
||||||
|
except:
|
||||||
|
logger.info("Category doesn't exists, creating category")
|
||||||
|
return Category(name=name)
|
||||||
|
|
||||||
|
def to_str(self):
|
||||||
|
return self.name
|
||||||
|
|
||||||
|
def dict(self):
|
||||||
|
return {
|
||||||
|
"id": self.id,
|
||||||
|
"slug": self.slug,
|
||||||
|
"name": self.name,
|
||||||
|
"recipes": [x.dict() for x in self.recipes],
|
||||||
|
}
|
||||||
|
|
||||||
|
def dict_no_recipes(self):
|
||||||
|
return {
|
||||||
|
"id": self.id,
|
||||||
|
"slug": self.slug,
|
||||||
|
"name": self.name,
|
||||||
|
}
|
16
mealie/db/models/recipe/ingredient.py
Normal file
16
mealie/db/models/recipe/ingredient.py
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
import sqlalchemy as sa
|
||||||
|
from db.models.model_base import SqlAlchemyBase
|
||||||
|
|
||||||
|
|
||||||
|
class RecipeIngredient(SqlAlchemyBase):
|
||||||
|
__tablename__ = "recipes_ingredients"
|
||||||
|
id = sa.Column(sa.Integer, primary_key=True)
|
||||||
|
position = sa.Column(sa.Integer)
|
||||||
|
parent_id = sa.Column(sa.String, sa.ForeignKey("recipes.id"))
|
||||||
|
ingredient = sa.Column(sa.String)
|
||||||
|
|
||||||
|
def update(self, ingredient):
|
||||||
|
self.ingredient = ingredient
|
||||||
|
|
||||||
|
def to_str(self):
|
||||||
|
return self.ingredient
|
16
mealie/db/models/recipe/instruction.py
Normal file
16
mealie/db/models/recipe/instruction.py
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
import sqlalchemy as sa
|
||||||
|
from db.models.model_base import SqlAlchemyBase
|
||||||
|
|
||||||
|
|
||||||
|
class RecipeInstruction(SqlAlchemyBase):
|
||||||
|
__tablename__ = "recipe_instructions"
|
||||||
|
id = sa.Column(sa.Integer, primary_key=True)
|
||||||
|
parent_id = sa.Column(sa.String, sa.ForeignKey("recipes.id"))
|
||||||
|
position = sa.Column(sa.Integer)
|
||||||
|
type = sa.Column(sa.String, default="")
|
||||||
|
text = sa.Column(sa.String)
|
||||||
|
|
||||||
|
def dict(self):
|
||||||
|
data = {"@type": self.type, "text": self.text}
|
||||||
|
|
||||||
|
return data
|
17
mealie/db/models/recipe/note.py
Normal file
17
mealie/db/models/recipe/note.py
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
import sqlalchemy as sa
|
||||||
|
from db.models.model_base import SqlAlchemyBase
|
||||||
|
|
||||||
|
|
||||||
|
class Note(SqlAlchemyBase):
|
||||||
|
__tablename__ = "notes"
|
||||||
|
id = sa.Column(sa.Integer, primary_key=True)
|
||||||
|
parent_id = sa.Column(sa.String, sa.ForeignKey("recipes.id"))
|
||||||
|
title = sa.Column(sa.String)
|
||||||
|
text = sa.Column(sa.String)
|
||||||
|
|
||||||
|
def __init__(self, title, text) -> None:
|
||||||
|
self.title = title
|
||||||
|
self.text = text
|
||||||
|
|
||||||
|
def dict(self):
|
||||||
|
return {"title": self.title, "text": self.text}
|
40
mealie/db/models/recipe/nutrition.py
Normal file
40
mealie/db/models/recipe/nutrition.py
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
import sqlalchemy as sa
|
||||||
|
from db.models.model_base import SqlAlchemyBase
|
||||||
|
|
||||||
|
|
||||||
|
class Nutrition(SqlAlchemyBase):
|
||||||
|
__tablename__ = "recipe_nutrition"
|
||||||
|
id = sa.Column(sa.Integer, primary_key=True)
|
||||||
|
parent_id = sa.Column(sa.String, sa.ForeignKey("recipes.id"))
|
||||||
|
calories = sa.Column(sa.Integer)
|
||||||
|
fatContent = sa.Column(sa.Integer)
|
||||||
|
fiberContent = sa.Column(sa.Integer)
|
||||||
|
proteinContent = sa.Column(sa.Integer)
|
||||||
|
sodiumContent = sa.Column(sa.Integer)
|
||||||
|
sugarContent = sa.Column(sa.Integer)
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
calories=None,
|
||||||
|
fatContent=None,
|
||||||
|
fiberContent=None,
|
||||||
|
proteinContent=None,
|
||||||
|
sodiumContent=None,
|
||||||
|
sugarContent=None,
|
||||||
|
) -> None:
|
||||||
|
self.calories = calories
|
||||||
|
self.fatContent = fatContent
|
||||||
|
self.fiberContent = fiberContent
|
||||||
|
self.proteinContent = proteinContent
|
||||||
|
self.sodiumContent = sodiumContent
|
||||||
|
self.sugarContent = sugarContent
|
||||||
|
|
||||||
|
def dict(self) -> dict:
|
||||||
|
return {
|
||||||
|
"calories": self.calories,
|
||||||
|
"fatContent": self.fatContent,
|
||||||
|
"fiberContent": self.fiberContent,
|
||||||
|
"proteinContent": self.proteinContent,
|
||||||
|
"sodiumContent": self.sodiumContent,
|
||||||
|
"sugarContent": self.sugarContent,
|
||||||
|
}
|
|
@ -5,182 +5,16 @@ from typing import List
|
||||||
import sqlalchemy as sa
|
import sqlalchemy as sa
|
||||||
import sqlalchemy.orm as orm
|
import sqlalchemy.orm as orm
|
||||||
from db.models.model_base import BaseMixins, SqlAlchemyBase
|
from db.models.model_base import BaseMixins, SqlAlchemyBase
|
||||||
from slugify import slugify
|
from db.models.recipe.api_extras import ApiExtras
|
||||||
|
from db.models.recipe.category import Category, recipes2categories
|
||||||
|
from db.models.recipe.ingredient import RecipeIngredient
|
||||||
|
from db.models.recipe.instruction import RecipeInstruction
|
||||||
|
from db.models.recipe.note import Note
|
||||||
|
from db.models.recipe.nutrition import Nutrition
|
||||||
|
from db.models.recipe.tag import Tag, recipes2tags
|
||||||
|
from db.models.recipe.tool import Tool
|
||||||
from sqlalchemy.ext.orderinglist import ordering_list
|
from sqlalchemy.ext.orderinglist import ordering_list
|
||||||
from sqlalchemy.orm import validates
|
from sqlalchemy.orm import validates
|
||||||
from fastapi.logger import logger
|
|
||||||
|
|
||||||
|
|
||||||
class ApiExtras(SqlAlchemyBase):
|
|
||||||
__tablename__ = "api_extras"
|
|
||||||
id = sa.Column(sa.Integer, primary_key=True)
|
|
||||||
parent_id = sa.Column(sa.String, sa.ForeignKey("recipes.id"))
|
|
||||||
key_name = sa.Column(sa.String, unique=True)
|
|
||||||
value = sa.Column(sa.String)
|
|
||||||
|
|
||||||
def __init__(self, key, value) -> None:
|
|
||||||
self.key_name = key
|
|
||||||
self.value = value
|
|
||||||
|
|
||||||
def dict(self):
|
|
||||||
return {self.key_name: self.value}
|
|
||||||
|
|
||||||
|
|
||||||
recipes2categories = sa.Table(
|
|
||||||
"recipes2categories",
|
|
||||||
SqlAlchemyBase.metadata,
|
|
||||||
sa.Column("recipe_id", sa.Integer, sa.ForeignKey("recipes.id")),
|
|
||||||
sa.Column("category_slug", sa.String, sa.ForeignKey("categories.slug")),
|
|
||||||
)
|
|
||||||
|
|
||||||
recipes2tags = sa.Table(
|
|
||||||
"recipes2tags",
|
|
||||||
SqlAlchemyBase.metadata,
|
|
||||||
sa.Column("recipe_id", sa.Integer, sa.ForeignKey("recipes.id")),
|
|
||||||
sa.Column("tag_slug", sa.Integer, sa.ForeignKey("tags.slug")),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class Category(SqlAlchemyBase):
|
|
||||||
__tablename__ = "categories"
|
|
||||||
id = sa.Column(sa.Integer, primary_key=True)
|
|
||||||
name = sa.Column(sa.String, index=True, nullable=False)
|
|
||||||
slug = sa.Column(sa.String, index=True, unique=True, nullable=False)
|
|
||||||
recipes = orm.relationship(
|
|
||||||
"RecipeModel", secondary=recipes2categories, back_populates="categories"
|
|
||||||
)
|
|
||||||
|
|
||||||
@validates("name")
|
|
||||||
def validate_name(self, key, name):
|
|
||||||
assert not name == ""
|
|
||||||
return name
|
|
||||||
|
|
||||||
def __init__(self, name) -> None:
|
|
||||||
self.name = name.strip()
|
|
||||||
self.slug = slugify(name)
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def create_if_not_exist(session, name: str = None):
|
|
||||||
test_slug = slugify(name)
|
|
||||||
try:
|
|
||||||
result = session.query(Category).filter(Category.slug == test_slug).one()
|
|
||||||
if result:
|
|
||||||
logger.info("Category exists, associating recipe")
|
|
||||||
return result
|
|
||||||
else:
|
|
||||||
logger.info("Category doesn't exists, creating tag")
|
|
||||||
return Category(name=name)
|
|
||||||
except:
|
|
||||||
logger.info("Category doesn't exists, creating category")
|
|
||||||
return Category(name=name)
|
|
||||||
|
|
||||||
def to_str(self):
|
|
||||||
return self.name
|
|
||||||
|
|
||||||
def dict(self):
|
|
||||||
return {
|
|
||||||
"id": self.id,
|
|
||||||
"slug": self.slug,
|
|
||||||
"name": self.name,
|
|
||||||
"recipes": [x.dict() for x in self.recipes],
|
|
||||||
}
|
|
||||||
|
|
||||||
def dict_no_recipes(self):
|
|
||||||
return {
|
|
||||||
"id": self.id,
|
|
||||||
"slug": self.slug,
|
|
||||||
"name": self.name,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class Tag(SqlAlchemyBase):
|
|
||||||
__tablename__ = "tags"
|
|
||||||
id = sa.Column(sa.Integer, primary_key=True)
|
|
||||||
name = sa.Column(sa.String, index=True, nullable=False)
|
|
||||||
slug = sa.Column(sa.String, index=True, unique=True, nullable=False)
|
|
||||||
recipes = orm.relationship(
|
|
||||||
"RecipeModel", secondary=recipes2tags, back_populates="tags"
|
|
||||||
)
|
|
||||||
|
|
||||||
@validates("name")
|
|
||||||
def validate_name(self, key, name):
|
|
||||||
assert not name == ""
|
|
||||||
return name
|
|
||||||
|
|
||||||
def to_str(self):
|
|
||||||
return self.name
|
|
||||||
|
|
||||||
def __init__(self, name) -> None:
|
|
||||||
self.name = name.strip()
|
|
||||||
self.slug = slugify(self.name)
|
|
||||||
|
|
||||||
def dict(self):
|
|
||||||
return {
|
|
||||||
"id": self.id,
|
|
||||||
"slug": self.slug,
|
|
||||||
"name": self.name,
|
|
||||||
"recipes": [x.dict() for x in self.recipes],
|
|
||||||
}
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def create_if_not_exist(session, name: str = None):
|
|
||||||
test_slug = slugify(name)
|
|
||||||
try:
|
|
||||||
result = session.query(Tag).filter(Tag.slug == test_slug).first()
|
|
||||||
|
|
||||||
if result:
|
|
||||||
logger.info("Tag exists, associating recipe")
|
|
||||||
|
|
||||||
return result
|
|
||||||
else:
|
|
||||||
logger.info("Tag doesn't exists, creating tag")
|
|
||||||
return Tag(name=name)
|
|
||||||
except:
|
|
||||||
logger.info("Tag doesn't exists, creating tag")
|
|
||||||
return Tag(name=name)
|
|
||||||
|
|
||||||
|
|
||||||
class Note(SqlAlchemyBase):
|
|
||||||
__tablename__ = "notes"
|
|
||||||
id = sa.Column(sa.Integer, primary_key=True)
|
|
||||||
parent_id = sa.Column(sa.String, sa.ForeignKey("recipes.id"))
|
|
||||||
title = sa.Column(sa.String)
|
|
||||||
text = sa.Column(sa.String)
|
|
||||||
|
|
||||||
def __init__(self, title, text) -> None:
|
|
||||||
self.title = title
|
|
||||||
self.text = text
|
|
||||||
|
|
||||||
def dict(self):
|
|
||||||
return {"title": self.title, "text": self.text}
|
|
||||||
|
|
||||||
|
|
||||||
class RecipeIngredient(SqlAlchemyBase):
|
|
||||||
__tablename__ = "recipes_ingredients"
|
|
||||||
id = sa.Column(sa.Integer, primary_key=True)
|
|
||||||
position = sa.Column(sa.Integer)
|
|
||||||
parent_id = sa.Column(sa.String, sa.ForeignKey("recipes.id"))
|
|
||||||
ingredient = sa.Column(sa.String)
|
|
||||||
|
|
||||||
def update(self, ingredient):
|
|
||||||
self.ingredient = ingredient
|
|
||||||
|
|
||||||
def to_str(self):
|
|
||||||
return self.ingredient
|
|
||||||
|
|
||||||
|
|
||||||
class RecipeInstruction(SqlAlchemyBase):
|
|
||||||
__tablename__ = "recipe_instructions"
|
|
||||||
id = sa.Column(sa.Integer, primary_key=True)
|
|
||||||
parent_id = sa.Column(sa.String, sa.ForeignKey("recipes.id"))
|
|
||||||
position = sa.Column(sa.Integer)
|
|
||||||
type = sa.Column(sa.String, default="")
|
|
||||||
text = sa.Column(sa.String)
|
|
||||||
|
|
||||||
def dict(self):
|
|
||||||
data = {"@type": self.type, "text": self.text}
|
|
||||||
|
|
||||||
return data
|
|
||||||
|
|
||||||
|
|
||||||
class RecipeModel(SqlAlchemyBase, BaseMixins):
|
class RecipeModel(SqlAlchemyBase, BaseMixins):
|
||||||
|
@ -192,7 +26,20 @@ class RecipeModel(SqlAlchemyBase, BaseMixins):
|
||||||
name = sa.Column(sa.String, nullable=False)
|
name = sa.Column(sa.String, nullable=False)
|
||||||
description = sa.Column(sa.String)
|
description = sa.Column(sa.String)
|
||||||
image = sa.Column(sa.String)
|
image = sa.Column(sa.String)
|
||||||
|
totalTime = sa.Column(sa.String)
|
||||||
|
prepTime = sa.Column(sa.String)
|
||||||
|
performTime = sa.Column(sa.String)
|
||||||
|
cookTime = sa.Column(sa.String)
|
||||||
recipeYield = sa.Column(sa.String)
|
recipeYield = sa.Column(sa.String)
|
||||||
|
recipeCuisine = sa.Column(sa.String)
|
||||||
|
tool: List[Tool] = orm.relationship("Tool", cascade="all, delete")
|
||||||
|
nutrition: Nutrition = orm.relationship(
|
||||||
|
"Nutrition", uselist=False, cascade="all, delete"
|
||||||
|
)
|
||||||
|
recipeCategory: List = orm.relationship(
|
||||||
|
"Category", secondary=recipes2categories, back_populates="recipes"
|
||||||
|
)
|
||||||
|
|
||||||
recipeIngredient: List[RecipeIngredient] = orm.relationship(
|
recipeIngredient: List[RecipeIngredient] = orm.relationship(
|
||||||
"RecipeIngredient",
|
"RecipeIngredient",
|
||||||
cascade="all, delete",
|
cascade="all, delete",
|
||||||
|
@ -206,16 +53,8 @@ class RecipeModel(SqlAlchemyBase, BaseMixins):
|
||||||
collection_class=ordering_list("position"),
|
collection_class=ordering_list("position"),
|
||||||
)
|
)
|
||||||
|
|
||||||
# How to Properties
|
|
||||||
totalTime = sa.Column(sa.String)
|
|
||||||
prepTime = sa.Column(sa.String)
|
|
||||||
performTime = sa.Column(sa.String)
|
|
||||||
|
|
||||||
# Mealie Specific
|
# Mealie Specific
|
||||||
slug = sa.Column(sa.String, index=True, unique=True)
|
slug = sa.Column(sa.String, index=True, unique=True)
|
||||||
categories: List = orm.relationship(
|
|
||||||
"Category", secondary=recipes2categories, back_populates="recipes"
|
|
||||||
)
|
|
||||||
tags: List[Tag] = orm.relationship(
|
tags: List[Tag] = orm.relationship(
|
||||||
"Tag", secondary=recipes2tags, back_populates="recipes"
|
"Tag", secondary=recipes2tags, back_populates="recipes"
|
||||||
)
|
)
|
||||||
|
@ -239,11 +78,14 @@ class RecipeModel(SqlAlchemyBase, BaseMixins):
|
||||||
recipeYield: str = None,
|
recipeYield: str = None,
|
||||||
recipeIngredient: List[str] = None,
|
recipeIngredient: List[str] = None,
|
||||||
recipeInstructions: List[dict] = None,
|
recipeInstructions: List[dict] = None,
|
||||||
|
recipeCuisine: str = None,
|
||||||
totalTime: str = None,
|
totalTime: str = None,
|
||||||
prepTime: str = None,
|
prepTime: str = None,
|
||||||
|
nutrition: dict = None,
|
||||||
|
tool: list[str] = [],
|
||||||
performTime: str = None,
|
performTime: str = None,
|
||||||
slug: str = None,
|
slug: str = None,
|
||||||
categories: List[str] = None,
|
recipeCategory: List[str] = None,
|
||||||
tags: List[str] = None,
|
tags: List[str] = None,
|
||||||
dateAdded: datetime.date = None,
|
dateAdded: datetime.date = None,
|
||||||
notes: List[dict] = None,
|
notes: List[dict] = None,
|
||||||
|
@ -254,6 +96,15 @@ class RecipeModel(SqlAlchemyBase, BaseMixins):
|
||||||
self.name = name
|
self.name = name
|
||||||
self.description = description
|
self.description = description
|
||||||
self.image = image
|
self.image = image
|
||||||
|
self.recipeCuisine = recipeCuisine
|
||||||
|
|
||||||
|
if self.nutrition:
|
||||||
|
self.nutrition = Nutrition(**nutrition)
|
||||||
|
else:
|
||||||
|
self.nutrition = Nutrition()
|
||||||
|
|
||||||
|
self.tool = [Tool(tool=x) for x in tool] if tool else []
|
||||||
|
|
||||||
self.recipeYield = recipeYield
|
self.recipeYield = recipeYield
|
||||||
self.recipeIngredient = [
|
self.recipeIngredient = [
|
||||||
RecipeIngredient(ingredient=ingr) for ingr in recipeIngredient
|
RecipeIngredient(ingredient=ingr) for ingr in recipeIngredient
|
||||||
|
@ -266,15 +117,14 @@ class RecipeModel(SqlAlchemyBase, BaseMixins):
|
||||||
self.prepTime = prepTime
|
self.prepTime = prepTime
|
||||||
self.performTime = performTime
|
self.performTime = performTime
|
||||||
|
|
||||||
# Mealie Specific
|
self.recipeCategory = [
|
||||||
self.slug = slug
|
|
||||||
self.categories = [
|
|
||||||
Category.create_if_not_exist(session=session, name=cat)
|
Category.create_if_not_exist(session=session, name=cat)
|
||||||
for cat in categories
|
for cat in recipeCategory
|
||||||
]
|
]
|
||||||
|
|
||||||
|
# Mealie Specific
|
||||||
self.tags = [Tag.create_if_not_exist(session=session, name=tag) for tag in tags]
|
self.tags = [Tag.create_if_not_exist(session=session, name=tag) for tag in tags]
|
||||||
|
self.slug = slug
|
||||||
self.dateAdded = dateAdded
|
self.dateAdded = dateAdded
|
||||||
self.notes = [Note(**note) for note in notes]
|
self.notes = [Note(**note) for note in notes]
|
||||||
self.rating = rating
|
self.rating = rating
|
||||||
|
@ -290,11 +140,14 @@ class RecipeModel(SqlAlchemyBase, BaseMixins):
|
||||||
recipeYield: str = None,
|
recipeYield: str = None,
|
||||||
recipeIngredient: List[str] = None,
|
recipeIngredient: List[str] = None,
|
||||||
recipeInstructions: List[dict] = None,
|
recipeInstructions: List[dict] = None,
|
||||||
|
recipeCuisine: str = None,
|
||||||
totalTime: str = None,
|
totalTime: str = None,
|
||||||
|
tool: list[str] = [],
|
||||||
prepTime: str = None,
|
prepTime: str = None,
|
||||||
performTime: str = None,
|
performTime: str = None,
|
||||||
|
nutrition: dict = None,
|
||||||
slug: str = None,
|
slug: str = None,
|
||||||
categories: List[str] = None,
|
recipeCategory: List[str] = None,
|
||||||
tags: List[str] = None,
|
tags: List[str] = None,
|
||||||
dateAdded: datetime.date = None,
|
dateAdded: datetime.date = None,
|
||||||
notes: List[dict] = None,
|
notes: List[dict] = None,
|
||||||
|
@ -303,7 +156,7 @@ class RecipeModel(SqlAlchemyBase, BaseMixins):
|
||||||
extras: dict = None,
|
extras: dict = None,
|
||||||
):
|
):
|
||||||
"""Updated a database entry by removing nested rows and rebuilds the row through the __init__ functions"""
|
"""Updated a database entry by removing nested rows and rebuilds the row through the __init__ functions"""
|
||||||
list_of_tables = [RecipeIngredient, RecipeInstruction, ApiExtras]
|
list_of_tables = [RecipeIngredient, RecipeInstruction, ApiExtras, Tool]
|
||||||
RecipeModel._sql_remove_list(session, list_of_tables, self.id)
|
RecipeModel._sql_remove_list(session, list_of_tables, self.id)
|
||||||
|
|
||||||
self.__init__(
|
self.__init__(
|
||||||
|
@ -315,10 +168,13 @@ class RecipeModel(SqlAlchemyBase, BaseMixins):
|
||||||
recipeIngredient=recipeIngredient,
|
recipeIngredient=recipeIngredient,
|
||||||
recipeInstructions=recipeInstructions,
|
recipeInstructions=recipeInstructions,
|
||||||
totalTime=totalTime,
|
totalTime=totalTime,
|
||||||
|
recipeCuisine=recipeCuisine,
|
||||||
prepTime=prepTime,
|
prepTime=prepTime,
|
||||||
performTime=performTime,
|
performTime=performTime,
|
||||||
|
nutrition=nutrition,
|
||||||
|
tool=tool,
|
||||||
slug=slug,
|
slug=slug,
|
||||||
categories=categories,
|
recipeCategory=recipeCategory,
|
||||||
tags=tags,
|
tags=tags,
|
||||||
dateAdded=dateAdded,
|
dateAdded=dateAdded,
|
||||||
notes=notes,
|
notes=notes,
|
||||||
|
@ -333,14 +189,17 @@ class RecipeModel(SqlAlchemyBase, BaseMixins):
|
||||||
"description": self.description,
|
"description": self.description,
|
||||||
"image": self.image,
|
"image": self.image,
|
||||||
"recipeYield": self.recipeYield,
|
"recipeYield": self.recipeYield,
|
||||||
|
"recipeCuisine": self.recipeCuisine,
|
||||||
|
"recipeCategory": [x.to_str() for x in self.recipeCategory],
|
||||||
"recipeIngredient": [x.to_str() for x in self.recipeIngredient],
|
"recipeIngredient": [x.to_str() for x in self.recipeIngredient],
|
||||||
"recipeInstructions": [x.dict() for x in self.recipeInstructions],
|
"recipeInstructions": [x.dict() for x in self.recipeInstructions],
|
||||||
|
"nutrition": self.nutrition.dict(),
|
||||||
"totalTime": self.totalTime,
|
"totalTime": self.totalTime,
|
||||||
"prepTime": self.prepTime,
|
"prepTime": self.prepTime,
|
||||||
"performTime": self.performTime,
|
"performTime": self.performTime,
|
||||||
# Mealie
|
"tool": [x.str() for x in self.tool],
|
||||||
|
# Mealie Specific
|
||||||
"slug": self.slug,
|
"slug": self.slug,
|
||||||
"categories": [x.to_str() for x in self.categories],
|
|
||||||
"tags": [x.to_str() for x in self.tags],
|
"tags": [x.to_str() for x in self.tags],
|
||||||
"dateAdded": self.dateAdded,
|
"dateAdded": self.dateAdded,
|
||||||
"notes": [x.dict() for x in self.notes],
|
"notes": [x.dict() for x in self.notes],
|
60
mealie/db/models/recipe/tag.py
Normal file
60
mealie/db/models/recipe/tag.py
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
import sqlalchemy as sa
|
||||||
|
import sqlalchemy.orm as orm
|
||||||
|
from db.models.model_base import SqlAlchemyBase
|
||||||
|
from fastapi.logger import logger
|
||||||
|
from slugify import slugify
|
||||||
|
from sqlalchemy.orm import validates
|
||||||
|
|
||||||
|
recipes2tags = sa.Table(
|
||||||
|
"recipes2tags",
|
||||||
|
SqlAlchemyBase.metadata,
|
||||||
|
sa.Column("recipe_id", sa.Integer, sa.ForeignKey("recipes.id")),
|
||||||
|
sa.Column("tag_slug", sa.Integer, sa.ForeignKey("tags.slug")),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class Tag(SqlAlchemyBase):
|
||||||
|
__tablename__ = "tags"
|
||||||
|
id = sa.Column(sa.Integer, primary_key=True)
|
||||||
|
name = sa.Column(sa.String, index=True, nullable=False)
|
||||||
|
slug = sa.Column(sa.String, index=True, unique=True, nullable=False)
|
||||||
|
recipes = orm.relationship(
|
||||||
|
"RecipeModel", secondary=recipes2tags, back_populates="tags"
|
||||||
|
)
|
||||||
|
|
||||||
|
@validates("name")
|
||||||
|
def validate_name(self, key, name):
|
||||||
|
assert not name == ""
|
||||||
|
return name
|
||||||
|
|
||||||
|
def to_str(self):
|
||||||
|
return self.name
|
||||||
|
|
||||||
|
def __init__(self, name) -> None:
|
||||||
|
self.name = name.strip()
|
||||||
|
self.slug = slugify(self.name)
|
||||||
|
|
||||||
|
def dict(self):
|
||||||
|
return {
|
||||||
|
"id": self.id,
|
||||||
|
"slug": self.slug,
|
||||||
|
"name": self.name,
|
||||||
|
"recipes": [x.dict() for x in self.recipes],
|
||||||
|
}
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def create_if_not_exist(session, name: str = None):
|
||||||
|
test_slug = slugify(name)
|
||||||
|
try:
|
||||||
|
result = session.query(Tag).filter(Tag.slug == test_slug).first()
|
||||||
|
|
||||||
|
if result:
|
||||||
|
logger.info("Tag exists, associating recipe")
|
||||||
|
|
||||||
|
return result
|
||||||
|
else:
|
||||||
|
logger.info("Tag doesn't exists, creating tag")
|
||||||
|
return Tag(name=name)
|
||||||
|
except:
|
||||||
|
logger.info("Tag doesn't exists, creating tag")
|
||||||
|
return Tag(name=name)
|
15
mealie/db/models/recipe/tool.py
Normal file
15
mealie/db/models/recipe/tool.py
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
import sqlalchemy as sa
|
||||||
|
from db.models.model_base import SqlAlchemyBase
|
||||||
|
|
||||||
|
|
||||||
|
class Tool(SqlAlchemyBase):
|
||||||
|
__tablename__ = "tools"
|
||||||
|
id = sa.Column(sa.Integer, primary_key=True)
|
||||||
|
parent_id = sa.Column(sa.String, sa.ForeignKey("recipes.id"))
|
||||||
|
tool = sa.Column(sa.String)
|
||||||
|
|
||||||
|
def __init__(self, tool) -> None:
|
||||||
|
self.tool = tool
|
||||||
|
|
||||||
|
def str(self):
|
||||||
|
return self.tool
|
|
@ -14,14 +14,25 @@ class RecipeStep(BaseModel):
|
||||||
text: str
|
text: str
|
||||||
|
|
||||||
|
|
||||||
|
class Nutrition(BaseModel):
|
||||||
|
calories: Optional[int]
|
||||||
|
fatContent: Optional[int]
|
||||||
|
fiberContent: Optional[int]
|
||||||
|
proteinContent: Optional[int]
|
||||||
|
sodiumContent: Optional[int]
|
||||||
|
sugarContent: Optional[int]
|
||||||
|
|
||||||
|
|
||||||
class Recipe(BaseModel):
|
class Recipe(BaseModel):
|
||||||
# Standard Schema
|
# Standard Schema
|
||||||
name: str
|
name: str
|
||||||
description: Optional[str]
|
description: Optional[str]
|
||||||
image: Optional[Any]
|
image: Optional[Any]
|
||||||
recipeYield: Optional[str]
|
recipeYield: Optional[str]
|
||||||
|
recipeCategory: Optional[List[str]] = []
|
||||||
recipeIngredient: Optional[list]
|
recipeIngredient: Optional[list]
|
||||||
recipeInstructions: Optional[list]
|
recipeInstructions: Optional[list]
|
||||||
|
nutrition: Optional[Nutrition]
|
||||||
|
|
||||||
totalTime: Optional[str] = None
|
totalTime: Optional[str] = None
|
||||||
prepTime: Optional[str] = None
|
prepTime: Optional[str] = None
|
||||||
|
@ -29,7 +40,6 @@ class Recipe(BaseModel):
|
||||||
|
|
||||||
# Mealie Specific
|
# Mealie Specific
|
||||||
slug: Optional[str] = ""
|
slug: Optional[str] = ""
|
||||||
categories: Optional[List[str]] = []
|
|
||||||
tags: Optional[List[str]] = []
|
tags: Optional[List[str]] = []
|
||||||
dateAdded: Optional[datetime.date]
|
dateAdded: Optional[datetime.date]
|
||||||
notes: Optional[List[RecipeNote]] = []
|
notes: Optional[List[RecipeNote]] = []
|
||||||
|
@ -56,7 +66,7 @@ class Recipe(BaseModel):
|
||||||
],
|
],
|
||||||
"slug": "chicken-and-rice-with-leeks-and-salsa-verde",
|
"slug": "chicken-and-rice-with-leeks-and-salsa-verde",
|
||||||
"tags": ["favorite", "yummy!"],
|
"tags": ["favorite", "yummy!"],
|
||||||
"categories": ["Dinner", "Pasta"],
|
"recipeCategory": ["Dinner", "Pasta"],
|
||||||
"notes": [{"title": "Watch Out!", "text": "Prep the day before!"}],
|
"notes": [{"title": "Watch Out!", "text": "Prep the day before!"}],
|
||||||
"orgURL": "https://www.bonappetit.com/recipe/chicken-and-rice-with-leeks-and-salsa-verde",
|
"orgURL": "https://www.bonappetit.com/recipe/chicken-and-rice-with-leeks-and-salsa-verde",
|
||||||
"rating": 3,
|
"rating": 3,
|
||||||
|
|
|
@ -6,11 +6,11 @@ from typing import List
|
||||||
|
|
||||||
from core.config import BACKUP_DIR, IMG_DIR, TEMP_DIR
|
from core.config import BACKUP_DIR, IMG_DIR, TEMP_DIR
|
||||||
from db.database import db
|
from db.database import db
|
||||||
|
from fastapi.logger import logger
|
||||||
from schema.restore import RecipeImport, SettingsImport, ThemeImport
|
from schema.restore import RecipeImport, SettingsImport, ThemeImport
|
||||||
from schema.theme import SiteTheme
|
from schema.theme import SiteTheme
|
||||||
from services.recipe_services import Recipe
|
from services.recipe_services import Recipe
|
||||||
from sqlalchemy.orm.session import Session
|
from sqlalchemy.orm.session import Session
|
||||||
from fastapi.logger import logger
|
|
||||||
|
|
||||||
|
|
||||||
class ImportDatabase:
|
class ImportDatabase:
|
||||||
|
@ -85,6 +85,11 @@ class ImportDatabase:
|
||||||
recipe_dict = json.loads(f.read())
|
recipe_dict = json.loads(f.read())
|
||||||
recipe_dict = ImportDatabase._recipe_migration(recipe_dict)
|
recipe_dict = ImportDatabase._recipe_migration(recipe_dict)
|
||||||
try:
|
try:
|
||||||
|
if recipe_dict.get("categories", False):
|
||||||
|
recipe_dict["recipeCategory"] = recipe_dict.get("categories")
|
||||||
|
del recipe_dict["categories"]
|
||||||
|
print(recipe_dict)
|
||||||
|
|
||||||
recipe_obj = Recipe(**recipe_dict)
|
recipe_obj = Recipe(**recipe_dict)
|
||||||
recipe_obj.save_to_db(self.session)
|
recipe_obj.save_to_db(self.session)
|
||||||
import_status = RecipeImport(
|
import_status = RecipeImport(
|
||||||
|
|
|
@ -19,14 +19,26 @@ class RecipeStep(BaseModel):
|
||||||
text: str
|
text: str
|
||||||
|
|
||||||
|
|
||||||
|
class Nutrition(BaseModel):
|
||||||
|
calories: Optional[int]
|
||||||
|
fatContent: Optional[int]
|
||||||
|
fiberContent: Optional[int]
|
||||||
|
proteinContent: Optional[int]
|
||||||
|
sodiumContent: Optional[int]
|
||||||
|
sugarContent: Optional[int]
|
||||||
|
|
||||||
|
|
||||||
class Recipe(BaseModel):
|
class Recipe(BaseModel):
|
||||||
# Standard Schema
|
# Standard Schema
|
||||||
name: str
|
name: str
|
||||||
description: Optional[str]
|
description: Optional[str]
|
||||||
image: Optional[Any]
|
image: Optional[Any]
|
||||||
|
nutrition: Optional[Nutrition]
|
||||||
recipeYield: Optional[str]
|
recipeYield: Optional[str]
|
||||||
|
recipeCategory: Optional[List[str]] = []
|
||||||
recipeIngredient: Optional[list]
|
recipeIngredient: Optional[list]
|
||||||
recipeInstructions: Optional[list]
|
recipeInstructions: Optional[list]
|
||||||
|
tool: Optional[list[str]]
|
||||||
|
|
||||||
totalTime: Optional[str] = None
|
totalTime: Optional[str] = None
|
||||||
prepTime: Optional[str] = None
|
prepTime: Optional[str] = None
|
||||||
|
@ -34,7 +46,6 @@ class Recipe(BaseModel):
|
||||||
|
|
||||||
# Mealie Specific
|
# Mealie Specific
|
||||||
slug: Optional[str] = ""
|
slug: Optional[str] = ""
|
||||||
categories: Optional[List[str]] = []
|
|
||||||
tags: Optional[List[str]] = []
|
tags: Optional[List[str]] = []
|
||||||
dateAdded: Optional[datetime.date]
|
dateAdded: Optional[datetime.date]
|
||||||
notes: Optional[List[RecipeNote]] = []
|
notes: Optional[List[RecipeNote]] = []
|
||||||
|
@ -61,7 +72,7 @@ class Recipe(BaseModel):
|
||||||
],
|
],
|
||||||
"slug": "chicken-and-rice-with-leeks-and-salsa-verde",
|
"slug": "chicken-and-rice-with-leeks-and-salsa-verde",
|
||||||
"tags": ["favorite", "yummy!"],
|
"tags": ["favorite", "yummy!"],
|
||||||
"categories": ["Dinner", "Pasta"],
|
"recipeCategory": ["Dinner", "Pasta"],
|
||||||
"notes": [{"title": "Watch Out!", "text": "Prep the day before!"}],
|
"notes": [{"title": "Watch Out!", "text": "Prep the day before!"}],
|
||||||
"orgURL": "https://www.bonappetit.com/recipe/chicken-and-rice-with-leeks-and-salsa-verde",
|
"orgURL": "https://www.bonappetit.com/recipe/chicken-and-rice-with-leeks-and-salsa-verde",
|
||||||
"rating": 3,
|
"rating": 3,
|
||||||
|
|
|
@ -34,7 +34,7 @@ def api_client():
|
||||||
|
|
||||||
yield TestClient(app)
|
yield TestClient(app)
|
||||||
|
|
||||||
# SQLITE_FILE.unlink()
|
SQLITE_FILE.unlink()
|
||||||
|
|
||||||
|
|
||||||
@fixture(scope="session")
|
@fixture(scope="session")
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue