From db9ba845e5a1bcccbbfce6ac7b3401995394fb9f Mon Sep 17 00:00:00 2001 From: Rushabh Mehta Date: Fri, 15 Jul 2016 16:28:20 +0530 Subject: [PATCH] [enhancement] bulk update tool! --- frappe/__init__.py | 11 ++ frappe/async.py | 3 + frappe/boot.py | 3 +- frappe/config/setup.py | 7 + frappe/desk/doctype/bulk_update/__init__.py | 0 .../desk/doctype/bulk_update/bulk_update.js | 36 ++++ .../desk/doctype/bulk_update/bulk_update.json | 187 ++++++++++++++++++ .../desk/doctype/bulk_update/bulk_update.py | 39 ++++ frappe/public/js/frappe/desk.js | 26 ++- frappe/public/js/frappe/form/control.js | 7 + frappe/public/js/frappe/socketio_client.js | 10 + 11 files changed, 318 insertions(+), 11 deletions(-) create mode 100644 frappe/desk/doctype/bulk_update/__init__.py create mode 100644 frappe/desk/doctype/bulk_update/bulk_update.js create mode 100644 frappe/desk/doctype/bulk_update/bulk_update.json create mode 100644 frappe/desk/doctype/bulk_update/bulk_update.py diff --git a/frappe/__init__.py b/frappe/__init__.py index b0f11dc355..bda3944b69 100644 --- a/frappe/__init__.py +++ b/frappe/__init__.py @@ -1180,6 +1180,17 @@ def attach_print(doctype, name, file_name=None, print_format=None, style=None, h return out +def publish_progress(*args, **kwargs): + """Show the user progress for a long request + + :param percent: Percent progress + :param title: Title + :param doctype: Optional, for DocType + :param name: Optional, for Document name + """ + import frappe.async + return frappe.async.publish_progress(*args, **kwargs) + def publish_realtime(*args, **kwargs): """Publish real-time updates diff --git a/frappe/async.py b/frappe/async.py index 5e7274b89e..786141a162 100644 --- a/frappe/async.py +++ b/frappe/async.py @@ -86,6 +86,9 @@ def remove_old_task_logs(): def is_file_old(file_path): return ((time.time() - os.stat(file_path).st_mtime) > TASK_LOG_MAX_AGE) +def publish_progress(percent, title=None, doctype=None, docname=None): + publish_realtime('progress', {'percent': percent, 'title': title}, + user=frappe.session.user, doctype=doctype, docname=docname) def publish_realtime(event=None, message=None, room=None, user=None, doctype=None, docname=None, task_id=None, diff --git a/frappe/boot.py b/frappe/boot.py index a11ae3de08..24e611d377 100644 --- a/frappe/boot.py +++ b/frappe/boot.py @@ -36,7 +36,8 @@ def get_bootinfo(): load_desktop_icons(bootinfo) bootinfo.module_app = frappe.local.module_app - bootinfo.single_types = frappe.db.sql_list("""select name from tabDocType where issingle=1""") + bootinfo.single_types = frappe.db.sql_list("""select name from tabDocType + where issingle=1""") add_home_page(bootinfo, doclist) bootinfo.page_info = get_allowed_pages() load_translations(bootinfo) diff --git a/frappe/config/setup.py b/frappe/config/setup.py index ee83945661..2034bc839f 100644 --- a/frappe/config/setup.py +++ b/frappe/config/setup.py @@ -109,6 +109,13 @@ def get_data(): "description": _("Rename many items by uploading a .csv file."), "hide_count": True }, + { + "type": "doctype", + "name": "Bulk Update", + "label": _("Bulk Update"), + "description": _("Update many values at one time."), + "hide_count": True + }, { "type": "page", "name": "backups", diff --git a/frappe/desk/doctype/bulk_update/__init__.py b/frappe/desk/doctype/bulk_update/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/frappe/desk/doctype/bulk_update/bulk_update.js b/frappe/desk/doctype/bulk_update/bulk_update.js new file mode 100644 index 0000000000..d6db24036b --- /dev/null +++ b/frappe/desk/doctype/bulk_update/bulk_update.js @@ -0,0 +1,36 @@ +// Copyright (c) 2016, Frappe Technologies and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Bulk Update', { + refresh: function(frm) { + frm.page.set_primary_action(__('Update'), function() { + frappe.call({ + method: 'frappe.desk.doctype.bulk_update.bulk_update.update', + args: { + doctype: frm.doc.document_type, + field: frm.doc.field, + value: frm.doc.update_value, + condition: frm.doc.condition, + limit: frm.doc.limit + }, + callback: function() { + frappe.hide_progress(); + } + }); + }); + }, + document_type: function(frm) { + // set field options + frappe.model.with_doctype(frm.doc.document_type, function() { + var options = $.map(frappe.get_meta(frm.doc.document_type).fields, + function(d) { + if(d.fieldname && frappe.model.no_value_type.indexOf(d.fieldtype)===-1) { + return d.fieldname; + } + return null; + } + ); + frm.set_df_property('field', 'options', options); + }); + } +}); diff --git a/frappe/desk/doctype/bulk_update/bulk_update.json b/frappe/desk/doctype/bulk_update/bulk_update.json new file mode 100644 index 0000000000..7272b7d877 --- /dev/null +++ b/frappe/desk/doctype/bulk_update/bulk_update.json @@ -0,0 +1,187 @@ +{ + "allow_copy": 0, + "allow_import": 0, + "allow_rename": 0, + "beta": 0, + "creation": "2016-07-15 05:51:29.224123", + "custom": 0, + "docstatus": 0, + "doctype": "DocType", + "document_type": "", + "editable_grid": 1, + "fields": [ + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "fieldname": "document_type", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "label": "Document Type", + "length": 0, + "no_copy": 0, + "options": "DocType", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "fieldname": "field", + "fieldtype": "Select", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "label": "Field", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "fieldname": "update_value", + "fieldtype": "Small Text", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "label": "Update Value", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "description": "SQL Conditions. Example: status=\"Open\"", + "fieldname": "condition", + "fieldtype": "Small Text", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "label": "Condition", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "default": "500", + "description": "Max 500 records at a time", + "fieldname": "limit", + "fieldtype": "Int", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "label": "Limit", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + } + ], + "hide_heading": 0, + "hide_toolbar": 0, + "idx": 0, + "image_view": 0, + "in_create": 0, + "in_dialog": 0, + "is_submittable": 0, + "issingle": 1, + "istable": 0, + "max_attachments": 0, + "modified": "2016-07-15 06:24:42.575613", + "modified_by": "Administrator", + "module": "Desk", + "name": "Bulk Update", + "name_case": "", + "owner": "Administrator", + "permissions": [ + { + "amend": 0, + "apply_user_permissions": 0, + "cancel": 0, + "create": 1, + "delete": 1, + "email": 1, + "export": 0, + "if_owner": 0, + "import": 0, + "permlevel": 0, + "print": 1, + "read": 1, + "report": 0, + "role": "System Manager", + "set_user_permissions": 0, + "share": 1, + "submit": 0, + "write": 1 + } + ], + "quick_entry": 0, + "read_only": 0, + "read_only_onload": 0, + "sort_field": "modified", + "sort_order": "DESC", + "track_seen": 0 +} \ No newline at end of file diff --git a/frappe/desk/doctype/bulk_update/bulk_update.py b/frappe/desk/doctype/bulk_update/bulk_update.py new file mode 100644 index 0000000000..d0b9f98457 --- /dev/null +++ b/frappe/desk/doctype/bulk_update/bulk_update.py @@ -0,0 +1,39 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2015, Frappe Technologies and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +import frappe +from frappe.model.document import Document +from frappe import _ +from frappe.utils import cint + +class BulkUpdate(Document): + pass + +@frappe.whitelist() +def update(doctype, field, value, condition='', limit=500): + if not limit or cint(limit) > 500: + limit = 500 + + if condition: + condition = ' where ' + condition + + if ';' in condition: + frappe.throw('; not allowed in condition') + + items = frappe.db.sql_list('select name from `tab{0}`{1} limit 0, {2}'.format(doctype, + condition, limit), debug=1) + n = len(items) + + for i, d in enumerate(items): + doc = frappe.get_doc(doctype, d) + doc.set(field, value) + doc.save() + + frappe.publish_progress(float(i)*100/n, + title = _('Updating Records'), doctype='Bulk Update', docname='Bulk Update') + + # clear messages + frappe.local.message_log = [] + frappe.msgprint(_('{0} records updated').format(n), title=_('Success'), indicator='green') \ No newline at end of file diff --git a/frappe/public/js/frappe/desk.js b/frappe/public/js/frappe/desk.js index 9bc9de127e..27682ae6e0 100644 --- a/frappe/public/js/frappe/desk.js +++ b/frappe/public/js/frappe/desk.js @@ -6,7 +6,7 @@ frappe.start_app = function() { return; frappe.assets.check(); frappe.provide('frappe.app'); - $.extend(frappe.app, new frappe.Application()); + frappe.app = new frappe.Application(); } $(document).ready(function() { @@ -249,22 +249,28 @@ frappe.Application = Class.extend({ $('').appendTo("head"); }, + trigger_primary_action: function() { + if(cur_dialog) { + // trigger primary + cur_dialog.get_primary_btn().trigger("click"); + } else if(cur_frm && cur_frm.page.btn_primary.is(':visible')) { + cur_frm.page.btn_primary.trigger('click'); + } else if(frappe.container.page.save_action) { + frappe.container.page.save_action(); + } + }, + setup_keyboard_shortcuts: function() { + var me = this; + $(document) .keydown("meta+g ctrl+g", function(e) { - $("#navbar-search").focus() + $("#navbar-search").focus(); return false; }) .keydown("meta+s ctrl+s", function(e) { e.preventDefault(); - if(cur_dialog) { - // trigger primary - cur_dialog.get_primary_btn().trigger("click"); - } else if(cur_frm && cur_frm.page.btn_primary.is(':visible')) { - cur_frm.page.btn_primary.trigger('click'); - } else if(frappe.container.page.save_action) { - frappe.container.page.save_action(); - } + me.trigger_primary_action(); return false; }) .keydown("meta+b ctrl+b", function(e) { diff --git a/frappe/public/js/frappe/form/control.js b/frappe/public/js/frappe/form/control.js index 90998a7d30..5206e516fd 100644 --- a/frappe/public/js/frappe/form/control.js +++ b/frappe/public/js/frappe/form/control.js @@ -421,6 +421,13 @@ frappe.ui.form.ControlData = frappe.ui.form.ControlInput.extend({ this.has_input = true; this.bind_change_event(); this.bind_focusout(); + + // somehow this event does not bubble up to document + // after v7, if you can debug, remove this + this.$input.keydown("ctrl+s meta+s", function(e) { + e.preventDefault(); + frappe.app && frappe.app.trigger_primary_action(); + }); }, set_input_attributes: function() { this.$input diff --git a/frappe/public/js/frappe/socketio_client.js b/frappe/public/js/frappe/socketio_client.js index 2f4ed84d16..268d17ea21 100644 --- a/frappe/public/js/frappe/socketio_client.js +++ b/frappe/public/js/frappe/socketio_client.js @@ -31,6 +31,16 @@ frappe.socket = { eval(message); }); + frappe.socket.socket.on('progress', function(data) { + if(data.percent) { + if(data.percent==100) { + frappe.hide_progress(); + } else { + frappe.show_progress(data.title || __("Progress"), data.percent, 100); + } + } + }); + frappe.socket.setup_listeners(); frappe.socket.setup_reconnect();