Set up alembic migration usage (#954)

* Set up alembic migration usage

* Fix import order, add isort as alembic hook

* Only run migrations if needed

* Include date as part of migration file name for better sorting

* Skip initial migrations if tables already exist
This eases the transition from a pre-alembic state

* Fix deprecation warning in alembic revision

* Add alembic test stubs
This commit is contained in:
Philipp Fischbeck 2022-02-21 21:44:23 +01:00 committed by GitHub
parent a897e180ed
commit fdfb5b1a5e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 1197 additions and 25 deletions

72
alembic.ini Normal file
View file

@ -0,0 +1,72 @@
# A generic, single database configuration.
[alembic]
# path to migration scripts
script_location = alembic
# template used to generate migration files
file_template = %%(year)d-%%(month).2d-%%(day).2d-%%(hour).2d:%%(minute).2d:%%(second).2d_%%(rev)s_%%(slug)s
# sys.path path, will be prepended to sys.path if present.
# defaults to the current working directory.
prepend_sys_path = .
# timezone to use when rendering the date within the migration file
# as well as the filename.
# If specified, requires the python-dateutil library that can be
# installed by adding `alembic[tz]` to the pip requirements
# string value is passed to dateutil.tz.gettz()
# leave blank for localtime
# timezone =
# max length of characters to apply to the
# "slug" field
# truncate_slug_length = 40
# set to 'true' to run the environment during
# the 'revision' command, regardless of autogenerate
# revision_environment = false
# set to 'true' to allow .pyc and .pyo files without
# a source .py file to be detected as revisions in the
# versions/ directory
# sourceless = false
# version location specification; This defaults
# to alembic/versions. When using multiple version
# directories, initial revisions must be specified with --version-path.
# The path separator used here should be the separator specified by "version_path_separator"
# version_locations = %(here)s/bar:%(here)s/bat:alembic/versions
# version path separator; As mentioned above, this is the character used to split
# version_locations. Valid values are:
#
# version_path_separator = :
# version_path_separator = ;
# version_path_separator = space
version_path_separator = os # default: use os.pathsep
# the output encoding used when revision files
# are written from script.py.mako
# output_encoding = utf-8
# Will be replaced in alembic/env.py with the actual url
sqlalchemy.url =
[post_write_hooks]
# post_write_hooks defines scripts or Python functions that are run
# on newly generated revision scripts. See the documentation for further
# detail and examples
hooks = isort, black
# format using "isort" - use the console_scripts runner, against the "isort" entrypoint
isort.type = console_scripts
isort.entrypoint = isort
isort.options = REVISION_SCRIPT_FILENAME
# format using "black" - use the console_scripts runner, against the "black" entrypoint
black.type = console_scripts
black.entrypoint = black
black.options = REVISION_SCRIPT_FILENAME

1
alembic/README Normal file
View file

@ -0,0 +1 @@
Generic single-database configuration.

77
alembic/env.py Normal file
View file

@ -0,0 +1,77 @@
from sqlalchemy import engine_from_config, pool
import mealie.db.models._all_models # noqa: F401
from alembic import context
from mealie.core.config import get_app_settings
from mealie.db.models._model_base import SqlAlchemyBase
# this is the Alembic Config object, which provides
# access to the values within the .ini file in use.
config = context.config
# add your model's MetaData object here
# for 'autogenerate' support
# from myapp import mymodel
# target_metadata = mymodel.Base.metadata
target_metadata = SqlAlchemyBase.metadata
# other values from the config, defined by the needs of env.py,
# can be acquired:
# my_important_option = config.get_main_option("my_important_option")
# ... etc.
# Set DB url from config
settings = get_app_settings()
config.set_main_option("sqlalchemy.url", settings.DB_URL)
def run_migrations_offline():
"""Run migrations in 'offline' mode.
This configures the context with just a URL
and not an Engine, though an Engine is acceptable
here as well. By skipping the Engine creation
we don't even need a DBAPI to be available.
Calls to context.execute() here emit the given string to the
script output.
"""
url = config.get_main_option("sqlalchemy.url")
context.configure(
url=url,
target_metadata=target_metadata,
literal_binds=True,
dialect_opts={"paramstyle": "named"},
)
with context.begin_transaction():
context.run_migrations()
def run_migrations_online():
"""Run migrations in 'online' mode.
In this scenario we need to create an Engine
and associate a connection with the context.
"""
connectable = engine_from_config(
config.get_section(config.config_ini_section),
prefix="sqlalchemy.",
poolclass=pool.NullPool,
)
with connectable.connect() as connection:
context.configure(
connection=connection, target_metadata=target_metadata, user_module_prefix="mealie.db.migration_types."
)
with context.begin_transaction():
context.run_migrations()
if context.is_offline_mode():
run_migrations_offline()
else:
run_migrations_online()

26
alembic/script.py.mako Normal file
View file

@ -0,0 +1,26 @@
"""${message}
Revision ID: ${up_revision}
Revises: ${down_revision | comma,n}
Create Date: ${create_date}
"""
import sqlalchemy as sa
import mealie.db.migration_types
from alembic import op
${imports if imports else ""}
# revision identifiers, used by Alembic.
revision = ${repr(up_revision)}
down_revision = ${repr(down_revision)}
branch_labels = ${repr(branch_labels)}
depends_on = ${repr(depends_on)}
def upgrade():
${upgrades if upgrades else "pass"}
def downgrade():
${downgrades if downgrades else "pass"}

View file

@ -0,0 +1,920 @@
"""Initial tables
Revision ID: 6b0f5f32d602
Revises:
Create Date: 2022-02-21 19:56:24.351115
"""
import sqlalchemy as sa
from sqlalchemy import engine_from_config
import mealie.db.migration_types
from alembic import op
# revision identifiers, used by Alembic.
revision = "6b0f5f32d602"
down_revision = None
branch_labels = None
depends_on = None
# Adapted from https://improveandrepeat.com/2021/09/python-friday-87-handling-pre-existing-tables-with-alembic-and-sqlalchemy/
def table_exists(table, schema=None):
config = op.get_context().config
engine = engine_from_config(config.get_section(config.config_ini_section), prefix="sqlalchemy.")
insp = sa.inspect(engine)
return insp.has_table(table, schema)
def upgrade():
# Only create initial tables if they don't exist yet, to ease transition from pre-alembic state
if table_exists("users"):
return
op.create_table(
"groups",
sa.Column("created_at", sa.DateTime(), nullable=True),
sa.Column("update_at", sa.DateTime(), nullable=True),
sa.Column("id", mealie.db.migration_types.GUID(), nullable=False),
sa.Column("name", sa.String(), nullable=False),
sa.PrimaryKeyConstraint("id"),
)
op.create_index(op.f("ix_groups_name"), "groups", ["name"], unique=True)
op.create_table(
"categories",
sa.Column("created_at", sa.DateTime(), nullable=True),
sa.Column("update_at", sa.DateTime(), nullable=True),
sa.Column("group_id", mealie.db.migration_types.GUID(), nullable=False),
sa.Column("id", mealie.db.migration_types.GUID(), nullable=False),
sa.Column("name", sa.String(), nullable=False),
sa.Column("slug", sa.String(), nullable=False),
sa.ForeignKeyConstraint(
["group_id"],
["groups.id"],
),
sa.PrimaryKeyConstraint("id"),
sa.UniqueConstraint("slug", "group_id", name="category_slug_group_id_key"),
)
op.create_index(op.f("ix_categories_group_id"), "categories", ["group_id"], unique=False)
op.create_index(op.f("ix_categories_name"), "categories", ["name"], unique=False)
op.create_index(op.f("ix_categories_slug"), "categories", ["slug"], unique=False)
op.create_table(
"cookbooks",
sa.Column("created_at", sa.DateTime(), nullable=True),
sa.Column("update_at", sa.DateTime(), nullable=True),
sa.Column("id", mealie.db.migration_types.GUID(), nullable=False),
sa.Column("position", sa.Integer(), nullable=False),
sa.Column("name", sa.String(), nullable=False),
sa.Column("slug", sa.String(), nullable=False),
sa.Column("description", sa.String(), nullable=True),
sa.Column("group_id", mealie.db.migration_types.GUID(), nullable=True),
sa.ForeignKeyConstraint(
["group_id"],
["groups.id"],
),
sa.PrimaryKeyConstraint("id"),
)
op.create_table(
"group_data_exports",
sa.Column("created_at", sa.DateTime(), nullable=True),
sa.Column("update_at", sa.DateTime(), nullable=True),
sa.Column("id", mealie.db.migration_types.GUID(), nullable=False),
sa.Column("group_id", mealie.db.migration_types.GUID(), nullable=True),
sa.Column("name", sa.String(), nullable=False),
sa.Column("filename", sa.String(), nullable=False),
sa.Column("path", sa.String(), nullable=False),
sa.Column("size", sa.String(), nullable=False),
sa.Column("expires", sa.String(), nullable=False),
sa.ForeignKeyConstraint(
["group_id"],
["groups.id"],
),
sa.PrimaryKeyConstraint("id"),
)
op.create_index(op.f("ix_group_data_exports_group_id"), "group_data_exports", ["group_id"], unique=False)
op.create_table(
"group_events_notifiers",
sa.Column("created_at", sa.DateTime(), nullable=True),
sa.Column("update_at", sa.DateTime(), nullable=True),
sa.Column("id", mealie.db.migration_types.GUID(), nullable=False),
sa.Column("name", sa.String(), nullable=False),
sa.Column("enabled", sa.Boolean(), nullable=False),
sa.Column("apprise_url", sa.String(), nullable=False),
sa.Column("group_id", mealie.db.migration_types.GUID(), nullable=True),
sa.ForeignKeyConstraint(
["group_id"],
["groups.id"],
),
sa.PrimaryKeyConstraint("id"),
)
op.create_index(op.f("ix_group_events_notifiers_group_id"), "group_events_notifiers", ["group_id"], unique=False)
op.create_table(
"group_meal_plan_rules",
sa.Column("created_at", sa.DateTime(), nullable=True),
sa.Column("update_at", sa.DateTime(), nullable=True),
sa.Column("id", mealie.db.migration_types.GUID(), nullable=False),
sa.Column("group_id", mealie.db.migration_types.GUID(), nullable=False),
sa.Column("day", sa.String(), nullable=False),
sa.Column("entry_type", sa.String(), nullable=False),
sa.ForeignKeyConstraint(
["group_id"],
["groups.id"],
),
sa.PrimaryKeyConstraint("id"),
)
op.create_table(
"group_preferences",
sa.Column("created_at", sa.DateTime(), nullable=True),
sa.Column("update_at", sa.DateTime(), nullable=True),
sa.Column("id", mealie.db.migration_types.GUID(), nullable=False),
sa.Column("group_id", mealie.db.migration_types.GUID(), nullable=False),
sa.Column("private_group", sa.Boolean(), nullable=True),
sa.Column("first_day_of_week", sa.Integer(), nullable=True),
sa.Column("recipe_public", sa.Boolean(), nullable=True),
sa.Column("recipe_show_nutrition", sa.Boolean(), nullable=True),
sa.Column("recipe_show_assets", sa.Boolean(), nullable=True),
sa.Column("recipe_landscape_view", sa.Boolean(), nullable=True),
sa.Column("recipe_disable_comments", sa.Boolean(), nullable=True),
sa.Column("recipe_disable_amount", sa.Boolean(), nullable=True),
sa.ForeignKeyConstraint(
["group_id"],
["groups.id"],
),
sa.PrimaryKeyConstraint("id"),
)
op.create_index(op.f("ix_group_preferences_group_id"), "group_preferences", ["group_id"], unique=False)
op.create_table(
"group_reports",
sa.Column("created_at", sa.DateTime(), nullable=True),
sa.Column("update_at", sa.DateTime(), nullable=True),
sa.Column("id", mealie.db.migration_types.GUID(), nullable=False),
sa.Column("name", sa.String(), nullable=False),
sa.Column("status", sa.String(), nullable=False),
sa.Column("category", sa.String(), nullable=False),
sa.Column("timestamp", sa.DateTime(), nullable=False),
sa.Column("group_id", mealie.db.migration_types.GUID(), nullable=False),
sa.ForeignKeyConstraint(
["group_id"],
["groups.id"],
),
sa.PrimaryKeyConstraint("id"),
)
op.create_index(op.f("ix_group_reports_category"), "group_reports", ["category"], unique=False)
op.create_index(op.f("ix_group_reports_group_id"), "group_reports", ["group_id"], unique=False)
op.create_table(
"ingredient_units",
sa.Column("created_at", sa.DateTime(), nullable=True),
sa.Column("update_at", sa.DateTime(), nullable=True),
sa.Column("id", mealie.db.migration_types.GUID(), nullable=False),
sa.Column("group_id", mealie.db.migration_types.GUID(), nullable=False),
sa.Column("name", sa.String(), nullable=True),
sa.Column("description", sa.String(), nullable=True),
sa.Column("abbreviation", sa.String(), nullable=True),
sa.Column("fraction", sa.Boolean(), nullable=True),
sa.ForeignKeyConstraint(
["group_id"],
["groups.id"],
),
sa.PrimaryKeyConstraint("id"),
)
op.create_table(
"invite_tokens",
sa.Column("id", sa.Integer(), nullable=False),
sa.Column("created_at", sa.DateTime(), nullable=True),
sa.Column("update_at", sa.DateTime(), nullable=True),
sa.Column("token", sa.String(), nullable=False),
sa.Column("uses_left", sa.Integer(), nullable=False),
sa.Column("group_id", mealie.db.migration_types.GUID(), nullable=True),
sa.ForeignKeyConstraint(
["group_id"],
["groups.id"],
),
sa.PrimaryKeyConstraint("id"),
)
op.create_index(op.f("ix_invite_tokens_token"), "invite_tokens", ["token"], unique=True)
op.create_table(
"multi_purpose_labels",
sa.Column("created_at", sa.DateTime(), nullable=True),
sa.Column("update_at", sa.DateTime(), nullable=True),
sa.Column("id", mealie.db.migration_types.GUID(), nullable=False),
sa.Column("name", sa.String(length=255), nullable=False),
sa.Column("color", sa.String(length=10), nullable=False),
sa.Column("group_id", mealie.db.migration_types.GUID(), nullable=False),
sa.ForeignKeyConstraint(
["group_id"],
["groups.id"],
),
sa.PrimaryKeyConstraint("id"),
)
op.create_index(op.f("ix_multi_purpose_labels_group_id"), "multi_purpose_labels", ["group_id"], unique=False)
op.create_table(
"recipes",
sa.Column("created_at", sa.DateTime(), nullable=True),
sa.Column("update_at", sa.DateTime(), nullable=True),
sa.Column("id", mealie.db.migration_types.GUID(), nullable=False),
sa.Column("slug", sa.String(), nullable=True),
sa.Column("group_id", mealie.db.migration_types.GUID(), nullable=False),
sa.Column("user_id", mealie.db.migration_types.GUID(), nullable=True),
sa.Column("name", sa.String(), nullable=False),
sa.Column("description", sa.String(), nullable=True),
sa.Column("image", sa.String(), nullable=True),
sa.Column("total_time", sa.String(), nullable=True),
sa.Column("prep_time", sa.String(), nullable=True),
sa.Column("perform_time", sa.String(), nullable=True),
sa.Column("cook_time", sa.String(), nullable=True),
sa.Column("recipe_yield", sa.String(), nullable=True),
sa.Column("recipeCuisine", sa.String(), nullable=True),
sa.Column("rating", sa.Integer(), nullable=True),
sa.Column("org_url", sa.String(), nullable=True),
sa.Column("date_added", sa.Date(), nullable=True),
sa.Column("date_updated", sa.DateTime(), nullable=True),
sa.ForeignKeyConstraint(
["group_id"],
["groups.id"],
),
sa.ForeignKeyConstraint(["user_id"], ["users.id"], use_alter=True),
sa.PrimaryKeyConstraint("id"),
sa.UniqueConstraint("slug", "group_id", name="recipe_slug_group_id_key"),
)
op.create_index(op.f("ix_recipes_group_id"), "recipes", ["group_id"], unique=False)
op.create_index(op.f("ix_recipes_slug"), "recipes", ["slug"], unique=False)
op.create_index(op.f("ix_recipes_user_id"), "recipes", ["user_id"], unique=False)
op.create_table(
"server_tasks",
sa.Column("id", sa.Integer(), nullable=False),
sa.Column("created_at", sa.DateTime(), nullable=True),
sa.Column("update_at", sa.DateTime(), nullable=True),
sa.Column("name", sa.String(), nullable=False),
sa.Column("completed_date", sa.DateTime(), nullable=True),
sa.Column("status", sa.String(), nullable=False),
sa.Column("log", sa.String(), nullable=True),
sa.Column("group_id", mealie.db.migration_types.GUID(), nullable=False),
sa.ForeignKeyConstraint(
["group_id"],
["groups.id"],
),
sa.PrimaryKeyConstraint("id"),
)
op.create_index(op.f("ix_server_tasks_group_id"), "server_tasks", ["group_id"], unique=False)
op.create_table(
"shopping_lists",
sa.Column("created_at", sa.DateTime(), nullable=True),
sa.Column("update_at", sa.DateTime(), nullable=True),
sa.Column("id", mealie.db.migration_types.GUID(), nullable=False),
sa.Column("group_id", mealie.db.migration_types.GUID(), nullable=False),
sa.Column("name", sa.String(), nullable=True),
sa.ForeignKeyConstraint(
["group_id"],
["groups.id"],
),
sa.PrimaryKeyConstraint("id"),
)
op.create_index(op.f("ix_shopping_lists_group_id"), "shopping_lists", ["group_id"], unique=False)
op.create_table(
"tags",
sa.Column("created_at", sa.DateTime(), nullable=True),
sa.Column("update_at", sa.DateTime(), nullable=True),
sa.Column("id", mealie.db.migration_types.GUID(), nullable=False),
sa.Column("group_id", mealie.db.migration_types.GUID(), nullable=False),
sa.Column("name", sa.String(), nullable=False),
sa.Column("slug", sa.String(), nullable=False),
sa.ForeignKeyConstraint(
["group_id"],
["groups.id"],
),
sa.PrimaryKeyConstraint("id"),
sa.UniqueConstraint("slug", "group_id", name="tags_slug_group_id_key"),
)
op.create_index(op.f("ix_tags_group_id"), "tags", ["group_id"], unique=False)
op.create_index(op.f("ix_tags_name"), "tags", ["name"], unique=False)
op.create_index(op.f("ix_tags_slug"), "tags", ["slug"], unique=False)
op.create_table(
"tools",
sa.Column("created_at", sa.DateTime(), nullable=True),
sa.Column("update_at", sa.DateTime(), nullable=True),
sa.Column("id", mealie.db.migration_types.GUID(), nullable=False),
sa.Column("group_id", mealie.db.migration_types.GUID(), nullable=False),
sa.Column("name", sa.String(), nullable=False),
sa.Column("slug", sa.String(), nullable=False),
sa.Column("on_hand", sa.Boolean(), nullable=True),
sa.ForeignKeyConstraint(
["group_id"],
["groups.id"],
),
sa.PrimaryKeyConstraint("id"),
sa.UniqueConstraint("slug", "group_id", name="tools_slug_group_id_key"),
)
op.create_index(op.f("ix_tools_name"), "tools", ["name"], unique=True)
op.create_index(op.f("ix_tools_slug"), "tools", ["slug"], unique=True)
op.create_table(
"webhook_urls",
sa.Column("created_at", sa.DateTime(), nullable=True),
sa.Column("update_at", sa.DateTime(), nullable=True),
sa.Column("id", mealie.db.migration_types.GUID(), nullable=False),
sa.Column("group_id", mealie.db.migration_types.GUID(), nullable=True),
sa.Column("enabled", sa.Boolean(), nullable=True),
sa.Column("name", sa.String(), nullable=True),
sa.Column("url", sa.String(), nullable=True),
sa.Column("time", sa.String(), nullable=True),
sa.ForeignKeyConstraint(
["group_id"],
["groups.id"],
),
sa.PrimaryKeyConstraint("id"),
)
op.create_index(op.f("ix_webhook_urls_group_id"), "webhook_urls", ["group_id"], unique=False)
op.create_table(
"api_extras",
sa.Column("created_at", sa.DateTime(), nullable=True),
sa.Column("update_at", sa.DateTime(), nullable=True),
sa.Column("id", sa.Integer(), nullable=False),
sa.Column("recipee_id", mealie.db.migration_types.GUID(), nullable=True),
sa.Column("key_name", sa.String(), nullable=True),
sa.Column("value", sa.String(), nullable=True),
sa.ForeignKeyConstraint(
["recipee_id"],
["recipes.id"],
),
sa.PrimaryKeyConstraint("id"),
)
op.create_table(
"cookbooks_to_categories",
sa.Column("cookbook_id", mealie.db.migration_types.GUID(), nullable=True),
sa.Column("category_id", mealie.db.migration_types.GUID(), nullable=True),
sa.ForeignKeyConstraint(
["category_id"],
["categories.id"],
),
sa.ForeignKeyConstraint(
["cookbook_id"],
["cookbooks.id"],
),
)
op.create_table(
"group_events_notifier_options",
sa.Column("created_at", sa.DateTime(), nullable=True),
sa.Column("update_at", sa.DateTime(), nullable=True),
sa.Column("id", mealie.db.migration_types.GUID(), nullable=False),
sa.Column("event_notifier_id", mealie.db.migration_types.GUID(), nullable=False),
sa.Column("recipe_created", sa.Boolean(), nullable=False),
sa.Column("recipe_updated", sa.Boolean(), nullable=False),
sa.Column("recipe_deleted", sa.Boolean(), nullable=False),
sa.Column("user_signup", sa.Boolean(), nullable=False),
sa.Column("data_migrations", sa.Boolean(), nullable=False),
sa.Column("data_export", sa.Boolean(), nullable=False),
sa.Column("data_import", sa.Boolean(), nullable=False),
sa.Column("mealplan_entry_created", sa.Boolean(), nullable=False),
sa.Column("shopping_list_created", sa.Boolean(), nullable=False),
sa.Column("shopping_list_updated", sa.Boolean(), nullable=False),
sa.Column("shopping_list_deleted", sa.Boolean(), nullable=False),
sa.Column("cookbook_created", sa.Boolean(), nullable=False),
sa.Column("cookbook_updated", sa.Boolean(), nullable=False),
sa.Column("cookbook_deleted", sa.Boolean(), nullable=False),
sa.Column("tag_created", sa.Boolean(), nullable=False),
sa.Column("tag_updated", sa.Boolean(), nullable=False),
sa.Column("tag_deleted", sa.Boolean(), nullable=False),
sa.Column("category_created", sa.Boolean(), nullable=False),
sa.Column("category_updated", sa.Boolean(), nullable=False),
sa.Column("category_deleted", sa.Boolean(), nullable=False),
sa.ForeignKeyConstraint(
["event_notifier_id"],
["group_events_notifiers.id"],
),
sa.PrimaryKeyConstraint("id"),
)
op.create_table(
"group_meal_plans",
sa.Column("id", sa.Integer(), nullable=False),
sa.Column("created_at", sa.DateTime(), nullable=True),
sa.Column("update_at", sa.DateTime(), nullable=True),
sa.Column("date", sa.Date(), nullable=False),
sa.Column("entry_type", sa.String(), nullable=False),
sa.Column("title", sa.String(), nullable=False),
sa.Column("text", sa.String(), nullable=False),
sa.Column("group_id", mealie.db.migration_types.GUID(), nullable=True),
sa.Column("recipe_id", mealie.db.migration_types.GUID(), nullable=True),
sa.ForeignKeyConstraint(
["group_id"],
["groups.id"],
),
sa.ForeignKeyConstraint(
["recipe_id"],
["recipes.id"],
),
sa.PrimaryKeyConstraint("id"),
)
op.create_index(op.f("ix_group_meal_plans_date"), "group_meal_plans", ["date"], unique=False)
op.create_index(op.f("ix_group_meal_plans_entry_type"), "group_meal_plans", ["entry_type"], unique=False)
op.create_index(op.f("ix_group_meal_plans_group_id"), "group_meal_plans", ["group_id"], unique=False)
op.create_index(op.f("ix_group_meal_plans_recipe_id"), "group_meal_plans", ["recipe_id"], unique=False)
op.create_index(op.f("ix_group_meal_plans_title"), "group_meal_plans", ["title"], unique=False)
op.create_table(
"group_to_categories",
sa.Column("group_id", mealie.db.migration_types.GUID(), nullable=True),
sa.Column("category_id", mealie.db.migration_types.GUID(), nullable=True),
sa.ForeignKeyConstraint(
["category_id"],
["categories.id"],
),
sa.ForeignKeyConstraint(
["group_id"],
["groups.id"],
),
)
op.create_table(
"ingredient_foods",
sa.Column("created_at", sa.DateTime(), nullable=True),
sa.Column("update_at", sa.DateTime(), nullable=True),
sa.Column("id", mealie.db.migration_types.GUID(), nullable=False),
sa.Column("group_id", mealie.db.migration_types.GUID(), nullable=False),
sa.Column("name", sa.String(), nullable=True),
sa.Column("description", sa.String(), nullable=True),
sa.Column("label_id", mealie.db.migration_types.GUID(), nullable=True),
sa.ForeignKeyConstraint(
["group_id"],
["groups.id"],
),
sa.ForeignKeyConstraint(
["label_id"],
["multi_purpose_labels.id"],
),
sa.PrimaryKeyConstraint("id"),
)
op.create_table(
"notes",
sa.Column("created_at", sa.DateTime(), nullable=True),
sa.Column("update_at", sa.DateTime(), nullable=True),
sa.Column("id", sa.Integer(), nullable=False),
sa.Column("recipe_id", mealie.db.migration_types.GUID(), nullable=True),
sa.Column("title", sa.String(), nullable=True),
sa.Column("text", sa.String(), nullable=True),
sa.ForeignKeyConstraint(
["recipe_id"],
["recipes.id"],
),
sa.PrimaryKeyConstraint("id"),
)
op.create_table(
"plan_rules_to_categories",
sa.Column("group_plan_rule_id", mealie.db.migration_types.GUID(), nullable=True),
sa.Column("category_id", mealie.db.migration_types.GUID(), nullable=True),
sa.ForeignKeyConstraint(
["category_id"],
["categories.id"],
),
sa.ForeignKeyConstraint(
["group_plan_rule_id"],
["group_meal_plan_rules.id"],
),
)
op.create_table(
"plan_rules_to_tags",
sa.Column("plan_rule_id", mealie.db.migration_types.GUID(), nullable=True),
sa.Column("tag_id", mealie.db.migration_types.GUID(), nullable=True),
sa.ForeignKeyConstraint(
["plan_rule_id"],
["group_meal_plan_rules.id"],
),
sa.ForeignKeyConstraint(
["tag_id"],
["tags.id"],
),
)
op.create_table(
"recipe_assets",
sa.Column("created_at", sa.DateTime(), nullable=True),
sa.Column("update_at", sa.DateTime(), nullable=True),
sa.Column("id", sa.Integer(), nullable=False),
sa.Column("recipe_id", mealie.db.migration_types.GUID(), nullable=True),
sa.Column("name", sa.String(), nullable=True),
sa.Column("icon", sa.String(), nullable=True),
sa.Column("file_name", sa.String(), nullable=True),
sa.ForeignKeyConstraint(
["recipe_id"],
["recipes.id"],
),
sa.PrimaryKeyConstraint("id"),
)
op.create_table(
"recipe_instructions",
sa.Column("created_at", sa.DateTime(), nullable=True),
sa.Column("update_at", sa.DateTime(), nullable=True),
sa.Column("id", mealie.db.migration_types.GUID(), nullable=False),
sa.Column("recipe_id", mealie.db.migration_types.GUID(), nullable=True),
sa.Column("position", sa.Integer(), nullable=True),
sa.Column("type", sa.String(), nullable=True),
sa.Column("title", sa.String(), nullable=True),
sa.Column("text", sa.String(), nullable=True),
sa.ForeignKeyConstraint(
["recipe_id"],
["recipes.id"],
),
sa.PrimaryKeyConstraint("id"),
)
op.create_table(
"recipe_nutrition",
sa.Column("created_at", sa.DateTime(), nullable=True),
sa.Column("update_at", sa.DateTime(), nullable=True),
sa.Column("id", sa.Integer(), nullable=False),
sa.Column("recipe_id", mealie.db.migration_types.GUID(), nullable=True),
sa.Column("calories", sa.String(), nullable=True),
sa.Column("fat_content", sa.String(), nullable=True),
sa.Column("fiber_content", sa.String(), nullable=True),
sa.Column("protein_content", sa.String(), nullable=True),
sa.Column("carbohydrate_content", sa.String(), nullable=True),
sa.Column("sodium_content", sa.String(), nullable=True),
sa.Column("sugar_content", sa.String(), nullable=True),
sa.ForeignKeyConstraint(
["recipe_id"],
["recipes.id"],
),
sa.PrimaryKeyConstraint("id"),
)
op.create_table(
"recipe_settings",
sa.Column("created_at", sa.DateTime(), nullable=True),
sa.Column("update_at", sa.DateTime(), nullable=True),
sa.Column("id", sa.Integer(), nullable=False),
sa.Column("recipe_id", mealie.db.migration_types.GUID(), nullable=True),
sa.Column("public", sa.Boolean(), nullable=True),
sa.Column("show_nutrition", sa.Boolean(), nullable=True),
sa.Column("show_assets", sa.Boolean(), nullable=True),
sa.Column("landscape_view", sa.Boolean(), nullable=True),
sa.Column("disable_amount", sa.Boolean(), nullable=True),
sa.Column("disable_comments", sa.Boolean(), nullable=True),
sa.Column("locked", sa.Boolean(), nullable=True),
sa.ForeignKeyConstraint(
["recipe_id"],
["recipes.id"],
),
sa.PrimaryKeyConstraint("id"),
)
op.create_table(
"recipe_share_tokens",
sa.Column("created_at", sa.DateTime(), nullable=True),
sa.Column("update_at", sa.DateTime(), nullable=True),
sa.Column("id", mealie.db.migration_types.GUID(), nullable=False),
sa.Column("group_id", mealie.db.migration_types.GUID(), nullable=False),
sa.Column("recipe_id", mealie.db.migration_types.GUID(), nullable=False),
sa.Column("expires_at", sa.DateTime(), nullable=False),
sa.ForeignKeyConstraint(
["group_id"],
["groups.id"],
),
sa.ForeignKeyConstraint(
["recipe_id"],
["recipes.id"],
),
sa.PrimaryKeyConstraint("id"),
)
op.create_index(op.f("ix_recipe_share_tokens_group_id"), "recipe_share_tokens", ["group_id"], unique=False)
op.create_table(
"recipes_to_categories",
sa.Column("recipe_id", mealie.db.migration_types.GUID(), nullable=True),
sa.Column("category_id", mealie.db.migration_types.GUID(), nullable=True),
sa.ForeignKeyConstraint(
["category_id"],
["categories.id"],
),
sa.ForeignKeyConstraint(
["recipe_id"],
["recipes.id"],
),
)
op.create_table(
"recipes_to_tags",
sa.Column("recipe_id", mealie.db.migration_types.GUID(), nullable=True),
sa.Column("tag_id", mealie.db.migration_types.GUID(), nullable=True),
sa.ForeignKeyConstraint(
["recipe_id"],
["recipes.id"],
),
sa.ForeignKeyConstraint(
["tag_id"],
["tags.id"],
),
)
op.create_table(
"recipes_to_tools",
sa.Column("recipe_id", mealie.db.migration_types.GUID(), nullable=True),
sa.Column("tool_id", mealie.db.migration_types.GUID(), nullable=True),
sa.ForeignKeyConstraint(
["recipe_id"],
["recipes.id"],
),
sa.ForeignKeyConstraint(
["tool_id"],
["tools.id"],
),
)
op.create_table(
"report_entries",
sa.Column("created_at", sa.DateTime(), nullable=True),
sa.Column("update_at", sa.DateTime(), nullable=True),
sa.Column("id", mealie.db.migration_types.GUID(), nullable=False),
sa.Column("success", sa.Boolean(), nullable=True),
sa.Column("message", sa.String(), nullable=True),
sa.Column("exception", sa.String(), nullable=True),
sa.Column("timestamp", sa.DateTime(), nullable=False),
sa.Column("report_id", mealie.db.migration_types.GUID(), nullable=False),
sa.ForeignKeyConstraint(
["report_id"],
["group_reports.id"],
),
sa.PrimaryKeyConstraint("id"),
)
op.create_table(
"shopping_list_recipe_reference",
sa.Column("created_at", sa.DateTime(), nullable=True),
sa.Column("update_at", sa.DateTime(), nullable=True),
sa.Column("id", mealie.db.migration_types.GUID(), nullable=False),
sa.Column("shopping_list_id", mealie.db.migration_types.GUID(), nullable=False),
sa.Column("recipe_id", mealie.db.migration_types.GUID(), nullable=True),
sa.Column("recipe_quantity", sa.Float(), nullable=False),
sa.ForeignKeyConstraint(
["recipe_id"],
["recipes.id"],
),
sa.ForeignKeyConstraint(
["shopping_list_id"],
["shopping_lists.id"],
),
sa.PrimaryKeyConstraint("id", "shopping_list_id"),
)
op.create_index(
op.f("ix_shopping_list_recipe_reference_recipe_id"),
"shopping_list_recipe_reference",
["recipe_id"],
unique=False,
)
op.create_table(
"users",
sa.Column("created_at", sa.DateTime(), nullable=True),
sa.Column("update_at", sa.DateTime(), nullable=True),
sa.Column("id", mealie.db.migration_types.GUID(), nullable=False),
sa.Column("full_name", sa.String(), nullable=True),
sa.Column("username", sa.String(), nullable=True),
sa.Column("email", sa.String(), nullable=True),
sa.Column("password", sa.String(), nullable=True),
sa.Column("admin", sa.Boolean(), nullable=True),
sa.Column("advanced", sa.Boolean(), nullable=True),
sa.Column("group_id", mealie.db.migration_types.GUID(), nullable=False),
sa.Column("cache_key", sa.String(), nullable=True),
sa.Column("can_manage", sa.Boolean(), nullable=True),
sa.Column("can_invite", sa.Boolean(), nullable=True),
sa.Column("can_organize", sa.Boolean(), nullable=True),
sa.Column("owned_recipes_id", mealie.db.migration_types.GUID(), nullable=True),
sa.ForeignKeyConstraint(
["group_id"],
["groups.id"],
),
sa.ForeignKeyConstraint(
["owned_recipes_id"],
["recipes.id"],
),
sa.PrimaryKeyConstraint("id"),
)
op.create_index(op.f("ix_users_email"), "users", ["email"], unique=True)
op.create_index(op.f("ix_users_full_name"), "users", ["full_name"], unique=False)
op.create_index(op.f("ix_users_group_id"), "users", ["group_id"], unique=False)
op.create_index(op.f("ix_users_username"), "users", ["username"], unique=True)
op.create_table(
"long_live_tokens",
sa.Column("id", sa.Integer(), nullable=False),
sa.Column("created_at", sa.DateTime(), nullable=True),
sa.Column("update_at", sa.DateTime(), nullable=True),
sa.Column("name", sa.String(), nullable=False),
sa.Column("token", sa.String(), nullable=False),
sa.Column("user_id", mealie.db.migration_types.GUID(), nullable=True),
sa.ForeignKeyConstraint(
["user_id"],
["users.id"],
),
sa.PrimaryKeyConstraint("id"),
)
op.create_table(
"password_reset_tokens",
sa.Column("id", sa.Integer(), nullable=False),
sa.Column("created_at", sa.DateTime(), nullable=True),
sa.Column("update_at", sa.DateTime(), nullable=True),
sa.Column("user_id", mealie.db.migration_types.GUID(), nullable=False),
sa.Column("token", sa.String(length=64), nullable=False),
sa.ForeignKeyConstraint(
["user_id"],
["users.id"],
),
sa.PrimaryKeyConstraint("id"),
sa.UniqueConstraint("token"),
)
op.create_table(
"recipe_comments",
sa.Column("created_at", sa.DateTime(), nullable=True),
sa.Column("update_at", sa.DateTime(), nullable=True),
sa.Column("id", mealie.db.migration_types.GUID(), nullable=False),
sa.Column("text", sa.String(), nullable=True),
sa.Column("recipe_id", mealie.db.migration_types.GUID(), nullable=False),
sa.Column("user_id", mealie.db.migration_types.GUID(), nullable=False),
sa.ForeignKeyConstraint(
["recipe_id"],
["recipes.id"],
),
sa.ForeignKeyConstraint(
["user_id"],
["users.id"],
),
sa.PrimaryKeyConstraint("id"),
)
op.create_table(
"recipe_ingredient_ref_link",
sa.Column("id", sa.Integer(), nullable=False),
sa.Column("created_at", sa.DateTime(), nullable=True),
sa.Column("update_at", sa.DateTime(), nullable=True),
sa.Column("instruction_id", mealie.db.migration_types.GUID(), nullable=True),
sa.Column("reference_id", mealie.db.migration_types.GUID(), nullable=True),
sa.ForeignKeyConstraint(
["instruction_id"],
["recipe_instructions.id"],
),
sa.PrimaryKeyConstraint("id"),
)
op.create_table(
"recipes_ingredients",
sa.Column("created_at", sa.DateTime(), nullable=True),
sa.Column("update_at", sa.DateTime(), nullable=True),
sa.Column("id", sa.Integer(), nullable=False),
sa.Column("position", sa.Integer(), nullable=True),
sa.Column("recipe_id", mealie.db.migration_types.GUID(), nullable=True),
sa.Column("title", sa.String(), nullable=True),
sa.Column("note", sa.String(), nullable=True),
sa.Column("unit_id", mealie.db.migration_types.GUID(), nullable=True),
sa.Column("food_id", mealie.db.migration_types.GUID(), nullable=True),
sa.Column("quantity", sa.Integer(), nullable=True),
sa.Column("reference_id", mealie.db.migration_types.GUID(), nullable=True),
sa.ForeignKeyConstraint(
["food_id"],
["ingredient_foods.id"],
),
sa.ForeignKeyConstraint(
["recipe_id"],
["recipes.id"],
),
sa.ForeignKeyConstraint(
["unit_id"],
["ingredient_units.id"],
),
sa.PrimaryKeyConstraint("id"),
)
op.create_table(
"shopping_list_items",
sa.Column("created_at", sa.DateTime(), nullable=True),
sa.Column("update_at", sa.DateTime(), nullable=True),
sa.Column("id", mealie.db.migration_types.GUID(), nullable=False),
sa.Column("shopping_list_id", mealie.db.migration_types.GUID(), nullable=True),
sa.Column("is_ingredient", sa.Boolean(), nullable=True),
sa.Column("position", sa.Integer(), nullable=False),
sa.Column("checked", sa.Boolean(), nullable=True),
sa.Column("quantity", sa.Float(), nullable=True),
sa.Column("note", sa.String(), nullable=True),
sa.Column("is_food", sa.Boolean(), nullable=True),
sa.Column("unit_id", mealie.db.migration_types.GUID(), nullable=True),
sa.Column("food_id", mealie.db.migration_types.GUID(), nullable=True),
sa.Column("label_id", mealie.db.migration_types.GUID(), nullable=True),
sa.ForeignKeyConstraint(
["food_id"],
["ingredient_foods.id"],
),
sa.ForeignKeyConstraint(
["label_id"],
["multi_purpose_labels.id"],
),
sa.ForeignKeyConstraint(
["shopping_list_id"],
["shopping_lists.id"],
),
sa.ForeignKeyConstraint(
["unit_id"],
["ingredient_units.id"],
),
sa.PrimaryKeyConstraint("id"),
)
op.create_table(
"users_to_favorites",
sa.Column("user_id", mealie.db.migration_types.GUID(), nullable=True),
sa.Column("recipe_id", mealie.db.migration_types.GUID(), nullable=True),
sa.ForeignKeyConstraint(
["recipe_id"],
["recipes.id"],
),
sa.ForeignKeyConstraint(
["user_id"],
["users.id"],
),
)
op.create_table(
"shopping_list_item_recipe_reference",
sa.Column("created_at", sa.DateTime(), nullable=True),
sa.Column("update_at", sa.DateTime(), nullable=True),
sa.Column("id", mealie.db.migration_types.GUID(), nullable=False),
sa.Column("shopping_list_item_id", mealie.db.migration_types.GUID(), nullable=False),
sa.Column("recipe_id", mealie.db.migration_types.GUID(), nullable=True),
sa.Column("recipe_quantity", sa.Float(), nullable=False),
sa.ForeignKeyConstraint(
["recipe_id"],
["recipes.id"],
),
sa.ForeignKeyConstraint(
["shopping_list_item_id"],
["shopping_list_items.id"],
),
sa.PrimaryKeyConstraint("id", "shopping_list_item_id"),
)
op.create_index(
op.f("ix_shopping_list_item_recipe_reference_recipe_id"),
"shopping_list_item_recipe_reference",
["recipe_id"],
unique=False,
)
def downgrade():
op.drop_index(
op.f("ix_shopping_list_item_recipe_reference_recipe_id"), table_name="shopping_list_item_recipe_reference"
)
op.drop_table("shopping_list_item_recipe_reference")
op.drop_table("users_to_favorites")
op.drop_table("shopping_list_items")
op.drop_table("recipes_ingredients")
op.drop_table("recipe_ingredient_ref_link")
op.drop_table("recipe_comments")
op.drop_table("password_reset_tokens")
op.drop_table("long_live_tokens")
op.drop_index(op.f("ix_users_username"), table_name="users")
op.drop_index(op.f("ix_users_group_id"), table_name="users")
op.drop_index(op.f("ix_users_full_name"), table_name="users")
op.drop_index(op.f("ix_users_email"), table_name="users")
op.drop_table("users")
op.drop_index(op.f("ix_shopping_list_recipe_reference_recipe_id"), table_name="shopping_list_recipe_reference")
op.drop_table("shopping_list_recipe_reference")
op.drop_table("report_entries")
op.drop_table("recipes_to_tools")
op.drop_table("recipes_to_tags")
op.drop_table("recipes_to_categories")
op.drop_index(op.f("ix_recipe_share_tokens_group_id"), table_name="recipe_share_tokens")
op.drop_table("recipe_share_tokens")
op.drop_table("recipe_settings")
op.drop_table("recipe_nutrition")
op.drop_table("recipe_instructions")
op.drop_table("recipe_assets")
op.drop_table("plan_rules_to_tags")
op.drop_table("plan_rules_to_categories")
op.drop_table("notes")
op.drop_table("ingredient_foods")
op.drop_table("group_to_categories")
op.drop_index(op.f("ix_group_meal_plans_title"), table_name="group_meal_plans")
op.drop_index(op.f("ix_group_meal_plans_recipe_id"), table_name="group_meal_plans")
op.drop_index(op.f("ix_group_meal_plans_group_id"), table_name="group_meal_plans")
op.drop_index(op.f("ix_group_meal_plans_entry_type"), table_name="group_meal_plans")
op.drop_index(op.f("ix_group_meal_plans_date"), table_name="group_meal_plans")
op.drop_table("group_meal_plans")
op.drop_table("group_events_notifier_options")
op.drop_table("cookbooks_to_categories")
op.drop_table("api_extras")
op.drop_index(op.f("ix_webhook_urls_group_id"), table_name="webhook_urls")
op.drop_table("webhook_urls")
op.drop_index(op.f("ix_tools_slug"), table_name="tools")
op.drop_index(op.f("ix_tools_name"), table_name="tools")
op.drop_table("tools")
op.drop_index(op.f("ix_tags_slug"), table_name="tags")
op.drop_index(op.f("ix_tags_name"), table_name="tags")
op.drop_index(op.f("ix_tags_group_id"), table_name="tags")
op.drop_table("tags")
op.drop_index(op.f("ix_shopping_lists_group_id"), table_name="shopping_lists")
op.drop_table("shopping_lists")
op.drop_index(op.f("ix_server_tasks_group_id"), table_name="server_tasks")
op.drop_table("server_tasks")
op.drop_index(op.f("ix_recipes_user_id"), table_name="recipes")
op.drop_index(op.f("ix_recipes_slug"), table_name="recipes")
op.drop_index(op.f("ix_recipes_group_id"), table_name="recipes")
op.drop_table("recipes")
op.drop_index(op.f("ix_multi_purpose_labels_group_id"), table_name="multi_purpose_labels")
op.drop_table("multi_purpose_labels")
op.drop_index(op.f("ix_invite_tokens_token"), table_name="invite_tokens")
op.drop_table("invite_tokens")
op.drop_table("ingredient_units")
op.drop_index(op.f("ix_group_reports_group_id"), table_name="group_reports")
op.drop_index(op.f("ix_group_reports_category"), table_name="group_reports")
op.drop_table("group_reports")
op.drop_index(op.f("ix_group_preferences_group_id"), table_name="group_preferences")
op.drop_table("group_preferences")
op.drop_table("group_meal_plan_rules")
op.drop_index(op.f("ix_group_events_notifiers_group_id"), table_name="group_events_notifiers")
op.drop_table("group_events_notifiers")
op.drop_index(op.f("ix_group_data_exports_group_id"), table_name="group_data_exports")
op.drop_table("group_data_exports")
op.drop_table("cookbooks")
op.drop_index(op.f("ix_categories_slug"), table_name="categories")
op.drop_index(op.f("ix_categories_name"), table_name="categories")
op.drop_index(op.f("ix_categories_group_id"), table_name="categories")
op.drop_table("categories")
op.drop_index(op.f("ix_groups_name"), table_name="groups")
op.drop_table("groups")

View file

@ -18,7 +18,7 @@ ENV = BASE_DIR.joinpath(".env")
dotenv.load_dotenv(ENV)
PRODUCTION = os.getenv("PRODUCTION", "True").lower() in ["true", "1"]
TESTING = os.getenv("TESTING", "True").lower() in ["true", "1"]
TESTING = os.getenv("TESTING", "False").lower() in ["true", "1"]
def determine_data_dir() -> Path:

View file

@ -1,7 +1,13 @@
from pathlib import Path
from sqlalchemy import engine
from alembic import command, config, script
from alembic.config import Config
from alembic.runtime import migration
from mealie.core import root_logger
from mealie.core.config import get_app_settings
from mealie.db.db_setup import create_session, engine
from mealie.db.models._model_base import SqlAlchemyBase
from mealie.db.db_setup import create_session
from mealie.repos.all_repositories import get_repositories
from mealie.repos.repository_factory import AllRepositories
from mealie.repos.seed.init_users import default_user_init
@ -9,15 +15,11 @@ from mealie.repos.seed.seeders import IngredientFoodsSeeder, IngredientUnitsSeed
from mealie.schema.user.user import GroupBase
from mealie.services.group_services.group_utils import create_new_group
PROJECT_DIR = Path(__file__).parent.parent.parent
logger = root_logger.get_logger("init_db")
def create_all_models():
import mealie.db.models._all_models # noqa: F401
SqlAlchemyBase.metadata.create_all(engine)
def init_db(db: AllRepositories) -> None:
# TODO: Port other seed data to use abstract seeder class
default_group_init(db)
@ -42,24 +44,33 @@ def default_group_init(db: AllRepositories):
create_new_group(db, GroupBase(name=settings.DEFAULT_GROUP))
# Adapted from https://alembic.sqlalchemy.org/en/latest/cookbook.html#test-current-database-revision-is-at-head-s
def db_is_at_head(alembic_cfg: config.Config) -> bool:
settings = get_app_settings()
url = settings.DB_URL
connectable = engine.create_engine(url)
directory = script.ScriptDirectory.from_config(alembic_cfg)
with connectable.begin() as connection:
context = migration.MigrationContext.configure(connection)
return set(context.get_current_heads()) == set(directory.get_heads())
def main():
create_all_models()
alembic_cfg = Config(str(PROJECT_DIR / "alembic.ini"))
if db_is_at_head(alembic_cfg):
logger.info("Migration not needed.")
else:
logger.info("Migration needed. Performing migration...")
command.upgrade(alembic_cfg, "head")
session = create_session()
db = get_repositories(session)
try:
init_user = db.users.get_all()
if not init_user:
raise Exception("No users found in database")
except Exception:
init_db(db)
return
if init_user:
logger.info("Database Exists")
logger.info("Database exists")
else:
logger.info("Database Doesn't Exists, Initializing...")
logger.info("Database contains no users, initializing...")
init_db(db)

View file

@ -0,0 +1 @@
from mealie.db.models._model_utils.guid import GUID # noqa: F401

View file

@ -10,10 +10,11 @@ from mealie.db.models._model_utils.guid import GUID
from .._model_base import BaseMixins, SqlAlchemyBase
from .._model_utils import auto_init
from ..users import users_to_favorites
from ..users.user_to_favorite import users_to_favorites
from .api_extras import ApiExtras
from .assets import RecipeAsset
from .category import recipes_to_categories
from .comment import RecipeComment
from .ingredient import RecipeIngredient
from .instruction import RecipeInstruction
from .note import Note
@ -50,7 +51,7 @@ class RecipeModel(SqlAlchemyBase, BaseMixins):
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"), index=True)
user_id = sa.Column(GUID, sa.ForeignKey("users.id", use_alter=True), index=True)
user = orm.relationship("User", uselist=False, foreign_keys=[user_id])
meal_entries = orm.relationship("GroupMealPlan", back_populates="recipe", cascade="all, delete-orphan")
@ -93,7 +94,9 @@ class RecipeModel(SqlAlchemyBase, BaseMixins):
RecipeShareTokenModel, back_populates="recipe", cascade="all, delete, delete-orphan"
)
comments: list = orm.relationship("RecipeComment", back_populates="recipe", cascade="all, delete, delete-orphan")
comments: list[RecipeComment] = orm.relationship(
"RecipeComment", back_populates="recipe", cascade="all, delete, delete-orphan"
)
# Mealie Specific
settings = orm.relationship("RecipeSettings", uselist=False, cascade="all, delete-orphan")

View file

@ -5,7 +5,6 @@ from mealie.db.models._model_utils.guid import GUID
from .._model_base import BaseMixins, SqlAlchemyBase
from .._model_utils import auto_init
from ..group import Group
from .user_to_favorite import users_to_favorites
@ -74,6 +73,8 @@ class User(SqlAlchemyBase, BaseMixins):
settings = get_app_settings()
group = settings.DEFAULT_GROUP
from mealie.db.models.group import Group
self.group = Group.get_ref(session, group)
self.favorite_recipes = []
@ -91,6 +92,8 @@ class User(SqlAlchemyBase, BaseMixins):
self.full_name = full_name
self.email = email
from mealie.db.models.group import Group
self.group = Group.get_ref(session, group)
if self.username is None:

45
poetry.lock generated
View file

@ -6,6 +6,21 @@ category = "main"
optional = false
python-versions = "*"
[[package]]
name = "alembic"
version = "1.7.5"
description = "A database migration tool for SQLAlchemy."
category = "main"
optional = false
python-versions = ">=3.6"
[package.dependencies]
Mako = "*"
SQLAlchemy = ">=1.3.0"
[package.extras]
tz = ["python-dateutil"]
[[package]]
name = "aniso8601"
version = "7.0.0"
@ -585,6 +600,21 @@ html5 = ["html5lib"]
htmlsoup = ["beautifulsoup4"]
source = ["Cython (>=0.29.7)"]
[[package]]
name = "mako"
version = "1.1.6"
description = "A super-fast templating language that borrows the best ideas from the existing templating languages."
category = "main"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
[package.dependencies]
MarkupSafe = ">=0.9.2"
[package.extras]
babel = ["babel"]
lingua = ["lingua"]
[[package]]
name = "markdown"
version = "3.3.6"
@ -1458,13 +1488,17 @@ pgsql = ["psycopg2-binary"]
[metadata]
lock-version = "1.1"
python-versions = "^3.10"
content-hash = "869d87ae4fb7dff15f0b53dddde737ce8a6c2b13217d6690f4e2b90d3198a18c"
content-hash = "94bda5d85d0fd3f5fb0c3ce59ff7cf515eaa3210c4533862e1c8bd22eecec8dd"
[metadata.files]
aiofiles = [
{file = "aiofiles-0.5.0-py3-none-any.whl", hash = "sha256:377fdf7815cc611870c59cbd07b68b180841d2a2b79812d8c218be02448c2acb"},
{file = "aiofiles-0.5.0.tar.gz", hash = "sha256:98e6bcfd1b50f97db4980e182ddd509b7cc35909e903a8fe50d8849e02d815af"},
]
alembic = [
{file = "alembic-1.7.5-py3-none-any.whl", hash = "sha256:a9dde941534e3d7573d9644e8ea62a2953541e27bc1793e166f60b777ae098b4"},
{file = "alembic-1.7.5.tar.gz", hash = "sha256:7c328694a2e68f03ee971e63c3bd885846470373a5b532cf2c9f1601c413b153"},
]
aniso8601 = [
{file = "aniso8601-7.0.0-py2.py3-none-any.whl", hash = "sha256:d10a4bf949f619f719b227ef5386e31f49a2b6d453004b21f02661ccc8670c7b"},
{file = "aniso8601-7.0.0.tar.gz", hash = "sha256:513d2b6637b7853806ae79ffaca6f3e8754bdd547048f5ccc1420aec4b714f1e"},
@ -1703,6 +1737,7 @@ greenlet = [
{file = "greenlet-1.1.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:97e5306482182170ade15c4b0d8386ded995a07d7cc2ca8f27958d34d6736497"},
{file = "greenlet-1.1.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e6a36bb9474218c7a5b27ae476035497a6990e21d04c279884eb10d9b290f1b1"},
{file = "greenlet-1.1.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:abb7a75ed8b968f3061327c433a0fbd17b729947b400747c334a9c29a9af6c58"},
{file = "greenlet-1.1.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b336501a05e13b616ef81ce329c0e09ac5ed8c732d9ba7e3e983fcc1a9e86965"},
{file = "greenlet-1.1.2-cp310-cp310-win_amd64.whl", hash = "sha256:14d4f3cd4e8b524ae9b8aa567858beed70c392fdec26dbdb0a8a418392e71708"},
{file = "greenlet-1.1.2-cp35-cp35m-macosx_10_14_x86_64.whl", hash = "sha256:17ff94e7a83aa8671a25bf5b59326ec26da379ace2ebc4411d690d80a7fbcf23"},
{file = "greenlet-1.1.2-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:9f3cba480d3deb69f6ee2c1825060177a22c7826431458c697df88e6aeb3caee"},
@ -1715,6 +1750,7 @@ greenlet = [
{file = "greenlet-1.1.2-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f9d29ca8a77117315101425ec7ec2a47a22ccf59f5593378fc4077ac5b754fce"},
{file = "greenlet-1.1.2-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:21915eb821a6b3d9d8eefdaf57d6c345b970ad722f856cd71739493ce003ad08"},
{file = "greenlet-1.1.2-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eff9d20417ff9dcb0d25e2defc2574d10b491bf2e693b4e491914738b7908168"},
{file = "greenlet-1.1.2-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:b8c008de9d0daba7b6666aa5bbfdc23dcd78cafc33997c9b7741ff6353bafb7f"},
{file = "greenlet-1.1.2-cp36-cp36m-win32.whl", hash = "sha256:32ca72bbc673adbcfecb935bb3fb1b74e663d10a4b241aaa2f5a75fe1d1f90aa"},
{file = "greenlet-1.1.2-cp36-cp36m-win_amd64.whl", hash = "sha256:f0214eb2a23b85528310dad848ad2ac58e735612929c8072f6093f3585fd342d"},
{file = "greenlet-1.1.2-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:b92e29e58bef6d9cfd340c72b04d74c4b4e9f70c9fa7c78b674d1fec18896dc4"},
@ -1723,6 +1759,7 @@ greenlet = [
{file = "greenlet-1.1.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1e12bdc622676ce47ae9abbf455c189e442afdde8818d9da983085df6312e7a1"},
{file = "greenlet-1.1.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8c790abda465726cfb8bb08bd4ca9a5d0a7bd77c7ac1ca1b839ad823b948ea28"},
{file = "greenlet-1.1.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f276df9830dba7a333544bd41070e8175762a7ac20350786b322b714b0e654f5"},
{file = "greenlet-1.1.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:8c5d5b35f789a030ebb95bff352f1d27a93d81069f2adb3182d99882e095cefe"},
{file = "greenlet-1.1.2-cp37-cp37m-win32.whl", hash = "sha256:64e6175c2e53195278d7388c454e0b30997573f3f4bd63697f88d855f7a6a1fc"},
{file = "greenlet-1.1.2-cp37-cp37m-win_amd64.whl", hash = "sha256:b11548073a2213d950c3f671aa88e6f83cda6e2fb97a8b6317b1b5b33d850e06"},
{file = "greenlet-1.1.2-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:9633b3034d3d901f0a46b7939f8c4d64427dfba6bbc5a36b1a67364cf148a1b0"},
@ -1731,6 +1768,7 @@ greenlet = [
{file = "greenlet-1.1.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e859fcb4cbe93504ea18008d1df98dee4f7766db66c435e4882ab35cf70cac43"},
{file = "greenlet-1.1.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:00e44c8afdbe5467e4f7b5851be223be68adb4272f44696ee71fe46b7036a711"},
{file = "greenlet-1.1.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec8c433b3ab0419100bd45b47c9c8551248a5aee30ca5e9d399a0b57ac04651b"},
{file = "greenlet-1.1.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:2bde6792f313f4e918caabc46532aa64aa27a0db05d75b20edfc5c6f46479de2"},
{file = "greenlet-1.1.2-cp38-cp38-win32.whl", hash = "sha256:288c6a76705dc54fba69fbcb59904ae4ad768b4c768839b8ca5fdadec6dd8cfd"},
{file = "greenlet-1.1.2-cp38-cp38-win_amd64.whl", hash = "sha256:8d2f1fb53a421b410751887eb4ff21386d119ef9cde3797bf5e7ed49fb51a3b3"},
{file = "greenlet-1.1.2-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:166eac03e48784a6a6e0e5f041cfebb1ab400b394db188c48b3a84737f505b67"},
@ -1739,6 +1777,7 @@ greenlet = [
{file = "greenlet-1.1.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b1692f7d6bc45e3200844be0dba153612103db241691088626a33ff1f24a0d88"},
{file = "greenlet-1.1.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7227b47e73dedaa513cdebb98469705ef0d66eb5a1250144468e9c3097d6b59b"},
{file = "greenlet-1.1.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7ff61ff178250f9bb3cd89752df0f1dd0e27316a8bd1465351652b1b4a4cdfd3"},
{file = "greenlet-1.1.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:0051c6f1f27cb756ffc0ffbac7d2cd48cb0362ac1736871399a739b2885134d3"},
{file = "greenlet-1.1.2-cp39-cp39-win32.whl", hash = "sha256:f70a9e237bb792c7cc7e44c531fd48f5897961701cdaa06cf22fc14965c496cf"},
{file = "greenlet-1.1.2-cp39-cp39-win_amd64.whl", hash = "sha256:013d61294b6cd8fe3242932c1c5e36e5d1db2c8afb58606c5a67efce62c1f5fd"},
{file = "greenlet-1.1.2.tar.gz", hash = "sha256:e30f5ea4ae2346e62cedde8794a56858a67b878dd79f7df76a0767e356b1744a"},
@ -1904,6 +1943,10 @@ lxml = [
{file = "lxml-4.7.1-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:447d5009d6b5447b2f237395d0018901dcc673f7d9f82ba26c1b9f9c3b444b60"},
{file = "lxml-4.7.1.tar.gz", hash = "sha256:a1613838aa6b89af4ba10a0f3a972836128801ed008078f8c1244e65958f1b24"},
]
mako = [
{file = "Mako-1.1.6-py2.py3-none-any.whl", hash = "sha256:afaf8e515d075b22fad7d7b8b30e4a1c90624ff2f3733a06ec125f5a5f043a57"},
{file = "Mako-1.1.6.tar.gz", hash = "sha256:4e9e345a41924a954251b95b4b28e14a301145b544901332e658907a7464b6b2"},
]
markdown = [
{file = "Markdown-3.3.6-py3-none-any.whl", hash = "sha256:9923332318f843411e9932237530df53162e29dc7a4e2b91e35764583c46c9a3"},
{file = "Markdown-3.3.6.tar.gz", hash = "sha256:76df8ae32294ec39dcf89340382882dfa12975f87f45c3ed1ecdb1e8cefc7006"},

View file

@ -17,6 +17,7 @@ fastapi = "^0.71.0"
uvicorn = {extras = ["standard"], version = "^0.13.0"}
APScheduler = "^3.8.1"
SQLAlchemy = "^1.4.29"
alembic = "^1.7.5"
Jinja2 = "^2.11.2"
python-dotenv = "^0.15.0"
python-slugify = "^4.0.1"

View file

@ -0,0 +1,14 @@
import pytest
# Test that alembic revisions are applicable and result in the current database
# See https://github.com/sqlalchemy/alembic/issues/724 for inspiration
@pytest.mark.skip("TODO: Implement")
def test_alembic_revisions_are_applicable():
pass
@pytest.mark.skip("TODO: Implement")
def test_alembic_revisions_are_up_to_date():
pass