mirror of
https://github.com/mealie-recipes/mealie.git
synced 2025-05-24 01:12:54 -04:00
Feature/user seedable foods (#1176)
* remove odd ingredients * UI Elements for food * update translated percentage * spek -> speck * generate types * seeder api endpoints + tests * implement foods seeder UI * localize some food strings
This commit is contained in:
parent
67178f9b74
commit
d6e2b4ab85
@ -19,7 +19,7 @@ repos:
|
||||
- id: isort
|
||||
name: isort (python)
|
||||
- repo: https://github.com/psf/black
|
||||
rev: 21.12b0
|
||||
rev: 22.3.0
|
||||
hooks:
|
||||
- id: black
|
||||
- repo: https://github.com/pycqa/flake8
|
||||
|
26
frontend/api/class-interfaces/group-seeder.ts
Normal file
26
frontend/api/class-interfaces/group-seeder.ts
Normal file
@ -0,0 +1,26 @@
|
||||
import { BaseAPI } from "../_base";
|
||||
import { SuccessResponse } from "~/types/api-types/response";
|
||||
import { SeederConfig } from "~/types/api-types/group";
|
||||
|
||||
const prefix = "/api";
|
||||
|
||||
const routes = {
|
||||
base: `${prefix}/groups/seeders`,
|
||||
foods: `${prefix}/groups/seeders/foods`,
|
||||
units: `${prefix}/groups/seeders/units`,
|
||||
labels: `${prefix}/groups/seeders/labels`,
|
||||
};
|
||||
|
||||
export class GroupDataSeederApi extends BaseAPI {
|
||||
foods(payload: SeederConfig) {
|
||||
return this.requests.post<SuccessResponse>(routes.foods, payload);
|
||||
}
|
||||
|
||||
units(payload: SeederConfig) {
|
||||
return this.requests.post<SuccessResponse>(routes.units, payload);
|
||||
}
|
||||
|
||||
labels(payload: SeederConfig) {
|
||||
return this.requests.post<SuccessResponse>(routes.labels, payload);
|
||||
}
|
||||
}
|
@ -23,6 +23,7 @@ import { ShoppingApi } from "./class-interfaces/group-shopping-lists";
|
||||
import { MultiPurposeLabelsApi } from "./class-interfaces/group-multiple-purpose-labels";
|
||||
import { GroupEventNotifierApi } from "./class-interfaces/group-event-notifier";
|
||||
import { MealPlanRulesApi } from "./class-interfaces/group-mealplan-rules";
|
||||
import { GroupDataSeederApi } from "./class-interfaces/group-seeder";
|
||||
import { ApiRequestInstance } from "~/types/api";
|
||||
|
||||
class Api {
|
||||
@ -50,6 +51,7 @@ class Api {
|
||||
public multiPurposeLabels: MultiPurposeLabelsApi;
|
||||
public groupEventNotifier: GroupEventNotifierApi;
|
||||
public upload: UploadFile;
|
||||
public seeders: GroupDataSeederApi;
|
||||
|
||||
constructor(requests: ApiRequestInstance) {
|
||||
// Recipes
|
||||
@ -75,6 +77,7 @@ class Api {
|
||||
this.groupReports = new GroupReportsApi(requests);
|
||||
this.shopping = new ShoppingApi(requests);
|
||||
this.multiPurposeLabels = new MultiPurposeLabelsApi(requests);
|
||||
this.seeders = new GroupDataSeederApi(requests);
|
||||
|
||||
// Admin
|
||||
this.backups = new BackupAPI(requests);
|
||||
|
@ -9,7 +9,7 @@
|
||||
:fullscreen="$vuetify.breakpoint.xsOnly"
|
||||
>
|
||||
<v-card height="100%">
|
||||
<v-app-bar dark :color="color" class="mt-n1">
|
||||
<v-app-bar dark dense :color="color" class="">
|
||||
<v-icon large left>
|
||||
{{ icon }}
|
||||
</v-icon>
|
||||
|
@ -6,7 +6,6 @@
|
||||
v-model="locale"
|
||||
:items="locales"
|
||||
item-text="name"
|
||||
menu-props="auto"
|
||||
class="my-3"
|
||||
hide-details
|
||||
outlined
|
||||
|
@ -3,12 +3,12 @@ export const LOCALES = [
|
||||
{
|
||||
name: "繁體中文 (Chinese traditional)",
|
||||
value: "zh-TW",
|
||||
progress: 100,
|
||||
progress: 90,
|
||||
},
|
||||
{
|
||||
name: "简体中文 (Chinese simplified)",
|
||||
value: "zh-CN",
|
||||
progress: 100,
|
||||
progress: 74,
|
||||
},
|
||||
{
|
||||
name: "Tiếng Việt (Vietnamese)",
|
||||
@ -18,17 +18,17 @@ export const LOCALES = [
|
||||
{
|
||||
name: "Українська (Ukrainian)",
|
||||
value: "uk-UA",
|
||||
progress: 100,
|
||||
progress: 99,
|
||||
},
|
||||
{
|
||||
name: "Türkçe (Turkish)",
|
||||
value: "tr-TR",
|
||||
progress: 7,
|
||||
progress: 5,
|
||||
},
|
||||
{
|
||||
name: "Svenska (Swedish)",
|
||||
value: "sv-SE",
|
||||
progress: 100,
|
||||
progress: 92,
|
||||
},
|
||||
{
|
||||
name: "српски (Serbian)",
|
||||
@ -38,12 +38,12 @@ export const LOCALES = [
|
||||
{
|
||||
name: "Slovak",
|
||||
value: "sk-SK",
|
||||
progress: 100,
|
||||
progress: 74,
|
||||
},
|
||||
{
|
||||
name: "Pусский (Russian)",
|
||||
value: "ru-RU",
|
||||
progress: 100,
|
||||
progress: 74,
|
||||
},
|
||||
{
|
||||
name: "Română (Romanian)",
|
||||
@ -53,12 +53,12 @@ export const LOCALES = [
|
||||
{
|
||||
name: "Português (Portugese)",
|
||||
value: "pt-PT",
|
||||
progress: 15,
|
||||
progress: 11,
|
||||
},
|
||||
{
|
||||
name: "Português do Brasil (Brazilian Portuguese)",
|
||||
value: "pt-BR",
|
||||
progress: 64,
|
||||
progress: 47,
|
||||
},
|
||||
{
|
||||
name: "Polski (Polish)",
|
||||
@ -68,12 +68,12 @@ export const LOCALES = [
|
||||
{
|
||||
name: "Norsk (Norwegian)",
|
||||
value: "no-NO",
|
||||
progress: 100,
|
||||
progress: 74,
|
||||
},
|
||||
{
|
||||
name: "Nederlands (Dutch)",
|
||||
value: "nl-NL",
|
||||
progress: 100,
|
||||
progress: 98,
|
||||
},
|
||||
{
|
||||
name: "한국어 (Korean)",
|
||||
@ -88,7 +88,7 @@ export const LOCALES = [
|
||||
{
|
||||
name: "Italiano (Italian)",
|
||||
value: "it-IT",
|
||||
progress: 99,
|
||||
progress: 96,
|
||||
},
|
||||
{
|
||||
name: "Magyar (Hungarian)",
|
||||
@ -103,12 +103,12 @@ export const LOCALES = [
|
||||
{
|
||||
name: "Français (French)",
|
||||
value: "fr-FR",
|
||||
progress: 100,
|
||||
progress: 99,
|
||||
},
|
||||
{
|
||||
name: "French, Canada",
|
||||
value: "fr-CA",
|
||||
progress: 100,
|
||||
progress: 88,
|
||||
},
|
||||
{
|
||||
name: "Suomi (Finnish)",
|
||||
@ -118,7 +118,7 @@ export const LOCALES = [
|
||||
{
|
||||
name: "Español (Spanish)",
|
||||
value: "es-ES",
|
||||
progress: 100,
|
||||
progress: 74,
|
||||
},
|
||||
{
|
||||
name: "American English",
|
||||
@ -128,22 +128,22 @@ export const LOCALES = [
|
||||
{
|
||||
name: "British English",
|
||||
value: "en-GB",
|
||||
progress: 100,
|
||||
progress: 74,
|
||||
},
|
||||
{
|
||||
name: "Ελληνικά (Greek)",
|
||||
value: "el-GR",
|
||||
progress: 100,
|
||||
progress: 86,
|
||||
},
|
||||
{
|
||||
name: "Deutsch (German)",
|
||||
value: "de-DE",
|
||||
progress: 100,
|
||||
progress: 99,
|
||||
},
|
||||
{
|
||||
name: "Dansk (Danish)",
|
||||
value: "da-DK",
|
||||
progress: 100,
|
||||
progress: 83,
|
||||
},
|
||||
{
|
||||
name: "Čeština (Czech)",
|
||||
@ -153,7 +153,7 @@ export const LOCALES = [
|
||||
{
|
||||
name: "Català (Catalan)",
|
||||
value: "ca-ES",
|
||||
progress: 100,
|
||||
progress: 74,
|
||||
},
|
||||
{
|
||||
name: "العربية (Arabic)",
|
||||
@ -165,4 +165,4 @@ export const LOCALES = [
|
||||
value: "af-ZA",
|
||||
progress: 0,
|
||||
},
|
||||
];
|
||||
]
|
||||
|
@ -130,7 +130,9 @@
|
||||
"url": "URL",
|
||||
"view": "View",
|
||||
"wednesday": "Wednesday",
|
||||
"yes": "Yes"
|
||||
"yes": "Yes",
|
||||
"foods": "Foods",
|
||||
"units": "Units"
|
||||
},
|
||||
"group": {
|
||||
"are-you-sure-you-want-to-delete-the-group": "Are you sure you want to delete <b>{groupName}<b/>?",
|
||||
@ -502,5 +504,14 @@
|
||||
"select-description": "Choose the language for the Mealie UI. The setting only applies to you, not other users.",
|
||||
"how-to-contribute-description": "Is something not translated yet, mistranslated, or your language missing from the list? {read-the-docs-link} on how to contribute!",
|
||||
"read-the-docs": "Read the docs"
|
||||
},
|
||||
"data-pages": {
|
||||
"seed-data": "Seed Data",
|
||||
"foods": {
|
||||
"merge-dialog-text": "Combining the selected foods will merge the source food and target food into a single food. The source food will be deleted and all of the references to the source food will be updated to point to the target food.",
|
||||
"merge-food-example": "Merging {food1} into {food2}",
|
||||
"seed-dialog-text": "Seed the database with foods based on your local language. This will create 200+ common foods that can be used to organize your database. Foods are translated via a community effort.",
|
||||
"seed-dialog-warning": "You have already have some items in your database. This action will not reconcile duplicates, you will have to manage them manually."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3,18 +3,57 @@
|
||||
<!-- Merge Dialog -->
|
||||
<BaseDialog v-model="mergeDialog" :icon="$globals.icons.foods" title="Combine Food" @confirm="mergeFoods">
|
||||
<v-card-text>
|
||||
Combining the selected foods will merge the Source Food and Target Food into a single food. The
|
||||
<strong> Source Food will be deleted </strong> and all of the references to the Source Food will be updated to
|
||||
point to the Target Food.
|
||||
<div>
|
||||
{{ $t("data-pages.foods.merge-dialog-text") }}
|
||||
</div>
|
||||
<v-autocomplete v-model="fromFood" return-object :items="foods" item-text="name" label="Source Food" />
|
||||
<v-autocomplete v-model="toFood" return-object :items="foods" item-text="name" label="Target Food" />
|
||||
|
||||
<template v-if="canMerge && fromFood && toFood">
|
||||
<div class="text-center">Merging {{ fromFood.name }} into {{ toFood.name }}</div>
|
||||
<div class="text-center">
|
||||
{{ $t("data-pages.foods.merge-food-example", { food1: fromFood.name, food2: toFood.name }) }}
|
||||
</div>
|
||||
</template>
|
||||
</v-card-text>
|
||||
</BaseDialog>
|
||||
|
||||
<!-- Seed Dialog-->
|
||||
<BaseDialog
|
||||
v-model="seedDialog"
|
||||
:icon="$globals.icons.foods"
|
||||
:title="$tc('data-pages.seed-data')"
|
||||
@confirm="seedDatabase"
|
||||
>
|
||||
<v-card-text>
|
||||
<div class="pb-2">
|
||||
{{ $t("data-pages.foods.seed-dialog-text") }}
|
||||
</div>
|
||||
<v-autocomplete
|
||||
v-model="locale"
|
||||
:items="locales"
|
||||
item-text="name"
|
||||
label="Select Language"
|
||||
class="my-3"
|
||||
hide-details
|
||||
outlined
|
||||
offset
|
||||
>
|
||||
<template #item="{ item }">
|
||||
<v-list-item-content>
|
||||
<v-list-item-title v-text="item.name"></v-list-item-title>
|
||||
<v-list-item-subtitle>
|
||||
{{ item.progress }}% {{ $tc("language-dialog.translated") }}
|
||||
</v-list-item-subtitle>
|
||||
</v-list-item-content>
|
||||
</template>
|
||||
</v-autocomplete>
|
||||
|
||||
<v-alert v-if="foods.length > 0" type="error" class="mb-0 text-body-2">
|
||||
{{ $t("data-pages.foods.seed-dialog-warning") }}
|
||||
</v-alert>
|
||||
</v-card-text>
|
||||
</BaseDialog>
|
||||
|
||||
<!-- Edit Dialog -->
|
||||
<BaseDialog
|
||||
v-model="editDialog"
|
||||
@ -73,6 +112,12 @@
|
||||
{{ item.label.name }}
|
||||
</MultiPurposeLabel>
|
||||
</template>
|
||||
<template #button-bottom>
|
||||
<BaseButton @click="seedDialog = true">
|
||||
<template #icon> {{ $globals.icons.database }} </template>
|
||||
Seed
|
||||
</BaseButton>
|
||||
</template>
|
||||
</CrudTable>
|
||||
</div>
|
||||
</template>
|
||||
@ -80,11 +125,13 @@
|
||||
<script lang="ts">
|
||||
import { defineComponent, onMounted, ref } from "@nuxtjs/composition-api";
|
||||
import { computed } from "vue-demi";
|
||||
import type { LocaleObject } from "@nuxtjs/i18n";
|
||||
import { validators } from "~/composables/use-validators";
|
||||
import { useUserApi } from "~/composables/api";
|
||||
import { IngredientFood } from "~/types/api-types/recipe";
|
||||
import MultiPurposeLabel from "~/components/Domain/ShoppingList/MultiPurposeLabel.vue";
|
||||
import { MultiPurposeLabelSummary } from "~/types/api-types/labels";
|
||||
import { useLocales } from "~/composables/use-locales";
|
||||
|
||||
export default defineComponent({
|
||||
components: { MultiPurposeLabel },
|
||||
@ -193,6 +240,30 @@ export default defineComponent({
|
||||
allLabels.value = data ?? [];
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Seed
|
||||
|
||||
const seedDialog = ref(false);
|
||||
const locale = ref("");
|
||||
|
||||
const { locales: LOCALES, locale: currentLocale, i18n } = useLocales();
|
||||
|
||||
onMounted(() => {
|
||||
locale.value = currentLocale.value;
|
||||
});
|
||||
|
||||
const locales = LOCALES.filter((locale) =>
|
||||
(i18n.locales as LocaleObject[]).map((i18nLocale) => i18nLocale.code).includes(locale.value)
|
||||
);
|
||||
|
||||
async function seedDatabase() {
|
||||
const { data } = await userApi.seeders.foods({ locale: locale.value });
|
||||
|
||||
if (data) {
|
||||
refreshFoods();
|
||||
}
|
||||
}
|
||||
|
||||
refreshLabels();
|
||||
return {
|
||||
tableConfig,
|
||||
@ -215,6 +286,11 @@ export default defineComponent({
|
||||
mergeDialog,
|
||||
fromFood,
|
||||
toFood,
|
||||
// Seed Data
|
||||
locale,
|
||||
locales,
|
||||
seedDialog,
|
||||
seedDatabase,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
@ -103,13 +103,11 @@ export interface RecipeSummary {
|
||||
}
|
||||
export interface RecipeCategory {
|
||||
id?: string;
|
||||
groupId: string;
|
||||
name: string;
|
||||
slug: string;
|
||||
}
|
||||
export interface RecipeTag {
|
||||
id?: string;
|
||||
groupId: string;
|
||||
name: string;
|
||||
slug: string;
|
||||
}
|
||||
|
@ -89,13 +89,11 @@ export interface RecipeSummary {
|
||||
}
|
||||
export interface RecipeCategory {
|
||||
id?: string;
|
||||
groupId: string;
|
||||
name: string;
|
||||
slug: string;
|
||||
}
|
||||
export interface RecipeTag {
|
||||
id?: string;
|
||||
groupId: string;
|
||||
name: string;
|
||||
slug: string;
|
||||
}
|
||||
|
@ -258,13 +258,11 @@ export interface RecipeSummary {
|
||||
}
|
||||
export interface RecipeCategory {
|
||||
id?: string;
|
||||
groupId: string;
|
||||
name: string;
|
||||
slug: string;
|
||||
}
|
||||
export interface RecipeTag {
|
||||
id?: string;
|
||||
groupId: string;
|
||||
name: string;
|
||||
slug: string;
|
||||
}
|
||||
@ -307,6 +305,9 @@ export interface SaveWebhook {
|
||||
time?: string;
|
||||
groupId: string;
|
||||
}
|
||||
export interface SeederConfig {
|
||||
locale: string;
|
||||
}
|
||||
export interface SetPermissions {
|
||||
userId: string;
|
||||
canManage?: boolean;
|
||||
|
@ -119,13 +119,11 @@ export interface RecipeSummary {
|
||||
}
|
||||
export interface RecipeCategory {
|
||||
id?: string;
|
||||
groupId: string;
|
||||
name: string;
|
||||
slug: string;
|
||||
}
|
||||
export interface RecipeTag {
|
||||
id?: string;
|
||||
groupId: string;
|
||||
name: string;
|
||||
slug: string;
|
||||
}
|
||||
|
@ -42,6 +42,7 @@ export interface CategoryOut {
|
||||
name: string;
|
||||
id: string;
|
||||
slug: string;
|
||||
groupId: string;
|
||||
}
|
||||
export interface CategorySave {
|
||||
name: string;
|
||||
@ -68,19 +69,14 @@ export interface CreateRecipeBulk {
|
||||
}
|
||||
export interface RecipeCategory {
|
||||
id?: string;
|
||||
groupId: string;
|
||||
name: string;
|
||||
slug: string;
|
||||
}
|
||||
export interface RecipeTag {
|
||||
id?: string;
|
||||
groupId: string;
|
||||
name: string;
|
||||
slug: string;
|
||||
}
|
||||
export interface CreateRecipeByUrl {
|
||||
url: string;
|
||||
}
|
||||
export interface CreateRecipeByUrlBulk {
|
||||
imports: CreateRecipeBulk[];
|
||||
}
|
||||
@ -115,10 +111,6 @@ export interface MultiPurposeLabelSummary {
|
||||
groupId: string;
|
||||
id: string;
|
||||
}
|
||||
export interface IngredientMerge {
|
||||
fromFood: string;
|
||||
toFood: string;
|
||||
}
|
||||
/**
|
||||
* A list of ingredient references.
|
||||
*/
|
||||
@ -140,6 +132,14 @@ export interface IngredientsRequest {
|
||||
parser?: RegisteredParser & string;
|
||||
ingredients: string[];
|
||||
}
|
||||
export interface MergeFood {
|
||||
fromFood: string;
|
||||
toFood: string;
|
||||
}
|
||||
export interface MergeUnit {
|
||||
fromUnit: string;
|
||||
toUnit: string;
|
||||
}
|
||||
export interface Nutrition {
|
||||
calories?: string;
|
||||
fatContent?: string;
|
||||
@ -348,6 +348,13 @@ export interface SaveIngredientUnit {
|
||||
abbreviation?: string;
|
||||
groupId: string;
|
||||
}
|
||||
export interface ScrapeRecipe {
|
||||
url: string;
|
||||
includeTags?: boolean;
|
||||
}
|
||||
export interface ScrapeRecipeTest {
|
||||
url: string;
|
||||
}
|
||||
export interface SlugResponse {}
|
||||
export interface TagIn {
|
||||
name: string;
|
||||
|
@ -132,13 +132,11 @@ export interface RecipeSummary {
|
||||
}
|
||||
export interface RecipeCategory {
|
||||
id?: string;
|
||||
groupId: string;
|
||||
name: string;
|
||||
slug: string;
|
||||
}
|
||||
export interface RecipeTag {
|
||||
id?: string;
|
||||
groupId: string;
|
||||
name: string;
|
||||
slug: string;
|
||||
}
|
||||
|
132
frontend/types/components.d.ts
vendored
132
frontend/types/components.d.ts
vendored
@ -1,76 +1,74 @@
|
||||
// This Code is auto generated by gen_global_components.py
|
||||
import BaseCardSectionTitle from "@/components/global/BaseCardSectionTitle.vue";
|
||||
import MarkdownEditor from "@/components/global/MarkdownEditor.vue";
|
||||
import AppLoader from "@/components/global/AppLoader.vue";
|
||||
import BaseOverflowButton from "@/components/global/BaseOverflowButton.vue";
|
||||
import ReportTable from "@/components/global/ReportTable.vue";
|
||||
import AppToolbar from "@/components/global/AppToolbar.vue";
|
||||
import BaseButtonGroup from "@/components/global/BaseButtonGroup.vue";
|
||||
import BaseButton from "@/components/global/BaseButton.vue";
|
||||
import BannerExperimental from "@/components/global/BannerExperimental.vue";
|
||||
import BaseDialog from "@/components/global/BaseDialog.vue";
|
||||
import RecipeJsonEditor from "@/components/global/RecipeJsonEditor.vue";
|
||||
import StatsCards from "@/components/global/StatsCards.vue";
|
||||
import HelpIcon from "@/components/global/HelpIcon.vue";
|
||||
import InputLabelType from "@/components/global/InputLabelType.vue";
|
||||
import BaseStatCard from "@/components/global/BaseStatCard.vue";
|
||||
import DevDumpJson from "@/components/global/DevDumpJson.vue";
|
||||
import LanguageDialog from "@/components/global/LanguageDialog.vue";
|
||||
import InputQuantity from "@/components/global/InputQuantity.vue";
|
||||
import ToggleState from "@/components/global/ToggleState.vue";
|
||||
import AppButtonCopy from "@/components/global/AppButtonCopy.vue";
|
||||
import CrudTable from "@/components/global/CrudTable.vue";
|
||||
import InputColor from "@/components/global/InputColor.vue";
|
||||
import BaseDivider from "@/components/global/BaseDivider.vue";
|
||||
import AutoForm from "@/components/global/AutoForm.vue";
|
||||
import AppButtonUpload from "@/components/global/AppButtonUpload.vue";
|
||||
import AdvancedOnly from "@/components/global/AdvancedOnly.vue";
|
||||
import BasePageTitle from "@/components/global/BasePageTitle.vue";
|
||||
import ButtonLink from "@/components/global/ButtonLink.vue";
|
||||
|
||||
import TheSnackbar from "@/components/layout/TheSnackbar.vue";
|
||||
import AppHeader from "@/components/layout/AppHeader.vue";
|
||||
import AppSidebar from "@/components/layout/AppSidebar.vue";
|
||||
import AppFooter from "@/components/layout/AppFooter.vue";
|
||||
import BaseCardSectionTitle from "@/components/global/BaseCardSectionTitle.vue";
|
||||
import MarkdownEditor from "@/components/global/MarkdownEditor.vue";
|
||||
import AppLoader from "@/components/global/AppLoader.vue";
|
||||
import BaseOverflowButton from "@/components/global/BaseOverflowButton.vue";
|
||||
import ReportTable from "@/components/global/ReportTable.vue";
|
||||
import AppToolbar from "@/components/global/AppToolbar.vue";
|
||||
import BaseButtonGroup from "@/components/global/BaseButtonGroup.vue";
|
||||
import BaseButton from "@/components/global/BaseButton.vue";
|
||||
import BannerExperimental from "@/components/global/BannerExperimental.vue";
|
||||
import BaseDialog from "@/components/global/BaseDialog.vue";
|
||||
import RecipeJsonEditor from "@/components/global/RecipeJsonEditor.vue";
|
||||
import StatsCards from "@/components/global/StatsCards.vue";
|
||||
import HelpIcon from "@/components/global/HelpIcon.vue";
|
||||
import InputLabelType from "@/components/global/InputLabelType.vue";
|
||||
import BaseStatCard from "@/components/global/BaseStatCard.vue";
|
||||
import DevDumpJson from "@/components/global/DevDumpJson.vue";
|
||||
import LanguageDialog from "@/components/global/LanguageDialog.vue";
|
||||
import InputQuantity from "@/components/global/InputQuantity.vue";
|
||||
import ToggleState from "@/components/global/ToggleState.vue";
|
||||
import AppButtonCopy from "@/components/global/AppButtonCopy.vue";
|
||||
import CrudTable from "@/components/global/CrudTable.vue";
|
||||
import InputColor from "@/components/global/InputColor.vue";
|
||||
import BaseDivider from "@/components/global/BaseDivider.vue";
|
||||
import AutoForm from "@/components/global/AutoForm.vue";
|
||||
import AppButtonUpload from "@/components/global/AppButtonUpload.vue";
|
||||
import AdvancedOnly from "@/components/global/AdvancedOnly.vue";
|
||||
import BasePageTitle from "@/components/global/BasePageTitle.vue";
|
||||
import ButtonLink from "@/components/global/ButtonLink.vue";
|
||||
|
||||
import TheSnackbar from "@/components/layout/TheSnackbar.vue";
|
||||
import AppHeader from "@/components/layout/AppHeader.vue";
|
||||
import AppSidebar from "@/components/layout/AppSidebar.vue";
|
||||
import AppFooter from "@/components/layout/AppFooter.vue";
|
||||
|
||||
declare module "vue" {
|
||||
export interface GlobalComponents {
|
||||
// Global Components
|
||||
BaseCardSectionTitle: typeof BaseCardSectionTitle;
|
||||
MarkdownEditor: typeof MarkdownEditor;
|
||||
AppLoader: typeof AppLoader;
|
||||
BaseOverflowButton: typeof BaseOverflowButton;
|
||||
ReportTable: typeof ReportTable;
|
||||
AppToolbar: typeof AppToolbar;
|
||||
BaseButtonGroup: typeof BaseButtonGroup;
|
||||
BaseButton: typeof BaseButton;
|
||||
BannerExperimental: typeof BannerExperimental;
|
||||
BaseDialog: typeof BaseDialog;
|
||||
RecipeJsonEditor: typeof RecipeJsonEditor;
|
||||
StatsCards: typeof StatsCards;
|
||||
HelpIcon: typeof HelpIcon;
|
||||
InputLabelType: typeof InputLabelType;
|
||||
BaseStatCard: typeof BaseStatCard;
|
||||
DevDumpJson: typeof DevDumpJson;
|
||||
LanguageDialog: typeof LanguageDialog;
|
||||
InputQuantity: typeof InputQuantity;
|
||||
ToggleState: typeof ToggleState;
|
||||
AppButtonCopy: typeof AppButtonCopy;
|
||||
CrudTable: typeof CrudTable;
|
||||
InputColor: typeof InputColor;
|
||||
BaseDivider: typeof BaseDivider;
|
||||
AutoForm: typeof AutoForm;
|
||||
AppButtonUpload: typeof AppButtonUpload;
|
||||
AdvancedOnly: typeof AdvancedOnly;
|
||||
BasePageTitle: typeof BasePageTitle;
|
||||
ButtonLink: typeof ButtonLink;
|
||||
// Layout Components
|
||||
TheSnackbar: typeof TheSnackbar;
|
||||
AppHeader: typeof AppHeader;
|
||||
AppSidebar: typeof AppSidebar;
|
||||
AppFooter: typeof AppFooter;
|
||||
|
||||
BaseCardSectionTitle: typeof BaseCardSectionTitle;
|
||||
MarkdownEditor: typeof MarkdownEditor;
|
||||
AppLoader: typeof AppLoader;
|
||||
BaseOverflowButton: typeof BaseOverflowButton;
|
||||
ReportTable: typeof ReportTable;
|
||||
AppToolbar: typeof AppToolbar;
|
||||
BaseButtonGroup: typeof BaseButtonGroup;
|
||||
BaseButton: typeof BaseButton;
|
||||
BannerExperimental: typeof BannerExperimental;
|
||||
BaseDialog: typeof BaseDialog;
|
||||
RecipeJsonEditor: typeof RecipeJsonEditor;
|
||||
StatsCards: typeof StatsCards;
|
||||
HelpIcon: typeof HelpIcon;
|
||||
InputLabelType: typeof InputLabelType;
|
||||
BaseStatCard: typeof BaseStatCard;
|
||||
DevDumpJson: typeof DevDumpJson;
|
||||
LanguageDialog: typeof LanguageDialog;
|
||||
InputQuantity: typeof InputQuantity;
|
||||
ToggleState: typeof ToggleState;
|
||||
AppButtonCopy: typeof AppButtonCopy;
|
||||
CrudTable: typeof CrudTable;
|
||||
InputColor: typeof InputColor;
|
||||
BaseDivider: typeof BaseDivider;
|
||||
AutoForm: typeof AutoForm;
|
||||
AppButtonUpload: typeof AppButtonUpload;
|
||||
AdvancedOnly: typeof AdvancedOnly;
|
||||
BasePageTitle: typeof BasePageTitle;
|
||||
ButtonLink: typeof ButtonLink;
|
||||
// Layout Components
|
||||
TheSnackbar: typeof TheSnackbar;
|
||||
AppHeader: typeof AppHeader;
|
||||
AppSidebar: typeof AppSidebar;
|
||||
AppFooter: typeof AppFooter;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -13,7 +13,7 @@ def fix_slug_food_names(db: AllRepositories):
|
||||
logger = root_logger.get_logger("init_db")
|
||||
|
||||
if not food:
|
||||
logger.info("No food found with slug: '{}' skipping fix".format(check_for_food))
|
||||
logger.info(f"No food found with slug: '{check_for_food}' skipping fix")
|
||||
return
|
||||
|
||||
all_foods = db.ingredient_foods.get_all()
|
||||
@ -23,5 +23,5 @@ def fix_slug_food_names(db: AllRepositories):
|
||||
for food in all_foods:
|
||||
if food.name in seed_foods:
|
||||
food.name = seed_foods[food.name]
|
||||
logger.info("Updating food: {}".format(food.name))
|
||||
logger.info(f"Updating food: {food.name}")
|
||||
db.ingredient_foods.update(food.id, food)
|
||||
|
@ -14,7 +14,7 @@ from mealie.db.fixes.fix_slug_foods import fix_slug_food_names
|
||||
from mealie.repos.all_repositories import get_repositories
|
||||
from mealie.repos.repository_factory import AllRepositories
|
||||
from mealie.repos.seed.init_users import default_user_init
|
||||
from mealie.repos.seed.seeders import IngredientFoodsSeeder, IngredientUnitsSeeder, MultiPurposeLabelSeeder
|
||||
from mealie.repos.seed.seeders import IngredientUnitsSeeder, MultiPurposeLabelSeeder
|
||||
from mealie.schema.user.user import GroupBase
|
||||
from mealie.services.group_services.group_service import GroupService
|
||||
|
||||
@ -32,7 +32,6 @@ def init_db(db: AllRepositories) -> None:
|
||||
|
||||
seeders = [
|
||||
MultiPurposeLabelSeeder(db, group_id=group_id),
|
||||
IngredientFoodsSeeder(db, group_id=group_id),
|
||||
IngredientUnitsSeeder(db, group_id=group_id),
|
||||
]
|
||||
|
||||
@ -63,11 +62,11 @@ def db_is_at_head(alembic_cfg: config.Config) -> bool:
|
||||
return set(context.get_current_heads()) == set(directory.get_heads())
|
||||
|
||||
|
||||
def safe_try(name: str, func: Callable):
|
||||
def safe_try(func: Callable):
|
||||
try:
|
||||
func()
|
||||
except Exception as e:
|
||||
logger.error(f"Error calling '{name}': {e}")
|
||||
logger.error(f"Error calling '{func.__name__}': {e}")
|
||||
|
||||
|
||||
def connect(session: orm.Session) -> bool:
|
||||
@ -108,14 +107,13 @@ def main():
|
||||
|
||||
db = get_repositories(session)
|
||||
|
||||
init_user = db.users.get_all()
|
||||
if init_user:
|
||||
if db.users.get_all():
|
||||
logger.info("Database exists")
|
||||
else:
|
||||
logger.info("Database contains no users, initializing...")
|
||||
init_db(db)
|
||||
|
||||
safe_try("fix slug food names", lambda: fix_slug_food_names(db))
|
||||
safe_try(lambda: fix_slug_food_names(db))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
@ -25,5 +25,5 @@ class AbstractSeeder(ABC):
|
||||
self.resources = Path(__file__).parent / "resources"
|
||||
|
||||
@abstractmethod
|
||||
def seed(self):
|
||||
def seed(self, locale: str | None = None) -> None:
|
||||
...
|
||||
|
@ -80,7 +80,7 @@
|
||||
"eggplant": "eggplant",
|
||||
"endive": "endive",
|
||||
"fats": "fats",
|
||||
"spek": "spek",
|
||||
"speck": "speck",
|
||||
"fava-beans": "fava beans",
|
||||
"fiddlehead": "fiddlehead",
|
||||
"fish": "fish",
|
||||
|
@ -80,7 +80,7 @@
|
||||
"eggplant": "eggplant",
|
||||
"endive": "endive",
|
||||
"fats": "fats",
|
||||
"spek": "spek",
|
||||
"speck": "speck",
|
||||
"fava-beans": "fava beans",
|
||||
"fiddlehead": "fiddlehead",
|
||||
"fish": "fish",
|
||||
|
@ -80,7 +80,7 @@
|
||||
"eggplant": "eggplant",
|
||||
"endive": "endive",
|
||||
"fats": "fats",
|
||||
"spek": "spek",
|
||||
"speck": "speck",
|
||||
"fava-beans": "fava beans",
|
||||
"fiddlehead": "fiddlehead",
|
||||
"fish": "fish",
|
||||
|
@ -80,7 +80,7 @@
|
||||
"eggplant": "eggplant",
|
||||
"endive": "endive",
|
||||
"fats": "fats",
|
||||
"spek": "spek",
|
||||
"speck": "speck",
|
||||
"fava-beans": "fava beans",
|
||||
"fiddlehead": "fiddlehead",
|
||||
"fish": "fish",
|
||||
|
@ -80,7 +80,7 @@
|
||||
"eggplant": "eggplant",
|
||||
"endive": "endive",
|
||||
"fats": "fats",
|
||||
"spek": "spek",
|
||||
"speck": "speck",
|
||||
"fava-beans": "fava beans",
|
||||
"fiddlehead": "fiddlehead",
|
||||
"fish": "fish",
|
||||
|
@ -80,7 +80,7 @@
|
||||
"eggplant": "Aubergine",
|
||||
"endive": "Endivie",
|
||||
"fats": "Fette",
|
||||
"spek": "spek",
|
||||
"speck": "speck",
|
||||
"fava-beans": "Ackerbohnen",
|
||||
"fiddlehead": "Farnspitzen",
|
||||
"fish": "Fisch",
|
||||
|
@ -80,7 +80,7 @@
|
||||
"eggplant": "eggplant",
|
||||
"endive": "endive",
|
||||
"fats": "fats",
|
||||
"spek": "spek",
|
||||
"speck": "speck",
|
||||
"fava-beans": "fava beans",
|
||||
"fiddlehead": "fiddlehead",
|
||||
"fish": "ψάρι",
|
||||
|
@ -80,7 +80,7 @@
|
||||
"eggplant": "eggplant",
|
||||
"endive": "endive",
|
||||
"fats": "fats",
|
||||
"spek": "spek",
|
||||
"speck": "speck",
|
||||
"fava-beans": "fava beans",
|
||||
"fiddlehead": "fiddlehead",
|
||||
"fish": "fish",
|
||||
|
@ -80,7 +80,7 @@
|
||||
"eggplant": "eggplant",
|
||||
"endive": "endive",
|
||||
"fats": "fats",
|
||||
"spek": "spek",
|
||||
"speck": "speck",
|
||||
"fava-beans": "fava beans",
|
||||
"fiddlehead": "fiddlehead",
|
||||
"fish": "fish",
|
||||
@ -119,7 +119,6 @@
|
||||
"oregano": "oregano",
|
||||
"parsley": "parsley",
|
||||
"honey": "honey",
|
||||
"horse": "horse",
|
||||
"icing-sugar": "icing sugar",
|
||||
"isomalt": "isomalt",
|
||||
"jackfruit": "jackfruit",
|
||||
@ -176,7 +175,6 @@
|
||||
"pumpkin": "pumpkin",
|
||||
"pumpkin-seeds": "pumpkin seeds",
|
||||
"radish": "radish",
|
||||
"rape": "rape",
|
||||
"raw-sugar": "raw sugar",
|
||||
"refined-sugar": "refined sugar",
|
||||
"rice-flour": "rice flour",
|
||||
|
@ -80,7 +80,7 @@
|
||||
"eggplant": "eggplant",
|
||||
"endive": "endive",
|
||||
"fats": "fats",
|
||||
"spek": "spek",
|
||||
"speck": "speck",
|
||||
"fava-beans": "fava beans",
|
||||
"fiddlehead": "fiddlehead",
|
||||
"fish": "fish",
|
||||
|
@ -80,7 +80,7 @@
|
||||
"eggplant": "eggplant",
|
||||
"endive": "endive",
|
||||
"fats": "fats",
|
||||
"spek": "spek",
|
||||
"speck": "speck",
|
||||
"fava-beans": "fava beans",
|
||||
"fiddlehead": "fiddlehead",
|
||||
"fish": "fish",
|
||||
|
@ -80,7 +80,7 @@
|
||||
"eggplant": "aubergine",
|
||||
"endive": "endive",
|
||||
"fats": "matières grasses",
|
||||
"spek": "spek",
|
||||
"speck": "speck",
|
||||
"fava-beans": "fèves",
|
||||
"fiddlehead": "crosse de fougère",
|
||||
"fish": "poisson",
|
||||
|
@ -80,7 +80,7 @@
|
||||
"eggplant": "aubergine",
|
||||
"endive": "endive",
|
||||
"fats": "matières grasses",
|
||||
"spek": "spek",
|
||||
"speck": "speck",
|
||||
"fava-beans": "fèves",
|
||||
"fiddlehead": "crosse de fougère",
|
||||
"fish": "poisson",
|
||||
|
@ -80,7 +80,7 @@
|
||||
"eggplant": "eggplant",
|
||||
"endive": "endive",
|
||||
"fats": "fats",
|
||||
"spek": "spek",
|
||||
"speck": "speck",
|
||||
"fava-beans": "fava beans",
|
||||
"fiddlehead": "fiddlehead",
|
||||
"fish": "fish",
|
||||
|
@ -80,7 +80,7 @@
|
||||
"eggplant": "padlizsán",
|
||||
"endive": "endívia",
|
||||
"fats": "zsírok",
|
||||
"spek": "speck sonka",
|
||||
"speck": "speck sonka",
|
||||
"fava-beans": "lóbab",
|
||||
"fiddlehead": "hegedűfej",
|
||||
"fish": "hal",
|
||||
|
@ -80,7 +80,7 @@
|
||||
"eggplant": "melanzana",
|
||||
"endive": "endive",
|
||||
"fats": "grassi",
|
||||
"spek": "spek",
|
||||
"speck": "speck",
|
||||
"fava-beans": "fave",
|
||||
"fiddlehead": "fiddlehead",
|
||||
"fish": "pesce",
|
||||
|
@ -80,7 +80,7 @@
|
||||
"eggplant": "eggplant",
|
||||
"endive": "endive",
|
||||
"fats": "fats",
|
||||
"spek": "spek",
|
||||
"speck": "speck",
|
||||
"fava-beans": "fava beans",
|
||||
"fiddlehead": "fiddlehead",
|
||||
"fish": "fish",
|
||||
|
@ -80,7 +80,7 @@
|
||||
"eggplant": "eggplant",
|
||||
"endive": "endive",
|
||||
"fats": "fats",
|
||||
"spek": "spek",
|
||||
"speck": "speck",
|
||||
"fava-beans": "fava beans",
|
||||
"fiddlehead": "fiddlehead",
|
||||
"fish": "fish",
|
||||
|
@ -80,7 +80,7 @@
|
||||
"eggplant": "aubergine",
|
||||
"endive": "andijvie",
|
||||
"fats": "vetten",
|
||||
"spek": "spek",
|
||||
"speck": "speck",
|
||||
"fava-beans": "tuinbonen",
|
||||
"fiddlehead": "varenkrul",
|
||||
"fish": "vis",
|
||||
|
@ -80,7 +80,7 @@
|
||||
"eggplant": "eggplant",
|
||||
"endive": "endive",
|
||||
"fats": "fats",
|
||||
"spek": "spek",
|
||||
"speck": "speck",
|
||||
"fava-beans": "fava beans",
|
||||
"fiddlehead": "fiddlehead",
|
||||
"fish": "fish",
|
||||
|
@ -80,7 +80,7 @@
|
||||
"eggplant": "bakłażan",
|
||||
"endive": "endywia",
|
||||
"fats": "tłuszcze",
|
||||
"spek": "boczek",
|
||||
"speck": "boczek",
|
||||
"fava-beans": "bób",
|
||||
"fiddlehead": "pędy paproci",
|
||||
"fish": "ryba",
|
||||
|
@ -80,7 +80,7 @@
|
||||
"eggplant": "eggplant",
|
||||
"endive": "endive",
|
||||
"fats": "fats",
|
||||
"spek": "spek",
|
||||
"speck": "speck",
|
||||
"fava-beans": "fava beans",
|
||||
"fiddlehead": "fiddlehead",
|
||||
"fish": "fish",
|
||||
|
@ -80,7 +80,7 @@
|
||||
"eggplant": "eggplant",
|
||||
"endive": "endive",
|
||||
"fats": "fats",
|
||||
"spek": "spek",
|
||||
"speck": "speck",
|
||||
"fava-beans": "fava beans",
|
||||
"fiddlehead": "fiddlehead",
|
||||
"fish": "fish",
|
||||
|
@ -80,7 +80,7 @@
|
||||
"eggplant": "eggplant",
|
||||
"endive": "endive",
|
||||
"fats": "fats",
|
||||
"spek": "spek",
|
||||
"speck": "speck",
|
||||
"fava-beans": "fava beans",
|
||||
"fiddlehead": "fiddlehead",
|
||||
"fish": "fish",
|
||||
|
@ -80,7 +80,7 @@
|
||||
"eggplant": "eggplant",
|
||||
"endive": "endive",
|
||||
"fats": "fats",
|
||||
"spek": "spek",
|
||||
"speck": "speck",
|
||||
"fava-beans": "fava beans",
|
||||
"fiddlehead": "fiddlehead",
|
||||
"fish": "fish",
|
||||
|
@ -80,7 +80,7 @@
|
||||
"eggplant": "eggplant",
|
||||
"endive": "endive",
|
||||
"fats": "fats",
|
||||
"spek": "spek",
|
||||
"speck": "speck",
|
||||
"fava-beans": "fava beans",
|
||||
"fiddlehead": "fiddlehead",
|
||||
"fish": "fish",
|
||||
|
@ -80,7 +80,7 @@
|
||||
"eggplant": "eggplant",
|
||||
"endive": "endive",
|
||||
"fats": "fats",
|
||||
"spek": "spek",
|
||||
"speck": "speck",
|
||||
"fava-beans": "fava beans",
|
||||
"fiddlehead": "fiddlehead",
|
||||
"fish": "fish",
|
||||
|
@ -80,7 +80,7 @@
|
||||
"eggplant": "äggplanta",
|
||||
"endive": "endive",
|
||||
"fats": "fetter",
|
||||
"spek": "spek",
|
||||
"speck": "speck",
|
||||
"fava-beans": "fava beans",
|
||||
"fiddlehead": "fiddlehead",
|
||||
"fish": "fisk",
|
||||
|
@ -80,7 +80,7 @@
|
||||
"eggplant": "eggplant",
|
||||
"endive": "endive",
|
||||
"fats": "fats",
|
||||
"spek": "spek",
|
||||
"speck": "speck",
|
||||
"fava-beans": "fava beans",
|
||||
"fiddlehead": "fiddlehead",
|
||||
"fish": "fish",
|
||||
|
@ -80,7 +80,7 @@
|
||||
"eggplant": "баклажан",
|
||||
"endive": "ендивій (салатний цикорій)",
|
||||
"fats": "жири",
|
||||
"spek": "шпек",
|
||||
"speck": "шпек",
|
||||
"fava-beans": "біб кінський",
|
||||
"fiddlehead": "рахіси папороті",
|
||||
"fish": "риба",
|
||||
|
@ -80,7 +80,7 @@
|
||||
"eggplant": "eggplant",
|
||||
"endive": "endive",
|
||||
"fats": "fats",
|
||||
"spek": "spek",
|
||||
"speck": "speck",
|
||||
"fava-beans": "fava beans",
|
||||
"fiddlehead": "fiddlehead",
|
||||
"fish": "fish",
|
||||
|
@ -80,7 +80,7 @@
|
||||
"eggplant": "eggplant",
|
||||
"endive": "endive",
|
||||
"fats": "fats",
|
||||
"spek": "spek",
|
||||
"speck": "speck",
|
||||
"fava-beans": "fava beans",
|
||||
"fiddlehead": "fiddlehead",
|
||||
"fish": "fish",
|
||||
|
@ -80,7 +80,7 @@
|
||||
"eggplant": "茄子",
|
||||
"endive": "endive",
|
||||
"fats": "脂肪",
|
||||
"spek": "spek",
|
||||
"speck": "speck",
|
||||
"fava-beans": "fava beans",
|
||||
"fiddlehead": "fiddlehead",
|
||||
"fish": "魚",
|
||||
|
@ -1,4 +1,5 @@
|
||||
import json
|
||||
import pathlib
|
||||
from collections.abc import Generator
|
||||
|
||||
from mealie.schema.labels import MultiPurposeLabelSave
|
||||
@ -9,8 +10,12 @@ from .resources import foods, labels, units
|
||||
|
||||
|
||||
class MultiPurposeLabelSeeder(AbstractSeeder):
|
||||
def load_data(self) -> Generator[MultiPurposeLabelSave, None, None]:
|
||||
file = labels.en_US
|
||||
def get_file(self, locale: str | None = None) -> pathlib.Path:
|
||||
locale_path = self.resources / "labels" / "locales" / f"{locale}.json"
|
||||
return locale_path if locale_path.exists() else labels.en_US
|
||||
|
||||
def load_data(self, locale: str | None = None) -> Generator[MultiPurposeLabelSave, None, None]:
|
||||
file = self.get_file(locale)
|
||||
|
||||
for label in json.loads(file.read_text()):
|
||||
yield MultiPurposeLabelSave(
|
||||
@ -18,9 +23,9 @@ class MultiPurposeLabelSeeder(AbstractSeeder):
|
||||
group_id=self.group_id,
|
||||
)
|
||||
|
||||
def seed(self) -> None:
|
||||
def seed(self, locale: str | None = None) -> None:
|
||||
self.logger.info("Seeding MultiPurposeLabel")
|
||||
for label in self.load_data():
|
||||
for label in self.load_data(locale):
|
||||
try:
|
||||
self.repos.group_multi_purpose_labels.create(label)
|
||||
except Exception as e:
|
||||
@ -28,8 +33,13 @@ class MultiPurposeLabelSeeder(AbstractSeeder):
|
||||
|
||||
|
||||
class IngredientUnitsSeeder(AbstractSeeder):
|
||||
def load_data(self) -> Generator[SaveIngredientUnit, None, None]:
|
||||
file = units.en_US
|
||||
def get_file(self, locale: str | None = None) -> pathlib.Path:
|
||||
locale_path = self.resources / "units" / "locales" / f"{locale}.json"
|
||||
return locale_path if locale_path.exists() else units.en_US
|
||||
|
||||
def load_data(self, locale: str | None = None) -> Generator[SaveIngredientUnit, None, None]:
|
||||
file = self.get_file(locale)
|
||||
|
||||
for unit in json.loads(file.read_text()).values():
|
||||
yield SaveIngredientUnit(
|
||||
group_id=self.group_id,
|
||||
@ -38,9 +48,9 @@ class IngredientUnitsSeeder(AbstractSeeder):
|
||||
abbreviation=unit["abbreviation"],
|
||||
)
|
||||
|
||||
def seed(self) -> None:
|
||||
def seed(self, locale: str | None = None) -> None:
|
||||
self.logger.info("Seeding Ingredient Units")
|
||||
for unit in self.load_data():
|
||||
for unit in self.load_data(locale):
|
||||
try:
|
||||
self.repos.ingredient_units.create(unit)
|
||||
except Exception as e:
|
||||
@ -48,8 +58,13 @@ class IngredientUnitsSeeder(AbstractSeeder):
|
||||
|
||||
|
||||
class IngredientFoodsSeeder(AbstractSeeder):
|
||||
def load_data(self) -> Generator[SaveIngredientFood, None, None]:
|
||||
file = foods.en_US
|
||||
def get_file(self, locale: str | None = None) -> pathlib.Path:
|
||||
locale_path = self.resources / "foods" / "locales" / f"{locale}.json"
|
||||
return locale_path if locale_path.exists() else foods.en_US
|
||||
|
||||
def load_data(self, locale: str | None = None) -> Generator[SaveIngredientFood, None, None]:
|
||||
file = self.get_file(locale)
|
||||
|
||||
seed_foods: dict[str, str] = json.loads(file.read_text())
|
||||
for food in seed_foods.values():
|
||||
yield SaveIngredientFood(
|
||||
@ -58,9 +73,9 @@ class IngredientFoodsSeeder(AbstractSeeder):
|
||||
description="",
|
||||
)
|
||||
|
||||
def seed(self) -> None:
|
||||
def seed(self, locale: str | None = None) -> None:
|
||||
self.logger.info("Seeding Ingredient Foods")
|
||||
for food in self.load_data():
|
||||
for food in self.load_data(locale):
|
||||
try:
|
||||
self.repos.ingredient_foods.create(food)
|
||||
except Exception as e:
|
||||
|
@ -11,6 +11,7 @@ from . import (
|
||||
controller_mealplan_config,
|
||||
controller_mealplan_rules,
|
||||
controller_migrations,
|
||||
controller_seeder,
|
||||
controller_shopping_lists,
|
||||
controller_webhooks,
|
||||
)
|
||||
@ -30,3 +31,4 @@ router.include_router(controller_shopping_lists.router)
|
||||
router.include_router(controller_shopping_lists.item_router)
|
||||
router.include_router(controller_labels.router)
|
||||
router.include_router(controller_group_notifications.router)
|
||||
router.include_router(controller_seeder.router)
|
||||
|
38
mealie/routes/groups/controller_seeder.py
Normal file
38
mealie/routes/groups/controller_seeder.py
Normal file
@ -0,0 +1,38 @@
|
||||
from functools import cached_property
|
||||
|
||||
from fastapi import APIRouter, HTTPException
|
||||
|
||||
from mealie.routes._base.base_controllers import BaseUserController
|
||||
from mealie.routes._base.controller import controller
|
||||
from mealie.schema.group.group_seeder import SeederConfig
|
||||
from mealie.schema.response.responses import ErrorResponse, SuccessResponse
|
||||
from mealie.services.seeder.seeder_service import SeederService
|
||||
|
||||
router = APIRouter(prefix="/groups/seeders", tags=["Groups: Seeders"])
|
||||
|
||||
|
||||
@controller(router)
|
||||
class DataSeederController(BaseUserController):
|
||||
@cached_property
|
||||
def service(self) -> SeederService:
|
||||
return SeederService(self.repos, self.user, self.group)
|
||||
|
||||
def _wrap(self, func):
|
||||
try:
|
||||
func()
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=ErrorResponse.respond("Seeding Failed")) from e
|
||||
|
||||
return SuccessResponse.respond("Seeding Successful")
|
||||
|
||||
@router.post("/foods", response_model=SuccessResponse)
|
||||
def seed_foods(self, data: SeederConfig) -> dict:
|
||||
return self._wrap(lambda: self.service.seed_foods(data.locale))
|
||||
|
||||
@router.post("/labels", response_model=SuccessResponse)
|
||||
def seed_labels(self, data: SeederConfig) -> dict:
|
||||
return self._wrap(lambda: self.service.seed_labels(data.locale))
|
||||
|
||||
@router.post("/units", response_model=SuccessResponse)
|
||||
def seed_units(self, data: SeederConfig) -> dict:
|
||||
return self._wrap(lambda: self.service.seed_units(data.locale))
|
@ -5,6 +5,7 @@ from .group_exports import *
|
||||
from .group_migration import *
|
||||
from .group_permissions import *
|
||||
from .group_preferences import *
|
||||
from .group_seeder import *
|
||||
from .group_shopping_list import *
|
||||
from .group_statistics import *
|
||||
from .invite_token import *
|
||||
|
53
mealie/schema/group/group_seeder.py
Normal file
53
mealie/schema/group/group_seeder.py
Normal file
@ -0,0 +1,53 @@
|
||||
from pydantic import validator
|
||||
|
||||
from mealie.schema._mealie.mealie_model import MealieModel
|
||||
|
||||
|
||||
def validate_locale(locale: str) -> bool:
|
||||
valid = {
|
||||
"el-GR",
|
||||
"it-IT",
|
||||
"ko-KR",
|
||||
"es-ES",
|
||||
"ja-JP",
|
||||
"zh-CN",
|
||||
"tr-TR",
|
||||
"ar-SA",
|
||||
"hu-HU",
|
||||
"pt-PT",
|
||||
"no-NO",
|
||||
"sv-SE",
|
||||
"ro-RO",
|
||||
"sk-SK",
|
||||
"uk-UA",
|
||||
"fr-CA",
|
||||
"pl-PL",
|
||||
"da-DK",
|
||||
"pt-BR",
|
||||
"de-DE",
|
||||
"ca-ES",
|
||||
"sr-SP",
|
||||
"cs-CZ",
|
||||
"fr-FR",
|
||||
"zh-TW",
|
||||
"af-ZA",
|
||||
"ru-RU",
|
||||
"he-IL",
|
||||
"nl-NL",
|
||||
"en-US",
|
||||
"en-GB",
|
||||
"fi-FI",
|
||||
"vi-VN",
|
||||
}
|
||||
|
||||
return locale in valid
|
||||
|
||||
|
||||
class SeederConfig(MealieModel):
|
||||
locale: str
|
||||
|
||||
@validator("locale")
|
||||
def valid_locale(cls, v, values, **kwargs):
|
||||
if not validate_locale(v):
|
||||
raise ValueError("passwords do not match")
|
||||
return v
|
0
mealie/services/seeder/__init__.py
Normal file
0
mealie/services/seeder/__init__.py
Normal file
24
mealie/services/seeder/seeder_service.py
Normal file
24
mealie/services/seeder/seeder_service.py
Normal file
@ -0,0 +1,24 @@
|
||||
from mealie.repos.repository_factory import AllRepositories
|
||||
from mealie.repos.seed.seeders import IngredientFoodsSeeder, IngredientUnitsSeeder, MultiPurposeLabelSeeder
|
||||
from mealie.schema.user.user import GroupInDB, PrivateUser
|
||||
from mealie.services._base_service import BaseService
|
||||
|
||||
|
||||
class SeederService(BaseService):
|
||||
def __init__(self, repos: AllRepositories, user: PrivateUser, group: GroupInDB):
|
||||
self.repos = repos
|
||||
self.user = user
|
||||
self.group = group
|
||||
super().__init__()
|
||||
|
||||
def seed_foods(self, locale: str) -> None:
|
||||
seeder = IngredientFoodsSeeder(self.repos, self.logger, self.group.id)
|
||||
seeder.seed(locale)
|
||||
|
||||
def seed_labels(self, locale: str) -> None:
|
||||
seeder = MultiPurposeLabelSeeder(self.repos, self.logger, self.group.id)
|
||||
seeder.seed(locale)
|
||||
|
||||
def seed_units(self, locale: str) -> None:
|
||||
seeder = IngredientUnitsSeeder(self.repos, self.logger, self.group.id)
|
||||
seeder.seed(locale)
|
@ -0,0 +1,56 @@
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from mealie.repos.repository_factory import AllRepositories
|
||||
from tests.utils.fixture_schemas import TestUser
|
||||
from tests.utils.routes import RoutesSeeders
|
||||
|
||||
|
||||
def test_seed_invalid_locale(api_client: TestClient, unique_user: TestUser):
|
||||
for route in [RoutesSeeders.foods, RoutesSeeders.labels, RoutesSeeders.units]:
|
||||
resp = api_client.post(route, json={"locale": "invalid"}, headers=unique_user.token)
|
||||
assert resp.status_code == 422
|
||||
|
||||
|
||||
def test_seed_foods(api_client: TestClient, unique_user: TestUser, database: AllRepositories):
|
||||
CREATED_FOODS = 220
|
||||
|
||||
# Check that the foods was created
|
||||
foods = database.ingredient_foods.by_group(unique_user.group_id).get_all()
|
||||
assert len(foods) == 0
|
||||
|
||||
resp = api_client.post(RoutesSeeders.foods, json={"locale": "en-US"}, headers=unique_user.token)
|
||||
assert resp.status_code == 200
|
||||
|
||||
# Check that the foods was created
|
||||
foods = database.ingredient_foods.by_group(unique_user.group_id).get_all()
|
||||
assert len(foods) == CREATED_FOODS
|
||||
|
||||
|
||||
def test_seed_units(api_client: TestClient, unique_user: TestUser, database: AllRepositories):
|
||||
CREATED_UNITS = 20
|
||||
|
||||
# Check that the foods was created
|
||||
units = database.ingredient_units.by_group(unique_user.group_id).get_all()
|
||||
assert len(units) == 0
|
||||
|
||||
resp = api_client.post(RoutesSeeders.units, json={"locale": "en-US"}, headers=unique_user.token)
|
||||
assert resp.status_code == 200
|
||||
|
||||
# Check that the foods was created
|
||||
units = database.ingredient_units.by_group(unique_user.group_id).get_all()
|
||||
assert len(units) == CREATED_UNITS
|
||||
|
||||
|
||||
def test_seed_labels(api_client: TestClient, unique_user: TestUser, database: AllRepositories):
|
||||
CREATED_LABELS = 21
|
||||
|
||||
# Check that the foods was created
|
||||
labels = database.group_multi_purpose_labels.by_group(unique_user.group_id).get_all()
|
||||
assert len(labels) == 0
|
||||
|
||||
resp = api_client.post(RoutesSeeders.labels, json={"locale": "en-US"}, headers=unique_user.token)
|
||||
assert resp.status_code == 200
|
||||
|
||||
# Check that the foods was created
|
||||
labels = database.group_multi_purpose_labels.by_group(unique_user.group_id).get_all()
|
||||
assert len(labels) == CREATED_LABELS
|
@ -50,3 +50,11 @@ class RoutesAdminUsers(RoutesBase):
|
||||
class RoutesUsers(RoutesBase):
|
||||
base = "/api/users"
|
||||
self = f"{base}/self"
|
||||
|
||||
|
||||
class RoutesSeeders(RoutesBase):
|
||||
base = "/api/groups/seeders"
|
||||
|
||||
foods = f"{base}/foods"
|
||||
units = f"{base}/units"
|
||||
labels = f"{base}/labels"
|
||||
|
Loading…
x
Reference in New Issue
Block a user