@@ -14,16 +14,24 @@ from frappe import _ | |||
@frappe.whitelist() | |||
def get(): | |||
args = get_form_params() | |||
save_list_settings_fields = False | |||
if args.save_list_settings_fields: | |||
save_list_settings_fields = True | |||
del args['save_list_settings_fields'] | |||
data = compress(execute(**args)) | |||
# update list settings if new search | |||
if not cint(args.limit_start) or cint(args.limit or args.limit_page_length) != 20: | |||
list_settings = { | |||
'columns': args.fields, | |||
'filters': args.filters, | |||
'limit': args.limit or args.limit_page_length, | |||
'order_by': args.order_by | |||
} | |||
if save_list_settings_fields: | |||
list_settings['fields'] = args.fields | |||
update_list_settings(args.doctype, list_settings) | |||
return data | |||
@@ -1,6 +1,6 @@ | |||
import frappe, json | |||
def get_list_settings(doctype): | |||
def get_list_settings(doctype, for_update=False): | |||
list_settings = frappe.cache().hget('_list_settings', | |||
'{0}::{1}'.format(doctype, frappe.session.user)) | |||
@@ -8,17 +8,19 @@ def get_list_settings(doctype): | |||
list_settings = frappe.db.sql('''select * from __ListSettings | |||
where user=%s and doctype=%s''', (frappe.session.user, doctype), as_dict=True) | |||
list_settings = list_settings and list_settings[0] or '{}' | |||
update_list_settings(doctype, list_settings) | |||
if not for_update: | |||
update_list_settings(doctype, list_settings) | |||
return list_settings | |||
def update_list_settings(doctype, list_settings): | |||
'''update list settings in cache''' | |||
if not isinstance(list_settings, basestring): | |||
list_settings = json.dumps(list_settings) | |||
current = json.loads(get_list_settings(doctype, for_update = True)) | |||
current.update(list_settings) | |||
frappe.cache().hset('_list_settings', '{0}::{1}'.format(doctype, frappe.session.user), | |||
list_settings) | |||
json.dumps(current)) | |||
def sync_list_settings(): | |||
'''Sync from cache to database (called asynchronously via the browser)''' | |||
@@ -1,4 +1,16 @@ | |||
from frappe.installer import create_list_settings_table | |||
from frappe.model.utils.list_settings import update_list_settings | |||
import frappe, json | |||
def execute(): | |||
create_list_settings_table() | |||
for user in frappe.db.get_all('User', {'user_type': 'System User'}): | |||
defaults = frappe.defaults.get_defaults_for(user.name) | |||
for key, value in defaults.iteritems(): | |||
if key.startswith('_list_settings:'): | |||
doctype = key.replace('_list_settings:', '') | |||
columns = ['`tab{1}`.`{0}`'.format(*c) for c in json.loads(value)] | |||
update_list_settings(doctype, {'fields': columns}) | |||
@@ -1,3 +1,6 @@ | |||
body { | |||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif; | |||
} | |||
a { | |||
cursor: pointer; | |||
} | |||
@@ -1,3 +1,6 @@ | |||
body { | |||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif; | |||
} | |||
a { | |||
cursor: pointer; | |||
} | |||
@@ -1,3 +1,6 @@ | |||
body { | |||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif; | |||
} | |||
a { | |||
cursor: pointer; | |||
} | |||
@@ -289,3 +289,4 @@ frappe.dom.set_box_shadow = function(ele, spread) { | |||
return this; | |||
} | |||
})(jQuery); | |||
@@ -132,14 +132,6 @@ frappe.views.DocListView = frappe.ui.Listing.extend({ | |||
this.init_select_all(); | |||
}, | |||
init_list_settings: function() { | |||
if(frappe.model.list_settings[this.doctype]) { | |||
this.list_settings = frappe.model.list_settings[this.doctype]; | |||
} else { | |||
this.list_settings = {}; | |||
} | |||
}, | |||
init_headers: function() { | |||
var main = frappe.render_template("list_item_main_head", { | |||
columns: this.listview.columns, | |||
@@ -227,10 +219,18 @@ frappe.views.DocListView = frappe.ui.Listing.extend({ | |||
if(this.list_settings.order_by) { | |||
// last saved settings | |||
parts = this.list_settings.order_by.split(' '); | |||
var order_by = this.list_settings.order_by | |||
if(order_by.indexOf('`.`')!==-1) { | |||
// scrub table name (separted by dot), like `tabTime Log`.`modified` desc` | |||
order_by = order_by.split('.')[1]; | |||
} | |||
parts = order_by.split(' '); | |||
if(parts.length===2) { | |||
var fieldname = strip(parts[0], '`'); | |||
args = { | |||
sort_by: parts[0], | |||
sort_by: fieldname, | |||
sort_order: parts[1] | |||
} | |||
} | |||
@@ -267,6 +267,7 @@ frappe.views.DocListView = frappe.ui.Listing.extend({ | |||
// init list | |||
this.make({ | |||
method: 'frappe.desk.reportview.get', | |||
save_list_settings: true, | |||
get_args: this.get_args, | |||
parent: this.wrapper, | |||
freeze: true, | |||
@@ -303,10 +304,8 @@ frappe.views.DocListView = frappe.ui.Listing.extend({ | |||
this.listview.settings.refresh(this); | |||
} | |||
if(frappe.route_options) { | |||
this.set_route_options(); | |||
this.run(); | |||
} else if(this.dirty) { | |||
this.set_filters_before_run(); | |||
if(this.dirty) { | |||
this.run(); | |||
} else { | |||
if(new Date() - (this.last_updated_on || 0) > 30000) { | |||
@@ -316,57 +315,69 @@ frappe.views.DocListView = frappe.ui.Listing.extend({ | |||
} | |||
}, | |||
set_route_options: function() { | |||
set_filters_before_run: function() { | |||
// set filters from frappe.route_options | |||
// before switching pages, frappe.route_options can have pre-set filters | |||
// for the list view | |||
var me = this; | |||
me.filter_list.clear_filters(); | |||
$.each(frappe.route_options, function(key, value) { | |||
var doctype = null; | |||
// if `Child DocType.fieldname` | |||
if (key.indexOf(".")!==-1) { | |||
doctype = key.split(".")[0]; | |||
key = key.split(".")[1]; | |||
} | |||
// find the table in which the key exists | |||
// for example the filter could be {"item_code": "X"} | |||
// where item_code is in the child table. | |||
// we can search all tables for mapping the doctype | |||
if(!doctype) { | |||
if(in_list(frappe.model.std_fields_list, key)) { | |||
// standard | |||
doctype = me.doctype; | |||
} else if(frappe.meta.has_field(me.doctype, key)) { | |||
// found in parent | |||
doctype = me.doctype; | |||
} else { | |||
frappe.meta.get_table_fields(me.doctype).every(function(d) { | |||
if(frappe.meta.has_field(d.options, key)) { | |||
doctype = d.options; | |||
return false; | |||
} | |||
}); | |||
if(frappe.route_options) { | |||
this.filter_list.clear_filters(); | |||
$.each(frappe.route_options, function(key, value) { | |||
var doctype = null; | |||
// if `Child DocType.fieldname` | |||
if (key.indexOf(".")!==-1) { | |||
doctype = key.split(".")[0]; | |||
key = key.split(".")[1]; | |||
} | |||
if(!doctype) { | |||
frappe.msgprint(__('Warning: Unable to find {0} in any table related to {1}', [ | |||
key, __(me.doctype)])); | |||
// find the table in which the key exists | |||
// for example the filter could be {"item_code": "X"} | |||
// where item_code is in the child table. | |||
// we can search all tables for mapping the doctype | |||
if(!doctype) { | |||
if(in_list(frappe.model.std_fields_list, key)) { | |||
// standard | |||
doctype = me.doctype; | |||
} else if(frappe.meta.has_field(me.doctype, key)) { | |||
// found in parent | |||
doctype = me.doctype; | |||
} else { | |||
frappe.meta.get_table_fields(me.doctype).every(function(d) { | |||
if(frappe.meta.has_field(d.options, key)) { | |||
doctype = d.options; | |||
return false; | |||
} | |||
}); | |||
if(!doctype) { | |||
frappe.msgprint(__('Warning: Unable to find {0} in any table related to {1}', [ | |||
key, __(me.doctype)])); | |||
} | |||
} | |||
} | |||
} | |||
if(doctype) { | |||
if($.isArray(value)) { | |||
me.filter_list.add_filter(doctype, key, value[0], value[1]); | |||
} else { | |||
me.filter_list.add_filter(doctype, key, "=", value); | |||
if(doctype) { | |||
if($.isArray(value)) { | |||
me.filter_list.add_filter(doctype, key, value[0], value[1]); | |||
} else { | |||
me.filter_list.add_filter(doctype, key, "=", value); | |||
} | |||
} | |||
} | |||
}); | |||
frappe.route_options = null; | |||
}); | |||
frappe.route_options = null; | |||
this.dirty = true; | |||
} else if(this.list_settings && this.list_settings.filters | |||
&& this.list_settings.updated_on != this.list_settings_updated_on) { | |||
// update remembered list settings | |||
this.filter_list.clear_filters(); | |||
this.list_settings.filters.forEach(function(f) { | |||
me.filter_list.add_filter(f[0], f[1], f[2], f[3]); | |||
}); | |||
this.dirty = true; | |||
} | |||
}, | |||
run: function(more) { | |||
@@ -415,6 +426,8 @@ frappe.views.DocListView = frappe.ui.Listing.extend({ | |||
if(this.listview.settings.post_render) { | |||
this.listview.settings.post_render(this); | |||
} | |||
this.list_settings_updated_on = this.list_settings.updated_on; | |||
}, | |||
make_no_result: function() { | |||
@@ -325,6 +325,26 @@ frappe.utils = { | |||
return list.reduce(function(previous_value, current_value) { return flt(previous_value) + flt(current_value); }, 0.0); | |||
}, | |||
arrays_equal: function(arr1, arr2) { | |||
if (!arr1 || !arr2) { | |||
return false; | |||
} | |||
if (arr1.length != arr2.length) { | |||
return false; | |||
} | |||
for (var i = 0; i < arr1.length; i++) { | |||
if ($.isArray(arr1[i])) { | |||
if (!frappe.utils.arrays_equal(arr1[i], arr2[i])) { | |||
return false; | |||
} | |||
} | |||
else if (arr1[i] !== arr2[i]) { | |||
return false; | |||
} | |||
} | |||
return true; | |||
}, | |||
intersection: function(a, b) { | |||
// from stackoverflow: http://stackoverflow.com/questions/1885557/simplest-code-for-array-intersection-in-javascript | |||
/* finds the intersection of | |||
@@ -118,6 +118,7 @@ $.extend(frappe.model, { | |||
if(r.list_settings) { | |||
// remember filters and other settings from last view | |||
frappe.model.list_settings[doctype] = JSON.parse(r.list_settings); | |||
frappe.model.list_settings[doctype].updated_on = moment().toString(); | |||
} | |||
callback && callback(r); | |||
} | |||
@@ -52,7 +52,7 @@ frappe.ui.Listing = Class.extend({ | |||
this.opts.no_result_message = __('Nothing to show'); | |||
} | |||
if(!this.opts.page_length) { | |||
this.opts.page_length = 20; | |||
this.opts.page_length = this.list_settings ? (this.list_settings.limit || 20) : 20; | |||
} | |||
this.opts._more = __("More"); | |||
}, | |||
@@ -174,14 +174,18 @@ frappe.ui.Listing = Class.extend({ | |||
if(this.onreset) this.onreset(); | |||
} | |||
if(!me.opts.no_loading) | |||
if(!me.opts.no_loading) { | |||
me.set_working(true); | |||
} | |||
var args = this.get_call_args(); | |||
this.save_list_settings_locally(args); | |||
return frappe.call({ | |||
method: this.opts.method || 'frappe.desk.query_builder.runquery', | |||
type: "GET", | |||
freeze: (this.opts.freeze != undefined ? this.opts.freeze : true), | |||
args: this.get_call_args(), | |||
args: args, | |||
callback: function(r) { | |||
if(!me.opts.no_loading) | |||
me.set_working(false); | |||
@@ -191,6 +195,39 @@ frappe.ui.Listing = Class.extend({ | |||
no_spinner: this.opts.no_loading | |||
}); | |||
}, | |||
save_list_settings_locally: function(args) { | |||
if(this.opts.save_list_settings && this.doctype && !this.docname) { | |||
// save list settings locally | |||
list_settings = frappe.model.list_settings[this.doctype]; | |||
var different = false; | |||
if(!frappe.utils.arrays_equal(args.filters, list_settings.filters)) { | |||
// settings are dirty if filters change | |||
list_settings.filters = args.filters || []; | |||
different = true; | |||
} | |||
if(list_settings.order_by !== args.order_by) { | |||
list_settings.order_by = args.order_by; | |||
different = true; | |||
} | |||
if(list_settings.limit != args.limit_page_length) { | |||
list_settings.limit = args.limit_page_length || 20 | |||
different = true; | |||
} | |||
// save fields in list settings | |||
if(args.save_list_settings_fields) { | |||
list_settings.fields = args.fields; | |||
}; | |||
if(different) { | |||
list_settings.updated_on = moment().toString(); | |||
} | |||
} | |||
}, | |||
set_working: function(flag) { | |||
this.$w.find('.img-load').toggle(flag); | |||
}, | |||
@@ -325,5 +362,12 @@ frappe.ui.Listing = Class.extend({ | |||
} | |||
} | |||
return this; | |||
} | |||
}, | |||
init_list_settings: function() { | |||
if(frappe.model.list_settings[this.doctype]) { | |||
this.list_settings = frappe.model.list_settings[this.doctype]; | |||
} else { | |||
this.list_settings = {}; | |||
} | |||
}, | |||
}); |
@@ -30,7 +30,7 @@ frappe.views.ReportViewPage = Class.extend({ | |||
me.parent.reportview.run(); | |||
}); | |||
} else { | |||
me.parent.reportview.set_route_filters(); | |||
me.parent.reportview.set_route_filters(true); | |||
me.parent.reportview.run(); | |||
} | |||
}); | |||
@@ -89,10 +89,12 @@ frappe.views.ReportView = frappe.ui.Listing.extend({ | |||
var me = this; | |||
this.page = this.parent.page; | |||
this.page_title = __('Report')+ ': ' + __(this.docname ? (this.doctype + ' - ' + this.docname) : this.doctype); | |||
this.page.set_title(this.page_title) | |||
this.page.set_title(this.page_title); | |||
this.init_list_settings(); | |||
this.make({ | |||
page: this.parent.page, | |||
method: 'frappe.desk.reportview.get', | |||
save_list_settings: true, | |||
get_args: this.get_args, | |||
parent: this.page.main, | |||
start: 0, | |||
@@ -130,8 +132,17 @@ frappe.views.ReportView = frappe.ui.Listing.extend({ | |||
set_init_columns: function() { | |||
// pre-select mandatory columns | |||
var columns = frappe.defaults.get_default("_list_settings:" + this.doctype); | |||
if(!columns) { | |||
var me = this; | |||
var columns = []; | |||
if(this.list_settings.fields) { | |||
this.list_settings.fields.forEach(function(field) { | |||
var coldef = me.get_column_info_from_field(field); | |||
if(!in_list(['_seen', '_comments', '_user_tags', '_assign', '_liked_by', 'docstatus'], coldef[0])) { | |||
columns.push(coldef); | |||
} | |||
}); | |||
}; | |||
if(!columns.length) { | |||
var columns = [['name', this.doctype],]; | |||
$.each(frappe.meta.docfield_list[this.doctype], function(i, df) { | |||
if((df.in_filter || df.in_list_view) && df.fieldname!='naming_series' | |||
@@ -168,7 +179,7 @@ frappe.views.ReportView = frappe.ui.Listing.extend({ | |||
if(opts.sort_order_next) this.sort_order_next_select.val(opts.sort_order_next); | |||
}, | |||
set_route_filters: function() { | |||
set_route_filters: function(first_load) { | |||
var me = this; | |||
if(frappe.route_options) { | |||
me.filter_list.clear_filters(); | |||
@@ -177,7 +188,16 @@ frappe.views.ReportView = frappe.ui.Listing.extend({ | |||
}); | |||
frappe.route_options = null; | |||
return true; | |||
} else if(this.list_settings && this.list_settings.filters && | |||
(this.list_settings.updated_on != this.list_settings_updated_on)) { | |||
// list settings (previous settings) | |||
this.filter_list.clear_filters(); | |||
$.each(this.list_settings.filters, function(i, f) { | |||
me.filter_list.add_filter(f[0], f[1], f[2], f[3]); | |||
}); | |||
return true; | |||
} | |||
this.list_settings_updated_on = this.list_settings.updated_on; | |||
}, | |||
setup_print: function() { | |||
@@ -196,6 +216,7 @@ frappe.views.ReportView = frappe.ui.Listing.extend({ | |||
fields: $.map(this.columns, function(v) { return me.get_full_column_name(v) }), | |||
order_by: this.get_order_by(), | |||
filters: this.filter_list.get_filters(), | |||
save_list_settings_fields: 1, | |||
with_childnames: 1 | |||
} | |||
}, | |||
@@ -229,6 +250,15 @@ frappe.views.ReportView = frappe.ui.Listing.extend({ | |||
return (v[1] ? ('`tab' + v[1] + '`') : this.tab_name) + '.`' + v[0] + '`'; | |||
}, | |||
get_column_info_from_field: function(t) { | |||
if(t.indexOf('.')===-1) { | |||
return [strip(t, '`'), this.doctype]; | |||
} else { | |||
var parts = t.split('.'); | |||
return [strip(parts[1], '`'), strip(parts[0], '`').substr(3)]; | |||
} | |||
}, | |||
// build columns for slickgrid | |||
build_columns: function() { | |||
var me = this; | |||
@@ -706,7 +736,6 @@ frappe.ui.ColumnPicker = Class.extend({ | |||
: null; | |||
}); | |||
frappe.defaults.set_default("_list_settings:" + this.doctype, columns); | |||
this.list.columns = columns; | |||
this.list.run(); | |||
} | |||
@@ -7,6 +7,13 @@ | |||
// font-family: "Open Sans", "Helvetica", Arial, "sans-serif"; | |||
// } | |||
body { | |||
font-family: -apple-system, BlinkMacSystemFont, | |||
"Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", | |||
"Fira Sans", "Droid Sans", "Helvetica Neue", | |||
sans-serif; | |||
} | |||
a { | |||
cursor: pointer; | |||
} | |||
@@ -2,7 +2,7 @@ | |||
# MIT License. See license.txt | |||
from __future__ import unicode_literals | |||
import redis, frappe, re, copy | |||
import redis, frappe, re | |||
import cPickle as pickle | |||
from frappe.utils import cstr | |||
@@ -126,6 +126,10 @@ class RedisWrapper(redis.Redis): | |||
except redis.exceptions.ConnectionError: | |||
pass | |||
def hgetall(self, name): | |||
return {key: pickle.loads(value) for key, value in | |||
super(redis.Redis, self).hgetall(self.make_key(name)).iteritems()} | |||
def hget(self, name, key, generator=None): | |||
if not name in frappe.local.cache: | |||
frappe.local.cache[name] = {} | |||