From ff8215331ae42e4f89785595100ddd14656ca1fc Mon Sep 17 00:00:00 2001 From: Revant Nandgaonkar Date: Fri, 8 Sep 2017 17:17:04 +0530 Subject: [PATCH 01/21] [Add] Webhook DocType and Tests --- frappe/config/integrations.py | 6 + .../integrations/doctype/webhook/__init__.py | 0 .../doctype/webhook/test_webhook.js | 23 + .../doctype/webhook/test_webhook.py | 21 + .../integrations/doctype/webhook/webhook.js | 8 + .../integrations/doctype/webhook/webhook.json | 550 ++++++++++++++++++ .../integrations/doctype/webhook/webhook.py | 30 + .../doctype/webhook_header/__init__.py | 0 .../webhook_header/webhook_header.json | 101 ++++ .../doctype/webhook_header/webhook_header.py | 10 + .../doctype/webhook_param/__init__.py | 0 .../doctype/webhook_param/webhook_param.json | 101 ++++ .../doctype/webhook_param/webhook_param.py | 10 + 13 files changed, 860 insertions(+) create mode 100644 frappe/integrations/doctype/webhook/__init__.py create mode 100644 frappe/integrations/doctype/webhook/test_webhook.js create mode 100644 frappe/integrations/doctype/webhook/test_webhook.py create mode 100644 frappe/integrations/doctype/webhook/webhook.js create mode 100644 frappe/integrations/doctype/webhook/webhook.json create mode 100644 frappe/integrations/doctype/webhook/webhook.py create mode 100644 frappe/integrations/doctype/webhook_header/__init__.py create mode 100644 frappe/integrations/doctype/webhook_header/webhook_header.json create mode 100644 frappe/integrations/doctype/webhook_header/webhook_header.py create mode 100644 frappe/integrations/doctype/webhook_param/__init__.py create mode 100644 frappe/integrations/doctype/webhook_param/webhook_param.json create mode 100644 frappe/integrations/doctype/webhook_param/webhook_param.py 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/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..7f5e071c07 --- /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_mandatory_fields(self): + base_doc = frappe.new_doc("Webhook") + + # Test for scheduler event webhook + scheduler_event_doc = base_doc + scheduler_event_doc.webhook_type = "Scheduler Event" + self.assertRaises(frappe.ValidationError, scheduler_event_doc.save) + + # Test for doc event webhook + doc_event_doc = base_doc + doc_event_doc.webhook_type = "Doc Event" + self.assertRaises(frappe.ValidationError, doc_event_doc.save) diff --git a/frappe/integrations/doctype/webhook/webhook.js b/frappe/integrations/doctype/webhook/webhook.js new file mode 100644 index 0000000000..84538f8ea1 --- /dev/null +++ b/frappe/integrations/doctype/webhook/webhook.js @@ -0,0 +1,8 @@ +// Copyright (c) 2017, Frappe Technologies and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Webhook', { + refresh: function(frm) { + + } +}); diff --git a/frappe/integrations/doctype/webhook/webhook.json b/frappe/integrations/doctype/webhook/webhook.json new file mode 100644 index 0000000000..627abdcaef --- /dev/null +++ b/frappe/integrations/doctype/webhook/webhook.json @@ -0,0 +1,550 @@ +{ + "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, + "fieldname": "sb_webhook_type", + "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 Type", + "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_type", + "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": "Webhook Type", + "length": 0, + "no_copy": 0, + "options": "Scheduler Event\nDoc Event", + "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, + "depends_on": "eval:doc.webhook_type==\"Doc Event\"", + "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": 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": "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": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "depends_on": "eval:doc.webhook_type==\"Scheduler Event\"", + "fieldname": "sb_scheduler_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": "Scheduler 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, + "fieldname": "scheduler_event", + "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": "Scheduler Event", + "length": 0, + "no_copy": 0, + "options": "\nall\ndaily\nhourly\nweekly\nmonthly", + "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", + "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": "cb_webhook", + "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": "request_verb", + "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": "Request Verb", + "length": 0, + "no_copy": 0, + "options": "GET\nPOST\nPUT", + "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_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_params", + "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 Params", + "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_params", + "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": "Params", + "length": 0, + "no_copy": 0, + "options": "Webhook Param", + "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-08 16:44:44.857115", + "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..afd437b26e --- /dev/null +++ b/frappe/integrations/doctype/webhook/webhook.py @@ -0,0 +1,30 @@ +# -*- 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 import _ +from frappe.model.document import Document + +class Webhook(Document): + def autoname(self): + if self.webhook_type == "Scheduler Event" and self.scheduler_event: + self.name = self.webhook_type + "-" + self.scheduler_event + if self.webhook_type == "Doc Event" and self.webhook_doctype: + self.name = self.webhook_type + "-" + self.webhook_doctype + "-" + self.webhook_docevent + def validate(self): + self.validate_mandatory_fields() + self.validate_docevent() + def validate_mandatory_fields(self): + if self.webhook_type == "Scheduler Event": + if not self.scheduler_event: + frappe.throw(_("Select Scheduler Event")) + if self.webhook_type == "Doc Event": + if not self.webhook_doctype: + frappe.throw(_("Select DocType")) + def validate_docevent(self): + if self.doctype: + is_submittable = frappe.get_value("DocType", self.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")) 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..535b626148 --- /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/integrations/doctype/webhook_param/__init__.py b/frappe/integrations/doctype/webhook_param/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/frappe/integrations/doctype/webhook_param/webhook_param.json b/frappe/integrations/doctype/webhook_param/webhook_param.json new file mode 100644 index 0000000000..93bd7a2dd7 --- /dev/null +++ b/frappe/integrations/doctype/webhook_param/webhook_param.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:28:38.852947", + "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:38.852947", + "modified_by": "Administrator", + "module": "Integrations", + "name": "Webhook Param", + "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_param/webhook_param.py b/frappe/integrations/doctype/webhook_param/webhook_param.py new file mode 100644 index 0000000000..badb5eafd5 --- /dev/null +++ b/frappe/integrations/doctype/webhook_param/webhook_param.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 WebhookParam(Document): + pass From 69e0d4c930d011c474ea8edf113c81eb98b1fccf Mon Sep 17 00:00:00 2001 From: Revant Nandgaonkar Date: Sat, 9 Sep 2017 12:26:47 +0530 Subject: [PATCH 02/21] Changes to Webhook DocType and Tests --- frappe/hooks.py | 17 +- .../doctype/webhook/test_webhook.py | 18 +- .../integrations/doctype/webhook/webhook.json | 248 +----------------- .../integrations/doctype/webhook/webhook.py | 17 +- 4 files changed, 25 insertions(+), 275 deletions(-) diff --git a/frappe/hooks.py b/frappe/hooks.py index f5d98f0421..2eec7997d6 100755 --- a/frappe/hooks.py +++ b/frappe/hooks.py @@ -108,14 +108,25 @@ doc_events = { "*": { "on_update": [ "frappe.desk.notifications.clear_doctype_notifications", - "frappe.core.doctype.communication.feed.update_feed" + "frappe.core.doctype.communication.feed.update_feed", + "frappe.integrations.webhooks.on_update_webhook" ], "after_rename": "frappe.desk.notifications.clear_doctype_notifications", "on_cancel": [ "frappe.desk.notifications.clear_doctype_notifications", + "frappe.integrations.webhooks.on_cancel_webhook" ], - "on_trash": "frappe.desk.notifications.clear_doctype_notifications", - "on_change": "frappe.core.doctype.feedback_trigger.feedback_trigger.trigger_feedback_request" + "on_trash": [ + "frappe.desk.notifications.clear_doctype_notifications", + "frappe.integrations.webhooks.on_trash_webhook" + ], + "on_change": [ + "frappe.core.doctype.feedback_trigger.feedback_trigger.trigger_feedback_request", + "frappe.integrations.webhooks.on_change_webhook" + ], + "after_insert": "frappe.integrations.webhooks.after_insert_webhook", + "on_submit": "frappe.integrations.webhooks.on_submit_webhook", + "on_update_after_submit": "frappe.integrations.webhooks.on_update_after_submit_webhook" }, "Email Group Member": { "validate": "frappe.email.doctype.email_group.email_group.restrict_email_group" diff --git a/frappe/integrations/doctype/webhook/test_webhook.py b/frappe/integrations/doctype/webhook/test_webhook.py index 7f5e071c07..908e9b2d0e 100644 --- a/frappe/integrations/doctype/webhook/test_webhook.py +++ b/frappe/integrations/doctype/webhook/test_webhook.py @@ -7,15 +7,9 @@ import frappe import unittest class TestWebhook(unittest.TestCase): - def test_mandatory_fields(self): - base_doc = frappe.new_doc("Webhook") - - # Test for scheduler event webhook - scheduler_event_doc = base_doc - scheduler_event_doc.webhook_type = "Scheduler Event" - self.assertRaises(frappe.ValidationError, scheduler_event_doc.save) - - # Test for doc event webhook - doc_event_doc = base_doc - doc_event_doc.webhook_type = "Doc Event" - self.assertRaises(frappe.ValidationError, doc_event_doc.save) + 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) diff --git a/frappe/integrations/doctype/webhook/webhook.json b/frappe/integrations/doctype/webhook/webhook.json index 627abdcaef..ea4c868816 100644 --- a/frappe/integrations/doctype/webhook/webhook.json +++ b/frappe/integrations/doctype/webhook/webhook.json @@ -18,68 +18,7 @@ "bold": 0, "collapsible": 0, "columns": 0, - "fieldname": "sb_webhook_type", - "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 Type", - "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_type", - "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": "Webhook Type", - "length": 0, - "no_copy": 0, - "options": "Scheduler Event\nDoc Event", - "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, - "depends_on": "eval:doc.webhook_type==\"Doc Event\"", + "depends_on": "", "fieldname": "sb_doc_events", "fieldtype": "Section Break", "hidden": 0, @@ -196,68 +135,6 @@ "set_only_once": 0, "unique": 0 }, - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "eval:doc.webhook_type==\"Scheduler Event\"", - "fieldname": "sb_scheduler_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": "Scheduler 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, - "fieldname": "scheduler_event", - "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": "Scheduler Event", - "length": 0, - "no_copy": 0, - "options": "\nall\ndaily\nhourly\nweekly\nmonthly", - "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, @@ -318,66 +195,6 @@ "set_only_once": 0, "unique": 0 }, - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "cb_webhook", - "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": "request_verb", - "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": "Request Verb", - "length": 0, - "no_copy": 0, - "options": "GET\nPOST\nPUT", - "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, @@ -438,67 +255,6 @@ "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_params", - "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 Params", - "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_params", - "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": "Params", - "length": 0, - "no_copy": 0, - "options": "Webhook Param", - "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, @@ -511,7 +267,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2017-09-08 16:44:44.857115", + "modified": "2017-09-09 12:10:09.682143", "modified_by": "Administrator", "module": "Integrations", "name": "Webhook", diff --git a/frappe/integrations/doctype/webhook/webhook.py b/frappe/integrations/doctype/webhook/webhook.py index afd437b26e..a8ba59b559 100644 --- a/frappe/integrations/doctype/webhook/webhook.py +++ b/frappe/integrations/doctype/webhook/webhook.py @@ -9,22 +9,11 @@ from frappe.model.document import Document class Webhook(Document): def autoname(self): - if self.webhook_type == "Scheduler Event" and self.scheduler_event: - self.name = self.webhook_type + "-" + self.scheduler_event - if self.webhook_type == "Doc Event" and self.webhook_doctype: - self.name = self.webhook_type + "-" + self.webhook_doctype + "-" + self.webhook_docevent + self.name = self.webhook_doctype + "-" + self.webhook_docevent def validate(self): - self.validate_mandatory_fields() self.validate_docevent() - def validate_mandatory_fields(self): - if self.webhook_type == "Scheduler Event": - if not self.scheduler_event: - frappe.throw(_("Select Scheduler Event")) - if self.webhook_type == "Doc Event": - if not self.webhook_doctype: - frappe.throw(_("Select DocType")) def validate_docevent(self): - if self.doctype: - is_submittable = frappe.get_value("DocType", self.doctype, "is_submittable") + 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")) From d6bfa7431bf454c10c7bb32f9f11501d3f535c3e Mon Sep 17 00:00:00 2001 From: Revant Nandgaonkar Date: Sat, 9 Sep 2017 14:10:09 +0530 Subject: [PATCH 03/21] Webhooks for frappe doc_events --- frappe/hooks.py | 14 ++++++------ .../doctype/webhook/test_webhook.py | 6 +++++ .../integrations/doctype/webhook/webhook.json | 6 ++--- .../integrations/doctype/webhook/webhook.py | 9 ++++++++ frappe/integrations/webhooks.py | 22 +++++++++++++++++++ 5 files changed, 47 insertions(+), 10 deletions(-) create mode 100644 frappe/integrations/webhooks.py diff --git a/frappe/hooks.py b/frappe/hooks.py index 2eec7997d6..9fdca1a87a 100755 --- a/frappe/hooks.py +++ b/frappe/hooks.py @@ -109,24 +109,24 @@ doc_events = { "on_update": [ "frappe.desk.notifications.clear_doctype_notifications", "frappe.core.doctype.communication.feed.update_feed", - "frappe.integrations.webhooks.on_update_webhook" + "frappe.integrations.webhooks.doc_event_webhook" ], "after_rename": "frappe.desk.notifications.clear_doctype_notifications", "on_cancel": [ "frappe.desk.notifications.clear_doctype_notifications", - "frappe.integrations.webhooks.on_cancel_webhook" + "frappe.integrations.webhooks.doc_event_webhook" ], "on_trash": [ "frappe.desk.notifications.clear_doctype_notifications", - "frappe.integrations.webhooks.on_trash_webhook" + "frappe.integrations.webhooks.doc_event_webhook" ], "on_change": [ "frappe.core.doctype.feedback_trigger.feedback_trigger.trigger_feedback_request", - "frappe.integrations.webhooks.on_change_webhook" + "frappe.integrations.webhooks.doc_event_webhook" ], - "after_insert": "frappe.integrations.webhooks.after_insert_webhook", - "on_submit": "frappe.integrations.webhooks.on_submit_webhook", - "on_update_after_submit": "frappe.integrations.webhooks.on_update_after_submit_webhook" + "after_insert": "frappe.integrations.webhooks.doc_event_webhook", + "on_submit": "frappe.integrations.webhooks.doc_event_webhook", + "on_update_after_submit": "frappe.integrations.webhooks.doc_event_webhook" }, "Email Group Member": { "validate": "frappe.email.doctype.email_group.email_group.restrict_email_group" diff --git a/frappe/integrations/doctype/webhook/test_webhook.py b/frappe/integrations/doctype/webhook/test_webhook.py index 908e9b2d0e..c43d431670 100644 --- a/frappe/integrations/doctype/webhook/test_webhook.py +++ b/frappe/integrations/doctype/webhook/test_webhook.py @@ -13,3 +13,9 @@ class TestWebhook(unittest.TestCase): 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.json b/frappe/integrations/doctype/webhook/webhook.json index ea4c868816..4f3ba1c180 100644 --- a/frappe/integrations/doctype/webhook/webhook.json +++ b/frappe/integrations/doctype/webhook/webhook.json @@ -72,7 +72,7 @@ "report_hide": 0, "reqd": 0, "search_index": 0, - "set_only_once": 0, + "set_only_once": 1, "unique": 0 }, { @@ -132,7 +132,7 @@ "report_hide": 0, "reqd": 0, "search_index": 0, - "set_only_once": 0, + "set_only_once": 1, "unique": 0 }, { @@ -267,7 +267,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2017-09-09 12:10:09.682143", + "modified": "2017-09-09 13:07:14.169116", "modified_by": "Administrator", "module": "Integrations", "name": "Webhook", diff --git a/frappe/integrations/doctype/webhook/webhook.py b/frappe/integrations/doctype/webhook/webhook.py index a8ba59b559..f5c85c387b 100644 --- a/frappe/integrations/doctype/webhook/webhook.py +++ b/frappe/integrations/doctype/webhook/webhook.py @@ -6,14 +6,23 @@ from __future__ import unicode_literals import frappe from frappe import _ from frappe.model.document import Document +from six.moves.urllib.parse import urlparse class Webhook(Document): def autoname(self): self.name = self.webhook_doctype + "-" + self.webhook_docevent def validate(self): self.validate_docevent() + self.validate_request_url() 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 + except: + frappe.throw(_("Check Request URL")) diff --git a/frappe/integrations/webhooks.py b/frappe/integrations/webhooks.py new file mode 100644 index 0000000000..4c0e97664c --- /dev/null +++ b/frappe/integrations/webhooks.py @@ -0,0 +1,22 @@ +# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors +# MIT License. See license.txt + +from __future__ import unicode_literals +import frappe, requests +from frappe import _ + +# Doc Events Webhook +def doc_event_webhook(doc, method=None, *args, **kwargs): + headers = {} + webhook = frappe.get_doc("Webhook", doc.get("doctype") + "-" + method) + if webhook: + 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") + try: + r = requests.post(webhook.request_url, data=doc.as_json(), headers=headers, timeout=5) + frappe.logger().debug({"webhook_success":r.text, "webhook": webhook.as_json()}) + except: + frappe.logger().debug({"webhook_error":r.text, "webhook": webhook.as_json()}) + frappe.throw(_("Unable to make request")) From 0b1a655da1210aac4bd34676ad7bd160499eef6e Mon Sep 17 00:00:00 2001 From: Revant Nandgaonkar Date: Sat, 9 Sep 2017 19:26:50 +0530 Subject: [PATCH 04/21] [Fix] filter only existing webhooks --- frappe/integrations/webhooks.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/frappe/integrations/webhooks.py b/frappe/integrations/webhooks.py index 4c0e97664c..1312d63712 100644 --- a/frappe/integrations/webhooks.py +++ b/frappe/integrations/webhooks.py @@ -8,7 +8,12 @@ from frappe import _ # Doc Events Webhook def doc_event_webhook(doc, method=None, *args, **kwargs): headers = {} - webhook = frappe.get_doc("Webhook", doc.get("doctype") + "-" + method) + filters = { + "webhook_doctype": doc.get("doctype"), + "webhook_docevent": method + } + webhooks = frappe.get_all("Webhook", filters=filters) + webhook = frappe.get_doc("Webhook", webhooks[0].get("name")) if webhooks and len(webhooks) > 0 else None if webhook: if webhook.webhook_headers: for h in webhook.webhook_headers: From f8a1d55689f317fccb612e6dfdea85ee220ac603 Mon Sep 17 00:00:00 2001 From: Revant Nandgaonkar Date: Sun, 10 Sep 2017 10:58:33 +0530 Subject: [PATCH 05/21] Fix and Documentation webhook_doctype field made Mandatory in Webhook Documentation and screenshot added --- frappe/docs/assets/img/webhook.png | Bin 0 -> 34615 bytes .../user/en/guides/integration/webhooks.md | 18 ++++++++++++++++++ .../integrations/doctype/webhook/webhook.json | 4 ++-- 3 files changed, 20 insertions(+), 2 deletions(-) create mode 100644 frappe/docs/assets/img/webhook.png create mode 100644 frappe/docs/user/en/guides/integration/webhooks.md diff --git a/frappe/docs/assets/img/webhook.png b/frappe/docs/assets/img/webhook.png new file mode 100644 index 0000000000000000000000000000000000000000..cbc16c1d76469689b62c77cee8db22175f5b0ca1 GIT binary patch literal 34615 zcmdqJWmH_v_BV(G37+5%A-KD{1&81s+}#~Qf(CbYcXyY@t#S9p-K80F-}~JEedp86 ze3&(B&gymQRMjrqyLO$utA3|L73C$75%3ToARv&XzKMN@fPhhifPl(@hkZ|J#QyF1 z9(-^Tky3$wzr5j%L*C=K&f*%*%66vCZU&Af5N5V^HYW5=Mvf*Xwoc}D&galw0uT@+ z5K>~oD(>kgYhK>*w;A_u>ih9y)T}84!=b-^eTadFO@I#>=*_*YR<*ENtE_iB25YUg zSrWH3v}t>}mHC`3w_2?{&!6e8c&Q(I3e=MzV1_%gU_?y)U?LS7GH^^6WdT{CJiao= zp~a?r_?nC+I0=A8cG6wAf6`m{)l%u9zag5+n2kjC3rz%`^xro_s5x16q5nkRGfZhe zD*pa2iZ3w#=lJ+PCA@i%Jll~x`KY#rWYxvtN#*l^metAQ8e@eim0@TiA(~Wj6fvLw z)fi|c8xWpLwF@$isRT|qyZl1iW+Nq+J@-e{ZAA%}5>a{YP*EIK@xO?-+wpJQsc31U zFI3=}X$y|)--}Jp&#}@%&3^kj53(45i$prSG}2tN`o`YmdILA3miGHgF4Hu}9#h0K5V?tP5?bX&KNLu|N( z9jmf&kX0Lkr+Wy@3(Y|j8-YQP3!5Ia+UfeAvl0Gjv`)->D9*{wgGaAbo|86+3eB+b z`Z*9(aiXZmZD$gGEd%wJfB)j$`)#7`5bv{)_tjS+Oje4M>+PrsXL>Oj=7C8?MS<0S zM4SJaJbwt=i}_y4pT;z(Dk&<`ZN|AgiS)5N!^co>S!>0@)~0CXo*GptPnZw#(Dc3j zEQA^=>0M>twKm0pFTa%6l?UM!fX{g0zb>+>vPtlSZNrO1(Skc7@&GFb&KX^{&4zTG z#OA6QL}`eDEGZX^~dukB4c}a z<^>DWR;X>G?~yf+EvVIIYIT>_?7I+DHr768{5&kQOAn7GmkXw}9SPn$Xp|icT9Lx4 z;sPm0DV4KGID8}f3uzbXmBu@JEp;H`{1G8NHP|ySNd_fPPJ6P)V2bbh_GaM1{e@ok zuQb~Va=+E@Uazi=4W-RD*T*fhY;tV`*1H26oIBKQvApvb>|`exXAG9MA?(Cw)5Iw2!(OH%YRBYbBZ4f_pgD7?I0pgWuk z_iCy9u3{(jK1eoY2=DM=8Cw*!oG65o*myNwy+ooOhUFVgOrc#oP3@~y9|d*o79Jh8 z3a88EHn;K|D&1ft?#Jvdyb(M;Tp!z2+X;!Uo8UK_)G(Xy^94*T2||QCIiLn9&7QeW z_Dda|yLrI(zcj-I3WZ%7z(v)%fF#`!j+!eN?yap@eN1!|gC2g25-*{U297kW?!q@&#b(z5zO}~!fTx-M0NB;$UW0gHVkJ{0|1bn#`}b1wqJj-S zdD7CZ!zjMnOmy7=OE94HXMdT}mtu4P{$BsYMqP)f+<=)amy0b$$kts@D1|P;0P#gm z|CpI>my~!+B8dn*Gg+>C#_OW4u!xdkNkek2Q;J;%SV~nus-}l166r`&!&2(@R^MBv zuLO(g2K9qf6DCD3kSXw0{wWCRLtbBgNdpdR9rd_e-n|&{bi1MS#I@*-`49N#ZW7TR zcxDxI=R;%AevA?8)W)gdRBW&~%U#`W!QsHh#jz=E)?=^k%Vf9{6U47Iw$qIX#IDz_ zoAno$GacKE!!Y@(DYOYiywZ9Id4&(*E~#B|H^c=F5F`@%qe`$?3QlB%x*v}P?79OI zUZ^|a_Gk6R2V+V0SI1^)sfkuaNx#69sHe<0$CC?i5h*p2lE$T8iK}ar+VE98_#(3CkElS_S9EG-lCc1JkhT z*a!8c(0WGjkw%VGDnv_ZjPG@6Z5rRPBH3XZ_hvH(!l^!(Q-86KIqY#2`gfD!7lD?%0N
`|Y>FGiHC3u8>X)CS6tHA23Bm>secq zE7F&gw-q4AwKUv90~aH(_aP|&(()c~6rFdIW1GH2z+@1d-}rNf&#WTVi+zN_3}UBI z;X&l6AS|K`GkHq;JYfOeKYh6mR-~q@3m8bcl%c|M~)1QC6}D%I|Z-u2y8wKLDRTWH6_V&g+ho zho-`5t#1ix`M=0KE(byJ9Bu4$xN>Ixz+t)4blQ==33jWlo0B9V*ywDvg((YTk%$7{ zu+S)?Hr9CAkySvG&Pw1XG$hs3@}VOOXi>)dVa@^9zWD@+rvnysH^x4iq1~WQE`(g3 zcPyz5nYc8lO!l06aFnHJ&`BIi@gDNfBaVg*H5zYU-%mQv^?NvOZG`$Rd+;tkdiv!y ziK5*vnoI;*`#f{mCIqyI9cr5U-%}Dw*vm8(vLK-$Aapq7bU%c&chR)vTO@jx_3R z8=m>mB|15wlL; zn|~$|T&2*jVl7EEJtA=mWVFHQB{>BWX(aZiG3$L0{`6;q>ZT~$C=o7LJSUX7Ygsgc z+`5HvMJfbK{%dK(Z*aNmkdXutpQV65#_E+~&5T7bu#l$~PO}BkOf?F~Sb2jDt$0c? zp7Ffq2vB$|Y#pF@t`J%6-GYa86@?PH>G%ta3a$Mv^CFZUXA;22Gt%HtB1K;Xt z?iQq&%N(St#lm`<5A*8&zFM0_^D;*9bs#{=N8Y=C6x)8DWWr@0{E760nQNgn#@!OH z0aSv7EXv>r7BS{Ru2`3K$U%Z6c&(Yin9Q1yC{Fw6(o z!^_ek>ft;RvdhW?=gY*f4*^tXm2u6G3CvO9`y~g*j++QUp-5#+dzdV$Zv`6NqFoz* z#`EP^mc-(FAW_5CB~$}EZaRjBa2`+RV@npM&XLjTl8tM_Sh048z-l+6fZs(%#tduS zPyA6epy)KrGSD~L*<#`()Uihj*=2*9z`*7g%>!f$pmL{SQ3?MC?r;s&<31a^Juw+n zb<(hihtNPxxwrXAa5QLQhek^pZuIfY&UKY{(jR|Fm%hcXoO{O)zUX z-V^|o0-o__Z*-^rG~QF-%NVb)Ia$5en^D$PWzMov(*-kc6d!JHZB7WzAPL2HZzhXR z6Sya-IQ)V(rjpjFXa?{B01FG>;MLhXS0qg zK?t*P1nLKh&~T^1V~+zEM@GN?-~tzOZwW+IG9p9}otyc%+N0+-q9hAM2(OLKaiKEM z-Ed}MT8z>2T^5M$mhv}%v ze)q(NYdRP<5u1W?bihYfCsLDT9<^}Dd2pW!&%4`Bh(io3w1#>9Iyc+x-|=sZZ%}8J z@=Wv^KgPN4rlR-E0fP}~uSfrE5^fqtK`VO*U^S$UwR?|QBL9NrUfRnBSm*bnk)f$f zE$%DDlzibS7r{iFGI9IFVydi^EU)!7NE-j`EF;k%hi>DWmV1bNTOd)MuTIh#)mf(R zQ?Ok81ZSQq?BT&-o{W0DxBgBfJ$;|VoH;fa!oY=CGyq4F0<-3ae>P8s4xyM1!WI&K z!Xqx)6>tqWIn0KP=mh-O`gC|>Nkzvnh=ZTICLk156Y`~Rv7>;8*hnoD=0M(+Naxkr zQw0M9(?A|^2FNcIEIQh|>ik7I**qws)SF)^E3Q#bFsNMz3s3d(Quvu)Lx$cw*8r-| zQ+l-aThznBiQ_i=o|0N2bXH&^B(~^2RJr9hPBZ1q5@B9~{`IA(HH%ytbxE<0QQUAu zJZ+NeQ8`W4)oXr+$Vsd^MYR7eFQz{CC&bA~O)Ejcro)d<CiLja z6MYD@cz+(ALuO4c6z5Lj4mA|!6J{bu+(UQvq^Ge9K`XSdmf~7yIXnv9-ivc#X!@>Z zzCC^h`NQ@@@uw=dt`VUUQe-BBVS6C&0i`9AJV*QiE}~1r^e43#b)}FctU|iFgg{+N z3+1r)d2ViVA4rqMp4Ap+nGvYVc%E;ux~R^LU9tJ}oi|P29ktB_(u$Mri(=RBae5+8 zxBv_zt9?4np4$YXyUzAFiK4i5s=S{45GwzPZw3Q~SlBoUh#bx^;hKUxI@-DH*`5BX z$Ae>eieBuTkYa&s=iV6^uBgxf%Fnpk61)8ZT7f?*wT1^F!COJ6`kS@6C&e?e*#5YW6+_ zc+cw#OWtGhPU`VjSD{@gb%Gl5@)c?^iHQN8gg1eboheHD`P6qa=qriyYkj1s;pC~GO9hzkfht&d0> zXC)HJO8s#V+py5z7A5Aj_;^K>3PtZ4e$;Op{b*+nySJ zD*$faL%zEuDw@+4zBL9UG;2<4h8364lbS7Oos}@{Ei$#8G?V#05eQ#}BbzO0o%07#9qLLzY$`Tlq=q z{5#3PNB4}5z*ZQZbRQ=p;mQz`Kay0wv1JO64r5=Nxd6ru5XAGMRqzWFrX9a9MRxN7~ z4!n3b*M2YF?ao!%5KYa1HtwM{1`h8b0n%qybBYry$3u-jG(3Eeh7@F^Sx}z%u-&M| zvz9DuT2mTS<9AN|d*sn&y{!4snF%s#L9o@aaa%c)*sy(zv?`_74o6@btZ%MFy*J3e zzcpdTR4hPgg;@&-IJ#egC`OdarY+cha2Vjh=ggR71Ak7$3cInj zhR$YJ5)e$A9`odqFxC)ny}RM18sPlJI4+bZ94K8OK^XoyG=NQ*Um5Qv6+VEv5aM3) zi#fZ~-m%>;QU&-3i~P~fFxl|Tbv?$+kQgUo1om$$HNLsUN<4TTb=Xi!n7_Qt&%PJa zq3)GG`1j>#gy92_Rvr`80BZniv;O{!{rjSrbmRlsABZ9U;e4zMG*5-tqs@`Ui8+m-W zGh2WC;47a`8J>p5snM$6MA%Nbh}gW@8PWeP>M{wLs(p9;-HOa4sY><_Et_1g-g2bd zPNYAD6Orm~GrY{fy*;x$+nSvN^DoP&k4cRA%O*u7a}zx%#7aoJAUx7dQix5SF_}q1 z0aYOqS?8DW&U({ zOO8$YD;6mMA;r{&)K0R&eEqTmkOUy2oEfzXA{F6E4N~(unh>v;%Z?oA8v>r0snDoc z^oa$dTj-}Euqzo#nHtM07cukk)e4DgEi=^^gec%}2sP1uqk^@t=%Ze5WlfYnK zW<8+B#YYb{I=T&{PvsKjR>(WIMf4B`P-q=-9Xd_Z;CWw%t!GDn@K&m;5ja8q;Y$%k*S;l(Dv!CX zdz3CfYsoGD&rIw?S6Oi`nn7%JUQuWVy*!mpZxFfKYVci6mz(icKg-P%(yTv#{o5}c z9Oq3)=g=TYGo!CeCr77QbC6paJRMl<*|eG?%i;1qfLTozi7c7{!3Bz|W^ey6XWb*z zlO;krN=`l`X)f7T`Ecn3iB@?TEiJqN#Vdfdp=~sT3(9C2O^;`JLSqN7`(6m%y+3jN zZ#ss#nB7l42x_G=^lXGg`6Xa96m3{4gx0VG9bUP(MM-a;`gHs4@De8M`0oixFX4Rv zR&X!x#rhg8`|H9DZxUYP8dP<|X?3?if+FJJ{hibC@Jd8n)9{|Hgin2t1OY z-25!ksXu}Y${(!+vnFdRO!bwq-EnQeX40kgx23{!vt3f(K zEhA`Jt@o!C5mzu=kpS`VWFTyXXR;92$bpYCSEpSCLad)S5x)>c8aeG@V~_C31XYUo z0NrfSr-M2t?-2>P(0d_L7il8>PZgFpesn|{rpEbs4iEOUY6!9UL>3mreqX2)v;yUk znN{&<5)dLdhH;8Zon;UEZakpGBh_M*};+ zb`nYbl@Z^>%hS>p2UKi=t2}c#3{Z;vvk0;C-OTZ2js`vGW@=+&PRb6c9J^w#aTI;r zRKj!N)_JNhUP;#==MtLoS>G=XB@NHbe;4{w7HWWz^~&_eMCaK6W@HmOwQ`yRbGskc zkvrx$;D+-d5XnsG&!iPtSwS`QAGWHXF+IQK|37tQ83%RI5rUKhAGGmTL zYNw|zM56UnBtjdL8Y3lK;c9D2^B(dz)O;AQnSLk+w3WpWR$$G4Z9L?!53aCLf<3v+ zA)*PVc-w163p6QO5f!eC_Y_$U zyPzwvr>VE-XsKRnASGbxy?FY1<7t#f&pJ8xh^!FzO$ROE4wmD5aeTkShDzKd-kfx5 zdgv^OLV#;_Xg!DlqXW;Hy}Y9wjaGnX+0!9o8oB_7$PAV6&X>D)MI0d0(LcM27>TaI%YJH312$gMmG&yZe2-VB& zWPe}*|1i8{s4{udm`qR3Men*)y83gZpt-vboOaZMur`|CRCMJN{hJ-*^N#w)i&AfyS$tc>u-1%VHuaBM?HxDyI^|C&wc{7%Z!O+dn2JxFi z0xrgyz)RjBcoQ7vC?5$t1STA$ybm3Vgu?X{WU$W0sSO{}Q3!m0+kcg1Li=K}&o%t` z&6lWNgBAFZJA(_#AU->o!3-^9DZbrg!x<;?jS?@7&5vLFZ@YbS=RcTmCXkvWsJ;0V`j^@3cB=y{?KRe(z9!(l8`8&qNG^( zKqwyT$2RJtx`b8B6mc@_X|kMcBF%pTZkUW}TUidNiszkW%(;o-FyK6CG)U zl8Zw{hYtiPX2^&KU>UyFH_{r<{qdH(nKE$OR>vQjXz*b%x~CDEm6Oz_1U&U=AeL#? z^&_-?R-4C+t3Uyoodei$WCc!idJN}P_6*@~fX?{|994KuiR|FVv~naDNcD=g_#-;Z zOh`)KhKQpC+LqEEXXA8P@s+<^@EHntC7V^>z zcBl>By2VF$2uHNEC#H>9QQF#-Z8h>*O1x=x*zO$Qq$OKEmhleS8s*pD(MZQ(I57xZ zUSNF2?k933sD2-gWLBKax26ymd<@qGPN&`_1vsrS4(;5Hj?|X{+7=sTq4+!9s!Pwb zup5kpk#uqy&3!&G$UE#YG=u-yCVoukc*q%wee|$OAkRQ zg1&2J?`cTJzr@Bkh)3wO-_gW(z&$!8p+83>=q z9qe__uzey^GAy1dJWf$h{tby4^ixNKU!F&yH6&ZaYRFJH220lz%R>O})aJB52&h=X zZd~}3t%grc^Ji)fw7VEeRqVWo5I)40w_H+#wVUJ2%jGdMP%SNKD!<djbaoLxj2*ZfbuFZ?JcgrZ{aeYdDLwxDQ)qhpjipo)Dco&+h69 zUO!;6u%w4o#!5*!)Tm>1aJhGKhOZyAris0|o2X8nc568#V5=pu+Gb21`Q9xa$M>Ly z>qx~o^W&U&cQ-l^hlb6Pa8Q;Vt!D=@kMp?5BP2i}_@toeO5qyidNA^}{c?<#D-iES zlRIA3C(*77vG%b(&i!YV6X*)j{Y!~Iww+kSFk6l4A8EVpY`pFrrJ+%9^SCf; z@w)wJ$$3!TY<%UTOegnDKbr2*dC1(t#rQN7g2vlkL^<)J)wpLT_Y*r?QiJ~L&~pAt z4|&!KPL{S|Z!JRQ>r3-_Zzzwc>ip&J5uiT54KV}`byZvHRqI#rd@5*d7%RJS9Ci6v zqvlOx$HNHxmYe#8Kj?bEMH-Fg7GppaS=T;|txS6nc>m$e$&%9^xYW4X+KmLQ+kw*R zNvK>&yC1}+FZ7s9sQQZwPMA`z!Y9kx{mJ3))B`Ff#(fsvBN%7^dN5G=1=6F^dSV zR(fPO)l>Yh+2}26DFZ3<-T9tlFUfwIQagiNhF84lX-QaA&7ow~E5mHLm#5Q39X9P= zviX~Q;;Z_Go80*uW%i*1KPp%v+`hx+d&`Cg#k((BxREB=dZF-)AYVE=_- zvOJ1Rjlfe;pYz#}2Da}JUxkuPG>EfV@f`#44}4qb+#+dZN*selMA47n8sR zk{T_yuHqx%#$)Xn4SmZ>yy~Y^SUyMm+qsZyhIMlGcNc>fsQO7HBws|@UCr5H!F(2D zcb94Rc4i3}^*J(K+2b$NrAJ6u9tAJLGu4v1f7O%&j|snEpplDw99uESto47PYz%GR zfip_I2#-se@0b=+(~RiPg$r9>bc^EDQr{Ut5e=dGFQ||A7uXIx*kC@;YN|9mXCR;FrI`5Bd5I8~GR0{?{!8chuFUSWO6%}>T znfxCy{7Y&au?#Q&O&0U>e-Jc(3IG2WH!Y+k`5zYaFFl;HR=ePHrL*eBm6&L54Nmsx zZwLU{KM|`hU%X$k>i@-_5Z22Tv(3jOQXrDnsqBKOak*6QEaGQ$Xas<{TZsYY>LEj*t2i2i=30AmZm<>OF zQdsxVetB&^kI;PMrTUMMAU|hM;7S8HI-k4o>&KnN3x{6{1`a+8(w>f|%eE%|1RVBm zId4fVS1acfblBb4sbbS7-+6|yqFZkCM4f>pi3GRP4om+s@$Wz%;!9d!zPJqtV|;&Mg1`wI0dOnVa)1<)=OxN=jB`?yTwzU!b-rht(hV8;)Y zR_ZVN(93^mlj;6}Lsvjql3c)I2Fhi^;d;MMFsJcKzqw$Oj`NlX)L@onaGYt1a^DO| zdrec;@-(>8va<|Rq}AZ*c6r|fj@LC1(jJ9od}HPOV?l_we2p$}AGqdD4XS*5f=L^M zO@i|qblLDgq|Da#MD6l&CR`dm%B3}vqHg#2kR6p*!Jxrc2vagM#tR2;@wPUGf|{fZf{VBX_72;db?j$0w#InOfB&S^kpl(2@`(`ngaY%ww-G--{;-(I}t zdfSQWNOx|%%pFjx_TMYxyo!*Evj*``5NG;?s^7EC%y6cfq&aj!a8bPImM| zxD)K?QD*EC_fe)qAwH-CF0wJ?RI_Sf7#!1!H};N zZyu?b8uH2r`c4@z=Yx<4^xESWT9GQH$e-);&1H<0W?)B^Q-SL7i)q_LmUTJ+D zz4mrtZsx8Zl)da#Y<2_EMq&{}`S+6OVHlFv{EA z@SThOdlECy1=3IX&|0^m>#agX@E0YzoCo@O!NE3M{BGO4bGblG+e#+JQtg`v%+`g# z>}CZR+^$vQJ?usKn_N^>bxQ^t`27e%k@q&thdI*8so0Cu9^KgqdP2EAfPNDn)s%f8 zvV7&gw{8uF%UV+q4aO%&l3pAiCVTccqP)LYKt1UesI4stD#NCn$-Z#F(^6nO-pw)H zzSeY^neu!ZQ?ro+;96j`+N;W4U10coC(=#eziN-h>=ZHDu8H?qAltOLJ1KxH%DA)? zrZOIu8k>V^qrBSDAIuj!BXlL7E`kE8QO({Dg~3mNm)i`%9bK~#iy_P52i?JY6)(z51+ECDVLcZBZk zppaO?*Eqpq#CHGf`5M#CL_^{bY9BU&cgpx@aN`!Fo2Rudm!^fab!L*3qg z-Iq8(C2e_Q|I%!uXs)0~?YmeFw}_cO+UX<8`+4&JS`NN0K)&6va+wC$y+VNZD&1w2 zA^cG5d4E*Hxiubh=Ej7?*ortD4}}SLkl2?M#wf@m5|UY zf{@4>+|~zP=Dpl6qEYu2O*`Mj2)jDGJ{>CC9QkAgpTDu+GCZcpLf-VyfxL}4CT72G86DXHd|O6{W2K{?e*kdWn*zrPELfT+)Pc4A#`|{ z`ZlE5e`WF*r)IS^~uO7!9_Vt zm{R6v3$J~Ex1Wrimj!$uWwF*)tjC~;5UZ8X!kX{^L$h$ z# z;og}SqTTt1Ja5Uf^mt^|zQ)&$rpE9zhU{%(g9}S?!Z&t3X}$A`LaY_1)Oz+W`xc&5 z>~kRf#V*-}ETs3A)x~Ms7Tg;YcT@K%Eh#|g<#~4N;VA;{2asMdCm<|*Yf*c%$zK=| zAN+~q5B0N;Xe?nEx1?6~1bM5st@|d!yqT~!-)z>^f$+JC_Vm>Wy0zRd=WDxHGvDU} zx5tT7{Fdj=^XoJKheHjA-g+=&7cQ>at`{m_?{(B#>LN|@QIv}?@71QZKLDmre@;2KhOaL`@jD($=>_i`IE>c~Q(ALCS46hH5(LeT#zyLG4i1{-CLIckgR2Hf}QW2C~T# zHuHMi-6XZ|+6jS!F0oDei#*4f z_&0-xZ-}%EzcH~EuOXS>!A$LUc@OpwWPZL5KL$}Gy?=fc%CNGwYJLNkCBq33R|z~= zt!J=_@=v6BwQdVqCfNfDQ-OnES;kkoCOY|-f~J(982{SZ$krH$ss@MpXOH5NfIg$N zo?A92_k0;IE9hYKltLR#-PlCG9SoH^;0_H zp?UXQ!IC0l{3Agvu}Vvej%FJm)9@K)Z%8NyiKiBLV#dLB4D zJZ<}8bR+p(0ct4Rb#Wqa|LH}vnqhvZJ+6!ThR!+G7~1T4pRc!XDcdsC=t-upW!?V>wrZdG zOi-5}%zvvZ#&+FuduHc2*|D)%_!*wSFDi&xl=T4HJE&@{Gp4GXftOyWz?sm^cgrx0 zEs$WTEkR%41^(@+cWe5h;ia|8adN?K$I8t+LAz{r^Zk%Kxo0~(e0p^8=rnhN8}d~0`6I<#ifxf-dx4nNV~UF_2%O{9tt}k{l6{e zEP)`JW?yoZ`0n=48f~BJ1DTji2~QuuOOT5|B-&1>L{oDl2+`^{u5$3_*+e0E4muh* z+?rFMaO=gS@ILL?HW+z)wXFkH$L=yu^4R@p5QEl>*8=zB$G%{Xi$Z)4CX-=?6Zve2 zAD`42>#km0IUn#mnm$(QVcA6-eQ?I6H!wIh5J2L*mXGU2E_10Tw3huNWoi6anpzT! z^#f9!f#b@%3e_Gsvy3=;WwD}3oE0+E{Ofkq`G=OM?mUN{%Wv-X4kCcuLd@Fw(6m@| znYNBssn;_=V7=s^SW63qcjxrAEy|@grC+m^Z+{7b80~KB0;fKE+CY13%Bg^=+TaC!5z%g<1rBcUq_wiI9J1Xu}FPSMPK23MhR^}xgFpdh2 zmP;_3we!2fA<8t)3EgYb8GQ!mo4&0CLcBW&UYG6=PAKB05VVgThf`YvTiZyLfAGQ> zSBCBr^<+T7x7O>mNo415kx6AVk7*{t;Fl2@zI$Kn%xPv7GPIzv@Fc~?y{|UJjaQ&@ z4k+U~OAAPMltBQ}nR|G!@56k_+K2XEfwn;q9plpbOPTop@mZKOviG%`1>pV&`N6e6 z&^{b|Xm3Wf?ZUR5`nesYK*OGUNqC_TNqlNl589s47oJ$y#D9yXD#Hbzg8oc&ec5OD zs0jLsuDVy|Q%X#aHytgG;@%zNulKW&ze)6E;xz zTh8kn?9H9p#?Lpo7lC+=Qsw1H8g{RkD`Y3P+=~oZaHoa6$JEYK&cuQ(ZdCp(nOgJ9 z4Fa`>GncoL7dp0-=@mPQO!HXM{_Hg2UAHpUbfV%AXGhLSzK$Xpx|ig;X@o8sMFiYktb!SepXl6t3;ov#Cu=;6ZP~8BsL?G-SdFff4+v&N|P_cu4Ou_Apo@CtAhG68~{`(7}{-Wh>$cF`twG;fz$Tum9 zRVY6nGak?k%y!fb};B5Kab_7ko7g*Uh~-rP`igyJ&V z?NfS6k4Lk&9^bm8M%yX6=095aXjCNn(2@e)5&AFt{5SjL-iP6%655YJY5F_C3JtlF zlH>8I469vk<+9^tJ4SwFOfUT(-yhAe_5F<5N#m(jj<1^3?kYic zV2qg1tUeBlJgRnP8^iC4ISTpGxuLx4%coAlrSURt*8$3M+LD4sj1jekK>+uuPu4=_ znz$&Akjz@pGArs9f~AID?8DOBkg~!F(P^h&A7;_do*R>_y%90_r>#WFMIIhaRrXOp zW={6k$(by=lH}rfYF~kgs>mKtnvPrI5C1 zH7nV^!uC0PpSg=oGqr-;Yp&zvYAa16+1zxg0_Zv-O4oI?uS#)e6X;x z$g7*=Exe`q#SWvx#uzB%hiyY>J2X;sH@Ncfy#ba+pwEVOKkm@N=zRT^t(B!Z0V6Jh zt1Bq{xOg0jW#nP(DyDmGAQZrOJQTst$Mn_ zwK4>j{cSB@=26H()t2J+P^E_C+M=N|yN=)Qgo>55DTm~s{!w(wGM=OzIq@8ul}Yy# zp)d7wYIVN1E%KqA{>SRrm$P-cf9;*S&zyWbreCPd_@e}tqRlT$3tUf25_9%x=atM> zCmD#Tf;0D~`A0p>>je<#Ctp(4CHI?0s&*J-|5Wkxwx7uR`uZ{I`$Z6j&&VYzR^L-D$C-%0IhaZw0Kix0>Kr*=~$iic;D?J#j6?8Efkz zFX%g2dIK)$@yh1sBV6g>$kc6%d7qituoog+C*erSB9G(bL>eCI%5ixwI~+b9SADBp zZy~VrhHi~IF*{DQ;p^I?ca8X{0J3XN%35tXK+>+1@2O9JChiPx?XRJI2NT(B43_Z% zeie~ty}m8A-BVSKKI`4sJ&wbeHY4>SWk8NlZBj|3$o>3;Bptp@ucVw;mLI`R>uH(V zV58-BcV0#O+FHin~VXU}qG!yeiA4nirGFJSZcknT;8kjgqFF0f{?>Ys|3q z!W@`=rAojoIK5y+KcD*Oo3(NO$S1gv`W~KpWZHqNXc^aeSzW$e1He3Q7cu-Yk8O3f zOFoa&w3Zd4>4J~P-dk>{#)a0Z>u!gBKh-D9(;Xb;m+FEK^I;xeQIU-f-R2y)D_ra#ydQ$G_I#c@#-<-Hf+@~@mdfK0 ziP6~8FFVg)WSpiCiN`_#vhq6*%Q^P7EO`Khi5kj`m^arsm&c+vSNkj#eg2&>?^|vC z@r37j&A&Zxx|$YzO!L0S&l-RCiKpfs#-8imC^tHeV!fTuK3SAkc(=@RcRBeAyuIlq z9SgYL1OzXsO77pkos1>D@!Elh8y@$4?@VCchwa|PZGC^X);6m9;XMXH5CRlPXJb!n z3*)o{?(dqtX`RvFps*@JzUOfn|EZD*B5H6PIyjc0Aov9;P$Ys<`CAGFmzbM&>9RKm zJ@@XGe2srt(=5L2bI|B#-+$#&bG|w`0u^L3QwqU`qjAgVd!GNo9{6_36ekUUJz8d@r?vJub&#<=F8i zhl7CVZi`;g^$#pI6fwf9`;yici_5-p^)gXoVc~6pqcuI19JH=_=*`TXYpKoS7U?IK z_Uc7lb`BQKWFVKKzNJi0L^O+#w=CRy2ujeryNOJn;^FYH)m?@w+?_4#U=?}hRoh+R z6cBijnX6B~y=>t&JkW9JOHbF;O4VM@l-kjZfa917rPrKlXKV61*9ZEl$n4h>rIfIn zW4TY=$|!vvs6q5%Y*9qpJX%b_!-s|Y-CeGW*ephG7oY2@qJOe z%udTIwa#;kvVz%K5+;MasGz7e4&%8n)s|PUGFGGM?ewQ3n|IbjY@(Tc6zmqB-KgPC zW95i&k+K{FT`2L^8aSO>Ejz13$h{tXWj6vUHjv?SptcwI(iV<=bD9h)U#;g>s8`=# z)I{HS5U*n5aKaiJl8IuNMCXUo8y#w;PN}nZV6cAw_Xy16tC_Tuzw77v;rp@)Hz3rS zr5(4jGGEYo+dNkMEjBb|g@@x5l`lSF5;>^&@APzAYa(p`jE{W6lwzTFiY$|RhQ2r3 zOU%jsKiYfmu%`00Z8XYO_Kbx++ffE-I#Px{(n7}>R17UjG15e(New0xDIqYnu>cyR z1`-)5(t=VAB|#a1P!j1a1Q19_fKUR2kiuEayx)7yb)9{_Z}00n?|WVQTqpmmtgN+u z>sO!We(vX9D|Ne{A|sa*kdrV&f9i%`)Wmx2h+rWZGG!j)VSfr-5HKS!t6>3*lf8KpO*uk?PU5bdc^_a zsqTT{==j8U$WujEd<~+8fANZ-GmIZK=ysYG%?C9J>uZ-4F4?_yM)yb~XQXXgIB6U9 z?os@hnmAfj(VBaNCbhV#HBL7cpprc-z^t? ze$Sl91v7s0`Z_jC)xLqeb$7s&GGx!Ndk0#TuMw{m5!XJX9#(+_`L%-$=SGLeatxw2 zej@IRP5hKkT$>@l6v}alc2+fS*r&WuRe=+UFYB)y;!$K$t>26s03FrY@0xUT(P^h$ z6;Ia88O{pz$e<^m|Fvx!A$;phu zJ5E-0+rPSQM3=lqxs7urLE|hP>VukgrI%KADaiH57s&Y>OEN6Q?1vH5gXH9UXh+cW zb6~mNE!PZ@ZtKtCrIT;lN!AZh!b;TabJHVL!9H1Zo*(88MMG9qws%rE?hjT4Phvya z_5pc#uUPKq$K!?7`laPi4(7ucVMGN_Qp@qqC^Qg=j#ElUR0p_@q-f86BI<40SfE-~ zzWKM)TWT`&mvqsM>K1)LYX_#=$+0Z)-ii!p#j&W%6$bP7i*idf;T9UY*3Gv|!{%TE zP-@>0WH-}@xX3`$#w%M&l}56Uoxo+Ara!cL6&*%dulJ9=YXghU2sZGYkuz-~a*Rf* zO64+M=qWGsR&lqlC{=joL8;1lXB)+9O~~0DIl*jAs>7$uA)v&epq23)=gC@auY_Bv zO6eA44vVUiq#B2q_wc2ysDh=lo_XDVtj9P?S}U8#c0q@gPv8 zW(Sk8h#50T=NeIpgYH#L)EcwJ=MbA67)BK3$cXC5pej1nu3yqN_ppvK4^wr-V#=!^ zT#JCWXwdeNvi!vJ#iCp8IPRs?@C4nkn5rILuR8PC(~e>x+n>11eWbtjG2P&GBRFJ& z+uhb;JGa4;Ej+E~4;xBO;K4FVduNd&E{bEgHMuNC=r!`0O&59nYP3sY9DjDqD&!~0 z>YFQLq8px>^_7&tw4NS9rT5s-kLt2|w|mZwa6}tiA0~;3L|k;yfaoV~$q9a!?r6HR zKZ*aN%!~adqnuz2ZW&^h9zUYSO4Q78#~ae1RD6a=#JabfDB<}H3n(Jyhn(Wx%_?MH znIB`xZ~n|ZGQl%ru-9&@xvmj1FXZP{TA~@nPs_Mj`o3h&eEqI13x_ zL}RFo1LYUKW7qrKccyV$@REQ#aGhnTTvnkLyG-G*G5<{!w9!l&&0RVv5})g}QYFq57m7uG^XZhcS<7T^6v}zb z;Ja7Rn<{AuI_~RrEv%B>#F{hvlNt!SMUnhWXK(K=drM&S9DRd*Nb;qFTv$!!Ws z3FUHTF-c_s>b!J%EVP;cCf;UBCZfWiJHZE=D#{^DZ=9E|;el7pjrc@kdAlR-gU?+f zpE*R)QX~25k6RE+hhT5zV^D+Gb;xA!rsafA=c zIP_|mgV{tnHr%neSN`y2C=OfEZ0bI2HQLd>RTz(!J(zSa^p&2arFl(Di2X2OJC?+M zs#A(aholmmG*1TlT@11rS7JP8#b58)Io@4U)^N51T4`4%KhVGJtP~${0k=G!!`twwr<;W7M?ew#9 z`5Z;;h`;!dIZu?SX!Y~HY0Iv|fMY0k)ND|Pq0Auu`tLMyT2zQ%UbZamgSJ?(n{|on zPYU5)2#$Ze^7LRrBEmfe!E>W*q98y*Y|01I%s}dMppXEOeSZP~m-%6K1mVxsMS3}R z5D0<04N8=_UW&zF2@dreDzn9^Ft3bn9j+fpx>w-pmJxa#Cr@s%&~OV7-EZ%?7I~3r zl%^DV`K-nvSsK-wuiI@cj)!Zu8wD`F95Z-r2Dvytdf`z2WW9EbXqB?m)#+dgyw<=X zd`WBPyHTXr@}lC4LsoZg6{nhh-(g(QX(?s9G z`R=Cue)C2Y$4$Udce$SeP)XHn3vzddJ1cZaviXT+Njbjza!?^XVQ0PN517ANuP=4n zq)>2B1_(WElJ^kIqf2`sJLucL?P1)1V*BQYn0=r}wP6z&v}dlx_KoU0F2wSE(=Fi$ z7k&8k^`@towb^bDJM`|k`tYe21Vz077~ID_1URFzu}nY8*EaCu`*)>pe#mH2I10!? zlV-;2b(+ZQX>54T3x>Vbi{z>-d!Q^DW2S z7iW!(f^Z_wQbL)wy&~wjN>iM$zi#$ZEqo`<5G>C8TL#!M>FXc8cIyqrItBx1u6t?O zo6eh$6tDhK@C52Sa;L>%1Vt=qDBp`Es9E%1FUJnh*Q5@Qv^&b3jd}v;hGJfRbLOJo?}Hltlh}9|9Pcj# zHP5=j+{Ux8H}8zdte&`5FVkxNf~F2ix@ViT7i5AipZiDKX5Y>Fsr;%0ZolZm16ihw zu4Iu;I?HBkpTBA`w7C^cm*gjKztEWNB@9ORR(Zo2Xyx7S<>R|QgF&k&_b?y`bYpj% zC$37+3Q?D>xN2It`aU%(Rqls^Nh9V%k0?Y}p?|AG6#CT8Y>@ z?^QyhGPMpZPO*3!6u+a{;Xh92A=pKMtT*E-EhzT##7lF@;sfFth$f3gPGI$`pvDqc zrkEe&(+l;HyBp|vq`I4`x7K=o2svGPIMfctF<7j9%FD67?dXkRl!Vh@o{Sk9dRCi54X2yy}} z<6SU2lQ6qD)g=xw8_2p->T@@U6v_Uo)RB?kU82hNOovO9Wtmt=e`L61MhpF%*|2QA zAG8{`2e8ix28{z}4&W&tcXA6u+)2AB@cBh+D`OwQJ>~~q#-`~!)H;i&22(3_L}8?z zb8?AUOt_VmK?Wpvu^g!UdOlfz80YA97NL!_>adBK@Pt_{V)Qy}edN$$e?!oPtFH#j%z!Ts}4M|c~jC|gml4DEmp zy^hv(uxYG*R~7W5g`$&pOE;z~$aJyF3Om5TxDFRdrb&ySR_v*B0Mpm?=5z;c@W&0_ zl~S+GbCT#@7q%VpV2nwngSQP5m(817BVrokF%A#S;9Q0iR69V`qq-3st4prmzn-1pX`HT=NV@9mLWZpCVr zY%HB$tT#X`wzRawt`9-4XIF9KZ+QeZViYzM5C|6z5*^owaR~2(1orkS`s^aCnt~Fk zR{Z9?=#O)QnXiEe=+&`3o9B<*t@EMs%~{MeD`Ze=C3HApw_9+Lg(W-rV4h*|)?)0T z>qOXXe{Z!wTJzAopa`iXM!^)vX(v=7>{0KJ?%*RAVY}K5v_=)5oyAXW73KPr&)J}) z>OYIG32^brr8S;<>M5PI#vg*IRms1)T3l_+X0x5(>+4p%%GRp1C|j!RGbd ztszw=T_2{2tRZP?njL}H=dP+$V zk)SEx#JJ7ycgqbNE?Mb_nG^CyitFkPyp51Z=S6qZN(r1N)H5ktGep*VR@v zz+6{Xj0)A2^!pU8%TG~&12JO^VEsAPAU^awASb)$O^($rr6vS^Dh^#KcW^`@nb&vJ z-LGY-w7n_k+!yr~@p$v{H->qD9xiZuA1Bl_iBNV!<*=DJNr@S&H-8+KApsLq-_$yY*GTvvpy!Y%Z zsMS0p$OYKMLt{K8%yb`GnK-}PR23aH8`!^tC42?CYYSjXfG@MGL-N#jS7q2^Q^c-D zLvisUfRW$rS%KF0%V8&TL-u&`h`+v-{!d1?HNr8x>!v(7tLbad-IO-ta3{4a_&0G; zZvN4mTN#yT&?w%!1-0kXV&oKnrxieLM)2!Lwdp!U+ zyXs9DkZ@r0a0a{Wsq4QP`<) z)9Qo+p%axb@loqo=g`s;tfj(;oSbXY=b$Rarr&V3ma|e#b zb~GrsbN>$PPei)sX`a7S1N4d&8uVNXjped(t~Gb1)e_TJ@IBSf8g|)Z~4I zT?x_jCg)AdC(=(?7{3PP<{6%t;OAt$^1H&77TT+r%I-?A^~+x_t+fSJ(qzjaZKeRrBlr=R7`M})_Z zq#ho0q>QTML)yvkKC7FHN@Oa5;asdIxlO%o*I#qmTD)cx6q1WUV(qKA2qy&GUJw5rqdVWMO|1+ltpCq89e=s~;jQM<>{b46Cu9a(mM8(q6@SKbHPPlSC2VntK8{ro%jQBwXo-|qqPv$_j%{ZkFlRK zEk^5&gS_)-JtwhGyv^4|=fa!~tYi~|12D-VPO9?UJAgl#skkB}{|L(9IWH{6wv((AioRk}F)LpiO=s%~XD#}e=<`lMu+8-g)Q# z_V(4{;g^_~h`T&`UZO)ar!Oj&eQx6LSY4ufc8L^T9o%=fQ?cj=aZCtNI~+_$sdXu1LmgVy)BI&jVMeD zfwTh%-U*;>Q}6Ek+XvKaNbHBPOzvs$;K(TzVuM7(JQ%m*-MrB*|FCiX#3PXG>{v z%Ea8fzNm2~TS4+`5;*f?+C2t;=#CVSq*&GPJKRCmqI#dF2s-dPgnF@qRqcz8mBO=C$`zu@C& zPwv|W!M5)SZs1pF%rl2>oT{j)-haNop1yU%TUTj(c4Me}SekzhCEsok99n!8-hE7l z;Mkp`6ZFH*{qIKenn>b%+KLO^1ZNwjE)MU@|0R$$*o|Spq zdt)j^@cEIf|MeiZa3V4ar}wq7eTu1A#yXRxEx3_uKgm|QJ3ChP2>i@3reio1{i#p3 zhWOgtSN%vB^0MXk$IX4l1L;z1u3~3rSsA!F5#u`O(9>6`?oas=G`2a%sU<(YaRQh> zPXLU{_@$ls6+1T0AJ_T9H2N0iNxOn|ow!&aPeZ$e2JAH73Q7YJ)|XNM9z}D1dwAUN>LsWO3ozgF3;d zaYZ)mT;oVpkFRG(36Y($e=wgtS{t^|D8)`JFc%j&mpNJkuz&hgMEqB)CtMAkC@+(b zu&p=^kGA;Q{+zQZrFVmox!4Z!Z_D*)pWG@dBCEkU7iy}`qJH;+Qnt(yatau)X52qOi%o|a&u~TphRg_aH5O@56?-cfl>yAkX6g56%SiW+GOh5d zy*+uz0DC*A9*v*4eR+7i{M$V}$d9~4&~}Qv=~}0V$|l9`b!I;8Fc9nl2lwy6ROWC~|KC1mAQNQ4k;T7#x-J=hVwU4 z>?L2y{4V-b2l*q!Ll9E7c93q&9h)7-#}JtZYk+}jod6UdNeyMi#-)|^0XR&asg64l zv2Y%S7?@Je@_w1Xc``R_6z?-IiSyzK=6+M9V4h-jMSubeelRfQdS=D=K!iVH>^MY- z8>zG-NS@C9ws-uKpa0188e?mYqjj(> z9q~4@3ZgV>_??Lw#d1WYo0CrSN#CGjy`miT7egpL+_{J47^(osRzZWCfSiRxBjfG8 z0=ZZAm=LSy76@;|q$9;BOFfOA7n+;z#p6Tfw+gEg-=c=ze~<*aDT@ARW;}ocrz;mG zFkXUE6%@qB$9e3f=u@*wKBXlj9jOOZ8&Nei$cgf-4)*P@)r~DQ7JRt?L1J9hGNeDl z_+5Vf-s_)S(i>Gua9GiFqn|gXYrHVtw|^+haD>cx06%S^^Jjy=d-mu_*JH}O&!t>5 z>A-9pPvCbo4C8q3Ci4j1DSWg9g@X1EWcb}$=j8EHHX>C~SPa>#n)V^LP@O`Ds^)`L z05-PNIyPb=i2}bS3Te2syc{nWTucLqLl8qA7+AQQBg)wapTLo#M1j?nw>PG71yLE4 z4uZW?tlRWxiw0%ugQ~EK^1W7i7pbrbT$N+$;evH_vL}#&t!F~Q5X1A7(tOGh9|rwzeKx1JsH^X}+CP8`?5V`=`fFyvbRM4);RO!`9S0+H5 z@jci~dbCY-u>qT7Hx52pU!I?@Fpxu!g!YeRRK%*V>Btv5ejfe^R{lMfS<}|RSMk^R zz2=w{ZwM@~-)iF{b<58;29xXOf$Fb8t$NJHVf?!fPJ|~6sg?KgFvWG0sD`7%^&H%s zj}pWxqHEtXcpB-$$)<;rC_ZVE|IJ=c{M2*qfXVIPbRHB!if}27~l=#J3#_Ctjp9f5s>R5TM3iU)lFV4aM=E0n}Q^x`zk2m1ci{^>4T(+Me^3 zJuKOiQZS;lXZBz?=!ZW@t$)Mqnlpe9a7*-C$oZ|q zb-xi*W%H(TA7<(4j|K?tyC5hZi(8b>FDiF!##8}ejmB|}qJW=$nR!2&*QlqAPw*R* zD9nDwiX=GA!(`j4d8h6OIY0K02XZ6Zxu1LnD*94DA0g>99o$nnWe!+d@H1us9$*g= zCfy9qzKm5|jF~HP=;>5Zl%eNf%>$TUEKmFWm+}C=-+cJ&j6uknEwX)3j9;Fr)lAz= zgKX}UR5zArbPOL>1f(Uc_lFDkRbX0`ch1C$S1wBypEDJ7vh3Yp_5l9~yk>WVush#R zkLGg>^Q^UupnA{=dke{2 z%5$WsMICamf9&0vS$Pg)5bP$Fl9IdR7F>Q*#lo?FGgQgT+W63k-mbx1XN>bvI1W3I z2Dxh4sH>-V8+*EGSg%`0X2YNAu0B6{7XI6+)G&6d101+?M?LU*TYW{s&Zl3Z-AohO z>3NeLUA}2n>0I!Tty^)uL-!~_hrIwjcrb3j=L&Y5qoPq%A0By;G;|Y~&Z{`UrvcXS z_N6vz9eV+IjTyMm(l&tm@`(E+$)F~1(;o?QJDDmcM)1O*^#Db55Up!O?Nu#-%I};J zIbWR;zpl+XSRE4Rqj{1w_e;y-07rpiS`wy5d3k=HXrB?W~oHpka7Z!Y9nlB+K?9NFM1$c1D{Ci)0es=LQ9(yki--kH# z)sJ37$6S0GEadBt`f-M%*l??}NcBx-dk45MK29TF?^$ofSMk_1>wuZW%vE(tXYG>v zM{gDHPW1kA8~+^uP)9LcQ+!;gBl94=~Mr`JgcvYY&~M# zj9*&{{Azcr1n+2TV^1vu6bNaTkSk|mzYzH)H~aSuUkmPGqqoOZMU)$#Ma%NaP&C{} z|K>%iyM@*xbx4#;B1@DOK-~$f7OmN?FLx|fFEpnkBYQ)tfLREt`lkp5(pZaec}I!= z&S)fjxr-om`}!5|*+=BJM%ei4vCX~%nE*4_xh)lHFdE})wJ~!c2j-FC$@R)-38(y) z;R~NC5`#UX!vHP8F58wUVfrxH3~ogthxl#OriiJ-CSZU3?mj8`$-`mM?((GPvpha( zYG#GogNvb9Mr6%ln$$GxUOF!RO#>16q$s;CxFgx46yjxaXZGi)zpI~nu|GPwQT9%f z+JP@m+Yj85iu%iUU(ruJ3^O5kH(dSp@{6!iGm~$RyQ`_b_?8BgWsi^k~0uFGKTd9N166~-D*UbEO5?K7<8#>WmVJjl&A!7S5K z3oA=_8>)+}0iqSS(kkbXvgpc|V~=W1_bRS~hsHZHqQvG-=n>X~CFIp8cS6%5wK z7A>EV_Th=&R81@}ma?y5o6tO*h-(Yo6^doT0hJARNm%-eS^jI9buJu^?Pkh@y+%Xs zT;#PL5wN7xYc^+O4RC(Z8<)27&1(E&IDQXH((PWSSp(y)`wS4&Tz|yj)VZ5CSkJlBV9g?UbcB!O41vAo@aX)LM*g&R&y*=&y31ILU{Ka?vSkGsCRSjCR*l zynr-f=bO1%L5+$Dqa3Nit*MjHb>pP70L#!mFg3GN-F}#ku(^t!e-t~{IZlt{2rNXS zZF0GJ_L@3u?-(1&nAEJ%~l62p0S2PhJGR z0uE^3pU+a9+e`NXXzOo(JhcJ(`Y-=MRs5G6E~`@l-Ow;MMtt943k?X4D;|Cg-23w9 z!O`b9s=s?*UGwnkw}1KS4Jd12wDWOoLqkbROUv+UZ4dL9mh5ul=GVi%ponjpW2Cel z8Dzbl&#R9&qMyF;Qpj4B^7?J|wR(uxjo*^*{*ow$>zV*7>jh z0bKU~8M`F^hnnKlUMdfussDw;mknxM268Gl{{{lF{-ZRx45k(lO}n=$lsneaD$!MnbKajAIy@qRpi3KdTXt=zm1YHsgSUx!UG zGF-Z=da9;`NcTHiTQ26jx~lN*7h6Y&{Ek7*%DAwI$tzeZw-R8KV*8xGJl`3 z0OwXLSfF^nC%2el3VnNToW|`3X)Es6Z1o3c%-WPVj}XmZk%r%r%>7Rflsv44-!{~d znhc6fSN94HD3M_mkas&2^1`V*6I0j;AypfiYlbT!@%`$Rt#TgM`mg57^GG2={dZz> z!J>M{0J{6px?WW{Wl3VYUL?I$#0ETJpU*<+a{9iz_KV%^AD9+3v#wiQ%N$R$EY+MI z!sKg+hr#=IdUB}KyNk)orY6SeL_1{ML2zLAGR*H26!$pKz1OUXO%+Xe3K7%NQ$17M zOUCGFF8!U^Ct6Z5coza>gAFoG_j!f$YI1O4mux*XaB2|mlN00phUV!x0`Q&s3qe-W z_yiSN-W$L96mmA-XDphzMX}(2ww@ElLyP;HG_^e2YlM^)R)K8EKwW^FnF0Px{3Crp zzQCBxaSAyo{mCV%JFK@|5~*TJ5}tEXqx}BC!rBmDTh&!eQc2ki3S(2IZ=iQPi;)R@ zYi5DstgPUNc^a_xx*g9QxY3t`#X6S-{%wzw7h%j zBuHC-)4hI;NjKUSCoI1x5Y0|mXBzS8m=EpswQ8ny3EqsD?#9Tx$(DC#93i>J-Y+t& z$1_`?+#avonpj`FjA;@WGP0vpjhLuc5cK)RCLpXb7*S)RIV!V)HD74p)Z^69MlsXN zdo1gb(m2xWA+v|lNf)u-dXyGo{BA83;jg>J;rH zUzVmCjn$S}nZ8q>iua3GLhs~Q?2HQG;U;OdG} zW6fn&IeDFaoB?vsscwwQrl~kwXBjAn#@!`_Vf`qa%RvZn4NS_pj)?*8yPBROFj=UM&1C?AwH*BiHTa5`mPEd01c-@N5WI~}q)+H3`X z;S!m2p@Nj#&T()`p#_lM5Byx*(P7{ncIjRW^Qp1E;melzM~Jd_VC$HUlrMW!7W_MM z)xL-^qoR_AGmjn~Wj-i)@G?_80yZpZY8YrBdjDcXUi~1V>J7#LoH&T_1)clf@B{yotA{*fKyP0z1FfvIy|&SO5oDjn4^Jwh2*F-T zfSp~8_`@sz;jU{@9R!cG*1ymF`2gg9gJ-^L(?cV@JXTVy4_o{R=$>1K>67>pMLdk!D1 zpguH0u~&kf#P(;#T39!ZI$m@68q|z(PX$mK+Pp>8D?c(8H53ESixkf1clhg1lp!@&9mDcJ*)xL@#CEIwF0TwJCSBJ zJP4}pY<>&My=f_9--!Sy^MRr|bjrJGadnFdB?dG{Wr71c#WHPUeIt`qw2iwzthoU$XOv;zuKptrvY(gEa^ zSs2_1<1+_wuYp90DQwG#6tF1TzWp%pWc9kD*1*I|mCB$>Y;RgxUgv74XPn-u!z)s$ zsiR!?Mo%av_@u}9Kx}|#{D9ZRj*I$M`B}cVy)}bwhnuxL*?h)R1n!dk=UvKxE+>R{ zQ>AQ7>3#TASXgdckQy(8S!>jS@U#iB&pmO`;H(keRd1g4H>N@Cmup9lLIV&1R;5+l zAAffC&V~wD$93nw10{WG^U($X)qS_|xaxY}(;YH0fuS>Q@+Y1=@_H#K&C<`XJ1JjA znTyy`z+D=TuMU4}4cllaVJE~yXmo%;+TZViFYTa=$c@TNEYY>p6dIZCh|QIEwgzXc zmBP02ay}&1Gp__ajhY^#Dm|(-Q0|F zzm`oCm2TIfw)a5(){aWbky0vjZglOP>v(yoXLgt9=j)Q6 z%7X1}$~x2xA``TOoQhc|`=fBaAkYc(J*3Rouc>J1K4g|74$MLigG=zJC z7(UZn$_S?cIntztga2n_bobDA@^Z8d)lk4$f~WwfyT-wZcyvCVgh9zR9RYqcsoJZ` ztGU}G3B|fod_45H*NRNaOJJ40H6RFL9+Mq<{OO)luMD z?K@Xr?bp8Aq}18K2N&My*rmqs$shQZmbkTc>^hq)awaA22J+OE*|Zw`u7ef7@TnMZ zDAPlLv%3&v21Ox5+QTKw^qAgci`D%6sM14+0S(OP^|5I;vakwcs0#|!8K@Kt-D79b-Ppz;?7Ce|y}G{}_o;=-l!?X4>ovZO6=YB4ckRcqb+f$nV$zl{n`J z#F9@xkG}TgouAz#F6Oq^PDKO~mFEgk=qoj=DABl zQXVeysP&d(BrOV14P&u;9oiF@@6;Y0+D+OVVday~1BN20;cxqn-{_O09g_*SpEYWj z&xiCUqPWcgYS4s0&vwk=v}YwKe-=&jt^^q-)CdF5yWoX2^j&pj%1{`LNwVINAy#Tp z01q*cl}xHJHx8H|r#?$j=)Fehsl*1X4_YgBjt-1W`tw1cgE^4%KLL&h(ObpB)y1#> zPFS-LF5nK+6U6dDUl`Comwop3Jq*kafk?3JFel@x%e%?|rS0y=x-}%BvvXmz?x%JdY*6l4tN2e)DWa^T# zV>~Qyd>}i9#P3oNaD2zcV@|=~M2!)y&e{GlevXa7CF$3-qj)L|DK=i3VEe?fC@mN;Gq|^U~3@XRM`>*SL@g;$epP z9ldeaUhwhtEJU~&yW4wAz?CKE2c|vqTS@n!iqnkes4}5Wm|C*1-m76(o`~+qrN=q? z9)!QQGLT7B^_&Rrtqjqd=-#6Nj7|fyRudo|Kd`EUyY*Q08QyHJ84tdkPh>IcEjFj? zpM||w#|zTH{-UZ~6TTQbMMa_4ysm~{%c@r~9jQkK(eq@cCb8Ydg|`1o5DW#RAJ_V8&#`?mFZ<{x&mIYnn4c`3N#>;DyRlXNoZ&DCJb zk66y~Rsyg>?}oe?jsxand1m`V70MFb>%@s*iynVhSeC0+MiyQVsDifYe$8_!fZ42c zJOa7Dm-@X{0fdgg7IgVvrcGK^gM&lgZ2lFv*y}H>J6>M{=Kt@Tk-Fu7{&b%^Pn~^# z_Yd6cTDjnXE0{SZXLU#4Wx$|L3E-oXekpE$7^&xz$>7e{DLqiV1n?hANocZ@{+P^$W v_8R 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. diff --git a/frappe/integrations/doctype/webhook/webhook.json b/frappe/integrations/doctype/webhook/webhook.json index 4f3ba1c180..1823987534 100644 --- a/frappe/integrations/doctype/webhook/webhook.json +++ b/frappe/integrations/doctype/webhook/webhook.json @@ -70,7 +70,7 @@ "read_only": 0, "remember_last_selected_value": 0, "report_hide": 0, - "reqd": 0, + "reqd": 1, "search_index": 0, "set_only_once": 1, "unique": 0 @@ -267,7 +267,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2017-09-09 13:07:14.169116", + "modified": "2017-09-09 20:05:43.406391", "modified_by": "Administrator", "module": "Integrations", "name": "Webhook", From 3d15e6a43151c1a0c7ae22265dd577871a5bed8f Mon Sep 17 00:00:00 2001 From: Revant Nandgaonkar Date: Sun, 10 Sep 2017 11:09:50 +0530 Subject: [PATCH 06/21] Cleanup --- .../doctype/webhook_param/__init__.py | 0 .../doctype/webhook_param/webhook_param.json | 101 ------------------ .../doctype/webhook_param/webhook_param.py | 10 -- 3 files changed, 111 deletions(-) delete mode 100644 frappe/integrations/doctype/webhook_param/__init__.py delete mode 100644 frappe/integrations/doctype/webhook_param/webhook_param.json delete mode 100644 frappe/integrations/doctype/webhook_param/webhook_param.py diff --git a/frappe/integrations/doctype/webhook_param/__init__.py b/frappe/integrations/doctype/webhook_param/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/frappe/integrations/doctype/webhook_param/webhook_param.json b/frappe/integrations/doctype/webhook_param/webhook_param.json deleted file mode 100644 index 93bd7a2dd7..0000000000 --- a/frappe/integrations/doctype/webhook_param/webhook_param.json +++ /dev/null @@ -1,101 +0,0 @@ -{ - "allow_copy": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "beta": 0, - "creation": "2017-09-08 16:28:38.852947", - "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:38.852947", - "modified_by": "Administrator", - "module": "Integrations", - "name": "Webhook Param", - "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_param/webhook_param.py b/frappe/integrations/doctype/webhook_param/webhook_param.py deleted file mode 100644 index badb5eafd5..0000000000 --- a/frappe/integrations/doctype/webhook_param/webhook_param.py +++ /dev/null @@ -1,10 +0,0 @@ -# -*- 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 WebhookParam(Document): - pass From 8c71042c6765860c29e446394c34fd7dcc6f7f82 Mon Sep 17 00:00:00 2001 From: Revant Nandgaonkar Date: Sun, 10 Sep 2017 11:49:53 +0530 Subject: [PATCH 07/21] [fix] Codacy errors --- frappe/integrations/doctype/webhook/webhook.py | 2 +- frappe/integrations/webhooks.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/frappe/integrations/doctype/webhook/webhook.py b/frappe/integrations/doctype/webhook/webhook.py index f5c85c387b..de62c9e833 100644 --- a/frappe/integrations/doctype/webhook/webhook.py +++ b/frappe/integrations/doctype/webhook/webhook.py @@ -24,5 +24,5 @@ class Webhook(Document): request_url = urlparse(self.request_url).netloc if not request_url: raise - except: + except Exception as e: frappe.throw(_("Check Request URL")) diff --git a/frappe/integrations/webhooks.py b/frappe/integrations/webhooks.py index 1312d63712..af4cba11cf 100644 --- a/frappe/integrations/webhooks.py +++ b/frappe/integrations/webhooks.py @@ -22,6 +22,6 @@ def doc_event_webhook(doc, method=None, *args, **kwargs): try: r = requests.post(webhook.request_url, data=doc.as_json(), headers=headers, timeout=5) frappe.logger().debug({"webhook_success":r.text, "webhook": webhook.as_json()}) - except: + except Exception as e: frappe.logger().debug({"webhook_error":r.text, "webhook": webhook.as_json()}) frappe.throw(_("Unable to make request")) From a928d7ac01fdea804fbabf91e07d9b52b43250e4 Mon Sep 17 00:00:00 2001 From: Revant Nandgaonkar Date: Sun, 10 Sep 2017 12:03:50 +0530 Subject: [PATCH 08/21] [Fix] Codacy --- frappe/integrations/doctype/webhook/webhook.py | 4 ++-- frappe/integrations/webhooks.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/frappe/integrations/doctype/webhook/webhook.py b/frappe/integrations/doctype/webhook/webhook.py index de62c9e833..ff86c67c7f 100644 --- a/frappe/integrations/doctype/webhook/webhook.py +++ b/frappe/integrations/doctype/webhook/webhook.py @@ -23,6 +23,6 @@ class Webhook(Document): try: request_url = urlparse(self.request_url).netloc if not request_url: - raise + raise frappe.ValidationError except Exception as e: - frappe.throw(_("Check Request URL")) + frappe.throw(_("Check Request URL"), exc=e) diff --git a/frappe/integrations/webhooks.py b/frappe/integrations/webhooks.py index af4cba11cf..219dceaa8e 100644 --- a/frappe/integrations/webhooks.py +++ b/frappe/integrations/webhooks.py @@ -24,4 +24,4 @@ def doc_event_webhook(doc, method=None, *args, **kwargs): frappe.logger().debug({"webhook_success":r.text, "webhook": webhook.as_json()}) except Exception as e: frappe.logger().debug({"webhook_error":r.text, "webhook": webhook.as_json()}) - frappe.throw(_("Unable to make request")) + frappe.throw(_("Unable to make request"), exc=e) From 4b75ec13b32ddec9995815ba3effdedcc9d7ffb3 Mon Sep 17 00:00:00 2001 From: Revant Nandgaonkar Date: Sun, 10 Sep 2017 18:30:50 +0530 Subject: [PATCH 09/21] [Fix] Codacy Errors --- frappe/integrations/doctype/webhook/webhook.js | 2 +- frappe/integrations/doctype/webhook_header/webhook_header.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/frappe/integrations/doctype/webhook/webhook.js b/frappe/integrations/doctype/webhook/webhook.js index 84538f8ea1..91ec3b3e99 100644 --- a/frappe/integrations/doctype/webhook/webhook.js +++ b/frappe/integrations/doctype/webhook/webhook.js @@ -2,7 +2,7 @@ // For license information, please see license.txt frappe.ui.form.on('Webhook', { - refresh: function(frm) { + refresh: function(/*frm*/) { } }); diff --git a/frappe/integrations/doctype/webhook_header/webhook_header.py b/frappe/integrations/doctype/webhook_header/webhook_header.py index 535b626148..11d3ee4085 100644 --- a/frappe/integrations/doctype/webhook_header/webhook_header.py +++ b/frappe/integrations/doctype/webhook_header/webhook_header.py @@ -3,7 +3,7 @@ # For license information, please see license.txt from __future__ import unicode_literals -import frappe +# import frappe from frappe.model.document import Document class WebhookHeader(Document): From beb6318d88b8081aca73e4d9103e426cb58dc7a6 Mon Sep 17 00:00:00 2001 From: Revant Nandgaonkar Date: Thu, 14 Sep 2017 13:24:02 +0530 Subject: [PATCH 10/21] Webhook Data DocType added --- .../integrations/doctype/webhook/webhook.js | 42 +++++- .../integrations/doctype/webhook/webhook.json | 63 ++++++++- .../integrations/doctype/webhook/webhook.py | 9 ++ .../doctype/webhook_data/__init__.py | 0 .../doctype/webhook_data/webhook_data.json | 130 ++++++++++++++++++ .../doctype/webhook_data/webhook_data.py | 10 ++ 6 files changed, 251 insertions(+), 3 deletions(-) create mode 100644 frappe/integrations/doctype/webhook_data/__init__.py create mode 100644 frappe/integrations/doctype/webhook_data/webhook_data.json create mode 100644 frappe/integrations/doctype/webhook_data/webhook_data.py diff --git a/frappe/integrations/doctype/webhook/webhook.js b/frappe/integrations/doctype/webhook/webhook.js index 91ec3b3e99..b31d7e9661 100644 --- a/frappe/integrations/doctype/webhook/webhook.js +++ b/frappe/integrations/doctype/webhook/webhook.js @@ -1,8 +1,46 @@ // Copyright (c) 2017, Frappe Technologies and contributors // For license information, please see license.txt -frappe.ui.form.on('Webhook', { - refresh: function(/*frm*/) { +frappe.webhook = { + set_fieldname_select: function(frm) { + var me = this, + 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; + } + }) + 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.fieldname + frm.refresh_field("webhook_data"); } }); diff --git a/frappe/integrations/doctype/webhook/webhook.json b/frappe/integrations/doctype/webhook/webhook.json index 1823987534..1a3866085a 100644 --- a/frappe/integrations/doctype/webhook/webhook.json +++ b/frappe/integrations/doctype/webhook/webhook.json @@ -255,6 +255,67 @@ "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, @@ -267,7 +328,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2017-09-09 20:05:43.406391", + "modified": "2017-09-14 13:16:53.974340", "modified_by": "Administrator", "module": "Integrations", "name": "Webhook", diff --git a/frappe/integrations/doctype/webhook/webhook.py b/frappe/integrations/doctype/webhook/webhook.py index ff86c67c7f..b65fbfa486 100644 --- a/frappe/integrations/doctype/webhook/webhook.py +++ b/frappe/integrations/doctype/webhook/webhook.py @@ -14,6 +14,7 @@ class Webhook(Document): def validate(self): self.validate_docevent() self.validate_request_url() + self.validate_repeating_companies() def validate_docevent(self): if self.webhook_doctype: is_submittable = frappe.get_value("DocType", self.webhook_doctype, "is_submittable") @@ -26,3 +27,11 @@ class Webhook(Document): raise frappe.ValidationError except Exception as e: frappe.throw(_("Check Request URL"), exc=e) + def validate_repeating_companies(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")) 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..f968a6424e --- /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 From 24a2656746cd997e2bc2fa5854523993130ca460 Mon Sep 17 00:00:00 2001 From: Revant Nandgaonkar Date: Thu, 14 Sep 2017 14:16:44 +0530 Subject: [PATCH 11/21] Only allow specified fields of doc --- frappe/integrations/doctype/webhook/webhook.py | 4 ++-- frappe/integrations/webhooks.py | 10 ++++++++-- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/frappe/integrations/doctype/webhook/webhook.py b/frappe/integrations/doctype/webhook/webhook.py index b65fbfa486..5f74b22ac4 100644 --- a/frappe/integrations/doctype/webhook/webhook.py +++ b/frappe/integrations/doctype/webhook/webhook.py @@ -14,7 +14,7 @@ class Webhook(Document): def validate(self): self.validate_docevent() self.validate_request_url() - self.validate_repeating_companies() + self.validate_repeating_fields() def validate_docevent(self): if self.webhook_doctype: is_submittable = frappe.get_value("DocType", self.webhook_doctype, "is_submittable") @@ -27,7 +27,7 @@ class Webhook(Document): raise frappe.ValidationError except Exception as e: frappe.throw(_("Check Request URL"), exc=e) - def validate_repeating_companies(self): + def validate_repeating_fields(self): """Error when Same Field is entered multiple times in webhook_data""" webhook_data = [] for entry in self.webhook_data: diff --git a/frappe/integrations/webhooks.py b/frappe/integrations/webhooks.py index 219dceaa8e..466a0f0470 100644 --- a/frappe/integrations/webhooks.py +++ b/frappe/integrations/webhooks.py @@ -2,12 +2,13 @@ # MIT License. See license.txt from __future__ import unicode_literals -import frappe, requests +import frappe, requests, json from frappe import _ # Doc Events Webhook def doc_event_webhook(doc, method=None, *args, **kwargs): headers = {} + data = {} filters = { "webhook_doctype": doc.get("doctype"), "webhook_docevent": method @@ -19,8 +20,13 @@ def doc_event_webhook(doc, method=None, *args, **kwargs): 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 k, v in doc.as_dict().items(): + for w in webhook.webhook_data: + if k == w.fieldname: + data[w.key] = v try: - r = requests.post(webhook.request_url, data=doc.as_json(), headers=headers, timeout=5) + r = requests.post(webhook.request_url, data=json.dumps(data), headers=headers, timeout=5) frappe.logger().debug({"webhook_success":r.text, "webhook": webhook.as_json()}) except Exception as e: frappe.logger().debug({"webhook_error":r.text, "webhook": webhook.as_json()}) From cedb309446651867a6bbea739b1db0c2ca7649fb Mon Sep 17 00:00:00 2001 From: Revant Nandgaonkar Date: Thu, 14 Sep 2017 17:26:34 +0530 Subject: [PATCH 12/21] Cleanup and Docs for Webhooks --- .../user/en/guides/integration/webhooks.md | 87 ++++++++++++++++++- .../integrations/doctype/webhook/webhook.js | 14 +-- .../doctype/webhook_data/webhook_data.py | 2 +- 3 files changed, 94 insertions(+), 9 deletions(-) diff --git a/frappe/docs/user/en/guides/integration/webhooks.md b/frappe/docs/user/en/guides/integration/webhooks.md index bad812ed6e..30d90fdd37 100644 --- a/frappe/docs/user/en/guides/integration/webhooks.md +++ b/frappe/docs/user/en/guides/integration/webhooks.md @@ -1,6 +1,6 @@ # 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. +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 @@ -16,3 +16,88 @@ Webhook 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 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/webhook.js b/frappe/integrations/doctype/webhook/webhook.js index b31d7e9661..bff8591592 100644 --- a/frappe/integrations/doctype/webhook/webhook.js +++ b/frappe/integrations/doctype/webhook/webhook.js @@ -3,27 +3,27 @@ frappe.webhook = { set_fieldname_select: function(frm) { - var me = this, - doc = frm.doc; + 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 } + return { label: d.label + ' (' + d.fieldtype + ')', value: d.fieldname }; } else if (d.fieldtype === 'Currency' || d.fieldtype === 'Float') { - return { label: d.label, value: d.fieldname } + 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) { @@ -40,7 +40,7 @@ frappe.ui.form.on("Webhook Data", { 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.fieldname + doc.key = df != undefined ? df.fieldname : "name"; frm.refresh_field("webhook_data"); } }); diff --git a/frappe/integrations/doctype/webhook_data/webhook_data.py b/frappe/integrations/doctype/webhook_data/webhook_data.py index f968a6424e..b7d989410f 100644 --- a/frappe/integrations/doctype/webhook_data/webhook_data.py +++ b/frappe/integrations/doctype/webhook_data/webhook_data.py @@ -3,7 +3,7 @@ # For license information, please see license.txt from __future__ import unicode_literals -import frappe +# import frappe from frappe.model.document import Document class WebhookData(Document): From 369ce0af6695a332f89a8eb5cbbf5612da6941cd Mon Sep 17 00:00:00 2001 From: Revant Nandgaonkar Date: Thu, 14 Sep 2017 17:30:08 +0530 Subject: [PATCH 13/21] [Docs] Webhook screenshot updated --- frappe/docs/assets/img/webhook.png | Bin 34615 -> 52412 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/frappe/docs/assets/img/webhook.png b/frappe/docs/assets/img/webhook.png index cbc16c1d76469689b62c77cee8db22175f5b0ca1..859b5f57bdbc5c2a99fb4494b19e21087ed693ec 100644 GIT binary patch literal 52412 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 z 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 literal 34615 zcmdqJWmH_v_BV(G37+5%A-KD{1&81s+}#~Qf(CbYcXyY@t#S9p-K80F-}~JEedp86 ze3&(B&gymQRMjrqyLO$utA3|L73C$75%3ToARv&XzKMN@fPhhifPl(@hkZ|J#QyF1 z9(-^Tky3$wzr5j%L*C=K&f*%*%66vCZU&Af5N5V^HYW5=Mvf*Xwoc}D&galw0uT@+ z5K>~oD(>kgYhK>*w;A_u>ih9y)T}84!=b-^eTadFO@I#>=*_*YR<*ENtE_iB25YUg zSrWH3v}t>}mHC`3w_2?{&!6e8c&Q(I3e=MzV1_%gU_?y)U?LS7GH^^6WdT{CJiao= zp~a?r_?nC+I0=A8cG6wAf6`m{)l%u9zag5+n2kjC3rz%`^xro_s5x16q5nkRGfZhe zD*pa2iZ3w#=lJ+PCA@i%Jll~x`KY#rWYxvtN#*l^metAQ8e@eim0@TiA(~Wj6fvLw z)fi|c8xWpLwF@$isRT|qyZl1iW+Nq+J@-e{ZAA%}5>a{YP*EIK@xO?-+wpJQsc31U zFI3=}X$y|)--}Jp&#}@%&3^kj53(45i$prSG}2tN`o`YmdILA3miGHgF4Hu}9#h0K5V?tP5?bX&KNLu|N( z9jmf&kX0Lkr+Wy@3(Y|j8-YQP3!5Ia+UfeAvl0Gjv`)->D9*{wgGaAbo|86+3eB+b z`Z*9(aiXZmZD$gGEd%wJfB)j$`)#7`5bv{)_tjS+Oje4M>+PrsXL>Oj=7C8?MS<0S zM4SJaJbwt=i}_y4pT;z(Dk&<`ZN|AgiS)5N!^co>S!>0@)~0CXo*GptPnZw#(Dc3j zEQA^=>0M>twKm0pFTa%6l?UM!fX{g0zb>+>vPtlSZNrO1(Skc7@&GFb&KX^{&4zTG z#OA6QL}`eDEGZX^~dukB4c}a z<^>DWR;X>G?~yf+EvVIIYIT>_?7I+DHr768{5&kQOAn7GmkXw}9SPn$Xp|icT9Lx4 z;sPm0DV4KGID8}f3uzbXmBu@JEp;H`{1G8NHP|ySNd_fPPJ6P)V2bbh_GaM1{e@ok zuQb~Va=+E@Uazi=4W-RD*T*fhY;tV`*1H26oIBKQvApvb>|`exXAG9MA?(Cw)5Iw2!(OH%YRBYbBZ4f_pgD7?I0pgWuk z_iCy9u3{(jK1eoY2=DM=8Cw*!oG65o*myNwy+ooOhUFVgOrc#oP3@~y9|d*o79Jh8 z3a88EHn;K|D&1ft?#Jvdyb(M;Tp!z2+X;!Uo8UK_)G(Xy^94*T2||QCIiLn9&7QeW z_Dda|yLrI(zcj-I3WZ%7z(v)%fF#`!j+!eN?yap@eN1!|gC2g25-*{U297kW?!q@&#b(z5zO}~!fTx-M0NB;$UW0gHVkJ{0|1bn#`}b1wqJj-S zdD7CZ!zjMnOmy7=OE94HXMdT}mtu4P{$BsYMqP)f+<=)amy0b$$kts@D1|P;0P#gm z|CpI>my~!+B8dn*Gg+>C#_OW4u!xdkNkek2Q;J;%SV~nus-}l166r`&!&2(@R^MBv zuLO(g2K9qf6DCD3kSXw0{wWCRLtbBgNdpdR9rd_e-n|&{bi1MS#I@*-`49N#ZW7TR zcxDxI=R;%AevA?8)W)gdRBW&~%U#`W!QsHh#jz=E)?=^k%Vf9{6U47Iw$qIX#IDz_ zoAno$GacKE!!Y@(DYOYiywZ9Id4&(*E~#B|H^c=F5F`@%qe`$?3QlB%x*v}P?79OI zUZ^|a_Gk6R2V+V0SI1^)sfkuaNx#69sHe<0$CC?i5h*p2lE$T8iK}ar+VE98_#(3CkElS_S9EG-lCc1JkhT z*a!8c(0WGjkw%VGDnv_ZjPG@6Z5rRPBH3XZ_hvH(!l^!(Q-86KIqY#2`gfD!7lD?%0N
`|Y>FGiHC3u8>X)CS6tHA23Bm>secq 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 v_8R Date: Thu, 14 Sep 2017 17:39:44 +0530 Subject: [PATCH 14/21] Webhook doc correction --- frappe/docs/user/en/guides/integration/webhooks.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/docs/user/en/guides/integration/webhooks.md b/frappe/docs/user/en/guides/integration/webhooks.md index 30d90fdd37..ff7df4015f 100644 --- a/frappe/docs/user/en/guides/integration/webhooks.md +++ b/frappe/docs/user/en/guides/integration/webhooks.md @@ -29,7 +29,7 @@ e.g. Webhook Note: if no headers or data is present, request will be made without any header or body -Example request sent by frappe server on `Quotation` - `on_update` to https://httpbin.org/post: +Example response of request sent by frappe server on `Quotation` - `on_update` to https://httpbin.org/post: ``` { From cbfbaed73e4717bc1668f4877e9602c5b5bee52b Mon Sep 17 00:00:00 2001 From: Revant Nandgaonkar Date: Thu, 14 Sep 2017 19:25:38 +0530 Subject: [PATCH 15/21] [Fix] Codacy https://www.codacy.com/app/frappe/frappe/pullRequest?prid=900990 --- frappe/integrations/doctype/webhook/webhook.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frappe/integrations/doctype/webhook/webhook.js b/frappe/integrations/doctype/webhook/webhook.js index bff8591592..3fc78e0d2c 100644 --- a/frappe/integrations/doctype/webhook/webhook.js +++ b/frappe/integrations/doctype/webhook/webhook.js @@ -3,7 +3,7 @@ frappe.webhook = { set_fieldname_select: function(frm) { - doc = frm.doc; + 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) { @@ -18,7 +18,7 @@ frappe.webhook = { return null; } }); - fields.unshift({"label":"Name (Doc Name)","value":"name"}) + fields.unshift({"label":"Name (Doc Name)","value":"name"}); frappe.meta.get_docfield("Webhook Data", "fieldname", frm.doc.name).options = [""].concat(fields); }); } From 421b43680311ba004443839f7707001361f510dd Mon Sep 17 00:00:00 2001 From: Revant Nandgaonkar Date: Mon, 18 Sep 2017 16:32:33 +0530 Subject: [PATCH 16/21] move run webhook to Document from hooks --- frappe/hooks.py | 17 ++----- .../integrations/doctype/webhook/webhook.py | 23 +++++++++ frappe/model/document.py | 47 +++++++++++++++++++ 3 files changed, 73 insertions(+), 14 deletions(-) diff --git a/frappe/hooks.py b/frappe/hooks.py index 9fdca1a87a..f5d98f0421 100755 --- a/frappe/hooks.py +++ b/frappe/hooks.py @@ -108,25 +108,14 @@ doc_events = { "*": { "on_update": [ "frappe.desk.notifications.clear_doctype_notifications", - "frappe.core.doctype.communication.feed.update_feed", - "frappe.integrations.webhooks.doc_event_webhook" + "frappe.core.doctype.communication.feed.update_feed" ], "after_rename": "frappe.desk.notifications.clear_doctype_notifications", "on_cancel": [ "frappe.desk.notifications.clear_doctype_notifications", - "frappe.integrations.webhooks.doc_event_webhook" ], - "on_trash": [ - "frappe.desk.notifications.clear_doctype_notifications", - "frappe.integrations.webhooks.doc_event_webhook" - ], - "on_change": [ - "frappe.core.doctype.feedback_trigger.feedback_trigger.trigger_feedback_request", - "frappe.integrations.webhooks.doc_event_webhook" - ], - "after_insert": "frappe.integrations.webhooks.doc_event_webhook", - "on_submit": "frappe.integrations.webhooks.doc_event_webhook", - "on_update_after_submit": "frappe.integrations.webhooks.doc_event_webhook" + "on_trash": "frappe.desk.notifications.clear_doctype_notifications", + "on_change": "frappe.core.doctype.feedback_trigger.feedback_trigger.trigger_feedback_request" }, "Email Group Member": { "validate": "frappe.email.doctype.email_group.email_group.restrict_email_group" diff --git a/frappe/integrations/doctype/webhook/webhook.py b/frappe/integrations/doctype/webhook/webhook.py index 5f74b22ac4..10b07adcba 100644 --- a/frappe/integrations/doctype/webhook/webhook.py +++ b/frappe/integrations/doctype/webhook/webhook.py @@ -4,6 +4,7 @@ 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 @@ -35,3 +36,25 @@ class Webhook(Document): if len(webhook_data)!= len(set(webhook_data)): frappe.throw(_("Same Field is entered more than once")) + def request(self, doc): + headers = {} + data = {} + if self.webhook_headers: + for h in self.webhook_headers: + if h.get("key") and h.get("value"): + headers[h.get("key")] = h.get("value") + if self.webhook_data: + for k, v in doc.as_dict().items(): + for w in self.webhook_data: + if k == w.fieldname: + data[w.key] = v + r = requests.post(self.request_url, data=json.dumps(data), headers=headers, timeout=5) + frappe.logger().debug({"webhook_success":r.text}) + +def evaluate_webhook(doc, webhook): + webhook = frappe.get_doc("Webhook", webhook.get("name")) + try: + webhook.request(doc) + except Exception as e: + frappe.log_error(message=frappe.get_traceback(), title=e) + frappe.throw(_("Error in Webhook")) diff --git a/frappe/model/document.py b/frappe/model/document.py index 033c807418..d43f487e8f 100644 --- a/frappe/model/document.py +++ b/frappe/model/document.py @@ -169,6 +169,7 @@ class Document(BaseDocument): return self.flags.email_alerts_executed = [] + self.flags.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 = [] + self.flags.webhooks_executed = [] if ignore_permissions!=None: self.flags.ignore_permissions = ignore_permissions @@ -671,6 +673,7 @@ class Document(BaseDocument): out = Document.hook(fn)(self, *args, **kwargs) self.run_email_alerts(method) + self.run_webhooks(method) return out @@ -723,6 +726,50 @@ class Document(BaseDocument): elif alert.event=='Method' and method == alert.method: _evaluate_alert(alert) + def run_webhooks(self, method): + '''Run webhooks for this method''' + if frappe.flags.in_import or frappe.flags.in_patch or frappe.flags.in_install: + return + + if self.flags.webhooks_executed==None: + self.flags.webhooks_executed = [] + + from frappe.integrations.doctype.webhook.webhook import evaluate_webhook + + if self.flags.webhooks == None: + webhooks = frappe.cache().hget('webhooks', self.doctype) + if webhooks==None: + webhooks = frappe.get_all('Webhook', + fields=["name", "webhook_docevent", "webhook_doctype"]) + frappe.cache().hset('webhooks', self.doctype, webhooks) + self.flags.webhooks = webhooks + + if not self.flags.webhooks: + return + + def _evaluate_webhook(webhook): + if not webhook.name in self.flags.webhooks_executed: + evaluate_webhook(self, webhook) + self.flags.webhooks_executed.append(webhook.name) + + event_map = { + "on_update": "on_update", + "after_insert": "after_insert", + "on_submit": "on_submit", + "on_cancel": "on_cancel", + "on_trash": "on_trash" + } + + if not self.flags.in_insert: + # value change is not applicable in insert + event_map['on_change'] = 'on_change' + event_map['before_update_after_submit'] = 'before_update_after_submit' + + for webhook in self.flags.webhooks: + event = event_map.get(method, None) + if event and webhook.webhook_docevent == event: + _evaluate_webhook(webhook) + @staticmethod def whitelist(f): f.whitelisted = True From fadb45e369dcdb9697fb63fdd8a6c7d77a8272bc Mon Sep 17 00:00:00 2001 From: Revant Nandgaonkar Date: Mon, 18 Sep 2017 17:10:53 +0530 Subject: [PATCH 17/21] enqueue webhook request --- .../integrations/doctype/webhook/webhook.py | 8 +++-- frappe/integrations/webhooks.py | 33 ------------------- 2 files changed, 5 insertions(+), 36 deletions(-) delete mode 100644 frappe/integrations/webhooks.py diff --git a/frappe/integrations/doctype/webhook/webhook.py b/frappe/integrations/doctype/webhook/webhook.py index 10b07adcba..1fe9161f51 100644 --- a/frappe/integrations/doctype/webhook/webhook.py +++ b/frappe/integrations/doctype/webhook/webhook.py @@ -51,10 +51,12 @@ class Webhook(Document): r = requests.post(self.request_url, data=json.dumps(data), headers=headers, timeout=5) frappe.logger().debug({"webhook_success":r.text}) -def evaluate_webhook(doc, webhook): +def enqueue_webhook(doc, webhook): webhook = frappe.get_doc("Webhook", webhook.get("name")) try: webhook.request(doc) except Exception as e: - frappe.log_error(message=frappe.get_traceback(), title=e) - frappe.throw(_("Error in Webhook")) + frappe.throw(_("Error in Webhook"), exc=e) + +def evaluate_webhook(doc, webhook): + frappe.enqueue('frappe.integrations.doctype.webhook.webhook.enqueue_webhook', doc=doc, webhook=webhook) diff --git a/frappe/integrations/webhooks.py b/frappe/integrations/webhooks.py deleted file mode 100644 index 466a0f0470..0000000000 --- a/frappe/integrations/webhooks.py +++ /dev/null @@ -1,33 +0,0 @@ -# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# MIT License. See license.txt - -from __future__ import unicode_literals -import frappe, requests, json -from frappe import _ - -# Doc Events Webhook -def doc_event_webhook(doc, method=None, *args, **kwargs): - headers = {} - data = {} - filters = { - "webhook_doctype": doc.get("doctype"), - "webhook_docevent": method - } - webhooks = frappe.get_all("Webhook", filters=filters) - webhook = frappe.get_doc("Webhook", webhooks[0].get("name")) if webhooks and len(webhooks) > 0 else None - if webhook: - 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 k, v in doc.as_dict().items(): - for w in webhook.webhook_data: - if k == w.fieldname: - data[w.key] = v - try: - r = requests.post(webhook.request_url, data=json.dumps(data), headers=headers, timeout=5) - frappe.logger().debug({"webhook_success":r.text, "webhook": webhook.as_json()}) - except Exception as e: - frappe.logger().debug({"webhook_error":r.text, "webhook": webhook.as_json()}) - frappe.throw(_("Unable to make request"), exc=e) From e7459c7e83ad5c5c68b2c458bcac4474194f217f Mon Sep 17 00:00:00 2001 From: Revant Nandgaonkar Date: Tue, 19 Sep 2017 16:40:17 +0530 Subject: [PATCH 18/21] Clean up --- .../integrations/doctype/webhook/webhook.py | 2 +- frappe/model/document.py | 24 +++++++------------ 2 files changed, 10 insertions(+), 16 deletions(-) diff --git a/frappe/integrations/doctype/webhook/webhook.py b/frappe/integrations/doctype/webhook/webhook.py index 1fe9161f51..8bbcbdc5ad 100644 --- a/frappe/integrations/doctype/webhook/webhook.py +++ b/frappe/integrations/doctype/webhook/webhook.py @@ -58,5 +58,5 @@ def enqueue_webhook(doc, webhook): except Exception as e: frappe.throw(_("Error in Webhook"), exc=e) -def evaluate_webhook(doc, webhook): +def webhook_request(doc, webhook): frappe.enqueue('frappe.integrations.doctype.webhook.webhook.enqueue_webhook', doc=doc, webhook=webhook) diff --git a/frappe/model/document.py b/frappe/model/document.py index d43f487e8f..2e83f085f3 100644 --- a/frappe/model/document.py +++ b/frappe/model/document.py @@ -734,41 +734,35 @@ class Document(BaseDocument): if self.flags.webhooks_executed==None: self.flags.webhooks_executed = [] - from frappe.integrations.doctype.webhook.webhook import evaluate_webhook + from frappe.integrations.doctype.webhook.webhook import webhook_request if self.flags.webhooks == None: webhooks = frappe.cache().hget('webhooks', self.doctype) if webhooks==None: webhooks = frappe.get_all('Webhook', - fields=["name", "webhook_docevent", "webhook_doctype"]) + fields=["name", "webhook_docevent"]) frappe.cache().hset('webhooks', self.doctype, webhooks) self.flags.webhooks = webhooks if not self.flags.webhooks: return - def _evaluate_webhook(webhook): + def _webhook_request(webhook): if not webhook.name in self.flags.webhooks_executed: - evaluate_webhook(self, webhook) + webhook_request(self, webhook) self.flags.webhooks_executed.append(webhook.name) - event_map = { - "on_update": "on_update", - "after_insert": "after_insert", - "on_submit": "on_submit", - "on_cancel": "on_cancel", - "on_trash": "on_trash" - } + event_list = ["on_update", "after_insert", "on_submit", "on_cancel", "on_trash"] if not self.flags.in_insert: # value change is not applicable in insert - event_map['on_change'] = 'on_change' - event_map['before_update_after_submit'] = 'before_update_after_submit' + event_list.append('on_change') + event_list.append('before_update_after_submit') for webhook in self.flags.webhooks: - event = event_map.get(method, None) + event = method if method in event_list else None if event and webhook.webhook_docevent == event: - _evaluate_webhook(webhook) + _webhook_request(webhook) @staticmethod def whitelist(f): From 1f9b1e40e36134210acb71213d220eb0dfad38c7 Mon Sep 17 00:00:00 2001 From: Revant Nandgaonkar Date: Wed, 20 Sep 2017 16:33:28 +0530 Subject: [PATCH 19/21] Moved run_webhooks to webhook.py --- .../integrations/doctype/webhook/webhook.py | 38 +++++++++++++++- frappe/model/document.py | 45 ++----------------- 2 files changed, 40 insertions(+), 43 deletions(-) diff --git a/frappe/integrations/doctype/webhook/webhook.py b/frappe/integrations/doctype/webhook/webhook.py index 8bbcbdc5ad..6a95085c24 100644 --- a/frappe/integrations/doctype/webhook/webhook.py +++ b/frappe/integrations/doctype/webhook/webhook.py @@ -49,6 +49,7 @@ class Webhook(Document): if k == w.fieldname: data[w.key] = v r = requests.post(self.request_url, data=json.dumps(data), headers=headers, timeout=5) + r.raise_for_status() frappe.logger().debug({"webhook_success":r.text}) def enqueue_webhook(doc, webhook): @@ -58,5 +59,38 @@ def enqueue_webhook(doc, webhook): except Exception as e: frappe.throw(_("Error in Webhook"), exc=e) -def webhook_request(doc, webhook): - frappe.enqueue('frappe.integrations.doctype.webhook.webhook.enqueue_webhook', doc=doc, webhook=webhook) +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"]) + 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: + _webhook_request(webhook) diff --git a/frappe/model/document.py b/frappe/model/document.py index 2e83f085f3..5e4bfc01c4 100644 --- a/frappe/model/document.py +++ b/frappe/model/document.py @@ -169,7 +169,7 @@ class Document(BaseDocument): return self.flags.email_alerts_executed = [] - self.flags.webhooks_executed = [] + setattr(frappe.local, 'webhooks_executed', []) if ignore_permissions!=None: self.flags.ignore_permissions = ignore_permissions @@ -243,7 +243,7 @@ class Document(BaseDocument): return self.flags.email_alerts_executed = [] - self.flags.webhooks_executed = [] + setattr(frappe.local, 'webhooks_executed', []) if ignore_permissions!=None: self.flags.ignore_permissions = ignore_permissions @@ -673,7 +673,8 @@ class Document(BaseDocument): out = Document.hook(fn)(self, *args, **kwargs) self.run_email_alerts(method) - self.run_webhooks(method) + from frappe.integrations.doctype.webhook.webhook import run_webhooks + run_webhooks(self, method) return out @@ -726,44 +727,6 @@ class Document(BaseDocument): elif alert.event=='Method' and method == alert.method: _evaluate_alert(alert) - def run_webhooks(self, method): - '''Run webhooks for this method''' - if frappe.flags.in_import or frappe.flags.in_patch or frappe.flags.in_install: - return - - if self.flags.webhooks_executed==None: - self.flags.webhooks_executed = [] - - from frappe.integrations.doctype.webhook.webhook import webhook_request - - if self.flags.webhooks == None: - webhooks = frappe.cache().hget('webhooks', self.doctype) - if webhooks==None: - webhooks = frappe.get_all('Webhook', - fields=["name", "webhook_docevent"]) - frappe.cache().hset('webhooks', self.doctype, webhooks) - self.flags.webhooks = webhooks - - if not self.flags.webhooks: - return - - def _webhook_request(webhook): - if not webhook.name in self.flags.webhooks_executed: - webhook_request(self, webhook) - self.flags.webhooks_executed.append(webhook.name) - - event_list = ["on_update", "after_insert", "on_submit", "on_cancel", "on_trash"] - - if not self.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 self.flags.webhooks: - event = method if method in event_list else None - if event and webhook.webhook_docevent == event: - _webhook_request(webhook) - @staticmethod def whitelist(f): f.whitelisted = True From 2d148c804e65d4e5405dde1666801c83023cb227 Mon Sep 17 00:00:00 2001 From: Revant Nandgaonkar Date: Wed, 20 Sep 2017 18:34:28 +0530 Subject: [PATCH 20/21] Webhook retry 3 times --- .../integrations/doctype/webhook/webhook.py | 49 +++++++++++-------- 1 file changed, 28 insertions(+), 21 deletions(-) diff --git a/frappe/integrations/doctype/webhook/webhook.py b/frappe/integrations/doctype/webhook/webhook.py index 6a95085c24..29b6bde639 100644 --- a/frappe/integrations/doctype/webhook/webhook.py +++ b/frappe/integrations/doctype/webhook/webhook.py @@ -8,6 +8,7 @@ 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): @@ -36,28 +37,34 @@ class Webhook(Document): if len(webhook_data)!= len(set(webhook_data)): frappe.throw(_("Same Field is entered more than once")) - def request(self, doc): - headers = {} - data = {} - if self.webhook_headers: - for h in self.webhook_headers: - if h.get("key") and h.get("value"): - headers[h.get("key")] = h.get("value") - if self.webhook_data: - for k, v in doc.as_dict().items(): - for w in self.webhook_data: - if k == w.fieldname: - data[w.key] = v - r = requests.post(self.request_url, data=json.dumps(data), headers=headers, timeout=5) - r.raise_for_status() - frappe.logger().debug({"webhook_success":r.text}) def enqueue_webhook(doc, webhook): webhook = frappe.get_doc("Webhook", webhook.get("name")) - try: - webhook.request(doc) - except Exception as e: - frappe.throw(_("Error in Webhook"), exc=e) + 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 + error_status = [] + 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''' @@ -71,7 +78,7 @@ def run_webhooks(doc, method): webhooks = frappe.cache().hget('webhooks', doc.doctype) if webhooks==None: webhooks = frappe.get_all('Webhook', - fields=["name", "webhook_docevent"]) + fields=["name", "webhook_docevent", "webhook_doctype"]) frappe.cache().hset('webhooks', doc.doctype, webhooks) doc.flags.webhooks = webhooks @@ -92,5 +99,5 @@ def run_webhooks(doc, method): for webhook in doc.flags.webhooks: event = method if method in event_list else None - if event and webhook.webhook_docevent == event: + if event and webhook.webhook_docevent == event and webhook.webhook_doctype == doc.doctype: _webhook_request(webhook) From 45f2ff43addfc374034f2b911ba29f998b37a122 Mon Sep 17 00:00:00 2001 From: Revant Nandgaonkar Date: Wed, 20 Sep 2017 18:52:50 +0530 Subject: [PATCH 21/21] [Fix] Codacy https://www.codacy.com/app/frappe/frappe/pullRequest?prid=900990 --- frappe/integrations/doctype/webhook/webhook.py | 1 - 1 file changed, 1 deletion(-) diff --git a/frappe/integrations/doctype/webhook/webhook.py b/frappe/integrations/doctype/webhook/webhook.py index 29b6bde639..26ecc36413 100644 --- a/frappe/integrations/doctype/webhook/webhook.py +++ b/frappe/integrations/doctype/webhook/webhook.py @@ -51,7 +51,6 @@ def enqueue_webhook(doc, webhook): for k, v in doc.as_dict().items(): if k == w.fieldname: data[w.key] = v - error_status = [] for i in range(3): try: r = requests.post(webhook.request_url, data=json.dumps(data), headers=headers, timeout=5)