feat(frontend): ✨ Add Meal Tags + UI Improvements (#807)
* feat: ✨ * fix colors * add additional support for settings meal tag * add defaults to recipe * use group reciep settings * fix login infinite loading * disable owner on initial load * add skeleton loader * add v-model support * formatting * fix overwriting existing values * feat(frontend): ✨ add markdown preview for steps * update black plus formatting * update help text * fix overwrite error * remove print Co-authored-by: hay-kot <hay-kot@pm.me>
This commit is contained in:
parent
d4bf81dee6
commit
912cc6d956
50 changed files with 456 additions and 246 deletions
|
@ -8,7 +8,7 @@ from jinja2 import Template
|
|||
|
||||
|
||||
def render_python_template(template_file: Path, dest: Path, data: dict) -> str:
|
||||
""" Render and Format a Jinja2 Template for Python Code"""
|
||||
"""Render and Format a Jinja2 Template for Python Code"""
|
||||
tplt = Template(template_file.read_text())
|
||||
text = tplt.render(data)
|
||||
text = format_str(text, mode=FileMode())
|
||||
|
|
|
@ -66,7 +66,7 @@ class OpenAPIParser:
|
|||
self.modules = {}
|
||||
|
||||
def dump(self, out_path: Path) -> Path:
|
||||
""" Writes the Open API as JSON to a json file"""
|
||||
"""Writes the Open API as JSON to a json file"""
|
||||
OPEN_API_FILE = out_path or Directories.out_dir / "openapi.json"
|
||||
|
||||
with open(OPEN_API_FILE, "w") as f:
|
||||
|
|
|
@ -18,7 +18,7 @@ class CodeDest:
|
|||
|
||||
|
||||
class CodeKeys:
|
||||
""" Hard coded comment IDs that are used to generate code"""
|
||||
"""Hard coded comment IDs that are used to generate code"""
|
||||
|
||||
nuxt_local_messages = "MESSAGE_LOCALES"
|
||||
nuxt_local_dates = "DATE_LOCALES"
|
||||
|
|
|
@ -126,7 +126,7 @@ def get_path_objects(app: FastAPI):
|
|||
|
||||
|
||||
def dump_open_api(app: FastAPI):
|
||||
""" Writes the Open API as JSON to a json file"""
|
||||
"""Writes the Open API as JSON to a json file"""
|
||||
OPEN_API_FILE = CWD / "openapi.json"
|
||||
|
||||
with open(OPEN_API_FILE, "w") as f:
|
||||
|
|
|
@ -5,10 +5,10 @@
|
|||
:class="{ 'on-hover': hover }"
|
||||
:elevation="hover ? 12 : 2"
|
||||
:to="route ? `/recipe/${slug}` : ''"
|
||||
min-height="275"
|
||||
:min-height="imageHeight + 75"
|
||||
@click="$emit('click')"
|
||||
>
|
||||
<RecipeCardImage icon-size="200" :slug="slug" small :image-version="image">
|
||||
<RecipeCardImage :icon-size="imageHeight" :height="imageHeight" :slug="slug" small :image-version="image">
|
||||
<v-expand-transition v-if="description">
|
||||
<div v-if="hover" class="d-flex transition-fast-in-fast-out secondary v-card--reveal" style="height: 100%">
|
||||
<v-card-text class="v-card--text-show white--text">
|
||||
|
@ -23,26 +23,28 @@
|
|||
</div>
|
||||
</v-card-title>
|
||||
|
||||
<v-card-actions>
|
||||
<RecipeFavoriteBadge v-if="loggedIn" :slug="slug" show-always />
|
||||
<RecipeRating :value="rating" :name="name" :slug="slug" :small="true" />
|
||||
<v-spacer></v-spacer>
|
||||
<RecipeChips :truncate="true" :items="tags" :title="false" :limit="2" :small="true" :is-category="false" />
|
||||
<RecipeContextMenu
|
||||
:slug="slug"
|
||||
:name="name"
|
||||
:recipe-id="recipeId"
|
||||
:use-items="{
|
||||
delete: true,
|
||||
edit: true,
|
||||
download: true,
|
||||
mealplanner: true,
|
||||
print: false,
|
||||
share: true,
|
||||
}"
|
||||
@delete="$emit('delete', slug)"
|
||||
/>
|
||||
</v-card-actions>
|
||||
<slot name="actions">
|
||||
<v-card-actions>
|
||||
<RecipeFavoriteBadge v-if="loggedIn" :slug="slug" show-always />
|
||||
<RecipeRating :value="rating" :name="name" :slug="slug" :small="true" />
|
||||
<v-spacer></v-spacer>
|
||||
<RecipeChips :truncate="true" :items="tags" :title="false" :limit="2" :small="true" :is-category="false" />
|
||||
<RecipeContextMenu
|
||||
:slug="slug"
|
||||
:name="name"
|
||||
:recipe-id="recipeId"
|
||||
:use-items="{
|
||||
delete: true,
|
||||
edit: true,
|
||||
download: true,
|
||||
mealplanner: true,
|
||||
print: false,
|
||||
share: true,
|
||||
}"
|
||||
@delete="$emit('delete', slug)"
|
||||
/>
|
||||
</v-card-actions>
|
||||
</slot>
|
||||
<slot></slot>
|
||||
</v-card>
|
||||
</v-hover>
|
||||
|
@ -92,6 +94,10 @@ export default {
|
|||
required: true,
|
||||
type: Number,
|
||||
},
|
||||
imageHeight: {
|
||||
type: Number,
|
||||
default: 200,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
|
|
@ -41,6 +41,7 @@
|
|||
</template>
|
||||
<v-date-picker v-model="newMealdate" no-title @input="pickerMenu = false"></v-date-picker>
|
||||
</v-menu>
|
||||
<v-select v-model="newMealType" :return-object="false" :items="planTypeOptions" label="Entry Type"></v-select>
|
||||
</v-card-text>
|
||||
</BaseDialog>
|
||||
<v-menu
|
||||
|
@ -77,6 +78,7 @@ import { defineComponent, reactive, ref, toRefs, useContext, useRouter } from "@
|
|||
import { useClipboard, useShare } from "@vueuse/core";
|
||||
import { useUserApi } from "~/composables/api";
|
||||
import { alert } from "~/composables/use-toast";
|
||||
import { MealType, planTypeOptions } from "~/composables/use-group-mealplan";
|
||||
|
||||
export interface ContextMenuIncludes {
|
||||
delete: boolean;
|
||||
|
@ -153,6 +155,7 @@ export default defineComponent({
|
|||
loading: false,
|
||||
menuItems: [] as ContextMenuItem[],
|
||||
newMealdate: "",
|
||||
newMealType: "dinner" as MealType,
|
||||
pickerMenu: false,
|
||||
});
|
||||
|
||||
|
@ -265,7 +268,7 @@ export default defineComponent({
|
|||
async function addRecipeToPlan() {
|
||||
const { response } = await api.mealplans.createOne({
|
||||
date: state.newMealdate,
|
||||
entryType: "dinner",
|
||||
entryType: state.newMealType,
|
||||
title: "",
|
||||
text: "",
|
||||
recipeId: props.recipeId,
|
||||
|
@ -310,6 +313,7 @@ export default defineComponent({
|
|||
domConfirmDelete,
|
||||
domMealplanDialog,
|
||||
icon,
|
||||
planTypeOptions,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
|
|
@ -31,7 +31,7 @@
|
|||
<div class="mr-auto">
|
||||
{{ $t("search.results") }}
|
||||
</div>
|
||||
<router-link to="/search"> {{ $t("search.advanced-search") }} </router-link>
|
||||
<router-link to="/search?advanced=true"> {{ $t("search.advanced-search") }} </router-link>
|
||||
</v-card-actions>
|
||||
|
||||
<RecipeCardMobile
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
<div v-if="value && value.length > 0">
|
||||
<div class="d-flex justify-start">
|
||||
<h2 class="mb-4 mt-1">{{ $t("recipe.ingredients") }}</h2>
|
||||
<AppButtonCopy btn-class="ml-auto" :copy-text="ingredientCopyText" />
|
||||
<AppButtonCopy btn-class="ml-auto" :copy-text="ingredientCopyText" />
|
||||
</div>
|
||||
<div>
|
||||
<div v-for="(ingredient, index) in value" :key="'ingredient' + index">
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
<section @keyup.ctrl.90="undoMerge">
|
||||
<!-- Ingredient Link Editor -->
|
||||
<v-dialog v-model="dialog" width="600">
|
||||
<v-card>
|
||||
<v-card :ripple="false">
|
||||
<v-app-bar dark color="primary" class="mt-n1 mb-3">
|
||||
<v-icon large left>
|
||||
{{ $globals.icons.link }}
|
||||
|
@ -127,8 +127,7 @@
|
|||
</v-fade-transition>
|
||||
</v-card-title>
|
||||
<v-card-text v-if="edit">
|
||||
<v-textarea :key="'instructions' + index" v-model="value[index]['text']" auto-grow dense rows="4">
|
||||
</v-textarea>
|
||||
<MarkdownEditor v-model="value[index]['text']" />
|
||||
<div
|
||||
v-for="ing in step.ingredientReferences"
|
||||
:key="ing.referenceId"
|
||||
|
@ -417,4 +416,9 @@ export default defineComponent({
|
|||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.v-card--link:before {
|
||||
background: none;
|
||||
}
|
||||
</style>
|
||||
|
||||
|
|
67
frontend/components/global/MarkdownEditor.vue
Normal file
67
frontend/components/global/MarkdownEditor.vue
Normal file
|
@ -0,0 +1,67 @@
|
|||
<template>
|
||||
<div>
|
||||
<v-tabs v-model="tab" height="30px" class="my-1">
|
||||
<v-tab>
|
||||
<v-icon small left> {{ $globals.icons.edit }}</v-icon>
|
||||
Edit
|
||||
</v-tab>
|
||||
<v-tab>
|
||||
<v-icon small left> {{ $globals.icons.eye }}</v-icon>
|
||||
Preview
|
||||
</v-tab>
|
||||
</v-tabs>
|
||||
<v-textarea
|
||||
v-if="tab == 0"
|
||||
v-model="inputVal"
|
||||
:class="label == '' ? '' : 'mt-5'"
|
||||
:label="label"
|
||||
auto-grow
|
||||
dense
|
||||
rows="4"
|
||||
></v-textarea>
|
||||
<VueMarkdown v-else :source="value"> </VueMarkdown>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
// @ts-ignore
|
||||
import VueMarkdown from "@adapttive/vue-markdown";
|
||||
|
||||
import { defineComponent, reactive, toRefs, computed } from "@nuxtjs/composition-api";
|
||||
|
||||
export default defineComponent({
|
||||
name: "MarkdownEditor",
|
||||
components: {
|
||||
VueMarkdown,
|
||||
},
|
||||
props: {
|
||||
value: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
label: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
},
|
||||
setup(props, context) {
|
||||
const state = reactive({
|
||||
tab: 0,
|
||||
});
|
||||
|
||||
const inputVal = computed({
|
||||
get: () => {
|
||||
return props.value;
|
||||
},
|
||||
set: (val) => {
|
||||
context.emit("input", val);
|
||||
},
|
||||
});
|
||||
return {
|
||||
inputVal,
|
||||
...toRefs(state),
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
|
@ -8,16 +8,26 @@
|
|||
<script lang="ts">
|
||||
import { defineComponent } from "@nuxtjs/composition-api";
|
||||
import { useToggle } from "@vueuse/shared";
|
||||
import { watch } from "vue-demi";
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
value: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
tag: {
|
||||
type: String,
|
||||
default: "div",
|
||||
},
|
||||
},
|
||||
setup() {
|
||||
setup(_, context) {
|
||||
const [state, toggle] = useToggle();
|
||||
|
||||
watch(state, () => {
|
||||
context.emit("input", state);
|
||||
});
|
||||
|
||||
return {
|
||||
state,
|
||||
toggle,
|
||||
|
|
|
@ -4,6 +4,15 @@ import { useAsyncKey } from "./use-utils";
|
|||
import { useUserApi } from "~/composables/api";
|
||||
import { CreateMealPlan, UpdateMealPlan } from "~/api/class-interfaces/group-mealplan";
|
||||
|
||||
export type MealType = "breakfast" | "lunch" | "dinner" | "snack";
|
||||
|
||||
export const planTypeOptions = [
|
||||
{ text: "Breakfast", value: "breakfast" },
|
||||
{ text: "Lunch", value: "lunch" },
|
||||
{ text: "Dinner", value: "dinner" },
|
||||
{ text: "Snack", value: "snack" },
|
||||
];
|
||||
|
||||
export const useMealplans = function () {
|
||||
const api = useUserApi();
|
||||
const loading = ref(false);
|
||||
|
@ -72,9 +81,14 @@ export const useMealplans = function () {
|
|||
this.refreshAll();
|
||||
}
|
||||
},
|
||||
|
||||
async setType(payload: UpdateMealPlan, typ: MealType) {
|
||||
payload.entryType = typ;
|
||||
await this.updateOne(payload);
|
||||
},
|
||||
};
|
||||
|
||||
const mealplans = actions.getAll();
|
||||
|
||||
return { mealplans, actions, validForm };
|
||||
return { mealplans, actions, validForm, loading };
|
||||
};
|
||||
|
|
|
@ -223,7 +223,7 @@ export default {
|
|||
background: "#202021",
|
||||
},
|
||||
light: {
|
||||
primary: process.env.THEME_LIGHT_PRIMARY || "#007A99",
|
||||
primary: process.env.THEME_LIGHT_PRIMARY || "#E58325",
|
||||
accent: process.env.THEME_LIGHT_ACCENT || "#007A99",
|
||||
secondary: process.env.THEME_DARK_SECONDARY || "#973542",
|
||||
success: process.env.THEME_DARK_SUCCESS || "#43A047",
|
||||
|
@ -237,7 +237,6 @@ export default {
|
|||
privateRuntimeConfig: {},
|
||||
|
||||
proxy: {
|
||||
// "http://localhost:9000/*/api",
|
||||
// See Proxy section
|
||||
[`${process.env.SUB_PATH || ""}api`]: {
|
||||
pathRewrite: {
|
||||
|
|
|
@ -193,6 +193,7 @@
|
|||
|
||||
<script lang="ts">
|
||||
import { defineComponent, ref, useContext, computed, reactive } from "@nuxtjs/composition-api";
|
||||
import { alert } from "~/composables/use-toast";
|
||||
export default defineComponent({
|
||||
layout: "basic",
|
||||
|
||||
|
@ -215,7 +216,16 @@ export default defineComponent({
|
|||
formData.append("username", form.email);
|
||||
formData.append("password", form.password);
|
||||
|
||||
await $auth.loginWith("local", { data: formData });
|
||||
try {
|
||||
await $auth.loginWith("local", { data: formData });
|
||||
} catch (error) {
|
||||
if (error.response.status === 401) {
|
||||
alert.error("Invalid Credentials");
|
||||
}
|
||||
else {
|
||||
alert.error("Something Went Wrong!")
|
||||
}
|
||||
}
|
||||
loggingIn.value = false;
|
||||
}
|
||||
|
||||
|
|
|
@ -35,6 +35,9 @@
|
|||
<v-date-picker v-model="newMeal.date" no-title @input="pickerMenu = false"></v-date-picker>
|
||||
</v-menu>
|
||||
<v-card-text>
|
||||
<v-select v-model="newMeal.entryType" :return-object="false" :items="planTypeOptions" label="Entry Type">
|
||||
</v-select>
|
||||
|
||||
<v-autocomplete
|
||||
v-if="!dialog.note"
|
||||
v-model="newMeal.recipeId"
|
||||
|
@ -68,7 +71,7 @@
|
|||
</div>
|
||||
</div>
|
||||
<v-switch v-model="edit" label="Editor"></v-switch>
|
||||
<v-row class="mt-2">
|
||||
<v-row class="">
|
||||
<v-col
|
||||
v-for="(plan, index) in mealsByDate"
|
||||
:key="index"
|
||||
|
@ -79,68 +82,120 @@
|
|||
xl="2"
|
||||
class="col-borders my-1 d-flex flex-column"
|
||||
>
|
||||
<p class="h5 text-center">
|
||||
{{ $d(plan.date, "short") }}
|
||||
</p>
|
||||
<draggable
|
||||
tag="div"
|
||||
:value="plan.meals"
|
||||
group="meals"
|
||||
:data-index="index"
|
||||
:data-box="plan.date"
|
||||
style="min-height: 150px"
|
||||
@end="onMoveCallback"
|
||||
>
|
||||
<v-card v-for="mealplan in plan.meals" :key="mealplan.id" v-model="hover[mealplan.id]" class="my-1">
|
||||
<v-list-item :to="edit ? null : `/recipe/${mealplan.recipe.slug}`">
|
||||
<v-list-item-avatar :rounded="false">
|
||||
<RecipeCardImage v-if="mealplan.recipe" tiny icon-size="25" :slug="mealplan.recipe.slug" />
|
||||
<v-icon v-else>
|
||||
{{ $globals.icons.primary }}
|
||||
</v-icon>
|
||||
</v-list-item-avatar>
|
||||
<v-list-item-content>
|
||||
<v-list-item-title class="mb-1">
|
||||
{{ mealplan.recipe ? mealplan.recipe.name : mealplan.title }}
|
||||
</v-list-item-title>
|
||||
<v-list-item-subtitle>
|
||||
{{ mealplan.recipe ? mealplan.recipe.description : mealplan.text }}
|
||||
</v-list-item-subtitle>
|
||||
</v-list-item-content>
|
||||
</v-list-item>
|
||||
<v-divider v-if="edit" class="mx-2"></v-divider>
|
||||
<v-card-actions v-if="edit">
|
||||
<v-btn color="error" icon @click="actions.deleteOne(mealplan.id)">
|
||||
<v-icon>{{ $globals.icons.delete }}</v-icon>
|
||||
</v-btn>
|
||||
<v-spacer></v-spacer>
|
||||
<v-btn
|
||||
v-if="mealplan.recipe"
|
||||
color="info"
|
||||
icon
|
||||
nuxt
|
||||
target="_blank"
|
||||
:to="`/recipe/${mealplan.recipe.slug}`"
|
||||
>
|
||||
<v-icon>{{ $globals.icons.openInNew }}</v-icon>
|
||||
</v-btn>
|
||||
<v-sheet class="mb-2 bottom-color-border">
|
||||
<p class="headline text-center mb-1">
|
||||
{{ $d(plan.date, "short") }}
|
||||
</p>
|
||||
</v-sheet>
|
||||
|
||||
<!-- Day Column Recipes -->
|
||||
<template v-if="edit">
|
||||
<draggable
|
||||
tag="div"
|
||||
:value="plan.meals"
|
||||
group="meals"
|
||||
:data-index="index"
|
||||
:data-box="plan.date"
|
||||
style="min-height: 150px"
|
||||
@end="onMoveCallback"
|
||||
>
|
||||
<v-card v-for="mealplan in plan.meals" :key="mealplan.id" v-model="hover[mealplan.id]" class="my-1">
|
||||
<v-list-item :to="edit || !mealplan.recipe ? null : `/recipe/${mealplan.recipe.slug}`">
|
||||
<v-list-item-avatar :rounded="false">
|
||||
<RecipeCardImage
|
||||
v-if="mealplan.recipe"
|
||||
tiny
|
||||
icon-size="25"
|
||||
:slug="mealplan.recipe ? mealplan.recipe.slug : ''"
|
||||
/>
|
||||
<v-icon v-else>
|
||||
{{ $globals.icons.primary }}
|
||||
</v-icon>
|
||||
</v-list-item-avatar>
|
||||
<v-list-item-content>
|
||||
<v-list-item-title class="mb-1">
|
||||
{{ mealplan.recipe ? mealplan.recipe.name : mealplan.title }}
|
||||
</v-list-item-title>
|
||||
<v-list-item-subtitle style="min-height: 16px">
|
||||
{{ mealplan.recipe ? mealplan.recipe.description + " " : mealplan.text }}
|
||||
</v-list-item-subtitle>
|
||||
</v-list-item-content>
|
||||
</v-list-item>
|
||||
<v-divider class="mx-2"></v-divider>
|
||||
<div class="py-2 px-2 d-flex">
|
||||
<v-menu offset-y>
|
||||
<template #activator="{ on, attrs }">
|
||||
<v-chip v-bind="attrs" label small color="accent" v-on="on" @click.prevent>
|
||||
<v-icon left>
|
||||
{{ $globals.icons.tags }}
|
||||
</v-icon>
|
||||
{{ mealplan.entryType }}
|
||||
</v-chip>
|
||||
</template>
|
||||
<v-list>
|
||||
<v-list-item
|
||||
v-for="mealType in planTypeOptions"
|
||||
:key="mealType.value"
|
||||
@click="actions.setType(mealplan, mealType.value)"
|
||||
>
|
||||
<v-list-item-title> {{ mealType.text }} </v-list-item-title>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
</v-menu>
|
||||
<v-spacer></v-spacer>
|
||||
<v-btn color="info" class="mr-2" small icon>
|
||||
<v-icon>{{ $globals.icons.cartCheck }}</v-icon>
|
||||
</v-btn>
|
||||
<v-btn color="error" small icon @click="actions.deleteOne(mealplan.id)">
|
||||
<v-icon>{{ $globals.icons.delete }}</v-icon>
|
||||
</v-btn>
|
||||
</div>
|
||||
</v-card>
|
||||
</draggable>
|
||||
|
||||
<!-- Day Column Actions -->
|
||||
<v-card outlined class="mt-auto">
|
||||
<v-card-actions class="d-flex">
|
||||
<div style="width: 50%">
|
||||
<v-btn block text @click="randomMeal(plan.date)">
|
||||
<v-icon large>{{ $globals.icons.diceMultiple }}</v-icon>
|
||||
</v-btn>
|
||||
</div>
|
||||
<div style="width: 50%">
|
||||
<v-btn block text @click="openDialog(plan.date)">
|
||||
<v-icon large>{{ $globals.icons.createAlt }}</v-icon>
|
||||
</v-btn>
|
||||
</div>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</draggable>
|
||||
<v-card v-if="edit" outlined class="mt-auto">
|
||||
<v-card-actions class="d-flex">
|
||||
<div style="width: 50%">
|
||||
<v-btn block text @click="randomMeal(plan.date)">
|
||||
<v-icon large>{{ $globals.icons.diceMultiple }}</v-icon>
|
||||
</v-btn>
|
||||
</div>
|
||||
<div style="width: 50%">
|
||||
<v-btn block text @click="openDialog(plan.date)">
|
||||
<v-icon large>{{ $globals.icons.createAlt }}</v-icon>
|
||||
</v-btn>
|
||||
</div>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</template>
|
||||
<template v-else-if="plan.meals">
|
||||
<RecipeCard
|
||||
v-for="mealplan in plan.meals"
|
||||
:key="mealplan.id"
|
||||
:recipe-id="0"
|
||||
:image-height="125"
|
||||
class="mb-2"
|
||||
:route="mealplan.recipe ? true : false"
|
||||
:slug="mealplan.recipe ? mealplan.recipe.slug : mealplan.title"
|
||||
:description="mealplan.recipe ? mealplan.recipe.description : mealplan.text"
|
||||
:name="mealplan.recipe ? mealplan.recipe.name : mealplan.title"
|
||||
>
|
||||
<template #actions>
|
||||
<v-divider class="mb-0 mt-2 mx-2"></v-divider>
|
||||
<v-card-actions class="justify-end mt-1">
|
||||
<v-chip label small color="accent">
|
||||
<v-icon left>
|
||||
{{ $globals.icons.tags }}
|
||||
</v-icon>
|
||||
{{ mealplan.entryType }}
|
||||
</v-chip>
|
||||
</v-card-actions>
|
||||
</template>
|
||||
</RecipeCard>
|
||||
</template>
|
||||
|
||||
<v-skeleton-loader v-else elevation="2" type="image, list-item-two-line"></v-skeleton-loader>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-container>
|
||||
|
@ -151,17 +206,19 @@ import { computed, defineComponent, reactive, ref, toRefs, watch } from "@nuxtjs
|
|||
import { isSameDay, addDays, subDays, parseISO, format } from "date-fns";
|
||||
import { SortableEvent } from "sortablejs"; // eslint-disable-line
|
||||
import draggable from "vuedraggable";
|
||||
import { useMealplans } from "~/composables/use-group-mealplan";
|
||||
import { useMealplans, planTypeOptions } from "~/composables/use-group-mealplan";
|
||||
import { useRecipes, allRecipes } from "~/composables/recipes";
|
||||
import RecipeCardImage from "~/components/Domain/Recipe/RecipeCardImage.vue";
|
||||
import RecipeCard from "~/components/Domain/Recipe/RecipeCard.vue";
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
draggable,
|
||||
RecipeCardImage,
|
||||
RecipeCard,
|
||||
},
|
||||
setup() {
|
||||
const { mealplans, actions } = useMealplans();
|
||||
const { mealplans, actions, loading } = useMealplans();
|
||||
|
||||
useRecipes(true, true);
|
||||
const state = reactive({
|
||||
|
@ -242,6 +299,7 @@ export default defineComponent({
|
|||
|
||||
// =====================================================
|
||||
// New Meal Dialog
|
||||
|
||||
const domMealDialog = ref(null);
|
||||
const dialog = reactive({
|
||||
loading: false,
|
||||
|
@ -256,11 +314,13 @@ export default defineComponent({
|
|||
newMeal.title = "";
|
||||
newMeal.text = "";
|
||||
});
|
||||
|
||||
const newMeal = reactive({
|
||||
date: "",
|
||||
title: "",
|
||||
text: "",
|
||||
recipeId: null,
|
||||
recipeId: null as Number | null,
|
||||
entryType: "dinner",
|
||||
});
|
||||
|
||||
function openDialog(date: Date) {
|
||||
|
@ -273,40 +333,45 @@ export default defineComponent({
|
|||
newMeal.date = "";
|
||||
newMeal.title = "";
|
||||
newMeal.text = "";
|
||||
newMeal.entryType = "dinner";
|
||||
newMeal.recipeId = null;
|
||||
}
|
||||
|
||||
function randomMeal(date: Date) {
|
||||
async function randomMeal(date: Date) {
|
||||
// TODO: Refactor to use API call to get random recipe
|
||||
// @ts-ignore
|
||||
const randomRecipe = allRecipes.value[Math.floor(Math.random() * allRecipes.value.length)];
|
||||
|
||||
newMeal.date = format(date, "yyyy-MM-dd");
|
||||
// @ts-ignore
|
||||
newMeal.recipeId = randomRecipe.id;
|
||||
|
||||
newMeal.recipeId = randomRecipe.id || null;
|
||||
|
||||
console.log(newMeal.recipeId, randomRecipe.id);
|
||||
|
||||
// @ts-ignore
|
||||
actions.createOne(newMeal);
|
||||
await actions.createOne({ ...newMeal });
|
||||
resetDialog();
|
||||
}
|
||||
|
||||
return {
|
||||
resetDialog,
|
||||
randomMeal,
|
||||
...toRefs(state),
|
||||
actions,
|
||||
allRecipes,
|
||||
backOneWeek,
|
||||
days,
|
||||
dialog,
|
||||
domMealDialog,
|
||||
openDialog,
|
||||
mealplans,
|
||||
actions,
|
||||
newMeal,
|
||||
allRecipes,
|
||||
...toRefs(state),
|
||||
mealsByDate,
|
||||
onMoveCallback,
|
||||
backOneWeek,
|
||||
forwardOneWeek,
|
||||
loading,
|
||||
mealplans,
|
||||
mealsByDate,
|
||||
newMeal,
|
||||
onMoveCallback,
|
||||
openDialog,
|
||||
planTypeOptions,
|
||||
randomMeal,
|
||||
resetDialog,
|
||||
weekRange,
|
||||
days,
|
||||
};
|
||||
},
|
||||
head() {
|
||||
|
@ -318,8 +383,16 @@ export default defineComponent({
|
|||
</script>
|
||||
|
||||
<style lang="css">
|
||||
.col-borders {
|
||||
/* .col-borders {
|
||||
border-top: 1px solid #e0e0e0;
|
||||
} */
|
||||
|
||||
.left-color-border {
|
||||
border-left: 5px solid var(--v-primary-base) !important;
|
||||
}
|
||||
|
||||
.bottom-color-border {
|
||||
border-bottom: 2px solid var(--v-primary-base) !important;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -110,7 +110,7 @@
|
|||
<!-- Advanced Editor -->
|
||||
<div v-if="form">
|
||||
<h2 class="mb-4">{{ $t("recipe.ingredients") }}</h2>
|
||||
<draggable v-model="recipe.recipeIngredient" handle=".handle">
|
||||
<draggable v-if="recipe.recipeIngredient.length > 0" v-model="recipe.recipeIngredient" handle=".handle">
|
||||
<RecipeIngredientEditor
|
||||
v-for="(ingredient, index) in recipe.recipeIngredient"
|
||||
:key="ingredient.referenceId"
|
||||
|
@ -119,6 +119,7 @@
|
|||
@delete="recipe.recipeIngredient.splice(index, 1)"
|
||||
/>
|
||||
</draggable>
|
||||
<v-skeleton-loader v-else boilerplate elevation="2" type="list-item"> </v-skeleton-loader>
|
||||
<div class="d-flex justify-end mt-2">
|
||||
<v-tooltip top color="accent">
|
||||
<template #activator="{ on, attrs }">
|
||||
|
|
|
@ -126,7 +126,7 @@ export default defineComponent({
|
|||
|
||||
const headers = reactive({
|
||||
id: false,
|
||||
owner: true,
|
||||
owner: false,
|
||||
tags: true,
|
||||
categories: true,
|
||||
recipeYield: false,
|
||||
|
|
|
@ -4,7 +4,7 @@ ALLOW_SIGNUP=true
|
|||
|
||||
# =====================================
|
||||
# Light Mode Config
|
||||
THEME_LIGHT_PRIMARY=#007A99
|
||||
THEME_LIGHT_PRIMARY=#E58325
|
||||
THEME_LIGHT_ACCENT=#007A99
|
||||
THEME_LIGHT_SECONDARY=#973542
|
||||
THEME_LIGHT_SUCCESS=#43A047
|
||||
|
|
|
@ -65,7 +65,7 @@ logging.basicConfig(
|
|||
|
||||
|
||||
def logger_init() -> logging.Logger:
|
||||
""" Returns the Root Loggin Object for Mealie """
|
||||
"""Returns the Root Loggin Object for Mealie"""
|
||||
return logging.getLogger("mealie")
|
||||
|
||||
|
||||
|
@ -74,7 +74,7 @@ root_logger.info("Testing Root Logger")
|
|||
|
||||
|
||||
def get_logger(module=None) -> logging.Logger:
|
||||
""" Returns a child logger for mealie """
|
||||
"""Returns a child logger for mealie"""
|
||||
global root_logger
|
||||
|
||||
if module is None:
|
||||
|
|
|
@ -14,7 +14,7 @@ logger = get_logger()
|
|||
|
||||
@router.get("", response_model=EventsOut)
|
||||
async def get_events(session: Session = Depends(generate_session)):
|
||||
""" Get event from the Database """
|
||||
"""Get event from the Database"""
|
||||
db = get_database(session)
|
||||
|
||||
return EventsOut(total=db.events.count_all(), events=db.events.get_all(order_by="time_stamp"))
|
||||
|
@ -22,7 +22,7 @@ async def get_events(session: Session = Depends(generate_session)):
|
|||
|
||||
@router.delete("")
|
||||
async def delete_events(session: Session = Depends(generate_session)):
|
||||
""" Get event from the Database """
|
||||
"""Get event from the Database"""
|
||||
db = get_database(session)
|
||||
db.events.delete_all()
|
||||
return {"message": "All events deleted"}
|
||||
|
@ -30,6 +30,6 @@ async def delete_events(session: Session = Depends(generate_session)):
|
|||
|
||||
@router.delete("/{id}")
|
||||
async def delete_event(id: int, session: Session = Depends(generate_session)):
|
||||
""" Delete event from the Database """
|
||||
"""Delete event from the Database"""
|
||||
db = get_database(session)
|
||||
return db.events.delete(id)
|
||||
|
|
|
@ -20,7 +20,7 @@ async def create_event_notification(
|
|||
event_data: EventNotificationIn,
|
||||
session: Session = Depends(generate_session),
|
||||
):
|
||||
""" Create event_notification in the Database """
|
||||
"""Create event_notification in the Database"""
|
||||
db = get_database(session)
|
||||
|
||||
return db.event_notifications.create(event_data)
|
||||
|
@ -31,7 +31,7 @@ async def test_notification_route(
|
|||
test_data: TestEvent,
|
||||
session: Session = Depends(generate_session),
|
||||
):
|
||||
""" Create event_notification in the Database """
|
||||
"""Create event_notification in the Database"""
|
||||
db = get_database(session)
|
||||
|
||||
if test_data.id:
|
||||
|
@ -47,21 +47,21 @@ async def test_notification_route(
|
|||
|
||||
@router.get("/notifications", response_model=list[EventNotificationOut])
|
||||
async def get_all_event_notification(session: Session = Depends(generate_session)):
|
||||
""" Get all event_notification from the Database """
|
||||
"""Get all event_notification from the Database"""
|
||||
db = get_database(session)
|
||||
return db.event_notifications.get_all(override_schema=EventNotificationOut)
|
||||
|
||||
|
||||
@router.put("/notifications/{id}")
|
||||
async def update_event_notification(id: int, session: Session = Depends(generate_session)):
|
||||
""" Update event_notification in the Database """
|
||||
"""Update event_notification in the Database"""
|
||||
# not yet implemented
|
||||
raise HTTPException(status.HTTP_405_METHOD_NOT_ALLOWED)
|
||||
|
||||
|
||||
@router.delete("/notifications/{id}")
|
||||
async def delete_event_notification(id: int, session: Session = Depends(generate_session)):
|
||||
""" Delete event_notification from the Database """
|
||||
"""Delete event_notification from the Database"""
|
||||
# Delete Item
|
||||
db = get_database(session)
|
||||
return db.event_notifications.delete(id)
|
||||
|
|
|
@ -12,7 +12,7 @@ router = APIRouter(prefix="/about")
|
|||
|
||||
@router.get("", response_model=AdminAboutInfo)
|
||||
async def get_app_info():
|
||||
""" Get general application information """
|
||||
"""Get general application information"""
|
||||
settings = get_app_settings()
|
||||
|
||||
return AdminAboutInfo(
|
||||
|
|
|
@ -25,7 +25,7 @@ class EmailTest(CamelModel):
|
|||
|
||||
@router.get("", response_model=EmailReady)
|
||||
async def check_email_config():
|
||||
""" Get general application information """
|
||||
"""Get general application information"""
|
||||
settings = get_app_settings()
|
||||
|
||||
return EmailReady(ready=settings.SMTP_ENABLE)
|
||||
|
|
|
@ -13,7 +13,7 @@ router = AdminAPIRouter(prefix="/groups")
|
|||
|
||||
@router.get("", response_model=list[GroupInDB])
|
||||
async def get_all_groups(session: Session = Depends(generate_session)):
|
||||
""" Returns a list of all groups in the database """
|
||||
"""Returns a list of all groups in the database"""
|
||||
db = get_database(session)
|
||||
|
||||
return db.groups.get_all()
|
||||
|
@ -25,7 +25,7 @@ async def create_group(
|
|||
group_data: GroupBase,
|
||||
session: Session = Depends(generate_session),
|
||||
):
|
||||
""" Creates a Group in the Database """
|
||||
"""Creates a Group in the Database"""
|
||||
db = get_database(session)
|
||||
|
||||
try:
|
||||
|
@ -38,7 +38,7 @@ async def create_group(
|
|||
|
||||
@router.put("/{id}")
|
||||
async def update_group_data(id: int, group_data: UpdateGroup, session: Session = Depends(generate_session)):
|
||||
""" Updates a User Group """
|
||||
"""Updates a User Group"""
|
||||
db = get_database(session)
|
||||
db.groups.update(id, group_data.dict())
|
||||
|
||||
|
@ -50,7 +50,7 @@ async def delete_user_group(
|
|||
current_user: PrivateUser = Depends(get_current_user),
|
||||
session: Session = Depends(generate_session),
|
||||
):
|
||||
""" Removes a user group from the database """
|
||||
"""Removes a user group from the database"""
|
||||
db = get_database(session)
|
||||
|
||||
if id == 1:
|
||||
|
|
|
@ -8,7 +8,7 @@ router = APIRouter(prefix="/logs")
|
|||
|
||||
@router.get("/{num}")
|
||||
async def get_log(num: int):
|
||||
""" Doc Str """
|
||||
"""Doc Str"""
|
||||
with open(LOGGER_FILE, "rb") as f:
|
||||
log_text = tail(f, num)
|
||||
return log_text
|
||||
|
@ -16,7 +16,7 @@ async def get_log(num: int):
|
|||
|
||||
@router.get("")
|
||||
async def get_log_file():
|
||||
""" Returns a token to download a file """
|
||||
"""Returns a token to download a file"""
|
||||
return {"fileToken": create_file_token(LOGGER_FILE)}
|
||||
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@ router = APIRouter(prefix="/about")
|
|||
|
||||
@router.get("", response_model=AppInfo)
|
||||
async def get_app_info():
|
||||
""" Get general application information """
|
||||
"""Get general application information"""
|
||||
settings = get_app_settings()
|
||||
|
||||
return AppInfo(
|
||||
|
|
|
@ -43,6 +43,6 @@ def get_token(
|
|||
|
||||
@user_router.get("/refresh")
|
||||
async def refresh_token(current_user: PrivateUser = Depends(get_current_user)):
|
||||
""" Use a valid token to get another token"""
|
||||
"""Use a valid token to get another token"""
|
||||
access_token = security.create_access_token(data=dict(sub=current_user.email))
|
||||
return {"access_token": access_token, "token_type": "bearer"}
|
||||
|
|
|
@ -63,7 +63,7 @@ def export_database(
|
|||
|
||||
@router.post("/upload", status_code=status.HTTP_200_OK)
|
||||
def upload_backup_file(archive: UploadFile = File(...)):
|
||||
""" Upload a .zip File to later be imported into Mealie """
|
||||
"""Upload a .zip File to later be imported into Mealie"""
|
||||
dest = app_dirs.BACKUP_DIR.joinpath(archive.filename)
|
||||
|
||||
with dest.open("wb") as buffer:
|
||||
|
@ -75,7 +75,7 @@ def upload_backup_file(archive: UploadFile = File(...)):
|
|||
|
||||
@router.get("/{file_name}/download")
|
||||
async def download_backup_file(file_name: str):
|
||||
""" Returns a token to download a file """
|
||||
"""Returns a token to download a file"""
|
||||
file = app_dirs.BACKUP_DIR.joinpath(file_name)
|
||||
|
||||
return {"fileToken": create_file_token(file)}
|
||||
|
@ -89,7 +89,7 @@ def import_database(
|
|||
session: Session = Depends(generate_session),
|
||||
user: PrivateUser = Depends(get_current_user),
|
||||
):
|
||||
""" Import a database backup file generated from Mealie. """
|
||||
"""Import a database backup file generated from Mealie."""
|
||||
|
||||
db_import = imports.import_database(
|
||||
user=user,
|
||||
|
@ -109,7 +109,7 @@ def import_database(
|
|||
|
||||
@router.delete("/{file_name}/delete", status_code=status.HTTP_200_OK)
|
||||
def delete_backup(file_name: str):
|
||||
""" Removes a database backup from the file system """
|
||||
"""Removes a database backup from the file system"""
|
||||
file_path = app_dirs.BACKUP_DIR.joinpath(file_name)
|
||||
|
||||
if not file_path.is_file():
|
||||
|
|
|
@ -14,14 +14,14 @@ admin_router = AdminAPIRouter()
|
|||
|
||||
@public_router.get("")
|
||||
async def get_all_recipe_categories(session: Session = Depends(generate_session)):
|
||||
""" Returns a list of available categories in the database """
|
||||
"""Returns a list of available categories in the database"""
|
||||
db = get_database(session)
|
||||
return db.categories.get_all_limit_columns(fields=["slug", "name"])
|
||||
|
||||
|
||||
@public_router.get("/empty")
|
||||
def get_empty_categories(session: Session = Depends(generate_session)):
|
||||
""" Returns a list of categories that do not contain any recipes"""
|
||||
"""Returns a list of categories that do not contain any recipes"""
|
||||
db = get_database(session)
|
||||
return db.categories.get_empty()
|
||||
|
||||
|
@ -30,7 +30,7 @@ def get_empty_categories(session: Session = Depends(generate_session)):
|
|||
def get_all_recipes_by_category(
|
||||
category: str, session: Session = Depends(generate_session), is_user: bool = Depends(is_logged_in)
|
||||
):
|
||||
""" Returns a list of recipes associated with the provided category. """
|
||||
"""Returns a list of recipes associated with the provided category."""
|
||||
db = get_database(session)
|
||||
|
||||
category_obj = db.categories.get(category)
|
||||
|
@ -44,7 +44,7 @@ def get_all_recipes_by_category(
|
|||
|
||||
@user_router.post("")
|
||||
async def create_recipe_category(category: CategoryIn, session: Session = Depends(generate_session)):
|
||||
""" Creates a Category in the database """
|
||||
"""Creates a Category in the database"""
|
||||
db = get_database(session)
|
||||
|
||||
try:
|
||||
|
@ -55,7 +55,7 @@ async def create_recipe_category(category: CategoryIn, session: Session = Depend
|
|||
|
||||
@admin_router.put("/{category}", response_model=RecipeCategoryResponse)
|
||||
async def update_recipe_category(category: str, new_category: CategoryIn, session: Session = Depends(generate_session)):
|
||||
""" Updates an existing Tag in the database """
|
||||
"""Updates an existing Tag in the database"""
|
||||
db = get_database(session)
|
||||
|
||||
try:
|
||||
|
|
|
@ -10,13 +10,13 @@ user_router = UserAPIRouter(prefix="/groups", tags=["Groups: Self Service"])
|
|||
|
||||
@user_router.get("/self", response_model=GroupInDB)
|
||||
async def get_logged_in_user_group(g_service: GroupSelfService = Depends(GroupSelfService.write_existing)):
|
||||
""" Returns the Group Data for the Current User """
|
||||
"""Returns the Group Data for the Current User"""
|
||||
return g_service.item
|
||||
|
||||
|
||||
@user_router.get("/members", response_model=list[UserOut])
|
||||
async def get_group_members(g_service: GroupSelfService = Depends(GroupSelfService.write_existing)):
|
||||
""" Returns the Group of user lists """
|
||||
"""Returns the Group of user lists"""
|
||||
return g_service.get_members()
|
||||
|
||||
|
||||
|
|
|
@ -33,7 +33,7 @@ async def get_recipe_img(slug: str, file_name: ImageType = ImageType.original):
|
|||
|
||||
@router.get("/{slug}/assets/{file_name}")
|
||||
async def get_recipe_asset(slug: str, file_name: str):
|
||||
""" Returns a recipe asset """
|
||||
"""Returns a recipe asset"""
|
||||
file = Recipe(slug=slug).asset_dir.joinpath(file_name)
|
||||
|
||||
try:
|
||||
|
|
|
@ -20,7 +20,7 @@ router = AdminAPIRouter(prefix="/api/migrations", tags=["Migration"])
|
|||
|
||||
@router.get("", response_model=List[Migrations])
|
||||
def get_all_migration_options():
|
||||
""" Returns a list of avaiable directories that can be imported into Mealie """
|
||||
"""Returns a list of avaiable directories that can be imported into Mealie"""
|
||||
response_data = []
|
||||
migration_dirs = [
|
||||
app_dirs.MIGRATION_DIR.joinpath("nextcloud"),
|
||||
|
@ -46,14 +46,14 @@ def import_migration(
|
|||
session: Session = Depends(generate_session),
|
||||
user: PrivateUser = Depends(get_logged_in_user),
|
||||
):
|
||||
""" Imports all the recipes in a given directory """
|
||||
"""Imports all the recipes in a given directory"""
|
||||
file_path = app_dirs.MIGRATION_DIR.joinpath(import_type.value, file_name)
|
||||
return migration.migrate(user, import_type, file_path, session)
|
||||
|
||||
|
||||
@router.delete("/{import_type}/{file_name}/delete", status_code=status.HTTP_200_OK)
|
||||
def delete_migration_data(import_type: migration.Migration, file_name: str):
|
||||
""" Removes migration data from the file system """
|
||||
"""Removes migration data from the file system"""
|
||||
|
||||
remove_path = app_dirs.MIGRATION_DIR.joinpath(import_type.value, file_name)
|
||||
|
||||
|
@ -67,7 +67,7 @@ def delete_migration_data(import_type: migration.Migration, file_name: str):
|
|||
|
||||
@router.post("/{import_type}/upload", status_code=status.HTTP_200_OK)
|
||||
def upload_nextcloud_zipfile(import_type: migration.Migration, archive: UploadFile = File(...)):
|
||||
""" Upload a .zip File to later be imported into Mealie """
|
||||
"""Upload a .zip File to later be imported into Mealie"""
|
||||
dir = app_dirs.MIGRATION_DIR.joinpath(import_type.value)
|
||||
dir.mkdir(parents=True, exist_ok=True)
|
||||
dest = dir.joinpath(archive.filename)
|
||||
|
|
|
@ -20,7 +20,7 @@ async def create_comment(
|
|||
session: Session = Depends(generate_session),
|
||||
current_user: PrivateUser = Depends(get_current_user),
|
||||
):
|
||||
""" Create comment in the Database """
|
||||
"""Create comment in the Database"""
|
||||
db = get_database(session)
|
||||
|
||||
new_comment = SaveComment(user=current_user.id, text=new_comment.text, recipe_slug=slug)
|
||||
|
@ -34,7 +34,7 @@ async def update_comment(
|
|||
session: Session = Depends(generate_session),
|
||||
current_user: PrivateUser = Depends(get_current_user),
|
||||
):
|
||||
""" Update comment in the Database """
|
||||
"""Update comment in the Database"""
|
||||
db = get_database(session)
|
||||
old_comment: CommentOut = db.comments.get(id)
|
||||
|
||||
|
@ -48,7 +48,7 @@ async def update_comment(
|
|||
async def delete_comment(
|
||||
id: int, session: Session = Depends(generate_session), current_user: PrivateUser = Depends(get_current_user)
|
||||
):
|
||||
""" Delete comment from the Database """
|
||||
"""Delete comment from the Database"""
|
||||
db = get_database(session)
|
||||
comment: CommentOut = db.comments.get(id)
|
||||
if current_user.id == comment.user.id or current_user.admin:
|
||||
|
|
|
@ -16,7 +16,7 @@ user_router = UserAPIRouter()
|
|||
|
||||
@user_router.post("/{slug}/image")
|
||||
def scrape_image_url(slug: str, url: CreateRecipeByUrl):
|
||||
""" Removes an existing image and replaces it with the incoming file. """
|
||||
"""Removes an existing image and replaces it with the incoming file."""
|
||||
|
||||
scrape_image(url.url, slug)
|
||||
|
||||
|
@ -28,7 +28,7 @@ def update_recipe_image(
|
|||
extension: str = Form(...),
|
||||
session: Session = Depends(generate_session),
|
||||
):
|
||||
""" Removes an existing image and replaces it with the incoming file. """
|
||||
"""Removes an existing image and replaces it with the incoming file."""
|
||||
db = get_database(session)
|
||||
write_image(slug, image, extension)
|
||||
new_version = db.recipes.update_image(slug, extension)
|
||||
|
@ -45,7 +45,7 @@ def upload_recipe_asset(
|
|||
file: UploadFile = File(...),
|
||||
session: Session = Depends(generate_session),
|
||||
):
|
||||
""" Upload a file to store as a recipe asset """
|
||||
"""Upload a file to store as a recipe asset"""
|
||||
file_name = slugify(name) + "." + extension
|
||||
asset_in = RecipeAsset(name=name, icon=icon, file_name=file_name)
|
||||
dest = Recipe(slug=slug).asset_dir.joinpath(file_name)
|
||||
|
|
|
@ -27,13 +27,13 @@ async def get_all(start=0, limit=None, service: RecipeService = Depends(RecipeSe
|
|||
|
||||
@user_router.post("", status_code=201, response_model=str)
|
||||
def create_from_name(data: CreateRecipe, recipe_service: RecipeService = Depends(RecipeService.private)) -> str:
|
||||
""" Takes in a JSON string and loads data into the database as a new entry"""
|
||||
"""Takes in a JSON string and loads data into the database as a new entry"""
|
||||
return recipe_service.create_one(data).slug
|
||||
|
||||
|
||||
@user_router.post("/create-url", status_code=201, response_model=str)
|
||||
def parse_recipe_url(url: CreateRecipeByUrl, recipe_service: RecipeService = Depends(RecipeService.private)):
|
||||
""" Takes in a URL and attempts to scrape data and load it into the database """
|
||||
"""Takes in a URL and attempts to scrape data and load it into the database"""
|
||||
recipe = create_from_url(url.url)
|
||||
return recipe_service.create_one(recipe).slug
|
||||
|
||||
|
@ -44,7 +44,7 @@ def parse_recipe_url_bulk(
|
|||
recipe_service: RecipeService = Depends(RecipeService.private),
|
||||
bg_service: BackgroundExecutor = Depends(BackgroundExecutor.private),
|
||||
):
|
||||
""" Takes in a URL and attempts to scrape data and load it into the database """
|
||||
"""Takes in a URL and attempts to scrape data and load it into the database"""
|
||||
|
||||
def bulk_import_func(task_id: int, session: Session) -> None:
|
||||
database = get_database(session)
|
||||
|
@ -94,30 +94,30 @@ async def create_recipe_from_zip(
|
|||
temp_path=Depends(temporary_zip_path),
|
||||
archive: UploadFile = File(...),
|
||||
):
|
||||
""" Create recipe from archive """
|
||||
"""Create recipe from archive"""
|
||||
recipe = recipe_service.create_from_zip(archive, temp_path)
|
||||
return recipe.slug
|
||||
|
||||
|
||||
@user_router.get("/{slug}", response_model=Recipe)
|
||||
def get_recipe(recipe_service: RecipeService = Depends(RecipeService.read_existing)):
|
||||
""" Takes in a recipe slug, returns all data for a recipe """
|
||||
"""Takes in a recipe slug, returns all data for a recipe"""
|
||||
return recipe_service.item
|
||||
|
||||
|
||||
@user_router.put("/{slug}")
|
||||
def update_recipe(data: Recipe, recipe_service: RecipeService = Depends(RecipeService.write_existing)):
|
||||
""" Updates a recipe by existing slug and data. """
|
||||
"""Updates a recipe by existing slug and data."""
|
||||
return recipe_service.update_one(data)
|
||||
|
||||
|
||||
@user_router.patch("/{slug}")
|
||||
def patch_recipe(data: Recipe, recipe_service: RecipeService = Depends(RecipeService.write_existing)):
|
||||
""" Updates a recipe by existing slug and data. """
|
||||
"""Updates a recipe by existing slug and data."""
|
||||
return recipe_service.patch_one(data)
|
||||
|
||||
|
||||
@user_router.delete("/{slug}")
|
||||
def delete_recipe(recipe_service: RecipeService = Depends(RecipeService.write_existing)):
|
||||
""" Deletes a recipe by slug """
|
||||
"""Deletes a recipe by slug"""
|
||||
return recipe_service.delete_one()
|
||||
|
|
|
@ -34,7 +34,7 @@ async def get_recipe_formats_and_templates(_: RecipeService = Depends(RecipeServ
|
|||
|
||||
@user_router.post("/{slug}/exports")
|
||||
async def get_recipe_zip_token(slug: str):
|
||||
""" Generates a recipe zip token to be used to download a recipe as a zip file """
|
||||
"""Generates a recipe zip token to be used to download a recipe as a zip file"""
|
||||
return {"token": create_recipe_slug_token(slug)}
|
||||
|
||||
|
||||
|
@ -62,7 +62,7 @@ async def get_recipe_as_zip(
|
|||
session: Session = Depends(generate_session),
|
||||
temp_path=Depends(temporary_zip_path),
|
||||
):
|
||||
""" Get a Recipe and It's Original Image as a Zip File """
|
||||
"""Get a Recipe and It's Original Image as a Zip File"""
|
||||
slug = validate_recipe_token(token)
|
||||
|
||||
if slug != slug:
|
||||
|
|
|
@ -6,7 +6,7 @@ from mealie.core.dependencies import get_admin_user, get_current_user
|
|||
|
||||
|
||||
class AdminAPIRouter(APIRouter):
|
||||
""" Router for functions to be protected behind admin authentication """
|
||||
"""Router for functions to be protected behind admin authentication"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
|
@ -17,7 +17,7 @@ class AdminAPIRouter(APIRouter):
|
|||
|
||||
|
||||
class UserAPIRouter(APIRouter):
|
||||
""" Router for functions to be protected behind user authentication """
|
||||
"""Router for functions to be protected behind user authentication"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
|
|
|
@ -17,7 +17,7 @@ async def create_shopping_list(
|
|||
current_user: PrivateUser = Depends(get_current_user),
|
||||
session: Session = Depends(generate_session),
|
||||
):
|
||||
""" Create Shopping List in the Database """
|
||||
"""Create Shopping List in the Database"""
|
||||
db = get_database(session)
|
||||
list_in.group = current_user.group
|
||||
|
||||
|
@ -26,20 +26,20 @@ async def create_shopping_list(
|
|||
|
||||
@router.get("/{id}", response_model=ShoppingListOut)
|
||||
async def get_shopping_list(id: int, session: Session = Depends(generate_session)):
|
||||
""" Get Shopping List from the Database """
|
||||
"""Get Shopping List from the Database"""
|
||||
db = get_database(session)
|
||||
return db.shopping_lists.get(id)
|
||||
|
||||
|
||||
@router.put("/{id}", response_model=ShoppingListOut)
|
||||
async def update_shopping_list(id: int, new_data: ShoppingListIn, session: Session = Depends(generate_session)):
|
||||
""" Update Shopping List in the Database """
|
||||
"""Update Shopping List in the Database"""
|
||||
db = get_database(session)
|
||||
return db.shopping_lists.update(id, new_data)
|
||||
|
||||
|
||||
@router.delete("/{id}")
|
||||
async def delete_shopping_list(id: int, session: Session = Depends(generate_session)):
|
||||
""" Delete Shopping List from the Database """
|
||||
"""Delete Shopping List from the Database"""
|
||||
db = get_database(session)
|
||||
return db.shopping_lists.delete(id)
|
||||
|
|
|
@ -14,7 +14,7 @@ admin_router = AdminAPIRouter(prefix="/api/site-settings", tags=["Settings"])
|
|||
|
||||
@public_router.get("")
|
||||
def get_main_settings(session: Session = Depends(generate_session)):
|
||||
""" Returns basic site settings """
|
||||
"""Returns basic site settings"""
|
||||
db = get_database(session)
|
||||
|
||||
return db.settings.get(1)
|
||||
|
@ -25,7 +25,7 @@ def test_webhooks(
|
|||
current_user: PrivateUser = Depends(get_current_user),
|
||||
session: Session = Depends(generate_session),
|
||||
):
|
||||
""" Run the function to test your webhooks """
|
||||
"""Run the function to test your webhooks"""
|
||||
db = get_database(session)
|
||||
group_entry: GroupInDB = db.groups.get(current_user.group, "name")
|
||||
|
||||
|
|
|
@ -14,14 +14,14 @@ admin_router = AdminAPIRouter()
|
|||
|
||||
@public_router.get("")
|
||||
async def get_all_recipe_tags(session: Session = Depends(generate_session)):
|
||||
""" Returns a list of available tags in the database """
|
||||
"""Returns a list of available tags in the database"""
|
||||
db = get_database(session)
|
||||
return db.tags.get_all_limit_columns(["slug", "name"])
|
||||
|
||||
|
||||
@public_router.get("/empty")
|
||||
def get_empty_tags(session: Session = Depends(generate_session)):
|
||||
""" Returns a list of tags that do not contain any recipes"""
|
||||
"""Returns a list of tags that do not contain any recipes"""
|
||||
db = get_database(session)
|
||||
return db.tags.get_empty()
|
||||
|
||||
|
@ -30,7 +30,7 @@ def get_empty_tags(session: Session = Depends(generate_session)):
|
|||
def get_all_recipes_by_tag(
|
||||
tag: str, session: Session = Depends(generate_session), is_user: bool = Depends(is_logged_in)
|
||||
):
|
||||
""" Returns a list of recipes associated with the provided tag. """
|
||||
"""Returns a list of recipes associated with the provided tag."""
|
||||
db = get_database(session)
|
||||
tag_obj = db.tags.get(tag)
|
||||
tag_obj = RecipeTagResponse.from_orm(tag_obj)
|
||||
|
@ -43,14 +43,14 @@ def get_all_recipes_by_tag(
|
|||
|
||||
@user_router.post("")
|
||||
async def create_recipe_tag(tag: TagIn, session: Session = Depends(generate_session)):
|
||||
""" Creates a Tag in the database """
|
||||
"""Creates a Tag in the database"""
|
||||
db = get_database(session)
|
||||
return db.tags.create(tag.dict())
|
||||
|
||||
|
||||
@admin_router.put("/{tag}", response_model=RecipeTagResponse)
|
||||
async def update_recipe_tag(tag: str, new_tag: TagIn, session: Session = Depends(generate_session)):
|
||||
""" Updates an existing Tag in the database """
|
||||
"""Updates an existing Tag in the database"""
|
||||
db = get_database(session)
|
||||
return db.tags.update(tag, new_tag.dict())
|
||||
|
||||
|
|
|
@ -20,7 +20,7 @@ async def create_api_token(
|
|||
current_user: PrivateUser = Depends(get_current_user),
|
||||
session: Session = Depends(generate_session),
|
||||
):
|
||||
""" Create api_token in the Database """
|
||||
"""Create api_token in the Database"""
|
||||
|
||||
token_data = {"long_token": True, "id": current_user.id}
|
||||
|
||||
|
@ -47,7 +47,7 @@ async def delete_api_token(
|
|||
current_user: PrivateUser = Depends(get_current_user),
|
||||
session: Session = Depends(generate_session),
|
||||
):
|
||||
""" Delete api_token from the Database """
|
||||
"""Delete api_token from the Database"""
|
||||
db = get_database(session)
|
||||
token: LongLiveTokenInDB = db.api_tokens.get(token_id)
|
||||
|
||||
|
|
|
@ -51,7 +51,7 @@ def delete_user(
|
|||
session: Session = Depends(generate_session),
|
||||
current_user: PrivateUser = Depends(get_current_user),
|
||||
):
|
||||
""" Removes a user from the database. Must be the current user or a super user"""
|
||||
"""Removes a user from the database. Must be the current user or a super user"""
|
||||
|
||||
assert_user_change_allowed(id, current_user)
|
||||
|
||||
|
|
|
@ -13,7 +13,7 @@ user_router = UserAPIRouter()
|
|||
|
||||
@user_router.get("/{id}/favorites", response_model=UserFavorites)
|
||||
async def get_favorites(id: str, session: Session = Depends(generate_session)):
|
||||
""" Get user's favorite recipes """
|
||||
"""Get user's favorite recipes"""
|
||||
db = get_database(session)
|
||||
return db.users.get(id, override_schema=UserFavorites)
|
||||
|
||||
|
@ -24,7 +24,7 @@ def add_favorite(
|
|||
current_user: PrivateUser = Depends(get_current_user),
|
||||
session: Session = Depends(generate_session),
|
||||
):
|
||||
""" Adds a Recipe to the users favorites """
|
||||
"""Adds a Recipe to the users favorites"""
|
||||
|
||||
current_user.favorite_recipes.append(slug)
|
||||
db = get_database(session)
|
||||
|
@ -37,7 +37,7 @@ def remove_favorite(
|
|||
current_user: PrivateUser = Depends(get_current_user),
|
||||
session: Session = Depends(generate_session),
|
||||
):
|
||||
""" Adds a Recipe to the users favorites """
|
||||
"""Adds a Recipe to the users favorites"""
|
||||
|
||||
assert_user_change_allowed(id, current_user)
|
||||
current_user.favorite_recipes = [x for x in current_user.favorite_recipes if x != slug]
|
||||
|
|
|
@ -18,7 +18,7 @@ user_router = UserAPIRouter(prefix="", tags=["Users: Images"])
|
|||
|
||||
@public_router.get("/{id}/image")
|
||||
async def get_user_image(id: str):
|
||||
""" Returns a users profile picture """
|
||||
"""Returns a users profile picture"""
|
||||
user_dir = app_dirs.USER_DIR.joinpath(id)
|
||||
for recipe_image in user_dir.glob("profile_image.*"):
|
||||
return FileResponse(recipe_image)
|
||||
|
@ -32,7 +32,7 @@ def update_user_image(
|
|||
profile_image: UploadFile = File(...),
|
||||
current_user: PrivateUser = Depends(get_current_user),
|
||||
):
|
||||
""" Updates a User Image """
|
||||
"""Updates a User Image"""
|
||||
|
||||
assert_user_change_allowed(id, current_user)
|
||||
|
||||
|
|
|
@ -26,19 +26,19 @@ async def reset_user_password(id: int, session: Session = Depends(generate_sessi
|
|||
|
||||
@user_router.put("/{item_id}/password")
|
||||
def update_password(password_change: ChangePassword, user_service: UserService = Depends(UserService.write_existing)):
|
||||
""" Resets the User Password"""
|
||||
"""Resets the User Password"""
|
||||
return user_service.change_password(password_change)
|
||||
|
||||
|
||||
@public_router.post("/forgot-password")
|
||||
def forgot_password(email: ForgotPassword, session: Session = Depends(generate_session)):
|
||||
""" Sends an email with a reset link to the user"""
|
||||
"""Sends an email with a reset link to the user"""
|
||||
f_service = PasswordResetService(session)
|
||||
return f_service.send_reset_email(email.email)
|
||||
|
||||
|
||||
@public_router.post("/reset-password")
|
||||
def reset_password(reset_password: ResetPassword, session: Session = Depends(generate_session)):
|
||||
""" Resets the user password"""
|
||||
"""Resets the user password"""
|
||||
f_service = PasswordResetService(session)
|
||||
return f_service.reset_password(reset_password.token, reset_password.password)
|
||||
|
|
|
@ -297,7 +297,7 @@ def import_data(lines):
|
|||
|
||||
|
||||
def export_data(lines):
|
||||
""" Parse "raw" ingredient lines into CRF-ready output """
|
||||
"""Parse "raw" ingredient lines into CRF-ready output"""
|
||||
output = []
|
||||
for line in lines:
|
||||
line_clean = re.sub("<[^<]+?>", "", line)
|
||||
|
|
|
@ -1,6 +1,24 @@
|
|||
from mealie.schema.recipe import Recipe
|
||||
from mealie.schema.recipe.recipe_ingredient import RecipeIngredient
|
||||
from mealie.schema.recipe.recipe_step import RecipeStep
|
||||
from mealie.schema.user.user import PrivateUser
|
||||
|
||||
step_text = """Recipe steps as well as other fields in the recipe page support markdown syntax.
|
||||
|
||||
**Add a link**
|
||||
|
||||
[My Link](https://beta.mealie.io)
|
||||
|
||||
**Imbed an image**
|
||||
|
||||
Use the `height="100"` or `width="100"` attributes to set the size of the image.
|
||||
|
||||
<img height="100" src="https://images.unsplash.com/photo-1567620905732-2d1ec7ab7445?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=960&q=80"></img>
|
||||
|
||||
"""
|
||||
|
||||
ingredient_note = "1 Cup Flour"
|
||||
|
||||
|
||||
def recipe_creation_factory(user: PrivateUser, name: str, additional_attrs: dict = None) -> Recipe:
|
||||
"""
|
||||
|
@ -13,4 +31,10 @@ def recipe_creation_factory(user: PrivateUser, name: str, additional_attrs: dict
|
|||
additional_attrs["user_id"] = user.id
|
||||
additional_attrs["group_id"] = user.group_id
|
||||
|
||||
if not additional_attrs.get("recipe_ingredient"):
|
||||
additional_attrs["recipe_ingredient"] = [RecipeIngredient(note=ingredient_note)]
|
||||
|
||||
if not additional_attrs.get("recipe_instructions"):
|
||||
additional_attrs["recipe_instructions"] = [RecipeStep(text=step_text)]
|
||||
|
||||
return Recipe(**additional_attrs)
|
||||
|
|
|
@ -13,6 +13,7 @@ from mealie.core.dependencies.grouped import PublicDeps, UserDeps
|
|||
from mealie.core.root_logger import get_logger
|
||||
from mealie.db.data_access_layer.recipe_access_model import RecipeDataAccessModel
|
||||
from mealie.schema.recipe.recipe import CreateRecipe, Recipe, RecipeSummary
|
||||
from mealie.schema.recipe.recipe_settings import RecipeSettings
|
||||
from mealie.services._base_http_service.crud_http_mixins import CrudHttpMixins
|
||||
from mealie.services._base_http_service.http_services import UserHttpService
|
||||
from mealie.services.events import create_recipe_event
|
||||
|
@ -71,8 +72,25 @@ class RecipeService(CrudHttpMixins[CreateRecipe, Recipe, Recipe], UserHttpServic
|
|||
return [RecipeSummary.construct(**x) for x in new_items]
|
||||
|
||||
def create_one(self, create_data: Union[Recipe, CreateRecipe]) -> Recipe:
|
||||
create_data = recipe_creation_factory(self.user, name=create_data.name, additional_attrs=create_data.dict())
|
||||
group = self.db.groups.get(self.group_id, "id")
|
||||
|
||||
create_data = recipe_creation_factory(
|
||||
self.user,
|
||||
name=create_data.name,
|
||||
additional_attrs=create_data.dict(),
|
||||
)
|
||||
|
||||
create_data.settings = RecipeSettings(
|
||||
public=group.preferences.recipe_public,
|
||||
show_nutrition=group.preferences.recipe_show_nutrition,
|
||||
show_assets=group.preferences.recipe_show_assets,
|
||||
landscape_view=group.preferences.recipe_landscape_view,
|
||||
disable_comments=group.preferences.recipe_disable_comments,
|
||||
disable_amount=group.preferences.recipe_disable_amount,
|
||||
)
|
||||
|
||||
self._create_one(create_data, self.t("generic.server-error"), self.exception_key)
|
||||
|
||||
self._create_event(
|
||||
"Recipe Created",
|
||||
f"'{self.item.name}' by {self.user.username} \n {self.settings.BASE_URL}/recipe/{self.item.slug}",
|
||||
|
|
72
poetry.lock
generated
72
poetry.lock
generated
|
@ -133,25 +133,30 @@ lxml = ["lxml"]
|
|||
|
||||
[[package]]
|
||||
name = "black"
|
||||
version = "20.8b1"
|
||||
version = "21.11b1"
|
||||
description = "The uncompromising code formatter."
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
python-versions = ">=3.6.2"
|
||||
|
||||
[package.dependencies]
|
||||
appdirs = "*"
|
||||
click = ">=7.1.2"
|
||||
mypy-extensions = ">=0.4.3"
|
||||
pathspec = ">=0.6,<1"
|
||||
regex = ">=2020.1.8"
|
||||
toml = ">=0.10.1"
|
||||
typed-ast = ">=1.4.0"
|
||||
typing-extensions = ">=3.7.4"
|
||||
pathspec = ">=0.9.0,<1"
|
||||
platformdirs = ">=2"
|
||||
regex = ">=2021.4.4"
|
||||
tomli = ">=0.2.6,<2.0.0"
|
||||
typing-extensions = [
|
||||
{version = ">=3.10.0.0", markers = "python_version < \"3.10\""},
|
||||
{version = "!=3.10.0.1", markers = "python_version >= \"3.10\""},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
colorama = ["colorama (>=0.4.3)"]
|
||||
d = ["aiohttp (>=3.3.2)", "aiohttp-cors"]
|
||||
d = ["aiohttp (>=3.7.4)"]
|
||||
jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"]
|
||||
python2 = ["typed-ast (>=1.4.3)"]
|
||||
uvloop = ["uvloop (>=0.15.2)"]
|
||||
|
||||
[[package]]
|
||||
name = "cachetools"
|
||||
|
@ -1259,12 +1264,12 @@ optional = false
|
|||
python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
|
||||
|
||||
[[package]]
|
||||
name = "typed-ast"
|
||||
version = "1.4.3"
|
||||
description = "a fork of Python 2 and 3 ast modules with type comment support"
|
||||
name = "tomli"
|
||||
version = "1.2.2"
|
||||
description = "A lil' TOML parser"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
python-versions = ">=3.6"
|
||||
|
||||
[[package]]
|
||||
name = "typing-extensions"
|
||||
|
@ -1418,7 +1423,7 @@ pgsql = ["psycopg2-binary"]
|
|||
[metadata]
|
||||
lock-version = "1.1"
|
||||
python-versions = "^3.9"
|
||||
content-hash = "31d3ee104998ad61b18322584c0cc84de32dbad0dc7657c9f7b7ae8214dae9c3"
|
||||
content-hash = "597bcfac6b50f5f6e203db40e05546b1a9aaf4c8438790d233424cf66fc84d19"
|
||||
|
||||
[metadata.files]
|
||||
aiofiles = [
|
||||
|
@ -1467,7 +1472,8 @@ beautifulsoup4 = [
|
|||
{file = "beautifulsoup4-4.10.0.tar.gz", hash = "sha256:c23ad23c521d818955a4151a67d81580319d4bf548d3d49f4223ae041ff98891"},
|
||||
]
|
||||
black = [
|
||||
{file = "black-20.8b1.tar.gz", hash = "sha256:1c02557aa099101b9d21496f8a914e9ed2222ef70336404eeeac8edba836fbea"},
|
||||
{file = "black-21.11b1-py3-none-any.whl", hash = "sha256:802c6c30b637b28645b7fde282ed2569c0cd777dbe493a41b6a03c1d903f99ac"},
|
||||
{file = "black-21.11b1.tar.gz", hash = "sha256:a042adbb18b3262faad5aff4e834ff186bb893f95ba3a8013f09de1e5569def2"},
|
||||
]
|
||||
cachetools = [
|
||||
{file = "cachetools-4.2.4-py3-none-any.whl", hash = "sha256:92971d3cb7d2a97efff7c7bb1657f21a8f5fb309a37530537c71b1774189f2d1"},
|
||||
|
@ -2023,6 +2029,8 @@ psycopg2-binary = [
|
|||
{file = "psycopg2_binary-2.9.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:661509f51531ec125e52357a489ea3806640d0ca37d9dada461ffc69ee1e7b6e"},
|
||||
{file = "psycopg2_binary-2.9.1-cp310-cp310-manylinux_2_24_aarch64.whl", hash = "sha256:d92272c7c16e105788efe2cfa5d680f07e34e0c29b03c1908f8636f55d5f915a"},
|
||||
{file = "psycopg2_binary-2.9.1-cp310-cp310-manylinux_2_24_ppc64le.whl", hash = "sha256:736b8797b58febabb85494142c627bd182b50d2a7ec65322983e71065ad3034c"},
|
||||
{file = "psycopg2_binary-2.9.1-cp310-cp310-win32.whl", hash = "sha256:ebccf1123e7ef66efc615a68295bf6fdba875a75d5bba10a05073202598085fc"},
|
||||
{file = "psycopg2_binary-2.9.1-cp310-cp310-win_amd64.whl", hash = "sha256:1f6ca4a9068f5c5c57e744b4baa79f40e83e3746875cac3c45467b16326bab45"},
|
||||
{file = "psycopg2_binary-2.9.1-cp36-cp36m-macosx_10_14_x86_64.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:c250a7ec489b652c892e4f0a5d122cc14c3780f9f643e1a326754aedf82d9a76"},
|
||||
{file = "psycopg2_binary-2.9.1-cp36-cp36m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aef9aee84ec78af51107181d02fe8773b100b01c5dfde351184ad9223eab3698"},
|
||||
{file = "psycopg2_binary-2.9.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:123c3fb684e9abfc47218d3784c7b4c47c8587951ea4dd5bc38b6636ac57f616"},
|
||||
|
@ -2339,37 +2347,9 @@ toml = [
|
|||
{file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"},
|
||||
{file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"},
|
||||
]
|
||||
typed-ast = [
|
||||
{file = "typed_ast-1.4.3-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:2068531575a125b87a41802130fa7e29f26c09a2833fea68d9a40cf33902eba6"},
|
||||
{file = "typed_ast-1.4.3-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:c907f561b1e83e93fad565bac5ba9c22d96a54e7ea0267c708bffe863cbe4075"},
|
||||
{file = "typed_ast-1.4.3-cp35-cp35m-manylinux2014_aarch64.whl", hash = "sha256:1b3ead4a96c9101bef08f9f7d1217c096f31667617b58de957f690c92378b528"},
|
||||
{file = "typed_ast-1.4.3-cp35-cp35m-win32.whl", hash = "sha256:dde816ca9dac1d9c01dd504ea5967821606f02e510438120091b84e852367428"},
|
||||
{file = "typed_ast-1.4.3-cp35-cp35m-win_amd64.whl", hash = "sha256:777a26c84bea6cd934422ac2e3b78863a37017618b6e5c08f92ef69853e765d3"},
|
||||
{file = "typed_ast-1.4.3-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:f8afcf15cc511ada719a88e013cec87c11aff7b91f019295eb4530f96fe5ef2f"},
|
||||
{file = "typed_ast-1.4.3-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:52b1eb8c83f178ab787f3a4283f68258525f8d70f778a2f6dd54d3b5e5fb4341"},
|
||||
{file = "typed_ast-1.4.3-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:01ae5f73431d21eead5015997ab41afa53aa1fbe252f9da060be5dad2c730ace"},
|
||||
{file = "typed_ast-1.4.3-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:c190f0899e9f9f8b6b7863debfb739abcb21a5c054f911ca3596d12b8a4c4c7f"},
|
||||
{file = "typed_ast-1.4.3-cp36-cp36m-win32.whl", hash = "sha256:398e44cd480f4d2b7ee8d98385ca104e35c81525dd98c519acff1b79bdaac363"},
|
||||
{file = "typed_ast-1.4.3-cp36-cp36m-win_amd64.whl", hash = "sha256:bff6ad71c81b3bba8fa35f0f1921fb24ff4476235a6e94a26ada2e54370e6da7"},
|
||||
{file = "typed_ast-1.4.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0fb71b8c643187d7492c1f8352f2c15b4c4af3f6338f21681d3681b3dc31a266"},
|
||||
{file = "typed_ast-1.4.3-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:760ad187b1041a154f0e4d0f6aae3e40fdb51d6de16e5c99aedadd9246450e9e"},
|
||||
{file = "typed_ast-1.4.3-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:5feca99c17af94057417d744607b82dd0a664fd5e4ca98061480fd8b14b18d04"},
|
||||
{file = "typed_ast-1.4.3-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:95431a26309a21874005845c21118c83991c63ea800dd44843e42a916aec5899"},
|
||||
{file = "typed_ast-1.4.3-cp37-cp37m-win32.whl", hash = "sha256:aee0c1256be6c07bd3e1263ff920c325b59849dc95392a05f258bb9b259cf39c"},
|
||||
{file = "typed_ast-1.4.3-cp37-cp37m-win_amd64.whl", hash = "sha256:9ad2c92ec681e02baf81fdfa056fe0d818645efa9af1f1cd5fd6f1bd2bdfd805"},
|
||||
{file = "typed_ast-1.4.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b36b4f3920103a25e1d5d024d155c504080959582b928e91cb608a65c3a49e1a"},
|
||||
{file = "typed_ast-1.4.3-cp38-cp38-manylinux1_i686.whl", hash = "sha256:067a74454df670dcaa4e59349a2e5c81e567d8d65458d480a5b3dfecec08c5ff"},
|
||||
{file = "typed_ast-1.4.3-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:7538e495704e2ccda9b234b82423a4038f324f3a10c43bc088a1636180f11a41"},
|
||||
{file = "typed_ast-1.4.3-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:af3d4a73793725138d6b334d9d247ce7e5f084d96284ed23f22ee626a7b88e39"},
|
||||
{file = "typed_ast-1.4.3-cp38-cp38-win32.whl", hash = "sha256:f2362f3cb0f3172c42938946dbc5b7843c2a28aec307c49100c8b38764eb6927"},
|
||||
{file = "typed_ast-1.4.3-cp38-cp38-win_amd64.whl", hash = "sha256:dd4a21253f42b8d2b48410cb31fe501d32f8b9fbeb1f55063ad102fe9c425e40"},
|
||||
{file = "typed_ast-1.4.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f328adcfebed9f11301eaedfa48e15bdece9b519fb27e6a8c01aa52a17ec31b3"},
|
||||
{file = "typed_ast-1.4.3-cp39-cp39-manylinux1_i686.whl", hash = "sha256:2c726c276d09fc5c414693a2de063f521052d9ea7c240ce553316f70656c84d4"},
|
||||
{file = "typed_ast-1.4.3-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:cae53c389825d3b46fb37538441f75d6aecc4174f615d048321b716df2757fb0"},
|
||||
{file = "typed_ast-1.4.3-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:b9574c6f03f685070d859e75c7f9eeca02d6933273b5e69572e5ff9d5e3931c3"},
|
||||
{file = "typed_ast-1.4.3-cp39-cp39-win32.whl", hash = "sha256:209596a4ec71d990d71d5e0d312ac935d86930e6eecff6ccc7007fe54d703808"},
|
||||
{file = "typed_ast-1.4.3-cp39-cp39-win_amd64.whl", hash = "sha256:9c6d1a54552b5330bc657b7ef0eae25d00ba7ffe85d9ea8ae6540d2197a3788c"},
|
||||
{file = "typed_ast-1.4.3.tar.gz", hash = "sha256:fb1bbeac803adea29cedd70781399c99138358c26d05fcbd23c13016b7f5ec65"},
|
||||
tomli = [
|
||||
{file = "tomli-1.2.2-py3-none-any.whl", hash = "sha256:f04066f68f5554911363063a30b108d2b5a5b1a010aa8b6132af78489fe3aade"},
|
||||
{file = "tomli-1.2.2.tar.gz", hash = "sha256:c6ce0015eb38820eaf32b5db832dbc26deb3dd427bd5f6556cf0acac2c214fee"},
|
||||
]
|
||||
typing-extensions = [
|
||||
{file = "typing_extensions-3.10.0.2-py2-none-any.whl", hash = "sha256:d8226d10bc02a29bcc81df19a26e56a9647f8b0a6d4a83924139f4a8b01f17b7"},
|
||||
|
|
|
@ -40,7 +40,6 @@ python-i18n = "^0.3.9"
|
|||
|
||||
[tool.poetry.dev-dependencies]
|
||||
pylint = "^2.6.0"
|
||||
black = "^20.8b1"
|
||||
pytest = "^6.2.1"
|
||||
pytest-cov = "^2.11.0"
|
||||
mkdocs-material = "^7.0.2"
|
||||
|
@ -51,6 +50,7 @@ rich = "^10.7.0"
|
|||
isort = "^5.9.3"
|
||||
regex = "2021.9.30" # TODO: Remove during Upgrade -> https://github.com/psf/black/issues/2524
|
||||
flake8-print = "^4.0.0"
|
||||
black = "^21.11b1"
|
||||
|
||||
[build-system]
|
||||
requires = ["poetry-core>=1.0.0"]
|
||||
|
|
Loading…
Reference in a new issue