mirror of
https://github.com/frappe/books.git
synced 2024-09-20 03:29:00 +00:00
added user, role, parent-child CRUD
This commit is contained in:
parent
f3407d79f5
commit
d5c03b5303
@ -112,34 +112,58 @@ class sqliteDatabase {
|
||||
// insert children
|
||||
let table_fields = frappe.get_meta(doctype).get_table_fields();
|
||||
for (let field of table_fields) {
|
||||
let idx = 0;
|
||||
for (let child of (doc[field.fieldname] || [])) {
|
||||
this.prepare_child(doctype, doc.name, child, field, idx);
|
||||
await this.insert_one(field.childtype, child);
|
||||
idx++;
|
||||
}
|
||||
}
|
||||
|
||||
return doc;
|
||||
}
|
||||
|
||||
async insert_one(doctype, doc) {
|
||||
let fields = this.get_keys(doctype);
|
||||
let placeholders = fields.map(d => '?').join(', ');
|
||||
|
||||
if (!doc.name) {
|
||||
doc.name = frappe.get_random_name();
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
async update(doctype, doc) {
|
||||
// updates parent and child, similar to insert, but writing separately for clarity
|
||||
|
||||
// update parent
|
||||
await this.update_one(doctype, doc);
|
||||
|
||||
// update children
|
||||
// insert or update children
|
||||
let table_fields = frappe.get_meta(doctype).get_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] || [])) {
|
||||
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) {
|
||||
@ -154,6 +178,13 @@ class sqliteDatabase {
|
||||
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) {
|
||||
return frappe.get_meta(doctype).get_valid_fields({ with_children: false });
|
||||
}
|
||||
@ -171,7 +202,32 @@ class sqliteDatabase {
|
||||
}
|
||||
|
||||
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' } = {}) {
|
||||
@ -227,7 +283,9 @@ class sqliteDatabase {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.conn.run(query, params, (err) => {
|
||||
if (err) {
|
||||
if (debug) {
|
||||
console.error(err);
|
||||
}
|
||||
reject(err);
|
||||
} else {
|
||||
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) {
|
||||
const name = await this.sql(`SELECT name FROM sqlite_master WHERE type='table' AND name='${table}'`);
|
||||
return (name && name.length) ? true : false;
|
||||
|
22
index.js
22
index.js
@ -56,21 +56,20 @@ module.exports = {
|
||||
}
|
||||
},
|
||||
|
||||
async get_doc(data, name) {
|
||||
if (typeof data==='string' && typeof name==='string') {
|
||||
let doc = this.get_doc_from_cache(data, name);
|
||||
async get_doc(doctype, name) {
|
||||
let doc = this.get_doc_from_cache(doctype, name);
|
||||
if (!doc) {
|
||||
let controller_class = this.get_controller_class(data);
|
||||
doc = new controller_class({doctype:data, name: name});
|
||||
let controller_class = this.get_controller_class(doctype);
|
||||
doc = new controller_class({doctype:doctype, name: name});
|
||||
await doc.load();
|
||||
this.add_to_cache(doc);
|
||||
}
|
||||
return doc;
|
||||
} else {
|
||||
},
|
||||
|
||||
new_doc(data) {
|
||||
let controller_class = this.get_controller_class(data.doctype);
|
||||
var doc = new controller_class(data);
|
||||
}
|
||||
return doc;
|
||||
return new controller_class(data);
|
||||
},
|
||||
|
||||
get_controller_class(doctype) {
|
||||
@ -83,7 +82,7 @@ module.exports = {
|
||||
},
|
||||
|
||||
async get_new_doc(doctype) {
|
||||
let doc = await frappe.get_doc({doctype: doctype});
|
||||
let doc = frappe.new_doc({doctype: doctype});
|
||||
doc.set_name();
|
||||
doc.__not_inserted = true;
|
||||
this.add_to_cache(doc);
|
||||
@ -91,8 +90,7 @@ module.exports = {
|
||||
},
|
||||
|
||||
async insert(data) {
|
||||
const doc = await this.get_doc(data);
|
||||
return await doc.insert();
|
||||
return await (this.new_doc(data)).insert();
|
||||
},
|
||||
|
||||
login(user='guest', user_key) {
|
||||
|
@ -10,7 +10,7 @@ module.exports = {
|
||||
"name": "${name}",
|
||||
"doctype": "DocType",
|
||||
"is_single": 0,
|
||||
"istable": 0,
|
||||
"is_child": 0,
|
||||
"keyword_fields": [],
|
||||
"fields": [
|
||||
{
|
||||
|
@ -36,7 +36,7 @@ module.exports = class BaseDocument {
|
||||
// assign a random name by default
|
||||
// override this to set a 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() {
|
||||
let doc = {};
|
||||
let data = {};
|
||||
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() {
|
||||
@ -102,21 +102,48 @@ module.exports = class BaseDocument {
|
||||
async load() {
|
||||
let data = await frappe.db.get(this.doctype, this.name);
|
||||
if (data.name) {
|
||||
Object.assign(this, data);
|
||||
this.sync_values(data);
|
||||
} else {
|
||||
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() {
|
||||
this.set_name();
|
||||
this.set_standard_values();
|
||||
this.set_keywords();
|
||||
await this.trigger('validate');
|
||||
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_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() {
|
||||
@ -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;
|
||||
}
|
||||
};
|
@ -48,14 +48,21 @@ module.exports = class BaseMeta extends BaseDocument {
|
||||
|
||||
get_valid_fields({ with_children = true } = {}) {
|
||||
if (!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);
|
||||
|
||||
// standard fields
|
||||
for (let field of frappe.model.common_fields) {
|
||||
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
|
||||
for (let field of frappe.model.child_fields) {
|
||||
if (frappe.db.type_map[field.fieldtype] && !doctype_fields.includes(field.fieldname)) {
|
||||
this._valid_fields.push(field);
|
||||
_add(field);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// parent fields
|
||||
for (let field of frappe.model.parent_fields) {
|
||||
if (frappe.db.type_map[field.fieldtype] && !doctype_fields.includes(field.fieldname)) {
|
||||
this._valid_fields.push(field);
|
||||
_add(field);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -79,18 +86,23 @@ module.exports = class BaseMeta extends BaseDocument {
|
||||
for (let field of this.fields) {
|
||||
let include = frappe.db.type_map[field.fieldtype];
|
||||
|
||||
// include tables if (with_children = True)
|
||||
if (!include && with_children) {
|
||||
include = field.fieldtype === 'Table';
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (with_children) {
|
||||
return this._valid_fields_with_children;
|
||||
} else {
|
||||
return this._valid_fields;
|
||||
}
|
||||
}
|
||||
|
||||
get_keyword_fields() {
|
||||
return this.keyword_fields || this.meta.fields.filter(field => field.reqd).map(field => field.fieldname);
|
||||
|
16
models/doctype/role/role.js
Normal file
16
models/doctype/role/role.js
Normal 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
|
||||
};
|
15
models/doctype/role/role.json
Normal file
15
models/doctype/role/role.json
Normal 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
|
||||
}
|
||||
]
|
||||
}
|
16
models/doctype/user/user.js
Normal file
16
models/doctype/user/user.js
Normal 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
|
||||
};
|
30
models/doctype/user/user.json
Normal file
30
models/doctype/user/user.json
Normal 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"
|
||||
}
|
||||
]
|
||||
}
|
16
models/doctype/user_role/user_role.js
Normal file
16
models/doctype/user_role/user_role.js
Normal 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
|
||||
};
|
15
models/doctype/user_role/user_role.json
Normal file
15
models/doctype/user_role/user_role.json
Normal 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"
|
||||
}
|
||||
]
|
||||
}
|
@ -27,7 +27,7 @@ module.exports = {
|
||||
app.post('/api/resource/:doctype', frappe.async_handler(async function(request, response) {
|
||||
data = request.body;
|
||||
data.doctype = request.params.doctype;
|
||||
let doc = await frappe.get_doc(data);
|
||||
let doc = frappe.new_doc(data);
|
||||
await doc.insert();
|
||||
await frappe.db.commit();
|
||||
return response.json(doc.get_valid_dict());
|
||||
|
@ -8,7 +8,7 @@ describe('Controller', () => {
|
||||
});
|
||||
|
||||
it('should call controller method', async () => {
|
||||
let doc = await frappe.get_doc({
|
||||
let doc = frappe.new_doc({
|
||||
doctype:'ToDo',
|
||||
subject: 'test'
|
||||
});
|
||||
|
@ -8,7 +8,7 @@ describe('Document', () => {
|
||||
});
|
||||
|
||||
it('should insert a doc', async () => {
|
||||
let doc1 = await test_doc();
|
||||
let doc1 = test_doc();
|
||||
doc1.subject = 'insert subject 1';
|
||||
doc1.description = 'insert description 1';
|
||||
await doc1.insert();
|
||||
@ -20,7 +20,7 @@ describe('Document', () => {
|
||||
});
|
||||
|
||||
it('should update a doc', async () => {
|
||||
let doc = await test_doc();
|
||||
let doc = test_doc();
|
||||
await doc.insert();
|
||||
|
||||
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 () => {
|
||||
let doc = await test_doc();
|
||||
let doc = test_doc();
|
||||
assert.equal(doc.get('subject'), 'testing 1');
|
||||
});
|
||||
|
||||
it('should set a value', async () => {
|
||||
let doc = await test_doc();
|
||||
let doc = test_doc();
|
||||
doc.set('subject', 'testing 1')
|
||||
assert.equal(doc.get('subject'), 'testing 1');
|
||||
});
|
||||
|
||||
it('should not allow incorrect Select option', async () => {
|
||||
let doc = await test_doc();
|
||||
let doc = test_doc();
|
||||
try {
|
||||
await doc.set('status', 'Illegal');
|
||||
assert.fail();
|
||||
@ -53,7 +53,7 @@ describe('Document', () => {
|
||||
});
|
||||
|
||||
it('should delete a document', async () => {
|
||||
let doc = await test_doc();
|
||||
let doc = test_doc();
|
||||
await doc.insert();
|
||||
|
||||
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);
|
||||
});
|
||||
|
||||
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() {
|
||||
return await frappe.get_doc({
|
||||
function test_doc() {
|
||||
return frappe.new_doc({
|
||||
doctype: 'ToDo',
|
||||
status: 'Open',
|
||||
subject: 'testing 1',
|
||||
|
@ -32,7 +32,7 @@ describe('REST', () => {
|
||||
});
|
||||
|
||||
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();
|
||||
|
||||
let doc1 = await frappe.get_doc('ToDo', doc.name);
|
||||
@ -42,7 +42,7 @@ describe('REST', () => {
|
||||
});
|
||||
|
||||
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();
|
||||
|
||||
doc.subject = 'subject changed';
|
||||
|
@ -7,6 +7,10 @@ Object.assign(utils, {
|
||||
return text.toLowerCase().replace(/ /g, '_');
|
||||
},
|
||||
|
||||
get_random_name() {
|
||||
return Math.random().toString(36).substr(3);
|
||||
},
|
||||
|
||||
async_handler(fn) {
|
||||
return (req, res, next) => Promise.resolve(fn(req, res, next))
|
||||
.catch((err) => {
|
||||
|
Loading…
Reference in New Issue
Block a user