diff --git a/frappe/api.py b/frappe/api.py index 5ec69d5f6d..99bc996f55 100644 --- a/frappe/api.py +++ b/frappe/api.py @@ -6,7 +6,6 @@ import json import frappe import frappe.handler import frappe.client -import frappe.desk.reportview from frappe.utils.response import build_response from frappe import _ diff --git a/frappe/core/report/todo/todo.py b/frappe/core/report/todo/todo.py index 19a3c6f10c..8757c05d05 100644 --- a/frappe/core/report/todo/todo.py +++ b/frappe/core/report/todo/todo.py @@ -4,15 +4,14 @@ from __future__ import unicode_literals import frappe from frappe import _ -from frappe.desk.reportview import execute as runreport from frappe.utils import getdate def execute(filters=None): priority_map = {"High": 3, "Medium": 2, "Low": 1} - todo_list = runreport(doctype="ToDo", fields=["name", "date", "description", + todo_list = frappe.get_list('ToDo', fields=["name", "date", "description", "priority", "reference_type", "reference_name", "assigned_by", "owner"], - filters=[["ToDo", "status", "=", "Open"]]) + filters={'status': 'Open'}) todo_list.sort(key=lambda todo: (priority_map.get(todo.priority, 0), todo.date and getdate(todo.date) or getdate("1900-01-01")), reverse=True) diff --git a/frappe/desk/form/load.py b/frappe/desk/form/load.py index d5d4fe8f63..77a190ee01 100644 --- a/frappe/desk/form/load.py +++ b/frappe/desk/form/load.py @@ -7,6 +7,7 @@ import frappe.utils import frappe.share import frappe.defaults import frappe.desk.form.meta +from frappe.model.utils.list_settings import get_list_settings from frappe.permissions import get_doc_permissions from frappe import _ @@ -54,6 +55,8 @@ def getdoctype(doctype, with_parent=False, cached_timestamp=None): """load doctype""" docs = [] + parent_dt = None + # with parent (called from report builder) if with_parent: parent_dt = frappe.model.meta.get_parent_dt(doctype) @@ -65,6 +68,7 @@ def getdoctype(doctype, with_parent=False, cached_timestamp=None): docs = get_meta_bundle(doctype) frappe.response['user_permissions'] = get_user_permissions(docs) + frappe.response['list_settings'] = get_list_settings(parent_dt or doctype) if cached_timestamp and docs[0].modified==cached_timestamp: return "use_cache" diff --git a/frappe/desk/form/utils.py b/frappe/desk/form/utils.py index 02348d513a..cdb138b325 100644 --- a/frappe/desk/form/utils.py +++ b/frappe/desk/form/utils.py @@ -60,7 +60,6 @@ def add_comment(doc): @frappe.whitelist() def get_next(doctype, value, prev, filters=None, order_by="modified desc"): - import frappe.desk.reportview prev = not int(prev) sort_field, sort_order = order_by.split(" ") @@ -82,7 +81,7 @@ def get_next(doctype, value, prev, filters=None, order_by="modified desc"): if not order_by[0] in [f[1] for f in filters]: filters.append([doctype, sort_field, condition, value]) - res = frappe.desk.reportview.execute(doctype, + res = frappe.get_list(doctype, fields = ["name"], filters = filters, order_by = sort_field + " " + sort_order, diff --git a/frappe/desk/reportview.py b/frappe/desk/reportview.py index 6d2a64ff3e..fb42082740 100644 --- a/frappe/desk/reportview.py +++ b/frappe/desk/reportview.py @@ -7,11 +7,34 @@ from __future__ import unicode_literals import frappe, json import frappe.permissions from frappe.model.db_query import DatabaseQuery +from frappe.model.utils.list_settings import update_list_settings +from frappe.utils import cint from frappe import _ @frappe.whitelist() def get(): - return compress(execute(**get_form_params())) + 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 = { + '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 def execute(doctype, *args, **kwargs): return DatabaseQuery(doctype).execute(*args, **kwargs) diff --git a/frappe/desk/search.py b/frappe/desk/search.py index 2169538958..8dad0ba59b 100644 --- a/frappe/desk/search.py +++ b/frappe/desk/search.py @@ -4,7 +4,6 @@ # Search from __future__ import unicode_literals import frappe -import frappe.desk.reportview from frappe.utils import cstr, unique # this is called by the Link Field @@ -83,7 +82,7 @@ def search_widget(doctype, txt, query=None, searchfield=None, start=0, fields.append("""locate("{_txt}", `tab{doctype}`.`name`) as `_relevance`""".format( _txt=frappe.db.escape((txt or "").replace("%", "")), doctype=frappe.db.escape(doctype))) - values = frappe.desk.reportview.execute(doctype, + values = frappe.get_list(doctype, filters=filters, fields=fields, or_filters = or_filters, limit_start = start, limit_page_length=page_len, diff --git a/frappe/hooks.py b/frappe/hooks.py index c160adc3a1..ea85d1a0bf 100644 --- a/frappe/hooks.py +++ b/frappe/hooks.py @@ -113,6 +113,7 @@ scheduler_events = { "frappe.email.doctype.email_account.email_account.notify_unreplied", "frappe.utils.error.collect_error_snapshots", "frappe.model.utils.link_count.update_link_count", + 'frappe.model.utils.list_settings.sync_list_settings' ], "daily": [ "frappe.email.bulk.clear_outbox", diff --git a/frappe/installer.py b/frappe/installer.py index a5a7af057d..5e4683838b 100755 --- a/frappe/installer.py +++ b/frappe/installer.py @@ -39,6 +39,8 @@ def install_db(root_login="root", root_password=None, db_name=None, source_sql=N remove_missing_apps() create_auth_table() + create_list_settings_table() + frappe.flags.in_install_db = False def get_current_host(): @@ -72,6 +74,14 @@ def create_auth_table(): `password` VARCHAR(180) NOT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8""") +def create_list_settings_table(): + frappe.db.sql_ddl("""create table if not exists __ListSettings ( + `user` VARCHAR(180) NOT NULL, + `doctype` VARCHAR(180) NOT NULL, + `data` TEXT, + UNIQUE(user, doctype) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8""") + def import_db_from_sql(source_sql, verbose): if verbose: print "Starting database import..." db_name = frappe.conf.db_name @@ -342,7 +352,7 @@ def check_if_ready_for_barracuda(): def extract_sql_gzip(sql_gz_path): success = -1 try: - success = subprocess.check_output(['gzip', '-d', '-v', '-f', sql_gz_path]) + subprocess.check_output(['gzip', '-d', '-v', '-f', sql_gz_path]) except Exception as subprocess.CalledProcessError: print subprocess.CalledProcessError.output finally: diff --git a/frappe/model/utils/list_settings.py b/frappe/model/utils/list_settings.py new file mode 100644 index 0000000000..1e1948d008 --- /dev/null +++ b/frappe/model/utils/list_settings.py @@ -0,0 +1,30 @@ +import frappe, json + +def get_list_settings(doctype, for_update=False): + list_settings = frappe.cache().hget('_list_settings', + '{0}::{1}'.format(doctype, frappe.session.user)) + + if list_settings is None: + 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 '{}' + + 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''' + 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), + json.dumps(current)) + +def sync_list_settings(): + '''Sync from cache to database (called asynchronously via the browser)''' + for key, data in frappe.cache().hgetall('_list_settings').iteritems(): + doctype, user = key.split('::') + frappe.db.sql('''insert into __ListSettings (user, doctype, data) values (%s, %s, %s) + on duplicate key update data=%s''', (user, doctype, data, data)) \ No newline at end of file diff --git a/frappe/patches.txt b/frappe/patches.txt index 2938c54eff..45c700cda8 100644 --- a/frappe/patches.txt +++ b/frappe/patches.txt @@ -126,3 +126,4 @@ frappe.patches.v7_0.set_user_fullname frappe.patches.v7_0.desktop_icons_hidden_by_admin_as_blocked frappe.patches.v7_0.add_communication_in_doc frappe.patches.v7_0.update_send_after_in_bulk_email +frappe.patches.v7_0.setup_list_settings diff --git a/frappe/patches/v7_0/setup_list_settings.py b/frappe/patches/v7_0/setup_list_settings.py new file mode 100644 index 0000000000..80bcd95810 --- /dev/null +++ b/frappe/patches/v7_0/setup_list_settings.py @@ -0,0 +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}) + diff --git a/frappe/public/css/common.css b/frappe/public/css/common.css index 20d00d7f3c..9d9d5bc44a 100644 --- a/frappe/public/css/common.css +++ b/frappe/public/css/common.css @@ -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; } diff --git a/frappe/public/css/desk.css b/frappe/public/css/desk.css index f5b52a660f..881284071c 100644 --- a/frappe/public/css/desk.css +++ b/frappe/public/css/desk.css @@ -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; } diff --git a/frappe/public/css/website.css b/frappe/public/css/website.css index 1223e7bb86..67545e193c 100644 --- a/frappe/public/css/website.css +++ b/frappe/public/css/website.css @@ -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; } diff --git a/frappe/public/js/frappe/dom.js b/frappe/public/js/frappe/dom.js index 66f33e153e..df3f7a00d1 100644 --- a/frappe/public/js/frappe/dom.js +++ b/frappe/public/js/frappe/dom.js @@ -289,3 +289,4 @@ frappe.dom.set_box_shadow = function(ele, spread) { return this; } })(jQuery); + diff --git a/frappe/public/js/frappe/list/doclistview.js b/frappe/public/js/frappe/list/doclistview.js index 6ce1c43518..bf539a83fb 100644 --- a/frappe/public/js/frappe/list/doclistview.js +++ b/frappe/public/js/frappe/list/doclistview.js @@ -118,6 +118,7 @@ frappe.views.DocListView = frappe.ui.Listing.extend({ this.can_delete = frappe.model.can_delete(this.doctype); this.meta = locals.DocType[this.doctype]; this.$page.find('.frappe-list-area').empty(), + this.init_list_settings(); this.setup_listview(); this.init_list(false); this.init_menu(); @@ -194,14 +195,19 @@ frappe.views.DocListView = frappe.ui.Listing.extend({ init_filters: function() { var me = this; - if(this.listview.settings.filters) { - $.each(this.listview.settings.filters, function(i, f) { + var set_filters = function(filters) { + $.each(filters, function(i, f) { if(f.length===3) { f = [me.doctype, f[0], f[1], f[2]] } me.filter_list.add_filter(f[0], f[1], f[2], f[3]); }); } + if(this.list_settings.filters) { + set_filters(this.list_settings.filters); + } else if(this.listview.settings.filters) { + set_filters(this.listview.settings.filters); + } }, init_sort_selector: function() { @@ -210,6 +216,25 @@ frappe.views.DocListView = frappe.ui.Listing.extend({ if(this.listview.sort_selector) { args = this.listview.sort_selector; } + + if(this.list_settings.order_by) { + // last saved settings + 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: fieldname, + sort_order: parts[1] + } + } + } this.sort_selector = new frappe.ui.SortSelector({ parent: this.wrapper.find('.list-filters'), doctype: this.doctype, @@ -234,7 +259,7 @@ frappe.views.DocListView = frappe.ui.Listing.extend({ setup_listview: function() { this.listview = frappe.views.get_listview(this.doctype, this); this.wrapper = this.$page.find('.frappe-list-area'); - this.page_length = 20; + this.page_length = this.list_settings.limit || 20; this.allow_delete = true; }, init_list: function(auto_run) { @@ -242,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, @@ -278,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) { @@ -291,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]; - } + if(frappe.route_options) { + this.filter_list.clear_filters(); + $.each(frappe.route_options, function(key, value) { + var doctype = null; - // 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. + // if `Child DocType.fieldname` + if (key.indexOf(".")!==-1) { + doctype = key.split(".")[0]; + key = key.split(".")[1]; + } - // 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; + // 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) { - 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) { @@ -390,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() { diff --git a/frappe/public/js/frappe/misc/utils.js b/frappe/public/js/frappe/misc/utils.js index b1e8d35183..dded7ff7a7 100644 --- a/frappe/public/js/frappe/misc/utils.js +++ b/frappe/public/js/frappe/misc/utils.js @@ -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 diff --git a/frappe/public/js/frappe/model/model.js b/frappe/public/js/frappe/model/model.js index e6d47df8e5..271ff2a8e9 100644 --- a/frappe/public/js/frappe/model/model.js +++ b/frappe/public/js/frappe/model/model.js @@ -33,6 +33,7 @@ $.extend(frappe.model, { new_names: {}, events: {}, + list_settings: {}, init: function() { // setup refresh if the document is updated somewhere else @@ -113,6 +114,12 @@ $.extend(frappe.model, { } frappe.model.init_doctype(doctype); frappe.defaults.set_user_permissions(r.user_permissions); + + 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); } }); diff --git a/frappe/public/js/frappe/ui/listing.js b/frappe/public/js/frappe/ui/listing.js index d059990318..07c399e916 100644 --- a/frappe/public/js/frappe/ui/listing.js +++ b/frappe/public/js/frappe/ui/listing.js @@ -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 = {}; + } + }, }); diff --git a/frappe/public/js/frappe/ui/sort_selector.js b/frappe/public/js/frappe/ui/sort_selector.js index da0cfa5d21..765c5c3c05 100644 --- a/frappe/public/js/frappe/ui/sort_selector.js +++ b/frappe/public/js/frappe/ui/sort_selector.js @@ -42,11 +42,12 @@ frappe.ui.SortSelector = Class.extend({ }, prepare_args: function() { var me = this; - // make from doctype if not given - if(!this.args && this.doctype) { - this.setup_from_doctype(); + if(!this.args) { + this.args = {}; } + this.setup_from_doctype(); + // if label is missing, add from options if(this.args.sort_by && !this.args.sort_by_label) { this.args.options.every(function(o) { @@ -57,67 +58,73 @@ frappe.ui.SortSelector = Class.extend({ } }, setup_from_doctype: function() { - var args = {}; var me = this; var meta = frappe.get_meta(this.doctype); - if(meta.sort_field) { - if(meta.sort_field.indexOf(',')!==-1) { - parts = meta.sort_field.split(',')[0].split(' '); - args.sort_by = parts[0]; - args.sort_order = parts[1]; + + 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(); + } } else { - args.sort_by = meta.sort_field; - args.sort_order = meta.sort_order.toLowerCase(); + // default + this.args.sort_by = 'modified'; + this.args.sort_order = 'desc'; } - } else { - // default - args.sort_by = 'modified'; - args.sort_order = 'desc'; - } - args.sort_by_label = this.get_label(args.sort_by); - - // default options - args._options = [ - {'fieldname': 'modified'}, - ] - - // title field - if(meta.title_field) { - args._options.push({'fieldname': meta.title_field}); } - // bold or mandatory - meta.fields.forEach(function(df) { - if(df.mandatory || df.bold) { - args._options.push({fieldname: df.fieldname, label: df.label}); - } - }); + if(!this.args.sort_by_label) { + this.args.sort_by_label = this.get_label(this.args.sort_by); + } - args._options.push({'fieldname': 'name'}); - args._options.push({'fieldname': 'creation'}); - args._options.push({'fieldname': 'idx'}); + if(!this.args.options) { + // default options + var _options = [ + {'fieldname': 'modified'}, + ] - // de-duplicate - var added = []; - args.options = []; - args._options.forEach(function(o) { - if(added.indexOf(o.fieldname)===-1) { - args.options.push(o); - added.push(o.fieldname); + // title field + if(meta.title_field) { + _options.push({'fieldname': meta.title_field}); } - }); - // add missing labels - args.options.forEach(function(o) { - if(!o.label) { - o.label = me.get_label(o.fieldname); - } - }); + // bold or mandatory + meta.fields.forEach(function(df) { + if(df.mandatory || df.bold) { + _options.push({fieldname: df.fieldname, label: df.label}); + } + }); + + _options.push({'fieldname': 'name'}); + _options.push({'fieldname': 'creation'}); + _options.push({'fieldname': 'idx'}); + + // de-duplicate + var added = []; + this.args.options = []; + _options.forEach(function(o) { + if(added.indexOf(o.fieldname)===-1) { + me.args.options.push(o); + added.push(o.fieldname); + } + }); + + // add missing labels + this.args.options.forEach(function(o) { + if(!o.label) { + o.label = me.get_label(o.fieldname); + } + }); + } // set default - this.args = args; - this.sort_by = args.sort_by; - this.sort_order = args.sort_order; + this.sort_by = this.args.sort_by; + this.sort_order = this.args.sort_order; }, get_label: function(fieldname) { if(fieldname==='idx') { diff --git a/frappe/public/js/frappe/views/reports/reportview.js b/frappe/public/js/frappe/views/reports/reportview.js index f764e26069..a41f79795f 100644 --- a/frappe/public/js/frappe/views/reports/reportview.js +++ b/frappe/public/js/frappe/views/reports/reportview.js @@ -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(); } diff --git a/frappe/public/less/common.less b/frappe/public/less/common.less index 7449d72b29..50718a5069 100644 --- a/frappe/public/less/common.less +++ b/frappe/public/less/common.less @@ -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; } diff --git a/frappe/utils/redis_wrapper.py b/frappe/utils/redis_wrapper.py index f069b258f7..fadf3f70d5 100644 --- a/frappe/utils/redis_wrapper.py +++ b/frappe/utils/redis_wrapper.py @@ -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] = {}