diff --git a/frappe/__init__.py b/frappe/__init__.py index f049e8b85c..077e6ae52d 100644 --- a/frappe/__init__.py +++ b/frappe/__init__.py @@ -602,7 +602,7 @@ def set_value(doctype, docname, fieldname, value=None): import frappe.client return frappe.client.set_value(doctype, docname, fieldname, value) -def get_doc(arg1, arg2=None): +def get_doc(*args, **kwargs): """Return a `frappe.model.document.Document` object of the given type and name. :param arg1: DocType name as string **or** document JSON. @@ -619,7 +619,7 @@ def get_doc(arg1, arg2=None): """ import frappe.model.document - return frappe.model.document.get_doc(arg1, arg2) + return frappe.model.document.get_doc(*args, **kwargs) def get_last_doc(doctype): """Get last created document of this type.""" diff --git a/frappe/core/doctype/communication/communication.py b/frappe/core/doctype/communication/communication.py index ac25b97c73..af684fe802 100644 --- a/frappe/core/doctype/communication/communication.py +++ b/frappe/core/doctype/communication/communication.py @@ -25,7 +25,7 @@ class Communication(Document): """create email flag queue""" if self.communication_type == "Communication" and self.communication_medium == "Email" \ and self.sent_or_received == "Received" and self.uid and self.uid != -1: - + email_flag_queue = frappe.db.get_value("Email Flag Queue", { "communication": self.name, "is_completed": 0}) @@ -69,7 +69,7 @@ class Communication(Document): def after_insert(self): if not (self.reference_doctype and self.reference_name): return - + if self.reference_doctype == "Communication" and self.sent_or_received == "Sent": frappe.db.set_value("Communication", self.reference_name, "status", "Replied") @@ -264,7 +264,7 @@ def has_permission(doc, ptype, user): if (doc.reference_doctype == "Communication" and doc.reference_name == doc.name) \ or (doc.timeline_doctype == "Communication" and doc.timeline_name == doc.name): return - + if doc.reference_doctype and doc.reference_name: if frappe.has_permission(doc.reference_doctype, ptype="read", doc=doc.reference_name): return True @@ -277,7 +277,9 @@ def get_permission_query_conditions_for_communication(user): if not user: user = frappe.session.user - if "Super Email User" in frappe.get_roles(user): + roles = frappe.get_roles(user) + + if "Super Email User" in roles or "System Manager" in roles: return None else: accounts = frappe.get_all("User Email", filters={ "parent": user }, diff --git a/frappe/core/doctype/communication/email.py b/frappe/core/doctype/communication/email.py index 04bc906161..3eeb0fc059 100755 --- a/frappe/core/doctype/communication/email.py +++ b/frappe/core/doctype/communication/email.py @@ -166,9 +166,6 @@ def _notify(doc, print_html=None, print_format=None, attachments=None, def update_parent_status(doc): """Update status of parent document based on who is replying.""" - if doc.communication_type != "Communication": - return - parent = doc.get_parent_doc() if not parent: return diff --git a/frappe/desk/doctype/todo/test_todo.js b/frappe/desk/doctype/todo/test_todo.js new file mode 100644 index 0000000000..de508991cf --- /dev/null +++ b/frappe/desk/doctype/todo/test_todo.js @@ -0,0 +1,23 @@ +/* eslint-disable */ +// rename this file from _test_[name] to test_[name] to activate +// and remove above this line + +QUnit.test("test: ToDo", function (assert) { + let done = assert.async(); + + // number of asserts + assert.expect(1); + + frappe.run_serially([ + // insert a new ToDo + () => frappe.tests.make('ToDo', [ + // values to be set + {key: 'value'} + ]), + () => { + assert.equal(cur_frm.doc.key, 'value'); + }, + () => done() + ]); + +}); diff --git a/frappe/desk/doctype/todo/todo.json b/frappe/desk/doctype/todo/todo.json index 9f1b4ae452..ca70fd1006 100644 --- a/frappe/desk/doctype/todo/todo.json +++ b/frappe/desk/doctype/todo/todo.json @@ -112,20 +112,18 @@ "bold": 0, "collapsible": 0, "columns": 0, - "fieldname": "color", - "fieldtype": "Color", + "fieldname": "column_break_2", + "fieldtype": "Column Break", "hidden": 0, "ignore_user_permissions": 0, "ignore_xss_filter": 0, "in_filter": 0, "in_global_search": 0, - "in_list_view": 1, + "in_list_view": 0, "in_standard_filter": 0, - "label": "Color", "length": 0, "no_copy": 0, "permlevel": 0, - "precision": "", "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 0, @@ -142,18 +140,20 @@ "bold": 0, "collapsible": 0, "columns": 0, - "fieldname": "column_break_2", - "fieldtype": "Column Break", + "fieldname": "color", + "fieldtype": "Color", "hidden": 0, "ignore_user_permissions": 0, "ignore_xss_filter": 0, "in_filter": 0, "in_global_search": 0, - "in_list_view": 0, + "in_list_view": 1, "in_standard_filter": 0, + "label": "Color", "length": 0, "no_copy": 0, "permlevel": 0, + "precision": "", "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 0, @@ -544,7 +544,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2017-09-05 12:54:58.044162", + "modified": "2017-09-30 13:57:29.398598", "modified_by": "Administrator", "module": "Desk", "name": "ToDo", diff --git a/frappe/email/doctype/email_account/email_account.py b/frappe/email/doctype/email_account/email_account.py index a36d461c60..7dbb71fd3f 100755 --- a/frappe/email/doctype/email_account/email_account.py +++ b/frappe/email/doctype/email_account/email_account.py @@ -273,7 +273,6 @@ class EmailAccount(Document): "uid_reindexed": uid_reindexed } communication = self.insert_communication(msg, args=args) - #self.notify_update() except SentEmailInInbox: frappe.db.rollback() diff --git a/frappe/model/document.py b/frappe/model/document.py index 8e11b03b9d..56a14cff8c 100644 --- a/frappe/model/document.py +++ b/frappe/model/document.py @@ -20,13 +20,13 @@ from frappe.integrations.doctype.webhook import run_webhooks # once_only validation # methods -def get_doc(arg1, arg2=None): +def get_doc(*args, **kwargs): """returns a frappe.model.Document object. :param arg1: Document dict or DocType name. :param arg2: [optional] document name. - There are two ways to call `get_doc` + There are multiple ways to call `get_doc` # will fetch the latest user object (with child table) from the database user = get_doc("User", "test@example.com") @@ -39,23 +39,39 @@ def get_doc(arg1, arg2=None): {"role": "System Manager"} ] }) + + # create new object with keyword arguments + user = get_doc(doctype='User', email_id='test@example.com') """ - if isinstance(arg1, BaseDocument): - return arg1 - elif isinstance(arg1, string_types): - doctype = arg1 - else: - doctype = arg1.get("doctype") + if args: + if isinstance(args[0], BaseDocument): + # already a document + return args[0] + elif isinstance(args[0], string_types): + doctype = args[0] + + elif isinstance(args[0], dict): + # passed a dict + kwargs = args[0] + + else: + raise ValueError('First non keyword argument must be a string or dict') + + if kwargs: + if 'doctype' in kwargs: + doctype = kwargs['doctype'] + else: + raise ValueError('"doctype" is a required key') controller = get_controller(doctype) if controller: - return controller(arg1, arg2) + return controller(*args, **kwargs) - raise ImportError(arg1) + raise ImportError(doctype) class Document(BaseDocument): """All controllers inherit from `Document`.""" - def __init__(self, arg1, arg2=None): + def __init__(self, *args, **kwargs): """Constructor. :param arg1: DocType name as string or document **dict** @@ -68,29 +84,37 @@ class Document(BaseDocument): self._default_new_docs = {} self.flags = frappe._dict() - if arg1 and isinstance(arg1, string_types): - if not arg2: + if args and args[0] and isinstance(args[0], string_types): + # first arugment is doctype + if len(args)==1: # single - self.doctype = self.name = arg1 + self.doctype = self.name = args[0] else: - self.doctype = arg1 - if isinstance(arg2, dict): + self.doctype = args[0] + if isinstance(args[1], dict): # filter - self.name = frappe.db.get_value(arg1, arg2, "name") + self.name = frappe.db.get_value(args[0], args[1], "name") if self.name is None: - frappe.throw(_("{0} {1} not found").format(_(arg1), arg2), frappe.DoesNotExistError) + frappe.throw(_("{0} {1} not found").format(_(args[0]), args[1]), + frappe.DoesNotExistError) else: - self.name = arg2 + self.name = args[1] self.load_from_db() + return + + if args and args[0] and isinstance(args[0], dict): + # first argument is a dict + kwargs = args[0] - elif isinstance(arg1, dict): - super(Document, self).__init__(arg1) + if kwargs: + # init base document + super(Document, self).__init__(kwargs) self.init_valid_columns() else: # incorrect arguments. let's not proceed. - raise frappe.DataError("Document({0}, {1})".format(arg1, arg2)) + raise ValueError('Illegal arguments') def reload(self): """Reload document from database""" diff --git a/frappe/public/js/frappe/form/controls/text_editor.js b/frappe/public/js/frappe/form/controls/text_editor.js index 5904ae8ec1..cd478f9f48 100644 --- a/frappe/public/js/frappe/form/controls/text_editor.js +++ b/frappe/public/js/frappe/form/controls/text_editor.js @@ -6,6 +6,11 @@ frappe.ui.form.ControlTextEditor = frappe.ui.form.ControlCode.extend({ this.setup_drag_drop(); this.setup_image_dialog(); this.setting_count = 0; + + $(document).on('form-refresh', () => { + // reset last keystroke when a new form is loaded + this.last_keystroke_on = null; + }) }, render_camera_button: (context) => { var ui = $.summernote.ui; @@ -74,7 +79,7 @@ frappe.ui.form.ControlTextEditor = frappe.ui.form.ControlCode.extend({ me.parse_validate_and_set_in_model(value); }, onKeydown: function(e) { - me._last_change_on = new Date(); + me.last_keystroke_on = new Date(); var key = frappe.ui.keys.get_key(e); // prevent 'New DocType (Ctrl + B)' shortcut in editor if(['ctrl+b', 'meta+b'].indexOf(key) !== -1) { @@ -205,20 +210,30 @@ frappe.ui.form.ControlTextEditor = frappe.ui.form.ControlCode.extend({ if(this.setting_count > 2) { // we don't understand how the internal triggers work, - // so if someone is setting the value third time, then quit + // so if someone is setting the value third time in 500ms, + // then quit return; } this.setting_count += 1; - let time_since_last_keystroke = moment() - moment(this._last_change_on); + let time_since_last_keystroke = moment() - moment(this.last_keystroke_on); - if(!this._last_change_on || (time_since_last_keystroke > 3000)) { + if(!this.last_keystroke_on || (time_since_last_keystroke > 3000)) { + // if 3 seconds have passed since the last keystroke and + // we have not set any value in the last 1 second, do this setTimeout(() => this.setting_count = 0, 500); this.editor.summernote('code', value || ''); + this.last_keystroke_on = null; } else { + // user is probably still in the middle of typing + // so lets not mess up the html by re-updating it + // keep checking every second if our 3 second barrier + // has been completed, so that we can refresh the html this._setting_value = setInterval(() => { if(time_since_last_keystroke > 3000) { + // 3 seconds done! lets refresh + // safe to update if(this.last_value !== this.get_input_value()) { // if not already in sync, reset this.editor.summernote('code', this.last_value || ''); @@ -226,6 +241,9 @@ frappe.ui.form.ControlTextEditor = frappe.ui.form.ControlCode.extend({ clearInterval(this._setting_value); this._setting_value = null; this.setting_count = 0; + + // clear timestamp of last keystroke + this.last_keystroke_on = null; } }, 1000); } diff --git a/frappe/public/js/frappe/socketio_client.js b/frappe/public/js/frappe/socketio_client.js index 1512f487e6..c7dd77594f 100644 --- a/frappe/public/js/frappe/socketio_client.js +++ b/frappe/public/js/frappe/socketio_client.js @@ -73,7 +73,7 @@ frappe.socketio = { frappe.socketio.doc_subscribe(frm.doctype, frm.docname); }); - $(document).on("form_refresh", function(e, frm) { + $(document).on("form-refresh", function(e, frm) { if (frm.is_new()) { return; } diff --git a/frappe/public/js/legacy/form.js b/frappe/public/js/legacy/form.js index 83c3ffe96f..413e1e594a 100644 --- a/frappe/public/js/legacy/form.js +++ b/frappe/public/js/legacy/form.js @@ -497,7 +497,7 @@ _f.Frm.prototype.render_form = function(is_a_different_doc) { // trigger global trigger // to use this - $(document).trigger('form_refresh', [this]); + $(document).trigger('form-refresh', [this]); // fields this.refresh_fields();