recipe book context menu

This commit is contained in:
vabene1111
2021-07-29 16:32:15 +02:00
parent 45454eb27b
commit 8642298eda
14 changed files with 157 additions and 47 deletions

View File

@@ -435,6 +435,14 @@ class RecipeBookSerializer(SpacedModelSerializer):
class RecipeBookEntrySerializer(serializers.ModelSerializer): class RecipeBookEntrySerializer(serializers.ModelSerializer):
book_content = serializers.SerializerMethodField(method_name='get_book_content', read_only=True)
recipe_content = serializers.SerializerMethodField(method_name='get_recipe_content', read_only=True)
def get_book_content(self, obj):
return RecipeBookSerializer(context={'request': self.context['request']}).to_representation(obj.book)
def get_recipe_content(self, obj):
return RecipeOverviewSerializer(context={'request': self.context['request']}).to_representation(obj.recipe)
def create(self, validated_data): def create(self, validated_data):
book = validated_data['book'] book = validated_data['book']
@@ -444,7 +452,7 @@ class RecipeBookEntrySerializer(serializers.ModelSerializer):
class Meta: class Meta:
model = RecipeBookEntry model = RecipeBookEntry
fields = ('id', 'book', 'recipe',) fields = ('id', 'book', 'book_content', 'recipe', 'recipe_content',)
class MealPlanSerializer(SpacedModelSerializer, WritableNestedModelSerializer): class MealPlanSerializer(SpacedModelSerializer, WritableNestedModelSerializer):

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -228,12 +228,30 @@ class RecipeBookViewSet(viewsets.ModelViewSet, StandardFilterMixin):
class RecipeBookEntryViewSet(viewsets.ModelViewSet, viewsets.GenericViewSet): class RecipeBookEntryViewSet(viewsets.ModelViewSet, viewsets.GenericViewSet):
"""
list:
optional parameters
- **recipe**: id of recipe - only return books for that recipe
- **book**: id of book - only return recipes in that book
"""
queryset = RecipeBookEntry.objects queryset = RecipeBookEntry.objects
serializer_class = RecipeBookEntrySerializer serializer_class = RecipeBookEntrySerializer
permission_classes = [CustomIsOwner] permission_classes = [CustomIsOwner]
def get_queryset(self): def get_queryset(self):
return self.queryset.filter(book__created_by=self.request.user).filter(book__space=self.request.space) queryset = self.queryset.filter(Q(book__created_by=self.request.user) | Q(book__shared=self.request.user)).filter(book__space=self.request.space)
recipe_id = self.request.query_params.get('recipe', None)
if recipe_id is not None:
queryset = queryset.filter(recipe__pk=recipe_id)
book_id = self.request.query_params.get('book', None)
if book_id is not None:
queryset = queryset.filter(book__pk=book_id)
return queryset
class MealPlanViewSet(viewsets.ModelViewSet): class MealPlanViewSet(viewsets.ModelViewSet):

View File

@@ -1,21 +1,32 @@
<template> <template>
<div> <div>
<b-modal class="modal" :id="`id_modal_add_book_${modal_id}`" :title="$t('Add_to_Book')" :ok-title="$t('Add')" <b-modal class="modal" :id="`id_modal_add_book_${modal_id}`" :title="$t('Manage_Books')" :ok-title="$t('Add')"
:cancel-title="$t('Close')" @ok="addToBook()"> :cancel-title="$t('Close')" @ok="addToBook()" @shown="loadBookEntries">
<table>
<tr v-for="be in this.recipe_book_list" v-bind:key="be.id">
<td>
<button class="btn btn-sm btn-danger" @click="removeFromBook(be)"><i class="fa fa-trash-alt"></i></button>
</td>
<td> {{ be.book_content.name }}</td>
</tr>
</table>
<multiselect <multiselect
style="margin-top: 1vh"
v-model="selected_book" v-model="selected_book"
:options="books" :options="books_filtered"
:taggable="true"
:preserve-search="true" @tag="createBook"
v-bind:tag-placeholder="$t('Create')"
:placeholder="$t('Select_Book')" :placeholder="$t('Select_Book')"
label="name" label="name"
track-by="id" track-by="id"
id="id_books" id="id_books"
:multiple="false" :multiple="false"
:loading="books_loading"
@search-change="loadBook"> @search-change="loadBooks">
</multiselect> </multiselect>
</b-modal> </b-modal>
</div> </div>
@@ -31,7 +42,8 @@ Vue.prototype.moment = moment
import Vue from "vue"; import Vue from "vue";
import {BootstrapVue} from "bootstrap-vue"; import {BootstrapVue} from "bootstrap-vue";
import {apiAddRecipeBookEntry, apiLoadCookBooks, apiLogCooking} from "@/utils/api"; import {ApiApiFactory} from "@/utils/openapi/api";
import {makeStandardToast, StandardToasts} from "@/utils/utils";
Vue.use(BootstrapVue) Vue.use(BootstrapVue)
@@ -47,21 +59,66 @@ export default {
data() { data() {
return { return {
books: [], books: [],
books_loading: false,
recipe_book_list: [],
selected_book: null, selected_book: null,
} }
}, },
computed: {
books_filtered: function () {
let books_filtered = []
this.books.forEach(b => {
if (this.recipe_book_list.filter(e => e.book === b.id).length === 0) {
books_filtered.push(b)
}
})
return books_filtered
}
},
mounted() { mounted() {
this.loadBook('')
}, },
methods: { methods: {
loadBook: function (query) { loadBooks: function (query) {
apiLoadCookBooks(query).then(results => { this.books_loading = true
this.books = results let apiFactory = new ApiApiFactory()
apiFactory.listRecipeBooks({query: {query: query}}).then(results => {
this.books = results.data.filter(e => this.recipe_book_list.indexOf(e) === -1)
this.books_loading = false
}) })
}, },
addToBook() { createBook: function (name) {
apiAddRecipeBookEntry({'recipe': this.recipe.id, 'book': this.selected_book.id}) let apiFactory = new ApiApiFactory()
apiFactory.createRecipeBook({name: name}).then(r => {
this.books.push(r.data)
this.selected_book = r.data
StandardToasts.makeStandardToast(StandardToasts.SUCCESS_CREATE)
})
}, },
addToBook: function () {
let apiFactory = new ApiApiFactory()
apiFactory.createRecipeBookEntry({book: this.selected_book.id, recipe: this.recipe.id}).then(r => {
this.recipe_book_list.push(r.data)
StandardToasts.makeStandardToast(StandardToasts.SUCCESS_CREATE)
})
},
removeFromBook: function (book_entry) {
let apiFactory = new ApiApiFactory()
apiFactory.destroyRecipeBookEntry(book_entry.id).then(r => {
this.recipe_book_list = this.recipe_book_list.filter(e => e.id !== book_entry.id)
StandardToasts.makeStandardToast(StandardToasts.SUCCESS_DELETE)
})
},
loadBookEntries: function () {
let apiFactory = new ApiApiFactory()
apiFactory.listRecipeBookEntrys({query: {recipe: this.recipe.id}}).then(r => {
this.recipe_book_list = r.data
this.loadBooks('')
})
}
} }
} }
</script> </script>

View File

@@ -17,7 +17,7 @@
<a href="#"> <a href="#">
<button class="dropdown-item" @click="$bvModal.show(`id_modal_add_book_${modal_id}`)"> <button class="dropdown-item" @click="$bvModal.show(`id_modal_add_book_${modal_id}`)">
<i class="fas fa-bookmark fa-fw"></i> {{ $t('Add_to_Book') }} <i class="fas fa-bookmark fa-fw"></i> {{ $t('Manage_Books') }}
</button> </button>
</a> </a>

View File

@@ -13,17 +13,15 @@
"convert_internal": "Convert to internal recipe", "convert_internal": "Convert to internal recipe",
"show_only_internal": "Show only internal recipes", "show_only_internal": "Show only internal recipes",
"Log_Recipe_Cooking": "Log Recipe Cooking", "Log_Recipe_Cooking": "Log Recipe Cooking",
"External_Recipe_Image": "External Recipe Image", "External_Recipe_Image": "External Recipe Image",
"Add_to_Book": "Add to Book",
"Add_to_Shopping": "Add to Shopping", "Add_to_Shopping": "Add to Shopping",
"Add_to_Plan": "Add to Plan", "Add_to_Plan": "Add to Plan",
"Step_start_time": "Step start time", "Step_start_time": "Step start time",
"Sort_by_new": "Sort by new", "Sort_by_new": "Sort by new",
"Recipes_per_page": "Recipes per Page", "Recipes_per_page": "Recipes per Page",
"Manage_Books": "Manage Books",
"Meal_Plan": "Meal Plan", "Meal_Plan": "Meal Plan",
"Select_Book": "Select Book", "Select_Book": "Select Book",
"Recipe_Image": "Recipe Image", "Recipe_Image": "Recipe Image",
@@ -53,6 +51,7 @@
"Add": "Add", "Add": "Add",
"New": "New", "New": "New",
"Success": "Success", "Success": "Success",
"Failure": "Failure",
"Ingredients": "Ingredients", "Ingredients": "Ingredients",
"Supermarket": "Supermarket", "Supermarket": "Supermarket",
"Categories": "Categories", "Categories": "Categories",
@@ -80,5 +79,6 @@
"or": "or", "or": "or",
"and": "and", "and": "and",
"Information": "Information", "Information": "Information",
"Download": "Download" "Download": "Download",
"Create": "Create"
} }

View File

@@ -37,23 +37,6 @@ export function apiLogCooking(cook_log) {
}) })
} }
export function apiLoadCookBooks(query) {
return axios.get(resolveDjangoUrl('api:recipebook-list') + '?query=' + query).then((response) => {
return response.data
}).catch((err) => {
//handleError(err, 'There was an error loading a resource!', 'danger')
})
}
export function apiAddRecipeBookEntry(entry) {
return axios.post(resolveDjangoUrl('api:recipebookentry-list',), entry).then((response) => {
makeToast('Saved', 'Recipe Book entry saved!', 'success')
}).catch((err) => {
handleError(err, 'There was an error creating a resource!', 'danger')
})
}
function handleError(error, message) { function handleError(error, message) {
if ('response' in error) { if ('response' in error) {
console.log(error.response) console.log(error.response)

View File

@@ -2,6 +2,7 @@
* Utility functions to call bootstrap toasts * Utility functions to call bootstrap toasts
* */ * */
import {BToast} from 'bootstrap-vue' import {BToast} from 'bootstrap-vue'
import i18n from "@/i18n";
export const ToastMixin = { export const ToastMixin = {
methods: { methods: {
@@ -21,6 +22,49 @@ export function makeToast(title, message, variant = null) {
}) })
} }
export class StandardToasts {
static SUCCESS_CREATE = 'SUCCESS_CREATE'
static SUCCESS_FETCH = 'SUCCESS_FETCH'
static SUCCESS_UPDATE = 'SUCCESS_UPDATE'
static SUCCESS_DELETE = 'SUCCESS_DELETE'
static FAIL_CREATE = 'FAIL_CREATE'
static FAIL_FETCH = 'FAIL_FETCH'
static FAIL_UPDATE = 'FAIL_UPDATE'
static FAIL_DELETE = 'FAIL_DELETE'
static makeStandardToast(toast) {
switch (toast) {
case StandardToasts.SUCCESS_CREATE:
makeToast(i18n.tc('Success'), i18n.tc('success_creating_resource'), 'success')
break;
case StandardToasts.SUCCESS_FETCH:
makeToast(i18n.tc('Success'), i18n.tc('success_fetching_resource'), 'success')
break;
case StandardToasts.SUCCESS_UPDATE:
makeToast(i18n.tc('Success'), i18n.tc('success_updating_resource'), 'success')
break;
case StandardToasts.SUCCESS_DELETE:
makeToast(i18n.tc('Success'), i18n.tc('success_deleting_resource'), 'success')
break;
case StandardToasts.FAIL_CREATE:
makeToast(i18n.tc('Failure'), i18n.tc('success_creating_resource'), 'danger')
break;
case StandardToasts.FAIL_FETCH:
makeToast(i18n.tc('Failure'), i18n.tc('err_fetching_resource'), 'danger')
break;
case StandardToasts.FAIL_UPDATE:
makeToast(i18n.tc('Failure'), i18n.tc('err_updating_resource'), 'danger')
break;
case StandardToasts.FAIL_DELETE:
makeToast(i18n.tc('Failure'), i18n.tc('err_deleting_resource'), 'danger')
break;
}
}
}
/* /*
* Utility functions to use djangos gettext * Utility functions to use djangos gettext
* */ * */