2
0
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:
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
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;

View File

@ -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) {

View File

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

View File

@ -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;
}
};

View File

@ -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);

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) {
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());

View File

@ -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'
});

View File

@ -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',

View File

@ -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';

View File

@ -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) => {