mirror of
				https://github.com/mealie-recipes/mealie.git
				synced 2025-11-03 19:18:22 -05:00 
			
		
		
		
	Merge pull request #3213 from michael-genson/feat/filter-shopping-lists
feat: Filter Out Shopping Lists That Aren't Yours
This commit is contained in:
		
						commit
						e84e5e2910
					
				@ -9,7 +9,7 @@ Create Date: 2023-21-02 22:03:19.837244
 | 
				
			|||||||
from uuid import uuid4
 | 
					from uuid import uuid4
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import sqlalchemy as sa
 | 
					import sqlalchemy as sa
 | 
				
			||||||
from sqlalchemy.orm.session import Session
 | 
					from sqlalchemy import orm
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import mealie.db.migration_types
 | 
					import mealie.db.migration_types
 | 
				
			||||||
from alembic import op
 | 
					from alembic import op
 | 
				
			||||||
@ -23,8 +23,10 @@ branch_labels = None
 | 
				
			|||||||
depends_on = None
 | 
					depends_on = None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def populate_shopping_lists_multi_purpose_labels(shopping_lists_multi_purpose_labels_table: sa.Table, session: Session):
 | 
					def populate_shopping_lists_multi_purpose_labels(
 | 
				
			||||||
    shopping_lists = session.query(ShoppingList).all()
 | 
					    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] = []
 | 
					    shopping_lists_labels_data: list[dict] = []
 | 
				
			||||||
    for shopping_list in shopping_lists:
 | 
					    for shopping_list in shopping_lists:
 | 
				
			||||||
@ -60,7 +62,7 @@ def upgrade():
 | 
				
			|||||||
    )
 | 
					    )
 | 
				
			||||||
    # ### end Alembic commands ###
 | 
					    # ### 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)
 | 
					    populate_shopping_lists_multi_purpose_labels(shopping_lists_multi_purpose_labels_table, session)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -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 WHERE group_id=:group_id 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 ###
 | 
				
			||||||
@ -107,7 +107,7 @@ export default defineComponent({
 | 
				
			|||||||
    })
 | 
					    })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    async function getShoppingLists() {
 | 
					    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) {
 | 
					      if (data) {
 | 
				
			||||||
        shoppingLists.value = data.items ?? [];
 | 
					        shoppingLists.value = data.items ?? [];
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
				
			|||||||
@ -321,7 +321,7 @@ export default defineComponent({
 | 
				
			|||||||
    const recipeRefWithScale = computed(() => recipeRef.value ? { scale: props.recipeScale, ...recipeRef.value } : undefined);
 | 
					    const recipeRefWithScale = computed(() => recipeRef.value ? { scale: props.recipeScale, ...recipeRef.value } : undefined);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    async function getShoppingLists() {
 | 
					    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) {
 | 
					      if (data) {
 | 
				
			||||||
        shoppingLists.value = data.items ?? [];
 | 
					        shoppingLists.value = data.items ?? [];
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
				
			|||||||
@ -3,7 +3,7 @@
 | 
				
			|||||||
    <BaseDialog v-if="shoppingListDialog" v-model="dialog" :title="$t('recipe.add-to-list')" :icon="$globals.icons.cartCheck">
 | 
					    <BaseDialog v-if="shoppingListDialog" v-model="dialog" :title="$t('recipe.add-to-list')" :icon="$globals.icons.cartCheck">
 | 
				
			||||||
      <v-card-text>
 | 
					      <v-card-text>
 | 
				
			||||||
        <v-card
 | 
					        <v-card
 | 
				
			||||||
          v-for="list in shoppingLists"
 | 
					          v-for="list in shoppingListChoices"
 | 
				
			||||||
          :key="list.id"
 | 
					          :key="list.id"
 | 
				
			||||||
          hover
 | 
					          hover
 | 
				
			||||||
          class="my-2 left-border"
 | 
					          class="my-2 left-border"
 | 
				
			||||||
@ -14,6 +14,18 @@
 | 
				
			|||||||
          </v-card-title>
 | 
					          </v-card-title>
 | 
				
			||||||
        </v-card>
 | 
					        </v-card>
 | 
				
			||||||
      </v-card-text>
 | 
					      </v-card-text>
 | 
				
			||||||
 | 
					      <template #card-actions>
 | 
				
			||||||
 | 
					        <v-btn
 | 
				
			||||||
 | 
					          text
 | 
				
			||||||
 | 
					          color="grey"
 | 
				
			||||||
 | 
					          @click="dialog = false"
 | 
				
			||||||
 | 
					        >
 | 
				
			||||||
 | 
					          {{ $t("general.cancel") }}
 | 
				
			||||||
 | 
					        </v-btn>
 | 
				
			||||||
 | 
					        <div class="d-flex justify-end" style="width: 100%;">
 | 
				
			||||||
 | 
					          <v-checkbox v-model="preferences.viewAllLists" hide-details :label="$tc('general.show-all')" class="my-auto mr-4" />
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					      </template>
 | 
				
			||||||
    </BaseDialog>
 | 
					    </BaseDialog>
 | 
				
			||||||
    <BaseDialog
 | 
					    <BaseDialog
 | 
				
			||||||
      v-if="shoppingListIngredientDialog"
 | 
					      v-if="shoppingListIngredientDialog"
 | 
				
			||||||
@ -120,6 +132,7 @@ import { toRefs } from "@vueuse/core";
 | 
				
			|||||||
import RecipeIngredientListItem from "./RecipeIngredientListItem.vue";
 | 
					import RecipeIngredientListItem from "./RecipeIngredientListItem.vue";
 | 
				
			||||||
import { useUserApi } from "~/composables/api";
 | 
					import { useUserApi } from "~/composables/api";
 | 
				
			||||||
import { alert } from "~/composables/use-toast";
 | 
					import { alert } from "~/composables/use-toast";
 | 
				
			||||||
 | 
					import { useShoppingListPreferences } from "~/composables/use-users/preferences";
 | 
				
			||||||
import { ShoppingListSummary } from "~/lib/api/types/group";
 | 
					import { ShoppingListSummary } from "~/lib/api/types/group";
 | 
				
			||||||
import { Recipe, RecipeIngredient } from "~/lib/api/types/recipe";
 | 
					import { Recipe, RecipeIngredient } from "~/lib/api/types/recipe";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -164,8 +177,9 @@ export default defineComponent({
 | 
				
			|||||||
    },
 | 
					    },
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  setup(props, context) {
 | 
					  setup(props, context) {
 | 
				
			||||||
    const { i18n } = useContext();
 | 
					    const { $auth, i18n } = useContext();
 | 
				
			||||||
    const api = useUserApi();
 | 
					    const api = useUserApi();
 | 
				
			||||||
 | 
					    const preferences = useShoppingListPreferences();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // v-model support
 | 
					    // v-model support
 | 
				
			||||||
    const dialog = computed({
 | 
					    const dialog = computed({
 | 
				
			||||||
@ -183,6 +197,10 @@ export default defineComponent({
 | 
				
			|||||||
      shoppingListIngredientDialog: false,
 | 
					      shoppingListIngredientDialog: false,
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const shoppingListChoices = computed(() => {
 | 
				
			||||||
 | 
					      return props.shoppingLists.filter((list) => preferences.value.viewAllLists || list.userId === $auth.user?.id);
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const recipeIngredientSections = ref<ShoppingListRecipeIngredientSection[]>([]);
 | 
					    const recipeIngredientSections = ref<ShoppingListRecipeIngredientSection[]>([]);
 | 
				
			||||||
    const selectedShoppingList = ref<ShoppingListSummary | null>(null);
 | 
					    const selectedShoppingList = ref<ShoppingListSummary | null>(null);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -334,6 +352,8 @@ export default defineComponent({
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    return {
 | 
					    return {
 | 
				
			||||||
      dialog,
 | 
					      dialog,
 | 
				
			||||||
 | 
					      preferences,
 | 
				
			||||||
 | 
					      shoppingListChoices,
 | 
				
			||||||
      ...toRefs(state),
 | 
					      ...toRefs(state),
 | 
				
			||||||
      addRecipesToList,
 | 
					      addRecipesToList,
 | 
				
			||||||
      bulkCheckIngredients,
 | 
					      bulkCheckIngredients,
 | 
				
			||||||
 | 
				
			|||||||
@ -22,6 +22,7 @@ export interface UserRecipePreferences {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export interface UserShoppingListPreferences {
 | 
					export interface UserShoppingListPreferences {
 | 
				
			||||||
 | 
					  viewAllLists: boolean;
 | 
				
			||||||
  viewByLabel: boolean;
 | 
					  viewByLabel: boolean;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -70,6 +71,7 @@ export function useShoppingListPreferences(): Ref<UserShoppingListPreferences> {
 | 
				
			|||||||
  const fromStorage = useLocalStorage(
 | 
					  const fromStorage = useLocalStorage(
 | 
				
			||||||
    "shopping-list-preferences",
 | 
					    "shopping-list-preferences",
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
 | 
					      viewAllLists: false,
 | 
				
			||||||
      viewByLabel: false,
 | 
					      viewByLabel: false,
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    { mergeDefaults: true }
 | 
					    { mergeDefaults: true }
 | 
				
			||||||
 | 
				
			|||||||
@ -142,6 +142,7 @@
 | 
				
			|||||||
    "save": "Save",
 | 
					    "save": "Save",
 | 
				
			||||||
    "settings": "Settings",
 | 
					    "settings": "Settings",
 | 
				
			||||||
    "share": "Share",
 | 
					    "share": "Share",
 | 
				
			||||||
 | 
					    "show-all": "Show All",
 | 
				
			||||||
    "shuffle": "Shuffle",
 | 
					    "shuffle": "Shuffle",
 | 
				
			||||||
    "sort": "Sort",
 | 
					    "sort": "Sort",
 | 
				
			||||||
    "sort-alphabetically": "Alphabetical",
 | 
					    "sort-alphabetically": "Alphabetical",
 | 
				
			||||||
 | 
				
			|||||||
@ -505,6 +505,7 @@ export interface ShoppingListOut {
 | 
				
			|||||||
  createdAt?: string;
 | 
					  createdAt?: string;
 | 
				
			||||||
  updateAt?: string;
 | 
					  updateAt?: string;
 | 
				
			||||||
  groupId: string;
 | 
					  groupId: string;
 | 
				
			||||||
 | 
					  userId: string;
 | 
				
			||||||
  id: string;
 | 
					  id: string;
 | 
				
			||||||
  listItems?: ShoppingListItemOut[];
 | 
					  listItems?: ShoppingListItemOut[];
 | 
				
			||||||
  recipeReferences: ShoppingListRecipeRefOut[];
 | 
					  recipeReferences: ShoppingListRecipeRefOut[];
 | 
				
			||||||
@ -568,6 +569,7 @@ export interface ShoppingListSave {
 | 
				
			|||||||
  createdAt?: string;
 | 
					  createdAt?: string;
 | 
				
			||||||
  updateAt?: string;
 | 
					  updateAt?: string;
 | 
				
			||||||
  groupId: string;
 | 
					  groupId: string;
 | 
				
			||||||
 | 
					  userId: string;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
export interface ShoppingListSummary {
 | 
					export interface ShoppingListSummary {
 | 
				
			||||||
  name?: string;
 | 
					  name?: string;
 | 
				
			||||||
@ -577,6 +579,7 @@ export interface ShoppingListSummary {
 | 
				
			|||||||
  createdAt?: string;
 | 
					  createdAt?: string;
 | 
				
			||||||
  updateAt?: string;
 | 
					  updateAt?: string;
 | 
				
			||||||
  groupId: string;
 | 
					  groupId: string;
 | 
				
			||||||
 | 
					  userId: string;
 | 
				
			||||||
  id: string;
 | 
					  id: string;
 | 
				
			||||||
  recipeReferences: ShoppingListRecipeRefOut[];
 | 
					  recipeReferences: ShoppingListRecipeRefOut[];
 | 
				
			||||||
  labelSettings: ShoppingListMultiPurposeLabelOut[];
 | 
					  labelSettings: ShoppingListMultiPurposeLabelOut[];
 | 
				
			||||||
@ -589,6 +592,7 @@ export interface ShoppingListUpdate {
 | 
				
			|||||||
  createdAt?: string;
 | 
					  createdAt?: string;
 | 
				
			||||||
  updateAt?: string;
 | 
					  updateAt?: string;
 | 
				
			||||||
  groupId: string;
 | 
					  groupId: string;
 | 
				
			||||||
 | 
					  userId: string;
 | 
				
			||||||
  id: string;
 | 
					  id: string;
 | 
				
			||||||
  listItems?: ShoppingListItemOut[];
 | 
					  listItems?: ShoppingListItemOut[];
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -68,6 +68,27 @@
 | 
				
			|||||||
        </v-card>
 | 
					        </v-card>
 | 
				
			||||||
      </BaseDialog>
 | 
					      </BaseDialog>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      <!-- Settings -->
 | 
				
			||||||
 | 
					      <BaseDialog
 | 
				
			||||||
 | 
					        v-model="settingsDialog"
 | 
				
			||||||
 | 
					        :icon="$globals.icons.cog"
 | 
				
			||||||
 | 
					        :title="$t('general.settings')"
 | 
				
			||||||
 | 
					        @confirm="updateSettings"
 | 
				
			||||||
 | 
					      >
 | 
				
			||||||
 | 
					        <v-container>
 | 
				
			||||||
 | 
					          <v-form>
 | 
				
			||||||
 | 
					            <v-select
 | 
				
			||||||
 | 
					              v-model="currentUserId"
 | 
				
			||||||
 | 
					              :items="allUsers"
 | 
				
			||||||
 | 
					              item-text="fullName"
 | 
				
			||||||
 | 
					              item-value="id"
 | 
				
			||||||
 | 
					              :label="$t('general.owner')"
 | 
				
			||||||
 | 
					              :prepend-icon="$globals.icons.user"
 | 
				
			||||||
 | 
					            />
 | 
				
			||||||
 | 
					          </v-form>
 | 
				
			||||||
 | 
					        </v-container>
 | 
				
			||||||
 | 
					      </BaseDialog>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      <!-- Create Item -->
 | 
					      <!-- Create Item -->
 | 
				
			||||||
      <div v-if="createEditorOpen">
 | 
					      <div v-if="createEditorOpen">
 | 
				
			||||||
        <ShoppingListItemEditor
 | 
					        <ShoppingListItemEditor
 | 
				
			||||||
@ -82,7 +103,7 @@
 | 
				
			|||||||
        />
 | 
					        />
 | 
				
			||||||
      </div>
 | 
					      </div>
 | 
				
			||||||
      <div v-else class="mt-4 d-flex justify-end">
 | 
					      <div v-else class="mt-4 d-flex justify-end">
 | 
				
			||||||
        <BaseButton v-if="preferences.viewByLabel" color="info" class="mr-2" @click="reorderLabelsDialog = true">
 | 
					        <BaseButton v-if="preferences.viewByLabel" edit class="mr-2" @click="reorderLabelsDialog = true">
 | 
				
			||||||
          <template #icon> {{ $globals.icons.tags }} </template>
 | 
					          <template #icon> {{ $globals.icons.tags }} </template>
 | 
				
			||||||
          {{ $t('shopping-list.reorder-labels') }}
 | 
					          {{ $t('shopping-list.reorder-labels') }}
 | 
				
			||||||
        </BaseButton>
 | 
					        </BaseButton>
 | 
				
			||||||
@ -197,6 +218,15 @@
 | 
				
			|||||||
      </section>
 | 
					      </section>
 | 
				
			||||||
    </v-lazy>
 | 
					    </v-lazy>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <v-lazy>
 | 
				
			||||||
 | 
					      <div class="d-flex justify-end">
 | 
				
			||||||
 | 
					        <BaseButton edit @click="toggleSettingsDialog">
 | 
				
			||||||
 | 
					          <template #icon> {{ $globals.icons.cog }} </template>
 | 
				
			||||||
 | 
					          {{ $t('general.settings') }}
 | 
				
			||||||
 | 
					        </BaseButton>
 | 
				
			||||||
 | 
					      </div>
 | 
				
			||||||
 | 
					    </v-lazy>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <v-lazy>
 | 
					    <v-lazy>
 | 
				
			||||||
      <div class="d-flex justify-end mt-10">
 | 
					      <div class="d-flex justify-end mt-10">
 | 
				
			||||||
        <ButtonLink :to="`/group/data/labels`" :text="$tc('shopping-list.manage-labels')" :icon="$globals.icons.tags" />
 | 
					        <ButtonLink :to="`/group/data/labels`" :text="$tc('shopping-list.manage-labels')" :icon="$globals.icons.tags" />
 | 
				
			||||||
@ -215,6 +245,7 @@ import { useUserApi } from "~/composables/api";
 | 
				
			|||||||
import MultiPurposeLabelSection from "~/components/Domain/ShoppingList/MultiPurposeLabelSection.vue"
 | 
					import MultiPurposeLabelSection from "~/components/Domain/ShoppingList/MultiPurposeLabelSection.vue"
 | 
				
			||||||
import ShoppingListItem from "~/components/Domain/ShoppingList/ShoppingListItem.vue";
 | 
					import ShoppingListItem from "~/components/Domain/ShoppingList/ShoppingListItem.vue";
 | 
				
			||||||
import { ShoppingListItemCreate, ShoppingListItemOut, ShoppingListMultiPurposeLabelOut, ShoppingListOut } from "~/lib/api/types/group";
 | 
					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 RecipeList from "~/components/Domain/Recipe/RecipeList.vue";
 | 
				
			||||||
import ShoppingListItemEditor from "~/components/Domain/ShoppingList/ShoppingListItemEditor.vue";
 | 
					import ShoppingListItemEditor from "~/components/Domain/ShoppingList/ShoppingListItemEditor.vue";
 | 
				
			||||||
import { useFoodStore, useLabelStore, useUnitStore } from "~/composables/store";
 | 
					import { useFoodStore, useLabelStore, useUnitStore } from "~/composables/store";
 | 
				
			||||||
@ -247,6 +278,7 @@ export default defineComponent({
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    const edit = ref(false);
 | 
					    const edit = ref(false);
 | 
				
			||||||
    const reorderLabelsDialog = ref(false);
 | 
					    const reorderLabelsDialog = ref(false);
 | 
				
			||||||
 | 
					    const settingsDialog = ref(false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const route = useRoute();
 | 
					    const route = useRoute();
 | 
				
			||||||
    const groupSlug = computed(() => route.value.params.groupSlug || $auth.user?.groupSlug || "");
 | 
					    const groupSlug = computed(() => route.value.params.groupSlug || $auth.user?.groupSlug || "");
 | 
				
			||||||
@ -464,6 +496,13 @@ export default defineComponent({
 | 
				
			|||||||
      reorderLabelsDialog.value = !reorderLabelsDialog.value
 | 
					      reorderLabelsDialog.value = !reorderLabelsDialog.value
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    async function toggleSettingsDialog() {
 | 
				
			||||||
 | 
					      if (!settingsDialog.value) {
 | 
				
			||||||
 | 
					        await fetchAllUsers();
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      settingsDialog.value = !settingsDialog.value;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    async function updateLabelOrder(labelSettings: ShoppingListMultiPurposeLabelOut[]) {
 | 
					    async function updateLabelOrder(labelSettings: ShoppingListMultiPurposeLabelOut[]) {
 | 
				
			||||||
      if (!shoppingList.value) {
 | 
					      if (!shoppingList.value) {
 | 
				
			||||||
        return;
 | 
					        return;
 | 
				
			||||||
@ -775,6 +814,39 @@ export default defineComponent({
 | 
				
			|||||||
      }
 | 
					      }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // ===============================================================
 | 
				
			||||||
 | 
					    // Shopping List Settings
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const allUsers = ref<UserOut[]>([]);
 | 
				
			||||||
 | 
					    const currentUserId = ref<string | undefined>();
 | 
				
			||||||
 | 
					    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 {
 | 
					    return {
 | 
				
			||||||
      addRecipeReferenceToList,
 | 
					      addRecipeReferenceToList,
 | 
				
			||||||
      updateListItems,
 | 
					      updateListItems,
 | 
				
			||||||
@ -799,6 +871,8 @@ export default defineComponent({
 | 
				
			|||||||
      removeRecipeReferenceToList,
 | 
					      removeRecipeReferenceToList,
 | 
				
			||||||
      reorderLabelsDialog,
 | 
					      reorderLabelsDialog,
 | 
				
			||||||
      toggleReorderLabelsDialog,
 | 
					      toggleReorderLabelsDialog,
 | 
				
			||||||
 | 
					      settingsDialog,
 | 
				
			||||||
 | 
					      toggleSettingsDialog,
 | 
				
			||||||
      updateLabelOrder,
 | 
					      updateLabelOrder,
 | 
				
			||||||
      saveListItem,
 | 
					      saveListItem,
 | 
				
			||||||
      shoppingList,
 | 
					      shoppingList,
 | 
				
			||||||
@ -810,6 +884,9 @@ export default defineComponent({
 | 
				
			|||||||
      updateIndexUncheckedByLabel,
 | 
					      updateIndexUncheckedByLabel,
 | 
				
			||||||
      allUnits,
 | 
					      allUnits,
 | 
				
			||||||
      allFoods,
 | 
					      allFoods,
 | 
				
			||||||
 | 
					      allUsers,
 | 
				
			||||||
 | 
					      currentUserId,
 | 
				
			||||||
 | 
					      updateSettings,
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  head() {
 | 
					  head() {
 | 
				
			||||||
 | 
				
			|||||||
@ -1,5 +1,5 @@
 | 
				
			|||||||
<template>
 | 
					<template>
 | 
				
			||||||
  <v-container v-if="shoppingLists" class="narrow-container">
 | 
					  <v-container v-if="shoppingListChoices" class="narrow-container">
 | 
				
			||||||
    <BaseDialog v-model="createDialog" :title="$tc('shopping-list.create-shopping-list')" @submit="createOne">
 | 
					    <BaseDialog v-model="createDialog" :title="$tc('shopping-list.create-shopping-list')" @submit="createOne">
 | 
				
			||||||
      <v-card-text>
 | 
					      <v-card-text>
 | 
				
			||||||
        <v-text-field v-model="createName" autofocus :label="$t('shopping-list.new-list')"> </v-text-field>
 | 
					        <v-text-field v-model="createName" autofocus :label="$t('shopping-list.new-list')"> </v-text-field>
 | 
				
			||||||
@ -15,10 +15,19 @@
 | 
				
			|||||||
      </template>
 | 
					      </template>
 | 
				
			||||||
      <template #title>{{ $t('shopping-list.shopping-lists') }}</template>
 | 
					      <template #title>{{ $t('shopping-list.shopping-lists') }}</template>
 | 
				
			||||||
    </BasePageTitle>
 | 
					    </BasePageTitle>
 | 
				
			||||||
    <BaseButton create @click="createDialog = true" />
 | 
					
 | 
				
			||||||
 | 
					    <v-container class="d-flex justify-end px-0 pt-0 pb-4">
 | 
				
			||||||
 | 
					      <v-checkbox v-model="preferences.viewAllLists" hide-details :label="$tc('general.show-all')" class="my-auto mr-4" />
 | 
				
			||||||
 | 
					      <BaseButton create @click="createDialog = true" />
 | 
				
			||||||
 | 
					    </v-container>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <section>
 | 
					    <section>
 | 
				
			||||||
      <v-card v-for="list in shoppingLists" :key="list.id" class="my-2 left-border" :to="`/shopping-lists/${list.id}`">
 | 
					      <v-card
 | 
				
			||||||
 | 
					        v-for="list in shoppingListChoices"
 | 
				
			||||||
 | 
					        :key="list.id"
 | 
				
			||||||
 | 
					        class="my-2 left-border"
 | 
				
			||||||
 | 
					        :to="`/shopping-lists/${list.id}`"
 | 
				
			||||||
 | 
					      >
 | 
				
			||||||
        <v-card-title>
 | 
					        <v-card-title>
 | 
				
			||||||
          <v-icon left>
 | 
					          <v-icon left>
 | 
				
			||||||
            {{ $globals.icons.cartCheck }}
 | 
					            {{ $globals.icons.cartCheck }}
 | 
				
			||||||
@ -42,6 +51,7 @@
 | 
				
			|||||||
import { computed, defineComponent, useAsync, useContext, reactive, toRefs, useRoute } from "@nuxtjs/composition-api";
 | 
					import { computed, defineComponent, useAsync, useContext, reactive, toRefs, useRoute } from "@nuxtjs/composition-api";
 | 
				
			||||||
import { useUserApi } from "~/composables/api";
 | 
					import { useUserApi } from "~/composables/api";
 | 
				
			||||||
import { useAsyncKey } from "~/composables/use-utils";
 | 
					import { useAsyncKey } from "~/composables/use-utils";
 | 
				
			||||||
 | 
					import { useShoppingListPreferences } from "~/composables/use-users/preferences";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default defineComponent({
 | 
					export default defineComponent({
 | 
				
			||||||
  middleware: "auth",
 | 
					  middleware: "auth",
 | 
				
			||||||
@ -50,6 +60,7 @@ export default defineComponent({
 | 
				
			|||||||
    const userApi = useUserApi();
 | 
					    const userApi = useUserApi();
 | 
				
			||||||
    const route = useRoute();
 | 
					    const route = useRoute();
 | 
				
			||||||
    const groupSlug = computed(() => route.value.params.groupSlug || $auth.user?.groupSlug || "");
 | 
					    const groupSlug = computed(() => route.value.params.groupSlug || $auth.user?.groupSlug || "");
 | 
				
			||||||
 | 
					    const preferences = useShoppingListPreferences();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const state = reactive({
 | 
					    const state = reactive({
 | 
				
			||||||
      createName: "",
 | 
					      createName: "",
 | 
				
			||||||
@ -62,8 +73,16 @@ export default defineComponent({
 | 
				
			|||||||
      return await fetchShoppingLists();
 | 
					      return await fetchShoppingLists();
 | 
				
			||||||
    }, useAsyncKey());
 | 
					    }, useAsyncKey());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const shoppingListChoices = computed(() => {
 | 
				
			||||||
 | 
					      if (!shoppingLists.value) {
 | 
				
			||||||
 | 
					        return [];
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      return shoppingLists.value.filter((list) => preferences.value.viewAllLists || list.userId === $auth.user?.id);
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    async function fetchShoppingLists() {
 | 
					    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) {
 | 
					      if (!data) {
 | 
				
			||||||
        return [];
 | 
					        return [];
 | 
				
			||||||
@ -100,7 +119,8 @@ export default defineComponent({
 | 
				
			|||||||
    return {
 | 
					    return {
 | 
				
			||||||
      ...toRefs(state),
 | 
					      ...toRefs(state),
 | 
				
			||||||
      groupSlug,
 | 
					      groupSlug,
 | 
				
			||||||
      shoppingLists,
 | 
					      preferences,
 | 
				
			||||||
 | 
					      shoppingListChoices,
 | 
				
			||||||
      createOne,
 | 
					      createOne,
 | 
				
			||||||
      deleteOne,
 | 
					      deleteOne,
 | 
				
			||||||
      openDelete,
 | 
					      openDelete,
 | 
				
			||||||
 | 
				
			|||||||
@ -14,6 +14,7 @@ from ..recipe.ingredient import IngredientFoodModel, IngredientUnitModel
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
if TYPE_CHECKING:
 | 
					if TYPE_CHECKING:
 | 
				
			||||||
    from group import Group
 | 
					    from group import Group
 | 
				
			||||||
 | 
					    from users import User
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    from ..recipe import RecipeModel
 | 
					    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_id: Mapped[GUID] = mapped_column(GUID, ForeignKey("groups.id"), nullable=False, index=True)
 | 
				
			||||||
    group: Mapped["Group"] = orm.relationship("Group", back_populates="shopping_lists")
 | 
					    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)
 | 
					    name: Mapped[str | None] = mapped_column(String)
 | 
				
			||||||
    list_items: Mapped[list[ShoppingListItem]] = orm.relationship(
 | 
					    list_items: Mapped[list[ShoppingListItem]] = orm.relationship(
 | 
				
			||||||
 | 
				
			|||||||
@ -17,6 +17,7 @@ from .user_to_favorite import users_to_favorites
 | 
				
			|||||||
if TYPE_CHECKING:
 | 
					if TYPE_CHECKING:
 | 
				
			||||||
    from ..group import Group
 | 
					    from ..group import Group
 | 
				
			||||||
    from ..group.mealplan import GroupMealPlan
 | 
					    from ..group.mealplan import GroupMealPlan
 | 
				
			||||||
 | 
					    from ..group.shopping_list import ShoppingList
 | 
				
			||||||
    from ..recipe import RecipeComment, RecipeModel, RecipeTimelineEvent
 | 
					    from ..recipe import RecipeComment, RecipeModel, RecipeTimelineEvent
 | 
				
			||||||
    from .password_reset import PasswordResetModel
 | 
					    from .password_reset import PasswordResetModel
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -81,7 +82,7 @@ class User(SqlAlchemyBase, BaseMixins):
 | 
				
			|||||||
    mealplans: Mapped[Optional["GroupMealPlan"]] = orm.relationship(
 | 
					    mealplans: Mapped[Optional["GroupMealPlan"]] = orm.relationship(
 | 
				
			||||||
        "GroupMealPlan", order_by="GroupMealPlan.date", **sp_args
 | 
					        "GroupMealPlan", order_by="GroupMealPlan.date", **sp_args
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
 | 
					    shopping_lists: Mapped[Optional["ShoppingList"]] = orm.relationship("ShoppingList", **sp_args)
 | 
				
			||||||
    favorite_recipes: Mapped[list["RecipeModel"]] = orm.relationship(
 | 
					    favorite_recipes: Mapped[list["RecipeModel"]] = orm.relationship(
 | 
				
			||||||
        "RecipeModel", secondary=users_to_favorites, back_populates="favorited_by"
 | 
					        "RecipeModel", secondary=users_to_favorites, back_populates="favorited_by"
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
 | 
				
			|||||||
@ -89,7 +89,7 @@ def publish_list_item_events(publisher: Callable, items_collection: ShoppingList
 | 
				
			|||||||
class ShoppingListItemController(BaseCrudController):
 | 
					class ShoppingListItemController(BaseCrudController):
 | 
				
			||||||
    @cached_property
 | 
					    @cached_property
 | 
				
			||||||
    def service(self):
 | 
					    def service(self):
 | 
				
			||||||
        return ShoppingListService(self.repos, self.group)
 | 
					        return ShoppingListService(self.repos, self.group, self.user)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @cached_property
 | 
					    @cached_property
 | 
				
			||||||
    def repo(self):
 | 
					    def repo(self):
 | 
				
			||||||
@ -154,7 +154,7 @@ router = APIRouter(prefix="/groups/shopping/lists", tags=["Group: Shopping Lists
 | 
				
			|||||||
class ShoppingListController(BaseCrudController):
 | 
					class ShoppingListController(BaseCrudController):
 | 
				
			||||||
    @cached_property
 | 
					    @cached_property
 | 
				
			||||||
    def service(self):
 | 
					    def service(self):
 | 
				
			||||||
        return ShoppingListService(self.repos, self.group)
 | 
					        return ShoppingListService(self.repos, self.group, self.user)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @cached_property
 | 
					    @cached_property
 | 
				
			||||||
    def repo(self):
 | 
					    def repo(self):
 | 
				
			||||||
 | 
				
			|||||||
@ -190,6 +190,7 @@ class ShoppingListRecipeRefOut(MealieModel):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
class ShoppingListSave(ShoppingListCreate):
 | 
					class ShoppingListSave(ShoppingListCreate):
 | 
				
			||||||
    group_id: UUID4
 | 
					    group_id: UUID4
 | 
				
			||||||
 | 
					    user_id: UUID4
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class ShoppingListSummary(ShoppingListSave):
 | 
					class ShoppingListSummary(ShoppingListSave):
 | 
				
			||||||
 | 
				
			|||||||
@ -19,13 +19,14 @@ from mealie.schema.group.group_shopping_list import (
 | 
				
			|||||||
)
 | 
					)
 | 
				
			||||||
from mealie.schema.recipe.recipe_ingredient import IngredientFood, IngredientUnit, RecipeIngredient
 | 
					from mealie.schema.recipe.recipe_ingredient import IngredientFood, IngredientUnit, RecipeIngredient
 | 
				
			||||||
from mealie.schema.response.pagination import OrderDirection, PaginationQuery
 | 
					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:
 | 
					class ShoppingListService:
 | 
				
			||||||
    def __init__(self, repos: AllRepositories, group: GroupInDB):
 | 
					    def __init__(self, repos: AllRepositories, group: GroupInDB, user: UserOut):
 | 
				
			||||||
        self.repos = repos
 | 
					        self.repos = repos
 | 
				
			||||||
        self.group = group
 | 
					        self.group = group
 | 
				
			||||||
 | 
					        self.user = user
 | 
				
			||||||
        self.shopping_lists = repos.group_shopping_lists
 | 
					        self.shopping_lists = repos.group_shopping_lists
 | 
				
			||||||
        self.list_items = repos.group_shopping_list_item
 | 
					        self.list_items = repos.group_shopping_list_item
 | 
				
			||||||
        self.list_item_refs = repos.group_shopping_list_item_references
 | 
					        self.list_item_refs = repos.group_shopping_list_item_references
 | 
				
			||||||
@ -476,7 +477,7 @@ class ShoppingListService:
 | 
				
			|||||||
        return self.shopping_lists.get_one(shopping_list.id), items  # type: ignore
 | 
					        return self.shopping_lists.get_one(shopping_list.id), items  # type: ignore
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def create_one_list(self, data: ShoppingListCreate):
 | 
					    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
 | 
					        new_list = self.shopping_lists.create(create_data)  # type: ignore
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        labels = self.repos.group_multi_purpose_labels.by_group(self.group.id).page_all(
 | 
					        labels = self.repos.group_multi_purpose_labels.by_group(self.group.id).page_all(
 | 
				
			||||||
 | 
				
			|||||||
@ -60,7 +60,8 @@ def delete_old_checked_list_items(group_id: UUID4 | None = None):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        for group in groups:
 | 
					        for group in groups:
 | 
				
			||||||
            event_bus_service = EventBusService(session=session, group_id=group.id)
 | 
					            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(
 | 
					            shopping_list_data = repos.group_shopping_lists.by_group(group.id).page_all(
 | 
				
			||||||
                PaginationQuery(page=1, per_page=-1)
 | 
					                PaginationQuery(page=1, per_page=-1)
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										6
									
								
								tests/fixtures/fixture_shopping_lists.py
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										6
									
								
								tests/fixtures/fixture_shopping_lists.py
									
									
									
									
										vendored
									
									
								
							@ -29,7 +29,7 @@ def shopping_lists(database: AllRepositories, unique_user: TestUser):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    for _ in range(3):
 | 
					    for _ in range(3):
 | 
				
			||||||
        model = database.group_shopping_lists.create(
 | 
					        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)
 | 
					        models.append(model)
 | 
				
			||||||
@ -46,7 +46,7 @@ def shopping_lists(database: AllRepositories, unique_user: TestUser):
 | 
				
			|||||||
@pytest.fixture(scope="function")
 | 
					@pytest.fixture(scope="function")
 | 
				
			||||||
def shopping_list(database: AllRepositories, unique_user: TestUser):
 | 
					def shopping_list(database: AllRepositories, unique_user: TestUser):
 | 
				
			||||||
    model = database.group_shopping_lists.create(
 | 
					    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
 | 
					    yield model
 | 
				
			||||||
@ -60,7 +60,7 @@ def shopping_list(database: AllRepositories, unique_user: TestUser):
 | 
				
			|||||||
@pytest.fixture(scope="function")
 | 
					@pytest.fixture(scope="function")
 | 
				
			||||||
def list_with_items(database: AllRepositories, unique_user: TestUser):
 | 
					def list_with_items(database: AllRepositories, unique_user: TestUser):
 | 
				
			||||||
    list_model = database.group_shopping_lists.create(
 | 
					    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):
 | 
					    for _ in range(10):
 | 
				
			||||||
 | 
				
			|||||||
@ -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["name"] == payload["name"]
 | 
				
			||||||
    assert response_list["groupId"] == str(unique_user.group_id)
 | 
					    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]):
 | 
					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["id"] == str(shopping_list.id)
 | 
				
			||||||
    assert response_list["name"] == shopping_list.name
 | 
					    assert response_list["name"] == shopping_list.name
 | 
				
			||||||
    assert response_list["groupId"] == str(shopping_list.group_id)
 | 
					    assert response_list["groupId"] == str(shopping_list.group_id)
 | 
				
			||||||
 | 
					    assert response_list["userId"] == str(shopping_list.user_id)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_shopping_lists_update_one(
 | 
					def test_shopping_lists_update_one(
 | 
				
			||||||
@ -58,6 +60,7 @@ def test_shopping_lists_update_one(
 | 
				
			|||||||
        "name": random_string(10),
 | 
					        "name": random_string(10),
 | 
				
			||||||
        "id": str(sample_list.id),
 | 
					        "id": str(sample_list.id),
 | 
				
			||||||
        "groupId": str(sample_list.group_id),
 | 
					        "groupId": str(sample_list.group_id),
 | 
				
			||||||
 | 
					        "userId": str(sample_list.user_id),
 | 
				
			||||||
        "listItems": [],
 | 
					        "listItems": [],
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -71,6 +74,7 @@ def test_shopping_lists_update_one(
 | 
				
			|||||||
    assert response_list["id"] == str(sample_list.id)
 | 
					    assert response_list["id"] == str(sample_list.id)
 | 
				
			||||||
    assert response_list["name"] == payload["name"]
 | 
					    assert response_list["name"] == payload["name"]
 | 
				
			||||||
    assert response_list["groupId"] == str(sample_list.group_id)
 | 
					    assert response_list["groupId"] == str(sample_list.group_id)
 | 
				
			||||||
 | 
					    assert response_list["userId"] == str(sample_list.user_id)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_shopping_lists_delete_one(
 | 
					def test_shopping_lists_delete_one(
 | 
				
			||||||
 | 
				
			|||||||
@ -758,7 +758,7 @@ def test_pagination_order_by_nulls(
 | 
				
			|||||||
def test_pagination_shopping_list_items_with_labels(database: AllRepositories, unique_user: TestUser):
 | 
					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
 | 
					    # create a shopping list and populate it with some items with labels, and some without labels
 | 
				
			||||||
    shopping_list = database.group_shopping_lists.create(
 | 
					    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(
 | 
					    labels = database.group_multi_purpose_labels.create_many(
 | 
				
			||||||
 | 
				
			|||||||
@ -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_repo = database.group_shopping_lists.by_group(unique_user.group_id)
 | 
				
			||||||
    list_item_repo = database.group_shopping_list_item
 | 
					    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(
 | 
					    unchecked_items = list_item_repo.create_many(
 | 
				
			||||||
        [
 | 
					        [
 | 
				
			||||||
            ShoppingListItemCreate(note=random_string(), shopping_list_id=shopping_list.id)
 | 
					            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_repo = database.group_shopping_lists.by_group(unique_user.group_id)
 | 
				
			||||||
    list_item_repo = database.group_shopping_list_item
 | 
					    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(
 | 
					    unchecked_items = list_item_repo.create_many(
 | 
				
			||||||
        [
 | 
					        [
 | 
				
			||||||
            ShoppingListItemCreate(note=random_string(), shopping_list_id=shopping_list.id)
 | 
					            ShoppingListItemCreate(note=random_string(), shopping_list_id=shopping_list.id)
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user