fix: Make Mealie Timezone-Aware (#3847)

Co-authored-by: boc-the-git <3479092+boc-the-git@users.noreply.github.com>
This commit is contained in:
Michael Genson
2024-07-08 16:12:20 -05:00
committed by GitHub
parent 17f9eef551
commit d5f7a883df
69 changed files with 250 additions and 176 deletions

View File

@@ -4,11 +4,13 @@ from sqlalchemy import DateTime, Integer
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
from text_unidecode import unidecode
from ._model_utils.datetime import get_utc_now
class SqlAlchemyBase(DeclarativeBase):
id: Mapped[int] = mapped_column(Integer, primary_key=True)
created_at: Mapped[datetime | None] = mapped_column(DateTime, default=datetime.now, index=True)
update_at: Mapped[datetime | None] = mapped_column(DateTime, default=datetime.now, onupdate=datetime.now)
created_at: Mapped[datetime | None] = mapped_column(DateTime, default=get_utc_now, index=True)
update_at: Mapped[datetime | None] = mapped_column(DateTime, default=get_utc_now, onupdate=get_utc_now)
@classmethod
def normalize(cls, val: str) -> str:

View File

@@ -1,7 +0,0 @@
from .auto_init import auto_init
from .guid import GUID
__all__ = [
"auto_init",
"GUID",
]

View File

@@ -0,0 +1,15 @@
from datetime import datetime, timezone
def get_utc_now():
"""
Returns the current time in UTC.
"""
return datetime.now(timezone.utc)
def get_utc_today():
"""
Returns the current date in UTC.
"""
return datetime.now(timezone.utc).date()

View File

@@ -4,13 +4,14 @@ from sqlalchemy import Boolean, ForeignKey, Integer, String, orm
from sqlalchemy.orm import Mapped, mapped_column
from .._model_base import BaseMixins, SqlAlchemyBase
from .._model_utils import auto_init, guid
from .._model_utils import guid
from .._model_utils.auto_init import auto_init
from ..recipe.category import Category, cookbooks_to_categories
from ..recipe.tag import Tag, cookbooks_to_tags
from ..recipe.tool import Tool, cookbooks_to_tools
if TYPE_CHECKING:
from group import Group
from .group import Group
class CookBook(SqlAlchemyBase, BaseMixins):

View File

@@ -4,10 +4,11 @@ from sqlalchemy import Boolean, ForeignKey, String, orm
from sqlalchemy.orm import Mapped, mapped_column
from .._model_base import BaseMixins, SqlAlchemyBase
from .._model_utils import GUID, auto_init
from .._model_utils.auto_init import auto_init
from .._model_utils.guid import GUID
if TYPE_CHECKING:
from group import Group
from .group import Group
class GroupEventNotifierOptionsModel(SqlAlchemyBase, BaseMixins):

View File

@@ -4,10 +4,11 @@ from sqlalchemy import ForeignKey, String, orm
from sqlalchemy.orm import Mapped, mapped_column
from .._model_base import BaseMixins, SqlAlchemyBase
from .._model_utils import GUID, auto_init
from .._model_utils.auto_init import auto_init
from .._model_utils.guid import GUID
if TYPE_CHECKING:
from group import Group
from .group import Group
class GroupDataExportsModel(SqlAlchemyBase, BaseMixins):

View File

@@ -11,7 +11,8 @@ from mealie.core.config import get_app_settings
from mealie.db.models.labels import MultiPurposeLabel
from .._model_base import BaseMixins, SqlAlchemyBase
from .._model_utils import GUID, auto_init
from .._model_utils.auto_init import auto_init
from .._model_utils.guid import GUID
from ..group.invite_tokens import GroupInviteToken
from ..group.webhooks import GroupWebhooksModel
from ..recipe.category import Category, group_to_categories

View File

@@ -4,10 +4,11 @@ from sqlalchemy import ForeignKey, Integer, String, orm
from sqlalchemy.orm import Mapped, mapped_column
from .._model_base import BaseMixins, SqlAlchemyBase
from .._model_utils import auto_init, guid
from .._model_utils import guid
from .._model_utils.auto_init import auto_init
if TYPE_CHECKING:
from group import Group
from .group import Group
class GroupInviteToken(SqlAlchemyBase, BaseMixins):

View File

@@ -7,14 +7,14 @@ from sqlalchemy.orm import Mapped, mapped_column
from mealie.db.models.recipe.tag import Tag, plan_rules_to_tags
from .._model_base import BaseMixins, SqlAlchemyBase
from .._model_utils import GUID, auto_init
from .._model_utils.auto_init import auto_init
from .._model_utils.guid import GUID
from ..recipe.category import Category, plan_rules_to_categories
if TYPE_CHECKING:
from group import Group
from ..recipe import RecipeModel
from ..users import User
from .group import Group
class GroupMealPlanRules(BaseMixins, SqlAlchemyBase):

View File

@@ -5,11 +5,11 @@ import sqlalchemy.orm as orm
from sqlalchemy.orm import Mapped, mapped_column
from .._model_base import BaseMixins, SqlAlchemyBase
from .._model_utils import auto_init
from .._model_utils.auto_init import auto_init
from .._model_utils.guid import GUID
if TYPE_CHECKING:
from group import Group
from .group import Group
class GroupPreferencesModel(SqlAlchemyBase, BaseMixins):

View File

@@ -4,10 +4,11 @@ from sqlalchemy import ForeignKey, String
from sqlalchemy.orm import Mapped, mapped_column, relationship
from .._model_base import BaseMixins, SqlAlchemyBase
from .._model_utils import GUID, auto_init
from .._model_utils.auto_init import auto_init
from .._model_utils.guid import GUID
if TYPE_CHECKING:
from group import Group
from .group import Group
class GroupRecipeAction(SqlAlchemyBase, BaseMixins):

View File

@@ -8,11 +8,12 @@ from sqlalchemy.sql.sqltypes import Boolean, DateTime, String
from mealie.db.models._model_base import BaseMixins, SqlAlchemyBase
from .._model_utils import auto_init
from .._model_utils.auto_init import auto_init
from .._model_utils.datetime import get_utc_now
from .._model_utils.guid import GUID
if TYPE_CHECKING:
from group import Group
from .group import Group
class ReportEntryModel(SqlAlchemyBase, BaseMixins):
@@ -22,7 +23,7 @@ class ReportEntryModel(SqlAlchemyBase, BaseMixins):
success: Mapped[bool | None] = mapped_column(Boolean, default=False)
message: Mapped[str] = mapped_column(String, nullable=True)
exception: Mapped[str] = mapped_column(String, nullable=True)
timestamp: Mapped[datetime] = mapped_column(DateTime, nullable=False, default=datetime.utcnow)
timestamp: Mapped[datetime] = mapped_column(DateTime, nullable=False, default=get_utc_now)
report_id: Mapped[GUID] = mapped_column(GUID, ForeignKey("group_reports.id"), nullable=False, index=True)
report: Mapped["ReportModel"] = orm.relationship("ReportModel", back_populates="entries")
@@ -39,7 +40,7 @@ class ReportModel(SqlAlchemyBase, BaseMixins):
name: Mapped[str] = mapped_column(String, nullable=False)
status: Mapped[str] = mapped_column(String, nullable=False)
category: Mapped[str] = mapped_column(String, index=True, nullable=False)
timestamp: Mapped[datetime] = mapped_column(DateTime, nullable=False, default=datetime.utcnow)
timestamp: Mapped[datetime] = mapped_column(DateTime, nullable=False, default=get_utc_now)
entries: Mapped[list[ReportEntryModel]] = orm.relationship(
ReportEntryModel, back_populates="report", cascade="all, delete-orphan"

View File

@@ -1,5 +1,5 @@
from contextvars import ContextVar
from datetime import datetime
from datetime import datetime, timezone
from typing import TYPE_CHECKING, Optional
from pydantic import ConfigDict
@@ -11,7 +11,8 @@ from mealie.db.models.labels import MultiPurposeLabel
from mealie.db.models.recipe.api_extras import ShoppingListExtras, ShoppingListItemExtras, api_extras
from .._model_base import BaseMixins, SqlAlchemyBase
from .._model_utils import GUID, auto_init
from .._model_utils.auto_init import auto_init
from .._model_utils.guid import GUID
from ..recipe.ingredient import IngredientFoodModel, IngredientUnitModel
if TYPE_CHECKING:
@@ -203,7 +204,7 @@ def update_shopping_lists(session: orm.Session, _):
if not shopping_list:
continue
shopping_list.update_at = datetime.now()
shopping_list.update_at = datetime.now(timezone.utc)
local_session.commit()
except Exception:
local_session.rollback()

View File

@@ -1,14 +1,15 @@
from datetime import datetime, time
from datetime import datetime, time, timezone
from typing import TYPE_CHECKING, Optional
from sqlalchemy import Boolean, ForeignKey, String, Time, orm
from sqlalchemy.orm import Mapped, mapped_column
from .._model_base import BaseMixins, SqlAlchemyBase
from .._model_utils import GUID, auto_init
from .._model_utils.auto_init import auto_init
from .._model_utils.guid import GUID
if TYPE_CHECKING:
from group import Group
from .group import Group
class GroupWebhooksModel(SqlAlchemyBase, BaseMixins):
@@ -24,7 +25,7 @@ class GroupWebhooksModel(SqlAlchemyBase, BaseMixins):
# New Fields
webhook_type: Mapped[str | None] = mapped_column(String, default="") # Future use for different types of webhooks
scheduled_time: Mapped[time | None] = mapped_column(Time, default=lambda: datetime.now().time())
scheduled_time: Mapped[time | None] = mapped_column(Time, default=lambda: datetime.now(timezone.utc).time())
# Columne is no longer used but is kept for since it's super annoying to
# delete a column in SQLite and it's not a big deal to keep it around

View File

@@ -5,13 +5,13 @@ from sqlalchemy.orm import Mapped, mapped_column
from mealie.db.models._model_base import BaseMixins, SqlAlchemyBase
from ._model_utils import auto_init
from ._model_utils.auto_init import auto_init
from ._model_utils.guid import GUID
if TYPE_CHECKING:
from group import Group
from group.shopping_list import ShoppingListItem, ShoppingListMultiPurposeLabel
from recipe import IngredientFoodModel
from .group.group import Group
from .group.shopping_list import ShoppingListItem, ShoppingListMultiPurposeLabel
from .recipe import IngredientFoodModel
class MultiPurposeLabel(SqlAlchemyBase, BaseMixins):

View File

@@ -4,7 +4,7 @@ from sqlalchemy import ForeignKey, String, orm
from sqlalchemy.orm import Mapped, mapped_column
from mealie.db.models._model_base import BaseMixins, SqlAlchemyBase
from mealie.db.models._model_utils import auto_init
from mealie.db.models._model_utils.auto_init import auto_init
from mealie.db.models._model_utils.guid import GUID
if TYPE_CHECKING:

View File

@@ -9,7 +9,7 @@ from mealie.db.models._model_base import BaseMixins, SqlAlchemyBase
from mealie.db.models.labels import MultiPurposeLabel
from mealie.db.models.recipe.api_extras import IngredientFoodExtras, api_extras
from .._model_utils import auto_init
from .._model_utils.auto_init import auto_init
from .._model_utils.guid import GUID
if TYPE_CHECKING:

View File

@@ -3,7 +3,7 @@ from sqlalchemy import ForeignKey, Integer, String, orm
from sqlalchemy.orm import Mapped, mapped_column
from .._model_base import BaseMixins, SqlAlchemyBase
from .._model_utils import auto_init
from .._model_utils.auto_init import auto_init
from .._model_utils.guid import GUID

View File

@@ -1,4 +1,4 @@
from datetime import date, datetime
from datetime import date, datetime, timezone
from typing import TYPE_CHECKING
import sqlalchemy as sa
@@ -10,10 +10,11 @@ from sqlalchemy.orm import Mapped, mapped_column, validates
from sqlalchemy.orm.attributes import get_history
from sqlalchemy.orm.session import object_session
from mealie.db.models._model_utils.auto_init import auto_init
from mealie.db.models._model_utils.datetime import get_utc_today
from mealie.db.models._model_utils.guid import GUID
from .._model_base import BaseMixins, SqlAlchemyBase
from .._model_utils import auto_init
from ..users.user_to_recipe import UserToRecipe
from .api_extras import ApiExtras, api_extras
from .assets import RecipeAsset
@@ -125,7 +126,7 @@ class RecipeModel(SqlAlchemyBase, BaseMixins):
is_ocr_recipe: Mapped[bool | None] = mapped_column(sa.Boolean, default=False)
# Time Stamp Properties
date_added: Mapped[date | None] = mapped_column(sa.Date, default=date.today)
date_added: Mapped[date | None] = mapped_column(sa.Date, default=get_utc_today)
date_updated: Mapped[datetime | None] = mapped_column(sa.DateTime)
last_made: Mapped[datetime | None] = mapped_column(sa.DateTime)
@@ -194,7 +195,7 @@ class RecipeModel(SqlAlchemyBase, BaseMixins):
if notes:
self.notes = [Note(**n) for n in notes]
self.date_updated = datetime.now()
self.date_updated = datetime.now(timezone.utc)
# SQLAlchemy events do not seem to register things that are set during auto_init
if name is not None:

View File

@@ -1,11 +1,11 @@
from datetime import datetime
from datetime import datetime, timezone
from typing import TYPE_CHECKING
from sqlalchemy import DateTime, ForeignKey, String
from sqlalchemy.orm import Mapped, mapped_column, relationship
from .._model_base import BaseMixins, SqlAlchemyBase
from .._model_utils import auto_init
from .._model_utils.auto_init import auto_init
from .._model_utils.guid import GUID
if TYPE_CHECKING:
@@ -42,4 +42,4 @@ class RecipeTimelineEvent(SqlAlchemyBase, BaseMixins):
timestamp=None,
**_,
) -> None:
self.timestamp = timestamp or datetime.now()
self.timestamp = timestamp or datetime.now(timezone.utc)

View File

@@ -1,4 +1,4 @@
from datetime import datetime, timedelta
from datetime import datetime, timedelta, timezone
from typing import TYPE_CHECKING
from uuid import uuid4
@@ -6,14 +6,15 @@ import sqlalchemy as sa
from sqlalchemy.orm import Mapped, mapped_column
from mealie.db.models._model_base import BaseMixins, SqlAlchemyBase
from mealie.db.models._model_utils import GUID, auto_init
from mealie.db.models._model_utils.auto_init import auto_init
from mealie.db.models._model_utils.guid import GUID
if TYPE_CHECKING:
from . import RecipeModel
def defaut_expires_at_time() -> datetime:
return datetime.utcnow() + timedelta(days=30)
return datetime.now(timezone.utc) + timedelta(days=30)
class RecipeShareTokenModel(SqlAlchemyBase, BaseMixins):

View File

@@ -5,7 +5,7 @@ from sqlalchemy import Boolean, Column, ForeignKey, String, Table, UniqueConstra
from sqlalchemy.orm import Mapped, mapped_column
from mealie.db.models._model_base import BaseMixins, SqlAlchemyBase
from mealie.db.models._model_utils import auto_init
from mealie.db.models._model_utils.auto_init import auto_init
from mealie.db.models._model_utils.guid import GUID
if TYPE_CHECKING:

View File

@@ -7,7 +7,7 @@ from sqlalchemy.orm import Mapped, mapped_column
from mealie.db.models._model_base import BaseMixins, SqlAlchemyBase
from mealie.db.models._model_utils.guid import GUID
from .._model_utils import auto_init
from .._model_utils.auto_init import auto_init
if TYPE_CHECKING:
from ..group import Group

View File

@@ -4,7 +4,7 @@ from sqlalchemy import ForeignKey, String, orm
from sqlalchemy.orm import Mapped, mapped_column
from .._model_base import BaseMixins, SqlAlchemyBase
from .._model_utils import GUID
from .._model_utils.guid import GUID
if TYPE_CHECKING:
from .users import User

View File

@@ -4,7 +4,8 @@ from sqlalchemy.orm import Mapped, mapped_column
from sqlalchemy.orm.session import Session
from .._model_base import BaseMixins, SqlAlchemyBase
from .._model_utils import GUID, auto_init
from .._model_utils.auto_init import auto_init
from .._model_utils.guid import GUID
class UserToRecipe(SqlAlchemyBase, BaseMixins):

View File

@@ -8,10 +8,10 @@ from sqlalchemy.ext.hybrid import hybrid_property
from sqlalchemy.orm import Mapped, mapped_column
from mealie.core.config import get_app_settings
from mealie.db.models._model_utils.auto_init import auto_init
from mealie.db.models._model_utils.guid import GUID
from .._model_base import BaseMixins, SqlAlchemyBase
from .._model_utils import auto_init
from .user_to_recipe import UserToRecipe
if TYPE_CHECKING: