From 3d4405cd42759309f2a9e5b58bd5ab43e8e52fbb Mon Sep 17 00:00:00 2001 From: Michael Genson <71845777+michael-genson@users.noreply.github.com> Date: Fri, 23 Feb 2024 16:03:56 +0000 Subject: [PATCH 01/15] order shopping lists alphabetically --- .../components/Domain/Group/GroupMealPlanDayContextMenu.vue | 2 +- frontend/components/Domain/Recipe/RecipeContextMenu.vue | 2 +- frontend/pages/shopping-lists/index.vue | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/frontend/components/Domain/Group/GroupMealPlanDayContextMenu.vue b/frontend/components/Domain/Group/GroupMealPlanDayContextMenu.vue index 9eb740a81a..968645e6c6 100644 --- a/frontend/components/Domain/Group/GroupMealPlanDayContextMenu.vue +++ b/frontend/components/Domain/Group/GroupMealPlanDayContextMenu.vue @@ -107,7 +107,7 @@ export default defineComponent({ }) async function getShoppingLists() { - const { data } = await api.shopping.lists.getAll(); + const { data } = await api.shopping.lists.getAll(1, -1, { orderBy: "name", orderDirection: "asc" }); if (data) { shoppingLists.value = data.items ?? []; } diff --git a/frontend/components/Domain/Recipe/RecipeContextMenu.vue b/frontend/components/Domain/Recipe/RecipeContextMenu.vue index fa1d6133f0..3207c12343 100644 --- a/frontend/components/Domain/Recipe/RecipeContextMenu.vue +++ b/frontend/components/Domain/Recipe/RecipeContextMenu.vue @@ -321,7 +321,7 @@ export default defineComponent({ const recipeRefWithScale = computed(() => recipeRef.value ? { scale: props.recipeScale, ...recipeRef.value } : undefined); async function getShoppingLists() { - const { data } = await api.shopping.lists.getAll(); + const { data } = await api.shopping.lists.getAll(1, -1, { orderBy: "name", orderDirection: "asc" }); if (data) { shoppingLists.value = data.items ?? []; } diff --git a/frontend/pages/shopping-lists/index.vue b/frontend/pages/shopping-lists/index.vue index 9a504f7be1..ce128aaf92 100644 --- a/frontend/pages/shopping-lists/index.vue +++ b/frontend/pages/shopping-lists/index.vue @@ -63,7 +63,7 @@ export default defineComponent({ }, useAsyncKey()); async function fetchShoppingLists() { - const { data } = await userApi.shopping.lists.getAll(); + const { data } = await userApi.shopping.lists.getAll(1, -1, { orderBy: "name", orderDirection: "asc" }); if (!data) { return []; From 74d6f583632d1c2ecee7d1b8d65a5ac4c31171cb Mon Sep 17 00:00:00 2001 From: Michael Genson <71845777+michael-genson@users.noreply.github.com> Date: Fri, 23 Feb 2024 16:49:37 +0000 Subject: [PATCH 02/15] added user to shopping list --- ...298bb460ffd_added_user_to_shopping_list.py | 85 +++++++++++++++++++ mealie/db/models/group/shopping_list.py | 3 + mealie/db/models/users/users.py | 3 +- 3 files changed, 90 insertions(+), 1 deletion(-) create mode 100644 alembic/versions/2024-02-23-16.15.07_2298bb460ffd_added_user_to_shopping_list.py diff --git a/alembic/versions/2024-02-23-16.15.07_2298bb460ffd_added_user_to_shopping_list.py b/alembic/versions/2024-02-23-16.15.07_2298bb460ffd_added_user_to_shopping_list.py new file mode 100644 index 0000000000..024497e921 --- /dev/null +++ b/alembic/versions/2024-02-23-16.15.07_2298bb460ffd_added_user_to_shopping_list.py @@ -0,0 +1,85 @@ +"""added user to shopping list + +Revision ID: 2298bb460ffd +Revises: ba1e4a6cfe99 +Create Date: 2024-02-23 16:15:07.115641 + +""" + +from uuid import UUID + +import sqlalchemy as sa +from sqlalchemy import orm + +import mealie.db.migration_types +from alembic import op + +# revision identifiers, used by Alembic. +revision = "2298bb460ffd" +down_revision = "ba1e4a6cfe99" +branch_labels = None +depends_on = None + + +def is_postgres(): + return op.get_context().dialect.name == "postgresql" + + +def find_user_id_for_group(group_id: UUID): + bind = op.get_bind() + session = orm.Session(bind=bind) + + if is_postgres(): + stmt = "SELECT id FROM users WHERE group_id=:group_id AND admin = TRUE LIMIT 1" + else: + stmt = "SELECT id FROM users WHERE group_id=:group_id AND admin = 1 LIMIT 1" + + with session: + try: + # try to find an admin user + user_id = session.execute(sa.text(stmt).bindparams(group_id=group_id)).scalar_one() + except orm.exc.NoResultFound: + # fallback to any user + user_id = session.execute( + sa.text("SELECT id FROM users LIMIT 1").bindparams(group_id=group_id) + ).scalar_one() + return user_id + + +def populate_shopping_list_users(): + bind = op.get_bind() + session = orm.Session(bind=bind) + + with session: + list_ids_and_group_ids = session.execute(sa.text("SELECT id, group_id FROM shopping_lists")).all() + for list_id, group_id in list_ids_and_group_ids: + user_id = find_user_id_for_group(group_id) + session.execute( + sa.text(f"UPDATE shopping_lists SET user_id=:user_id WHERE id=:id").bindparams( + user_id=user_id, id=list_id + ) + ) + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + with op.batch_alter_table("shopping_lists") as batch_op: + # allow nulls during migration + batch_op.add_column(sa.Column("user_id", mealie.db.migration_types.GUID(), nullable=True)) + batch_op.create_index(op.f("ix_shopping_lists_user_id"), ["user_id"], unique=False) + batch_op.create_foreign_key("fk_user_shopping_lists", "users", ["user_id"], ["id"]) + # ### end Alembic commands ### + + populate_shopping_list_users() + + # forbid nulls after migration + with op.batch_alter_table("shopping_lists") as batch_op: + batch_op.alter_column("user_id", nullable=False) + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_constraint(None, "shopping_lists", type_="foreignkey") + op.drop_index(op.f("ix_shopping_lists_user_id"), table_name="shopping_lists") + op.drop_column("shopping_lists", "user_id") + # ### end Alembic commands ### diff --git a/mealie/db/models/group/shopping_list.py b/mealie/db/models/group/shopping_list.py index 68f69d5b96..3e1aeed99d 100644 --- a/mealie/db/models/group/shopping_list.py +++ b/mealie/db/models/group/shopping_list.py @@ -14,6 +14,7 @@ if TYPE_CHECKING: from group import Group + from users import User from ..recipe import RecipeModel @@ -122,6 +123,8 @@ class ShoppingList(SqlAlchemyBase, BaseMixins): group_id: Mapped[GUID] = mapped_column(GUID, ForeignKey("groups.id"), nullable=False, index=True) group: Mapped["Group"] = orm.relationship("Group", back_populates="shopping_lists") + user_id: Mapped[GUID] = mapped_column(GUID, ForeignKey("users.id"), nullable=False, index=True) + user: Mapped["User"] = orm.relationship("User", back_populates="shopping_lists") name: Mapped[str | None] = mapped_column(String) list_items: Mapped[ShoppingListItem] = orm.relationship( diff --git a/mealie/db/models/users/users.py b/mealie/db/models/users/users.py index ca6bea91fa..83f96ef605 100644 --- a/mealie/db/models/users/users.py +++ b/mealie/db/models/users/users.py @@ -17,6 +17,7 @@ if TYPE_CHECKING: from ..group import Group from ..group.mealplan import GroupMealPlan + from ..group.shopping_list import ShoppingList from ..recipe import RecipeComment, RecipeModel, RecipeTimelineEvent from .password_reset import PasswordResetModel @@ -81,7 +82,7 @@ class User(SqlAlchemyBase, BaseMixins): mealplans: Mapped[Optional["GroupMealPlan"]] = orm.relationship( "GroupMealPlan", order_by="GroupMealPlan.date", **sp_args ) - + shopping_lists: Mapped[Optional["ShoppingList"]] = orm.relationship("ShoppingList", **sp_args) favorite_recipes: Mapped[list["RecipeModel"]] = orm.relationship( "RecipeModel", secondary=users_to_favorites, back_populates="favorited_by" ) From 0bf3aed28757cbefa1a62aac5826b85dbca34409 Mon Sep 17 00:00:00 2001 From: Michael Genson <71845777+michael-genson@users.noreply.github.com> Date: Fri, 23 Feb 2024 17:02:32 +0000 Subject: [PATCH 03/15] updated models/services/tests to include user_id --- mealie/routes/groups/controller_shopping_lists.py | 4 ++-- mealie/schema/group/group_shopping_list.py | 1 + mealie/services/group_services/shopping_lists.py | 7 ++++--- .../tasks/delete_old_checked_shopping_list_items.py | 3 ++- tests/fixtures/fixture_shopping_lists.py | 6 +++--- tests/unit_tests/repository_tests/test_pagination.py | 2 +- .../tasks/test_delete_old_checked_shopping_list_items.py | 8 ++++++-- 7 files changed, 19 insertions(+), 12 deletions(-) diff --git a/mealie/routes/groups/controller_shopping_lists.py b/mealie/routes/groups/controller_shopping_lists.py index a61ad3f397..2b5a8174e7 100644 --- a/mealie/routes/groups/controller_shopping_lists.py +++ b/mealie/routes/groups/controller_shopping_lists.py @@ -89,7 +89,7 @@ def publish_list_item_events(publisher: Callable, items_collection: ShoppingList class ShoppingListItemController(BaseCrudController): @cached_property def service(self): - return ShoppingListService(self.repos, self.group) + return ShoppingListService(self.repos, self.group, self.user) @cached_property def repo(self): @@ -154,7 +154,7 @@ def delete_one(self, item_id: UUID4): class ShoppingListController(BaseCrudController): @cached_property def service(self): - return ShoppingListService(self.repos, self.group) + return ShoppingListService(self.repos, self.group, self.user) @cached_property def repo(self): diff --git a/mealie/schema/group/group_shopping_list.py b/mealie/schema/group/group_shopping_list.py index ae5a27da5e..83a6291768 100644 --- a/mealie/schema/group/group_shopping_list.py +++ b/mealie/schema/group/group_shopping_list.py @@ -190,6 +190,7 @@ def loader_options(cls) -> list[LoaderOption]: class ShoppingListSave(ShoppingListCreate): group_id: UUID4 + user_id: UUID4 class ShoppingListSummary(ShoppingListSave): diff --git a/mealie/services/group_services/shopping_lists.py b/mealie/services/group_services/shopping_lists.py index b477d9bb56..09350b6037 100644 --- a/mealie/services/group_services/shopping_lists.py +++ b/mealie/services/group_services/shopping_lists.py @@ -19,13 +19,14 @@ ) from mealie.schema.recipe.recipe_ingredient import IngredientFood, IngredientUnit, RecipeIngredient from mealie.schema.response.pagination import OrderDirection, PaginationQuery -from mealie.schema.user.user import GroupInDB +from mealie.schema.user.user import GroupInDB, UserOut class ShoppingListService: - def __init__(self, repos: AllRepositories, group: GroupInDB): + def __init__(self, repos: AllRepositories, group: GroupInDB, user: UserOut): self.repos = repos self.group = group + self.user = user self.shopping_lists = repos.group_shopping_lists self.list_items = repos.group_shopping_list_item self.list_item_refs = repos.group_shopping_list_item_references @@ -476,7 +477,7 @@ def remove_recipe_ingredients_from_list( return self.shopping_lists.get_one(shopping_list.id), items # type: ignore def create_one_list(self, data: ShoppingListCreate): - create_data = data.cast(ShoppingListSave, group_id=self.group.id) + create_data = data.cast(ShoppingListSave, group_id=self.group.id, user_id=self.user.id) new_list = self.shopping_lists.create(create_data) # type: ignore labels = self.repos.group_multi_purpose_labels.by_group(self.group.id).page_all( diff --git a/mealie/services/scheduler/tasks/delete_old_checked_shopping_list_items.py b/mealie/services/scheduler/tasks/delete_old_checked_shopping_list_items.py index cc532c8caa..1f33ec5801 100644 --- a/mealie/services/scheduler/tasks/delete_old_checked_shopping_list_items.py +++ b/mealie/services/scheduler/tasks/delete_old_checked_shopping_list_items.py @@ -60,7 +60,8 @@ def delete_old_checked_list_items(group_id: UUID4 | None = None): for group in groups: event_bus_service = EventBusService(session=session, group_id=group.id) - shopping_list_service = ShoppingListService(repos, group) + # user is passed as None since we don't use it here + shopping_list_service = ShoppingListService(repos, group, None) # type: ignore shopping_list_data = repos.group_shopping_lists.by_group(group.id).page_all( PaginationQuery(page=1, per_page=-1) ) diff --git a/tests/fixtures/fixture_shopping_lists.py b/tests/fixtures/fixture_shopping_lists.py index 835ea0f51e..2eae671ffd 100644 --- a/tests/fixtures/fixture_shopping_lists.py +++ b/tests/fixtures/fixture_shopping_lists.py @@ -29,7 +29,7 @@ def shopping_lists(database: AllRepositories, unique_user: TestUser): for _ in range(3): model = database.group_shopping_lists.create( - ShoppingListSave(name=random_string(10), group_id=unique_user.group_id), + ShoppingListSave(name=random_string(10), group_id=unique_user.group_id, user_id=unique_user.user_id), ) models.append(model) @@ -46,7 +46,7 @@ def shopping_lists(database: AllRepositories, unique_user: TestUser): @pytest.fixture(scope="function") def shopping_list(database: AllRepositories, unique_user: TestUser): model = database.group_shopping_lists.create( - ShoppingListSave(name=random_string(10), group_id=unique_user.group_id), + ShoppingListSave(name=random_string(10), group_id=unique_user.group_id, user_id=unique_user.user_id), ) yield model @@ -60,7 +60,7 @@ def shopping_list(database: AllRepositories, unique_user: TestUser): @pytest.fixture(scope="function") def list_with_items(database: AllRepositories, unique_user: TestUser): list_model = database.group_shopping_lists.create( - ShoppingListSave(name=random_string(10), group_id=unique_user.group_id), + ShoppingListSave(name=random_string(10), group_id=unique_user.group_id, user_id=unique_user.user_id), ) for _ in range(10): diff --git a/tests/unit_tests/repository_tests/test_pagination.py b/tests/unit_tests/repository_tests/test_pagination.py index 8d46134de8..6007878bf9 100644 --- a/tests/unit_tests/repository_tests/test_pagination.py +++ b/tests/unit_tests/repository_tests/test_pagination.py @@ -758,7 +758,7 @@ def test_pagination_order_by_nulls( def test_pagination_shopping_list_items_with_labels(database: AllRepositories, unique_user: TestUser): # create a shopping list and populate it with some items with labels, and some without labels shopping_list = database.group_shopping_lists.create( - ShoppingListSave(name=random_string(), group_id=unique_user.group_id) + ShoppingListSave(name=random_string(), group_id=unique_user.group_id, user_id=unique_user.user_id) ) labels = database.group_multi_purpose_labels.create_many( diff --git a/tests/unit_tests/services_tests/scheduler/tasks/test_delete_old_checked_shopping_list_items.py b/tests/unit_tests/services_tests/scheduler/tasks/test_delete_old_checked_shopping_list_items.py index b462745dab..34705dee05 100644 --- a/tests/unit_tests/services_tests/scheduler/tasks/test_delete_old_checked_shopping_list_items.py +++ b/tests/unit_tests/services_tests/scheduler/tasks/test_delete_old_checked_shopping_list_items.py @@ -14,7 +14,9 @@ def test_cleanup(database: AllRepositories, unique_user: TestUser): list_repo = database.group_shopping_lists.by_group(unique_user.group_id) list_item_repo = database.group_shopping_list_item - shopping_list = list_repo.create(ShoppingListSave(name=random_string(), group_id=unique_user.group_id)) + shopping_list = list_repo.create( + ShoppingListSave(name=random_string(), group_id=unique_user.group_id, user_id=unique_user.user_id) + ) unchecked_items = list_item_repo.create_many( [ ShoppingListItemCreate(note=random_string(), shopping_list_id=shopping_list.id) @@ -57,7 +59,9 @@ def test_no_cleanup(database: AllRepositories, unique_user: TestUser): list_repo = database.group_shopping_lists.by_group(unique_user.group_id) list_item_repo = database.group_shopping_list_item - shopping_list = list_repo.create(ShoppingListSave(name=random_string(), group_id=unique_user.group_id)) + shopping_list = list_repo.create( + ShoppingListSave(name=random_string(), group_id=unique_user.group_id, user_id=unique_user.user_id) + ) unchecked_items = list_item_repo.create_many( [ ShoppingListItemCreate(note=random_string(), shopping_list_id=shopping_list.id) From 8e5ea1df5e49cbe01a4c8fe349de07eeff7aa622 Mon Sep 17 00:00:00 2001 From: Michael Genson <71845777+michael-genson@users.noreply.github.com> Date: Fri, 23 Feb 2024 17:07:43 +0000 Subject: [PATCH 04/15] added "show all" toggle on list of shopping lists --- frontend/lang/messages/en-US.json | 1 + frontend/lib/api/types/group.ts | 4 ++++ frontend/pages/shopping-lists/index.vue | 15 +++++++++++++-- 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/frontend/lang/messages/en-US.json b/frontend/lang/messages/en-US.json index 932a49bb30..8b7a1f1187 100644 --- a/frontend/lang/messages/en-US.json +++ b/frontend/lang/messages/en-US.json @@ -142,6 +142,7 @@ "save": "Save", "settings": "Settings", "share": "Share", + "show-all": "Show All", "shuffle": "Shuffle", "sort": "Sort", "sort-alphabetically": "Alphabetical", diff --git a/frontend/lib/api/types/group.ts b/frontend/lib/api/types/group.ts index d8aa408fe0..f9bb8c9ece 100644 --- a/frontend/lib/api/types/group.ts +++ b/frontend/lib/api/types/group.ts @@ -505,6 +505,7 @@ export interface ShoppingListOut { createdAt?: string; updateAt?: string; groupId: string; + userId: string; id: string; listItems?: ShoppingListItemOut[]; recipeReferences: ShoppingListRecipeRefOut[]; @@ -568,6 +569,7 @@ export interface ShoppingListSave { createdAt?: string; updateAt?: string; groupId: string; + userId: string; } export interface ShoppingListSummary { name?: string; @@ -577,6 +579,7 @@ export interface ShoppingListSummary { createdAt?: string; updateAt?: string; groupId: string; + userId: string; id: string; recipeReferences: ShoppingListRecipeRefOut[]; labelSettings: ShoppingListMultiPurposeLabelOut[]; @@ -589,6 +592,7 @@ export interface ShoppingListUpdate { createdAt?: string; updateAt?: string; groupId: string; + userId: string; id: string; listItems?: ShoppingListItemOut[]; } diff --git a/frontend/pages/shopping-lists/index.vue b/frontend/pages/shopping-lists/index.vue index ce128aaf92..6f20dca742 100644 --- a/frontend/pages/shopping-lists/index.vue +++ b/frontend/pages/shopping-lists/index.vue @@ -15,10 +15,20 @@ - + + + + +
- + {{ $globals.icons.cartCheck }} @@ -56,6 +66,7 @@ export default defineComponent({ createDialog: false, deleteDialog: false, deleteTarget: "", + showAll: false, }); const shoppingLists = useAsync(async () => { From 62adc920a9c388f88b93fca9eb7b333803678110 Mon Sep 17 00:00:00 2001 From: Michael Genson <71845777+michael-genson@users.noreply.github.com> Date: Fri, 23 Feb 2024 17:54:50 +0000 Subject: [PATCH 05/15] added settings to shopping list to change user --- frontend/pages/shopping-lists/_id.vue | 73 ++++++++++++++++++++++++++- 1 file changed, 72 insertions(+), 1 deletion(-) diff --git a/frontend/pages/shopping-lists/_id.vue b/frontend/pages/shopping-lists/_id.vue index c5bdd2cc82..f215f7a125 100644 --- a/frontend/pages/shopping-lists/_id.vue +++ b/frontend/pages/shopping-lists/_id.vue @@ -68,6 +68,26 @@ + + + + + + + + +
- + {{ $t('shopping-list.reorder-labels') }} + + + {{ $t('general.settings') }} +
@@ -215,6 +239,7 @@ import { useUserApi } from "~/composables/api"; import MultiPurposeLabelSection from "~/components/Domain/ShoppingList/MultiPurposeLabelSection.vue" import ShoppingListItem from "~/components/Domain/ShoppingList/ShoppingListItem.vue"; import { ShoppingListItemCreate, ShoppingListItemOut, ShoppingListMultiPurposeLabelOut, ShoppingListOut } from "~/lib/api/types/group"; +import { UserOut } from "~/lib/api/types/user"; import RecipeList from "~/components/Domain/Recipe/RecipeList.vue"; import ShoppingListItemEditor from "~/components/Domain/ShoppingList/ShoppingListItemEditor.vue"; import { useFoodStore, useLabelStore, useUnitStore } from "~/composables/store"; @@ -247,6 +272,7 @@ export default defineComponent({ const edit = ref(false); const reorderLabelsDialog = ref(false); + const settingsDialog = ref(false); const route = useRoute(); const groupSlug = computed(() => route.value.params.groupSlug || $auth.user?.groupSlug || ""); @@ -435,6 +461,13 @@ export default defineComponent({ reorderLabelsDialog.value = !reorderLabelsDialog.value } + async function toggleSettingsDialog() { + if (!settingsDialog.value) { + await fetchAllUsers(); + } + settingsDialog.value = !settingsDialog.value; + } + async function updateLabelOrder(labelSettings: ShoppingListMultiPurposeLabelOut[]) { if (!shoppingList.value) { return; @@ -746,6 +779,39 @@ export default defineComponent({ } } + // =============================================================== + // Shopping List Settings + + const allUsers = ref([]); + const currentUserId = ref(); + async function fetchAllUsers() { + const { data } = await userApi.users.getAll(1, -1, { orderBy: "full_name", orderDirection: "asc" }); + if (!data) { + return; + } + + // update current user + allUsers.value = data.items; + currentUserId.value = shoppingList.value?.userId; + } + + async function updateSettings() { + if (!shoppingList.value || !currentUserId.value) { + return; + } + + loadingCounter.value += 1; + const { data } = await userApi.shopping.lists.updateOne( + shoppingList.value.id, + {...shoppingList.value, userId: currentUserId.value}, + ); + loadingCounter.value -= 1; + + if (data) { + refresh(); + } + } + return { addRecipeReferenceToList, updateListItems, @@ -770,6 +836,8 @@ export default defineComponent({ removeRecipeReferenceToList, reorderLabelsDialog, toggleReorderLabelsDialog, + settingsDialog, + toggleSettingsDialog, updateLabelOrder, saveListItem, shoppingList, @@ -781,6 +849,9 @@ export default defineComponent({ updateIndexUncheckedByLabel, allUnits, allFoods, + allUsers, + currentUserId, + updateSettings, }; }, head() { From 7ca50b63f9f088582c7507b075fc810e15814e01 Mon Sep 17 00:00:00 2001 From: Michael Genson <71845777+michael-genson@users.noreply.github.com> Date: Fri, 23 Feb 2024 18:00:13 +0000 Subject: [PATCH 06/15] moved settings under list contents --- frontend/pages/shopping-lists/_id.vue | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/frontend/pages/shopping-lists/_id.vue b/frontend/pages/shopping-lists/_id.vue index f215f7a125..b8fb46b606 100644 --- a/frontend/pages/shopping-lists/_id.vue +++ b/frontend/pages/shopping-lists/_id.vue @@ -106,10 +106,6 @@ {{ $t('shopping-list.reorder-labels') }} - - - {{ $t('general.settings') }} - @@ -221,6 +217,15 @@
+ +
+ + + {{ $t('general.settings') }} + +
+
+
From f9b71f4b4c4638fc9b539fd839f329b1e4590999 Mon Sep 17 00:00:00 2001 From: Michael Genson <71845777+michael-genson@users.noreply.github.com> Date: Fri, 23 Feb 2024 18:00:58 +0000 Subject: [PATCH 07/15] translated owner string --- frontend/pages/shopping-lists/_id.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/pages/shopping-lists/_id.vue b/frontend/pages/shopping-lists/_id.vue index b8fb46b606..d862588548 100644 --- a/frontend/pages/shopping-lists/_id.vue +++ b/frontend/pages/shopping-lists/_id.vue @@ -81,7 +81,7 @@ :items="allUsers" item-text="fullName" item-value="id" - :label="'Owner'" + :label="$t('general.owner')" :prepend-icon="$globals.icons.user" /> From ac3514f4c6e8fa79db550d1f0efee5bdea717bff Mon Sep 17 00:00:00 2001 From: Michael Genson <71845777+michael-genson@users.noreply.github.com> Date: Fri, 23 Feb 2024 18:09:23 +0000 Subject: [PATCH 08/15] fixed broken migrations --- ..._b04a08da2108_added_shopping_list_label_settings.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/alembic/versions/2023-02-21-22.03.19_b04a08da2108_added_shopping_list_label_settings.py b/alembic/versions/2023-02-21-22.03.19_b04a08da2108_added_shopping_list_label_settings.py index a230433292..fdc7046ca3 100644 --- a/alembic/versions/2023-02-21-22.03.19_b04a08da2108_added_shopping_list_label_settings.py +++ b/alembic/versions/2023-02-21-22.03.19_b04a08da2108_added_shopping_list_label_settings.py @@ -9,7 +9,7 @@ from uuid import uuid4 import sqlalchemy as sa -from sqlalchemy.orm.session import Session +from sqlalchemy import orm import mealie.db.migration_types from alembic import op @@ -23,8 +23,10 @@ depends_on = None -def populate_shopping_lists_multi_purpose_labels(shopping_lists_multi_purpose_labels_table: sa.Table, session: Session): - shopping_lists = session.query(ShoppingList).all() +def populate_shopping_lists_multi_purpose_labels( + shopping_lists_multi_purpose_labels_table: sa.Table, session: orm.Session +): + shopping_lists = session.query(ShoppingList).options(orm.load_only(ShoppingList.id, ShoppingList.group_id)).all() shopping_lists_labels_data: list[dict] = [] for shopping_list in shopping_lists: @@ -60,7 +62,7 @@ def upgrade(): ) # ### end Alembic commands ### - session = Session(bind=op.get_bind()) + session = orm.Session(bind=op.get_bind()) populate_shopping_lists_multi_purpose_labels(shopping_lists_multi_purpose_labels_table, session) From 4a1371417751ca1a4769c924ea7afbe428118200 Mon Sep 17 00:00:00 2001 From: Michael Genson <71845777+michael-genson@users.noreply.github.com> Date: Fri, 23 Feb 2024 18:09:35 +0000 Subject: [PATCH 09/15] updated tests to include userId --- .../user_group_tests/test_group_shopping_lists.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/integration_tests/user_group_tests/test_group_shopping_lists.py b/tests/integration_tests/user_group_tests/test_group_shopping_lists.py index b3cdd434cf..dd64810890 100644 --- a/tests/integration_tests/user_group_tests/test_group_shopping_lists.py +++ b/tests/integration_tests/user_group_tests/test_group_shopping_lists.py @@ -34,6 +34,7 @@ def test_shopping_lists_create_one(api_client: TestClient, unique_user: TestUser assert response_list["name"] == payload["name"] assert response_list["groupId"] == str(unique_user.group_id) + assert response_list["userId"] == str(unique_user.user_id) def test_shopping_lists_get_one(api_client: TestClient, unique_user: TestUser, shopping_lists: list[ShoppingListOut]): @@ -47,6 +48,7 @@ def test_shopping_lists_get_one(api_client: TestClient, unique_user: TestUser, s assert response_list["id"] == str(shopping_list.id) assert response_list["name"] == shopping_list.name assert response_list["groupId"] == str(shopping_list.group_id) + assert response_list["userId"] == str(shopping_list.user_id) def test_shopping_lists_update_one( @@ -58,6 +60,7 @@ def test_shopping_lists_update_one( "name": random_string(10), "id": str(sample_list.id), "groupId": str(sample_list.group_id), + "userId": str(sample_list.user_id), "listItems": [], } @@ -71,6 +74,7 @@ def test_shopping_lists_update_one( assert response_list["id"] == str(sample_list.id) assert response_list["name"] == payload["name"] assert response_list["groupId"] == str(sample_list.group_id) + assert response_list["userId"] == str(sample_list.user_id) def test_shopping_lists_delete_one( From 0abe8b1921821a913ced0e9af0bf300a8e13d2de Mon Sep 17 00:00:00 2001 From: Michael Genson <71845777+michael-genson@users.noreply.github.com> Date: Fri, 23 Feb 2024 18:12:06 +0000 Subject: [PATCH 10/15] fixed missing group_id filter --- ...4-02-23-16.15.07_2298bb460ffd_added_user_to_shopping_list.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/alembic/versions/2024-02-23-16.15.07_2298bb460ffd_added_user_to_shopping_list.py b/alembic/versions/2024-02-23-16.15.07_2298bb460ffd_added_user_to_shopping_list.py index 024497e921..88570eff81 100644 --- a/alembic/versions/2024-02-23-16.15.07_2298bb460ffd_added_user_to_shopping_list.py +++ b/alembic/versions/2024-02-23-16.15.07_2298bb460ffd_added_user_to_shopping_list.py @@ -41,7 +41,7 @@ def find_user_id_for_group(group_id: UUID): except orm.exc.NoResultFound: # fallback to any user user_id = session.execute( - sa.text("SELECT id FROM users LIMIT 1").bindparams(group_id=group_id) + sa.text("SELECT id FROM users WHERE group_id=:group_id LIMIT 1").bindparams(group_id=group_id) ).scalar_one() return user_id From 44cd2fef1c984ba657b2214fa693a2eb04af7dd6 Mon Sep 17 00:00:00 2001 From: Michael Genson <71845777+michael-genson@users.noreply.github.com> Date: Fri, 23 Feb 2024 18:58:47 +0000 Subject: [PATCH 11/15] fixed trailing quote --- frontend/pages/shopping-lists/index.vue | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/pages/shopping-lists/index.vue b/frontend/pages/shopping-lists/index.vue index 6f20dca742..54e555ab81 100644 --- a/frontend/pages/shopping-lists/index.vue +++ b/frontend/pages/shopping-lists/index.vue @@ -27,8 +27,8 @@ v-if="showAll || ($auth.user && $auth.user.id == list.userId)" :key="list.id" class="my-2 left-border" - :to="`/shopping-lists/${list.id}` - "> + :to="`/shopping-lists/${list.id}`" + > {{ $globals.icons.cartCheck }} From 478a4e5d7311f4063806f46a2d2b03e6711af446 Mon Sep 17 00:00:00 2001 From: Michael Genson <71845777+michael-genson@users.noreply.github.com> Date: Fri, 23 Feb 2024 19:11:28 +0000 Subject: [PATCH 12/15] fixed showAll centering and added to recipe dialog --- .../Recipe/RecipeDialogAddToShoppingList.vue | 14 ++++++++++++++ frontend/pages/shopping-lists/index.vue | 2 +- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/frontend/components/Domain/Recipe/RecipeDialogAddToShoppingList.vue b/frontend/components/Domain/Recipe/RecipeDialogAddToShoppingList.vue index 1992ca3797..00749c62ec 100644 --- a/frontend/components/Domain/Recipe/RecipeDialogAddToShoppingList.vue +++ b/frontend/components/Domain/Recipe/RecipeDialogAddToShoppingList.vue @@ -4,6 +4,7 @@ + ([]); diff --git a/frontend/pages/shopping-lists/index.vue b/frontend/pages/shopping-lists/index.vue index 54e555ab81..613d8d84c2 100644 --- a/frontend/pages/shopping-lists/index.vue +++ b/frontend/pages/shopping-lists/index.vue @@ -17,7 +17,7 @@ - + From ae8ea16dab294ed5469211ef5854631cb4a9f7d7 Mon Sep 17 00:00:00 2001 From: Michael Genson <71845777+michael-genson@users.noreply.github.com> Date: Fri, 23 Feb 2024 19:17:08 +0000 Subject: [PATCH 13/15] lint --- .../components/Domain/Recipe/RecipeDialogAddToShoppingList.vue | 2 +- frontend/pages/shopping-lists/_id.vue | 3 ++- frontend/pages/shopping-lists/index.vue | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/frontend/components/Domain/Recipe/RecipeDialogAddToShoppingList.vue b/frontend/components/Domain/Recipe/RecipeDialogAddToShoppingList.vue index 00749c62ec..193f13928e 100644 --- a/frontend/components/Domain/Recipe/RecipeDialogAddToShoppingList.vue +++ b/frontend/components/Domain/Recipe/RecipeDialogAddToShoppingList.vue @@ -24,7 +24,7 @@ {{ $t("general.cancel") }}
- +
diff --git a/frontend/pages/shopping-lists/_id.vue b/frontend/pages/shopping-lists/_id.vue index d862588548..7dd12c9929 100644 --- a/frontend/pages/shopping-lists/_id.vue +++ b/frontend/pages/shopping-lists/_id.vue @@ -77,7 +77,8 @@ > - - + From aa4527e5f7bf3098f437cba5ea6c304d07143788 Mon Sep 17 00:00:00 2001 From: Michael Genson <71845777+michael-genson@users.noreply.github.com> Date: Fri, 23 Feb 2024 19:22:39 +0000 Subject: [PATCH 14/15] replace v-for/v-if with computed ref --- .../Recipe/RecipeDialogAddToShoppingList.vue | 10 +++++++--- frontend/pages/shopping-lists/index.vue | 15 +++++++++++---- 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/frontend/components/Domain/Recipe/RecipeDialogAddToShoppingList.vue b/frontend/components/Domain/Recipe/RecipeDialogAddToShoppingList.vue index 193f13928e..d7a57ce8fe 100644 --- a/frontend/components/Domain/Recipe/RecipeDialogAddToShoppingList.vue +++ b/frontend/components/Domain/Recipe/RecipeDialogAddToShoppingList.vue @@ -3,8 +3,7 @@ { + return props.shoppingLists.filter((list) => state.showAll || list.userId === $auth.user?.id); + }); + const recipeIngredientSections = ref([]); const selectedShoppingList = ref(null); @@ -348,6 +351,7 @@ export default defineComponent({ return { dialog, + shoppingListChoices, ...toRefs(state), addRecipesToList, bulkCheckIngredients, diff --git a/frontend/pages/shopping-lists/index.vue b/frontend/pages/shopping-lists/index.vue index 3945e7ad40..19ac10db8a 100644 --- a/frontend/pages/shopping-lists/index.vue +++ b/frontend/pages/shopping-lists/index.vue @@ -1,5 +1,5 @@ @@ -132,6 +132,7 @@ import { toRefs } from "@vueuse/core"; import RecipeIngredientListItem from "./RecipeIngredientListItem.vue"; import { useUserApi } from "~/composables/api"; import { alert } from "~/composables/use-toast"; +import { useShoppingListPreferences } from "~/composables/use-users/preferences"; import { ShoppingListSummary } from "~/lib/api/types/group"; import { Recipe, RecipeIngredient } from "~/lib/api/types/recipe"; @@ -178,6 +179,7 @@ export default defineComponent({ setup(props, context) { const { $auth, i18n } = useContext(); const api = useUserApi(); + const preferences = useShoppingListPreferences(); // v-model support const dialog = computed({ @@ -193,11 +195,10 @@ export default defineComponent({ const state = reactive({ shoppingListDialog: true, shoppingListIngredientDialog: false, - showAll: false, }); const shoppingListChoices = computed(() => { - return props.shoppingLists.filter((list) => state.showAll || list.userId === $auth.user?.id); + return props.shoppingLists.filter((list) => preferences.value.viewAllLists || list.userId === $auth.user?.id); }); const recipeIngredientSections = ref([]); @@ -351,6 +352,7 @@ export default defineComponent({ return { dialog, + preferences, shoppingListChoices, ...toRefs(state), addRecipesToList, diff --git a/frontend/composables/use-users/preferences.ts b/frontend/composables/use-users/preferences.ts index c93c5dcb4c..a6933576fd 100644 --- a/frontend/composables/use-users/preferences.ts +++ b/frontend/composables/use-users/preferences.ts @@ -22,6 +22,7 @@ export interface UserRecipePreferences { } export interface UserShoppingListPreferences { + viewAllLists: boolean; viewByLabel: boolean; } @@ -70,6 +71,7 @@ export function useShoppingListPreferences(): Ref { const fromStorage = useLocalStorage( "shopping-list-preferences", { + viewAllLists: false, viewByLabel: false, }, { mergeDefaults: true } diff --git a/frontend/pages/shopping-lists/index.vue b/frontend/pages/shopping-lists/index.vue index 19ac10db8a..bde98330a5 100644 --- a/frontend/pages/shopping-lists/index.vue +++ b/frontend/pages/shopping-lists/index.vue @@ -17,7 +17,7 @@ - + @@ -51,6 +51,7 @@ import { computed, defineComponent, useAsync, useContext, reactive, toRefs, useRoute } from "@nuxtjs/composition-api"; import { useUserApi } from "~/composables/api"; import { useAsyncKey } from "~/composables/use-utils"; +import { useShoppingListPreferences } from "~/composables/use-users/preferences"; export default defineComponent({ middleware: "auth", @@ -59,13 +60,13 @@ export default defineComponent({ const userApi = useUserApi(); const route = useRoute(); const groupSlug = computed(() => route.value.params.groupSlug || $auth.user?.groupSlug || ""); + const preferences = useShoppingListPreferences(); const state = reactive({ createName: "", createDialog: false, deleteDialog: false, deleteTarget: "", - showAll: false, }); const shoppingLists = useAsync(async () => { @@ -77,7 +78,7 @@ export default defineComponent({ return []; } - return shoppingLists.value.filter((list) => state.showAll || list.userId === $auth.user?.id); + return shoppingLists.value.filter((list) => preferences.value.viewAllLists || list.userId === $auth.user?.id); }); async function fetchShoppingLists() { @@ -118,6 +119,7 @@ export default defineComponent({ return { ...toRefs(state), groupSlug, + preferences, shoppingListChoices, createOne, deleteOne,