mirror of
https://github.com/mealie-recipes/mealie.git
synced 2025-12-28 21:15:26 -05:00
feature: proper multi-tenant-support (#969)(WIP)
* update naming * refactor tests to use shared structure * shorten names * add tools test case * refactor to support multi-tenant * set group_id on creation * initial refactor for multitenant tags/cats * spelling * additional test case for same valued resources * fix recipe update tests * apply indexes to foreign keys * fix performance regressions * handle unknown exception * utility decorator for function debugging * migrate recipe_id to UUID * GUID for recipes * remove unused import * move image functions into package * move utilities to packages dir * update import * linter * image image and asset routes * update assets and images to use UUIDs * fix migration base * image asset test coverage * use ids for categories and tag crud functions * refactor recipe organizer test suite to reduce duplication * add uuid serlization utility * organizer base router * slug routes testing and fixes * fix postgres error * adopt UUIDs * move tags, categories, and tools under "organizers" umbrella * update composite label * generate ts types * fix import error * update frontend types * fix type errors * fix postgres errors * fix #978 * add null check for title validation * add note in docs on multi-tenancy
This commit is contained in:
@@ -12,7 +12,12 @@ def sql_global_init(db_url: str):
|
||||
if "sqlite" in db_url:
|
||||
connect_args["check_same_thread"] = False
|
||||
|
||||
engine = sa.create_engine(db_url, echo=False, connect_args=connect_args, pool_pre_ping=True)
|
||||
engine = sa.create_engine(
|
||||
db_url,
|
||||
echo=False,
|
||||
connect_args=connect_args,
|
||||
pool_pre_ping=True,
|
||||
)
|
||||
|
||||
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
|
||||
|
||||
|
||||
@@ -58,6 +58,9 @@ class Group(SqlAlchemyBase, BaseMixins):
|
||||
# Owned Models
|
||||
ingredient_units = orm.relationship("IngredientUnitModel", **common_args)
|
||||
ingredient_foods = orm.relationship("IngredientFoodModel", **common_args)
|
||||
tools = orm.relationship("Tool", **common_args)
|
||||
tags = orm.relationship("Tag", **common_args)
|
||||
categories = orm.relationship("Category", **common_args)
|
||||
|
||||
class Config:
|
||||
exclude = {
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
from sqlalchemy import Column, Date, ForeignKey, String, orm
|
||||
from sqlalchemy.sql.sqltypes import Integer
|
||||
|
||||
from mealie.db.models.recipe.tag import Tag, plan_rules_to_tags
|
||||
|
||||
@@ -36,7 +35,7 @@ class GroupMealPlan(SqlAlchemyBase, BaseMixins):
|
||||
group_id = Column(GUID, ForeignKey("groups.id"), index=True)
|
||||
group = orm.relationship("Group", back_populates="mealplans")
|
||||
|
||||
recipe_id = Column(Integer, ForeignKey("recipes.id"))
|
||||
recipe_id = Column(GUID, ForeignKey("recipes.id"), index=True)
|
||||
recipe = orm.relationship("RecipeModel", back_populates="meal_entries", uselist=False)
|
||||
|
||||
@auto_init()
|
||||
|
||||
@@ -9,7 +9,7 @@ from .._model_utils import auto_init
|
||||
|
||||
class GroupPreferencesModel(SqlAlchemyBase, BaseMixins):
|
||||
__tablename__ = "group_preferences"
|
||||
group_id = sa.Column(GUID, sa.ForeignKey("groups.id"))
|
||||
group_id = sa.Column(GUID, sa.ForeignKey("groups.id"), nullable=False, index=True)
|
||||
group = orm.relationship("Group", back_populates="preferences")
|
||||
|
||||
private_group: bool = sa.Column(sa.Boolean, default=True)
|
||||
|
||||
@@ -38,7 +38,7 @@ class ReportModel(SqlAlchemyBase, BaseMixins):
|
||||
entries = orm.relationship(ReportEntryModel, back_populates="report", cascade="all, delete-orphan")
|
||||
|
||||
# Relationships
|
||||
group_id = Column(GUID, ForeignKey("groups.id"))
|
||||
group_id = Column(GUID, ForeignKey("groups.id"), nullable=False, index=True)
|
||||
group = orm.relationship("Group", back_populates="group_reports", single_parent=True)
|
||||
|
||||
class Config:
|
||||
|
||||
@@ -13,7 +13,7 @@ class ShoppingListItemRecipeReference(BaseMixins, SqlAlchemyBase):
|
||||
id = Column(GUID, primary_key=True, default=GUID.generate)
|
||||
|
||||
shopping_list_item_id = Column(GUID, ForeignKey("shopping_list_items.id"), primary_key=True)
|
||||
recipe_id = Column(Integer, ForeignKey("recipes.id"))
|
||||
recipe_id = Column(GUID, ForeignKey("recipes.id"), index=True)
|
||||
recipe = orm.relationship("RecipeModel", back_populates="shopping_list_item_refs")
|
||||
recipe_quantity = Column(Float, nullable=False)
|
||||
|
||||
@@ -40,10 +40,10 @@ class ShoppingListItem(SqlAlchemyBase, BaseMixins):
|
||||
is_food = Column(Boolean, default=False)
|
||||
|
||||
# Scaling Items
|
||||
unit_id = Column(Integer, ForeignKey("ingredient_units.id"))
|
||||
unit_id = Column(GUID, ForeignKey("ingredient_units.id"))
|
||||
unit = orm.relationship(IngredientUnitModel, uselist=False)
|
||||
|
||||
food_id = Column(Integer, ForeignKey("ingredient_foods.id"))
|
||||
food_id = Column(GUID, ForeignKey("ingredient_foods.id"))
|
||||
food = orm.relationship(IngredientFoodModel, uselist=False)
|
||||
|
||||
label_id = Column(GUID, ForeignKey("multi_purpose_labels.id"))
|
||||
@@ -66,7 +66,7 @@ class ShoppingListRecipeReference(BaseMixins, SqlAlchemyBase):
|
||||
|
||||
shopping_list_id = Column(GUID, ForeignKey("shopping_lists.id"), primary_key=True)
|
||||
|
||||
recipe_id = Column(Integer, ForeignKey("recipes.id"))
|
||||
recipe_id = Column(GUID, ForeignKey("recipes.id"), index=True)
|
||||
recipe = orm.relationship("RecipeModel", uselist=False, back_populates="shopping_list_refs")
|
||||
|
||||
recipe_quantity = Column(Float, nullable=False)
|
||||
@@ -83,7 +83,7 @@ class ShoppingList(SqlAlchemyBase, BaseMixins):
|
||||
__tablename__ = "shopping_lists"
|
||||
id = Column(GUID, primary_key=True, default=GUID.generate)
|
||||
|
||||
group_id = Column(GUID, ForeignKey("groups.id"))
|
||||
group_id = Column(GUID, ForeignKey("groups.id"), nullable=False, index=True)
|
||||
group = orm.relationship("Group", back_populates="shopping_lists")
|
||||
|
||||
name = Column(String)
|
||||
|
||||
@@ -12,7 +12,7 @@ class MultiPurposeLabel(SqlAlchemyBase, BaseMixins):
|
||||
name = Column(String(255), nullable=False)
|
||||
color = Column(String(10), nullable=False, default="")
|
||||
|
||||
group_id = Column(GUID, ForeignKey("groups.id"))
|
||||
group_id = Column(GUID, ForeignKey("groups.id"), nullable=False, index=True)
|
||||
group = orm.relationship("Group", back_populates="labels")
|
||||
|
||||
shopping_list_items = orm.relationship("ShoppingListItem", back_populates="label")
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
import sqlalchemy as sa
|
||||
|
||||
from mealie.db.models._model_base import SqlAlchemyBase
|
||||
from mealie.db.models._model_utils.guid import GUID
|
||||
|
||||
|
||||
class ApiExtras(SqlAlchemyBase):
|
||||
__tablename__ = "api_extras"
|
||||
id = sa.Column(sa.Integer, primary_key=True)
|
||||
parent_id = sa.Column(sa.Integer, sa.ForeignKey("recipes.id"))
|
||||
recipee_id = sa.Column(GUID, sa.ForeignKey("recipes.id"))
|
||||
key_name = sa.Column(sa.String)
|
||||
value = sa.Column(sa.String)
|
||||
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
import sqlalchemy as sa
|
||||
|
||||
from mealie.db.models._model_base import SqlAlchemyBase
|
||||
from mealie.db.models._model_utils.guid import GUID
|
||||
|
||||
|
||||
class RecipeAsset(SqlAlchemyBase):
|
||||
__tablename__ = "recipe_assets"
|
||||
id = sa.Column(sa.Integer, primary_key=True)
|
||||
parent_id = sa.Column(sa.Integer, sa.ForeignKey("recipes.id"))
|
||||
recipe_id = sa.Column(GUID, sa.ForeignKey("recipes.id"))
|
||||
name = sa.Column(sa.String)
|
||||
icon = sa.Column(sa.String)
|
||||
file_name = sa.Column(sa.String)
|
||||
|
||||
@@ -15,47 +15,51 @@ group2categories = sa.Table(
|
||||
"group2categories",
|
||||
SqlAlchemyBase.metadata,
|
||||
sa.Column("group_id", GUID, sa.ForeignKey("groups.id")),
|
||||
sa.Column("category_id", sa.Integer, sa.ForeignKey("categories.id")),
|
||||
sa.Column("category_id", GUID, sa.ForeignKey("categories.id")),
|
||||
)
|
||||
|
||||
plan_rules_to_categories = sa.Table(
|
||||
"plan_rules_to_categories",
|
||||
SqlAlchemyBase.metadata,
|
||||
sa.Column("group_plan_rule_id", GUID, sa.ForeignKey("group_meal_plan_rules.id")),
|
||||
sa.Column("category_id", sa.Integer, sa.ForeignKey("categories.id")),
|
||||
sa.Column("category_id", GUID, sa.ForeignKey("categories.id")),
|
||||
)
|
||||
|
||||
recipes2categories = sa.Table(
|
||||
"recipes2categories",
|
||||
recipes_to_categories = sa.Table(
|
||||
"recipes_to_categories",
|
||||
SqlAlchemyBase.metadata,
|
||||
sa.Column("recipe_id", sa.Integer, sa.ForeignKey("recipes.id")),
|
||||
sa.Column("category_id", sa.Integer, sa.ForeignKey("categories.id")),
|
||||
sa.Column("recipe_id", GUID, sa.ForeignKey("recipes.id")),
|
||||
sa.Column("category_id", GUID, sa.ForeignKey("categories.id")),
|
||||
)
|
||||
|
||||
cookbooks_to_categories = sa.Table(
|
||||
"cookbooks_to_categories",
|
||||
SqlAlchemyBase.metadata,
|
||||
sa.Column("cookbook_id", sa.Integer, sa.ForeignKey("cookbooks.id")),
|
||||
sa.Column("category_id", sa.Integer, sa.ForeignKey("categories.id")),
|
||||
sa.Column("category_id", GUID, sa.ForeignKey("categories.id")),
|
||||
)
|
||||
|
||||
|
||||
class Category(SqlAlchemyBase, BaseMixins):
|
||||
__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="recipe_category")
|
||||
__table_args__ = (sa.UniqueConstraint("slug", "group_id", name="category_slug_group_id_key"),)
|
||||
|
||||
class Config:
|
||||
get_attr = "slug"
|
||||
# ID Relationships
|
||||
group_id = sa.Column(GUID, sa.ForeignKey("groups.id"), nullable=False, index=True)
|
||||
group = orm.relationship("Group", back_populates="categories", foreign_keys=[group_id])
|
||||
|
||||
id = sa.Column(GUID, primary_key=True, default=GUID.generate)
|
||||
name = sa.Column(sa.String, index=True, nullable=False)
|
||||
slug = sa.Column(sa.String, index=True, nullable=False)
|
||||
recipes = orm.relationship("RecipeModel", secondary=recipes_to_categories, back_populates="recipe_category")
|
||||
|
||||
@validates("name")
|
||||
def validate_name(self, key, name):
|
||||
assert name != ""
|
||||
return name
|
||||
|
||||
def __init__(self, name, **_) -> None:
|
||||
def __init__(self, name, group_id, **_) -> None:
|
||||
self.group_id = group_id
|
||||
self.name = name.strip()
|
||||
self.slug = slugify(name)
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from sqlalchemy import Column, ForeignKey, Integer, String, orm
|
||||
from sqlalchemy import Column, ForeignKey, String, orm
|
||||
|
||||
from mealie.db.models._model_base import BaseMixins, SqlAlchemyBase
|
||||
from mealie.db.models._model_utils import auto_init
|
||||
@@ -11,7 +11,7 @@ class RecipeComment(SqlAlchemyBase, BaseMixins):
|
||||
text = Column(String)
|
||||
|
||||
# Recipe Link
|
||||
recipe_id = Column(Integer, ForeignKey("recipes.id"), nullable=False)
|
||||
recipe_id = Column(GUID, ForeignKey("recipes.id"), nullable=False)
|
||||
recipe = orm.relationship("RecipeModel", back_populates="comments")
|
||||
|
||||
# User Link
|
||||
|
||||
@@ -9,12 +9,12 @@ from .._model_utils.guid import GUID
|
||||
|
||||
class IngredientUnitModel(SqlAlchemyBase, BaseMixins):
|
||||
__tablename__ = "ingredient_units"
|
||||
id = Column(GUID, primary_key=True, default=GUID.generate)
|
||||
|
||||
# ID Relationships
|
||||
group_id = Column(GUID, ForeignKey("groups.id"), nullable=False)
|
||||
group = orm.relationship("Group", back_populates="ingredient_units", foreign_keys=[group_id])
|
||||
|
||||
id = Column(Integer, primary_key=True)
|
||||
name = Column(String)
|
||||
description = Column(String)
|
||||
abbreviation = Column(String)
|
||||
@@ -28,12 +28,12 @@ class IngredientUnitModel(SqlAlchemyBase, BaseMixins):
|
||||
|
||||
class IngredientFoodModel(SqlAlchemyBase, BaseMixins):
|
||||
__tablename__ = "ingredient_foods"
|
||||
id = Column(GUID, primary_key=True, default=GUID.generate)
|
||||
|
||||
# ID Relationships
|
||||
group_id = Column(GUID, ForeignKey("groups.id"), nullable=False)
|
||||
group = orm.relationship("Group", back_populates="ingredient_foods", foreign_keys=[group_id])
|
||||
|
||||
id = Column(Integer, primary_key=True)
|
||||
name = Column(String)
|
||||
description = Column(String)
|
||||
ingredients = orm.relationship("RecipeIngredient", back_populates="food")
|
||||
@@ -50,16 +50,16 @@ class RecipeIngredient(SqlAlchemyBase, BaseMixins):
|
||||
__tablename__ = "recipes_ingredients"
|
||||
id = Column(Integer, primary_key=True)
|
||||
position = Column(Integer)
|
||||
parent_id = Column(Integer, ForeignKey("recipes.id"))
|
||||
recipe_id = Column(GUID, ForeignKey("recipes.id"))
|
||||
|
||||
title = Column(String) # Section Header - Shows if Present
|
||||
note = Column(String) # Force Show Text - Overrides Concat
|
||||
|
||||
# Scaling Items
|
||||
unit_id = Column(Integer, ForeignKey("ingredient_units.id"))
|
||||
unit_id = Column(GUID, ForeignKey("ingredient_units.id"))
|
||||
unit = orm.relationship(IngredientUnitModel, uselist=False)
|
||||
|
||||
food_id = Column(Integer, ForeignKey("ingredient_foods.id"))
|
||||
food_id = Column(GUID, ForeignKey("ingredient_foods.id"))
|
||||
food = orm.relationship(IngredientFoodModel, uselist=False)
|
||||
quantity = Column(Integer)
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ class RecipeIngredientRefLink(SqlAlchemyBase, BaseMixins):
|
||||
class RecipeInstruction(SqlAlchemyBase):
|
||||
__tablename__ = "recipe_instructions"
|
||||
id = Column(GUID, primary_key=True, default=GUID.generate)
|
||||
parent_id = Column(Integer, ForeignKey("recipes.id"))
|
||||
recipe_id = Column(GUID, ForeignKey("recipes.id"))
|
||||
position = Column(Integer)
|
||||
type = Column(String, default="")
|
||||
title = Column(String)
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
import sqlalchemy as sa
|
||||
|
||||
from mealie.db.models._model_base import SqlAlchemyBase
|
||||
from mealie.db.models._model_utils.guid import GUID
|
||||
|
||||
|
||||
class Note(SqlAlchemyBase):
|
||||
__tablename__ = "notes"
|
||||
id = sa.Column(sa.Integer, primary_key=True)
|
||||
parent_id = sa.Column(sa.Integer, sa.ForeignKey("recipes.id"))
|
||||
recipe_id = sa.Column(GUID, sa.ForeignKey("recipes.id"))
|
||||
title = sa.Column(sa.String)
|
||||
text = sa.Column(sa.String)
|
||||
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
import sqlalchemy as sa
|
||||
|
||||
from mealie.db.models._model_base import SqlAlchemyBase
|
||||
from mealie.db.models._model_utils.guid import GUID
|
||||
|
||||
|
||||
class Nutrition(SqlAlchemyBase):
|
||||
__tablename__ = "recipe_nutrition"
|
||||
id = sa.Column(sa.Integer, primary_key=True)
|
||||
parent_id = sa.Column(sa.Integer, sa.ForeignKey("recipes.id"))
|
||||
recipe_id = sa.Column(GUID, sa.ForeignKey("recipes.id"))
|
||||
calories = sa.Column(sa.String)
|
||||
fat_content = sa.Column(sa.String)
|
||||
fiber_content = sa.Column(sa.String)
|
||||
|
||||
@@ -13,14 +13,14 @@ from .._model_utils import auto_init
|
||||
from ..users import users_to_favorites
|
||||
from .api_extras import ApiExtras
|
||||
from .assets import RecipeAsset
|
||||
from .category import recipes2categories
|
||||
from .category import recipes_to_categories
|
||||
from .ingredient import RecipeIngredient
|
||||
from .instruction import RecipeInstruction
|
||||
from .note import Note
|
||||
from .nutrition import Nutrition
|
||||
from .settings import RecipeSettings
|
||||
from .shared import RecipeShareTokenModel
|
||||
from .tag import Tag, recipes2tags
|
||||
from .tag import Tag, recipes_to_tags
|
||||
from .tool import recipes_to_tools
|
||||
|
||||
|
||||
@@ -43,13 +43,14 @@ class RecipeModel(SqlAlchemyBase, BaseMixins):
|
||||
__tablename__ = "recipes"
|
||||
__table_args__ = (sa.UniqueConstraint("slug", "group_id", name="recipe_slug_group_id_key"),)
|
||||
|
||||
id = sa.Column(GUID, primary_key=True, default=GUID.generate)
|
||||
slug = sa.Column(sa.String, index=True)
|
||||
|
||||
# ID Relationships
|
||||
group_id = sa.Column(GUID, sa.ForeignKey("groups.id"))
|
||||
group_id = sa.Column(GUID, sa.ForeignKey("groups.id"), nullable=False, index=True)
|
||||
group = orm.relationship("Group", back_populates="recipes", foreign_keys=[group_id])
|
||||
|
||||
user_id = sa.Column(GUID, sa.ForeignKey("users.id"))
|
||||
user_id = sa.Column(GUID, sa.ForeignKey("users.id"), index=True)
|
||||
user = orm.relationship("User", uselist=False, foreign_keys=[user_id])
|
||||
|
||||
meal_entries = orm.relationship("GroupMealPlan", back_populates="recipe", cascade="all, delete-orphan")
|
||||
@@ -72,7 +73,7 @@ class RecipeModel(SqlAlchemyBase, BaseMixins):
|
||||
|
||||
assets = orm.relationship("RecipeAsset", cascade="all, delete-orphan")
|
||||
nutrition: Nutrition = orm.relationship("Nutrition", uselist=False, cascade="all, delete-orphan")
|
||||
recipe_category = orm.relationship("Category", secondary=recipes2categories, back_populates="recipes")
|
||||
recipe_category = orm.relationship("Category", secondary=recipes_to_categories, back_populates="recipes")
|
||||
tools = orm.relationship("Tool", secondary=recipes_to_tools, back_populates="recipes")
|
||||
|
||||
recipe_ingredient: list[RecipeIngredient] = orm.relationship(
|
||||
@@ -96,7 +97,7 @@ class RecipeModel(SqlAlchemyBase, BaseMixins):
|
||||
|
||||
# Mealie Specific
|
||||
settings = orm.relationship("RecipeSettings", uselist=False, cascade="all, delete-orphan")
|
||||
tags: list[Tag] = orm.relationship("Tag", secondary=recipes2tags, back_populates="recipes")
|
||||
tags: list[Tag] = orm.relationship("Tag", secondary=recipes_to_tags, back_populates="recipes")
|
||||
notes: list[Note] = orm.relationship("Note", cascade="all, delete-orphan")
|
||||
rating = sa.Column(sa.Integer)
|
||||
org_url = sa.Column(sa.String)
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
import sqlalchemy as sa
|
||||
|
||||
from mealie.db.models._model_base import SqlAlchemyBase
|
||||
from mealie.db.models._model_utils.guid import GUID
|
||||
|
||||
|
||||
class RecipeSettings(SqlAlchemyBase):
|
||||
__tablename__ = "recipe_settings"
|
||||
id = sa.Column(sa.Integer, primary_key=True)
|
||||
parent_id = sa.Column(sa.Integer, sa.ForeignKey("recipes.id"))
|
||||
recipe_id = sa.Column(GUID, sa.ForeignKey("recipes.id"))
|
||||
public = sa.Column(sa.Boolean)
|
||||
show_nutrition = sa.Column(sa.Boolean)
|
||||
show_assets = sa.Column(sa.Boolean)
|
||||
|
||||
@@ -15,9 +15,9 @@ class RecipeShareTokenModel(SqlAlchemyBase, BaseMixins):
|
||||
__tablename__ = "recipe_share_tokens"
|
||||
id = sa.Column(GUID, primary_key=True, default=uuid4)
|
||||
|
||||
group_id = sa.Column(GUID, sa.ForeignKey("groups.id"))
|
||||
group_id = sa.Column(GUID, sa.ForeignKey("groups.id"), nullable=False, index=True)
|
||||
|
||||
recipe_id = sa.Column(sa.Integer, sa.ForeignKey("recipes.id"), nullable=False)
|
||||
recipe_id = sa.Column(GUID, sa.ForeignKey("recipes.id"), nullable=False)
|
||||
recipe = sa.orm.relationship("RecipeModel", back_populates="share_tokens", uselist=False)
|
||||
|
||||
expires_at = sa.Column(sa.DateTime, nullable=False)
|
||||
|
||||
@@ -9,27 +9,33 @@ from mealie.db.models._model_utils import guid
|
||||
|
||||
logger = root_logger.get_logger()
|
||||
|
||||
recipes2tags = sa.Table(
|
||||
"recipes2tags",
|
||||
recipes_to_tags = sa.Table(
|
||||
"recipes_to_tags",
|
||||
SqlAlchemyBase.metadata,
|
||||
sa.Column("recipe_id", sa.Integer, sa.ForeignKey("recipes.id")),
|
||||
sa.Column("tag_id", sa.Integer, sa.ForeignKey("tags.id")),
|
||||
sa.Column("recipe_id", guid.GUID, sa.ForeignKey("recipes.id")),
|
||||
sa.Column("tag_id", guid.GUID, sa.ForeignKey("tags.id")),
|
||||
)
|
||||
|
||||
plan_rules_to_tags = sa.Table(
|
||||
"plan_rules_to_tags",
|
||||
SqlAlchemyBase.metadata,
|
||||
sa.Column("plan_rule_id", guid.GUID, sa.ForeignKey("group_meal_plan_rules.id")),
|
||||
sa.Column("tag_id", sa.Integer, sa.ForeignKey("tags.id")),
|
||||
sa.Column("tag_id", guid.GUID, sa.ForeignKey("tags.id")),
|
||||
)
|
||||
|
||||
|
||||
class Tag(SqlAlchemyBase, BaseMixins):
|
||||
__tablename__ = "tags"
|
||||
id = sa.Column(sa.Integer, primary_key=True)
|
||||
__table_args__ = (sa.UniqueConstraint("slug", "group_id", name="tags_slug_group_id_key"),)
|
||||
|
||||
# ID Relationships
|
||||
group_id = sa.Column(guid.GUID, sa.ForeignKey("groups.id"), nullable=False, index=True)
|
||||
group = orm.relationship("Group", back_populates="tags", foreign_keys=[group_id])
|
||||
|
||||
id = sa.Column(guid.GUID, primary_key=True, default=guid.GUID.generate)
|
||||
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")
|
||||
slug = sa.Column(sa.String, index=True, nullable=False)
|
||||
recipes = orm.relationship("RecipeModel", secondary=recipes_to_tags, back_populates="tags")
|
||||
|
||||
class Config:
|
||||
get_attr = "slug"
|
||||
@@ -39,7 +45,8 @@ class Tag(SqlAlchemyBase, BaseMixins):
|
||||
assert name != ""
|
||||
return name
|
||||
|
||||
def __init__(self, name, **_) -> None:
|
||||
def __init__(self, name, group_id, **_) -> None:
|
||||
self.group_id = group_id
|
||||
self.name = name.strip()
|
||||
self.slug = slugify(self.name)
|
||||
|
||||
|
||||
@@ -1,19 +1,27 @@
|
||||
from slugify import slugify
|
||||
from sqlalchemy import Boolean, Column, ForeignKey, Integer, String, Table, orm
|
||||
from sqlalchemy import Boolean, Column, ForeignKey, String, Table, UniqueConstraint, orm
|
||||
|
||||
from mealie.db.models._model_base import BaseMixins, SqlAlchemyBase
|
||||
from mealie.db.models._model_utils import auto_init
|
||||
from mealie.db.models._model_utils.guid import GUID
|
||||
|
||||
recipes_to_tools = Table(
|
||||
"recipes_to_tools",
|
||||
SqlAlchemyBase.metadata,
|
||||
Column("recipe_id", Integer, ForeignKey("recipes.id")),
|
||||
Column("tool_id", Integer, ForeignKey("tools.id")),
|
||||
Column("recipe_id", GUID, ForeignKey("recipes.id")),
|
||||
Column("tool_id", GUID, ForeignKey("tools.id")),
|
||||
)
|
||||
|
||||
|
||||
class Tool(SqlAlchemyBase, BaseMixins):
|
||||
__tablename__ = "tools"
|
||||
id = Column(GUID, primary_key=True, default=GUID.generate)
|
||||
__table_args__ = (UniqueConstraint("slug", "group_id", name="tools_slug_group_id_key"),)
|
||||
|
||||
# ID Relationships
|
||||
group_id = Column(GUID, ForeignKey("groups.id"), nullable=False)
|
||||
group = orm.relationship("Group", back_populates="tools", foreign_keys=[group_id])
|
||||
|
||||
name = Column(String, index=True, unique=True, nullable=False)
|
||||
slug = Column(String, index=True, unique=True, nullable=False)
|
||||
on_hand = Column(Boolean, default=False)
|
||||
|
||||
@@ -13,7 +13,7 @@ class ServerTaskModel(SqlAlchemyBase, BaseMixins):
|
||||
status = Column(String, nullable=False)
|
||||
log = Column(String, nullable=True)
|
||||
|
||||
group_id = Column(GUID, ForeignKey("groups.id"))
|
||||
group_id = Column(GUID, ForeignKey("groups.id"), nullable=False, index=True)
|
||||
group = orm.relationship("Group", back_populates="server_tasks")
|
||||
|
||||
@auto_init()
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from sqlalchemy import Column, ForeignKey, Integer, Table
|
||||
from sqlalchemy import Column, ForeignKey, Table
|
||||
|
||||
from .._model_base import SqlAlchemyBase
|
||||
from .._model_utils import GUID
|
||||
@@ -7,5 +7,5 @@ users_to_favorites = Table(
|
||||
"users_to_favorites",
|
||||
SqlAlchemyBase.metadata,
|
||||
Column("user_id", GUID, ForeignKey("users.id")),
|
||||
Column("recipe_id", Integer, ForeignKey("recipes.id")),
|
||||
Column("recipe_id", GUID, ForeignKey("recipes.id")),
|
||||
)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from sqlalchemy import Boolean, Column, ForeignKey, Integer, String, orm
|
||||
from sqlalchemy import Boolean, Column, ForeignKey, String, orm
|
||||
|
||||
from mealie.core.config import get_app_settings
|
||||
from mealie.db.models._model_utils.guid import GUID
|
||||
@@ -33,7 +33,7 @@ class User(SqlAlchemyBase, BaseMixins):
|
||||
admin = Column(Boolean, default=False)
|
||||
advanced = Column(Boolean, default=False)
|
||||
|
||||
group_id = Column(GUID, ForeignKey("groups.id"))
|
||||
group_id = Column(GUID, ForeignKey("groups.id"), nullable=False, index=True)
|
||||
group = orm.relationship("Group", back_populates="users")
|
||||
|
||||
cache_key = Column(String, default="1234")
|
||||
@@ -53,7 +53,7 @@ class User(SqlAlchemyBase, BaseMixins):
|
||||
comments = orm.relationship("RecipeComment", **sp_args)
|
||||
password_reset_tokens = orm.relationship("PasswordResetModel", **sp_args)
|
||||
|
||||
owned_recipes_id = Column(Integer, ForeignKey("recipes.id"))
|
||||
owned_recipes_id = Column(GUID, ForeignKey("recipes.id"))
|
||||
owned_recipes = orm.relationship("RecipeModel", single_parent=True, foreign_keys=[owned_recipes_id])
|
||||
|
||||
favorite_recipes = orm.relationship("RecipeModel", secondary=users_to_favorites, back_populates="favorited_by")
|
||||
|
||||
Reference in New Issue
Block a user