2
0
mirror of https://github.com/frappe/books.git synced 2024-11-10 07:40:55 +00:00

added user, role, parent-child CRUD

This commit is contained in:
Rushabh Mehta 2018-02-01 16:37:36 +05:30
parent f3407d79f5
commit d5c03b5303
16 changed files with 284 additions and 77 deletions

View File

@ -112,34 +112,58 @@ class sqliteDatabase {
// insert children // insert children
let table_fields = frappe.get_meta(doctype).get_table_fields(); let table_fields = frappe.get_meta(doctype).get_table_fields();
for (let field of table_fields) { for (let field of table_fields) {
let idx = 0;
for (let child of (doc[field.fieldname] || [])) { for (let child of (doc[field.fieldname] || [])) {
this.prepare_child(doctype, doc.name, child, field, idx);
await this.insert_one(field.childtype, child); await this.insert_one(field.childtype, child);
idx++;
} }
} }
return doc;
} }
async insert_one(doctype, doc) { async insert_one(doctype, doc) {
let fields = this.get_keys(doctype); let fields = this.get_keys(doctype);
let placeholders = fields.map(d => '?').join(', '); let placeholders = fields.map(d => '?').join(', ');
if (!doc.name) {
doc.name = frappe.get_random_name();
}
return await this.run(`insert into ${frappe.slug(doctype)} return await this.run(`insert into ${frappe.slug(doctype)}
(${Object.keys(doc).join(", ")}) (${fields.map(field => field.fieldname).join(", ")})
values (${placeholders})`, this.get_formatted_values(fields, doc)); values (${placeholders})`, this.get_formatted_values(fields, doc));
} }
async update(doctype, doc) { async update(doctype, doc) {
// updates parent and child, similar to insert, but writing separately for clarity
// update parent // update parent
await this.update_one(doctype, doc); await this.update_one(doctype, doc);
// update children // insert or update children
let table_fields = frappe.get_meta(doctype).get_table_fields(); let table_fields = frappe.get_meta(doctype).get_table_fields();
for (let field of table_fields) { for (let field of table_fields) {
// first key is "parent" - for SQL params
let added_children = [doc.name];
for (let child of (doc[field.fieldname] || [])) { for (let child of (doc[field.fieldname] || [])) {
await this.update_one(field.childtype, child); this.prepare_child(doctype, doc.name, child, field, added_children.length - 1);
if (await this.exists(field.childtype, child.name)) {
await this.update_one(field.childtype, child);
} else {
await this.insert_one(field.childtype, child);
}
added_children.push(child.name);
} }
// delete other children
// `delete from doctype where parent = ? and name not in (?, ?, ?)}`
await this.run(`delete from ${frappe.slug(field.childtype)}
where
parent = ? and
name not in (${added_children.map(d => '?').join(', ')})`, added_children);
} }
return doc;
} }
async update_one(doctype, doc) { async update_one(doctype, doc) {
@ -154,6 +178,13 @@ class sqliteDatabase {
set ${assigns.join(", ")} where name=?`, values); set ${assigns.join(", ")} where name=?`, values);
} }
prepare_child(parenttype, parent, child, field, idx) {
child.parent = parent;
child.parenttype = parenttype;
child.parentfield = field.fieldname;
child.idx = idx;
}
get_keys(doctype) { get_keys(doctype) {
return frappe.get_meta(doctype).get_valid_fields({ with_children: false }); return frappe.get_meta(doctype).get_valid_fields({ with_children: false });
} }
@ -171,7 +202,32 @@ class sqliteDatabase {
} }
async delete(doctype, name) { async delete(doctype, name) {
return await this.run(`delete from ${frappe.slug(doctype)} where name=?`, name); await this.run(`delete from ${frappe.slug(doctype)} where name=?`, name);
// delete children
let table_fields = frappe.get_meta(doctype).get_table_fields();
for (let field of table_fields) {
await this.run(`delete from ${frappe.slug(field.childtype)} where parent=?`, name)
}
}
async exists(doctype, name) {
return (await this.get_value(doctype, name)) ? true : false;
}
async get_value(doctype, filters, fieldname = 'name') {
if (typeof filters === 'string') {
filters = { name: filters };
}
let row = await this.get_all({
doctype: doctype,
fields: [fieldname],
filters: filters,
start: 0,
limit: 1
});
return row.length ? row[0][fieldname] : null;
} }
get_all({ doctype, fields, filters, start, limit, order_by = 'modified', order = 'desc' } = {}) { get_all({ doctype, fields, filters, start, limit, order_by = 'modified', order = 'desc' } = {}) {
@ -227,7 +283,9 @@ class sqliteDatabase {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
this.conn.run(query, params, (err) => { this.conn.run(query, params, (err) => {
if (err) { if (err) {
console.error(err); if (debug) {
console.error(err);
}
reject(err); reject(err);
} else { } else {
resolve(); resolve();
@ -254,21 +312,6 @@ class sqliteDatabase {
} }
} }
async get_value(doctype, filters, fieldname = 'name') {
if (typeof filters === 'string') {
filters = { name: filters };
}
let row = await this.get_all({
doctype: doctype,
fields: [fieldname],
filters: filters,
start: 0,
limit: 1
});
return row.length ? row[0][fieldname] : null;
}
async table_exists(table) { async table_exists(table) {
const name = await this.sql(`SELECT name FROM sqlite_master WHERE type='table' AND name='${table}'`); const name = await this.sql(`SELECT name FROM sqlite_master WHERE type='table' AND name='${table}'`);
return (name && name.length) ? true : false; return (name && name.length) ? true : false;

View File

@ -56,23 +56,22 @@ module.exports = {
} }
}, },
async get_doc(data, name) { async get_doc(doctype, name) {
if (typeof data==='string' && typeof name==='string') { let doc = this.get_doc_from_cache(doctype, name);
let doc = this.get_doc_from_cache(data, name); if (!doc) {
if (!doc) { let controller_class = this.get_controller_class(doctype);
let controller_class = this.get_controller_class(data); doc = new controller_class({doctype:doctype, name: name});
doc = new controller_class({doctype:data, name: name}); await doc.load();
await doc.load(); this.add_to_cache(doc);
this.add_to_cache(doc);
}
return doc;
} else {
let controller_class = this.get_controller_class(data.doctype);
var doc = new controller_class(data);
} }
return doc; return doc;
}, },
new_doc(data) {
let controller_class = this.get_controller_class(data.doctype);
return new controller_class(data);
},
get_controller_class(doctype) { get_controller_class(doctype) {
doctype = this.slug(doctype); doctype = this.slug(doctype);
if (this.modules[doctype] && this.modules[doctype].Document) { if (this.modules[doctype] && this.modules[doctype].Document) {
@ -83,7 +82,7 @@ module.exports = {
}, },
async get_new_doc(doctype) { async get_new_doc(doctype) {
let doc = await frappe.get_doc({doctype: doctype}); let doc = frappe.new_doc({doctype: doctype});
doc.set_name(); doc.set_name();
doc.__not_inserted = true; doc.__not_inserted = true;
this.add_to_cache(doc); this.add_to_cache(doc);
@ -91,8 +90,7 @@ module.exports = {
}, },
async insert(data) { async insert(data) {
const doc = await this.get_doc(data); return await (this.new_doc(data)).insert();
return await doc.insert();
}, },
login(user='guest', user_key) { login(user='guest', user_key) {

View File

@ -10,7 +10,7 @@ module.exports = {
"name": "${name}", "name": "${name}",
"doctype": "DocType", "doctype": "DocType",
"is_single": 0, "is_single": 0,
"istable": 0, "is_child": 0,
"keyword_fields": [], "keyword_fields": [],
"fields": [ "fields": [
{ {

View File

@ -36,7 +36,7 @@ module.exports = class BaseDocument {
// assign a random name by default // assign a random name by default
// override this to set a name // override this to set a name
if (!this.name) { if (!this.name) {
this.name = Math.random().toString(36).substr(3); this.name = frappe.get_random_name();
} }
} }
@ -79,11 +79,11 @@ module.exports = class BaseDocument {
} }
get_valid_dict() { get_valid_dict() {
let doc = {}; let data = {};
for (let field of this.meta.get_valid_fields()) { for (let field of this.meta.get_valid_fields()) {
doc[field.fieldname] = this.get(field.fieldname); data[field.fieldname] = this[field.fieldname];
} }
return doc; return data;
} }
set_standard_values() { set_standard_values() {
@ -102,21 +102,48 @@ module.exports = class BaseDocument {
async load() { async load() {
let data = await frappe.db.get(this.doctype, this.name); let data = await frappe.db.get(this.doctype, this.name);
if (data.name) { if (data.name) {
Object.assign(this, data); this.sync_values(data);
} else { } else {
throw new frappe.errors.NotFound(`Not Found: ${this.doctype} ${this.name}`); throw new frappe.errors.NotFound(`Not Found: ${this.doctype} ${this.name}`);
} }
} }
sync_values(data) {
this.clear_values();
Object.assign(this, data);
}
clear_values() {
for (let field of this.meta.get_valid_fields()) {
if(this[field.fieldname]) {
delete this[field.fieldname];
}
}
}
async insert() { async insert() {
this.set_name(); this.set_name();
this.set_standard_values(); this.set_standard_values();
this.set_keywords(); this.set_keywords();
await this.trigger('validate'); await this.trigger('validate');
await this.trigger('before_insert'); await this.trigger('before_insert');
await frappe.db.insert(this.doctype, this.get_valid_dict()); this.sync_values(await frappe.db.insert(this.doctype, this.get_valid_dict()));
await this.trigger('after_insert'); await this.trigger('after_insert');
await this.trigger('after_save'); await this.trigger('after_save');
return this;
}
async update() {
this.set_standard_values();
this.set_keywords();
await this.trigger('validate');
await this.trigger('before_update');
this.sync_values(await frappe.db.update(this.doctype, this.get_valid_dict()));
await this.trigger('after_update');
await this.trigger('after_save');
return this;
} }
async delete() { async delete() {
@ -136,15 +163,4 @@ module.exports = class BaseDocument {
} }
} }
} }
async update() {
this.set_standard_values();
this.set_keywords();
await this.trigger('validate');
await this.trigger('before_update');
await frappe.db.update(this.doctype, this.get_valid_dict());
await this.trigger('after_update');
await this.trigger('after_save');
return this;
}
}; };

View File

@ -48,14 +48,21 @@ module.exports = class BaseMeta extends BaseDocument {
get_valid_fields({ with_children = true } = {}) { get_valid_fields({ with_children = true } = {}) {
if (!this._valid_fields) { if (!this._valid_fields) {
this._valid_fields = []; this._valid_fields = [];
this._valid_fields_with_children = [];
const _add = (field) => {
this._valid_fields.push(field);
this._valid_fields_with_children.push(field);
}
const doctype_fields = this.fields.map((field) => field.fieldname); const doctype_fields = this.fields.map((field) => field.fieldname);
// standard fields // standard fields
for (let field of frappe.model.common_fields) { for (let field of frappe.model.common_fields) {
if (frappe.db.type_map[field.fieldtype] && !doctype_fields.includes(field.fieldname)) { if (frappe.db.type_map[field.fieldtype] && !doctype_fields.includes(field.fieldname)) {
this._valid_fields.push(field); _add(field);
} }
} }
@ -63,14 +70,14 @@ module.exports = class BaseMeta extends BaseDocument {
// child fields // child fields
for (let field of frappe.model.child_fields) { for (let field of frappe.model.child_fields) {
if (frappe.db.type_map[field.fieldtype] && !doctype_fields.includes(field.fieldname)) { if (frappe.db.type_map[field.fieldtype] && !doctype_fields.includes(field.fieldname)) {
this._valid_fields.push(field); _add(field);
} }
} }
} else { } else {
// parent fields // parent fields
for (let field of frappe.model.parent_fields) { for (let field of frappe.model.parent_fields) {
if (frappe.db.type_map[field.fieldtype] && !doctype_fields.includes(field.fieldname)) { if (frappe.db.type_map[field.fieldtype] && !doctype_fields.includes(field.fieldname)) {
this._valid_fields.push(field); _add(field);
} }
} }
} }
@ -79,17 +86,22 @@ module.exports = class BaseMeta extends BaseDocument {
for (let field of this.fields) { for (let field of this.fields) {
let include = frappe.db.type_map[field.fieldtype]; let include = frappe.db.type_map[field.fieldtype];
// include tables if (with_children = True)
if (!include && with_children) {
include = field.fieldtype === 'Table';
}
if (include) { if (include) {
this._valid_fields.push(field); _add(field);
}
// include tables if (with_children = True)
if (!include && field.fieldtype === 'Table') {
this._valid_fields_with_children.push(field);
} }
} }
} }
return this._valid_fields; if (with_children) {
return this._valid_fields_with_children;
} else {
return this._valid_fields;
}
} }
get_keyword_fields() { get_keyword_fields() {

View File

@ -0,0 +1,16 @@
const BaseMeta = require('frappejs/model/meta');
const BaseDocument = require('frappejs/model/document');
class RoleMeta extends BaseMeta {
setup_meta() {
Object.assign(this, require('./role.json'));
}
}
class Role extends BaseDocument {
}
module.exports = {
Document: Role,
Meta: RoleMeta
};

View File

@ -0,0 +1,15 @@
{
"name": "Role",
"doctype": "DocType",
"is_single": 0,
"is_child": 0,
"keyword_fields": [],
"fields": [
{
"fieldname": "name",
"label": "Name",
"fieldtype": "Data",
"reqd": 1
}
]
}

View File

@ -0,0 +1,16 @@
const BaseMeta = require('frappejs/model/meta');
const BaseDocument = require('frappejs/model/document');
class UserMeta extends BaseMeta {
setup_meta() {
Object.assign(this, require('./user.json'));
}
}
class User extends BaseDocument {
}
module.exports = {
Document: User,
Meta: UserMeta
};

View File

@ -0,0 +1,30 @@
{
"name": "User",
"doctype": "DocType",
"is_single": 0,
"is_child": 0,
"keyword_fields": [
"name",
"full_name"
],
"fields": [
{
"fieldname": "name",
"label": "Name",
"fieldtype": "Data",
"reqd": 1
},
{
"fieldname": "full_name",
"label": "Full Name",
"fieldtype": "Data",
"reqd": 1
},
{
"fieldname": "roles",
"label": "Roles",
"fieldtype": "Table",
"childtype": "User Role"
}
]
}

View File

@ -0,0 +1,16 @@
const BaseMeta = require('frappejs/model/meta');
const BaseDocument = require('frappejs/model/document');
class UserRoleMeta extends BaseMeta {
setup_meta() {
Object.assign(this, require('./user_role.json'));
}
}
class UserRole extends BaseDocument {
}
module.exports = {
Document: UserRole,
Meta: UserRoleMeta
};

View File

@ -0,0 +1,15 @@
{
"name": "User Role",
"doctype": "DocType",
"is_single": 0,
"is_child": 1,
"keyword_fields": [],
"fields": [
{
"fieldname": "role",
"label": "Role",
"fieldtype": "Link",
"target": "Role"
}
]
}

View File

@ -27,7 +27,7 @@ module.exports = {
app.post('/api/resource/:doctype', frappe.async_handler(async function(request, response) { app.post('/api/resource/:doctype', frappe.async_handler(async function(request, response) {
data = request.body; data = request.body;
data.doctype = request.params.doctype; data.doctype = request.params.doctype;
let doc = await frappe.get_doc(data); let doc = frappe.new_doc(data);
await doc.insert(); await doc.insert();
await frappe.db.commit(); await frappe.db.commit();
return response.json(doc.get_valid_dict()); return response.json(doc.get_valid_dict());

View File

@ -8,7 +8,7 @@ describe('Controller', () => {
}); });
it('should call controller method', async () => { it('should call controller method', async () => {
let doc = await frappe.get_doc({ let doc = frappe.new_doc({
doctype:'ToDo', doctype:'ToDo',
subject: 'test' subject: 'test'
}); });

View File

@ -8,7 +8,7 @@ describe('Document', () => {
}); });
it('should insert a doc', async () => { it('should insert a doc', async () => {
let doc1 = await test_doc(); let doc1 = test_doc();
doc1.subject = 'insert subject 1'; doc1.subject = 'insert subject 1';
doc1.description = 'insert description 1'; doc1.description = 'insert description 1';
await doc1.insert(); await doc1.insert();
@ -20,7 +20,7 @@ describe('Document', () => {
}); });
it('should update a doc', async () => { it('should update a doc', async () => {
let doc = await test_doc(); let doc = test_doc();
await doc.insert(); await doc.insert();
assert.notEqual(await frappe.db.get_value(doc.doctype, doc.name, 'subject'), 'subject 2'); assert.notEqual(await frappe.db.get_value(doc.doctype, doc.name, 'subject'), 'subject 2');
@ -32,18 +32,18 @@ describe('Document', () => {
}) })
it('should get a value', async () => { it('should get a value', async () => {
let doc = await test_doc(); let doc = test_doc();
assert.equal(doc.get('subject'), 'testing 1'); assert.equal(doc.get('subject'), 'testing 1');
}); });
it('should set a value', async () => { it('should set a value', async () => {
let doc = await test_doc(); let doc = test_doc();
doc.set('subject', 'testing 1') doc.set('subject', 'testing 1')
assert.equal(doc.get('subject'), 'testing 1'); assert.equal(doc.get('subject'), 'testing 1');
}); });
it('should not allow incorrect Select option', async () => { it('should not allow incorrect Select option', async () => {
let doc = await test_doc(); let doc = test_doc();
try { try {
await doc.set('status', 'Illegal'); await doc.set('status', 'Illegal');
assert.fail(); assert.fail();
@ -53,7 +53,7 @@ describe('Document', () => {
}); });
it('should delete a document', async () => { it('should delete a document', async () => {
let doc = await test_doc(); let doc = test_doc();
await doc.insert(); await doc.insert();
assert.equal(await frappe.db.get_value(doc.doctype, doc.name), doc.name); assert.equal(await frappe.db.get_value(doc.doctype, doc.name), doc.name);
@ -63,10 +63,36 @@ describe('Document', () => {
assert.equal(await frappe.db.get_value(doc.doctype, doc.name), null); assert.equal(await frappe.db.get_value(doc.doctype, doc.name), null);
}); });
it('should add, fetch and delete documents with children', async() => {
await frappe.new_doc({doctype: 'Role', name: 'Test Role'}).insert();
await frappe.new_doc({doctype: 'Role', name: 'Test Role 1'}).insert();
let user = frappe.new_doc({
doctype: 'User',
name: 'test_user',
full_name: 'Test User',
roles: [
{
role: 'Test Role'
}
]
});
await user.insert();
assert.equal(user.roles.length, 1);
assert.equal(user.roles[0].parent, user.name);
assert.equal(user.roles[0].parenttype, user.doctype);
assert.equal(user.roles[0].parentfield, 'roles');
// add another role
user.roles.push({role: 'Test Role 1'});
});
}); });
async function test_doc() { function test_doc() {
return await frappe.get_doc({ return frappe.new_doc({
doctype: 'ToDo', doctype: 'ToDo',
status: 'Open', status: 'Open',
subject: 'testing 1', subject: 'testing 1',

View File

@ -32,7 +32,7 @@ describe('REST', () => {
}); });
it('should create a document', async () => { it('should create a document', async () => {
let doc = await frappe.get_doc({doctype:'ToDo', subject:'test rest insert 1'}); let doc = frappe.new_doc({doctype:'ToDo', subject:'test rest insert 1'});
await doc.insert(); await doc.insert();
let doc1 = await frappe.get_doc('ToDo', doc.name); let doc1 = await frappe.get_doc('ToDo', doc.name);
@ -42,7 +42,7 @@ describe('REST', () => {
}); });
it('should update a document', async () => { it('should update a document', async () => {
let doc = await frappe.get_doc({doctype:'ToDo', subject:'test rest insert 1'}); let doc = frappe.new_doc({doctype:'ToDo', subject:'test rest insert 1'});
await doc.insert(); await doc.insert();
doc.subject = 'subject changed'; doc.subject = 'subject changed';

View File

@ -7,6 +7,10 @@ Object.assign(utils, {
return text.toLowerCase().replace(/ /g, '_'); return text.toLowerCase().replace(/ /g, '_');
}, },
get_random_name() {
return Math.random().toString(36).substr(3);
},
async_handler(fn) { async_handler(fn) {
return (req, res, next) => Promise.resolve(fn(req, res, next)) return (req, res, next) => Promise.resolve(fn(req, res, next))
.catch((err) => { .catch((err) => {