From 80699c833a7133bcd5a8acbbab6095fe677ca1c2 Mon Sep 17 00:00:00 2001 From: Rushabh Mehta Date: Mon, 1 Jan 2018 15:58:30 +0530 Subject: [PATCH] get_all with filters --- README.md | 118 +++++++++++++++++++++++++++++--- frappe/model/database.js | 68 +++++++++++++++--- frappe/tests/test_controller.js | 2 +- frappe/tests/test_database.js | 41 +++++++++-- frappe/tests/test_document.js | 2 +- frappe/tests/test_meta.js | 1 + frappe/tests/test_models.js | 1 + 7 files changed, 209 insertions(+), 24 deletions(-) diff --git a/README.md b/README.md index 41514abf..6cd2bc61 100644 --- a/README.md +++ b/README.md @@ -2,22 +2,124 @@ Core libs for Frappe Framework JS -### Examples +## Examples + +### Declaring Models + +Models are declared by adding a `.json` model file in the `models/doctype` folder of the module/app. + + { + "autoname": "hash", + "name": "ToDo", + "doctype": "DocType", + "issingle": 0, + "fields": [ + { + "fieldname": "subject", + "label": "Subject", + "fieldtype": "Data", + "reqd": 1 + }, + { + "fieldname": "description", + "label": "Description", + "fieldtype": "Text" + }, + { + "fieldname": "status", + "label": "Status", + "fieldtype": "Select", + "options": [ + "Open", + "Closed" + ], + "default": "Open", + "reqd": 1 + } + ] + } + +### Setup / Migrate const frappe = require('frappe-core); - # init database frappe.init(); - # make a new todo + // sync all schema from `models` folders in all apps + frappe.migrate(); + +### Managing Documents + +Frappe Object-Relational-Mapper (ORM) helps you manage (create, read, update, delete) documents based on the DocTypes declared + +#### Create + + const frappe = require('frappe-core); + frappe.init(); + + // make a new todo let todo = frappe.get_doc({doctype: 'ToDo', subject: 'something'}) todo.insert() - # get all todos - let total_open = 0; - for (let d of frappe.get_all('ToDo')) { - todo = frappe.get_doc('ToDo', d.name); - if (todo.status == 'Open') total_open += 1; +#### Read + + const frappe = require('frappe-core); + frappe.init(); + + // get all open todos + let todos = frappe.db.get_all('ToDo', ['name'], {status: "Open"}); + let first_todo = frappe.get_doc('ToDo', toods.name); + + +#### Update + + const frappe = require('frappe-core); + frappe.init(); + + // get all open todos + let todos = frappe.db.get_all('ToDo', ['name'], {status: "Open"}); + let first_todo = frappe.get_doc('ToDo', toods.name); + + first_todo.status = 'Closed'; + first_todo.update(); + +### Metadata + + const frappe = require('frappe-core); + + frappe.init(); + + let todo_meta = frappe.get_meta('ToDo'); + + // get all fields of type "Data" + let data_fields = todo_meta.fields.map(d => d.fieldtype=='Data' ? d : null); + +### Controllers + +You can write event handlers in controllers, by declaring a `.js` file in the `models/doctype/` folder along with the model file. + +The name of the class must be the slugged name of the DocType + + const frappe = require('frappe-core'); + + class todo extends frappe.document.Document { + validate() { + // set default status as "Open" if not set + if (!this.status) { + this.status = 'Open'; + } + } } + module.exports = { todo: todo }; + +### Database + +You can also directly write SQL with `frappe.db.sql` + + const frappe = require('frappe-core); + + frappe.init(); + + all_todos = frappe.db.sql('select name from todo'); diff --git a/frappe/model/database.js b/frappe/model/database.js index 88c9048e..7e0966bd 100644 --- a/frappe/model/database.js +++ b/frappe/model/database.js @@ -43,20 +43,21 @@ class Database { } } + migrate() { + for (let doctype in frappe.models.path_map.doctype) { + if (this.table_exists(doctype)) { + this.alter_table(doctype); + } else { + this.create_table(doctype); + } + } + } + create_table(doctype) { let meta = frappe.get_meta(doctype); let columns = []; - // add standard fields - let fields = frappe.model.standard_fields.slice(); - if (meta.istable) { - fields = fields.concat(model.child_fields); - } - - // add model fields - fields = fields.concat(meta.fields); - - for (let df of fields) { + for (let df of this.get_fields(meta)) { if (frappe.model.type_map[df.fieldtype]) { columns.push(`${df.fieldname} ${frappe.model.type_map[df.fieldtype]} ${df.reqd ? "not null" : ""} ${df.default ? ("default " + frappe.utils.sqlescape(df.default)) : ""}`); } @@ -68,6 +69,13 @@ class Database { return this.sql(query); } + alter_table(doctype) { + // add columns + + // change columns + + } + get(doctype, name) { let doc = frappe.db.sql(`select * from ${frappe.slug(doctype)} where name = ${frappe.db.escape(name)}`); return doc ? doc[0] : {}; @@ -88,6 +96,29 @@ class Database { set ${assigns.join(", ")}`); } + get_all(doctype, fields=['name'], filters, start, limit) { + return this.sql(`select ${fields.join(", ")} + from ${frappe.slug(doctype)} + ${filters ? "where" : ""} ${this.get_filter_conditions(filters)} + ${limit ? ("limit " + limit) : ""} ${start ? ("offset " + start) : ""}`); + } + + get_filter_conditions(filters) { + // {"status": "Open"} => `status = "Open"` + // {"status": "Open", "name": ["like", "apple%"]} + // => `status="Open" and name like "apple%" + let conditions = []; + for (let key in filters) { + const value = filters[key]; + if (value instanceof Array) { + conditions.push(`${key} ${value[0]} ${this.escape(value)}`); + } else { + conditions.push(`${key} = ${this.escape(value)}`); + } + } + return conditions.join(" and "); + } + sql(query, opts={}) { //console.log(query); const result = frappe.db._conn.exec(query); @@ -109,6 +140,23 @@ class Database { escape(value) { return frappe.utils.sqlescape(value); } + + get_fields(meta) { + // add standard fields + let fields = frappe.model.standard_fields.slice(); + if (meta.istable) { + fields = fields.concat(frappe.model.child_fields); + } + + // add model fields + fields = fields.concat(meta.fields); + + return fields; + } + + table_exists(table) { + return this.sql(`SELECT name FROM sqlite_master WHERE type='table' AND name='${table}'`) ? true : false; + } } function sql_result_to_obj(result) { diff --git a/frappe/tests/test_controller.js b/frappe/tests/test_controller.js index 2f90fd7e..8a50d5cd 100644 --- a/frappe/tests/test_controller.js +++ b/frappe/tests/test_controller.js @@ -4,7 +4,7 @@ const frappe = require('frappe-core'); describe('Controller', () => { before(function() { frappe.init(); - frappe.db.create_table('ToDo'); + frappe.db.migrate(); }); it('should call controller method', () => { diff --git a/frappe/tests/test_database.js b/frappe/tests/test_database.js index b539c136..8ea152bc 100644 --- a/frappe/tests/test_database.js +++ b/frappe/tests/test_database.js @@ -4,10 +4,43 @@ const frappe = require('frappe-core'); describe('Document', () => { before(function() { frappe.init(); + frappe.db.migrate(); }); - // it('should create a table', () => { - // frappe.db.create_table('ToDo'); - // frappe.db.write(); - // }); + it('should insert and get values', () => { + frappe.db.sql('delete from todo'); + frappe.get_doc({doctype:'ToDo', subject: 'testing 1'}).insert(); + frappe.get_doc({doctype:'ToDo', subject: 'testing 3'}).insert(); + frappe.get_doc({doctype:'ToDo', subject: 'testing 2'}).insert(); + + let subjects = frappe.db.get_all('ToDo', ['name', 'subject']).map(d => d.subject); + + assert.ok(subjects.includes('testing 1')); + assert.ok(subjects.includes('testing 2')); + assert.ok(subjects.includes('testing 3')); + }); + + it('should filter correct values', () => { + let subjects = null; + + frappe.db.sql('delete from todo'); + frappe.get_doc({doctype:'ToDo', subject: 'testing 1', status: 'Open'}).insert(); + frappe.get_doc({doctype:'ToDo', subject: 'testing 3', status: 'Open'}).insert(); + frappe.get_doc({doctype:'ToDo', subject: 'testing 2', status: 'Closed'}).insert(); + + subjects = frappe.db.get_all('ToDo', ['name', 'subject'], + {status: 'Open'}).map(d => d.subject); + + assert.ok(subjects.includes('testing 1')); + assert.ok(subjects.includes('testing 3')); + assert.equal(subjects.includes('testing 2'), false); + + subjects = frappe.db.get_all('ToDo', ['name', 'subject'], + {status: 'Closed'}).map(d => d.subject); + + assert.equal(subjects.includes('testing 1'), false); + assert.equal(subjects.includes('testing 3'), false); + assert.ok(subjects.includes('testing 2')); + + }); }); \ No newline at end of file diff --git a/frappe/tests/test_document.js b/frappe/tests/test_document.js index b4c07dd9..08ca8be9 100644 --- a/frappe/tests/test_document.js +++ b/frappe/tests/test_document.js @@ -4,7 +4,7 @@ const frappe = require('frappe-core'); describe('Document', () => { before(function() { frappe.init(); - frappe.db.create_table('ToDo'); + frappe.db.migrate(); }); it('should insert a doc', () => { diff --git a/frappe/tests/test_meta.js b/frappe/tests/test_meta.js index 3f56a894..6d2cc9a7 100644 --- a/frappe/tests/test_meta.js +++ b/frappe/tests/test_meta.js @@ -4,6 +4,7 @@ const frappe = require('frappe-core'); describe('Meta', () => { before(function() { frappe.init(); + frappe.db.migrate(); }); it('should get init from json file', () => { diff --git a/frappe/tests/test_models.js b/frappe/tests/test_models.js index 072a068c..0da55671 100644 --- a/frappe/tests/test_models.js +++ b/frappe/tests/test_models.js @@ -4,6 +4,7 @@ const frappe = require('frappe-core'); describe('Models', () => { before(function() { frappe.init(); + frappe.db.migrate(); }); it('should get todo json', () => {