浏览代码

Merge branch 'develop'

version-14
Nabin Hait 8 年前
父节点
当前提交
6f5817d76d
共有 31 个文件被更改,包括 3045 次插入2380 次删除
  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 查看文件

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

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

local = Local()


+ 301
- 0
frappe/build.js 查看文件

@@ -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 查看文件

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

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

"""
Build the `public` folders and setup languages
@@ -22,16 +23,30 @@ def setup():
except ImportError: pass
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"""
# build js files
setup()

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)

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

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

setup()

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

return build_maps

timestamps = {}


+ 6
- 4
frappe/commands/utils.py 查看文件

@@ -8,19 +8,21 @@ from frappe.commands import pass_context, get_site
@click.command('build')
@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')
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"
import frappe.build
import frappe
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')
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"
import frappe.build
frappe.init('')
frappe.build.watch(True)
frappe.build.watch(True, beta=beta)

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


+ 1246
- 1217
frappe/core/doctype/docfield/docfield.json
文件差异内容过多而无法显示
查看文件


+ 2
- 1
frappe/core/doctype/doctype/doctype.py 查看文件

@@ -397,7 +397,7 @@ def validate_fields(meta):
def check_link_table_options(d):
if d.fieldtype in ("Link", "Table"):
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:
return
if d.options != d.parent:
@@ -571,6 +571,7 @@ def validate_fields(meta):

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


+ 0
- 3
frappe/custom/doctype/customize_form/customize_form.js 查看文件

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

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



+ 22
- 1
frappe/custom/doctype/customize_form/customize_form.json 查看文件

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


+ 2
- 1
frappe/custom/doctype/customize_form/customize_form.py 查看文件

@@ -60,7 +60,8 @@ docfield_properties = {
'read_only': 'Check',
'length': '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'),


+ 1139
- 1070
frappe/custom/doctype/customize_form_field/customize_form_field.json
文件差异内容过多而无法显示
查看文件


+ 1
- 1
frappe/desk/query_report.py 查看文件

@@ -146,7 +146,7 @@ def export_query():
if row:
row_list = []
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)
else:
result = result + data.result


+ 2
- 2
frappe/frappeclient.py 查看文件

@@ -165,8 +165,8 @@ class FrappeClient(object):
params = {}
if 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,
params=params, verify=self.verify)


+ 2
- 1
frappe/patches.txt 查看文件

@@ -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("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.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 查看文件

@@ -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 查看文件

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

+ 2
- 2
frappe/public/build.json 查看文件

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


+ 8
- 1
frappe/public/js/frappe/form/control.js 查看文件

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

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)
.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)));
},



+ 4
- 0
frappe/public/js/frappe/form/grid.js 查看文件

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


+ 3
- 3
frappe/public/js/frappe/list/list_renderer.js 查看文件

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

$list_items.append($item_container);
if (this.settings.post_render_item) {
this.settings.post_render_item(this, $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)
? (`<p><button class='btn btn-primary btn-sm'
list_view_doc='${this.doctype}'>
${__('Make a new ' + __(this.doctype))}
${__('Make a new {0}', [__(this.doctype)])}
</button></p>`)
: '';
var no_result_message =


+ 4
- 3
frappe/public/js/frappe/router.js 查看文件

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

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)) {
frappe.route_options = a;
return null;


+ 36
- 0
frappe/public/js/frappe/socketio_client.js 查看文件

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

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

//Enable secure option when using HTTPS
if (window.location.protocol == "https:") {
frappe.socket.socket = io.connect(frappe.socket.get_host(), {secure: true});
@@ -186,7 +191,38 @@ frappe.socket = {
}
}, 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) {
if(!data) {


+ 11
- 6
frappe/public/js/frappe/ui/messages.js 查看文件

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

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

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 {
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")
.fadeIn(300);

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

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



+ 1
- 2
frappe/public/js/frappe/views/image/image_view.js 查看文件

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

frappe.views.GalleryView = Class.extend({


+ 34
- 13
frappe/public/js/frappe/views/kanban/kanban_board.js 查看文件

@@ -178,21 +178,33 @@ frappe.provide("frappe.views");
});
},
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",
args: {
board_name: this.board.name,
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({
cards: cards,
columns: columns
cards: _cards,
columns: _columns
});
});
},
@@ -237,8 +249,12 @@ frappe.provide("frappe.views");
self.cur_list = opts.cur_list;
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() {
@@ -250,8 +266,13 @@ frappe.provide("frappe.views");
}

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');
bind_events();
setup_sortable();


+ 1
- 1
frappe/public/js/frappe/views/kanban/kanban_view.js 查看文件

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



+ 1
- 1
frappe/public/js/legacy/globals.js 查看文件

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

// API globals
var frms={};


+ 1
- 1
frappe/tests/test_global_search.py 查看文件

@@ -172,6 +172,6 @@ class TestGlobalSearch(unittest.TestCase):
field_as_text = ''
for field in doc.meta.fields:
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)

+ 161
- 39
frappe/utils/global_search.py 查看文件

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

def setup_global_search_table():
'''Creates __global_seach table'''
@@ -27,11 +28,13 @@ def reset():
'''Deletes all data in __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'''
def _get():
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)
if len(meta.get_global_search_fields()) > 0:
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)

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):
'''Add values marked with `in_global_search` to
`frappe.flags.update_global_search` from given doc
: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:
frappe.flags.update_global_search = []

content = []
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:
published = 0
@@ -83,12 +223,11 @@ def update_global_search(doc):
dict(doctype=doc.doctype, name=doc.name, content=' ||| '.join(content or ''),
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'''

from HTMLParser import HTMLParser

value = doc.get(field.fieldname)
if(getattr(field, 'fieldtype', None) in ["Text", "Text Editor"]):
h = HTMLParser()
value = h.unescape(value)
@@ -111,23 +250,6 @@ def sync_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):
'''Delete the __global_search entry of a document that has
been deleted


+ 9
- 0
frappe/www/print.py 查看文件

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

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

// close the window after print


+ 2
- 0
socketio.js 查看文件

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

return conf;
}



+ 11
- 0
test_cafe.js 查看文件

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

正在加载...
取消
保存