Skip to content

Commit

Permalink
feat: plural foods and units, and aliases (#2674)
Browse files Browse the repository at this point in the history
* added plural names and alias tables to foods/units

* updated models to include plural names and aliases

* updated parser to include plural and aliases

* fixed migrations

* fixed recursive models

* added plural abbreviation to migration

* updated parser and display prop

* update displays to use plurals

* fix display edgecase and remove print

* added/updated display tests

* fixed model bug and added parser tests

* added tests for aliases

* added new plural options to data management page

* removed unique constraint

* made base dialog more customizable

* added alias management to food and unit data pages

* removed unused awaits

* 🧹
  • Loading branch information
michael-genson authored Nov 14, 2023
1 parent 4b55b83 commit d440d51
Show file tree
Hide file tree
Showing 17 changed files with 1,175 additions and 98 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,15 @@ def populate_normalized_fields():
bind = op.get_bind()
session = orm.Session(bind=bind)

units = session.execute(select(IngredientUnitModel)).scalars().all()
units = (
session.execute(
select(IngredientUnitModel).options(
orm.load_only(IngredientUnitModel.name, IngredientUnitModel.abbreviation)
)
)
.scalars()
.all()
)
for unit in units:
if unit.name is not None:
unit.name_normalized = IngredientUnitModel.normalize(unit.name)
Expand All @@ -32,7 +40,9 @@ def populate_normalized_fields():

session.add(unit)

foods = session.execute(select(IngredientFoodModel)).scalars().all()
foods = (
session.execute(select(IngredientFoodModel).options(orm.load_only(IngredientFoodModel.name))).scalars().all()
)
for food in foods:
if food.name is not None:
food.name_normalized = IngredientFoodModel.normalize(food.name)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
from typing import Any

import sqlalchemy as sa
from sqlalchemy.orm import Session
from sqlalchemy.orm import Session, load_only

import mealie.db.migration_types
from alembic import op
Expand Down Expand Up @@ -44,7 +44,7 @@ def _is_postgres():

def _get_duplicates(session: Session, model: SqlAlchemyBase) -> defaultdict[str, list[str]]:
duplicate_map: defaultdict[str, list[str]] = defaultdict(list)
for obj in session.query(model).all():
for obj in session.query(model).options(load_only(model.id, model.group_id, model.name)).all():
key = f"{obj.group_id}$${obj.name}"
duplicate_map[key].append(str(obj.id))

Expand Down Expand Up @@ -117,9 +117,9 @@ def _resolve_duplivate_foods_units_labels():
continue

keep_id = ids[0]
keep_obj = session.query(model).filter_by(id=keep_id).first()
keep_obj = session.query(model).options(load_only(model.id)).filter_by(id=keep_id).first()
for dupe_id in ids[1:]:
dupe_obj = session.query(model).filter_by(id=dupe_id).first()
dupe_obj = session.query(model).options(load_only(model.id)).filter_by(id=dupe_id).first()
resolve_func(session, keep_obj, dupe_obj)


Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
"""added plural names and alias tables for foods and units
Revision ID: ba1e4a6cfe99
Revises: dded3119c1fe
Create Date: 2023-10-19 19:22:55.369319
"""
import sqlalchemy as sa

import mealie.db.migration_types
from alembic import op

# revision identifiers, used by Alembic.
revision = "ba1e4a6cfe99"
down_revision = "dded3119c1fe"
branch_labels = None
depends_on = None


def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.create_table(
"ingredient_units_aliases",
sa.Column("id", mealie.db.migration_types.GUID(), nullable=False),
sa.Column("unit_id", mealie.db.migration_types.GUID(), nullable=False),
sa.Column("name", sa.String(), nullable=False),
sa.Column("name_normalized", sa.String(), nullable=True),
sa.Column("created_at", sa.DateTime(), nullable=True),
sa.Column("update_at", sa.DateTime(), nullable=True),
sa.ForeignKeyConstraint(
["unit_id"],
["ingredient_units.id"],
),
sa.PrimaryKeyConstraint("id", "unit_id"),
)
op.create_index(
op.f("ix_ingredient_units_aliases_created_at"), "ingredient_units_aliases", ["created_at"], unique=False
)
op.create_index(
op.f("ix_ingredient_units_aliases_name_normalized"),
"ingredient_units_aliases",
["name_normalized"],
unique=False,
)
op.create_table(
"ingredient_foods_aliases",
sa.Column("id", mealie.db.migration_types.GUID(), nullable=False),
sa.Column("food_id", mealie.db.migration_types.GUID(), nullable=False),
sa.Column("name", sa.String(), nullable=False),
sa.Column("name_normalized", sa.String(), nullable=True),
sa.Column("created_at", sa.DateTime(), nullable=True),
sa.Column("update_at", sa.DateTime(), nullable=True),
sa.ForeignKeyConstraint(
["food_id"],
["ingredient_foods.id"],
),
sa.PrimaryKeyConstraint("id", "food_id"),
)
op.create_index(
op.f("ix_ingredient_foods_aliases_created_at"), "ingredient_foods_aliases", ["created_at"], unique=False
)
op.create_index(
op.f("ix_ingredient_foods_aliases_name_normalized"),
"ingredient_foods_aliases",
["name_normalized"],
unique=False,
)
op.add_column("ingredient_foods", sa.Column("plural_name", sa.String(), nullable=True))
op.add_column("ingredient_foods", sa.Column("plural_name_normalized", sa.String(), nullable=True))
op.create_index(
op.f("ix_ingredient_foods_plural_name_normalized"), "ingredient_foods", ["plural_name_normalized"], unique=False
)
op.add_column("ingredient_units", sa.Column("plural_name", sa.String(), nullable=True))
op.add_column("ingredient_units", sa.Column("plural_name_normalized", sa.String(), nullable=True))
op.create_index(
op.f("ix_ingredient_units_plural_name_normalized"), "ingredient_units", ["plural_name_normalized"], unique=False
)
op.add_column("ingredient_units", sa.Column("plural_abbreviation", sa.String(), nullable=True))
op.add_column("ingredient_units", sa.Column("plural_abbreviation_normalized", sa.String(), nullable=True))
op.create_index(
op.f("ix_ingredient_units_plural_abbreviation_normalized"),
"ingredient_units",
["plural_abbreviation_normalized"],
unique=False,
)
# ### end Alembic commands ###


def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_index(op.f("ix_ingredient_units_plural_abbreviation_normalized"), table_name="ingredient_units")
op.drop_column("ingredient_units", "plural_abbreviation_normalized")
op.drop_column("ingredient_units", "plural_abbreviation")
op.drop_index(op.f("ix_ingredient_units_plural_name_normalized"), table_name="ingredient_units")
op.drop_column("ingredient_units", "plural_name_normalized")
op.drop_column("ingredient_units", "plural_name")
op.drop_index(op.f("ix_ingredient_foods_plural_name_normalized"), table_name="ingredient_foods")
op.drop_column("ingredient_foods", "plural_name_normalized")
op.drop_column("ingredient_foods", "plural_name")
op.drop_index(op.f("ix_ingredient_foods_aliases_name_normalized"), table_name="ingredient_foods_aliases")
op.drop_index(op.f("ix_ingredient_foods_aliases_created_at"), table_name="ingredient_foods_aliases")
op.drop_table("ingredient_foods_aliases")
op.drop_index(op.f("ix_ingredient_units_aliases_name_normalized"), table_name="ingredient_units_aliases")
op.drop_index(op.f("ix_ingredient_units_aliases_created_at"), table_name="ingredient_units_aliases")
op.drop_table("ingredient_units_aliases")
# ### end Alembic commands ###
141 changes: 141 additions & 0 deletions frontend/components/Domain/Recipe/RecipeDataAliasManagerDialog.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
<template>
<div>
<BaseDialog
v-model="dialog"
:title="$t('data-pages.manage-aliases')"
:icon="$globals.icons.edit"
:submit-icon="$globals.icons.check"
:submit-text="$tc('general.confirm')"
@submit="saveAliases"
@cancel="$emit('cancel')"
>
<v-card-text>
<v-container>
<v-row v-for="alias, i in aliases" :key="i">
<v-col cols="10">
<v-text-field
v-model="alias.name"
:label="$t('general.name')"
:rules="[validators.required]"
/>
</v-col>
<v-col cols="2">
<BaseButtonGroup
:buttons="[
{
icon: $globals.icons.delete,
text: $tc('general.delete'),
event: 'delete'
}
]"
@delete="deleteAlias(i)"
/>
</v-col>
</v-row>
</v-container>
</v-card-text>
<template #custom-card-action>
<BaseButton edit @click="createAlias">{{ $t('data-pages.create-alias') }}
<template #icon>
{{ $globals.icons.create }}
</template>
</BaseButton>
</template>
</BaseDialog>
</div>
</template>

<script lang="ts">
import { computed, defineComponent, ref } from "@nuxtjs/composition-api";
import { whenever } from "@vueuse/core";
import { validators } from "~/composables/use-validators";
import { IngredientFood, IngredientUnit } from "~/lib/api/types/recipe";
export interface GenericAlias {
name: string;
}
export default defineComponent({
props: {
value: {
type: Boolean,
default: false,
},
data: {
type: Object as () => IngredientFood | IngredientUnit,
required: true,
},
},
setup(props, context) {
// V-Model Support
const dialog = computed({
get: () => {
return props.value;
},
set: (val) => {
context.emit("input", val);
},
});
function createAlias() {
aliases.value.push({
"name": "",
})
}
function deleteAlias(index: number) {
aliases.value.splice(index, 1);
}
const aliases = ref<GenericAlias[]>(props.data.aliases || []);
function initAliases() {
aliases.value = [...props.data.aliases || []];
if (!aliases.value.length) {
createAlias();
}
}
initAliases();
whenever(
() => props.value,
() => {
initAliases();
},
)
function saveAliases() {
const seenAliasNames: string[] = [];
const keepAliases: GenericAlias[] = [];
aliases.value.forEach((alias) => {
if (
!alias.name
|| alias.name === props.data.name
|| alias.name === props.data.pluralName
// @ts-ignore only applies to units
|| alias.name === props.data.abbreviation
// @ts-ignore only applies to units
|| alias.name === props.data.pluralAbbreviation
|| seenAliasNames.includes(alias.name)
) {
return;
}
keepAliases.push(alias);
seenAliasNames.push(alias.name);
})
aliases.value = keepAliases;
context.emit("submit", keepAliases);
}
return {
aliases,
createAlias,
dialog,
deleteAlias,
saveAliases,
validators,
}
},
});
</script>
8 changes: 8 additions & 0 deletions frontend/components/global/BaseDialog.vue
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,12 @@
</template>
{{ $t("general.confirm") }}
</BaseButton>
<slot name="custom-card-action"></slot>
<BaseButton v-if="$listeners.submit" type="submit" @click="submitEvent">
{{ submitText }}
<template v-if="submitIcon" #icon>
{{ submitIcon }}
</template>
</BaseButton>
</slot>
</v-card-actions>
Expand Down Expand Up @@ -109,6 +113,10 @@ export default defineComponent({
default: null,
type: Boolean,
},
submitIcon: {
type: String,
default: null,
},
submitText: {
type: String,
default: function () {
Expand Down
Loading

0 comments on commit d440d51

Please sign in to comment.