@@ -7,6 +7,22 @@ from frappe import _ | |||
from frappe.utils import cstr | |||
from frappe.model import default_fields | |||
@frappe.whitelist() | |||
def make_mapped_doc(method, source_name, selected_children=None): | |||
'''Returns the mapped document calling the given mapper method. | |||
Sets selected_children as flags for the `get_mapped_doc` method. | |||
Called from `open_mapped_doc` from create_new.js''' | |||
method = frappe.get_attr(method) | |||
if method not in frappe.whitelisted: | |||
raise frappe.PermissionError | |||
frappe.flags.selected_children = selected_children | |||
return method(source_name) | |||
def get_mapped_doc(from_doctype, from_docname, table_maps, target_doc=None, | |||
postprocess=None, ignore_permissions=False, ignore_child_tables=False): | |||
@@ -51,6 +67,13 @@ def get_mapped_doc(from_doctype, from_docname, table_maps, target_doc=None, | |||
if not table_map["condition"](source_d): | |||
continue | |||
# if children are selected (checked from UI) for this table type, | |||
# and this record is not in the selected children, then continue | |||
if (frappe.flags.selected_children | |||
and (df.fieldname in frappe.flags.selected_children) | |||
and source_d.name not in frappe.flags.selected_children): | |||
continue | |||
target_child_doctype = table_map["doctype"] | |||
target_parentfield = target_doc.get_parentfield_of_doctype(target_child_doctype) | |||
@@ -58,10 +58,45 @@ frappe.ui.form.Grid = Class.extend({ | |||
this.custom_buttons = {}; | |||
this.grid_buttons = this.wrapper.find('.grid-buttons'); | |||
this.remove_rows_button = this.grid_buttons.find('.grid-remove-rows') | |||
this.setup_allow_bulk_edit(); | |||
this.setup_check(); | |||
}, | |||
setup_check: function() { | |||
var me = this; | |||
this.wrapper.on('click', '.grid-row-check', function(e) { | |||
$check = $(this); | |||
if($check.parents('.grid-heading-row:first').length!==0) { | |||
$check.parents('.form-grid:first').find('.grid-row-check').prop('checked', $check.prop('checked')); | |||
} | |||
me.refresh_remove_rows_button(); | |||
}); | |||
this.remove_rows_button.on('click', function() { | |||
me.get_selected().forEach(function(docname) { | |||
me.grid_rows_by_docname[docname].remove(); | |||
}); | |||
setTimeout(function() { me.refresh_remove_rows_button(); }, 100); | |||
}); | |||
}, | |||
refresh_remove_rows_button: function() { | |||
this.remove_rows_button.toggleClass('hide', | |||
this.wrapper.find('.grid-body .grid-row-check:checked:first').length ? false : true); | |||
}, | |||
get_selected: function() { | |||
var selected = []; | |||
var me = this; | |||
this.wrapper.find('.grid-body .grid-row-check:checked').each(function() { | |||
selected.push($(this).parents('.grid-row:first').attr('data-name')); | |||
}); | |||
return selected; | |||
}, | |||
refresh_checks: function() { | |||
var show = this.is_editable() || this.frm.has_mapper(); | |||
this.wrapper.find('.grid-row-check').toggle(show); | |||
}, | |||
make_head: function() { | |||
// labels | |||
if(!this.header_row) { | |||
@@ -132,6 +167,7 @@ frappe.ui.form.Grid = Class.extend({ | |||
// toolbar | |||
this.setup_toolbar(); | |||
this.refresh_checks(); | |||
// sortable | |||
if(this.is_sortable() && !this.sortable_setup_done) { | |||
@@ -143,6 +179,8 @@ frappe.ui.form.Grid = Class.extend({ | |||
this.last_docname = this.frm.docname; | |||
frappe.utils.scroll_to(_scroll_y); | |||
} | |||
this.refresh_remove_rows_button(); | |||
}, | |||
setup_toolbar: function() { | |||
if(this.is_editable()) { | |||
@@ -216,7 +254,6 @@ frappe.ui.form.Grid = Class.extend({ | |||
return; | |||
} | |||
new Sortable($rows.get(0), { | |||
group: {name: 'row'}, | |||
handle: ".sortable-handle", | |||
@@ -545,6 +582,7 @@ frappe.ui.form.GridRow = Class.extend({ | |||
init: function(opts) { | |||
this.on_grid_fields_dict = {}; | |||
this.on_grid_fields = []; | |||
this.row_check_html = '<input type="checkbox" class="grid-row-check pull-left">'; | |||
this.columns = {}; | |||
this.columns_list = []; | |||
$.extend(this, opts); | |||
@@ -552,9 +590,13 @@ frappe.ui.form.GridRow = Class.extend({ | |||
}, | |||
make: function() { | |||
var me = this; | |||
this.wrapper = $('<div class="grid-row"></div>').appendTo(this.parent).data("grid_row", this); | |||
this.row = $('<div class="data-row row sortable-handle"></div>').appendTo(this.wrapper) | |||
.on("click", function() { | |||
.on("click", function(e) { | |||
if($(e.target).hasClass('grid-row-check')) { | |||
return; | |||
} | |||
if(me.grid.allow_on_grid_editing() && me.grid.is_editable()) { | |||
// pass | |||
} else { | |||
@@ -563,6 +605,11 @@ frappe.ui.form.GridRow = Class.extend({ | |||
} | |||
}); | |||
// no checkboxes if too small | |||
if(this.is_too_small()) { | |||
this.row_check_html = ''; | |||
} | |||
if(this.grid.template && !this.grid.meta.editable_grid) { | |||
this.render_template(); | |||
} else { | |||
@@ -582,7 +629,7 @@ frappe.ui.form.GridRow = Class.extend({ | |||
this.wrapper | |||
.attr('data-name', this.doc.name) | |||
.attr("data-idx", this.doc.idx) | |||
.find(".row-index, .grid-form-row-index").html(this.doc.idx) | |||
.find(".row-index span, .grid-form-row-index").html(this.doc.idx) | |||
} | |||
}, | |||
@@ -638,9 +685,9 @@ frappe.ui.form.GridRow = Class.extend({ | |||
if(this.doc) { | |||
if(!this.row_index) { | |||
this.row_index = $('<div style="float: left; margin-left: 15px; margin-top: 8px; \ | |||
margin-right: -20px;"></div>').appendTo(this.row); | |||
margin-right: -20px;">'+this.row_check_html+' <span></span></div>').appendTo(this.row); | |||
} | |||
this.row_index.html(this.doc.idx); | |||
this.row_index.find('span').html(this.doc.idx); | |||
} | |||
this.row_display = $('<div class="row-data template-row">'+ | |||
@@ -657,11 +704,18 @@ frappe.ui.form.GridRow = Class.extend({ | |||
// index (1, 2, 3 etc) | |||
if(!this.row_index) { | |||
this.row_index = $('<div class="row-index col col-xs-1">' + (this.doc ? this.doc.idx : " ")+ '</div>') | |||
var txt = (this.doc ? this.doc.idx : " "); | |||
this.row_index = $('<div class="row-index col col-xs-1">' + | |||
this.row_check_html + | |||
' <span>' + txt + '</span></div>') | |||
.appendTo(this.row) | |||
.on('click', function() { me.toggle_view(); }); | |||
.on('click', function(e) { | |||
if(!$(e.target).hasClass('grid-row-check')) { | |||
me.toggle_view(); | |||
} | |||
}); | |||
} else { | |||
this.row_index.html(this.doc ? this.doc.idx : " "); | |||
this.row_index.find('span').html(txt); | |||
} | |||
this.setup_columns(); | |||
@@ -676,6 +730,10 @@ frappe.ui.form.GridRow = Class.extend({ | |||
this.row.toggleClass('editable-row', this.grid.is_editable()); | |||
}, | |||
is_too_small: function() { | |||
return this.row.width() < 400; | |||
}, | |||
add_open_form_button: function() { | |||
var me = this; | |||
if(this.doc) { | |||
@@ -686,7 +744,7 @@ frappe.ui.form.GridRow = Class.extend({ | |||
.appendTo($('<div class="col col-xs-1"></div>').appendTo(this.row)) | |||
.on('click', function() { me.toggle_view(); return false; }); | |||
if(this.row.width() < 400) { | |||
if(this.is_too_small()) { | |||
// narrow | |||
this.open_form_button.css({'margin-right': '-2px'}); | |||
} | |||
@@ -99,11 +99,11 @@ frappe.ui.form.LinkSelector = Class.extend({ | |||
}) | |||
}) | |||
} else { | |||
$('<div class="alert alert-info">' + __("No Results") | |||
+ (frappe.model.can_read(me.doctype) ? | |||
('. <a class="new-doc">' | |||
+ __("Make a new") + " " + __(me.doctype) + "</a>") : '') | |||
+ '</div>').appendTo(parent).find(".new-doc").click(function() { | |||
$('<p><br><span class="text-muted">' + __("No Results") + '</span>' | |||
+ (frappe.model.can_create(me.doctype) ? | |||
('<br><br><a class="new-doc btn btn-default btn-sm">' | |||
+ __("Make a new {0}", [__(me.doctype)]) + "</a>") : '') | |||
+ '</p>').appendTo(parent).find(".new-doc").click(function() { | |||
me.target.new_doc(); | |||
}); | |||
} | |||
@@ -7,6 +7,8 @@ | |||
<div class="small form-clickable-section grid-footer"> | |||
<div class="row"> | |||
<div class="col-sm-6 grid-buttons"> | |||
<button type="reset" class="btn btn-xs btn-danger grid-remove-rows hide"> | |||
{%= __("Delete") %}</button> | |||
<button type="reset" | |||
class="grid-add-multiple-rows btn btn-xs btn-default hide" | |||
style="margin-right: 10px;"> | |||
@@ -275,9 +275,11 @@ $.extend(frappe.model, { | |||
return frappe.call({ | |||
type: "POST", | |||
method: opts.method, | |||
method: 'frappe.model.mapper.make_mapped_doc', | |||
args: { | |||
"source_name": opts.source_name | |||
method: opts.method, | |||
source_name: opts.source_name, | |||
selected_children: opts.frm.get_selected() | |||
}, | |||
freeze: true, | |||
callback: function(r) { | |||
@@ -398,6 +398,28 @@ _f.Frm.prototype.get_title = function() { | |||
} | |||
} | |||
_f.Frm.prototype.get_selected = function() { | |||
// returns list of children that are selected. returns [parentfield, name] for each | |||
var selected = {}, me = this; | |||
frappe.meta.get_table_fields(this.doctype).forEach(function(df) { | |||
var _selected = me.fields_dict[df.fieldname].grid.get_selected(); | |||
if(_selected.length) { | |||
selected[df.fieldname] = _selected; | |||
} | |||
}); | |||
return selected; | |||
} | |||
_f.Frm.prototype.has_mapper = function() { | |||
// hackalert! | |||
// if open_mapped_doc is mentioned in the custom script, then mapper exists | |||
if(this._has_mapper === undefined) { | |||
this._has_mapper = (this.meta.__js && this.meta.__js.search('open_mapped_doc')!==-1) ? | |||
true: false; | |||
} | |||
return this._has_mapper; | |||
} | |||
_f.Frm.prototype.set_indicator_formatter = function(fieldname, get_color, get_text) { | |||
// get doctype from parent | |||
if(frappe.meta.docfield_map[this.doctype][fieldname]) { | |||
@@ -26,8 +26,7 @@ class TestScheduler(TestCase): | |||
self.assertTrue("all" in frappe.flags.ran_schedulers) | |||
def test_enabled_events(self): | |||
val = json.dumps(["hourly", "hourly_long", "daily", "daily_long", "weekly", "weekly_long", "monthly", "monthly_long"]) | |||
frappe.db.set_global('enabled_scheduler_events', val) | |||
frappe.flags.enabled_events = ["hourly", "hourly_long", "daily", "daily_long", "weekly", "weekly_long", "monthly", "monthly_long"] | |||
# maintain last_event and next_event on the same day | |||
last_event = now_datetime().replace(hour=0, minute=0, second=0, microsecond=0) | |||
@@ -44,8 +43,7 @@ class TestScheduler(TestCase): | |||
self.assertTrue("all" in frappe.flags.ran_schedulers) | |||
self.assertTrue("hourly" in frappe.flags.ran_schedulers) | |||
frappe.db.set_global('enabled_scheduler_events', "") | |||
del frappe.flags['enabled_events'] | |||
def test_enabled_events_day_change(self): | |||
val = json.dumps(["daily", "daily_long", "weekly", "weekly_long", "monthly", "monthly_long"]) | |||
@@ -144,6 +144,9 @@ def trigger(site, event, queued_jobs=(), now=False): | |||
if not queued_jobs and not now: | |||
queued_jobs = get_jobs(site=site, queue=queue) | |||
if frappe.flags.in_test: | |||
frappe.flags.ran_schedulers.append(event) | |||
events = get_scheduler_events(event) | |||
if not events: | |||
return | |||
@@ -155,8 +158,6 @@ def trigger(site, event, queued_jobs=(), now=False): | |||
else: | |||
scheduler_task(site=site, event=event, handler=handler, now=True) | |||
if frappe.flags.in_test: | |||
frappe.flags.ran_schedulers.append(event) | |||
def get_scheduler_events(event): | |||
'''Get scheduler events from hooks and integrations''' | |||
@@ -193,6 +194,9 @@ def log(method, message=None): | |||
return message | |||
def get_enabled_scheduler_events(): | |||
if 'enabled_events' in frappe.flags: | |||
return frappe.flags.enabled_events | |||
enabled_events = frappe.db.get_global("enabled_scheduler_events") | |||
if enabled_events: | |||
if isinstance(enabled_events, basestring): | |||