Browse Source

Merge branch 'develop'

version-14
Nabin Hait 8 years ago
parent
commit
6f5817d76d
31 changed files with 3045 additions and 2380 deletions
  1. +1
    -1
      frappe/__init__.py
  2. +301
    -0
      frappe/build.js
  3. +17
    -3
      frappe/build.py
  4. +6
    -4
      frappe/commands/utils.py
  5. +1246
    -1217
      frappe/core/doctype/docfield/docfield.json
  6. +2
    -1
      frappe/core/doctype/doctype/doctype.py
  7. +0
    -3
      frappe/custom/doctype/customize_form/customize_form.js
  8. +22
    -1
      frappe/custom/doctype/customize_form/customize_form.json
  9. +2
    -1
      frappe/custom/doctype/customize_form/customize_form.py
  10. +1139
    -1070
      frappe/custom/doctype/customize_form_field/customize_form_field.json
  11. +1
    -1
      frappe/desk/query_report.py
  12. +2
    -2
      frappe/frappeclient.py
  13. +2
    -1
      frappe/patches.txt
  14. +12
    -0
      frappe/patches/v8_0/install_new_build_system_requirements.py
  15. +3
    -3
      frappe/patches/v8_0/update_records_in_global_search.py
  16. +2
    -2
      frappe/public/build.json
  17. +8
    -1
      frappe/public/js/frappe/form/control.js
  18. +4
    -0
      frappe/public/js/frappe/form/grid.js
  19. +3
    -3
      frappe/public/js/frappe/list/list_renderer.js
  20. +4
    -3
      frappe/public/js/frappe/router.js
  21. +36
    -0
      frappe/public/js/frappe/socketio_client.js
  22. +11
    -6
      frappe/public/js/frappe/ui/messages.js
  23. +1
    -2
      frappe/public/js/frappe/views/image/image_view.js
  24. +34
    -13
      frappe/public/js/frappe/views/kanban/kanban_board.js
  25. +1
    -1
      frappe/public/js/frappe/views/kanban/kanban_view.js
  26. +1
    -1
      frappe/public/js/legacy/globals.js
  27. +1
    -1
      frappe/tests/test_global_search.py
  28. +161
    -39
      frappe/utils/global_search.py
  29. +9
    -0
      frappe/www/print.py
  30. +2
    -0
      socketio.js
  31. +11
    -0
      test_cafe.js

+ 1
- 1
frappe/__init__.py View File

@@ -13,7 +13,7 @@ import os, sys, importlib, inspect, json
from .exceptions import * from .exceptions import *
from .utils.jinja import get_jenv, get_template, render_template from .utils.jinja import get_jenv, get_template, render_template


__version__ = '8.0.19'
__version__ = '8.0.20'
__title__ = "Frappe Framework" __title__ = "Frappe Framework"


local = Local() local = Local()


+ 301
- 0
frappe/build.js View File

@@ -0,0 +1,301 @@
const path = require('path');
const fs = require('fs');
const babel = require('babel-core');
const less = require('less');
const chokidar = require('chokidar');

// for file watcher
const app = require('express')();
const http = require('http').Server(app);
const io = require('socket.io')(http);
const file_watcher_port = 6787;

const p = path.resolve;

// basic setup
const sites_path = p(__dirname, '..', '..', '..', 'sites');
const apps_path = p(__dirname, '..', '..', '..', 'apps'); // the apps folder
const apps_contents = fs.readFileSync(p(sites_path, 'apps.txt'), 'utf8');
const apps = apps_contents.split('\n');
const app_paths = apps.map(app => p(apps_path, app, app)) // base_path of each app
const assets_path = p(sites_path, 'assets');
const build_map = make_build_map();

// command line args
const action = process.argv[2] || '--build';

if (!['--build', '--watch'].includes(action)) {
console.log('Invalid argument: ', action);
return;
}

if (action === '--build') {
const minify = process.argv[3] === '--minify' ? true : false;
build({ minify });
}

if (action === '--watch') {
watch();
}

function build({ minify=false } = {}) {
for (const output_path in build_map) {
pack(output_path, build_map[output_path], minify);
}
}

let socket_connection = false;

function watch() {
http.listen(file_watcher_port, function () {
console.log('file watching on *:', file_watcher_port);
});

compile_less().then(() => {
build();
watch_less(function (filename) {
if(socket_connection) {
io.emit('reload_css', filename);
}
});
watch_js(function (filename) {
if(socket_connection) {
io.emit('reload_js', filename);
}
});
});

io.on('connection', function (socket) {
socket_connection = true;

socket.on('disconnect', function() {
socket_connection = false;
})
});
}

function pack(output_path, inputs, minify) {
const output_type = output_path.split('.').pop();

let output_txt = '';
for (const file of inputs) {

if (!fs.existsSync(file)) {
console.log('File not found: ', file);
continue;
}

let file_content = fs.readFileSync(file, 'utf-8');

if (file.endsWith('.html') && output_type === 'js') {
file_content = html_to_js_template(file, file_content);
}

if(file.endsWith('class.js')) {
file_content = minify_js(file_content, file);
}

if (file.endsWith('.js') && !file.includes('/lib/') && output_type === 'js' && !file.endsWith('class.js')) {
file_content = babelify(file_content, file, minify);
}

if(!minify) {
output_txt += `\n/*\n *\t${file}\n */\n`
}
output_txt += file_content;
}

const target = p(assets_path, output_path);

try {
fs.writeFileSync(target, output_txt);
console.log(`Wrote ${output_path} - ${get_file_size(target)}`);
return target;
} catch (e) {
console.log('Error writing to file', output_path);
console.log(e);
}
}

function babelify(content, path, minify) {
let presets = ['es2015', 'es2016'];
if(minify) {
presets.push('babili'); // new babel minifier
}
try {
return babel.transform(content, {
presets: presets,
comments: false
}).code;
} catch (e) {
console.log('Cannot babelify', path);
console.log(e);
return content;
}
}

function minify_js(content, path) {
try {
return babel.transform(content, {
comments: false
}).code;
} catch (e) {
console.log('Cannot minify', path);
console.log(e);
return content;
}
}

function make_build_map() {
const build_map = {};
for (const app_path of app_paths) {
const build_json_path = p(app_path, 'public', 'build.json');
if (!fs.existsSync(build_json_path)) continue;

let build_json = fs.readFileSync(build_json_path);
try {
build_json = JSON.parse(build_json);
} catch (e) {
console.log(e);
continue;
}

for (const target in build_json) {
const sources = build_json[target];

const new_sources = [];
for (const source of sources) {
const s = p(app_path, source);
new_sources.push(s);
}

if (new_sources.length)
build_json[target] = new_sources;
else
delete build_json[target];
}

Object.assign(build_map, build_json);
}
return build_map;
}

function compile_less() {
return new Promise(function (resolve) {
const promises = [];
for (const app_path of app_paths) {
const public_path = p(app_path, 'public');
const less_path = p(public_path, 'less');
if (!fs.existsSync(less_path)) continue;

const files = fs.readdirSync(less_path);
for (const file of files) {
if(file.includes('variables.less')) continue;
promises.push(compile_less_file(file, less_path, public_path))
}
}

Promise.all(promises).then(() => {
console.log('Less files compiled');
resolve();
});
});
}

function compile_less_file(file, less_path, public_path) {
const file_content = fs.readFileSync(p(less_path, file), 'utf8');
const output_file = file.split('.')[0] + '.css';
console.log('compiling', file);

return less.render(file_content, {
paths: [less_path],
filename: file,
sourceMap: false
}).then(output => {
const out_css = p(public_path, 'css', output_file);
fs.writeFileSync(out_css, output.css);
return out_css;
}).catch(e => {
console.log('Error compiling ', file);
console.log(e);
});
}

function watch_less(ondirty) {
const less_paths = app_paths.map(path => p(path, 'public', 'less'));

const to_watch = [];
for (const less_path of less_paths) {
if (!fs.existsSync(less_path)) continue;
to_watch.push(less_path);
}
chokidar.watch(to_watch).on('change', (filename, stats) => {
console.log(filename, 'dirty');
var last_index = filename.lastIndexOf('/');
const less_path = filename.slice(0, last_index);
const public_path = p(less_path, '..');
filename = filename.split('/').pop();

compile_less_file(filename, less_path, public_path)
.then(css_file_path => {
// build the target css file for which this css file is input
for (const target in build_map) {
const sources = build_map[target];
if (sources.includes(css_file_path)) {
pack(target, sources);
ondirty && ondirty(target);
break;
}
}
})
});
}

function watch_js(ondirty) {
const js_paths = app_paths.map(path => p(path, 'public', 'js'));

const to_watch = [];
for (const js_path of js_paths) {
if (!fs.existsSync(js_path)) continue;
to_watch.push(js_path);
}
chokidar.watch(to_watch).on('change', (filename, stats) => {
console.log(filename, 'dirty');
var last_index = filename.lastIndexOf('/');
const js_path = filename.slice(0, last_index);
const public_path = p(js_path, '..');

// build the target js file for which this js/html file is input
for (const target in build_map) {
const sources = build_map[target];
if (sources.includes(filename)) {
pack(target, sources);
ondirty && ondirty(target);
break;
}
}
});
}

function html_to_js_template(path, content) {
let key = path.split('/');
key = key[key.length - 1];
key = key.split('.')[0];

content = scrub_html_template(content);
return `frappe.templates['${key}'] = '${content}';\n`;
}

function scrub_html_template(content) {
content = content.replace(/\s/g, ' ');
content = content.replace(/(<!--.*?-->)/g, '');
return content.replace("'", "\'");
}

function get_file_size(filepath) {
const stats = fs.statSync(filepath);
const size = stats.size;
// convert it to humanly readable format.
const i = Math.floor(Math.log(size) / Math.log(1024));
return (size / Math.pow(1024, i)).toFixed(2) * 1 + ' ' + ['B', 'KB', 'MB', 'GB', 'TB'][i];
}

+ 17
- 3
frappe/build.py View File

@@ -3,6 +3,7 @@


from __future__ import unicode_literals from __future__ import unicode_literals
from frappe.utils.minify import JavascriptMinify from frappe.utils.minify import JavascriptMinify
import subprocess


""" """
Build the `public` folders and setup languages Build the `public` folders and setup languages
@@ -22,16 +23,30 @@ def setup():
except ImportError: pass except ImportError: pass
app_paths = [os.path.dirname(pymodule.__file__) for pymodule in pymodules] app_paths = [os.path.dirname(pymodule.__file__) for pymodule in pymodules]


def bundle(no_compress, make_copy=False, verbose=False):
def bundle(no_compress, make_copy=False, verbose=False, beta=False):
"""concat / minify js files""" """concat / minify js files"""
# build js files # build js files
setup() setup()


make_asset_dirs(make_copy=make_copy) make_asset_dirs(make_copy=make_copy)

if beta:
command = 'node ../apps/frappe/frappe/build.js --build'
if not no_compress:
command += ' --minify'
subprocess.call(command.split(' '))
return

build(no_compress, verbose) build(no_compress, verbose)


def watch(no_compress):
def watch(no_compress, beta=False):
"""watch and rebuild if necessary""" """watch and rebuild if necessary"""

if beta:
command = 'node ../apps/frappe/frappe/build.js --watch'
subprocess.Popen(command.split(' '))
return

setup() setup()


import time import time
@@ -101,7 +116,6 @@ def get_build_maps():
except ValueError, e: except ValueError, e:
print path print path
print 'JSON syntax error {0}'.format(str(e)) print 'JSON syntax error {0}'.format(str(e))

return build_maps return build_maps


timestamps = {} timestamps = {}


+ 6
- 4
frappe/commands/utils.py View File

@@ -8,19 +8,21 @@ from frappe.commands import pass_context, get_site
@click.command('build') @click.command('build')
@click.option('--make-copy', is_flag=True, default=False, help='Copy the files instead of symlinking') @click.option('--make-copy', is_flag=True, default=False, help='Copy the files instead of symlinking')
@click.option('--verbose', is_flag=True, default=False, help='Verbose') @click.option('--verbose', is_flag=True, default=False, help='Verbose')
def build(make_copy=False, verbose=False):
@click.option('--beta', is_flag=True, default=False, help='Use the new NodeJS build system')
def build(make_copy=False, verbose=False, beta=False):
"Minify + concatenate JS and CSS files, build translations" "Minify + concatenate JS and CSS files, build translations"
import frappe.build import frappe.build
import frappe import frappe
frappe.init('') frappe.init('')
frappe.build.bundle(False, make_copy=make_copy, verbose=verbose)
frappe.build.bundle(False, make_copy=make_copy, verbose=verbose, beta=beta)


@click.command('watch') @click.command('watch')
def watch():
@click.option('--beta', is_flag=True, default=False, help='Use the new NodeJS build system')
def watch(beta=False):
"Watch and concatenate JS and CSS files as and when they change" "Watch and concatenate JS and CSS files as and when they change"
import frappe.build import frappe.build
frappe.init('') frappe.init('')
frappe.build.watch(True)
frappe.build.watch(True, beta=beta)


@click.command('clear-cache') @click.command('clear-cache')
@pass_context @pass_context


+ 1246
- 1217
frappe/core/doctype/docfield/docfield.json
File diff suppressed because it is too large
View File


+ 2
- 1
frappe/core/doctype/doctype/doctype.py View File

@@ -397,7 +397,7 @@ def validate_fields(meta):
def check_link_table_options(d): def check_link_table_options(d):
if d.fieldtype in ("Link", "Table"): if d.fieldtype in ("Link", "Table"):
if not d.options: if not d.options:
frappe.throw(_("Options requried for Link or Table type field {0} in row {1}").format(d.label, d.idx))
frappe.throw(_("Options required for Link or Table type field {0} in row {1}").format(d.label, d.idx))
if d.options=="[Select]" or d.options==d.parent: if d.options=="[Select]" or d.options==d.parent:
return return
if d.options != d.parent: if d.options != d.parent:
@@ -571,6 +571,7 @@ def validate_fields(meta):


for d in fields: for d in fields:
if not d.permlevel: d.permlevel = 0 if not d.permlevel: d.permlevel = 0
if d.fieldtype != "Table": d.allow_bulk_edit = 0
if not d.fieldname: if not d.fieldname:
frappe.throw(_("Fieldname is required in row {0}").format(d.idx)) frappe.throw(_("Fieldname is required in row {0}").format(d.idx))
d.fieldname = d.fieldname.lower() d.fieldname = d.fieldname.lower()


+ 0
- 3
frappe/custom/doctype/customize_form/customize_form.js View File

@@ -4,9 +4,6 @@
frappe.provide("frappe.customize_form"); frappe.provide("frappe.customize_form");


frappe.ui.form.on("Customize Form", { frappe.ui.form.on("Customize Form", {
setup: function(frm) {
frm.get_docfield("fields").allow_bulk_edit = 1;
},
onload: function(frm) { onload: function(frm) {
frappe.customize_form.add_fields_help(frm); frappe.customize_form.add_fields_help(frm);




+ 22
- 1
frappe/custom/doctype/customize_form/customize_form.json View File

@@ -13,6 +13,7 @@
"editable_grid": 1, "editable_grid": 1,
"fields": [ "fields": [
{ {
"allow_bulk_edit": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
@@ -42,6 +43,7 @@
"unique": 0 "unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
@@ -71,6 +73,7 @@
"unique": 0 "unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
@@ -100,6 +103,7 @@
"unique": 0 "unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
@@ -129,6 +133,7 @@
"unique": 0 "unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
@@ -158,6 +163,7 @@
"unique": 0 "unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
@@ -186,6 +192,7 @@
"unique": 0 "unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
@@ -215,6 +222,7 @@
"unique": 0 "unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
@@ -245,6 +253,7 @@
"unique": 0 "unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
@@ -275,6 +284,7 @@
"unique": 0 "unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
@@ -304,6 +314,7 @@
"unique": 0 "unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
@@ -334,6 +345,7 @@
"unique": 0 "unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
@@ -362,6 +374,7 @@
"unique": 0 "unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
@@ -392,6 +405,7 @@
"unique": 0 "unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
@@ -422,6 +436,7 @@
"unique": 0 "unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
@@ -451,6 +466,7 @@
"unique": 0 "unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
@@ -480,6 +496,7 @@
"unique": 0 "unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
@@ -508,6 +525,7 @@
"unique": 0 "unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
@@ -536,6 +554,7 @@
"unique": 0 "unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
@@ -565,6 +584,7 @@
"unique": 0 "unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
@@ -595,6 +615,7 @@
"unique": 0 "unique": 0
}, },
{ {
"allow_bulk_edit": 1,
"allow_on_submit": 0, "allow_on_submit": 0,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
@@ -635,7 +656,7 @@
"issingle": 1, "issingle": 1,
"istable": 0, "istable": 0,
"max_attachments": 0, "max_attachments": 0,
"modified": "2017-04-10 12:17:23.627634",
"modified": "2017-04-21 16:59:12.752428",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Custom", "module": "Custom",
"name": "Customize Form", "name": "Customize Form",


+ 2
- 1
frappe/custom/doctype/customize_form/customize_form.py View File

@@ -60,7 +60,8 @@ docfield_properties = {
'read_only': 'Check', 'read_only': 'Check',
'length': 'Int', 'length': 'Int',
'columns': 'Int', 'columns': 'Int',
'remember_last_selected_value': 'Check'
'remember_last_selected_value': 'Check',
'allow_bulk_edit': 'Check',
} }


allowed_fieldtype_change = (('Currency', 'Float', 'Percent'), ('Small Text', 'Data'), allowed_fieldtype_change = (('Currency', 'Float', 'Percent'), ('Small Text', 'Data'),


+ 1139
- 1070
frappe/custom/doctype/customize_form_field/customize_form_field.json
File diff suppressed because it is too large
View File


+ 1
- 1
frappe/desk/query_report.py View File

@@ -146,7 +146,7 @@ def export_query():
if row: if row:
row_list = [] row_list = []
for idx in range(len(data.columns)): for idx in range(len(data.columns)):
row_list.append(row[columns[idx]["fieldname"]])
row_list.append(row.get(columns[idx]["fieldname"],""))
result.append(row_list) result.append(row_list)
else: else:
result = result + data.result result = result + data.result


+ 2
- 2
frappe/frappeclient.py View File

@@ -165,8 +165,8 @@ class FrappeClient(object):
params = {} params = {}
if filters: if filters:
params["filters"] = json.dumps(filters) params["filters"] = json.dumps(filters)
if fields:
params["fields"] = json.dumps(fields)
if fields:
params["fields"] = json.dumps(fields)


res = self.session.get(self.url + "/api/resource/" + doctype + "/" + name, res = self.session.get(self.url + "/api/resource/" + doctype + "/" + name,
params=params, verify=self.verify) params=params, verify=self.verify)


+ 2
- 1
frappe/patches.txt View File

@@ -178,4 +178,5 @@ frappe.patches.v8_0.newsletter_childtable_migrate
execute:frappe.db.sql("delete from `tabDesktop Icon` where module_name='Communication'") execute:frappe.db.sql("delete from `tabDesktop Icon` where module_name='Communication'")
execute:frappe.db.sql("update `tabDesktop Icon` set type='list' where _doctype='Communication'") execute:frappe.db.sql("update `tabDesktop Icon` set type='list' where _doctype='Communication'")
frappe.patches.v8_0.fix_non_english_desktop_icons # 2017-04-12 frappe.patches.v8_0.fix_non_english_desktop_icons # 2017-04-12
frappe.patches.v8_0.set_doctype_values_in_custom_role
frappe.patches.v8_0.set_doctype_values_in_custom_role
frappe.patches.v8_0.install_new_build_system_requirements

+ 12
- 0
frappe/patches/v8_0/install_new_build_system_requirements.py View File

@@ -0,0 +1,12 @@
import subprocess

def execute():
subprocess.call([
'npm', 'install',
'babel-core',
'chokidar',
'babel-preset-es2015',
'babel-preset-es2016',
'babel-preset-es2017',
'babel-preset-babili'
])

+ 3
- 3
frappe/patches/v8_0/update_records_in_global_search.py View File

@@ -1,5 +1,5 @@
def execute():
from frappe.utils.global_search import get_doctypes_with_global_search, rebuild_for_doctype
from frappe.utils.global_search import get_doctypes_with_global_search, rebuild_for_doctype


for doctype in get_doctypes_with_global_search():
def execute():
for doctype in get_doctypes_with_global_search(with_child_tables=False):
rebuild_for_doctype(doctype) rebuild_for_doctype(doctype)

+ 2
- 2
frappe/public/build.json View File

@@ -6,6 +6,7 @@
"public/css/avatar.css" "public/css/avatar.css"
], ],
"js/frappe-web.min.js": [ "js/frappe-web.min.js": [
"public/js/frappe/class.js",
"public/js/lib/bootstrap.min.js", "public/js/lib/bootstrap.min.js",
"public/js/lib/md5.min.js", "public/js/lib/md5.min.js",
"public/js/frappe/provide.js", "public/js/frappe/provide.js",
@@ -16,7 +17,6 @@
"public/js/frappe/misc/pretty_date.js", "public/js/frappe/misc/pretty_date.js",
"public/js/lib/moment/moment.min.js", "public/js/lib/moment/moment.min.js",
"public/js/lib/highlight.pack.js", "public/js/lib/highlight.pack.js",
"public/js/frappe/class.js",
"public/js/lib/microtemplate.js", "public/js/lib/microtemplate.js",
"public/js/frappe/query_string.js", "public/js/frappe/query_string.js",
"website/js/website.js", "website/js/website.js",
@@ -75,8 +75,8 @@
"public/js/lib/datepicker/locale-all.js" "public/js/lib/datepicker/locale-all.js"
], ],
"js/desk.min.js": [ "js/desk.min.js": [
"public/js/frappe/provide.js",
"public/js/frappe/class.js", "public/js/frappe/class.js",
"public/js/frappe/provide.js",
"public/js/frappe/assets.js", "public/js/frappe/assets.js",
"public/js/frappe/format.js", "public/js/frappe/format.js",
"public/js/frappe/form/formatters.js", "public/js/frappe/form/formatters.js",


+ 8
- 1
frappe/public/js/frappe/form/control.js View File

@@ -318,8 +318,15 @@ frappe.ui.form.ControlInput = frappe.ui.form.Control.extend({
}, },


set_disp_area: function() { set_disp_area: function() {
let value = this.get_value();
if(inList(["Currency", "Int", "Float"], this.df.fieldtype) && (this.value === 0 || value === 0)) {
// to set the 0 value in readonly for currency, int, float field
value = 0;
} else {
value = this.value || value;
}
this.disp_area && $(this.disp_area) this.disp_area && $(this.disp_area)
.html(frappe.format(this.value || this.get_value(), this.df, {no_icon:true, inline:true},
.html(frappe.format(value, this.df, {no_icon:true, inline:true},
this.doc || (this.frm && this.frm.doc))); this.doc || (this.frm && this.frm.doc)));
}, },




+ 4
- 0
frappe/public/js/frappe/form/grid.js View File

@@ -348,6 +348,10 @@ frappe.ui.form.Grid = Class.extend({
this.get_docfield(fieldname).read_only = enable ? 0 : 1;; this.get_docfield(fieldname).read_only = enable ? 0 : 1;;
this.refresh(); this.refresh();
}, },
toggle_display: function(fieldname, show) {
this.get_docfield(fieldname).hidden = show ? 0 : 1;;
this.refresh();
},
get_docfield: function(fieldname) { get_docfield: function(fieldname) {
return frappe.meta.get_docfield(this.doctype, fieldname, this.frm ? this.frm.docname : null); return frappe.meta.get_docfield(this.doctype, fieldname, this.frm ? this.frm.docname : null);
}, },


+ 3
- 3
frappe/public/js/frappe/list/list_renderer.js View File

@@ -320,11 +320,11 @@ frappe.views.ListRenderer = Class.extend({
const $item_container = $('<div class="list-item-container">').append($item); const $item_container = $('<div class="list-item-container">').append($item);


$list_items.append($item_container); $list_items.append($item_container);
if (this.settings.post_render_item) { if (this.settings.post_render_item) {
this.settings.post_render_item(this, $item_container, value); this.settings.post_render_item(this, $item_container, value);
} }
this.render_tags($item_container, value); this.render_tags($item_container, value);
}); });


@@ -541,7 +541,7 @@ frappe.views.ListRenderer = Class.extend({
var new_button = frappe.boot.user.can_create.includes(this.doctype) var new_button = frappe.boot.user.can_create.includes(this.doctype)
? (`<p><button class='btn btn-primary btn-sm' ? (`<p><button class='btn btn-primary btn-sm'
list_view_doc='${this.doctype}'> list_view_doc='${this.doctype}'>
${__('Make a new ' + __(this.doctype))}
${__('Make a new {0}', [__(this.doctype)])}
</button></p>`) </button></p>`)
: ''; : '';
var no_result_message = var no_result_message =


+ 4
- 3
frappe/public/js/frappe/router.js View File

@@ -116,10 +116,11 @@ frappe.get_route_str = function(route) {
} }


frappe.set_route = function() { frappe.set_route = function() {
if(arguments.length===1 && $.isArray(arguments[0])) {
arguments = arguments[0];
var params = arguments;
if(params.length===1 && $.isArray(params[0])) {
params = params[0];
} }
route = $.map(arguments, function(a) {
route = $.map(params, function(a) {
if($.isPlainObject(a)) { if($.isPlainObject(a)) {
frappe.route_options = a; frappe.route_options = a;
return null; return null;


+ 36
- 0
frappe/public/js/frappe/socketio_client.js View File

@@ -11,6 +11,11 @@ frappe.socket = {
return; return;
} }


if (frappe.boot.developer_mode) {
// File watchers for development
frappe.socket.setup_file_watchers();
}

//Enable secure option when using HTTPS //Enable secure option when using HTTPS
if (window.location.protocol == "https:") { if (window.location.protocol == "https:") {
frappe.socket.socket = io.connect(frappe.socket.get_host(), {secure: true}); frappe.socket.socket = io.connect(frappe.socket.get_host(), {secure: true});
@@ -186,7 +191,38 @@ frappe.socket = {
} }
}, 5000); }, 5000);
}); });
},
setup_file_watchers: function() {
var host = window.location.origin;
var port = '6787';
// remove the port number from string
host = host.split(':').slice(0, -1).join(":");
host = host + ':' + port;


frappe.socket.file_watcher = io.connect(host);
// css files auto reload
frappe.socket.file_watcher.on('reload_css', function(filename) {
let abs_file_path = "assets/" + filename;
const link = $(`link[href*="${abs_file_path}"]`);
abs_file_path = abs_file_path.split('?')[0] + '?v='+ moment();
link.attr('href', abs_file_path);
frappe.show_alert({
indicator: 'orange',
message: filename + ' reloaded'
}, 5);
});
// js files show alert
frappe.socket.file_watcher.on('reload_js', function(filename) {
filename = "assets/" + filename;
var msg = $(`
<span>${filename} changed <a data-action="reload">Click to Reload</a></span>
`)
msg.find('a').click(frappe.ui.toolbar.clear_cache);
frappe.show_alert({
indicator: 'orange',
message: msg
}, 5);
});
}, },
process_response: function(data, method) { process_response: function(data, method) {
if(!data) { if(!data) {


+ 11
- 6
frappe/public/js/frappe/ui/messages.js View File

@@ -250,7 +250,7 @@ frappe.hide_progress = function() {
} }


// Floating Message // Floating Message
frappe.show_alert = function(message, seconds) {
frappe.show_alert = function(message, seconds=7) {
if(typeof message==='string') { if(typeof message==='string') {
message = { message = {
message: message message: message
@@ -261,14 +261,19 @@ frappe.show_alert = function(message, seconds) {
} }


if(message.indicator) { if(message.indicator) {
message_html = '<span class="indicator ' + message.indicator + '">' + message.message + '</span>';
message_html = $('<span class="indicator ' + message.indicator + '"></span>').append(message.message);
} else { } else {
message_html = message.message; message_html = message.message;
} }


var div = $(repl('<div class="alert desk-alert" style="display: none;">'
+ '<span class="alert-message">%(txt)s</span><a class="close">&times;</a>'
+ '</div>', {txt: message_html}))
var div = $(`
<div class="alert desk-alert">
<span class="alert-message"></span><a class="close">&times;</a>
</div>`);

div.find('.alert-message').append(message_html);

div.hide()
.appendTo("#alert-container") .appendTo("#alert-container")
.fadeIn(300); .fadeIn(300);


@@ -277,7 +282,7 @@ frappe.show_alert = function(message, seconds) {
return false; return false;
}); });


div.delay(seconds ? seconds * 1000 : 7000).fadeOut(300);
div.delay(seconds * 1000).fadeOut(300);
return div; return div;
} }




+ 1
- 2
frappe/public/js/frappe/views/image/image_view.js View File

@@ -68,8 +68,7 @@ frappe.views.ImageView = frappe.views.ListRenderer.extend({
gallery.show(name); gallery.show(name);
return false; return false;
}); });
},
refresh: this.render_view
}
}); });


frappe.views.GalleryView = Class.extend({ frappe.views.GalleryView = Class.extend({


+ 34
- 13
frappe/public/js/frappe/views/kanban/kanban_board.js View File

@@ -178,21 +178,33 @@ frappe.provide("frappe.views");
}); });
}, },
update_order: function(updater, order) { update_order: function(updater, order) {
return frappe.call({
// cache original order
const _cards = this.cards.slice();
const _columns = this.columns.slice();
frappe.call({
method: method_prefix + "update_order", method: method_prefix + "update_order",
args: { args: {
board_name: this.board.name, board_name: this.board.name,
order: order order: order
},
callback: (r) => {
var state = this;
var board = r.message[0];
var updated_cards = r.message[1];
var cards = update_cards_column(updated_cards);
var columns = prepare_columns(board.columns);
updater.set({
cards: cards,
columns: columns
});
} }
}).then(function(r) {
var state = this;
var board = r.message[0];
var updated_cards = r.message[1];
var cards = update_cards_column(updated_cards);
var columns = prepare_columns(board.columns);
})
.fail(function(e) {
// revert original order
updater.set({ updater.set({
cards: cards,
columns: columns
cards: _cards,
columns: _columns
}); });
}); });
}, },
@@ -237,8 +249,12 @@ frappe.provide("frappe.views");
self.cur_list = opts.cur_list; self.cur_list = opts.cur_list;
self.board_name = opts.board_name; self.board_name = opts.board_name;


self.update_cards = function(cards) {
fluxify.doAction('update_cards', cards);
self.update = function(cards) {
if(self.wrapper.find('.kanban').length > 0) {
fluxify.doAction('update_cards', cards);
} else {
init();
}
} }


function init() { function init() {
@@ -250,8 +266,13 @@ frappe.provide("frappe.views");
} }


function prepare() { function prepare() {
self.$kanban_board = $(frappe.render_template("kanban_board"));
self.$kanban_board.appendTo(self.wrapper);
self.$kanban_board = self.wrapper.find('.kanban');

if(self.$kanban_board.length === 0) {
self.$kanban_board = $(frappe.render_template("kanban_board"));
self.$kanban_board.appendTo(self.wrapper);
}

self.$filter_area = self.cur_list.$page.find('.set-filters'); self.$filter_area = self.cur_list.$page.find('.set-filters');
bind_events(); bind_events();
setup_sortable(); setup_sortable();


+ 1
- 1
frappe/public/js/frappe/views/kanban/kanban_view.js View File

@@ -5,7 +5,7 @@ frappe.views.KanbanView = frappe.views.ListRenderer.extend({
render_view: function(values) { render_view: function(values) {
var board_name = this.get_board_name(); var board_name = this.get_board_name();
if(this.kanban && board_name === this.kanban.board_name) { if(this.kanban && board_name === this.kanban.board_name) {
this.kanban.update_cards(values);
this.kanban.update(values);
return; return;
} }




+ 1
- 1
frappe/public/js/legacy/globals.js View File

@@ -39,7 +39,7 @@ var user_img = {};
var _f = {}; var _f = {};
var _p = {}; var _p = {};
var _r = {}; var _r = {};
var FILTER_SEP = '\1';
// var FILTER_SEP = '\1';


// API globals // API globals
var frms={}; var frms={};


+ 1
- 1
frappe/tests/test_global_search.py View File

@@ -172,6 +172,6 @@ class TestGlobalSearch(unittest.TestCase):
field_as_text = '' field_as_text = ''
for field in doc.meta.fields: for field in doc.meta.fields:
if field.fieldname == 'description': if field.fieldname == 'description':
field_as_text = global_search.get_field_value(doc, field)
field_as_text = global_search.get_formatted_value(doc.description, field)


self.assertEquals(case["result"], field_as_text) self.assertEquals(case["result"], field_as_text)

+ 161
- 39
frappe/utils/global_search.py View File

@@ -6,6 +6,7 @@ from __future__ import unicode_literals
import frappe import frappe
import re import re
from frappe.utils import cint, strip_html_tags from frappe.utils import cint, strip_html_tags
from frappe.model.base_document import get_controller


def setup_global_search_table(): def setup_global_search_table():
'''Creates __global_seach table''' '''Creates __global_seach table'''
@@ -27,11 +28,13 @@ def reset():
'''Deletes all data in __global_search''' '''Deletes all data in __global_search'''
frappe.db.sql('delete from __global_search') frappe.db.sql('delete from __global_search')


def get_doctypes_with_global_search():
def get_doctypes_with_global_search(with_child_tables=True):
'''Return doctypes with global search fields''' '''Return doctypes with global search fields'''
def _get(): def _get():
global_search_doctypes = [] global_search_doctypes = []
for d in frappe.get_all('DocType', 'name, module'):
if not with_child_tables:
filters = {"istable": ["!=", 1]}
for d in frappe.get_all('DocType', fields=['name', 'module'], filters=filters):
meta = frappe.get_meta(d.name) meta = frappe.get_meta(d.name)
if len(meta.get_global_search_fields()) > 0: if len(meta.get_global_search_fields()) > 0:
global_search_doctypes.append(d) global_search_doctypes.append(d)
@@ -44,35 +47,172 @@ def get_doctypes_with_global_search():


return frappe.cache().get_value('doctypes_with_global_search', _get) return frappe.cache().get_value('doctypes_with_global_search', _get)


def rebuild_for_doctype(doctype):
'''Rebuild entries of doctype's documents in __global_search on change of
searchable fields
:param doctype: Doctype '''

def _get_filters():
filters = frappe._dict({ "docstatus": ["!=", 1] })
if meta.has_field("enabled"):
filters.enabled = 1
if meta.has_field("disabled"):
filters.disabled = 0
return filters

meta = frappe.get_meta(doctype)
if cint(meta.istable) == 1:
parent_doctypes = frappe.get_all("DocField", fields="parent", filters={
"fieldtype": "Table",
"options": doctype
})
for p in parent_doctypes:
rebuild_for_doctype(p.parent)
return
# Delete records
delete_global_search_records_for_doctype(doctype)

parent_search_fields = meta.get_global_search_fields()
fieldnames = get_selected_fields(meta, parent_search_fields)
# Get all records from parent doctype table
all_records = frappe.get_all(doctype, fields=fieldnames, filters=_get_filters())
# Children data
all_children, child_search_fields = get_children_data(doctype, meta)
all_contents = []
for doc in all_records:
content = []
for field in parent_search_fields:
value = doc.get(field.fieldname)
if value:
content.append(get_formatted_value(value, field))
# get children data
for child_doctype, records in all_children.get(doc.name, {}).items():
for field in child_search_fields.get(child_doctype):
for r in records:
if r.get(field.fieldname):
content.append(get_formatted_value(r.get(field.fieldname), field))
if content:
# if doctype published in website, push title, route etc.
published = 0
title, route = "", ""
if hasattr(get_controller(doctype), "is_website_published") and meta.allow_guest_to_view:
d = frappe.get_doc(doctype, doc.name)
published = 1 if d.is_website_published() else 0
title = d.get_title()
route = d.get("route")
all_contents.append({
"doctype": doctype,
"name": frappe.db.escape(doc.name),
"content": frappe.db.escape(' ||| '.join(content or '')),
"published": published,
"title": frappe.db.escape(title or ''),
"route": frappe.db.escape(route or '')
})
if all_contents:
insert_values_for_multiple_docs(all_contents)
def delete_global_search_records_for_doctype(doctype):
frappe.db.sql('''
delete
from __global_search
where
doctype = %s''', doctype, as_dict=True)
def get_selected_fields(meta, global_search_fields):
fieldnames = [df.fieldname for df in global_search_fields]
if meta.istable==1:
fieldnames.append("parent")
elif "name" not in fieldnames:
fieldnames.append("name")
if meta.has_field("is_website_published"):
fieldnames.append("is_website_published")
return fieldnames
def get_children_data(doctype, meta):
"""
Get all records from all the child tables of a doctype
all_children = {
"parent1": {
"child_doctype1": [
{
"field1": val1,
"field2": val2
}
]
}
}
"""
all_children = frappe._dict()
child_search_fields = frappe._dict()
for child in meta.get_table_fields():
child_meta = frappe.get_meta(child.options)
search_fields = child_meta.get_global_search_fields()
if search_fields:
child_search_fields.setdefault(child.options, search_fields)
child_fieldnames = get_selected_fields(child_meta, search_fields)
child_records = frappe.get_all(child.options, fields=child_fieldnames, filters={
"docstatus": ["!=", 1],
"parenttype": doctype
})
for record in child_records:
all_children.setdefault(record.parent, frappe._dict())\
.setdefault(child.options, []).append(record)
return all_children, child_search_fields
def insert_values_for_multiple_docs(all_contents):
values = []
for content in all_contents:
values.append("( '{doctype}', '{name}', '{content}', '{published}', '{title}', '{route}')"
.format(**content))
frappe.db.sql('''
insert into __global_search
(doctype, name, content, published, title, route)
values
{0}
'''.format(", ".join(values)))

def update_global_search(doc): def update_global_search(doc):
'''Add values marked with `in_global_search` to '''Add values marked with `in_global_search` to
`frappe.flags.update_global_search` from given doc `frappe.flags.update_global_search` from given doc
:param doc: Document to be added to global search''' :param doc: Document to be added to global search'''


if cint(doc.meta.istable) == 1 and frappe.db.exists("DocType", doc.parenttype):
d = frappe.get_doc(doc.parenttype, doc.parent)
update_global_search(d)
return

if doc.docstatus > 1:
return
if doc.docstatus > 1 or (doc.meta.has_field("enabled") and not doc.get("enabled")) \
or doc.get("disabled"):
return


if frappe.flags.update_global_search==None: if frappe.flags.update_global_search==None:
frappe.flags.update_global_search = [] frappe.flags.update_global_search = []


content = [] content = []
for field in doc.meta.get_global_search_fields(): for field in doc.meta.get_global_search_fields():
if doc.get(field.fieldname):
if getattr(field, 'fieldtype', None) == "Table":

# Get children
for d in doc.get(field.fieldname):
if d.parent == doc.name:
for field in d.meta.get_global_search_fields():
if d.get(field.fieldname):
content.append(get_field_value(d, field))
else:
content.append(get_field_value(doc, field))
if doc.get(field.fieldname) and field.fieldtype != "Table":
content.append(get_formatted_value(doc.get(field.fieldname), field))
# Get children
for child in doc.meta.get_table_fields():
for d in doc.get(child.fieldname):
if d.parent == doc.name:
for field in d.meta.get_global_search_fields():
if d.get(field.fieldname):
content.append(get_formatted_value(d.get(field.fieldname), field))


if content: if content:
published = 0 published = 0
@@ -83,12 +223,11 @@ def update_global_search(doc):
dict(doctype=doc.doctype, name=doc.name, content=' ||| '.join(content or ''), dict(doctype=doc.doctype, name=doc.name, content=' ||| '.join(content or ''),
published=published, title=doc.get_title(), route=doc.get('route'))) published=published, title=doc.get_title(), route=doc.get('route')))


def get_field_value(doc, field):
def get_formatted_value(value, field):
'''Prepare field from raw data''' '''Prepare field from raw data'''


from HTMLParser import HTMLParser from HTMLParser import HTMLParser


value = doc.get(field.fieldname)
if(getattr(field, 'fieldtype', None) in ["Text", "Text Editor"]): if(getattr(field, 'fieldtype', None) in ["Text", "Text Editor"]):
h = HTMLParser() h = HTMLParser()
value = h.unescape(value) value = h.unescape(value)
@@ -111,23 +250,6 @@ def sync_global_search():


frappe.flags.update_global_search = [] frappe.flags.update_global_search = []


def rebuild_for_doctype(doctype):
'''Rebuild entries of doctype's documents in __global_search on change of
searchable fields
:param doctype: Doctype '''

frappe.flags.update_global_search = []

frappe.db.sql('''
delete
from __global_search
where
doctype = %s''', doctype, as_dict=True)

for d in frappe.get_all(doctype):
update_global_search(frappe.get_doc(doctype, d.name))
sync_global_search()

def delete_for_document(doc): def delete_for_document(doc):
'''Delete the __global_search entry of a document that has '''Delete the __global_search entry of a document that has
been deleted been deleted


+ 9
- 0
frappe/www/print.py View File

@@ -433,6 +433,15 @@ def column_has_value(data, fieldname):


trigger_print_script = """ trigger_print_script = """
<script> <script>
//allow wrapping of long tr
var elements = document.getElementsByTagName("tr");
var i = elements.length;
while (i--) {
if(elements[i].clientHeight>300){
elements[i].setAttribute("style", "page-break-inside: auto;");
}
}

window.print(); window.print();


// close the window after print // close the window after print


+ 2
- 0
socketio.js View File

@@ -293,3 +293,5 @@ function get_conf() {


return conf; return conf;
} }



+ 11
- 0
test_cafe.js View File

@@ -0,0 +1,11 @@

fixture `Example page`
.page `http:localhost:8000/login`;
test('Successful Login', async t => {
await t
.typeText('#login_email', 'Administrator')
.click('#login_password', 'admin')
.click('.btn-login')
.click('[data-link="modules"]');
});

Loading…
Cancel
Save