add to book reimplemented
This commit is contained in:
@ -319,6 +319,12 @@ class RecipeBookEntry(models.Model):
|
||||
def __str__(self):
|
||||
return self.recipe.name
|
||||
|
||||
def get_owner(self):
|
||||
try:
|
||||
return self.book.created_by
|
||||
except AttributeError:
|
||||
return None
|
||||
|
||||
class Meta:
|
||||
unique_together = (('recipe', 'book'),)
|
||||
|
||||
|
@ -90,6 +90,20 @@ class SyncLogSerializer(serializers.ModelSerializer):
|
||||
fields = ('id', 'sync', 'status', 'msg', 'created_at')
|
||||
|
||||
|
||||
class KeywordLabelSerializer(serializers.ModelSerializer):
|
||||
label = serializers.SerializerMethodField('get_label')
|
||||
|
||||
def get_label(self, obj):
|
||||
return str(obj)
|
||||
|
||||
class Meta:
|
||||
model = Keyword
|
||||
fields = (
|
||||
'id', 'label',
|
||||
)
|
||||
read_only_fields = ('id', 'label')
|
||||
|
||||
|
||||
class KeywordSerializer(UniqueFieldsMixin, serializers.ModelSerializer):
|
||||
label = serializers.SerializerMethodField('get_label')
|
||||
|
||||
@ -181,6 +195,19 @@ class NutritionInformationSerializer(serializers.ModelSerializer):
|
||||
fields = ('carbohydrates', 'fats', 'proteins', 'calories', 'source')
|
||||
|
||||
|
||||
class RecipeOverviewSerializer(WritableNestedModelSerializer):
|
||||
keywords = KeywordLabelSerializer(many=True)
|
||||
|
||||
class Meta:
|
||||
model = Recipe
|
||||
fields = (
|
||||
'id', 'name', 'description', 'image', 'keywords', 'working_time',
|
||||
'waiting_time', 'created_by', 'created_at', 'updated_at',
|
||||
'internal', 'servings', 'file_path'
|
||||
)
|
||||
read_only_fields = ['image', 'created_by', 'created_at']
|
||||
|
||||
|
||||
class RecipeSerializer(WritableNestedModelSerializer):
|
||||
nutrition = NutritionInformationSerializer(allow_null=True, required=False)
|
||||
steps = StepSerializer(many=True)
|
||||
|
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
@ -32,6 +32,8 @@ router.register(r'shopping-list-entry', api.ShoppingListEntryViewSet)
|
||||
router.register(r'shopping-list-recipe', api.ShoppingListRecipeViewSet)
|
||||
router.register(r'view-log', api.ViewLogViewSet)
|
||||
router.register(r'cook-log', api.CookLogViewSet)
|
||||
router.register(r'recipe-book', api.RecipeBookViewSet)
|
||||
router.register(r'recipe-book-entry', api.RecipeBookEntryViewSet)
|
||||
|
||||
urlpatterns = [
|
||||
path('', views.index, name='index'),
|
||||
|
@ -21,7 +21,7 @@ from PIL import Image
|
||||
from rest_framework import decorators, permissions, viewsets
|
||||
from rest_framework.exceptions import APIException
|
||||
from rest_framework.mixins import (ListModelMixin, RetrieveModelMixin,
|
||||
UpdateModelMixin)
|
||||
UpdateModelMixin, CreateModelMixin)
|
||||
from rest_framework.parsers import MultiPartParser
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.viewsets import ViewSetMixin
|
||||
@ -35,7 +35,7 @@ from cookbook.models import (CookLog, Food, Ingredient, Keyword, MealPlan,
|
||||
MealType, Recipe, RecipeBook, ShoppingList,
|
||||
ShoppingListEntry, ShoppingListRecipe, Step,
|
||||
Storage, Sync, SyncLog, Unit, UserPreference,
|
||||
ViewLog)
|
||||
ViewLog, RecipeBookEntry)
|
||||
from cookbook.provider.dropbox import Dropbox
|
||||
from cookbook.provider.nextcloud import Nextcloud
|
||||
from cookbook.serializer import (FoodSerializer, IngredientSerializer,
|
||||
@ -49,7 +49,7 @@ from cookbook.serializer import (FoodSerializer, IngredientSerializer,
|
||||
StorageSerializer, SyncLogSerializer,
|
||||
SyncSerializer, UnitSerializer,
|
||||
UserNameSerializer, UserPreferenceSerializer,
|
||||
ViewLogSerializer, CookLogSerializer)
|
||||
ViewLogSerializer, CookLogSerializer, RecipeBookEntrySerializer, RecipeOverviewSerializer)
|
||||
|
||||
|
||||
class UserNameViewSet(viewsets.ReadOnlyModelViewSet):
|
||||
@ -121,6 +121,13 @@ class StandardFilterMixin(ViewSetMixin):
|
||||
if query is not None:
|
||||
queryset = queryset.filter(name__icontains=query)
|
||||
|
||||
updated_at = self.request.query_params.get('updated_at', None)
|
||||
if updated_at is not None:
|
||||
try:
|
||||
queryset = queryset.filter(updated_at__gte=updated_at)
|
||||
except FieldError:
|
||||
pass
|
||||
|
||||
limit = self.request.query_params.get('limit', None)
|
||||
random = self.request.query_params.get('random', False)
|
||||
if limit is not None:
|
||||
@ -157,16 +164,23 @@ class FoodViewSet(viewsets.ModelViewSet, StandardFilterMixin):
|
||||
permission_classes = [CustomIsUser]
|
||||
|
||||
|
||||
class RecipeBookViewSet(
|
||||
RetrieveModelMixin,
|
||||
UpdateModelMixin,
|
||||
ListModelMixin,
|
||||
viewsets.GenericViewSet
|
||||
):
|
||||
class RecipeBookViewSet(viewsets.ModelViewSet, StandardFilterMixin):
|
||||
queryset = RecipeBook.objects.all()
|
||||
serializer_class = RecipeBookSerializer
|
||||
permission_classes = [CustomIsOwner, CustomIsAdmin]
|
||||
|
||||
def get_queryset(self):
|
||||
self.queryset = super(RecipeBookViewSet, self).get_queryset()
|
||||
if self.request.user.is_superuser:
|
||||
return self.queryset
|
||||
return self.queryset.filter(created_by=self.request.user)
|
||||
|
||||
|
||||
class RecipeBookEntryViewSet(viewsets.ModelViewSet, viewsets.GenericViewSet):
|
||||
queryset = RecipeBookEntry.objects.all()
|
||||
serializer_class = RecipeBookEntrySerializer
|
||||
permission_classes = [CustomIsOwner, CustomIsAdmin]
|
||||
|
||||
def get_queryset(self):
|
||||
if self.request.user.is_superuser:
|
||||
return self.queryset
|
||||
@ -253,6 +267,11 @@ class RecipeViewSet(viewsets.ModelViewSet, StandardFilterMixin):
|
||||
|
||||
# TODO write extensive tests for permissions
|
||||
|
||||
def get_serializer_class(self):
|
||||
if self.action == 'list':
|
||||
return RecipeOverviewSerializer
|
||||
return self.serializer_class
|
||||
|
||||
@decorators.action(
|
||||
detail=True,
|
||||
methods=['PUT'],
|
||||
@ -338,6 +357,7 @@ class CookLogViewSet(viewsets.ModelViewSet):
|
||||
queryset = ViewLog.objects.filter(created_by=self.request.user).all()[:5]
|
||||
return queryset
|
||||
|
||||
|
||||
# -------------- non django rest api views --------------------
|
||||
|
||||
def get_recipe_provider(recipe):
|
||||
|
@ -13,6 +13,7 @@
|
||||
"core-js": "^3.6.5",
|
||||
"moment": "^2.29.1",
|
||||
"vue": "^2.6.11",
|
||||
"vue-multiselect": "^2.1.6",
|
||||
"vue-template-compiler": "^2.6.12",
|
||||
"vuex": "^3.6.0"
|
||||
},
|
||||
|
@ -70,9 +70,8 @@
|
||||
</div>
|
||||
<hr/>
|
||||
|
||||
<div class="row" style="margin-top: 2vh">
|
||||
<div class="col-md-6 order-md-1 col-sm-12 order-sm-2 col-12 order-2" v-if="recipe && ingredient_count > 0"
|
||||
style="margin-top: 2vh">
|
||||
<div class="row">
|
||||
<div class="col-md-6 order-md-1 col-sm-12 order-sm-2 col-12 order-2" v-if="recipe && ingredient_count > 0">
|
||||
<div class="card border-primary">
|
||||
<div class="card-body">
|
||||
<div class="row">
|
||||
@ -108,7 +107,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row" style="margin-top: 2vh">
|
||||
<div class="row" style="margin-top: 2vh; margin-bottom: 2vh">
|
||||
<div class="col-12">
|
||||
<Nutrition :recipe="recipe" :ingredient_factor="ingredient_factor"></Nutrition>
|
||||
</div>
|
||||
@ -134,6 +133,8 @@
|
||||
@update-start-time="updateStartTime" @checked-state-changed="updateIngredientCheckedState"></Step>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<add-recipe-to-book :recipe="recipe"></add-recipe-to-book>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -157,6 +158,7 @@ import Nutrition from "@/components/Nutrition";
|
||||
import moment from 'moment'
|
||||
import Keywords from "@/components/Keywords";
|
||||
import LoadingSpinner from "@/components/LoadingSpinner";
|
||||
import AddRecipeToBook from "@/components/AddRecipeToBook";
|
||||
|
||||
Vue.prototype.moment = moment
|
||||
|
||||
@ -178,6 +180,7 @@ export default {
|
||||
Nutrition,
|
||||
Keywords,
|
||||
LoadingSpinner,
|
||||
AddRecipeToBook,
|
||||
},
|
||||
computed: {
|
||||
ingredient_factor: function () {
|
||||
|
73
vue/src/components/AddRecipeToBook.vue
Normal file
73
vue/src/components/AddRecipeToBook.vue
Normal file
@ -0,0 +1,73 @@
|
||||
<template>
|
||||
|
||||
<div>
|
||||
<b-modal class="modal" id="id_modal_add_book" :title="_('Add to Book')" :ok-title="_('Add')"
|
||||
:cancel-title="_('Close')" @ok="addToBook()">
|
||||
|
||||
<multiselect
|
||||
v-model="selected_book"
|
||||
:options="books"
|
||||
|
||||
:preserve-search="true"
|
||||
:placeholder="_('Select Book')"
|
||||
label="name"
|
||||
track-by="id"
|
||||
id="id_books"
|
||||
:multiple="false"
|
||||
|
||||
@search-change="loadBook">
|
||||
</multiselect>
|
||||
</b-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
import {GettextMixin} from "@/utils/utils";
|
||||
import Multiselect from 'vue-multiselect'
|
||||
|
||||
import moment from 'moment'
|
||||
|
||||
Vue.prototype.moment = moment
|
||||
|
||||
import Vue from "vue";
|
||||
import {BootstrapVue} from "bootstrap-vue";
|
||||
import {apiAddRecipeBookEntry, apiLoadCookBooks, apiLogCooking} from "@/utils/api";
|
||||
|
||||
Vue.use(BootstrapVue)
|
||||
|
||||
export default {
|
||||
name: 'AddRecipeToBook',
|
||||
mixins: [
|
||||
GettextMixin,
|
||||
],
|
||||
components: {
|
||||
Multiselect
|
||||
},
|
||||
props: {
|
||||
recipe: Object,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
books: [],
|
||||
selected_book: null,
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.loadBook('')
|
||||
},
|
||||
methods: {
|
||||
loadBook: function (query) {
|
||||
apiLoadCookBooks(query).then(results => {
|
||||
console.log(results)
|
||||
this.books = results
|
||||
})
|
||||
},
|
||||
addToBook() {
|
||||
apiAddRecipeBookEntry({'recipe': this.recipe.id, 'book': this.selected_book.id})
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style src="vue-multiselect/dist/vue-multiselect.min.css"></style>
|
@ -15,7 +15,7 @@
|
||||
<a class="dropdown-item" :href="resolveDjangoUrl('edit_convert_recipe', recipe.id)" v-if="!recipe.internal"><i
|
||||
class="fas fa-exchange-alt fa-fw"></i> {{ _('Convert to internal recipe') }}</a>
|
||||
|
||||
<button class="dropdown-item" onclick="$('#bookmarkModal').modal({'show':true})">
|
||||
<button class="dropdown-item" @click="$bvModal.show('id_modal_add_book')">
|
||||
<i class="fas fa-bookmark fa-fw"></i> {{ _('Add to Book') }}
|
||||
</button>
|
||||
|
||||
|
@ -154,7 +154,9 @@ export default {
|
||||
return calculateAmount(x, this.ingredient_factor)
|
||||
},
|
||||
updateTime: function () {
|
||||
this.$emit('update-start-time', moment(this.set_time_input).add(this.time_offset * -1, 'minutes').format('yyyy-MM-DDTHH:mm'))
|
||||
let new_start_time = moment(this.set_time_input).add(this.step.time_offset * -1, 'minutes').format('yyyy-MM-DDTHH:mm')
|
||||
|
||||
this.$emit('update-start-time', new_start_time)
|
||||
this.closePopover()
|
||||
},
|
||||
closePopover: function () {
|
||||
|
@ -28,6 +28,25 @@ export function apiLogCooking(cook_log) {
|
||||
})
|
||||
}
|
||||
|
||||
export function apiLoadCookBooks(query) {
|
||||
return axios.get(resolveDjangoUrl('api:recipebook-list') + '?query=' + query).then((response) => {
|
||||
console.log(response)
|
||||
return response.data
|
||||
}).catch((err) => {
|
||||
handleError(err, 'There was an error creating a resource!', 'danger')
|
||||
})
|
||||
}
|
||||
|
||||
export function apiAddRecipeBookEntry(entry) {
|
||||
return axios.post(resolveDjangoUrl('api:recipebookentry-list',), entry).then((response) => {
|
||||
console.log(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)
|
||||
|
@ -8266,6 +8266,11 @@ vue-loader@^15.9.2:
|
||||
vue-hot-reload-api "^2.3.0"
|
||||
vue-style-loader "^4.1.0"
|
||||
|
||||
vue-multiselect@^2.1.6:
|
||||
version "2.1.6"
|
||||
resolved "https://registry.yarnpkg.com/vue-multiselect/-/vue-multiselect-2.1.6.tgz#5be5d811a224804a15c43a4edbb7485028a89c7f"
|
||||
integrity sha512-s7jmZPlm9FeueJg1RwJtnE9KNPtME/7C8uRWSfp9/yEN4M8XcS/d+bddoyVwVnvFyRh9msFo0HWeW0vTL8Qv+w==
|
||||
|
||||
vue-style-loader@^4.1.0, vue-style-loader@^4.1.2:
|
||||
version "4.1.2"
|
||||
resolved "https://registry.npm.taobao.org/vue-style-loader/download/vue-style-loader-4.1.2.tgz#dedf349806f25ceb4e64f3ad7c0a44fba735fcf8"
|
||||
|
Reference in New Issue
Block a user