recipe book context menu
This commit is contained in:
parent
45454eb27b
commit
8642298eda
@ -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
@ -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):
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
||||
|
@ -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"
|
||||
}
|
@ -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)
|
||||
|
@ -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
|
||||
* */
|
||||
|
Loading…
Reference in New Issue
Block a user