food layout complete, edit food working
This commit is contained in:
parent
b56c2429c7
commit
d5e9c5d8f8
@ -23,7 +23,7 @@ def backwards(apps, schema_editor):
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('cookbook', '0148_auto_20210813_1829'),
|
||||
('cookbook', '0148_food_to_tree'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
|
@ -1,9 +1,11 @@
|
||||
import json
|
||||
import random
|
||||
from datetime import timedelta
|
||||
from decimal import Decimal
|
||||
from gettext import gettext as _
|
||||
from django.contrib.auth.models import User
|
||||
from django.db.models import Avg, QuerySet, Sum
|
||||
from django.db.models import Avg, Manager, QuerySet, Sum
|
||||
from django.urls import reverse
|
||||
from drf_writable_nested import (UniqueFieldsMixin,
|
||||
WritableNestedModelSerializer)
|
||||
from rest_framework import serializers
|
||||
@ -56,6 +58,24 @@ class SpaceFilterSerializer(serializers.ListSerializer):
|
||||
return super().to_representation(data)
|
||||
|
||||
|
||||
# custom related field, sends details on read, accepts primary key on write
|
||||
# class RelatedFieldAlternative(serializers.PrimaryKeyRelatedField):
|
||||
# def __init__(self, **kwargs):
|
||||
# self.serializer = kwargs.pop('serializer', None)
|
||||
# if self.serializer is not None and not issubclass(self.serializer, serializers.Serializer):
|
||||
# raise TypeError('"serializer" is not a valid serializer class')
|
||||
|
||||
# super().__init__(**kwargs)
|
||||
|
||||
# def use_pk_only_optimization(self):
|
||||
# return False if self.serializer else True
|
||||
|
||||
# def to_representation(self, instance):
|
||||
# if self.serializer:
|
||||
# return self.serializer(instance, context=self.context).data
|
||||
# return super().to_representation(instance)
|
||||
|
||||
|
||||
class SpacedModelSerializer(serializers.ModelSerializer):
|
||||
def create(self, validated_data):
|
||||
validated_data['space'] = self.context['request'].space
|
||||
@ -286,8 +306,21 @@ class SupermarketSerializer(UniqueFieldsMixin, SpacedModelSerializer):
|
||||
fields = ('id', 'name', 'category_to_supermarket')
|
||||
|
||||
|
||||
class RecipeSimpleSerializer(serializers.ModelSerializer):
|
||||
url = serializers.SerializerMethodField('get_url')
|
||||
|
||||
def get_url(self, obj):
|
||||
return reverse('view_recipe', args=[obj.id])
|
||||
|
||||
class Meta:
|
||||
model = Recipe
|
||||
fields = ('id', 'name', 'url')
|
||||
read_only_fields = ['id', 'name', 'url']
|
||||
|
||||
|
||||
class FoodSerializer(UniqueFieldsMixin, WritableNestedModelSerializer):
|
||||
supermarket_category = SupermarketCategorySerializer(allow_null=True, required=False)
|
||||
# RelatedFieldAlternative adds details of related object on read, accepts PK on write
|
||||
# this approach prevents adding *new* objects when updating Food, SupermarketCategory must be created elsewhere
|
||||
image = serializers.SerializerMethodField('get_image')
|
||||
numrecipe = serializers.SerializerMethodField('count_recipes')
|
||||
|
||||
@ -309,6 +342,19 @@ class FoodSerializer(UniqueFieldsMixin, WritableNestedModelSerializer):
|
||||
def count_recipes(self, obj):
|
||||
return Recipe.objects.filter(steps__ingredients__food=obj, space=obj.space).count()
|
||||
|
||||
def to_representation(self, instance):
|
||||
response = super().to_representation(instance)
|
||||
# turns a GET of food.recipe into a dict of data while allowing a PATCH/PUT of an integer to update a food with a recipe
|
||||
recipe = RecipeSimpleSerializer(instance.recipe, allow_null=True).data
|
||||
supermarket_category = SupermarketCategorySerializer(instance.supermarket_category, allow_null=True).data
|
||||
response['recipe'] = recipe if recipe else None
|
||||
# the SupermarketCategorySerializer returns a dict instead of None when the column is null
|
||||
if supermarket_category == {'name': ''} or None:
|
||||
response['supermarket_category'] = None
|
||||
else:
|
||||
response['supermarket_category'] = supermarket_category
|
||||
return response
|
||||
|
||||
def create(self, validated_data):
|
||||
validated_data['name'] = validated_data['name'].strip()
|
||||
validated_data['space'] = self.context['request'].space
|
||||
@ -321,7 +367,7 @@ class FoodSerializer(UniqueFieldsMixin, WritableNestedModelSerializer):
|
||||
|
||||
class Meta:
|
||||
model = Food
|
||||
fields = ('id', 'name', 'recipe', 'ignore_shopping', 'supermarket_category', 'image', 'parent', 'numchild', 'numrecipe')
|
||||
fields = ('id', 'name', 'description', 'recipe', 'ignore_shopping', 'supermarket_category', 'image', 'parent', 'numchild', 'numrecipe')
|
||||
read_only_fields = ('id', 'numchild', 'parent', 'image')
|
||||
|
||||
|
||||
|
@ -1 +1 @@
|
||||
.shake[data-v-33424c9e]{-webkit-animation:shake-data-v-33424c9e .82s cubic-bezier(.36,.07,.19,.97) both;animation:shake-data-v-33424c9e .82s cubic-bezier(.36,.07,.19,.97) both;transform:translateZ(0);-webkit-backface-visibility:hidden;backface-visibility:hidden;perspective:1000px}@-webkit-keyframes shake-data-v-33424c9e{10%,90%{transform:translate3d(-1px,0,0)}20%,80%{transform:translate3d(2px,0,0)}30%,50%,70%{transform:translate3d(-4px,0,0)}40%,60%{transform:translate3d(4px,0,0)}}@keyframes shake-data-v-33424c9e{10%,90%{transform:translate3d(-1px,0,0)}20%,80%{transform:translate3d(2px,0,0)}30%,50%,70%{transform:translate3d(-4px,0,0)}40%,60%{transform:translate3d(4px,0,0)}}
|
||||
.shake[data-v-2ff37af5]{-webkit-animation:shake-data-v-2ff37af5 .82s cubic-bezier(.36,.07,.19,.97) both;animation:shake-data-v-2ff37af5 .82s cubic-bezier(.36,.07,.19,.97) both;transform:translateZ(0);-webkit-backface-visibility:hidden;backface-visibility:hidden;perspective:1000px}@-webkit-keyframes shake-data-v-2ff37af5{10%,90%{transform:translate3d(-1px,0,0)}20%,80%{transform:translate3d(2px,0,0)}30%,50%,70%{transform:translate3d(-4px,0,0)}40%,60%{transform:translate3d(4px,0,0)}}@keyframes shake-data-v-2ff37af5{10%,90%{transform:translate3d(-1px,0,0)}20%,80%{transform:translate3d(2px,0,0)}30%,50%,70%{transform:translate3d(-4px,0,0)}40%,60%{transform:translate3d(4px,0,0)}}
|
@ -1 +1 @@
|
||||
.shake[data-v-d5a65348]{-webkit-animation:shake-data-v-d5a65348 .82s cubic-bezier(.36,.07,.19,.97) both;animation:shake-data-v-d5a65348 .82s cubic-bezier(.36,.07,.19,.97) both;transform:translateZ(0);-webkit-backface-visibility:hidden;backface-visibility:hidden;perspective:1000px}@-webkit-keyframes shake-data-v-d5a65348{10%,90%{transform:translate3d(-1px,0,0)}20%,80%{transform:translate3d(2px,0,0)}30%,50%,70%{transform:translate3d(-4px,0,0)}40%,60%{transform:translate3d(4px,0,0)}}@keyframes shake-data-v-d5a65348{10%,90%{transform:translate3d(-1px,0,0)}20%,80%{transform:translate3d(2px,0,0)}30%,50%,70%{transform:translate3d(-4px,0,0)}40%,60%{transform:translate3d(4px,0,0)}}
|
||||
.shake[data-v-eea8728a]{-webkit-animation:shake-data-v-eea8728a .82s cubic-bezier(.36,.07,.19,.97) both;animation:shake-data-v-eea8728a .82s cubic-bezier(.36,.07,.19,.97) both;transform:translateZ(0);-webkit-backface-visibility:hidden;backface-visibility:hidden;perspective:1000px}@-webkit-keyframes shake-data-v-eea8728a{10%,90%{transform:translate3d(-1px,0,0)}20%,80%{transform:translate3d(2px,0,0)}30%,50%,70%{transform:translate3d(-4px,0,0)}40%,60%{transform:translate3d(4px,0,0)}}@keyframes shake-data-v-eea8728a{10%,90%{transform:translate3d(-1px,0,0)}20%,80%{transform:translate3d(2px,0,0)}30%,50%,70%{transform:translate3d(-4px,0,0)}40%,60%{transform:translate3d(4px,0,0)}}
|
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
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
@ -119,10 +119,11 @@
|
||||
<label for="id_food_description_edit">{{ this.$t('Description') }}</label>
|
||||
<input class="form-control" type="text" id="id_food_description_edit" v-model="this_item.description">
|
||||
<label for="id_food_recipe_edit">{{ this.$t('Recipe') }}</label>
|
||||
<!-- TODO initial selection isn't working and I don't know why -->
|
||||
<generic-multiselect
|
||||
@change="this_item.recipe=$event.val"
|
||||
label="name"
|
||||
:initial_selection="this_item.recipe"
|
||||
:initial_selection="[this_item.recipe]"
|
||||
search_function="listRecipes"
|
||||
:multiple="false"
|
||||
:sticky_options="[{'id': null,'name': $t('None')}]"
|
||||
@ -134,7 +135,16 @@
|
||||
<label class="form-check-label" for="id_food_ignore_edit">{{ this.$t('Ignore_Shopping') }}</label>
|
||||
</div>
|
||||
<label for="id_food_category_edit">{{ this.$t('Shopping_Category') }}</label>
|
||||
<input class="form-control" type="text" id="id_food_category_edit" >
|
||||
<generic-multiselect
|
||||
@change="this_item.supermarket_category=$event.val"
|
||||
label="name"
|
||||
:initial_selection="[this_item.supermarket_category]"
|
||||
search_function="listSupermarketCategorys"
|
||||
:multiple="false"
|
||||
:sticky_options="[{'id': null,'name': $t('None')}]"
|
||||
style="flex-grow: 1; flex-shrink: 1; flex-basis: 0"
|
||||
:placeholder="this.$t('Shopping_Category')">
|
||||
</generic-multiselect>
|
||||
</form>
|
||||
</b-modal>
|
||||
<!-- delete modal -->
|
||||
@ -223,6 +233,7 @@ export default {
|
||||
right: +new Date(),
|
||||
isDirtyRight: false,
|
||||
left_page: 0,
|
||||
update_recipe: [],
|
||||
left: +new Date(),
|
||||
isDirtyLeft: false,
|
||||
this_item: {
|
||||
@ -230,8 +241,9 @@ export default {
|
||||
'name': '',
|
||||
'description': '',
|
||||
'recipe': null,
|
||||
'ignore_shopping': '',
|
||||
'supermarket_category': null,
|
||||
'recipe_full': undefined,
|
||||
'ignore_shopping': false,
|
||||
'supermarket_category': undefined,
|
||||
'target': {
|
||||
'id': -1,
|
||||
'name': ''
|
||||
@ -282,6 +294,7 @@ export default {
|
||||
this.$bvModal.show('id_modal_food_edit')
|
||||
} else if (e.action == 'edit') {
|
||||
this.this_item = source
|
||||
console.log('start edit', this.this_item)
|
||||
this.$bvModal.show('id_modal_food_edit')
|
||||
} else if (e.action === 'move') {
|
||||
this.this_item = source
|
||||
@ -316,14 +329,17 @@ export default {
|
||||
},
|
||||
saveFood: function () {
|
||||
let apiClient = new ApiApiFactory()
|
||||
console.log(this.this_item, !this.this_item.id)
|
||||
// let food = {
|
||||
// name: this.this_item.name,
|
||||
// description: this.this_item.description,
|
||||
// icon: this.this_item.icon,
|
||||
// }
|
||||
console.log('this item', this.this_item.supermarket_category?.id, this.this_item.supermarket_category?.id ?? null)
|
||||
let food = {
|
||||
name: this.this_item.name,
|
||||
description: this.this_item.description,
|
||||
recipe: this.this_item.recipe?.id ?? null,
|
||||
ignore_shopping: this.this_item.ignore_shopping,
|
||||
supermarket_category: this.this_item.supermarket_category?.id ?? null,
|
||||
}
|
||||
console.log('food', food)
|
||||
if (!this.this_item.id) { // if there is no item id assume its a new item
|
||||
apiClient.createFood(this.this_item).then(result => {
|
||||
apiClient.createFood(food).then(result => {
|
||||
// place all new foods at the top of the list - could sort instead
|
||||
this.foods = [result.data].concat(this.foods)
|
||||
// this creates a deep copy to make sure that columns stay independent
|
||||
@ -338,7 +354,7 @@ export default {
|
||||
this.this_item = {}
|
||||
})
|
||||
} else {
|
||||
apiClient.partialUpdateFood(this.this_item.id, this.this_item).then(result => {
|
||||
apiClient.partialUpdateFood(this.this_item.id, food).then(result => {
|
||||
this.refreshCard(this.this_item.id)
|
||||
this.this_item={}
|
||||
}).catch((err) => {
|
||||
@ -428,7 +444,6 @@ export default {
|
||||
let apiClient = new ApiApiFactory()
|
||||
let parent = {}
|
||||
let pageSize = 200
|
||||
console.log(apiClient.listRecipes)
|
||||
|
||||
apiClient.listRecipes(
|
||||
undefined, undefined, String(food.id), undefined, undefined, undefined,
|
||||
@ -583,7 +598,7 @@ export default {
|
||||
}
|
||||
this.foods = this.foods.filter(kw => kw.id != id)
|
||||
this.foods2 = this.foods2.filter(kw => kw.id != id)
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -11,7 +11,7 @@
|
||||
@dragleave="handleDragLeave($event)"
|
||||
@drop="handleDragDrop($event)">
|
||||
<b-row no-gutters style="height:inherit;">
|
||||
<b-col no-gutters md="3" style="justify-content: center; height:inherit;">
|
||||
<b-col no-gutters md="3" style="height:inherit;">
|
||||
<b-card-img-lazy style="object-fit: cover; height: 10vh;" :src="food_image" v-bind:alt="$t('Recipe_Image')"></b-card-img-lazy>
|
||||
</b-col>
|
||||
<b-col no-gutters md="9" style="height:inherit;">
|
||||
@ -34,9 +34,10 @@
|
||||
</b-card-text>
|
||||
</b-card-body>
|
||||
</b-col>
|
||||
<div class="card-img-overlay h-100 d-flex flex-column justify-content-right"
|
||||
style="float:right; text-align: right; padding-top: 10px; padding-right: 5px">
|
||||
<generic-context-menu style="float:right"
|
||||
<div class="card-img-overlay justify-content-right h-25 m-0 p-0 text-right">
|
||||
<b-button v-if="food.recipe" v-b-tooltip.hover :title="food.recipe.name"
|
||||
class=" btn fas fa-book-open p-0 border-0" variant="link" :href="food.recipe.url"/>
|
||||
<generic-context-menu class="p-0"
|
||||
:show_merge="true"
|
||||
:show_move="true"
|
||||
@item-action="$emit('item-action', {'action': $event, 'source': food})">
|
||||
|
@ -1,5 +1,4 @@
|
||||
<template>
|
||||
<div>
|
||||
<b-dropdown variant="link" toggle-class="text-decoration-none" no-caret>
|
||||
<template #button-content>
|
||||
<i class="fas fa-ellipsis-v" ></i>
|
||||
@ -21,7 +20,6 @@
|
||||
</b-dropdown-item>
|
||||
|
||||
</b-dropdown>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
@ -44,11 +44,20 @@ export default {
|
||||
},
|
||||
watch: {
|
||||
initial_selection: function (newVal, oldVal) { // watch it
|
||||
this.selected_objects = newVal
|
||||
if (this.multiple) {
|
||||
this.selected_objects = newVal
|
||||
} else if (this.selected_objects != newVal?.[0]) {
|
||||
// when not using multiple selections need to convert array to value
|
||||
this.selected_objects = newVal?.[0] ?? null
|
||||
}
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.search('')
|
||||
// when not using multiple selections need to convert array to value
|
||||
if (!this.multiple & this.selected_objects != this.initial_selection?.[0]) {
|
||||
this.selected_objects = this.initial_selection?.[0] ?? null
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
search: function (query) {
|
||||
|
@ -11,7 +11,7 @@
|
||||
@dragleave="handleDragLeave($event)"
|
||||
@drop="handleDragDrop($event)">
|
||||
<b-row no-gutters style="height:inherit;">
|
||||
<b-col no-gutters md="3" style="justify-content: center; height:inherit;">
|
||||
<b-col no-gutters md="3" style="height:inherit;">
|
||||
<b-card-img-lazy style="object-fit: cover; height: 10vh;" :src="keyword_image" v-bind:alt="$t('Recipe_Image')"></b-card-img-lazy>
|
||||
</b-col>
|
||||
<b-col no-gutters md="9" style="height:inherit;">
|
||||
@ -34,9 +34,8 @@
|
||||
</b-card-text>
|
||||
</b-card-body>
|
||||
</b-col>
|
||||
<div class="card-img-overlay h-100 d-flex flex-column justify-content-right"
|
||||
style="float:right; text-align: right; padding-top: 10px; padding-right: 5px">
|
||||
<generic-context-menu style="float:right"
|
||||
<div class="card-img-overlay justify-content-right h-25 m-0 p-0 text-right">
|
||||
<generic-context-menu class="p-0"
|
||||
:show_merge="true"
|
||||
:show_move="true"
|
||||
@item-action="$emit('item-action', {'action': $event, 'source': keyword})">
|
||||
|
@ -84,8 +84,9 @@ module.exports = {
|
||||
},
|
||||
},
|
||||
},
|
||||
// TODO make this conditional on .env DEBUG = FALSE
|
||||
config.optimization.minimize(true)
|
||||
},
|
||||
// TODO make this conditional on .env DEBUG = TRUE
|
||||
config.optimization.minimize(false)
|
||||
);
|
||||
|
||||
//TODO somehow remov them as they are also added to the manifest config of the service worker
|
||||
|
Loading…
Reference in New Issue
Block a user