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):
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):
book = validated_data['book']
@ -444,7 +452,7 @@ class RecipeBookEntrySerializer(serializers.ModelSerializer):
class Meta:
model = RecipeBookEntry
fields = ('id', 'book', 'recipe',)
fields = ('id', 'book', 'book_content', 'recipe', 'recipe_content',)
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):
"""
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
serializer_class = RecipeBookEntrySerializer
permission_classes = [CustomIsOwner]
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):

View File

@ -1,21 +1,32 @@
<template>
<div>
<b-modal class="modal" :id="`id_modal_add_book_${modal_id}`" :title="$t('Add_to_Book')" :ok-title="$t('Add')"
:cancel-title="$t('Close')" @ok="addToBook()">
<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()" @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
style="margin-top: 1vh"
v-model="selected_book"
:options="books"
:preserve-search="true"
:options="books_filtered"
:taggable="true"
@tag="createBook"
v-bind:tag-placeholder="$t('Create')"
:placeholder="$t('Select_Book')"
label="name"
track-by="id"
id="id_books"
:multiple="false"
@search-change="loadBook">
:loading="books_loading"
@search-change="loadBooks">
</multiselect>
</b-modal>
</div>
@ -31,7 +42,8 @@ Vue.prototype.moment = moment
import Vue from "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)
@ -47,21 +59,66 @@ export default {
data() {
return {
books: [],
books_loading: false,
recipe_book_list: [],
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() {
this.loadBook('')
},
methods: {
loadBook: function (query) {
apiLoadCookBooks(query).then(results => {
this.books = results
loadBooks: function (query) {
this.books_loading = true
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() {
apiAddRecipeBookEntry({'recipe': this.recipe.id, 'book': this.selected_book.id})
createBook: function (name) {
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>

View File

@ -17,7 +17,7 @@
<a href="#">
<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>
</a>

View File

@ -13,17 +13,15 @@
"convert_internal": "Convert to internal recipe",
"show_only_internal": "Show only internal recipes",
"Log_Recipe_Cooking": "Log Recipe Cooking",
"External_Recipe_Image": "External Recipe Image",
"Add_to_Book": "Add to Book",
"Add_to_Shopping": "Add to Shopping",
"Add_to_Plan": "Add to Plan",
"Step_start_time": "Step start time",
"Sort_by_new": "Sort by new",
"Recipes_per_page": "Recipes per Page",
"Manage_Books": "Manage Books",
"Meal_Plan": "Meal Plan",
"Select_Book": "Select Book",
"Recipe_Image": "Recipe Image",
@ -53,6 +51,7 @@
"Add": "Add",
"New": "New",
"Success": "Success",
"Failure": "Failure",
"Ingredients": "Ingredients",
"Supermarket": "Supermarket",
"Categories": "Categories",
@ -80,5 +79,6 @@
"or": "or",
"and": "and",
"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) {
if ('response' in error) {
console.log(error.response)

View File

@ -2,6 +2,7 @@
* Utility functions to call bootstrap toasts
* */
import {BToast} from 'bootstrap-vue'
import i18n from "@/i18n";
export const ToastMixin = {
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
* */