diff --git a/frappe/config/integrations.py b/frappe/config/integrations.py index 92604b716b..cb69cb2a6d 100644 --- a/frappe/config/integrations.py +++ b/frappe/config/integrations.py @@ -72,6 +72,12 @@ def get_data(): "name": "GSuite Templates", "description": _("Google GSuite Templates to integration with DocTypes"), }, + { + "type": "doctype", + "name": "Webhook", + "description": _("Webhooks calling API requests into web apps"), + } + ] } ] diff --git a/frappe/docs/assets/img/webhook.png b/frappe/docs/assets/img/webhook.png new file mode 100644 index 0000000000..859b5f57bd Binary files /dev/null and b/frappe/docs/assets/img/webhook.png differ diff --git a/frappe/docs/user/en/guides/integration/webhooks.md b/frappe/docs/user/en/guides/integration/webhooks.md new file mode 100644 index 0000000000..ff7df4015f --- /dev/null +++ b/frappe/docs/user/en/guides/integration/webhooks.md @@ -0,0 +1,103 @@ +# Webhooks + +Webhooks are "user-defined HTTP callbacks". You can create webhook which triggers on Doc Event of the selected DocType. When the `doc_events` occurs, the source site makes an HTTP request to the URI configured for the webhook. Users can configure them to cause events on one site to invoke behaviour on another. + +#### Configure Webhook + +To add Webhook go to + +> Integrations > External Documents > Webhook + +Webhook + + + +1. Select the DocType for which hook needs to be triggered e.g. Note +2. Select the DocEvent for which hook needs to be triggered e.g. on_trash +3. Enter a valid request URL. On occurence of DocEvent, POST request with doc's json as data is made to the URL. +4. Optionally you can add headers to the request to be made. Useful for sending api key if required. +5. Optionally you can select fields and set its `key` to be sent as data json + +e.g. Webhook + +- **DocType** : `Quotation` +- **Doc Event** : `on_update` +- **Request URL** : `https://httpbin.org/post` +- **Webhook Data** : + 1. **Fieldname** : `name` and **Key** : `id` + 2. **Fieldname** : `items` and **Key** : `lineItems` + +Note: if no headers or data is present, request will be made without any header or body + +Example response of request sent by frappe server on `Quotation` - `on_update` to https://httpbin.org/post: + +``` +{ + "args": {}, + "data": "{\"lineItems\": [{\"stock_qty\": 1.0, \"base_price_list_rate\": 1.0, \"image\": \"\", \"creation\": \"2017-09-14 13:41:58.373023\", \"base_amount\": 1.0, \"qty\": 1.0, \"margin_rate_or_amount\": 0.0, \"rate\": 1.0, \"owner\": \"Administrator\", \"stock_uom\": \"Unit\", \"base_net_amount\": 1.0, \"page_break\": 0, \"modified_by\": \"Administrator\", \"base_net_rate\": 1.0, \"discount_percentage\": 0.0, \"item_name\": \"I1\", \"amount\": 1.0, \"actual_qty\": 0.0, \"net_rate\": 1.0, \"conversion_factor\": 1.0, \"warehouse\": \"Finished Goods - R\", \"docstatus\": 0, \"prevdoc_docname\": null, \"uom\": \"Unit\", \"description\": \"I1\", \"parent\": \"QTN-00001\", \"brand\": null, \"gst_hsn_code\": null, \"base_rate\": 1.0, \"item_code\": \"I1\", \"projected_qty\": 0.0, \"margin_type\": \"\", \"doctype\": \"Quotation Item\", \"rate_with_margin\": 0.0, \"pricing_rule\": null, \"price_list_rate\": 1.0, \"name\": \"QUOD/00001\", \"idx\": 1, \"item_tax_rate\": \"{}\", \"item_group\": \"Products\", \"modified\": \"2017-09-14 17:09:51.239271\", \"parenttype\": \"Quotation\", \"customer_item_code\": null, \"net_amount\": 1.0, \"prevdoc_doctype\": null, \"parentfield\": \"items\"}], \"id\": \"QTN-00001\"}", + "files": {}, + "form": {}, + "headers": { + "Accept": "*/*", + "Accept-Encoding": "gzip, deflate", + "Connection": "close", + "Content-Length": "1075", + "Host": "httpbin.org", + "User-Agent": "python-requests/2.18.1" + }, + "json": { + "id": "QTN-00001", + "lineItems": [ + { + "actual_qty": 0.0, + "amount": 1.0, + "base_amount": 1.0, + "base_net_amount": 1.0, + "base_net_rate": 1.0, + "base_price_list_rate": 1.0, + "base_rate": 1.0, + "brand": null, + "conversion_factor": 1.0, + "creation": "2017-09-14 13:41:58.373023", + "customer_item_code": null, + "description": "I1", + "discount_percentage": 0.0, + "docstatus": 0, + "doctype": "Quotation Item", + "gst_hsn_code": null, + "idx": 1, + "image": "", + "item_code": "I1", + "item_group": "Products", + "item_name": "I1", + "item_tax_rate": "{}", + "margin_rate_or_amount": 0.0, + "margin_type": "", + "modified": "2017-09-14 17:09:51.239271", + "modified_by": "Administrator", + "name": "QUOD/00001", + "net_amount": 1.0, + "net_rate": 1.0, + "owner": "Administrator", + "page_break": 0, + "parent": "QTN-00001", + "parentfield": "items", + "parenttype": "Quotation", + "prevdoc_docname": null, + "prevdoc_doctype": null, + "price_list_rate": 1.0, + "pricing_rule": null, + "projected_qty": 0.0, + "qty": 1.0, + "rate": 1.0, + "rate_with_margin": 0.0, + "stock_qty": 1.0, + "stock_uom": "Unit", + "uom": "Unit", + "warehouse": "Finished Goods - R" + } + ] + }, + "url": "https://httpbin.org/post" +} +``` \ No newline at end of file diff --git a/frappe/integrations/doctype/webhook/__init__.py b/frappe/integrations/doctype/webhook/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/frappe/integrations/doctype/webhook/test_webhook.js b/frappe/integrations/doctype/webhook/test_webhook.js new file mode 100644 index 0000000000..799b952bed --- /dev/null +++ b/frappe/integrations/doctype/webhook/test_webhook.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: Webhook", function (assert) { + let done = assert.async(); + + // number of asserts + assert.expect(1); + + frappe.run_serially([ + // insert a new Webhook + () => frappe.tests.make('Webhook', [ + // values to be set + {key: 'value'} + ]), + () => { + assert.equal(cur_frm.doc.key, 'value'); + }, + () => done() + ]); + +}); diff --git a/frappe/integrations/doctype/webhook/test_webhook.py b/frappe/integrations/doctype/webhook/test_webhook.py new file mode 100644 index 0000000000..c43d431670 --- /dev/null +++ b/frappe/integrations/doctype/webhook/test_webhook.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2017, Frappe Technologies and Contributors +# See license.txt +from __future__ import unicode_literals + +import frappe +import unittest + +class TestWebhook(unittest.TestCase): + def test_validate_docevents(self): + doc = frappe.new_doc("Webhook") + doc.webhook_doctype = "User" + doc.webhook_docevent = "on_submit" + doc.request_url = "https://httpbin.org/post" + self.assertRaises(frappe.ValidationError, doc.save) + def test_validate_request_url(self): + doc = frappe.new_doc("Webhook") + doc.webhook_doctype = "User" + doc.webhook_docevent = "after_insert" + doc.request_url = "httpbin.org?post" + self.assertRaises(frappe.ValidationError, doc.save) diff --git a/frappe/integrations/doctype/webhook/webhook.js b/frappe/integrations/doctype/webhook/webhook.js new file mode 100644 index 0000000000..3fc78e0d2c --- /dev/null +++ b/frappe/integrations/doctype/webhook/webhook.js @@ -0,0 +1,46 @@ +// Copyright (c) 2017, Frappe Technologies and contributors +// For license information, please see license.txt + +frappe.webhook = { + set_fieldname_select: function(frm) { + var doc = frm.doc; + if (doc.webhook_doctype) { + frappe.model.with_doctype(doc.webhook_doctype, function() { + var fields = $.map(frappe.get_doc("DocType", frm.doc.webhook_doctype).fields, function(d) { + if (frappe.model.no_value_type.indexOf(d.fieldtype) === -1 || + d.fieldtype === 'Table') { + return { label: d.label + ' (' + d.fieldtype + ')', value: d.fieldname }; + } + else if (d.fieldtype === 'Currency' || d.fieldtype === 'Float') { + return { label: d.label, value: d.fieldname }; + } + else { + return null; + } + }); + fields.unshift({"label":"Name (Doc Name)","value":"name"}); + frappe.meta.get_docfield("Webhook Data", "fieldname", frm.doc.name).options = [""].concat(fields); + }); + } + } +}; + +frappe.ui.form.on('Webhook', { + refresh: function(frm) { + frappe.webhook.set_fieldname_select(frm); + }, + webhook_doctype: function(frm) { + frappe.webhook.set_fieldname_select(frm); + } +}); + +frappe.ui.form.on("Webhook Data", { + fieldname: function(frm, doctype, name) { + var doc = frappe.get_doc(doctype, name); + var df = $.map(frappe.get_doc("DocType", frm.doc.webhook_doctype).fields, function(d) { + return doc.fieldname == d.fieldname ? d : null; + })[0]; + doc.key = df != undefined ? df.fieldname : "name"; + frm.refresh_field("webhook_data"); + } +}); diff --git a/frappe/integrations/doctype/webhook/webhook.json b/frappe/integrations/doctype/webhook/webhook.json new file mode 100644 index 0000000000..1a3866085a --- /dev/null +++ b/frappe/integrations/doctype/webhook/webhook.json @@ -0,0 +1,367 @@ +{ + "allow_copy": 0, + "allow_guest_to_view": 0, + "allow_import": 0, + "allow_rename": 0, + "beta": 0, + "creation": "2017-09-08 16:16:13.060641", + "custom": 0, + "docstatus": 0, + "doctype": "DocType", + "document_type": "", + "editable_grid": 1, + "engine": "InnoDB", + "fields": [ + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "depends_on": "", + "fieldname": "sb_doc_events", + "fieldtype": "Section Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Doc Events", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "description": "", + "fieldname": "webhook_doctype", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "DocType", + "length": 0, + "no_copy": 0, + "options": "DocType", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 1, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "cb_doc_events", + "fieldtype": "Column Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "webhook_docevent", + "fieldtype": "Select", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Doc Event", + "length": 0, + "no_copy": 0, + "options": "after_insert\non_update\non_submit\non_cancel\non_trash\non_update_after_submit\non_change", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 1, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "sb_webhook", + "fieldtype": "Section Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Webhook Request", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "request_url", + "fieldtype": "Data", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Request URL", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "sb_webhook_headers", + "fieldtype": "Section Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Webhook Headers", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "webhook_headers", + "fieldtype": "Table", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Headers", + "length": 0, + "no_copy": 0, + "options": "Webhook Header", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "sb_webhook_data", + "fieldtype": "Section Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Webhook Data", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "webhook_data", + "fieldtype": "Table", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Data", + "length": 0, + "no_copy": 0, + "options": "Webhook Data", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + } + ], + "has_web_view": 0, + "hide_heading": 0, + "hide_toolbar": 0, + "idx": 0, + "image_view": 0, + "in_create": 0, + "is_submittable": 0, + "issingle": 0, + "istable": 0, + "max_attachments": 0, + "modified": "2017-09-14 13:16:53.974340", + "modified_by": "Administrator", + "module": "Integrations", + "name": "Webhook", + "name_case": "", + "owner": "Administrator", + "permissions": [ + { + "amend": 0, + "apply_user_permissions": 0, + "cancel": 0, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "if_owner": 0, + "import": 0, + "permlevel": 0, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "set_user_permissions": 0, + "share": 1, + "submit": 0, + "write": 1 + } + ], + "quick_entry": 0, + "read_only": 0, + "read_only_onload": 0, + "show_name_in_global_search": 0, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1, + "track_seen": 0 +} \ No newline at end of file diff --git a/frappe/integrations/doctype/webhook/webhook.py b/frappe/integrations/doctype/webhook/webhook.py new file mode 100644 index 0000000000..26ecc36413 --- /dev/null +++ b/frappe/integrations/doctype/webhook/webhook.py @@ -0,0 +1,102 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2017, Frappe Technologies and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +import frappe +import json, requests +from frappe import _ +from frappe.model.document import Document +from six.moves.urllib.parse import urlparse +from time import sleep + +class Webhook(Document): + def autoname(self): + self.name = self.webhook_doctype + "-" + self.webhook_docevent + def validate(self): + self.validate_docevent() + self.validate_request_url() + self.validate_repeating_fields() + def validate_docevent(self): + if self.webhook_doctype: + is_submittable = frappe.get_value("DocType", self.webhook_doctype, "is_submittable") + if not is_submittable and self.webhook_docevent in ["on_submit", "on_cancel", "on_update_after_submit"]: + frappe.throw(_("DocType must be Submittable for the selected Doc Event")) + def validate_request_url(self): + try: + request_url = urlparse(self.request_url).netloc + if not request_url: + raise frappe.ValidationError + except Exception as e: + frappe.throw(_("Check Request URL"), exc=e) + def validate_repeating_fields(self): + """Error when Same Field is entered multiple times in webhook_data""" + webhook_data = [] + for entry in self.webhook_data: + webhook_data.append(entry.fieldname) + + if len(webhook_data)!= len(set(webhook_data)): + frappe.throw(_("Same Field is entered more than once")) + +def enqueue_webhook(doc, webhook): + webhook = frappe.get_doc("Webhook", webhook.get("name")) + headers = {} + data = {} + if webhook.webhook_headers: + for h in webhook.webhook_headers: + if h.get("key") and h.get("value"): + headers[h.get("key")] = h.get("value") + if webhook.webhook_data: + for w in webhook.webhook_data: + for k, v in doc.as_dict().items(): + if k == w.fieldname: + data[w.key] = v + for i in range(3): + try: + r = requests.post(webhook.request_url, data=json.dumps(data), headers=headers, timeout=5) + r.raise_for_status() + frappe.logger().debug({"webhook_success":r.text}) + break + except Exception as e: + frappe.logger().debug({"webhook_error":e, "try": i+1}) + sleep(3*i + 1) + if i !=2: + continue + else: + raise e + +def run_webhooks(doc, method): + '''Run webhooks for this method''' + if frappe.flags.in_import or frappe.flags.in_patch or frappe.flags.in_install: + return + + if not getattr(frappe.local, 'webhooks_executed', None): + frappe.local.webhooks_executed = [] + + if doc.flags.webhooks == None: + webhooks = frappe.cache().hget('webhooks', doc.doctype) + if webhooks==None: + webhooks = frappe.get_all('Webhook', + fields=["name", "webhook_docevent", "webhook_doctype"]) + frappe.cache().hset('webhooks', doc.doctype, webhooks) + doc.flags.webhooks = webhooks + + if not doc.flags.webhooks: + return + + def _webhook_request(webhook): + if not webhook.name in frappe.local.webhooks_executed: + frappe.enqueue("frappe.integrations.doctype.webhook.webhook.enqueue_webhook", doc=doc, webhook=webhook) + frappe.local.webhooks_executed.append(webhook.name) + + event_list = ["on_update", "after_insert", "on_submit", "on_cancel", "on_trash"] + + if not doc.flags.in_insert: + # value change is not applicable in insert + event_list.append('on_change') + event_list.append('before_update_after_submit') + + for webhook in doc.flags.webhooks: + event = method if method in event_list else None + if event and webhook.webhook_docevent == event and webhook.webhook_doctype == doc.doctype: + _webhook_request(webhook) diff --git a/frappe/integrations/doctype/webhook_data/__init__.py b/frappe/integrations/doctype/webhook_data/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/frappe/integrations/doctype/webhook_data/webhook_data.json b/frappe/integrations/doctype/webhook_data/webhook_data.json new file mode 100644 index 0000000000..96ae7f786a --- /dev/null +++ b/frappe/integrations/doctype/webhook_data/webhook_data.json @@ -0,0 +1,130 @@ +{ + "allow_copy": 0, + "allow_guest_to_view": 0, + "allow_import": 0, + "allow_rename": 0, + "beta": 0, + "creation": "2017-09-14 12:08:50.302810", + "custom": 0, + "docstatus": 0, + "doctype": "DocType", + "document_type": "", + "editable_grid": 1, + "engine": "InnoDB", + "fields": [ + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "fieldname", + "fieldtype": "Select", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Fieldname", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "cb_doc_data", + "fieldtype": "Column Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "key", + "fieldtype": "Data", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Key", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + } + ], + "has_web_view": 0, + "hide_heading": 0, + "hide_toolbar": 0, + "idx": 0, + "image_view": 0, + "in_create": 0, + "is_submittable": 0, + "issingle": 0, + "istable": 1, + "max_attachments": 0, + "modified": "2017-09-14 13:16:58.252176", + "modified_by": "Administrator", + "module": "Integrations", + "name": "Webhook Data", + "name_case": "", + "owner": "Administrator", + "permissions": [], + "quick_entry": 0, + "read_only": 0, + "read_only_onload": 0, + "show_name_in_global_search": 0, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 0, + "track_seen": 0 +} \ No newline at end of file diff --git a/frappe/integrations/doctype/webhook_data/webhook_data.py b/frappe/integrations/doctype/webhook_data/webhook_data.py new file mode 100644 index 0000000000..b7d989410f --- /dev/null +++ b/frappe/integrations/doctype/webhook_data/webhook_data.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2017, Frappe Technologies and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +# import frappe +from frappe.model.document import Document + +class WebhookData(Document): + pass diff --git a/frappe/integrations/doctype/webhook_header/__init__.py b/frappe/integrations/doctype/webhook_header/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/frappe/integrations/doctype/webhook_header/webhook_header.json b/frappe/integrations/doctype/webhook_header/webhook_header.json new file mode 100644 index 0000000000..315d28335f --- /dev/null +++ b/frappe/integrations/doctype/webhook_header/webhook_header.json @@ -0,0 +1,101 @@ +{ + "allow_copy": 0, + "allow_guest_to_view": 0, + "allow_import": 0, + "allow_rename": 0, + "beta": 0, + "creation": "2017-09-08 16:27:39.195379", + "custom": 0, + "docstatus": 0, + "doctype": "DocType", + "document_type": "", + "editable_grid": 1, + "engine": "InnoDB", + "fields": [ + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "key", + "fieldtype": "Data", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Key", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "value", + "fieldtype": "Data", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Value", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + } + ], + "has_web_view": 0, + "hide_heading": 0, + "hide_toolbar": 0, + "idx": 0, + "image_view": 0, + "in_create": 0, + "is_submittable": 0, + "issingle": 0, + "istable": 1, + "max_attachments": 0, + "modified": "2017-09-08 16:28:20.025612", + "modified_by": "Administrator", + "module": "Integrations", + "name": "Webhook Header", + "name_case": "", + "owner": "Administrator", + "permissions": [], + "quick_entry": 1, + "read_only": 0, + "read_only_onload": 0, + "show_name_in_global_search": 0, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1, + "track_seen": 0 +} \ No newline at end of file diff --git a/frappe/integrations/doctype/webhook_header/webhook_header.py b/frappe/integrations/doctype/webhook_header/webhook_header.py new file mode 100644 index 0000000000..11d3ee4085 --- /dev/null +++ b/frappe/integrations/doctype/webhook_header/webhook_header.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2017, Frappe Technologies and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +# import frappe +from frappe.model.document import Document + +class WebhookHeader(Document): + pass diff --git a/frappe/model/document.py b/frappe/model/document.py index 033c807418..5e4bfc01c4 100644 --- a/frappe/model/document.py +++ b/frappe/model/document.py @@ -169,6 +169,7 @@ class Document(BaseDocument): return self.flags.email_alerts_executed = [] + setattr(frappe.local, 'webhooks_executed', []) if ignore_permissions!=None: self.flags.ignore_permissions = ignore_permissions @@ -242,6 +243,7 @@ class Document(BaseDocument): return self.flags.email_alerts_executed = [] + setattr(frappe.local, 'webhooks_executed', []) if ignore_permissions!=None: self.flags.ignore_permissions = ignore_permissions @@ -671,6 +673,8 @@ class Document(BaseDocument): out = Document.hook(fn)(self, *args, **kwargs) self.run_email_alerts(method) + from frappe.integrations.doctype.webhook.webhook import run_webhooks + run_webhooks(self, method) return out