Merge branch 'master' into staging-fixes

This commit is contained in:
Ameya Shenoy 2018-10-30 12:09:41 +00:00
commit 25a0b8dba2
No known key found for this signature in database
GPG key ID: AC016A555657D0A3
7 changed files with 703 additions and 5 deletions

View file

@ -17,7 +17,7 @@ from faker import Faker
from .exceptions import *
from .utils.jinja import (get_jenv, get_template, render_template, get_email_from_template, get_jloader)
__version__ = '10.1.56'
__version__ = '10.1.57'
__title__ = "Frappe Framework"
local = Local()

View file

@ -226,6 +226,7 @@ frappe.patches.v11_0.get_docs_apps_if_not_present
frappe.patches.v10_0.set_default_locking_time
frappe.patches.v11_0.rename_google_maps_doctype
frappe.patches.v10_0.modify_smallest_currency_fraction
frappe.patches.v10_0.modify_naming_series_table
frappe.patches.v10_0.enhance_security
frappe.patches.v11_0.multiple_references_in_events
frappe.patches.v11_0.set_allow_self_approval_in_workflow
frappe.patches.v11_0.set_allow_self_approval_in_workflow

View file

@ -0,0 +1,9 @@
'''
Modify the Integer 10 Digits Value to BigInt 20 Digit value
to generate long Naming Series
'''
import frappe
def execute():
frappe.db.sql(""" ALTER TABLE `tabSeries` MODIFY current BIGINT """)

View file

@ -0,0 +1,678 @@
// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
// MIT License. See license.txt
frappe.ui.FilterList = Class.extend({
init: function(opts) {
$.extend(this, opts);
this.filters = [];
this.wrapper = this.parent;
this.stats = [];
this.make();
this.set_events();
},
make: function() {
this.wrapper.find('.show_filters, .filter_area').remove();
this.wrapper.append(`
<div class="show_filters">
<div class="set-filters">
<button
style="margin-right: 10px;"
class="btn btn-default btn-xs new-filter text-muted">
${__("Add Filter")}</button>
</div>
</div>
<div class="filter_area"></div>`);
},
set_events: function() {
var me = this;
// show filters
this.wrapper.find('.new-filter').bind('click', function() {
me.add_filter();
});
this.wrapper.find('.clear-filters').bind('click', function() {
me.clear_filters();
$('.date-range-picker').val('')
me.base_list.run();
$(this).addClass("hide");
});
},
show_filters: function() {
this.wrapper.find('.show_filters').toggle();
if(!this.filters.length) {
this.add_filter(this.doctype, 'name');
this.filters[0].wrapper.find(".filter_field input").focus();
}
},
clear_filters: function() {
$.each(this.filters, function(i, f) { f.remove(true); });
if(this.base_list.page.fields_dict) {
$.each(this.base_list.page.fields_dict, (key, value) => {
value.set_input('');
});
}
this.filters = [];
},
add_filter: function(doctype, fieldname, condition, value, hidden) {
// adds a new filter, returns true if filter has been added
// allow equal to be used as like
let base_filter = this.base_list.page.fields_dict[fieldname];
if (base_filter
&& (base_filter.df.condition==condition
|| (condition==='=' && base_filter.df.condition==='like'))) {
// if filter exists in base_list, then exit
this.base_list.page.fields_dict[fieldname].set_input(value);
return true;
}
if(doctype && fieldname
&& !frappe.meta.has_field(doctype, fieldname)
&& !in_list(frappe.model.std_fields_list, fieldname)) {
frappe.msgprint({
message: __('Filter {0} missing', [fieldname.bold()]),
title: 'Invalid Filter',
indicator: 'red'
});
return false;
}
this.wrapper.find('.show_filters').toggle(true);
var is_new_filter = arguments.length===0;
if (is_new_filter && this.wrapper.find(".is-new-filter:visible").length) {
// only allow 1 new filter at a time!
return false;
}
var filter = this.push_new_filter(doctype, fieldname, condition, value);
if (!filter) return;
if(this.wrapper.find('.clear-filters').hasClass("hide")) {
this.wrapper.find('.clear-filters').removeClass("hide");
}
if (filter && is_new_filter) {
filter.wrapper.addClass("is-new-filter");
} else {
filter.freeze();
}
if (hidden) {
filter.$btn_group.addClass("hide");
}
return true;
},
push_new_filter: function(doctype, fieldname, condition, value) {
if(this.filter_exists(doctype, fieldname, condition, value)) {
return;
}
// if standard filter exists, then clear it.
if(this.base_list.page.fields_dict[fieldname]) {
this.base_list.page.fields_dict[fieldname].set_input('');
}
var filter = new frappe.ui.Filter({
flist: this,
_doctype: doctype,
fieldname: fieldname,
condition: condition,
value: value
});
this.filters.push(filter);
return filter;
},
remove: function(filter) {
// remove `filter` from flist
for (var i in this.filters) {
if (this.filters[i] === filter) {
break;
}
}
if (i!==undefined) {
// remove index
this.filters.splice(i, 1);
}
},
filter_exists: function(doctype, fieldname, condition, value) {
var flag = false;
for(var i in this.filters) {
if(this.filters[i].field) {
var f = this.filters[i].get_value();
if(f[0]==doctype && f[1]==fieldname && f[2]==condition && f[3]==value) {
flag = true;
} else if($.isArray(value) && frappe.utils.arrays_equal(value, f[3])) {
flag = true;
}
}
}
return flag;
},
get_filters: function() {
// get filter values as dict
var values = [];
$.each(this.filters, function(i, filter) {
if(filter.field) {
filter.freeze();
values.push(filter.get_value());
}
});
this.base_list.update_standard_filters(values);
return values;
},
// remove hidden filters
update_filters: function() {
var fl = [];
$.each(this.filters, function(i, f) {
if(f.field) fl.push(f);
})
this.filters = fl;
if(this.filters.length === 0) {
this.wrapper.find('.clear-filters').addClass("hide");
}
},
get_filter: function(fieldname) {
for(var i in this.filters) {
if(this.filters[i].field && this.filters[i].field.df.fieldname==fieldname)
return this.filters[i];
}
},
get_formatted_value: function(field, val){
var value = val;
if(field.df.fieldname==="docstatus") {
value = {0:"Draft", 1:"Submitted", 2:"Cancelled"}[value] || value;
} else if(field.df.original_type==="Check") {
value = {0:"No", 1:"Yes"}[cint(value)];
}
value = frappe.format(value, field.df, {only_value: 1});
return value;
}
});
frappe.ui.Filter = Class.extend({
init: function(opts) {
$.extend(this, opts);
this.doctype = this.flist.doctype;
this.make();
this.make_select();
this.set_events();
},
make: function() {
this.wrapper = $(frappe.render_template("edit_filter", {}))
.appendTo(this.flist.wrapper.find('.filter_area'));
},
make_select: function() {
var me = this;
this.fieldselect = new frappe.ui.FieldSelect({
parent: this.wrapper.find('.fieldname_select_area'),
doctype: this.doctype,
filter_fields: this.filter_fields,
select: function(doctype, fieldname) {
me.set_field(doctype, fieldname);
}
});
if(this.fieldname) {
this.fieldselect.set_value(this._doctype || this.doctype, this.fieldname);
}
},
set_events: function() {
var me = this;
this.wrapper.find("a.remove-filter").on("click", function() {
me.remove();
});
this.wrapper.find(".set-filter-and-run").on("click", function() {
me.wrapper.removeClass("is-new-filter");
me.flist.base_list.run();
me.apply();
});
// add help for "in" codition
me.wrapper.find('.condition').change(function() {
if(!me.field) return;
var condition = $(this).val();
if(in_list(["in", "like", "not in", "not like"], condition)) {
me.set_field(me.field.df.parent, me.field.df.fieldname, 'Data', condition);
if(!me.field.desc_area) {
me.field.desc_area = $('<div class="text-muted small">').appendTo(me.field.wrapper);
}
// set description
me.field.desc_area.html((in_list(["in", "not in"], condition)==="in"
? __("values separated by commas")
: __("use % as wildcard"))+'</div>');
} else {
//if condition selected after refresh
me.set_field(me.field.df.parent, me.field.df.fieldname, null, condition);
}
});
// set the field
if(me.fieldname) {
// pre-sets given (could be via tags!)
return this.set_values(me._doctype, me.fieldname, me.condition, me.value);
} else {
me.set_field(me.doctype, 'name');
}
},
apply: function() {
var f = this.get_value();
this.flist.remove(this);
this.flist.push_new_filter(f[0], f[1], f[2], f[3]);
this.remove();
},
remove: function(dont_run) {
this.wrapper.remove();
this.$btn_group && this.$btn_group.remove();
this.field = null;
this.flist.update_filters();
if(!dont_run) {
this.flist.base_list.refresh(true);
}
},
set_values: function(doctype, fieldname, condition, value) {
// presents given (could be via tags!)
this.set_field(doctype, fieldname);
// change 0,1 to Yes, No for check field type
if(this.field.df.original_type==='Check') {
if(value==0) value = 'No';
else if(value==1) value = 'Yes';
}
if(condition) {
this.wrapper.find('.condition').val(condition).change();
}
if(value!=null) {
return this.field.set_value(value);
}
},
set_field: function(doctype, fieldname, fieldtype, condition) {
var me = this;
// set in fieldname (again)
var cur = me.field ? {
fieldname: me.field.df.fieldname,
fieldtype: me.field.df.fieldtype,
parent: me.field.df.parent,
} : {};
var original_docfield = me.fieldselect.fields_by_name[doctype][fieldname];
if(!original_docfield) {
frappe.msgprint(__("Field {0} is not selectable.", [fieldname]));
return;
}
var df = copy_dict(me.fieldselect.fields_by_name[doctype][fieldname]);
// filter field shouldn't be read only or hidden
df.read_only = 0;
df.hidden = 0;
if(!condition) this.set_default_condition(df, fieldtype);
this.set_fieldtype(df, fieldtype);
// called when condition is changed,
// don't change if all is well
if(me.field && cur.fieldname == fieldname && df.fieldtype == cur.fieldtype &&
df.parent == cur.parent) {
return;
}
// clear field area and make field
me.fieldselect.selected_doctype = doctype;
me.fieldselect.selected_fieldname = fieldname;
// save old text
var old_text = null;
if(me.field) {
old_text = me.field.get_value();
}
var field_area = me.wrapper.find('.filter_field').empty().get(0);
var f = frappe.ui.form.make_control({
df: df,
parent: field_area,
only_input: true,
})
f.refresh();
me.field = f;
if(old_text && me.field.df.fieldtype===cur.fieldtype) {
me.field.set_value(old_text);
}
// run on enter
$(me.field.wrapper).find(':input').keydown(function(ev) {
if(ev.which==13) {
me.flist.base_list.run();
}
})
},
set_fieldtype: function(df, fieldtype) {
// reset
if(df.original_type)
df.fieldtype = df.original_type;
else
df.original_type = df.fieldtype;
df.description = ''; df.reqd = 0;
df.ignore_link_validation = true;
// given
if(fieldtype) {
df.fieldtype = fieldtype;
return;
}
// scrub
if(df.fieldname=="docstatus") {
df.fieldtype="Select",
df.options=[
{value:0, label:__("Draft")},
{value:1, label:__("Submitted")},
{value:2, label:__("Cancelled")}
]
} else if(df.fieldtype=='Check') {
df.fieldtype='Select';
df.options='No\nYes';
} else if(['Text','Small Text','Text Editor','Code','Tag','Comments',
'Dynamic Link','Read Only','Assign'].indexOf(df.fieldtype)!=-1) {
df.fieldtype = 'Data';
} else if(df.fieldtype=='Link' && ['=', '!='].indexOf(this.wrapper.find('.condition').val())==-1) {
df.fieldtype = 'Data';
}
if(df.fieldtype==="Data" && (df.options || "").toLowerCase()==="email") {
df.options = null;
}
if(this.wrapper.find('.condition').val()== "Between" && (df.fieldtype == 'Date' || df.fieldtype == 'Datetime')){
df.fieldtype = 'DateRange';
}
},
set_default_condition: function(df, fieldtype) {
if(!fieldtype) {
// set as "like" for data fields
if (df.fieldtype == 'Data') {
this.wrapper.find('.condition').val('like');
} else if (df.fieldtype == 'Date' || df.fieldtype == 'Datetime'){
this.wrapper.find('.condition').val('Between');
}else{
this.wrapper.find('.condition').val('=');
}
}
},
get_value: function() {
return [this.fieldselect.selected_doctype,
this.field.df.fieldname, this.get_condition(), this.get_selected_value()];
},
get_selected_value: function() {
var val = this.field.get_value();
if(typeof val==='string') {
val = strip(val);
}
if(this.field.df.original_type == 'Check') {
val = (val=='Yes' ? 1 :0);
}
if(this.get_condition().indexOf('like', 'not like')!==-1) {
// automatically append wildcards
if(val) {
if(val.slice(0,1) !== "%") {
val = "%" + val;
}
if(val.slice(-1) !== "%") {
val = val + "%";
}
}
} else if(in_list(["in", "not in"], this.get_condition())) {
if(val) {
val = $.map(val.split(","), function(v) { return strip(v); });
}
} if(val === '%') {
val = "";
}
return val;
},
get_condition: function() {
return this.wrapper.find('.condition').val();
},
freeze: function() {
if(this.$btn_group) {
// already made, just hide the condition setter
this.set_filter_button_text();
this.wrapper.toggle(false);
return;
}
var me = this;
// add a button for new filter if missing
this.$btn_group = $(`<div class="btn-group">
<button class="btn btn-default btn-xs toggle-filter"
title="${ __("Edit Filter") }">
</button>
<button class="btn btn-default btn-xs remove-filter"
title="${ __("Remove Filter") }">
<i class="fa fa-remove text-muted"></i>
</button></div>`)
.insertAfter(this.flist.wrapper.find(".set-filters .new-filter"));
this.set_filter_button_text();
this.$btn_group.find(".remove-filter").on("click", function() {
me.remove();
});
this.$btn_group.find(".toggle-filter").on("click", function() {
$(this).closest('.show_filters').find('.filter_area').show();
me.wrapper.toggle();
})
this.wrapper.toggle(false);
},
set_filter_button_text: function() {
var value = this.get_selected_value();
value = this.flist.get_formatted_value(this.field, value);
// for translations
// __("like"), __("not like"), __("in")
this.$btn_group.find(".toggle-filter")
.html(repl('%(label)s %(condition)s "%(value)s"', {
label: __(this.field.df.label),
condition: __(this.get_condition()),
value: __(value),
}));
}
});
// <select> widget with all fields of a doctype as options
frappe.ui.FieldSelect = Class.extend({
// opts parent, doctype, filter_fields, with_blank, select
init: function(opts) {
var me = this;
$.extend(this, opts);
this.fields_by_name = {};
this.options = [];
this.$select = $('<input class="form-control">')
.appendTo(this.parent)
.on("click", function () { $(this).select(); });
this.select_input = this.$select.get(0);
this.awesomplete = new Awesomplete(this.select_input, {
minChars: 0,
maxItems: 99,
autoFirst: true,
list: me.options,
item: function(item, input) {
return $(repl('<li class="filter-field-select"><p>%(label)s</p></li>', item))
.data("item.autocomplete", item)
.get(0);
}
});
this.$select.on("awesomplete-select", function(e) {
var o = e.originalEvent;
var value = o.text.value;
var item = me.awesomplete.get_item(value);
me.selected_doctype = item.doctype;
me.selected_fieldname = item.fieldname;
if(me.select) me.select(item.doctype, item.fieldname);
});
this.$select.on("awesomplete-selectcomplete", function(e) {
var o = e.originalEvent;
var value = o.text.value;
var item = me.awesomplete.get_item(value);
me.$select.val(item.label);
});
if(this.filter_fields) {
for(var i in this.filter_fields)
this.add_field_option(this.filter_fields[i])
} else {
this.build_options();
}
this.set_value(this.doctype, "name");
window.last_filter = this;
},
get_value: function() {
return this.selected_doctype ? this.selected_doctype + "." + this.selected_fieldname : null;
},
val: function(value) {
if(value===undefined) {
return this.get_value();
} else {
this.set_value(value);
}
},
clear: function() {
this.selected_doctype = null;
this.selected_fieldname = null;
this.$select.val("");
},
set_value: function(doctype, fieldname) {
var me = this;
this.clear();
if(!doctype) return;
// old style
if(doctype.indexOf(".")!==-1) {
var parts = doctype.split(".");
doctype = parts[0];
fieldname = parts[1];
}
$.each(this.options, function(i, v) {
if(v.doctype===doctype && v.fieldname===fieldname) {
me.selected_doctype = doctype;
me.selected_fieldname = fieldname;
me.$select.val(v.label);
return false;
}
});
},
build_options: function() {
var me = this;
me.table_fields = [];
var std_filters = $.map(frappe.model.std_fields, function(d) {
var opts = {parent: me.doctype}
if(d.fieldname=="name") opts.options = me.doctype;
return $.extend(copy_dict(d), opts);
});
// add parenttype column
var doctype_obj = locals['DocType'][me.doctype];
if(doctype_obj && cint(doctype_obj.istable)) {
std_filters = std_filters.concat([{
fieldname: 'parent',
fieldtype: 'Data',
label: 'Parent',
parent: me.doctype,
}]);
}
// blank
if(this.with_blank) {
this.options.push({
label:"",
value:"",
})
}
// main table
var main_table_fields = std_filters.concat(frappe.meta.docfield_list[me.doctype]);
$.each(frappe.utils.sort(main_table_fields, "label", "string"), function(i, df) {
// show fields where user has read access and if report hide flag is not set
if(frappe.perm.has_perm(me.doctype, df.permlevel, "read") && !df.report_hide)
me.add_field_option(df);
});
// child tables
$.each(me.table_fields, function(i, table_df) {
if(table_df.options && !table_df.hidden) {
var child_table_fields = [].concat(frappe.meta.docfield_list[table_df.options]);
$.each(frappe.utils.sort(child_table_fields, "label", "string"), function(i, df) {
// show fields where user has read access and if report hide flag is not set
if(frappe.perm.has_perm(me.doctype, df.permlevel, "read") && !df.report_hide)
me.add_field_option(df);
});
}
});
},
add_field_option: function(df) {
var me = this;
if(me.doctype && df.parent==me.doctype) {
var label = __(df.label);
var table = me.doctype;
if(df.fieldtype=='Table') me.table_fields.push(df);
} else {
var label = __(df.label) + ' (' + __(df.parent) + ')';
var table = df.parent;
}
if(frappe.model.no_value_type.indexOf(df.fieldtype) == -1 &&
!(me.fields_by_name[df.parent] && me.fields_by_name[df.parent][df.fieldname]) && !df.hidden) {
this.options.push({
label: label,
value: table + "." + df.fieldname,
fieldname: df.fieldname,
doctype: df.parent
});
if(!me.fields_by_name[df.parent]) me.fields_by_name[df.parent] = {};
me.fields_by_name[df.parent][df.fieldname] = df;
}
},
})

File diff suppressed because one or more lines are too long

View file

@ -177,7 +177,7 @@ class HelpDatabase(object):
with io.open(fpath, 'r', encoding = 'utf-8') as f:
try:
content = frappe.render_template(f.read(),
{'docs_base_url': '/assets/{docs_app}_docs'.format(docs_app=docs_app)})
{'docs_base_url': '/assets/{app}_docs'.format(app=app)}, safe_render=False)
relpath = self.get_out_path(fpath)
relpath = relpath.replace("user", app)

View file

@ -50,12 +50,16 @@ def validate_template(html):
frappe.msgprint('Line {}: {}'.format(e.lineno, e.message))
frappe.throw(frappe._("Syntax error in template"))
def render_template(template, context, is_path=None):
def render_template(template, context, is_path=None, safe_render=True):
'''Render a template using Jinja
:param template: path or HTML containing the jinja template
:param context: dict of properties to pass to the template
:param is_path: (optional) assert that the `template` parameter is a path'''
:param is_path: (optional) assert that the `template` parameter is a path
:param safe_render: (optional) prevent server side scripting via jinja templating
'''
from frappe import throw
if not template:
return ""
@ -66,6 +70,8 @@ def render_template(template, context, is_path=None):
or (template.endswith('.html') and '\n' not in template)):
return get_jenv().get_template(template).render(context)
else:
if safe_render and ".__" in template:
throw("Illegal template")
return get_jenv().from_string(template).render(context)
def get_allowed_functions_for_jenv():