From 92355be627eabde99aa1b901112dafff3f12fb07 Mon Sep 17 00:00:00 2001 From: Rushabh Mehta Date: Mon, 12 Feb 2018 17:31:31 +0530 Subject: [PATCH] added singles --- backends/database.js | 118 ++++++++++++++---- backends/sqlite.js | 13 +- index.js | 16 ++- model/boilerplate.js | 6 +- model/document.js | 48 +++++-- model/index.js | 2 +- model/meta.js | 27 ++-- models/doctype/number_series/number_series.js | 8 +- .../doctype/number_series/number_series.json | 6 +- models/doctype/role/role.json | 6 +- models/doctype/session/session.json | 6 +- models/doctype/single_value/single_value.js | 16 +++ models/doctype/single_value/single_value.json | 27 ++++ .../system_settings/system_settings.js | 16 +++ .../system_settings/system_settings.json | 21 ++++ models/doctype/todo/todo.json | 4 +- models/doctype/user/user.json | 6 +- models/doctype/user_role/user_role.json | 6 +- server/rest_api.js | 2 +- tests/test_meta.js | 2 +- tests/test_models.js | 2 +- tests/test_number_series.js | 6 +- tests/test_single.js | 24 ++++ 23 files changed, 308 insertions(+), 80 deletions(-) create mode 100644 models/doctype/single_value/single_value.js create mode 100644 models/doctype/single_value/single_value.json create mode 100644 models/doctype/system_settings/system_settings.js create mode 100644 models/doctype/system_settings/system_settings.json create mode 100644 tests/test_single.js diff --git a/backends/database.js b/backends/database.js index 91085c6d..5512ec50 100644 --- a/backends/database.js +++ b/backends/database.js @@ -17,12 +17,14 @@ module.exports = class Database { for (let doctype in frappe.modules) { // check if controller module if (frappe.modules[doctype].Meta) { - if (await this.tableExists(doctype)) { - await this.alterTable(doctype); - } else { - await this.createTable(doctype); + let meta = frappe.getMeta(doctype); + if (!meta.isSingle) { + if (await this.tableExists(doctype)) { + await this.alterTable(doctype); + } else { + await this.createTable(doctype); + } } - } } await this.commit(); @@ -33,9 +35,9 @@ module.exports = class Database { let columns = []; let values = []; - for (let df of meta.getValidFields({ with_children: false })) { - if (this.type_map[df.fieldtype]) { - columns.push(this.getColumnDefinition(df)); + for (let field of meta.getValidFields({ withChildren: false })) { + if (this.type_map[field.fieldtype]) { + columns.push(this.getColumnDefinition(field)); } } @@ -60,7 +62,7 @@ module.exports = class Database { let meta = frappe.getMeta(doctype); let values = []; - for (let field of meta.getValidFields({ with_children: false })) { + for (let field of meta.getValidFields({ withChildren: false })) { if (!tableColumns.includes(field.fieldname) && this.type_map[field.fieldtype]) { values = [] if (field.default) { @@ -79,12 +81,25 @@ module.exports = class Database { // alter table {doctype} add column ({column_def}); } - async get(doctype, name, fields = '*') { - // load parent - let doc = await this.getOne(doctype, name, fields); + async get(doctype, name=null, fields = '*') { + let meta = frappe.getMeta(doctype); + let doc; + if (meta.isSingle) { + doc = await this.getSingle(doctype); + doc.name = doctype; + } else { + if (!name) { + throw frappe.errors.ValueError('name is mandatory'); + } + doc = await this.getOne(doctype, name, fields); + } + await this.loadChildren(doc, meta); + return doc; + } + async loadChildren(doc, meta) { // load children - let tableFields = frappe.getMeta(doctype).getTableFields(); + let tableFields = meta.getTableFields(); for (let field of tableFields) { doc[field.fieldname] = await this.getAll({ doctype: field.childtype, @@ -94,6 +109,20 @@ module.exports = class Database { order: 'asc' }); } + } + + async getSingle(doctype) { + let values = await this.getAll({ + doctype: 'Single Value', + fields: ['fieldname', 'value'], + filters: { parent: doctype }, + order_by: 'fieldname', + order: 'asc' + }); + let doc = {}; + for (let row of values) { + doc[row.fieldname] = row.value; + } return doc; } @@ -109,11 +138,23 @@ module.exports = class Database { } async insert(doctype, doc) { - // insert parent - await this.insertOne(doctype, doc); + let meta = frappe.getMeta(doctype); + + // insert parent + if (meta.isSingle) { + await this.updateSingle(meta, doc, doctype); + } else { + await this.insertOne(doctype, doc); + } // insert children - let tableFields = frappe.getMeta(doctype).getTableFields(); + await this.insertChildren(meta, doc, doctype); + + return doc; + } + + async insertChildren(meta, doc, doctype) { + let tableFields = meta.getTableFields(); for (let field of tableFields) { let idx = 0; for (let child of (doc[field.fieldname] || [])) { @@ -122,8 +163,6 @@ module.exports = class Database { idx++; } } - - return doc; } async insertOne(doctype, doc) { @@ -131,28 +170,37 @@ module.exports = class Database { } async update(doctype, doc) { + let meta = frappe.getMeta(doctype); + // update parent - await this.updateOne(doctype, doc); + if (meta.isSingle) { + await this.updateSingle(meta, doc, doctype); + } else { + await this.updateOne(doctype, doc); + } // insert or update children - let tableFields = frappe.getMeta(doctype).getTableFields(); - for (let field of tableFields) { + await this.updateChildren(meta, doc, doctype); + return doc; + } + async updateChildren(meta, doc, doctype) { + let tableFields = meta.getTableFields(); + for (let field of tableFields) { // first key is "parent" - for SQL params let added = [doc.name]; for (let child of (doc[field.fieldname] || [])) { this.prepareChild(doctype, doc.name, child, field, added.length - 1); if (await this.exists(field.childtype, child.name)) { await this.updateOne(field.childtype, child); - } else { + } + else { await this.insertOne(field.childtype, child); } added.push(child.name); } - await this.runDeleteOtherChildren(field, added); } - return doc; } async updateOne(doctype, doc) { @@ -163,6 +211,26 @@ module.exports = class Database { // delete from doctype where parent = ? and name not in (?, ?, ?) } + async updateSingle(meta, doc, doctype) { + await this.deleteSingleValues(); + for (let field of meta.getValidFields({withChildren: false})) { + let value = doc[field.fieldname]; + if (value) { + let singleValue = frappe.newDoc({ + doctype: 'Single Value', + parent: doctype, + fieldname: field.fieldname, + value: value + }) + await singleValue.insert(); + } + } + } + + async deleteSingleValues(name) { + // await frappe.db.run('delete from single_value where parent=?', name) + } + prepareChild(parenttype, parent, child, field, idx) { if (!child.name) { child.name = frappe.getRandomName(); @@ -174,7 +242,7 @@ module.exports = class Database { } getKeys(doctype) { - return frappe.getMeta(doctype).getValidFields({ with_children: false }); + return frappe.getMeta(doctype).getValidFields({ withChildren: false }); } getFormattedValues(fields, doc) { diff --git a/backends/sqlite.js b/backends/sqlite.js index 1cf80064..f07c84e6 100644 --- a/backends/sqlite.js +++ b/backends/sqlite.js @@ -97,7 +97,11 @@ module.exports = class sqliteDatabase extends Database { } async deleteChildren(parenttype, parent) { - await this.run(`delete from ${parent} where parent=?`, parent); + await this.run(`delete from ${parenttype} where parent=?`, parent); + } + + async deleteSingleValues(name) { + await frappe.db.run('delete from single_value where parent=?', name) } getAll({ doctype, fields, filters, start, limit, order_by = 'modified', order = 'desc' } = {}) { @@ -106,12 +110,13 @@ module.exports = class sqliteDatabase extends Database { } return new Promise((resolve, reject) => { let conditions = this.getFilterConditions(filters); - - this.conn.all(`select ${fields.join(", ")} + let query = `select ${fields.join(", ")} from ${frappe.slug(doctype)} ${conditions.conditions ? "where" : ""} ${conditions.conditions} ${order_by ? ("order by " + order_by) : ""} ${order_by ? (order || "asc") : ""} - ${limit ? ("limit " + limit) : ""} ${start ? ("offset " + start) : ""}`, conditions.values, + ${limit ? ("limit " + limit) : ""} ${start ? ("offset " + start) : ""}`; + + this.conn.all(query, conditions.values, (err, rows) => { if (err) { reject(err); diff --git a/index.js b/index.js index 2c01fa1d..926937f8 100644 --- a/index.js +++ b/index.js @@ -59,17 +59,23 @@ module.exports = { async getDoc(doctype, name) { let doc = this.getDocFromCache(doctype, name); if (!doc) { - let controller_class = this.getControllerClass(doctype); - doc = new controller_class({doctype:doctype, name: name}); + let controllerClass = this.getControllerClass(doctype); + doc = new controllerClass({doctype:doctype, name: name}); await doc.load(); this.addToCache(doc); } return doc; }, + async getSingle(doctype) { + return await this.getDoc(doctype, doctype); + }, + newDoc(data) { - let controller_class = this.getControllerClass(data.doctype); - return new controller_class(data); + let controllerClass = this.getControllerClass(data.doctype); + let doc = new controllerClass(data); + doc.setDefaults(); + return doc; }, getControllerClass(doctype) { @@ -83,8 +89,8 @@ module.exports = { async getNewDoc(doctype) { let doc = this.newDoc({doctype: doctype}); - doc.setName(); doc._notInserted = true; + doc.name = this.getRandomName(); this.addToCache(doc); return doc; }, diff --git a/model/boilerplate.js b/model/boilerplate.js index 7ab6d203..7f7f07d5 100644 --- a/model/boilerplate.js +++ b/model/boilerplate.js @@ -9,9 +9,9 @@ module.exports = { fs.writeFileSync(`./models/doctype/${utils.slug(name)}/${utils.slug(name)}.json`, `{ "name": "${name}", "doctype": "DocType", - "is_single": 0, - "is_child": 0, - "keyword_fields": [], + "isSingle": 0, + "isChild": 0, + "keywordFields": [], "fields": [ { "fieldname": "name", diff --git a/model/document.js b/model/document.js index 69aea3d0..d67e3f5d 100644 --- a/model/document.js +++ b/model/document.js @@ -23,6 +23,20 @@ module.exports = class BaseDocument { this.handlers[key].push(method || key); } + get meta() { + if (!this._meta) { + this._meta = frappe.getMeta(this.doctype); + } + return this._meta; + } + + async getSettings() { + if (!this._settings) { + this._settings = await frappe.getSingle(this.meta.settings); + } + return this._settings; + } + get(fieldname) { return this[fieldname]; } @@ -39,7 +53,22 @@ module.exports = class BaseDocument { } } - setName() { + async setName() { + // name === doctype for Single + if (this.meta.isSingle) { + this.name = this.meta.name; + return; + } + + if (this.meta.settings) { + const number_series = (await this.getSettings()).number_series; + console.log(1, number_series); + if(number_series) { + this.name = await frappe.model.getSeriesNext(number_series); + console.log(2, this.name); + } + } + // assign a random name by default // override this to set a name if (!this.name) { @@ -47,6 +76,14 @@ module.exports = class BaseDocument { } } + setDefaults() { + for (let field of this.meta.fields) { + if (!this[field.fieldname] && field.default) { + this[field.fieldname] = field.default; + } + } + } + setKeywords() { let keywords = []; for (let fieldname of this.meta.getKeywordFields()) { @@ -55,13 +92,6 @@ module.exports = class BaseDocument { this.keywords = keywords.join(', '); } - get meta() { - if (!this._meta) { - this._meta = frappe.getMeta(this.doctype); - } - return this._meta; - } - append(key, document) { if (!this[key]) { this[key] = []; @@ -170,7 +200,7 @@ module.exports = class BaseDocument { async commit() { // re-run triggers - this.setName(); + await this.setName(); this.setStandardValues(); this.setKeywords(); this.setChildIdx(); diff --git a/model/index.js b/model/index.js index 2729db88..223487e6 100644 --- a/model/index.js +++ b/model/index.js @@ -1,7 +1,7 @@ const frappe = require('frappejs'); module.exports = { - async get_series_next(prefix) { + async getSeriesNext(prefix) { let series; try { series = await frappe.getDoc('Number Series', prefix); diff --git a/model/meta.js b/model/meta.js index 979774d0..66fe23d7 100644 --- a/model/meta.js +++ b/model/meta.js @@ -70,15 +70,15 @@ module.exports = class BaseMeta extends BaseDocument { return this[fieldname]; } - getValidFields({ with_children = true } = {}) { + getValidFields({ withChildren = true } = {}) { if (!this._valid_fields) { this._valid_fields = []; - this._valid_fields_with_children = []; + this._valid_fields_withChildren = []; const _add = (field) => { this._valid_fields.push(field); - this._valid_fields_with_children.push(field); + this._valid_fields_withChildren.push(field); } const doctype_fields = this.fields.map((field) => field.fieldname); @@ -90,7 +90,7 @@ module.exports = class BaseMeta extends BaseDocument { } } - if (this.is_child) { + if (this.isChild) { // child fields for (let field of frappe.model.child_fields) { if (frappe.db.type_map[field.fieldtype] && !doctype_fields.includes(field.fieldname)) { @@ -114,22 +114,31 @@ module.exports = class BaseMeta extends BaseDocument { _add(field); } - // include tables if (with_children = True) + // include tables if (withChildren = True) if (!include && field.fieldtype === 'Table') { - this._valid_fields_with_children.push(field); + this._valid_fields_withChildren.push(field); } } } - if (with_children) { - return this._valid_fields_with_children; + if (withChildren) { + return this._valid_fields_withChildren; } else { return this._valid_fields; } } getKeywordFields() { - return this.keyword_fields || this.meta.fields.filter(field => field.required).map(field => field.fieldname); + if (!this._keywordFields) { + this._keywordFields = this.keywordFields; + if (!(this._keywordFields && this._keywordFields.length && this.fields)) { + this._keywordFields = this.fields.filter(field => field.required).map(field => field.fieldname); + } + if (!(this._keywordFields && this._keywordFields.length)) { + this._keywordFields = ['name'] + } + } + return this._keywordFields; } validate_select(field, value) { diff --git a/models/doctype/number_series/number_series.js b/models/doctype/number_series/number_series.js index c8f6c372..34ec2926 100644 --- a/models/doctype/number_series/number_series.js +++ b/models/doctype/number_series/number_series.js @@ -8,10 +8,16 @@ class NumberSeriesMeta extends BaseMeta { } class NumberSeries extends BaseDocument { - async next() { + setup() { + this.addHandler('validate'); + } + validate() { if (this.current===null || this.current===undefined) { this.current = 0; } + } + async next() { + this.validate(); this.current++; await this.update(); return this.current; diff --git a/models/doctype/number_series/number_series.json b/models/doctype/number_series/number_series.json index 2924aa56..9465d44f 100644 --- a/models/doctype/number_series/number_series.json +++ b/models/doctype/number_series/number_series.json @@ -1,9 +1,9 @@ { "name": "Number Series", "doctype": "DocType", - "is_single": 0, - "is_child": 0, - "keyword_fields": [], + "isSingle": 0, + "isChild": 0, + "keywordFields": [], "fields": [ { "fieldname": "name", diff --git a/models/doctype/role/role.json b/models/doctype/role/role.json index 232528b1..68d31502 100644 --- a/models/doctype/role/role.json +++ b/models/doctype/role/role.json @@ -1,9 +1,9 @@ { "name": "Role", "doctype": "DocType", - "is_single": 0, - "is_child": 0, - "keyword_fields": [], + "isSingle": 0, + "isChild": 0, + "keywordFields": [], "fields": [ { "fieldname": "name", diff --git a/models/doctype/session/session.json b/models/doctype/session/session.json index aad7d487..6e8e5480 100644 --- a/models/doctype/session/session.json +++ b/models/doctype/session/session.json @@ -1,9 +1,9 @@ { "name": "Session", "doctype": "DocType", - "is_single": 0, - "is_child": 0, - "keyword_fields": [], + "isSingle": 0, + "isChild": 0, + "keywordFields": [], "fields": [ { "fieldname": "username", diff --git a/models/doctype/single_value/single_value.js b/models/doctype/single_value/single_value.js new file mode 100644 index 00000000..194b1b3f --- /dev/null +++ b/models/doctype/single_value/single_value.js @@ -0,0 +1,16 @@ +const BaseMeta = require('frappejs/model/meta'); +const BaseDocument = require('frappejs/model/document'); + +class SingleValueMeta extends BaseMeta { + setupMeta() { + Object.assign(this, require('./single_value.json')); + } +} + +class SingleValue extends BaseDocument { +} + +module.exports = { + Document: SingleValue, + Meta: SingleValueMeta +}; \ No newline at end of file diff --git a/models/doctype/single_value/single_value.json b/models/doctype/single_value/single_value.json new file mode 100644 index 00000000..9ae1e6a0 --- /dev/null +++ b/models/doctype/single_value/single_value.json @@ -0,0 +1,27 @@ +{ + "name": "Single Value", + "doctype": "DocType", + "isSingle": 0, + "isChild": 0, + "keywordFields": [], + "fields": [ + { + "fieldname": "parent", + "label": "Parent", + "fieldtype": "Data", + "required": 1 + }, + { + "fieldname": "fieldname", + "label": "Fieldname", + "fieldtype": "Data", + "required": 1 + }, + { + "fieldname": "value", + "label": "Value", + "fieldtype": "Data", + "required": 1 + } + ] +} \ No newline at end of file diff --git a/models/doctype/system_settings/system_settings.js b/models/doctype/system_settings/system_settings.js new file mode 100644 index 00000000..7013d491 --- /dev/null +++ b/models/doctype/system_settings/system_settings.js @@ -0,0 +1,16 @@ +const BaseMeta = require('frappejs/model/meta'); +const BaseDocument = require('frappejs/model/document'); + +class SystemSettingsMeta extends BaseMeta { + setupMeta() { + Object.assign(this, require('./system_settings.json')); + } +} + +class SystemSettings extends BaseDocument { +} + +module.exports = { + Document: SystemSettings, + Meta: SystemSettingsMeta +}; \ No newline at end of file diff --git a/models/doctype/system_settings/system_settings.json b/models/doctype/system_settings/system_settings.json new file mode 100644 index 00000000..440d6ff3 --- /dev/null +++ b/models/doctype/system_settings/system_settings.json @@ -0,0 +1,21 @@ +{ + "name": "System Settings", + "doctype": "DocType", + "isSingle": 1, + "isChild": 0, + "keywordFields": [], + "fields": [ + { + "fieldname": "dateFormat", + "label": "Date Format", + "fieldtype": "Select", + "options": [ + "dd/mm/yyyy", + "mm/dd/yyyy", + "dd-mm-yyyy", + "mm-dd-yyyy" + ], + "required": 1 + } + ] +} \ No newline at end of file diff --git a/models/doctype/todo/todo.json b/models/doctype/todo/todo.json index 7d2eff2d..7ba60ef1 100644 --- a/models/doctype/todo/todo.json +++ b/models/doctype/todo/todo.json @@ -2,8 +2,8 @@ "autoname": "hash", "name": "ToDo", "doctype": "DocType", - "is_single": 0, - "keyword_fields": [ + "isSingle": 0, + "keywordFields": [ "subject", "description" ], diff --git a/models/doctype/user/user.json b/models/doctype/user/user.json index d3d46398..f158ed33 100644 --- a/models/doctype/user/user.json +++ b/models/doctype/user/user.json @@ -1,9 +1,9 @@ { "name": "User", "doctype": "DocType", - "is_single": 0, - "is_child": 0, - "keyword_fields": [ + "isSingle": 0, + "isChild": 0, + "keywordFields": [ "name", "full_name" ], diff --git a/models/doctype/user_role/user_role.json b/models/doctype/user_role/user_role.json index d6778e93..9c9420c6 100644 --- a/models/doctype/user_role/user_role.json +++ b/models/doctype/user_role/user_role.json @@ -1,9 +1,9 @@ { "name": "User Role", "doctype": "DocType", - "is_single": 0, - "is_child": 1, - "keyword_fields": [], + "isSingle": 0, + "isChild": 1, + "keywordFields": [], "fields": [ { "fieldname": "role", diff --git a/server/rest_api.js b/server/rest_api.js index dde97708..ca1c21d7 100644 --- a/server/rest_api.js +++ b/server/rest_api.js @@ -4,7 +4,7 @@ module.exports = { setup(app) { // get list app.get('/api/resource/:doctype', frappe.async_handler(async function(request, response) { - for (key of ['fields', 'filters']) { + for (let key of ['fields', 'filters']) { if (request.query[key]) { request.query[key] = JSON.parse(request.query[key]); } diff --git a/tests/test_meta.js b/tests/test_meta.js index 4585ebe4..161e6081 100644 --- a/tests/test_meta.js +++ b/tests/test_meta.js @@ -9,7 +9,7 @@ describe('Meta', () => { it('should get init from json file', () => { let todo = frappe.getMeta('ToDo'); - assert.equal(todo.is_single, 0); + assert.equal(todo.isSingle, 0); }); it('should get fields from meta', () => { diff --git a/tests/test_models.js b/tests/test_models.js index 6a3e0f85..99f33472 100644 --- a/tests/test_models.js +++ b/tests/test_models.js @@ -9,6 +9,6 @@ describe('Models', () => { it('should get todo json', () => { let todo = frappe.getMeta('todo'); - assert.equal(todo.is_single, 0); + assert.equal(todo.isSingle, 0); }); }); \ No newline at end of file diff --git a/tests/test_number_series.js b/tests/test_number_series.js index b6cd8e2c..8eb8ace2 100644 --- a/tests/test_number_series.js +++ b/tests/test_number_series.js @@ -9,8 +9,8 @@ describe('Number Series', () => { it('should start a series and get next value', async () => { frappe.db.delete('Number Series', 'test-series-') - assert.equal(await frappe.model.get_series_next('test-series-'), 'test-series-1'); - assert.equal(await frappe.model.get_series_next('test-series-'), 'test-series-2'); - assert.equal(await frappe.model.get_series_next('test-series-'), 'test-series-3'); + assert.equal(await frappe.model.getSeriesNext('test-series-'), 'test-series-1'); + assert.equal(await frappe.model.getSeriesNext('test-series-'), 'test-series-2'); + assert.equal(await frappe.model.getSeriesNext('test-series-'), 'test-series-3'); }); }); \ No newline at end of file diff --git a/tests/test_single.js b/tests/test_single.js new file mode 100644 index 00000000..19a8d01a --- /dev/null +++ b/tests/test_single.js @@ -0,0 +1,24 @@ +const assert = require('assert'); +const frappe = require('frappejs'); +const helpers = require('./helpers'); + +describe('Single Documents', () => { + before(async function() { + await helpers.init_sqlite(); + }); + + it('should set a single value', async () => { + let systemSettings = await frappe.getSingle('System Settings'); + systemSettings.dateFormat = 'dd/mm/yyyy'; + await systemSettings.update(); + + systemSettings = await frappe.getSingle('System Settings'); + assert.equal(systemSettings.dateFormat, 'dd/mm/yyyy'); + + systemSettings.dateFormat = 'mm/dd/yyyy'; + await systemSettings.update(); + + systemSettings = await frappe.getSingle('System Settings'); + assert.equal(systemSettings.dateFormat, 'mm/dd/yyyy'); + }); +}); \ No newline at end of file