Merge branch 'develop'
This commit is contained in:
commit
20519ffde9
32 changed files with 435 additions and 85 deletions
|
|
@ -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/
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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 = []
|
||||
|
|
|
|||
|
|
@ -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)):
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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 = {}
|
||||
|
|
|
|||
|
|
@ -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=''):
|
||||
|
|
|
|||
|
|
@ -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')
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
frappe/public/js/frappe/form/multi_select_dialog.js
Normal file
212
frappe/public/js/frappe/form/multi_select_dialog.js
Normal file
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
});
|
||||
|
|
@ -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,
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -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 () {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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");
|
||||
|
|
|
|||
|
|
@ -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();});
|
||||
},
|
||||
}
|
||||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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"):
|
||||
|
|
|
|||
|
|
@ -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"):
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue