refactored Generic API
This commit is contained in:
parent
caeb47aee9
commit
a1d1cbac5d
@ -1,4 +1,3 @@
|
|||||||
from cookbook.models import SearchFields
|
|
||||||
from django.db import migrations
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
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
@ -730,7 +730,6 @@
|
|||||||
searchKeywords: function (query) {
|
searchKeywords: function (query) {
|
||||||
this.keywords_loading = true
|
this.keywords_loading = true
|
||||||
this.$http.get("{% url 'api:keyword-list' %}" + '?query=' + query + '&limit=10').then((response) => {
|
this.$http.get("{% url 'api:keyword-list' %}" + '?query=' + query + '&limit=10').then((response) => {
|
||||||
console.log(response.data)
|
|
||||||
this.keywords = response.data.results;
|
this.keywords = response.data.results;
|
||||||
this.keywords_loading = false
|
this.keywords_loading = false
|
||||||
}).catch((err) => {
|
}).catch((err) => {
|
||||||
@ -780,7 +779,7 @@
|
|||||||
searchFoods: function (query) {
|
searchFoods: function (query) {
|
||||||
this.foods_loading = true
|
this.foods_loading = true
|
||||||
this.$http.get("{% url 'api:food-list' %}" + '?query=' + query + '&limit=10').then((response) => {
|
this.$http.get("{% url 'api:food-list' %}" + '?query=' + query + '&limit=10').then((response) => {
|
||||||
this.foods = response.data
|
this.foods = response.data.results
|
||||||
|
|
||||||
if (this.recipe !== undefined) {
|
if (this.recipe !== undefined) {
|
||||||
for (let s of this.recipe.steps) {
|
for (let s of this.recipe.steps) {
|
||||||
|
@ -66,7 +66,6 @@ from cookbook.serializer import (FoodSerializer, IngredientSerializer,
|
|||||||
|
|
||||||
|
|
||||||
class StandardFilterMixin(ViewSetMixin):
|
class StandardFilterMixin(ViewSetMixin):
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
queryset = self.queryset
|
queryset = self.queryset
|
||||||
query = self.request.query_params.get('query', None)
|
query = self.request.query_params.get('query', None)
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
<template>
|
<template>
|
||||||
<div id="app" style="margin-bottom: 4vh">
|
<div id="app" style="margin-bottom: 4vh">
|
||||||
<generic-modal-form
|
<generic-modal-form
|
||||||
:model="this_model"
|
:model="this_model.name"
|
||||||
:action="this_action"/>
|
:action="this_action"/>
|
||||||
<generic-split-lists
|
<generic-split-lists
|
||||||
:list_name="this_model"
|
:list_name="this_model.name"
|
||||||
@reset="resetList"
|
@reset="resetList"
|
||||||
@get-list="getFoods"
|
@get-list="getFoods"
|
||||||
@item-action="startAction"
|
@item-action="startAction"
|
||||||
@ -13,7 +13,7 @@
|
|||||||
<generic-horizontal-card
|
<generic-horizontal-card
|
||||||
v-for="f in foods" v-bind:key="f.id"
|
v-for="f in foods" v-bind:key="f.id"
|
||||||
:model=f
|
:model=f
|
||||||
:model_name="this_model"
|
:model_name="this_model.name"
|
||||||
:draggable="true"
|
:draggable="true"
|
||||||
:merge="true"
|
:merge="true"
|
||||||
:move="true"
|
:move="true"
|
||||||
@ -28,7 +28,7 @@
|
|||||||
<template v-slot:cards-right>
|
<template v-slot:cards-right>
|
||||||
<generic-horizontal-card v-for="f in foods2" v-bind:key="f.id"
|
<generic-horizontal-card v-for="f in foods2" v-bind:key="f.id"
|
||||||
:model=f
|
:model=f
|
||||||
:model_name="this_model"
|
:model_name="this_model.name"
|
||||||
:draggable="true"
|
:draggable="true"
|
||||||
:merge="true"
|
:merge="true"
|
||||||
:move="true"
|
:move="true"
|
||||||
@ -90,7 +90,7 @@
|
|||||||
:title="this.$t('Delete_Food')"
|
:title="this.$t('Delete_Food')"
|
||||||
:ok-title="this.$t('Delete')"
|
:ok-title="this.$t('Delete')"
|
||||||
:cancel-title="this.$t('Cancel')"
|
:cancel-title="this.$t('Cancel')"
|
||||||
@ok="deleteThis(this_item.id, this_model)">
|
@ok="deleteThis(this_item.id)">
|
||||||
{{this.$t("delete_confimation", {'kw': this_item.name})}}
|
{{this.$t("delete_confimation", {'kw': this_item.name})}}
|
||||||
</b-modal>
|
</b-modal>
|
||||||
<!-- move modal -->
|
<!-- move modal -->
|
||||||
@ -141,7 +141,9 @@ import {BootstrapVue} from 'bootstrap-vue'
|
|||||||
|
|
||||||
import 'bootstrap-vue/dist/bootstrap-vue.css'
|
import 'bootstrap-vue/dist/bootstrap-vue.css'
|
||||||
|
|
||||||
import {ToastMixin, ApiMixin, CardMixin} from "@/utils/utils";
|
import {ApiMixin, CardMixin, ToastMixin} from "@/utils/utils";
|
||||||
|
import {Models, Actions} from "@/utils/models";
|
||||||
|
import {StandardToasts} from "@/utils/utils";
|
||||||
|
|
||||||
import GenericSplitLists from "@/components/GenericSplitLists";
|
import GenericSplitLists from "@/components/GenericSplitLists";
|
||||||
import GenericHorizontalCard from "@/components/GenericHorizontalCard";
|
import GenericHorizontalCard from "@/components/GenericHorizontalCard";
|
||||||
@ -152,11 +154,11 @@ Vue.use(BootstrapVue)
|
|||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'FoodListView',
|
name: 'FoodListView',
|
||||||
mixins: [ToastMixin, ApiMixin, CardMixin],
|
mixins: [ApiMixin, CardMixin, ToastMixin],
|
||||||
components: {GenericHorizontalCard, GenericMultiselect, GenericSplitLists, GenericModalForm},
|
components: {GenericHorizontalCard, GenericMultiselect, GenericSplitLists, GenericModalForm},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
this_model: 'Food',
|
this_model: Models.FOOD,
|
||||||
this_action:'',
|
this_action:'',
|
||||||
foods: [],
|
foods: [],
|
||||||
foods2: [],
|
foods2: [],
|
||||||
@ -249,7 +251,7 @@ export default {
|
|||||||
getFoods: function(params, callback) {
|
getFoods: function(params, callback) {
|
||||||
let column = params?.column ?? 'left'
|
let column = params?.column ?? 'left'
|
||||||
|
|
||||||
this.genericAPI(this.this_model, 'list', params).then((result) => {
|
this.genericAPI(this.this_model, Actions.LIST, params).then((result) => {
|
||||||
if (result.data.results.length){
|
if (result.data.results.length){
|
||||||
if (column ==='left') {
|
if (column ==='left') {
|
||||||
this.foods = this.foods.concat(result.data.results)
|
this.foods = this.foods.concat(result.data.results)
|
||||||
@ -266,18 +268,18 @@ export default {
|
|||||||
callback(result.data.count < (column==="left" ? this.foods.length : this.foods2.length))
|
callback(result.data.count < (column==="left" ? this.foods.length : this.foods2.length))
|
||||||
}).catch((err) => {
|
}).catch((err) => {
|
||||||
console.log(err)
|
console.log(err)
|
||||||
this.makeToast(this.$t('Error'), err.bodyText, 'danger')
|
StandardToasts.makeStandardToast(StandardToasts.FAIL_FETCH)
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
getThis: function(id, callback){
|
getThis: function(id, callback){
|
||||||
return this.genericAPI(this.this_model, 'retrieve', {'id': id})
|
return this.genericAPI(this.this_model, Actions.FETCH, {'id': id})
|
||||||
},
|
},
|
||||||
saveFood: function () {
|
saveFood: function () {
|
||||||
let food = {...this.this_item}
|
let food = {...this.this_item}
|
||||||
food.supermarket_category = this.this_item.supermarket_category?.id ?? null
|
food.supermarket_category = this.this_item.supermarket_category?.id ?? null
|
||||||
food.recipe = this.this_item.recipe?.id ?? null
|
food.recipe = this.this_item.recipe?.id ?? null
|
||||||
if (!food?.id) { // if there is no item id assume it's a new item
|
if (!food?.id) { // if there is no item id assume it's a new item
|
||||||
this.genericAPI(this.this_model, 'create', food).then((result) => {
|
this.genericAPI(this.this_model, Actions.CREATE, food).then((result) => {
|
||||||
// place all new foods at the top of the list - could sort instead
|
// place all new foods at the top of the list - could sort instead
|
||||||
this.foods = [result.data].concat(this.foods)
|
this.foods = [result.data].concat(this.foods)
|
||||||
// this creates a deep copy to make sure that columns stay independent
|
// this creates a deep copy to make sure that columns stay independent
|
||||||
@ -286,20 +288,24 @@ export default {
|
|||||||
} else {
|
} else {
|
||||||
this.foods2 = []
|
this.foods2 = []
|
||||||
}
|
}
|
||||||
|
StandardToasts.makeStandardToast(StandardToasts.SUCCESS_CREATE)
|
||||||
}).catch((err) => {
|
}).catch((err) => {
|
||||||
console.log(err)
|
console.log(err)
|
||||||
|
StandardToasts.makeStandardToast(StandardToasts.FAIL_CREATE)
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
this.genericAPI(this.this_model, 'updatePartial', food).then((result) => {
|
this.genericAPI(this.this_model, Actions.UPDATE, food).then((result) => {
|
||||||
this.refreshObject(this.this_item.id)
|
this.refreshObject(food.id)
|
||||||
|
StandardToasts.makeStandardToast(StandardToasts.SUCCESS_UPDATE)
|
||||||
}).catch((err) => {
|
}).catch((err) => {
|
||||||
console.log(err)
|
console.log(err)
|
||||||
|
StandardToasts.makeStandardToast(StandardToasts.FAIL_UPDATE)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
this.this_item = {...this.blank_item}
|
this.this_item = {...this.blank_item}
|
||||||
},
|
},
|
||||||
moveFood: function (source_id, target_id) {
|
moveFood: function (source_id, target_id) {
|
||||||
this.genericAPI(this.this_model, 'move', {'source': source_id, 'target': target_id}).then((result) => {
|
this.genericAPI(this.this_model, Actions.MOVE, {'source': source_id, 'target': target_id}).then((result) => {
|
||||||
if (target_id === 0) {
|
if (target_id === 0) {
|
||||||
let food = this.findCard(source_id, this.foods) || this.findCard(source_id, this.foods2)
|
let food = this.findCard(source_id, this.foods) || this.findCard(source_id, this.foods2)
|
||||||
this.foods = [food].concat(this.destroyCard(source_id, this.foods)) // order matters, destroy old card before adding it back in at root
|
this.foods = [food].concat(this.destroyCard(source_id, this.foods)) // order matters, destroy old card before adding it back in at root
|
||||||
@ -310,6 +316,8 @@ export default {
|
|||||||
this.foods2 = this.destroyCard(source_id, this.foods2)
|
this.foods2 = this.destroyCard(source_id, this.foods2)
|
||||||
this.refreshObject(target_id)
|
this.refreshObject(target_id)
|
||||||
}
|
}
|
||||||
|
// TODO make standard toast
|
||||||
|
this.makeToast(this.$t('Success'), 'Succesfully moved food', 'success')
|
||||||
}).catch((err) => {
|
}).catch((err) => {
|
||||||
// TODO none of the error checking works because the openapi generated functions don't throw an error?
|
// TODO none of the error checking works because the openapi generated functions don't throw an error?
|
||||||
// or i'm capturing it incorrectly
|
// or i'm capturing it incorrectly
|
||||||
@ -318,7 +326,7 @@ export default {
|
|||||||
})
|
})
|
||||||
},
|
},
|
||||||
mergeFood: function (source_id, target_id) {
|
mergeFood: function (source_id, target_id) {
|
||||||
this.genericAPI(this.this_model, 'merge', {'source': source_id, 'target': target_id}).then((result) => {
|
this.genericAPI(this.this_model, Actions.MERGE, {'source': source_id, 'target': target_id}).then((result) => {
|
||||||
this.foods = this.destroyCard(source_id, this.foods)
|
this.foods = this.destroyCard(source_id, this.foods)
|
||||||
this.foods2 = this.destroyCard(source_id, this.foods2)
|
this.foods2 = this.destroyCard(source_id, this.foods2)
|
||||||
this.refreshObject(target_id)
|
this.refreshObject(target_id)
|
||||||
@ -326,6 +334,8 @@ export default {
|
|||||||
console.log('Error', err)
|
console.log('Error', err)
|
||||||
this.makeToast(this.$t('Error'), err.bodyText, 'danger')
|
this.makeToast(this.$t('Error'), err.bodyText, 'danger')
|
||||||
})
|
})
|
||||||
|
// TODO make standard toast
|
||||||
|
this.makeToast(this.$t('Success'), 'Succesfully merged food', 'success')
|
||||||
},
|
},
|
||||||
getChildren: function(col, food){
|
getChildren: function(col, food){
|
||||||
let parent = {}
|
let parent = {}
|
||||||
@ -333,7 +343,7 @@ export default {
|
|||||||
'root': food.id,
|
'root': food.id,
|
||||||
'pageSize': 200
|
'pageSize': 200
|
||||||
}
|
}
|
||||||
this.genericAPI(this.this_model, 'list', options).then((result) => {
|
this.genericAPI(this.this_model, Actions.LIST, options).then((result) => {
|
||||||
parent = this.findCard(food.id, col === 'left' ? this.foods : this.foods2)
|
parent = this.findCard(food.id, col === 'left' ? this.foods : this.foods2)
|
||||||
if (parent) {
|
if (parent) {
|
||||||
Vue.set(parent, 'children', result.data.results)
|
Vue.set(parent, 'children', result.data.results)
|
||||||
@ -352,7 +362,7 @@ export default {
|
|||||||
'pageSize': 200
|
'pageSize': 200
|
||||||
}
|
}
|
||||||
|
|
||||||
this.genericAPI('recipe', 'list', options).then((result) => {
|
this.genericAPI(Models.RECIPE, Actions.LIST, options).then((result) => {
|
||||||
parent = this.findCard(food.id, col === 'left' ? this.foods : this.foods2)
|
parent = this.findCard(food.id, col === 'left' ? this.foods : this.foods2)
|
||||||
if (parent) {
|
if (parent) {
|
||||||
Vue.set(parent, 'recipes', result.data.results)
|
Vue.set(parent, 'recipes', result.data.results)
|
||||||
@ -366,6 +376,7 @@ export default {
|
|||||||
})
|
})
|
||||||
},
|
},
|
||||||
refreshObject: function(id){
|
refreshObject: function(id){
|
||||||
|
console.log('refresh object', id)
|
||||||
this.getThis(id).then(result => {
|
this.getThis(id).then(result => {
|
||||||
this.refreshCard(result.data, this.foods)
|
this.refreshCard(result.data, this.foods)
|
||||||
this.refreshCard({...result.data}, this.foods2)
|
this.refreshCard({...result.data}, this.foods2)
|
||||||
@ -382,12 +393,13 @@ export default {
|
|||||||
this.this_item.icon = icon
|
this.this_item.icon = icon
|
||||||
},
|
},
|
||||||
deleteThis: function(id, model) {
|
deleteThis: function(id, model) {
|
||||||
this.genericAPI(this.this_model, 'destroy', {'id': id}).then((result) => {
|
this.genericAPI(this.this_model, Actions.DELETE, {'id': id}).then((result) => {
|
||||||
this.foods = this.destroyCard(id, this.foods)
|
this.foods = this.destroyCard(id, this.foods)
|
||||||
this.foods2 = this.destroyCard(id, this.foods2)
|
this.foods2 = this.destroyCard(id, this.foods2)
|
||||||
|
StandardToasts.makeStandardToast(StandardToasts.SUCCESS_DELETE)
|
||||||
}).catch((err) => {
|
}).catch((err) => {
|
||||||
console.log(err)
|
console.log(err)
|
||||||
this.makeToast(this.$t('Error'), err.bodyText, 'danger')
|
StandardToasts.makeStandardToast(StandardToasts.FAIL_DELETE)
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -107,6 +107,7 @@ export default {
|
|||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
Button: function(e) {
|
Button: function(e) {
|
||||||
|
console.log(typeof({}), typeof([]), typeof('this'), typeof(1))
|
||||||
this.action='new'
|
this.action='new'
|
||||||
this.$bvModal.show('modal')
|
this.$bvModal.show('modal')
|
||||||
},
|
},
|
||||||
|
103
vue/src/utils/models.js
Normal file
103
vue/src/utils/models.js
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
/*
|
||||||
|
* Utility CLASS to define model configurations
|
||||||
|
* */
|
||||||
|
|
||||||
|
// TODO this needs rethought and simplified
|
||||||
|
// maybe a function that returns a single dictionary based on action?
|
||||||
|
export class Models {
|
||||||
|
// Arrays correspond to ORDERED list of parameters required by ApiApiFactory
|
||||||
|
// Inner arrays are used to construct a dictionary of key:value pairs
|
||||||
|
// MODEL configurations will override MODEL_TYPE configurations with will override ACTION configurations
|
||||||
|
|
||||||
|
// MODEL_TYPES - inherited by MODELS, inherits and takes precedence over ACTIONS
|
||||||
|
static TREE = {
|
||||||
|
'list': {
|
||||||
|
'params': ['query', 'root', 'tree', 'page', 'pageSize'],
|
||||||
|
'config': {
|
||||||
|
'root': {
|
||||||
|
'default': {
|
||||||
|
'function': 'CONDITIONAL',
|
||||||
|
'check': 'query',
|
||||||
|
'operator': 'not_exist',
|
||||||
|
'true': 0,
|
||||||
|
'false': undefined
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'tree': {'default': undefined},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MODELS - inherits and takes precedence over MODEL_TYPES and ACTIONS
|
||||||
|
static FOOD = {
|
||||||
|
'name': 'Food', // *OPTIONAL: parameters will be built model -> model_type -> default
|
||||||
|
'model_type': this.TREE, // *OPTIONAL* model specific params for api, if not present will attempt modeltype_create then default_create
|
||||||
|
// REQUIRED: unordered array of fields that can be set during create
|
||||||
|
'create': {
|
||||||
|
// if not defined partialUpdate will use the same parameters, prepending 'id'
|
||||||
|
'params': [['name', 'description', 'recipe', 'ignore_shopping', 'supermarket_category']]
|
||||||
|
},
|
||||||
|
|
||||||
|
}
|
||||||
|
static KEYWORD = {}
|
||||||
|
static UNIT = {}
|
||||||
|
static RECIPE = {}
|
||||||
|
static SHOPPING_LIST = {}
|
||||||
|
static RECIPE = {
|
||||||
|
'name': 'Recipe',
|
||||||
|
'list': {
|
||||||
|
'params': ['query', 'keywords', 'foods', 'books', 'keywordsOr', 'foodsOr', 'booksOr', 'internal', 'random', '_new', 'page', 'pageSize', 'options'],
|
||||||
|
'config': {
|
||||||
|
'foods': {'type':'string'},
|
||||||
|
'keywords': {'type': 'string'},
|
||||||
|
'books': {'type': 'string'},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export class Actions {
|
||||||
|
static CREATE = {
|
||||||
|
"function": "create"
|
||||||
|
}
|
||||||
|
static UPDATE = {
|
||||||
|
"function": "partialUpdate",
|
||||||
|
}
|
||||||
|
static DELETE = {
|
||||||
|
"function": "destroy",
|
||||||
|
'params': ['id']
|
||||||
|
}
|
||||||
|
static FETCH = {
|
||||||
|
"function": "retrieve",
|
||||||
|
'params': ['id']
|
||||||
|
}
|
||||||
|
static LIST = {
|
||||||
|
"function": "list",
|
||||||
|
"suffix": "s",
|
||||||
|
"params": ['query', 'page', 'pageSize'],
|
||||||
|
"config": {
|
||||||
|
'query': {'default':undefined},
|
||||||
|
'page': {'default': 1},
|
||||||
|
'pageSize': {'default': 25}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
static MERGE = {
|
||||||
|
"function": "merge",
|
||||||
|
'params': ['source', 'target'],
|
||||||
|
"config": {
|
||||||
|
'source': {'type':'string'},
|
||||||
|
'target': {'type': 'string'}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
static MOVE = {
|
||||||
|
"function": "move",
|
||||||
|
'params': ['source', 'target'],
|
||||||
|
"config": {
|
||||||
|
'source': {'type':'string'},
|
||||||
|
'target': {'type': 'string'}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -162,145 +162,120 @@ import axios from "axios";
|
|||||||
axios.defaults.xsrfCookieName = 'csrftoken'
|
axios.defaults.xsrfCookieName = 'csrftoken'
|
||||||
axios.defaults.xsrfHeaderName = "X-CSRFTOKEN"
|
axios.defaults.xsrfHeaderName = "X-CSRFTOKEN"
|
||||||
export const ApiMixin = {
|
export const ApiMixin = {
|
||||||
data() {
|
methods: {
|
||||||
return {
|
/**
|
||||||
api_settings: {
|
* constructs OpenAPI Generator function using named parameters
|
||||||
// TODO consider moving this to an API type dictionary that contains api types with details on the function call name, suffix, etc
|
* @param {string} model string to define which model API to use
|
||||||
'suffix': { // if OpenApiGenerator adds a suffix to model name in function calls
|
* @param {string} api string to define which of the API functions to use
|
||||||
'list': 's'
|
* @param {object} options dictionary to define all of the parameters necessary to use API
|
||||||
},
|
*/
|
||||||
// model specific settings, when not provided will use defaults instead
|
genericAPI: function(model, action, options) {
|
||||||
'food': { // must be lowercase
|
let setup = getConfig(model, action)
|
||||||
// *REQUIRED* name is name of the model used for translations and looking up APIFactory
|
let func = setup.function
|
||||||
'name': 'Food',
|
let config = setup?.config ?? {}
|
||||||
// *OPTIONAL: parameters will be built model -> model_type -> default
|
let params = setup?.params ?? []
|
||||||
'model_type': 'tree',
|
console.log('config', config, 'params', params)
|
||||||
// *OPTIONAL* model specific params for api, if not present will attempt modeltype_create then default_create
|
let parameters = []
|
||||||
// an array will create a dict of name:value pairs
|
|
||||||
'create': [['name', 'description', 'recipe', 'ignore_shopping', 'supermarket_category']], // required: unordered array of fields that can be set during create
|
|
||||||
// *OPTIONAL* model specific params for api, includes ordered list of parameters
|
|
||||||
// and an unordered array that will be converted to a dictionary and passed as the 2nd param
|
|
||||||
'partialUpdate': ['id', // required: ordered array of list params to patch existing object
|
|
||||||
['name', 'description', 'recipe', 'ignore_shopping', 'supermarket_category'] // must include ordered array of field names that can be updated
|
|
||||||
],
|
|
||||||
// *OPTIONAL* provide model specific typing
|
|
||||||
// 'typing': {},
|
|
||||||
},
|
|
||||||
'keyword': {},
|
|
||||||
'unit': {},
|
|
||||||
'recipe': {
|
|
||||||
'name': 'Recipe',
|
|
||||||
'list': ['query', 'keywords', 'foods', 'books', 'keywordsOr', 'foodsOr', 'booksOr', 'internal', 'random', '_new', 'page', 'pageSize', 'options'],
|
|
||||||
'typing': {
|
|
||||||
'list': {
|
|
||||||
'foods': 'string',
|
|
||||||
'keywords': 'string',
|
|
||||||
'books': 'string',
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
},
|
let this_value = undefined
|
||||||
// collection of default attributes for model_type TREE. All settings except typing and values will be overwritten by model values
|
params.forEach(function (item, index) {
|
||||||
'tree': {
|
if (Array.isArray(item)) {
|
||||||
'values': {
|
this_value = {}
|
||||||
'root': 'getFunction()', // if default value is exactly 'getFunction()' will call getFunction(model_type, param, params)
|
// if the value is an array, convert it to a dictionary of key:value
|
||||||
'tree': undefined,
|
// filtered based on OPTIONS passed
|
||||||
},
|
// maybe map/reduce is better?
|
||||||
'list': ['query', 'root', 'tree', 'page', 'pageSize'], // ordered array of list params for tree
|
for (const [k, v] of Object.entries(options)) {
|
||||||
'typing': {
|
if (item.includes(k)) {
|
||||||
'move': {
|
this_value[k] = formatParam(config?.[k], v)
|
||||||
'source': 'string',
|
}
|
||||||
'target': 'string',
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
// collection of global defaults. All settings except typing and values will be overwritten by model_type or model values
|
|
||||||
'default': {
|
|
||||||
'list': ['query', 'page', 'pageSize'], // ordered array of list params for default listApi
|
|
||||||
'destroy': ['id'], // ordered array of list params for default deleteApi
|
|
||||||
'retrieve': ['id'], // ordered array of list params for default retrieveApi
|
|
||||||
'merge': ['source', 'target'], // ordered array of list params for default mergeApi
|
|
||||||
'move': ['source', 'target'], // ordered array of list params for default moveApi
|
|
||||||
'create': [], // ordered array of list params for default createApi
|
|
||||||
'partialUpdate': [], // ordered array of list params for default updateApi
|
|
||||||
'values': {
|
|
||||||
'query': undefined, // default values for list API
|
|
||||||
'page': 1,
|
|
||||||
'pageSize': 25,
|
|
||||||
},
|
|
||||||
'typing': { // optional settings to force type on parameters
|
|
||||||
'merge': {
|
|
||||||
'source': 'string',
|
|
||||||
'target': 'string',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
/**
|
|
||||||
* constructs OpenAPI Generator function using named parameters
|
|
||||||
* @param {string} model string to define which model API to use
|
|
||||||
* @param {string} api string to define which of the API functions to use
|
|
||||||
* @param {object} options dictionary to define all of the parameters necessary to use API
|
|
||||||
*/
|
|
||||||
genericAPI: function(model, api, options) {
|
|
||||||
model = model.toLowerCase()
|
|
||||||
// construct settings for this model and this api - values are assigned in order (default is overwritten by api overwritten by model)
|
|
||||||
let settings = {...this.api_settings?.default ?? {}, ...this.api_settings?.[this.api_settings?.[model]?.model_type] ?? {}, ...this.api_settings[model]};
|
|
||||||
// values and typing are also dicts and need to be merged,
|
|
||||||
settings.values = {...this.api_settings?.default?.values ?? {},
|
|
||||||
...this.api_settings?.[this.api_settings?.[model]?.model_type]?.values ?? {},
|
|
||||||
...this.api_settings[model].values}
|
|
||||||
settings.typing = {...this.api_settings?.default?.typing?.[api] ?? {},
|
|
||||||
...this.api_settings?.[this.api_settings?.[model]?.model_type]?.typing?.[api] ?? {},
|
|
||||||
...this.api_settings[model].typing?.[api]}
|
|
||||||
let func = api + settings.name + (this.api_settings.suffix?.[api] ?? '')
|
|
||||||
// does model params exist?
|
|
||||||
let params = []
|
|
||||||
let this_value = undefined
|
|
||||||
settings[api].forEach(function (item, index) {
|
|
||||||
|
|
||||||
if (Array.isArray(item)) {
|
|
||||||
this_value = {}
|
|
||||||
for (const [k, v] of Object.entries(options)) { // filters options dict based on valus in array, I'm sure there's a better way to do this
|
|
||||||
if (item.includes(k)) {
|
|
||||||
this_value[k] = v
|
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
this_value = options?.[item] ?? undefined
|
||||||
|
if (this_value) {this_value = formatParam(config?.[item], this_value)}
|
||||||
}
|
}
|
||||||
} else {
|
// if no value is found so far, get the default if it exists
|
||||||
this_value = options?.[item] ?? settings.values?.[item] ?? undefined // set the value or apply default
|
if (!this_value) {
|
||||||
}
|
this_value = getDefault(config?.[item], options)
|
||||||
if (this_value ==='getFunction()') {
|
|
||||||
this_value = getFunction(item, settings?.model_type, options)
|
|
||||||
}
|
|
||||||
if (Object.keys(settings?.typing).includes(item)) {
|
|
||||||
switch (settings.typing[item]) {
|
|
||||||
case 'string':
|
|
||||||
if (this_value) {this_value = String(this_value)}
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
parameters.push(this_value)
|
||||||
params.push(this_value)
|
});
|
||||||
});
|
|
||||||
|
console.log(func, 'parameters', parameters, 'passed options', options)
|
||||||
let apiClient = new ApiApiFactory()
|
let apiClient = new ApiApiFactory()
|
||||||
return apiClient[func](...params)
|
return apiClient[func](...parameters)
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
// /*
|
||||||
* Utility functions to calculate default value
|
// * local functions for ApiMixin
|
||||||
* */
|
// * */
|
||||||
export function getFunction(item, model_type, params) {
|
function formatParam(config, value) {
|
||||||
if (item==='root' && model_type==='tree') {
|
if (config) {
|
||||||
if ((!params?.query ?? undefined) || params?.query?.length == 0) {
|
for (const [k, v] of Object.entries(config)) {
|
||||||
return 0
|
switch(k) {
|
||||||
|
case 'type':
|
||||||
|
switch(v) {
|
||||||
|
case 'string':
|
||||||
|
value = String(value)
|
||||||
|
break;
|
||||||
|
case 'integer':
|
||||||
|
value = parseInt(value)
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
function getDefault(config, options) {
|
||||||
|
let value = undefined
|
||||||
|
value = config?.default ?? undefined
|
||||||
|
if (typeof(value) === 'object') {
|
||||||
|
let condition = false
|
||||||
|
switch(value.function) {
|
||||||
|
// CONDITIONAL case requires 4 keys:
|
||||||
|
// - check: which other OPTIONS key to check against
|
||||||
|
// - operator: what type of operation to perform
|
||||||
|
// - true: what value to assign when true
|
||||||
|
// - false: what value to assign when false
|
||||||
|
case 'CONDITIONAL':
|
||||||
|
switch(value.operator) {
|
||||||
|
case 'not_exist':
|
||||||
|
condition = (
|
||||||
|
(!options?.[value.check] ?? undefined)
|
||||||
|
|| options?.[value.check]?.length == 0
|
||||||
|
)
|
||||||
|
if (condition) {
|
||||||
|
value = value.true
|
||||||
|
} else {
|
||||||
|
value = value.false
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
return undefined
|
|
||||||
}
|
}
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
function getConfig(model, action) {
|
||||||
|
let f = action.function
|
||||||
|
// if not defined partialUpdate will use params from create
|
||||||
|
if (f === 'partialUpdate' && !model?.[f]?.params) {
|
||||||
|
model[f] = {'params': [...['id'], ...model.create.params]}
|
||||||
|
}
|
||||||
|
|
||||||
|
let config = {
|
||||||
|
'name': model.name,
|
||||||
|
'function': f + model.name + action?.suffix
|
||||||
|
}
|
||||||
|
// spread operator merges dictionaries - last item in list takes precedence
|
||||||
|
config = {...config, ...action, ...model.model_type?.[f], ...model?.[f]}
|
||||||
|
// nested dictionaries are not merged - so merge again on any nested keys
|
||||||
|
config.config = {...action?.config, ...model.model_type?.[f]?.config, ...model?.[f]?.config}
|
||||||
|
config.function = config.function + config.name + (config?.suffix ?? '') // parens are required to force optional chaining to evaluate before concat
|
||||||
|
return config
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -365,4 +340,5 @@ export const CardMixin = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user