Procházet zdrojové kódy

Merge branch 'develop'

version-14
Nabin Hait před 8 roky
rodič
revize
20519ffde9
32 změnil soubory, kde provedl 435 přidání a 85 odebrání
  1. +1
    -0
      .travis.yml
  2. +1
    -1
      frappe/__init__.py
  3. +5
    -0
      frappe/config/integrations.py
  4. +1
    -1
      frappe/core/doctype/system_settings/system_settings.json
  5. +3
    -1
      frappe/custom/doctype/custom_field/custom_field.js
  6. +8
    -5
      frappe/desk/doctype/kanban_board/kanban_board.py
  7. +12
    -7
      frappe/desk/search.py
  8. +8
    -1
      frappe/email/doctype/email_alert/email_alert.py
  9. +30
    -0
      frappe/email/doctype/email_alert/test_email_alert.py
  10. +1
    -0
      frappe/exceptions.py
  11. +17
    -6
      frappe/model/mapper.py
  12. +7
    -3
      frappe/model/naming.py
  13. +1
    -1
      frappe/patches.txt
  14. +2
    -0
      frappe/patches/v8_0/update_records_in_global_search.py
  15. +2
    -1
      frappe/permissions.py
  16. +2
    -0
      frappe/public/build.json
  17. +7
    -0
      frappe/public/css/desk.css
  18. +3
    -2
      frappe/public/js/frappe/form/footer/timeline.js
  19. +212
    -0
      frappe/public/js/frappe/form/multi_select_dialog.js
  20. +1
    -1
      frappe/public/js/frappe/form/quick_entry.js
  21. +1
    -1
      frappe/public/js/frappe/list/list_renderer.js
  22. +5
    -0
      frappe/public/js/frappe/model/model.js
  23. +0
    -1
      frappe/public/js/frappe/ui/field_group.js
  24. +23
    -18
      frappe/public/js/frappe/ui/keyboard.js
  25. +30
    -13
      frappe/public/js/frappe/ui/sort_selector.js
  26. +1
    -5
      frappe/public/js/frappe/ui/toolbar/search_utils.js
  27. +9
    -0
      frappe/public/js/frappe/views/kanban/kanban_board.js
  28. +7
    -4
      frappe/public/js/frappe/views/reports/reportview.js
  29. +11
    -2
      frappe/public/less/desk.less
  30. +1
    -1
      frappe/utils/global_search.py
  31. +1
    -1
      frappe/utils/install.py
  32. +22
    -9
      frappe/utils/xlsxutils.py

+ 1
- 0
.travis.yml Zobrazit soubor

@@ -24,6 +24,7 @@ install:
- cp -r $TRAVIS_BUILD_DIR/test_sites/test_site ~/frappe-bench/sites/

script:
- set -e
- bench --verbose run-tests
- bench reinstall --yes
- testcafe chrome apps/frappe/frappe/tests/testcafe/


+ 1
- 1
frappe/__init__.py Zobrazit soubor

@@ -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.43'
__version__ = '8.0.44'
__title__ = "Frappe Framework"

local = Local()


+ 5
- 0
frappe/config/integrations.py Zobrazit soubor

@@ -7,6 +7,11 @@ def get_data():
"label": _("Payments"),
"icon": "fa fa-star",
"items": [
{
"type": "doctype",
"name": "Stripe Settings",
"description": _("Stripe payment gateway settings"),
},
{
"type": "doctype",
"name": "PayPal Settings",


+ 1
- 1
frappe/core/doctype/system_settings/system_settings.json Zobrazit soubor

@@ -902,7 +902,7 @@
"issingle": 1,
"istable": 0,
"max_attachments": 0,
"modified": "2017-05-01 15:27:11.079447",
"modified": "2017-05-11 15:27:11.079447",
"modified_by": "Administrator",
"module": "Core",
"name": "System Settings",


+ 3
- 1
frappe/custom/doctype/custom_field/custom_field.js Zobrazit soubor

@@ -13,7 +13,9 @@ frappe.ui.form.on('Custom Field', {
if(user!=="Administrator") {
filters.push(['DocType', 'module', '!=', 'Core'])
}
return filters
return {
"filters": filters
}
});
},
refresh: function(frm) {


+ 8
- 5
frappe/desk/doctype/kanban_board/kanban_board.py Zobrazit soubor

@@ -118,10 +118,13 @@ def update_order(board_name, order):
def quick_kanban_board(doctype, board_name, field_name):
'''Create new KanbanBoard quickly with default options'''
doc = frappe.new_doc('Kanban Board')
options = frappe.get_value('DocField', dict(
parent=doctype,
fieldname=field_name
), 'options')

meta = frappe.get_meta(doctype)

options = ''
for field in meta.fields:
if field.fieldname == field_name:
options = field.options

columns = []
if options:
@@ -198,4 +201,4 @@ def set_indicator(board_name, column_name, indicator):
def save_filters(board_name, filters):
'''Save filters silently'''
frappe.db.set_value('Kanban Board', board_name, 'filters',
filters, update_modified=False)
filters, update_modified=False)

+ 12
- 7
frappe/desk/search.py Zobrazit soubor

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

# Search
from __future__ import unicode_literals
import frappe
import frappe, json
from frappe.utils import cstr, unique

# this is called by the Link Field
@@ -16,7 +16,7 @@ def search_link(doctype, txt, query=None, filters=None, page_len=20, searchfield
# this is called by the search box
@frappe.whitelist()
def search_widget(doctype, txt, query=None, searchfield=None, start=0,
page_len=10, filters=None, as_dict=False):
page_len=10, filters=None, filter_fields=None, as_dict=False):
if isinstance(filters, basestring):
import json
filters = json.loads(filters)
@@ -76,20 +76,24 @@ def search_widget(doctype, txt, query=None, searchfield=None, start=0,
if meta.get("fields", {"fieldname":"disabled", "fieldtype":"Check"}):
filters.append([doctype, "disabled", "!=", 1])

# format a list of fields combining search fields and filter fields
fields = get_std_fields_list(meta, searchfield or "name")
if filter_fields:
fields = list(set(fields + json.loads(filter_fields)))
formatted_fields = ['`tab%s`.`%s`' % (meta.name, f.strip()) for f in fields]

# find relevance as location of search term from the beginning of string `name`. used for sorting results.
fields.append("""locate("{_txt}", `tab{doctype}`.`name`) as `_relevance`""".format(
formatted_fields.append("""locate("{_txt}", `tab{doctype}`.`name`) as `_relevance`""".format(
_txt=frappe.db.escape((txt or "").replace("%", "")), doctype=frappe.db.escape(doctype)))

# In order_by, `idx` gets second priority, because it stores link count
from frappe.model.db_query import get_order_by
order_by_based_on_meta = get_order_by(doctype, meta)
order_by = "if(_relevance, _relevance, 99999), idx desc, {0}".format(order_by_based_on_meta)
values = frappe.get_list(doctype,
filters=filters, fields=fields,
filters=filters, fields=formatted_fields,
or_filters = or_filters, limit_start = start,
limit_page_length=page_len,
order_by=order_by,
@@ -97,6 +101,7 @@ def search_widget(doctype, txt, query=None, searchfield=None, start=0,
as_list=not as_dict)

# remove _relevance from results
frappe.response["fields"] = fields
frappe.response["values"] = [r[:-1] for r in values]

def get_std_fields_list(meta, key):
@@ -107,7 +112,7 @@ def get_std_fields_list(meta, key):
if not key in sflist:
sflist = sflist + [key]

return ['`tab%s`.`%s`' % (meta.name, f.strip()) for f in sflist]
return sflist

def build_for_autosuggest(res):
results = []


+ 8
- 1
frappe/email/doctype/email_alert/email_alert.py Zobrazit soubor

@@ -217,7 +217,14 @@ def evaluate_alert(doc, alert, event):
return

if event=="Value Change" and not doc.is_new():
db_value = frappe.db.get_value(doc.doctype, doc.name, alert.value_changed)
try:
db_value = frappe.db.get_value(doc.doctype, doc.name, alert.value_changed)
except frappe.DatabaseOperationalError as e:
if e.args[0]==1054:
alert.db_set('enabled', 0)
frappe.log_error('Email Alert {0} has been disabled due to missing field'.format(alert.name))
return

db_value = parse_val(db_value)
if (doc.get(alert.value_changed) == db_value) or \
(not db_value and not doc.get(alert.value_changed)):


+ 30
- 0
frappe/email/doctype/email_alert/test_email_alert.py Zobrazit soubor

@@ -87,6 +87,36 @@ class TestEmailAlert(unittest.TestCase):
self.assertTrue(frappe.db.get_value("Email Queue", {"reference_doctype": "Event",
"reference_name": event.name, "status":"Not Sent"}))

def test_alert_disabled_on_wrong_field(self):
frappe.set_user('Administrator')
email_alert = frappe.get_doc({
"doctype": "Email Alert",
"subject":"_Test Email Alert for wrong field",
"document_type": "Event",
"event": "Value Change",
"attach_print": 0,
"value_changed": "description1",
"message": "Description changed",
"recipients": [
{ "email_by_document_field": "owner" }
]
}).insert()

event = frappe.new_doc("Event")
event.subject = "test-2",
event.event_type = "Private"
event.starts_on = "2014-06-06 12:00:00"
event.insert()
event.subject = "test 1"
event.save()

# verify that email_alert is disabled
email_alert.reload()
self.assertEqual(email_alert.enabled, 0)
email_alert.delete()
event.delete()


def test_date_changed(self):
event = frappe.new_doc("Event")
event.subject = "test",


+ 1
- 0
frappe/exceptions.py Zobrazit soubor

@@ -7,6 +7,7 @@ from __future__ import unicode_literals

from werkzeug.exceptions import NotFound
from MySQLdb import ProgrammingError as SQLError, Error
from MySQLdb import OperationalError as DatabaseOperationalError


class ValidationError(Exception):


+ 17
- 6
frappe/model/mapper.py Zobrazit soubor

@@ -25,16 +25,21 @@ def make_mapped_doc(method, source_name, selected_children=None):

return method(source_name)

@frappe.whitelist()
def map_docs(method, source_names, target_doc):
'''Returns the mapped document calling the given mapper method
with each of the given source docs on the target doc'''
method = frappe.get_attr(method)
if method not in frappe.whitelisted:
raise frappe.PermissionError

for src in json.loads(source_names):
target_doc = method(src, target_doc)
return target_doc

def get_mapped_doc(from_doctype, from_docname, table_maps, target_doc=None,
postprocess=None, ignore_permissions=False, ignore_child_tables=False):

source_doc = frappe.get_doc(from_doctype, from_docname)

if not ignore_permissions:
if not source_doc.has_permission("read"):
source_doc.raise_no_permission_to("read")

# main
if not target_doc:
target_doc = frappe.new_doc(table_maps[from_doctype]["doctype"])
@@ -44,6 +49,12 @@ def get_mapped_doc(from_doctype, from_docname, table_maps, target_doc=None,
if not ignore_permissions and not target_doc.has_permission("create"):
target_doc.raise_no_permission_to("create")

source_doc = frappe.get_doc(from_doctype, from_docname)

if not ignore_permissions:
if not source_doc.has_permission("read"):
source_doc.raise_no_permission_to("read")

map_doc(source_doc, target_doc, table_maps[source_doc.doctype])

row_exists_for_parentfield = {}


+ 7
- 3
frappe/model/naming.py Zobrazit soubor

@@ -94,12 +94,15 @@ def make_autoname(key='', doctype='', doc=''):
elif not "." in key:
frappe.throw(_("Invalid naming series (. missing)") + (_(" for {0}").format(doctype) if doctype else ""))

parts = key.split('.')
n = parse_naming_series(parts, doctype, doc)
return n

def parse_naming_series(parts, doctype= '', doc = ''):
n = ''
l = key.split('.')
series_set = False
today = now_datetime()

for e in l:
for e in parts:
part = ''
if e.startswith('#'):
if not series_set:
@@ -120,6 +123,7 @@ def make_autoname(key='', doctype='', doc=''):

if isinstance(part, basestring):
n+=part

return n

def getseries(key, digits, doctype=''):


+ 1
- 1
frappe/patches.txt Zobrazit soubor

@@ -12,7 +12,7 @@ frappe.patches.v8_0.drop_in_dialog
execute:frappe.reload_doc('core', 'doctype', 'docfield', force=True) #2017-03-03
execute:frappe.reload_doc('core', 'doctype', 'docperm') #2017-03-03
frappe.patches.v8_0.drop_is_custom_from_docperm
frappe.patches.v8_0.update_records_in_global_search
frappe.patches.v8_0.update_records_in_global_search #11-05-2017
frappe.patches.v8_0.update_published_in_global_search
execute:frappe.reload_doc('core', 'doctype', 'custom_docperm')
execute:frappe.reload_doc('core', 'doctype', 'deleted_document')


+ 2
- 0
frappe/patches/v8_0/update_records_in_global_search.py Zobrazit soubor

@@ -1,5 +1,7 @@
import frappe
from frappe.utils.global_search import get_doctypes_with_global_search, rebuild_for_doctype

def execute():
frappe.cache().delete_value('doctypes_with_global_search')
for doctype in get_doctypes_with_global_search(with_child_tables=False):
rebuild_for_doctype(doctype)

+ 2
- 1
frappe/permissions.py Zobrazit soubor

@@ -330,7 +330,8 @@ def get_all_perms(role):
'''Returns valid permissions for a given role'''
perms = frappe.get_all('DocPerm', fields='*', filters=dict(role=role))
custom_perms = frappe.get_all('Custom DocPerm', fields='*', filters=dict(role=role))
doctypes_with_custom_perms = list(set(p.parent for p in custom_perms))
doctypes_with_custom_perms = frappe.db.sql_list("""select distinct parent
from `tabCustom DocPerm`""")

for p in perms:
if p.parent not in doctypes_with_custom_perms:


+ 2
- 0
frappe/public/build.json Zobrazit soubor

@@ -30,6 +30,7 @@
"public/js/frappe/ui/field_group.js",
"public/js/frappe/form/control.js",
"public/js/frappe/form/link_selector.js",
"public/js/frappe/form/multi_select_dialog.js",
"public/js/frappe/ui/dialog.js"
],
"css/desk.min.css": [
@@ -104,6 +105,7 @@
"public/js/frappe/ui/field_group.js",
"public/js/frappe/form/control.js",
"public/js/frappe/form/link_selector.js",
"public/js/frappe/form/multi_select_dialog.js",
"public/js/frappe/ui/dialog.js",
"public/js/frappe/ui/app_icon.js",



+ 7
- 0
frappe/public/css/desk.css Zobrazit soubor

@@ -978,6 +978,13 @@ input[type="checkbox"]:checked:before {
font-size: 13px;
color: #3b99fc;
}
.multiselect-empty-state {
min-height: 300px;
display: flex;
align-items: center;
justify-content: center;
height: 100%;
}
@-moz-document url-prefix() {
input[type="checkbox"] {
visibility: visible;


+ 3
- 2
frappe/public/js/frappe/form/footer/timeline.js Zobrazit soubor

@@ -447,8 +447,9 @@ frappe.ui.form.Timeline = Class.extend({
var parts = [], count = 0;
data.row_changed.every(function(row) {
row[3].every(function(p) {
var df = frappe.meta.get_docfield(me.frm.fields_dict[row[0]].grid.doctype,
p[0], me.frm.docname);
var df = me.frm.fields_dict[row[0]] &&
frappe.meta.get_docfield(me.frm.fields_dict[row[0]].grid.doctype,
p[0], me.frm.docname);

if(df && !df.hidden) {
field_display_status = frappe.perm.get_field_display_status(df,


+ 212
- 0
frappe/public/js/frappe/form/multi_select_dialog.js Zobrazit soubor

@@ -0,0 +1,212 @@
// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
// MIT License. See license.txt

frappe.ui.form.MultiSelectDialog = Class.extend({
init: function(opts) {
/* Options: doctype, target, setters, get_query, action */
$.extend(this, opts);

var me = this;
if(this.doctype!="[Select]") {
frappe.model.with_doctype(this.doctype, function(r) {
me.make();
});
} else {
this.make();
}
},
make: function() {
let me = this;

let fields = [];
let count = 0;
if(!this.date_field) {
this.date_field = "transaction_date";
}
Object.keys(this.setters).forEach(function(setter) {
fields.push({
fieldtype: me.target.fields_dict[setter].df.fieldtype,
label: me.target.fields_dict[setter].df.label,
fieldname: setter,
options: me.target.fields_dict[setter].df.options,
default: me.setters[setter]
});
if (count++ < Object.keys(me.setters).length - 1) {
fields.push({fieldtype: "Column Break"});
}
});

fields = fields.concat([
{ fieldtype: "Section Break" },
{ fieldtype: "HTML", fieldname: "results_area" },
{ fieldtype: "Button", fieldname: "make_new", label: __("Make a new " + me.doctype) }
]);

let doctype_plural = !this.doctype.endsWith('y') ? this.doctype + 's'
: this.doctype.slice(0, -1) + 'ies';

this.dialog = new frappe.ui.Dialog({
title: __("Select {0}", [(this.doctype=='[Select]') ? __("value") : __(doctype_plural)]),
fields: fields,
primary_action_label: __("Get Items"),
primary_action: function() {
me.action(me.get_checked_values(), me.args);
}
});

this.$parent = $(this.dialog.body);
this.$wrapper = this.dialog.fields_dict.results_area.$wrapper.append(`<div class="results"
style="border: 1px solid #d1d8dd; border-radius: 3px; height: 300px; overflow: auto;"></div>`);
this.$results = this.$wrapper.find('.results');
this.$make_new_btn = this.dialog.fields_dict.make_new.$wrapper;

this.$placeholder = $(`<div class="multiselect-empty-state">
<span class="text-center" style="margin-top: -40px;">
<i class="fa fa-2x fa-tags text-extra-muted"></i>
<p class="text-extra-muted">No ${this.doctype} found</p>
<button class="btn btn-default btn-xs text-muted" data-fieldtype="Button"
data-fieldname="make_new" placeholder="" value="">Make a new ${this.doctype}</button>
</span>
</div>`);

this.args = {};

this.bind_events();
this.get_results();
this.dialog.show();
},

bind_events: function() {
let me = this;
this.$results.on('click', '.list-item-container', function (e) {
if (!$(e.target).is(':checkbox') && !$(e.target).is('a')) {
$(this).find(':checkbox').trigger('click');
}
});
this.$results.on('click', '.list-item--head :checkbox', (e) => {
this.$results.find('.list-item-container .list-row-check')
.prop("checked", ($(e.target).is(':checked')));
});

this.$parent.find('.input-with-feedback').on('change', (e) => {
this.get_results();
});
this.$parent.on('click', '.btn[data-fieldname="make_new"]', (e) => {
frappe.route_options = {};
Object.keys(this.setters).forEach(function(setter) {
frappe.route_options[setter] = me.dialog.fields_dict[setter].get_value() || undefined;
});
frappe.new_doc(this.doctype, true);
});
},

get_checked_values: function() {
return this.$results.find('.list-item-container').map(function() {
if ($(this).find('.list-row-check:checkbox:checked').length > 0 ) {
return $(this).attr('data-item-name');
}
}).get();
},

make_list_row: function(result={}) {
var me = this;
// Make a head row by default (if result not passed)
let head = Object.keys(result).length === 0;

let contents = ``;
let columns = (["name"].concat(Object.keys(this.setters))).concat("Date");
columns.forEach(function(column) {
contents += `<div class="list-item__content ellipsis">
${
head ? __(frappe.model.unscrub(column))

: (column !== "name" ? __(result[column])
: `<a href="${"#Form/"+ me.doctype + "/" + result[column]}" class="list-id">
${__(result[column])}</a>`)
}
</div>`;
})

let $row = $(`<div class="list-item">
<div class="list-item__content ellipsis" style="flex: 0 0 10px;">
<input type="checkbox" class="list-row-check" ${result.checked ? 'checked' : ''}>
</div>
${contents}
</div>`);

head ? $row.addClass('list-item--head')
: $row = $(`<div class="list-item-container" data-item-name="${result.name}"></div>`).append($row);
return $row;
},

render_result_list: function(results) {
var me = this;
this.$results.empty();
if(results.length === 0) {
this.$make_new_btn.addClass('hide');
this.$results.append(me.$placeholder);
return;
}
this.$make_new_btn.removeClass('hide');

this.$results.append(this.make_list_row());
results.forEach((result) => {
me.$results.append(me.make_list_row(result));
})
},

get_results: function() {
let me = this;

let filters = this.get_query().filters;
Object.keys(this.setters).forEach(function(setter) {
filters[setter] = me.dialog.fields_dict[setter].get_value() || undefined;
me.args[setter] = filters[setter];
});

let args = {
doctype: me.doctype,
txt: '',
filters: filters,
filter_fields: Object.keys(me.setters).concat([me.date_field])
}
frappe.call({
type: "GET",
method:'frappe.desk.search.search_widget',
no_spinner: true,
args: args,
callback: function(r) {
if(r.values) {
let results = [];
r.values.forEach(function(value_list) {
let result = {};
value_list.forEach(function(value, index){
if(r.fields[index] === me.date_field) {
result["Date"] = value;
} else {
result[r.fields[index]] = value;
}
});
result.checked = 0;
result.parsed_date = Date.parse(result["Date"]);
results.push(result);
});

results.map( (result) => {
result["Date"] = frappe.format(result["Date"], {"fieldtype":"Date"});
})

results.sort((a, b) => {
return a.parsed_date - b.parsed_date;
});

// Preselect oldest entry
results[0].checked = 1

me.render_result_list(results);
}
}
});
},

});

+ 1
- 1
frappe/public/js/frappe/form/quick_entry.js Zobrazit soubor

@@ -31,7 +31,7 @@ frappe.ui.form.quick_entry = function(doctype, success) {
}

var dialog = new frappe.ui.Dialog({
title: __("New {0}", [doctype]),
title: __("New {0}", [__(doctype)]),
fields: mandatory,
});



+ 1
- 1
frappe/public/js/frappe/list/list_renderer.js Zobrazit soubor

@@ -144,7 +144,7 @@ frappe.views.ListRenderer = Class.extend({
}
// kanban column fields
if (me.meta.__kanban_column_fields) {
me.fields = me.fields.concat(me.meta.__kanban_column_fields);
me.meta.__kanban_column_fields.map(add_field);
}
},
set_columns: function () {


+ 5
- 0
frappe/public/js/frappe/model/model.js Zobrazit soubor

@@ -187,6 +187,11 @@ $.extend(frappe.model, {
return txt.replace(/ /g, "_").toLowerCase();
},

unscrub: function(txt) {
return __(txt || '').replace(/-|_/g, " ").replace(/\w*/g,
function(keywords){return keywords.charAt(0).toUpperCase() + keywords.substr(1).toLowerCase();});
},

can_create: function(doctype) {
return frappe.boot.user.can_create.indexOf(doctype)!==-1;
},


+ 0
- 1
frappe/public/js/frappe/ui/field_group.js Zobrazit soubor

@@ -67,7 +67,6 @@ frappe.ui.FieldGroup = frappe.ui.form.Layout.extend({
var f = this.fields_dict[key];
if(f.get_parsed_value) {
var v = f.get_parsed_value();

if(f.df.reqd && is_null(v))
errors.push(__(f.df.label));



+ 23
- 18
frappe/public/js/frappe/ui/keyboard.js Zobrazit soubor

@@ -8,7 +8,6 @@ frappe.ui.keys.setup = function() {
for(var i=0, l = frappe.ui.keys.handlers[key].length; i<l; i++) {
var handler = frappe.ui.keys.handlers[key][i];
var _out = handler.apply(this, [e]);

if(_out===false) {
out = _out;
}
@@ -23,10 +22,9 @@ frappe.ui.keys.get_key = function(e) {
//safari doesn't have key property
if(!key) {
var keycode = e.keyCode || e.which;
key = frappe.ui.keys.key_map[keycode] ||
String.fromCharCode(keycode);
key = frappe.ui.keys.key_map[keycode] || String.fromCharCode(keycode);
}
if(key.substr(0, 5)==='Arrow') {
if(key.substr(0, 5) === 'Arrow') {
// ArrowDown -> down
key = key.substr(5).toLowerCase();
}
@@ -69,22 +67,13 @@ frappe.ui.keys.on('ctrl+b', function(e) {
}
});

frappe.ui.keys.on('Escape', function(e) {
// close open grid row
var open_row = $(".grid-row-open");
if(open_row.length) {
var grid_row = open_row.data("grid_row");
grid_row.toggle_view(false);
return false;
}

// close open dialog
if(cur_dialog && !cur_dialog.no_cancel_flag) {
cur_dialog.cancel();
return false;
}
frappe.ui.keys.on('escape', function(e) {
close_grid_and_dialog();
});

frappe.ui.keys.on('esc', function(e) {
close_grid_and_dialog();
});

frappe.ui.keys.on('Enter', function(e) {
if(cur_dialog && cur_dialog.confirm_dialog) {
@@ -127,4 +116,20 @@ frappe.ui.keyCode = {
ENTER: 13,
TAB: 9,
SPACE: 32
}

function close_grid_and_dialog() {
// close open grid row
var open_row = $(".grid-row-open");
if (open_row.length) {
var grid_row = open_row.data("grid_row");
grid_row.toggle_view(false);
return false;
}

// close open dialog
if (cur_dialog && !cur_dialog.no_cancel_flag) {
cur_dialog.cancel();
return false;
}
}

+ 30
- 13
frappe/public/js/frappe/ui/sort_selector.js Zobrazit soubor

@@ -91,16 +91,12 @@ frappe.ui.SortSelector = Class.extend({
var me = this;
var meta = frappe.get_meta(this.doctype);

var { meta_sort_field, meta_sort_order } = this.get_meta_sort_field();

if(!this.args.sort_by) {
if(meta.sort_field) {
if(meta.sort_field.indexOf(',')!==-1) {
parts = meta.sort_field.split(',')[0].split(' ');
this.args.sort_by = parts[0];
this.args.sort_order = parts[1];
} else {
this.args.sort_by = meta.sort_field;
this.args.sort_order = meta.sort_order.toLowerCase();
}
if(meta_sort_field) {
this.args.sort_by = meta_sort_field;
this.args.sort_order = meta_sort_order;
} else {
// default
this.args.sort_by = 'modified';
@@ -115,7 +111,7 @@ frappe.ui.SortSelector = Class.extend({
if(!this.args.options) {
// default options
var _options = [
{'fieldname': 'modified'},
{'fieldname': 'modified'}
]

// title field
@@ -130,9 +126,15 @@ frappe.ui.SortSelector = Class.extend({
}
});

_options.push({'fieldname': 'name'});
_options.push({'fieldname': 'creation'});
_options.push({'fieldname': 'idx'});
// meta sort field
if(meta_sort_field) _options.push({ 'fieldname': meta_sort_field });
// more default options
_options.push(
{'fieldname': 'name'},
{'fieldname': 'creation'},
{'fieldname': 'idx'}
)

// de-duplicate
this.args.options = _options.uniqBy(function(obj) {
@@ -151,6 +153,21 @@ frappe.ui.SortSelector = Class.extend({
this.sort_by = this.args.sort_by;
this.sort_order = this.args.sort_order;
},
get_meta_sort_field: function() {
var meta = frappe.get_meta(this.doctype);
if(meta.sort_field && meta.sort_field.includes(',')) {
var parts = meta.sort_field.split(',')[0].split(' ');
return {
meta_sort_field: parts[0],
meta_sort_order: parts[1]
}
} else {
return {
meta_sort_field: meta.sort_field,
meta_sort_order: meta.sort_order.toLowerCase()
}
}
},
get_label: function(fieldname) {
if(fieldname==='idx') {
return __("Most Used");


+ 1
- 5
frappe/public/js/frappe/ui/toolbar/search_utils.js Zobrazit soubor

@@ -575,10 +575,6 @@ frappe.search.utils = {
return rendered;
}

},
}

unscrub_and_titlecase: function(str) {
return __(str || '').replace(/-|_/g, " ").replace(/\w*/g,
function(keywords){return keywords.charAt(0).toUpperCase() + keywords.substr(1).toLowerCase();});
},
}

+ 9
- 0
frappe/public/js/frappe/views/kanban/kanban_board.js Zobrazit soubor

@@ -41,6 +41,12 @@ frappe.provide("frappe.views");
columns: columns,
cur_list: opts.cur_list
});
})
.fail(function() {
// redirect back to List
setTimeout(() => {
frappe.set_route('List', opts.doctype, 'List');
}, 2000);
});
},
update_cards: function (updater, cards) {
@@ -1038,6 +1044,9 @@ frappe.provide("frappe.views");
function is_filters_modified(board, cur_list) {
return new Promise(function(resolve, reject) {
setTimeout(function() {
// sometimes the filter_list is not initiated, so early return
if(!cur_list.filter_list) resolve(false);

var list_filters = JSON.stringify(cur_list.filter_list.get_filters());
resolve(list_filters !== board.filters);
}, 2000);


+ 7
- 4
frappe/public/js/frappe/views/reports/reportview.js Zobrazit soubor

@@ -227,7 +227,7 @@ frappe.views.ReportView = frappe.ui.BaseList.extend({

set_route_filters: function(first_load) {
var me = this;
if(frappe.route_options && !this.user_settings.filters) {
if(frappe.route_options) {
this.set_filters_from_route_options();
return true;
} else if(this.user_settings
@@ -548,9 +548,12 @@ frappe.views.ReportView = frappe.ui.BaseList.extend({
$.each(frappe.model.get_all_docs(doc), function(i, d) {
// find the document of the current updated record
// from locals (which is synced in the response)
if(item[d.doctype + ":name"]===d.name) {
for(k in d) {
v = d[k];
var name = item[d.doctype + ":name"];
if(!name) name = item.name;

if(name===d.name) {
for(var k in d) {
var v = d[k];
if(frappe.model.std_fields_list.indexOf(k)===-1
&& item[k]!==undefined) {
new_item[k] = v;


+ 11
- 2
frappe/public/less/desk.less Zobrazit soubor

@@ -431,7 +431,7 @@ textarea.form-control {
flex: 0 0 36px;
order: -1;
justify-content: flex-end;
input[type="checkbox"] {
margin-right: 0;
}
@@ -894,8 +894,17 @@ input[type="checkbox"] {
}
}

// Will not be required after commonifying lists with empty state
.multiselect-empty-state{
min-height: 300px;
display: flex;
align-items: center;
justify-content: center;
height: 100%;
}

// mozilla doesn't support pseudo elements on checkbox
// mozilla doesn't support
// pseudo elements on checkbox
@-moz-document url-prefix() {
input[type="checkbox"] {
visibility: visible;


+ 1
- 1
frappe/utils/global_search.py Zobrazit soubor

@@ -54,7 +54,7 @@ def rebuild_for_doctype(doctype):
:param doctype: Doctype '''

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


+ 1
- 1
frappe/utils/install.py Zobrazit soubor

@@ -105,7 +105,7 @@ def import_country_and_currency():
country = frappe._dict(data[name])
add_country_and_currency(name, country)

print
print()

# enable frequently used currencies
for currency in ("INR", "USD", "GBP", "EUR", "AED", "AUD", "JPY", "CNY", "CHF"):


+ 22
- 9
frappe/utils/xlsxutils.py Zobrazit soubor

@@ -9,7 +9,6 @@ import openpyxl
from cStringIO import StringIO
from openpyxl.styles import Font

import html2text

# return xlsx file object
def make_xlsx(data, sheet_name):
@@ -24,19 +23,33 @@ def make_xlsx(data, sheet_name):
clean_row = []
for item in row:
if isinstance(item, basestring):
obj = html2text.HTML2Text()
obj.ignore_links = True
obj.body_width = 0
obj = obj.handle(unicode(item or ""))
obj = obj.rsplit('\n', 1)
value = obj[0]
value = handle_html(item)
else:
value = item

clean_row.append(value)

ws.append(clean_row)

xlsx_file = StringIO()
wb.save(xlsx_file)
return xlsx_file
return xlsx_file


def handle_html(data):
# import html2text
from html2text import unescape, HTML2Text

h = HTML2Text()
h.unicode_snob = True
h = h.unescape(data or "")

obj = HTML2Text()
obj.ignore_links = True
obj.body_width = 0
value = obj.handle(h)
value = value.split('\n', 1)
value = value[0].split('# ',1)
if len(value) < 2:
return value[0]
else:
return value[1]

Načítá se…
Zrušit
Uložit