This commit is contained in:
smilerz 2021-08-16 15:01:35 -05:00
parent 0559143f0e
commit b56c2429c7
14 changed files with 84 additions and 108 deletions

View File

@ -47,7 +47,7 @@ class SpaceFilterSerializer(serializers.ListSerializer):
def to_representation(self, data):
if (type(data) == QuerySet and data.query.is_sliced):
# if query is sliced or if is a MP_NodeQuerySet it came from api request not nested serializer
# if query is sliced it came from api request not nested serializer
return super().to_representation(data)
if self.child.Meta.model == User:
data = data.filter(userpreference__space=self.context['request'].space)
@ -212,8 +212,8 @@ class KeywordSerializer(UniqueFieldsMixin, serializers.ModelSerializer):
def get_image(self, obj):
recipes = obj.recipe_set.all().filter(space=obj.space).exclude(image__isnull=True).exclude(image__exact='')
if len(recipes) == 0:
recipes = Recipe.objects.filter(keywords__in=obj.get_tree(), space=obj.space).exclude(image__isnull=True).exclude(image__exact='') # if no recipes found - check whole tree
if len(recipes) == 0 and obj.has_children():
recipes = Recipe.objects.filter(keywords__in=obj.get_descendants(), space=obj.space).exclude(image__isnull=True).exclude(image__exact='') # if no recipes found - check whole tree
if len(recipes) != 0:
return random.choice(recipes).image.url
else:
@ -297,10 +297,9 @@ class FoodSerializer(UniqueFieldsMixin, WritableNestedModelSerializer):
return obj.recipe.image.url
# if food is not also a recipe, look for recipe images that use the food
recipes = Recipe.objects.filter(steps__ingredients__food=obj, space=obj.space).exclude(image__isnull=True).exclude(image__exact='')
# if no recipes found - check whole tree
if len(recipes) == 0:
recipes = Recipe.objects.filter(steps__ingredients__food__in=obj.get_tree(), space=obj.space).exclude(image__isnull=True).exclude(image__exact='')
if len(recipes) == 0 and obj.has_children():
recipes = Recipe.objects.filter(steps__ingredients__food__in=obj.get_descendants(), space=obj.space).exclude(image__isnull=True).exclude(image__exact='')
if len(recipes) != 0:
return random.choice(recipes).image.url

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

View File

@ -135,10 +135,6 @@ urlpatterns = [
name='web_manifest'),
]
# generic_models = (
# Recipe, RecipeImport, Storage, RecipeBook, MealPlan, SyncLog, Sync,
# Comment, RecipeBookEntry, Keyword, Food, ShoppingList, InviteLink
# )
generic_models = (
Recipe, RecipeImport, Storage, RecipeBook, MealPlan, SyncLog, Sync,
Comment, RecipeBookEntry, ShoppingList, InviteLink

View File

@ -80,7 +80,7 @@
spinner="waveDots">
</infinite-loading>
</div>
<!-- right side keyword cards -->
<!-- right side food cards -->
<div class="col col-md mh-100 overflow-auto " v-if="show_split">
<food-card
v-for="f in foods2"
@ -108,51 +108,56 @@
<!-- TODO Modals can probably be made generic and moved to component -->
<!-- edit modal -->
<b-modal class="modal"
:id="'id_modal_keyword_edit'"
@shown="prepareEmoji"
:title="this.$t('Edit_Keyword')"
:id="'id_modal_food_edit'"
:title="this.$t('Edit_Food')"
:ok-title="this.$t('Save')"
:cancel-title="this.$t('Cancel')"
@ok="saveKeyword">
@ok="saveFood">
<form>
<label for="id_keyword_name_edit">{{ this.$t('Name') }}</label>
<input class="form-control" type="text" id="id_keyword_name_edit" v-model="this_item.name">
<label for="id_keyword_description_edit">{{ this.$t('Description') }}</label>
<input class="form-control" type="text" id="id_keyword_description_edit" v-model="this_item.description">
<label for="id_keyword_icon_edit">{{ this.$t('Icon') }}</label>
<twemoji-textarea
id="id_keyword_icon_edit"
ref="_edit"
:emojiData="emojiDataAll"
:emojiGroups="emojiGroups"
triggerType="hover"
recentEmojisFeat="true"
recentEmojisStorage="local"
@contentChanged="setIcon"
/>
<label for="id_food_name_edit">{{ this.$t('Name') }}</label>
<input class="form-control" type="text" id="id_food_name_edit" v-model="this_item.name">
<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>
<generic-multiselect
@change="this_item.recipe=$event.val"
label="name"
:initial_selection="this_item.recipe"
search_function="listRecipes"
:multiple="false"
:sticky_options="[{'id': null,'name': $t('None')}]"
style="flex-grow: 1; flex-shrink: 1; flex-basis: 0"
:placeholder="this.$t('Search')">
</generic-multiselect>
<div class="form-group form-check">
<input type="checkbox" class="form-check-input" id="id_food_ignore_edit" v-model="this_item.ignore_shopping">
<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" >
</form>
</b-modal>
<!-- delete modal -->
<b-modal class="modal"
:id="'id_modal_keyword_delete'"
:title="this.$t('Delete_Keyword')"
:id="'id_modal_food_delete'"
:title="this.$t('Delete_Food')"
:ok-title="this.$t('Delete')"
:cancel-title="this.$t('Cancel')"
@ok="delKeyword(this_item.id)">
@ok="delFood(this_item.id)">
{{this.$t("delete_confimation", {'kw': this_item.name})}} {{this_item.name}}
</b-modal>
<!-- move modal -->
<b-modal class="modal"
:id="'id_modal_keyword_move'"
:title="this.$t('Move_Keyword')"
:id="'id_modal_food_move'"
:title="this.$t('Move_Food')"
:ok-title="this.$t('Move')"
:cancel-title="this.$t('Cancel')"
@ok="moveKeyword(this_item.id, this_item.target.id)">
@ok="moveFood(this_item.id, this_item.target.id)">
{{ this.$t("move_selection", {'child': this_item.name}) }}
<generic-multiselect
@change="this_item.target=$event.val"
label="name"
search_function="listKeywords"
search_function="listFood"
:multiple="false"
:sticky_options="[{'id': 0,'name': $t('Root')}]"
:tree_api="true"
@ -162,16 +167,16 @@
</b-modal>
<!-- merge modal -->
<b-modal class="modal"
:id="'id_modal_keyword_merge'"
:title="this.$t('Merge_Keyword')"
:id="'id_modal_food_merge'"
:title="this.$t('Merge_Food')"
:ok-title="this.$t('Merge')"
:cancel-title="this.$t('Cancel')"
@ok="mergeKeyword(this_item.id, this_item.target.id)">
{{ this.$t("merge_selection", {'source': this_item.name, 'type': this.$t('keyword')}) }}
@ok="mergeFood(this_item.id, this_item.target.id)">
{{ this.$t("merge_selection", {'source': this_item.name, 'type': this.$t('food')}) }}
<generic-multiselect
@change="this_item.target=$event.val"
label="name"
search_function="listKeywords"
search_function="listFoods"
:multiple="false"
:tree_api="true"
style="flex-grow: 1; flex-shrink: 1; flex-basis: 0"
@ -193,36 +198,19 @@ import {BootstrapVue} from 'bootstrap-vue'
import 'bootstrap-vue/dist/bootstrap-vue.css'
import _debounce from 'lodash/debounce'
import {ResolveUrlMixin} from "@/utils/utils";
import {ToastMixin} from "@/utils/utils";
import {ApiApiFactory} from "@/utils/openapi/api.ts";
import FoodCard from "@/components/FoodCard";
import GenericMultiselect from "@/components/GenericMultiselect";
import InfiniteLoading from 'vue-infinite-loading';
// would move with modals if made generic
import {TwemojiTextarea} from '@kevinfaguiar/vue-twemoji-picker';
// TODO add localization
import EmojiAllData from '@kevinfaguiar/vue-twemoji-picker/emoji-data/en/emoji-all-groups.json';
import EmojiGroups from '@kevinfaguiar/vue-twemoji-picker/emoji-data/emoji-groups.json';
// end move with generic modals
Vue.use(BootstrapVue)
export default {
name: 'FoodListView',
mixins: [ResolveUrlMixin],
components: {TwemojiTextarea, FoodCard, GenericMultiselect, InfiniteLoading},
computed: {
// move with generic modals
emojiDataAll() {
return EmojiAllData;
},
emojiGroups() {
return EmojiGroups;
}
// end move with generic modals
},
mixins: [ToastMixin],
components: {FoodCard, GenericMultiselect, InfiniteLoading},
data() {
return {
foods: [],
@ -241,7 +229,9 @@ export default {
'id': -1,
'name': '',
'description': '',
'icon': '',
'recipe': null,
'ignore_shopping': '',
'supermarket_category': null,
'target': {
'id': -1,
'name': ''
@ -262,15 +252,6 @@ export default {
}, 700)
},
methods: {
makeToast: function (title, message, variant = null) {
//TODO remove duplicate function in favor of central one
this.$bvToast.toast(message, {
title: title,
variant: variant,
toaster: 'b-toaster-top-center',
solid: true
})
},
resetSearch: function () {
if (this.search_input !== '') {
this.search_input = ''
@ -295,26 +276,26 @@ export default {
if (e.action == 'delete') {
this.this_item = source
this.$bvModal.show('id_modal_keyword_delete')
this.$bvModal.show('id_modal_food_delete')
} else if (e.action == 'new') {
this.this_item = {}
this.$bvModal.show('id_modal_keyword_edit')
this.$bvModal.show('id_modal_food_edit')
} else if (e.action == 'edit') {
this.this_item = source
this.$bvModal.show('id_modal_keyword_edit')
this.$bvModal.show('id_modal_food_edit')
} else if (e.action === 'move') {
this.this_item = source
if (target == null) {
this.$bvModal.show('id_modal_keyword_move')
this.$bvModal.show('id_modal_food_move')
} else {
this.moveKeyword(source.id, target.id)
this.moveFood(source.id, target.id)
}
} else if (e.action === 'merge') {
this.this_item = source
if (target == null) {
this.$bvModal.show('id_modal_keyword_merge')
this.$bvModal.show('id_modal_food_merge')
} else {
this.mergeKeyword(e.source.id, e.target.id)
this.mergeFood(e.source.id, e.target.id)
}
} else if (e.action === 'get-children') {
if (source.expanded) {
@ -335,13 +316,14 @@ export default {
},
saveFood: function () {
let apiClient = new ApiApiFactory()
let food = {
name: this.this_item.name,
description: this.this_item.description,
icon: this.this_item.icon,
}
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,
// }
if (!this.this_item.id) { // if there is no item id assume its a new item
apiClient.createFood(food).then(result => {
apiClient.createFood(this.this_item).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
@ -356,7 +338,7 @@ export default {
this.this_item = {}
})
} else {
apiClient.partialUpdateFood(this.this_item.id, food).then(result => {
apiClient.partialUpdateFood(this.this_item.id, this.this_item).then(result => {
this.refreshCard(this.this_item.id)
this.this_item={}
}).catch((err) => {
@ -427,9 +409,9 @@ export default {
apiClient.listFoods(query, root, tree, page, pageSize).then(result => {
if (col == 'left') {
parent = this.findFood(this.keywords, food.id)
parent = this.findFood(this.foods, food.id)
} else if (col == 'right'){
parent = this.findFood(this.keywords2, food.id)
parent = this.findFood(this.foods2, food.id)
}
if (parent) {
Vue.set(parent, 'children', result.data.results)

View File

@ -194,7 +194,7 @@ import {BootstrapVue} from 'bootstrap-vue'
import 'bootstrap-vue/dist/bootstrap-vue.css'
import _debounce from 'lodash/debounce'
import {ResolveUrlMixin} from "@/utils/utils";
import {ToastMixin} from "@/utils/utils";
import {ApiApiFactory} from "@/utils/openapi/api.ts";
import KeywordCard from "@/components/KeywordCard";
@ -212,7 +212,7 @@ Vue.use(BootstrapVue)
export default {
name: 'KeywordListView',
mixins: [ResolveUrlMixin],
mixins: [ToastMixin],
components: {TwemojiTextarea, KeywordCard, GenericMultiselect, InfiniteLoading},
computed: {
// move with generic modals
@ -263,15 +263,6 @@ export default {
}, 700)
},
methods: {
makeToast: function (title, message, variant = null) {
//TODO remove duplicate function in favor of central one
this.$bvToast.toast(message, {
title: title,
variant: variant,
toaster: 'b-toaster-top-center',
solid: true
})
},
resetSearch: function () {
if (this.search_input !== '') {
this.search_input = ''

View File

@ -65,6 +65,11 @@ export default {
apiClient[this.search_function](query, root, tree, page, pageSize).then(result => {
this.objects = this.sticky_options.concat(result.data.results)
})
} else if (this.search_function === 'listRecipes') {
apiClient[this.search_function](query, undefined, undefined, undefined, undefined, undefined,
undefined, undefined, undefined, undefined, undefined, 25, undefined).then(result => {
this.objects = this.sticky_options.concat(result.data.results)
})
} else {
apiClient[this.search_function]({query: {query: query, limit: this.limit}}).then(result => {
this.objects = this.sticky_options.concat(result.data)

View File

@ -102,5 +102,8 @@
"merge_selection": "Replace all occurences of {source} with the selected {type}.",
"Advanced Search Settings": "Advanced Search Settings",
"Download": "Download",
"Root": "Root"
"Root": "Root",
"Ignore_Shopping": "Ignore Shopping",
"Shopping_Category": "Shopping Category",
"Edit_Food": "Edit Food"
}