From c0d210206fb37624888d6f7b8aba92a6e7366063 Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Thu, 2 Apr 2020 18:12:43 +0530 Subject: [PATCH 001/191] feat: paytm integration --- .../doctype/paytm_settings/__init__.py | 0 .../doctype/paytm_settings/paytm_settings.js | 8 ++++ .../paytm_settings/paytm_settings.json | 46 +++++++++++++++++++ .../doctype/paytm_settings/paytm_settings.py | 10 ++++ .../paytm_settings/test_paytm_settings.py | 10 ++++ 5 files changed, 74 insertions(+) create mode 100644 frappe/integrations/doctype/paytm_settings/__init__.py create mode 100644 frappe/integrations/doctype/paytm_settings/paytm_settings.js create mode 100644 frappe/integrations/doctype/paytm_settings/paytm_settings.json create mode 100644 frappe/integrations/doctype/paytm_settings/paytm_settings.py create mode 100644 frappe/integrations/doctype/paytm_settings/test_paytm_settings.py diff --git a/frappe/integrations/doctype/paytm_settings/__init__.py b/frappe/integrations/doctype/paytm_settings/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/frappe/integrations/doctype/paytm_settings/paytm_settings.js b/frappe/integrations/doctype/paytm_settings/paytm_settings.js new file mode 100644 index 0000000000..d51a6e4a25 --- /dev/null +++ b/frappe/integrations/doctype/paytm_settings/paytm_settings.js @@ -0,0 +1,8 @@ +// Copyright (c) 2020, Frappe Technologies and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Paytm Settings', { + // refresh: function(frm) { + + // } +}); diff --git a/frappe/integrations/doctype/paytm_settings/paytm_settings.json b/frappe/integrations/doctype/paytm_settings/paytm_settings.json new file mode 100644 index 0000000000..e57c8a192c --- /dev/null +++ b/frappe/integrations/doctype/paytm_settings/paytm_settings.json @@ -0,0 +1,46 @@ +{ + "actions": [], + "creation": "2020-04-02 00:11:22.846697", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "merchant_id", + "merchant_key" + ], + "fields": [ + { + "fieldname": "merchant_id", + "fieldtype": "Data", + "label": "Merchant ID" + }, + { + "fieldname": "merchant_key", + "fieldtype": "Data", + "hidden": 1, + "label": "Merchant Key" + } + ], + "issingle": 1, + "links": [], + "modified": "2020-04-02 00:11:22.846697", + "modified_by": "Administrator", + "module": "Integrations", + "name": "Paytm Settings", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "print": 1, + "read": 1, + "role": "System Manager", + "share": 1, + "write": 1 + } + ], + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/frappe/integrations/doctype/paytm_settings/paytm_settings.py b/frappe/integrations/doctype/paytm_settings/paytm_settings.py new file mode 100644 index 0000000000..74d51d1dcc --- /dev/null +++ b/frappe/integrations/doctype/paytm_settings/paytm_settings.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2020, 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 PaytmSettings(Document): + pass diff --git a/frappe/integrations/doctype/paytm_settings/test_paytm_settings.py b/frappe/integrations/doctype/paytm_settings/test_paytm_settings.py new file mode 100644 index 0000000000..77a16c82ae --- /dev/null +++ b/frappe/integrations/doctype/paytm_settings/test_paytm_settings.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2020, Frappe Technologies and Contributors +# See license.txt +from __future__ import unicode_literals + +# import frappe +import unittest + +class TestPaytmSettings(unittest.TestCase): + pass From 8831bc6531bad3925526ea0d4e9f345cfbd89610 Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Wed, 8 Apr 2020 18:49:48 +0530 Subject: [PATCH 002/191] feat: blink checkout --- .../doctype/paytm_settings/checksum.py | 143 ++++++++++++++++++ .../includes/integrations/paytm_checkout.js | 42 +++++ 2 files changed, 185 insertions(+) create mode 100644 frappe/integrations/doctype/paytm_settings/checksum.py create mode 100644 frappe/templates/includes/integrations/paytm_checkout.js diff --git a/frappe/integrations/doctype/paytm_settings/checksum.py b/frappe/integrations/doctype/paytm_settings/checksum.py new file mode 100644 index 0000000000..95fb7dc164 --- /dev/null +++ b/frappe/integrations/doctype/paytm_settings/checksum.py @@ -0,0 +1,143 @@ +import base64 +import string +import random +import hashlib + +from Crypto.Cipher import AES + + +IV = "@@@@&&&&####$$$$" +BLOCK_SIZE = 16 + + +def generate_checksum(param_dict, merchant_key, salt=None): + params_string = __get_param_string__(param_dict) + salt = salt if salt else __id_generator__(4) + final_string = '%s|%s' % (params_string, salt) + + hasher = hashlib.sha256(final_string.encode()) + hash_string = hasher.hexdigest() + + hash_string += salt + + return __encode__(hash_string, IV, merchant_key) + + +def generate_refund_checksum(param_dict, merchant_key, salt=None): + for i in param_dict: + if("|" in param_dict[i]): + param_dict = {} + exit() + params_string = __get_param_string__(param_dict) + salt = salt if salt else __id_generator__(4) + final_string = '%s|%s' % (params_string, salt) + + hasher = hashlib.sha256(final_string.encode()) + hash_string = hasher.hexdigest() + + hash_string += salt + + return __encode__(hash_string, IV, merchant_key) + + +def generate_checksum_by_str(param_str, merchant_key, salt=None): + params_string = param_str + salt = salt if salt else __id_generator__(4) + final_string = '%s|%s' % (params_string, salt) + + hasher = hashlib.sha256(final_string.encode()) + hash_string = hasher.hexdigest() + + hash_string += salt + + return __encode__(hash_string, IV, merchant_key) + + +def verify_checksum(param_dict, merchant_key, checksum): + # Remove checksum + if 'CHECKSUMHASH' in param_dict: + param_dict.pop('CHECKSUMHASH') + + # Get salt + paytm_hash = __decode__(checksum, IV, merchant_key) + salt = paytm_hash[-4:] + calculated_checksum = generate_checksum( + param_dict, merchant_key, salt=salt) + return calculated_checksum == checksum + + +def verify_checksum_by_str(param_str, merchant_key, checksum): + # Remove checksum + # if 'CHECKSUMHASH' in param_dict: + # param_dict.pop('CHECKSUMHASH') + + # Get salt + paytm_hash = __decode__(checksum, IV, merchant_key) + salt = paytm_hash[-4:] + calculated_checksum = generate_checksum_by_str( + param_str, merchant_key, salt=salt) + return calculated_checksum == checksum + + +def __id_generator__(size=6, chars=string.ascii_uppercase + string.digits + string.ascii_lowercase): + return ''.join(random.choice(chars) for _ in range(size)) + + +def __get_param_string__(params): + params_string = [] + for key in sorted(params.keys()): + if("REFUND" in params[key] or "|" in params[key]): + respons_dict = {} + exit() + value = params[key] + params_string.append('' if value == 'null' else str(value)) + return '|'.join(params_string) + + +def __pad__(s): return s + (BLOCK_SIZE - len(s) % + BLOCK_SIZE) * chr(BLOCK_SIZE - len(s) % BLOCK_SIZE) + + +def __unpad__(s): return s[0:-ord(s[-1])] + + +def __encode__(to_encode, iv, key): + # Pad + to_encode = __pad__(to_encode) + # Encrypt + c = AES.new(key, AES.MODE_CBC, iv) + to_encode = c.encrypt(to_encode) + # Encode + to_encode = base64.b64encode(to_encode) + return to_encode.decode("UTF-8") + + +def __decode__(to_decode, iv, key): + # Decode + to_decode = base64.b64decode(to_decode) + # Decrypt + c = AES.new(key, AES.MODE_CBC, iv) + to_decode = c.decrypt(to_decode) + if type(to_decode) == bytes: + # convert bytes array to str. + to_decode = to_decode.decode() + # remove pad + return __unpad__(to_decode) + + +if __name__ == "__main__": + params = { + "MID": "mid", + "ORDER_ID": "order_id", + "CUST_ID": "cust_id", + "TXN_AMOUNT": "1", + "CHANNEL_ID": "WEB", + "INDUSTRY_TYPE_ID": "Retail", + "WEBSITE": "xxxxxxxxxxx" + } + + print(verify_checksum( + params, 'xxxxxxxxxxxxxxxx', + "CD5ndX8VVjlzjWbbYoAtKQIlvtXPypQYOg0Fi2AUYKXZA5XSHiRF0FDj7vQu66S8MHx9NaDZ/uYm3WBOWHf+sDQAmTyxqUipA7i1nILlxrk=")) + + # print(generate_checksum(params, "xxxxxxxxxxxxxxxx")) diff --git a/frappe/templates/includes/integrations/paytm_checkout.js b/frappe/templates/includes/integrations/paytm_checkout.js new file mode 100644 index 0000000000..4cf070b60b --- /dev/null +++ b/frappe/templates/includes/integrations/paytm_checkout.js @@ -0,0 +1,42 @@ +function onScriptLoad() { + console.log('inside on load') + var config = { + root: '', + flow: 'DEFAULT', + data: { + orderId: '{{ order_id}}', + token: '{{ token }}', + tokenType: 'TXN_TOKEN', + amount: '{{ amount }}' + }, + handler: { + notifyMerchant: function(eventName, data) { + // notify about the state of the payment page ( invalid token , session expire , cancel transaction) + console.log('notifyMerchant handler function called'); + console.log('eventName => ', eventName); + console.log('data => ', data); + }, + transactionStatus: function transactionStatus(paymentStatus) { + // provide information to merchant about the payment status. + console.log('transaction status handler function called'); + console.log('paymentStatus => ', paymentStatus); + } + } + }; + + $('.paytm-loading').addClass('hidden'); + if (window.Paytm && window.Paytm.CheckoutJS) { + window.Paytm.CheckoutJS.onLoad(function excecuteAfterCompleteLoad() { + // initialze configuration using init method + window.Paytm.CheckoutJS.init(config) + .then(function onSuccess() { + // after successfully updating configuration, invoke Blink Checkout + window.Paytm.CheckoutJS.invoke(); + }) + .catch(function onError(error) { + console.log('inside the error window') + console.log('error => ', error); + }); + }); + } +} From 72e88b344033d8512651770c1f170324fa66d08d Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Fri, 10 Apr 2020 12:50:54 +0530 Subject: [PATCH 003/191] feat: create transaction token --- .../doctype/paytm_settings/paytm_settings.py | 150 +++++++++++++++++- .../pages/integrations/paytm_checkout.html | 26 +++ .../pages/integrations/paytm_checkout.py | 105 ++++++++++++ 3 files changed, 279 insertions(+), 2 deletions(-) create mode 100644 frappe/templates/pages/integrations/paytm_checkout.html create mode 100644 frappe/templates/pages/integrations/paytm_checkout.py diff --git a/frappe/integrations/doctype/paytm_settings/paytm_settings.py b/frappe/integrations/doctype/paytm_settings/paytm_settings.py index 74d51d1dcc..1f9e508460 100644 --- a/frappe/integrations/doctype/paytm_settings/paytm_settings.py +++ b/frappe/integrations/doctype/paytm_settings/paytm_settings.py @@ -3,8 +3,154 @@ # For license information, please see license.txt from __future__ import unicode_literals -# import frappe +import frappe from frappe.model.document import Document +from frappe import _ +from six.moves.urllib.parse import urlencode +from frappe.utils import get_url, call_hook_method, cint, flt +from frappe.integrations.utils import make_get_request, make_post_request, create_request_log, create_payment_gateway +from frappe.utils import get_request_site_address + +import json +import requests class PaytmSettings(Document): - pass + supported_currencies = ["INR"] + + def validate(self): + create_payment_gateway('Paytm') + call_hook_method('payment_gateway_enabled', gateway='Paytm') + if not self.flags.ignore_mandatory: + self.validate_paytm_credentails() + + def validate_paytm_credentails(self): + if self.merchant_id and self.merchant_key: + pass + # header = {"Authorization": "Bearer {0}".format(self.get_password(fieldname="secret_key", raise_exception=False))} + # try: + # make_get_request(url="https://api.stripe.com/v1/charges", headers=header) + # except Exception: + # frappe.throw(_("Seems Publishable Key or Secret Key is wrong !!!")) + + def validate_transaction_currency(self, currency): + if currency not in self.supported_currencies: + frappe.throw(_("Please select another payment method. Stripe does not support transactions in currency '{0}'").format(currency)) + + def get_payment_url(self, **kwargs): + '''Return payment url with several params''' + # create unique order id by making it equal to the integration request + integration_request = create_request_log(kwargs, "Host", "Paytm") + kwargs.update(dict(order_id=integration_request.name)) + + return get_url("./integrations/paytm_checkout?{0}".format(urlencode(kwargs))) + + def create_request(self, data): + self.data = frappe._dict(data) + + try: + self.integration_request = create_request_log(self.data, "Host", "Paytm") + return self.generate_transaction_token() + + except Exception: + frappe.log_error(frappe.get_traceback()) + return{ + "redirect_to": frappe.redirect_to_message(_('Server Error'), _("It seems that there is an issue with the server's stripe configuration. In case of failure, the amount will get refunded to your account.")), + "status": 401 + } + + def generate_transaction_token(self): + import stripe + try: + charge = stripe.Charge.create(amount=cint(flt(self.data.amount)*100), currency=self.data.currency, source=self.data.stripe_token_id, description=self.data.description, receipt_email=self.data.payer_email) + + if charge.captured == True: + self.integration_request.db_set('status', 'Completed', update_modified=False) + self.flags.status_changed_to = "Completed" + + else: + frappe.log_error(charge.failure_message, 'Stripe Payment not completed') + + except Exception: + frappe.log_error(frappe.get_traceback()) + + return self.finalize_request() + + + def finalize_request(self): + redirect_to = self.data.get('redirect_to') or None + redirect_message = self.data.get('redirect_message') or None + status = self.integration_request.status + + if self.flags.status_changed_to == "Completed": + if self.data.reference_doctype and self.data.reference_docname: + custom_redirect_to = None + try: + custom_redirect_to = frappe.get_doc(self.data.reference_doctype, + self.data.reference_docname).run_method("on_payment_authorized", self.flags.status_changed_to) + except Exception: + frappe.log_error(frappe.get_traceback()) + + if custom_redirect_to: + redirect_to = custom_redirect_to + + redirect_url = 'payment-success' + + if self.redirect_url: + redirect_url = self.redirect_url + redirect_to = None + else: + redirect_url = 'payment-failed' + + if redirect_to: + redirect_url += '?' + urlencode({'redirect_to': redirect_to}) + if redirect_message: + redirect_url += '&' + urlencode({'redirect_message': redirect_message}) + + return { + "redirect_to": redirect_url, + "status": status + } + +def generate_transaction_token(payment_details, order_id, merchant_id, merchant_key): + + # initialize a dictionary + paytmParams = dict() + + redirect_uri = get_request_site_address(True) + "?cmd=frappe.templates.pages.integrations.paytm_checkout.get_transaction_status" + + # body parameters + paytmParams["body"] = get_paytm_params(payment_details, order_id, merchant_id, merchant_key) + + checksum = generate_checksum_by_str(json.dumps(paytmParams["body"]), merchant_key) + + paytmParams["head"] = { + "signature" : checksum + } + + post_data = json.dumps(paytmParams) + + url = "https://securegw-stage.paytm.in/theia/api/v1/initiateTransaction?mid={0}&orderId={1}".format(merchant_id, order_id) + + response = requests.post(url, data = post_data, headers = {"Content-type": "application/json"}).json() + return response['body'].get('txnToken') + +def get_paytm_params(payment_details, order_id, merchant_id, merchant_key): + return { + "requestType" : "Payment", + "mid" : merchant_id, + "websiteName" : "WEBSTAGING", + "orderId" : order_id, + "callbackUrl" : redirect_uri, + "txnAmount" : { + "value" : flt(payment_details['amount'], 2), + "currency" : "INR", + }, + "userInfo" : { + "custId" : payment_details['payer_email'], + }, + } + +def get_gateway_controller(doctype, docname): + reference_doc = frappe.get_doc(doctype, docname) + gateway_controller = frappe.db.get_value("Payment Gateway", reference_doc.payment_gateway, "gateway_controller") + return gateway_controller \ No newline at end of file diff --git a/frappe/templates/pages/integrations/paytm_checkout.html b/frappe/templates/pages/integrations/paytm_checkout.html new file mode 100644 index 0000000000..2aedf31d65 --- /dev/null +++ b/frappe/templates/pages/integrations/paytm_checkout.html @@ -0,0 +1,26 @@ +{% extends "templates/web.html" %} {% block title %} Payment {% endblock %} {%- +block header -%}{% endblock %} {% block script %} + + +{% endblock %} {%- block page_content -%} +
+

+ Loading Payment System + +

+ +{% endblock %} {% block style %} + +{% endblock %} diff --git a/frappe/templates/pages/integrations/paytm_checkout.py b/frappe/templates/pages/integrations/paytm_checkout.py new file mode 100644 index 0000000000..a17873bc80 --- /dev/null +++ b/frappe/templates/pages/integrations/paytm_checkout.py @@ -0,0 +1,105 @@ +# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors +# License: GNU General Public License v3. See license.txt +from __future__ import unicode_literals +import frappe +from frappe import _ +from frappe.utils import flt, cint +import json +from six import string_types +from frappe.integrations.doctype.paytm_settings.checksum import generate_checksum_by_str +from frappe.utils import get_request_site_address +import requests + +no_cache = 1 + +expected_keys = ('amount', 'title', 'description', 'reference_doctype', 'reference_docname', + 'payer_name', 'payer_email') + +def get_context(context): + context.no_cache = 1 + merchant_id, merchant_key = get_paytm_credentials() + + try: + doc = frappe.get_doc("Integration Request", frappe.form_dict['order_id']) + payment_details = json.loads(doc.data) + context.token = generate_transaction_token(payment_details, doc.name, merchant_id, merchant_key) + context.order_id = doc.name + + + for key in expected_keys: + context[key] = payment_details[key] + + context['amount'] = flt(context['amount'], 2) + context.host = 'https://securegw-stage.paytm.in' + context.mid = merchant_id + + except Exception as e: + frappe.log_error() + frappe.redirect_to_message(_('Invalid Token'), + _('Seems token you are using is invalid!'), + http_status_code=400, indicator_color='red') + + frappe.local.flags.redirect_location = frappe.local.response.location + raise frappe.Redirect + +def get_paytm_credentials(): + return frappe.db.get_value("Paytm Settings", None, ['merchant_id', 'merchant_key']) + +@frappe.whitelist(allow_guest=True) +def make_payment(paytm_payment_id, options, reference_doctype, reference_docname, token): + data = {} + + if isinstance(options, string_types): + data = json.loads(options) + + data.update({ + "paytm_payment_id": paytm_payment_id, + "reference_docname": reference_docname, + "reference_doctype": reference_doctype, + "token": token + }) + + data = frappe.get_doc("Paytm Settings").create_request(data) + frappe.db.commit() + return data + +def generate_transaction_token(payment_details, order_id, merchant_id, merchant_key): + + # initialize a dictionary + paytmParams = dict() + + redirect_uri = get_request_site_address(True) + "?cmd=frappe.templates.pages.integrations.paytm_checkout.get_transaction_status" + + # body parameters + paytmParams["body"] = { + "requestType" : "Payment", + "mid" : merchant_id, + "websiteName" : "WEBSTAGING", + "orderId" : order_id, + "callbackUrl" : redirect_uri, + "txnAmount" : { + "value" : flt(payment_details['amount'], 2), + "currency" : "INR", + }, + "userInfo" : { + "custId" : payment_details['payer_email'], + }, + } + + checksum = generate_checksum_by_str(json.dumps(paytmParams["body"]), merchant_key) + + paytmParams["head"] = { + "signature" : checksum + } + + print(paytmParams) + post_data = json.dumps(paytmParams) + + url = "https://securegw-stage.paytm.in/theia/api/v1/initiateTransaction?mid={0}&orderId={1}".format(merchant_id, order_id) + + response = requests.post(url, data = post_data, headers = {"Content-type": "application/json"}).json() + return response['body'].get('txnToken') + +@frappe.whitelist(allow_guest=True) +def get_transaction_status(): + print(vars(frappe.form_dict)) \ No newline at end of file From 3f6459edbb384d1529fe350bd93bd413c4b4f136 Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Thu, 16 Apr 2020 15:48:37 +0530 Subject: [PATCH 004/191] fix: paytm integration --- .pylintrc | 2 -- frappe/integrations/doctype/paytm_settings/paytm_settings.json | 3 +-- 2 files changed, 1 insertion(+), 4 deletions(-) delete mode 100644 .pylintrc diff --git a/.pylintrc b/.pylintrc deleted file mode 100644 index c11c0ab6a3..0000000000 --- a/.pylintrc +++ /dev/null @@ -1,2 +0,0 @@ -disable=access-member-before-definition -disable=no-member \ No newline at end of file diff --git a/frappe/integrations/doctype/paytm_settings/paytm_settings.json b/frappe/integrations/doctype/paytm_settings/paytm_settings.json index e57c8a192c..eb68f0ab79 100644 --- a/frappe/integrations/doctype/paytm_settings/paytm_settings.json +++ b/frappe/integrations/doctype/paytm_settings/paytm_settings.json @@ -17,13 +17,12 @@ { "fieldname": "merchant_key", "fieldtype": "Data", - "hidden": 1, "label": "Merchant Key" } ], "issingle": 1, "links": [], - "modified": "2020-04-02 00:11:22.846697", + "modified": "2020-04-07 19:57:24.126640", "modified_by": "Administrator", "module": "Integrations", "name": "Paytm Settings", From b0a34c602d9954f9b1d77fae918ce167a2ba799e Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Thu, 16 Apr 2020 15:51:11 +0530 Subject: [PATCH 005/191] fix: initiate payment via standard checkout --- .../pages/integrations/paytm_checkout.html | 47 ++++++----- .../pages/integrations/paytm_checkout.py | 78 ++----------------- 2 files changed, 33 insertions(+), 92 deletions(-) diff --git a/frappe/templates/pages/integrations/paytm_checkout.html b/frappe/templates/pages/integrations/paytm_checkout.html index 2aedf31d65..772e16ecb8 100644 --- a/frappe/templates/pages/integrations/paytm_checkout.html +++ b/frappe/templates/pages/integrations/paytm_checkout.html @@ -1,26 +1,35 @@ -{% extends "templates/web.html" %} {% block title %} Payment {% endblock %} {%- -block header -%}{% endblock %} {% block script %} - - -{% endblock %} {%- block page_content -%} -
-

- Loading Payment System - -

+{% extends "templates/web.html" %} -{% endblock %} {% block style %} +{% block title %} Payment {% endblock %} + +{%- block header -%} + + Merchant Checkout Page + +{% endblock %} + +{% block script %} + +{% endblock %} + +{%- block page_content -%} + +

Please do not refresh this page...

+
+ {% for name, value in payment_details.items() %} + + {% endfor %} +
+ +{% endblock %} + +{% block style %} -{% endblock %} +{% endblock %} \ No newline at end of file diff --git a/frappe/templates/pages/integrations/paytm_checkout.py b/frappe/templates/pages/integrations/paytm_checkout.py index a17873bc80..1c61d5cab4 100644 --- a/frappe/templates/pages/integrations/paytm_checkout.py +++ b/frappe/templates/pages/integrations/paytm_checkout.py @@ -6,8 +6,8 @@ from frappe import _ from frappe.utils import flt, cint import json from six import string_types -from frappe.integrations.doctype.paytm_settings.checksum import generate_checksum_by_str from frappe.utils import get_request_site_address +from frappe.integrations.doctype.paytm_settings.paytm_settings import get_paytm_params, get_paytm_config import requests no_cache = 1 @@ -17,21 +17,15 @@ expected_keys = ('amount', 'title', 'description', 'reference_doctype', 'referen def get_context(context): context.no_cache = 1 - merchant_id, merchant_key = get_paytm_credentials() + paytm_config = get_paytm_config() try: doc = frappe.get_doc("Integration Request", frappe.form_dict['order_id']) payment_details = json.loads(doc.data) - context.token = generate_transaction_token(payment_details, doc.name, merchant_id, merchant_key) - context.order_id = doc.name + context.payment_details = get_paytm_params(json.loads(doc.data), doc.name, paytm_config) - for key in expected_keys: - context[key] = payment_details[key] - - context['amount'] = flt(context['amount'], 2) - context.host = 'https://securegw-stage.paytm.in' - context.mid = merchant_id + context.url = paytm_config.url except Exception as e: frappe.log_error() @@ -40,66 +34,4 @@ def get_context(context): http_status_code=400, indicator_color='red') frappe.local.flags.redirect_location = frappe.local.response.location - raise frappe.Redirect - -def get_paytm_credentials(): - return frappe.db.get_value("Paytm Settings", None, ['merchant_id', 'merchant_key']) - -@frappe.whitelist(allow_guest=True) -def make_payment(paytm_payment_id, options, reference_doctype, reference_docname, token): - data = {} - - if isinstance(options, string_types): - data = json.loads(options) - - data.update({ - "paytm_payment_id": paytm_payment_id, - "reference_docname": reference_docname, - "reference_doctype": reference_doctype, - "token": token - }) - - data = frappe.get_doc("Paytm Settings").create_request(data) - frappe.db.commit() - return data - -def generate_transaction_token(payment_details, order_id, merchant_id, merchant_key): - - # initialize a dictionary - paytmParams = dict() - - redirect_uri = get_request_site_address(True) + "?cmd=frappe.templates.pages.integrations.paytm_checkout.get_transaction_status" - - # body parameters - paytmParams["body"] = { - "requestType" : "Payment", - "mid" : merchant_id, - "websiteName" : "WEBSTAGING", - "orderId" : order_id, - "callbackUrl" : redirect_uri, - "txnAmount" : { - "value" : flt(payment_details['amount'], 2), - "currency" : "INR", - }, - "userInfo" : { - "custId" : payment_details['payer_email'], - }, - } - - checksum = generate_checksum_by_str(json.dumps(paytmParams["body"]), merchant_key) - - paytmParams["head"] = { - "signature" : checksum - } - - print(paytmParams) - post_data = json.dumps(paytmParams) - - url = "https://securegw-stage.paytm.in/theia/api/v1/initiateTransaction?mid={0}&orderId={1}".format(merchant_id, order_id) - - response = requests.post(url, data = post_data, headers = {"Content-type": "application/json"}).json() - return response['body'].get('txnToken') - -@frappe.whitelist(allow_guest=True) -def get_transaction_status(): - print(vars(frappe.form_dict)) \ No newline at end of file + raise frappe.Redirect \ No newline at end of file From 9d71bdcd874451181c21b384be81f8e88f390500 Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Thu, 16 Apr 2020 18:53:56 +0530 Subject: [PATCH 006/191] feat: add paytm config, transaction status --- .../paytm_settings/paytm_settings.json | 40 +++- .../doctype/paytm_settings/paytm_settings.py | 206 ++++++++++-------- 2 files changed, 148 insertions(+), 98 deletions(-) diff --git a/frappe/integrations/doctype/paytm_settings/paytm_settings.json b/frappe/integrations/doctype/paytm_settings/paytm_settings.json index eb68f0ab79..0540dc4948 100644 --- a/frappe/integrations/doctype/paytm_settings/paytm_settings.json +++ b/frappe/integrations/doctype/paytm_settings/paytm_settings.json @@ -6,23 +6,55 @@ "engine": "InnoDB", "field_order": [ "merchant_id", - "merchant_key" + "merchant_key", + "staging", + "column_break_4", + "industry_type", + "website" ], "fields": [ { "fieldname": "merchant_id", "fieldtype": "Data", - "label": "Merchant ID" + "in_list_view": 1, + "label": "Merchant ID", + "reqd": 1 }, { "fieldname": "merchant_key", + "fieldtype": "Password", + "in_list_view": 1, + "label": "Merchant Key", + "reqd": 1 + }, + { + "default": "0", + "fieldname": "staging", + "fieldtype": "Check", + "label": "Staging" + }, + { + "depends_on": "eval: !doc.staging", + "fieldname": "industry_type", "fieldtype": "Data", - "label": "Merchant Key" + "label": "Industry Type", + "mandatory_depends_on": "eval: !doc.staging" + }, + { + "depends_on": "eval: !doc.staging", + "fieldname": "website", + "fieldtype": "Data", + "label": "Website", + "mandatory_depends_on": "eval: !doc.staging" + }, + { + "fieldname": "column_break_4", + "fieldtype": "Column Break" } ], "issingle": 1, "links": [], - "modified": "2020-04-07 19:57:24.126640", + "modified": "2020-04-16 14:16:19.687449", "modified_by": "Administrator", "module": "Integrations", "name": "Paytm Settings", diff --git a/frappe/integrations/doctype/paytm_settings/paytm_settings.py b/frappe/integrations/doctype/paytm_settings/paytm_settings.py index 1f9e508460..10282d34ea 100644 --- a/frappe/integrations/doctype/paytm_settings/paytm_settings.py +++ b/frappe/integrations/doctype/paytm_settings/paytm_settings.py @@ -3,16 +3,18 @@ # For license information, please see license.txt from __future__ import unicode_literals +import json +import requests +from six.moves.urllib.parse import urlencode + import frappe from frappe.model.document import Document from frappe import _ -from six.moves.urllib.parse import urlencode -from frappe.utils import get_url, call_hook_method, cint, flt +from frappe.utils import get_url, call_hook_method, cint, flt, cstr from frappe.integrations.utils import make_get_request, make_post_request, create_request_log, create_payment_gateway from frappe.utils import get_request_site_address - -import json -import requests +from frappe.integrations.doctype.paytm_settings.checksum import generate_checksum, verify_checksum +from frappe.utils.password import get_decrypted_password class PaytmSettings(Document): supported_currencies = ["INR"] @@ -44,110 +46,126 @@ class PaytmSettings(Document): return get_url("./integrations/paytm_checkout?{0}".format(urlencode(kwargs))) - def create_request(self, data): - self.data = frappe._dict(data) +def get_paytm_config(): + ''' Returns paytm config ''' - try: - self.integration_request = create_request_log(self.data, "Host", "Paytm") - return self.generate_transaction_token() + paytm_config = frappe.db.get_singles_dict('Paytm Settings') + paytm_config.update(dict(merchant_key=get_decrypted_password('Paytm Settings', 'Paytm Settings', 'merchant_key'))) - except Exception: - frappe.log_error(frappe.get_traceback()) - return{ - "redirect_to": frappe.redirect_to_message(_('Server Error'), _("It seems that there is an issue with the server's stripe configuration. In case of failure, the amount will get refunded to your account.")), - "status": 401 - } + if cint(paytm_config.staging): + paytm_config.update(dict( + website="WEBSTAGING", + url='https://securegw-stage.paytm.in/order/process', + transaction_status_url='https://securegw-stage.paytm.in/order/status', + industry_type_id='RETAIL' + )) + else: + paytm_config.update(dict( + url='https://securegw.paytm.in/order/process', + transaction_status_url='https://securegw.paytm.in/order/status', + )) + return paytm_config - def generate_transaction_token(self): - import stripe - try: - charge = stripe.Charge.create(amount=cint(flt(self.data.amount)*100), currency=self.data.currency, source=self.data.stripe_token_id, description=self.data.description, receipt_email=self.data.payer_email) - - if charge.captured == True: - self.integration_request.db_set('status', 'Completed', update_modified=False) - self.flags.status_changed_to = "Completed" - - else: - frappe.log_error(charge.failure_message, 'Stripe Payment not completed') - - except Exception: - frappe.log_error(frappe.get_traceback()) - - return self.finalize_request() - - - def finalize_request(self): - redirect_to = self.data.get('redirect_to') or None - redirect_message = self.data.get('redirect_message') or None - status = self.integration_request.status - - if self.flags.status_changed_to == "Completed": - if self.data.reference_doctype and self.data.reference_docname: - custom_redirect_to = None - try: - custom_redirect_to = frappe.get_doc(self.data.reference_doctype, - self.data.reference_docname).run_method("on_payment_authorized", self.flags.status_changed_to) - except Exception: - frappe.log_error(frappe.get_traceback()) - - if custom_redirect_to: - redirect_to = custom_redirect_to - - redirect_url = 'payment-success' - - if self.redirect_url: - redirect_url = self.redirect_url - redirect_to = None - else: - redirect_url = 'payment-failed' - - if redirect_to: - redirect_url += '?' + urlencode({'redirect_to': redirect_to}) - if redirect_message: - redirect_url += '&' + urlencode({'redirect_message': redirect_message}) - - return { - "redirect_to": redirect_url, - "status": status - } - -def generate_transaction_token(payment_details, order_id, merchant_id, merchant_key): +def get_paytm_params(payment_details, order_id, paytm_config): # initialize a dictionary - paytmParams = dict() + paytm_params = dict() + + # redirect_uri = get_request_site_address(True) + "/api/method/frappe.integrations.doctype.paytm_settings.paytm_settings.get_transaction_status" + redirect_uri = "http://cf9b2bb1.ngrok.io/api/method/frappe.integrations.doctype.paytm_settings.paytm_settings.get_transaction_status" - redirect_uri = get_request_site_address(True) + "?cmd=frappe.templates.pages.integrations.paytm_checkout.get_transaction_status" + paytm_params.update({ + "MID" : paytm_config.merchant_id, + "WEBSITE" : paytm_config.website, + "INDUSTRY_TYPE_ID" : paytm_config.industry_type_id, + "CHANNEL_ID" : "WEB", + "ORDER_ID" : order_id, + "CUST_ID" : payment_details['payer_email'], + "EMAIL" : payment_details['payer_email'], + "TXN_AMOUNT" : cstr(flt(payment_details['amount'], 2)), + "CALLBACK_URL" : redirect_uri, + }) - # body parameters - paytmParams["body"] = get_paytm_params(payment_details, order_id, merchant_id, merchant_key) + checksum = generate_checksum(paytm_params, paytm_config.merchant_key) - checksum = generate_checksum_by_str(json.dumps(paytmParams["body"]), merchant_key) + paytm_params.update({ + "CHECKSUMHASH" : checksum + }) - paytmParams["head"] = { - "signature" : checksum - } + return paytm_params - post_data = json.dumps(paytmParams) +@frappe.whitelist(allow_guest=True) +def verify_transaction(**kwargs): + '''Verify checksum for received data in the callback and then verify the transaction''' + paytm_config = get_paytm_config() + received_data = frappe._dict(kwargs) - url = "https://securegw-stage.paytm.in/theia/api/v1/initiateTransaction?mid={0}&orderId={1}".format(merchant_id, order_id) + print(received_data) + paytm_params = {} + for key, value in received_data.items(): + if key == 'CHECKSUMHASH': + paytm_checksum = value + else: + paytm_params[key] = value + + # Verify checksum + is_valid_checksum = verify_checksum(paytm_params, paytm_config.merchant_key, paytm_checksum) + + if is_valid_checksum and received_data['RESPCODE'] == '01': + verify_transaction_status(paytm_config, received_data['ORDERID']) + else: + frappe.respond_as_web_page("Payment Failed", + "Transaction failed to complete. Don't worry, in case of failure amount will get refunded to your account.", + http_status_code=401, indicator_color='red') + frappe.log_error("Order unsuccessful, received data:"+received_data, 'Paytm Payment Failed') + +def verify_transaction_status(paytm_config, order_id): + '''Verify transaction completion after checksum has been verified''' + paytm_params=dict( + MID=paytm_config.merchant_id, + ORDERID= order_id + ) + + checksum = generate_checksum(paytm_params, paytm_config.merchant_key) + paytm_params["CHECKSUMHASH"] = checksum + + post_data = json.dumps(paytm_params) + url = paytm_config.transaction_status_url response = requests.post(url, data = post_data, headers = {"Content-type": "application/json"}).json() - return response['body'].get('txnToken') + print('transaction status response') + print(response) + finalize_request(order_id, response) + +def finalize_request(order_id, response): + request = frappe.db.get_value('Integration Request', order_id) + redirect_to = request.data.get('redirect_to') or None + redirect_message = request.data.get('redirect_message') or None + + if request.flags.status_changed_to == "Completed": + if request.data.reference_doctype and request.data.reference_docname: + custom_redirect_to = None + try: + custom_redirect_to = frappe.get_doc(request.data.reference_doctype, + request.data.reference_docname).run_method("on_payment_authorized", request.flags.status_changed_to) + except Exception: + frappe.log_error(frappe.get_traceback()) + + if custom_redirect_to: + redirect_to = custom_redirect_to + + redirect_url = 'payment-success' + else: + redirect_url = 'payment-failed' + + if redirect_to: + redirect_url += '?' + urlencode({'redirect_to': redirect_to}) + if redirect_message: + redirect_url += '&' + urlencode({'redirect_message': redirect_message}) -def get_paytm_params(payment_details, order_id, merchant_id, merchant_key): return { - "requestType" : "Payment", - "mid" : merchant_id, - "websiteName" : "WEBSTAGING", - "orderId" : order_id, - "callbackUrl" : redirect_uri, - "txnAmount" : { - "value" : flt(payment_details['amount'], 2), - "currency" : "INR", - }, - "userInfo" : { - "custId" : payment_details['payer_email'], - }, + "redirect_to": redirect_url, + "status": status } def get_gateway_controller(doctype, docname): From cf5da1c787e1a24ef707d8541e26e5091421ee63 Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Fri, 17 Apr 2020 10:02:54 +0530 Subject: [PATCH 007/191] fix: check for transaction success in the final stage --- .../doctype/paytm_settings/paytm_settings.py | 13 ++++--------- .../templates/pages/integrations/paytm_checkout.py | 1 - 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/frappe/integrations/doctype/paytm_settings/paytm_settings.py b/frappe/integrations/doctype/paytm_settings/paytm_settings.py index 10282d34ea..3382aeb6ed 100644 --- a/frappe/integrations/doctype/paytm_settings/paytm_settings.py +++ b/frappe/integrations/doctype/paytm_settings/paytm_settings.py @@ -100,7 +100,6 @@ def verify_transaction(**kwargs): paytm_config = get_paytm_config() received_data = frappe._dict(kwargs) - print(received_data) paytm_params = {} for key, value in received_data.items(): if key == 'CHECKSUMHASH': @@ -133,16 +132,14 @@ def verify_transaction_status(paytm_config, order_id): url = paytm_config.transaction_status_url response = requests.post(url, data = post_data, headers = {"Content-type": "application/json"}).json() - print('transaction status response') - print(response) finalize_request(order_id, response) -def finalize_request(order_id, response): +def finalize_request(order_id, transaction_response): request = frappe.db.get_value('Integration Request', order_id) redirect_to = request.data.get('redirect_to') or None redirect_message = request.data.get('redirect_message') or None - if request.flags.status_changed_to == "Completed": + if transaction_response['STATUS'] == "TXN_SUCCESS": if request.data.reference_doctype and request.data.reference_docname: custom_redirect_to = None try: @@ -163,10 +160,8 @@ def finalize_request(order_id, response): if redirect_message: redirect_url += '&' + urlencode({'redirect_message': redirect_message}) - return { - "redirect_to": redirect_url, - "status": status - } + frappe.local.response['type'] = 'redirect' + frappe.local.response['location'] = 'redirect_url' def get_gateway_controller(doctype, docname): reference_doc = frappe.get_doc(doctype, docname) diff --git a/frappe/templates/pages/integrations/paytm_checkout.py b/frappe/templates/pages/integrations/paytm_checkout.py index 1c61d5cab4..1fa947a68f 100644 --- a/frappe/templates/pages/integrations/paytm_checkout.py +++ b/frappe/templates/pages/integrations/paytm_checkout.py @@ -21,7 +21,6 @@ def get_context(context): try: doc = frappe.get_doc("Integration Request", frappe.form_dict['order_id']) - payment_details = json.loads(doc.data) context.payment_details = get_paytm_params(json.loads(doc.data), doc.name, paytm_config) From 0cbf937763cf96ee2fd567aba550ebf0fe29b273 Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Fri, 17 Apr 2020 13:34:26 +0530 Subject: [PATCH 008/191] fix: change status of integration request on completion --- .../integrations/doctype/paytm_settings/paytm_settings.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/frappe/integrations/doctype/paytm_settings/paytm_settings.py b/frappe/integrations/doctype/paytm_settings/paytm_settings.py index 3382aeb6ed..39a9f42aa0 100644 --- a/frappe/integrations/doctype/paytm_settings/paytm_settings.py +++ b/frappe/integrations/doctype/paytm_settings/paytm_settings.py @@ -135,7 +135,7 @@ def verify_transaction_status(paytm_config, order_id): finalize_request(order_id, response) def finalize_request(order_id, transaction_response): - request = frappe.db.get_value('Integration Request', order_id) + request = frappe.get_doc('Integration Request', order_id) redirect_to = request.data.get('redirect_to') or None redirect_message = request.data.get('redirect_message') or None @@ -144,8 +144,10 @@ def finalize_request(order_id, transaction_response): custom_redirect_to = None try: custom_redirect_to = frappe.get_doc(request.data.reference_doctype, - request.data.reference_docname).run_method("on_payment_authorized", request.flags.status_changed_to) + request.data.reference_docname).run_method("on_payment_authorized", 'Completed') + request.db_set('status', 'Completed') except Exception: + request.db_set('status', 'Failed') frappe.log_error(frappe.get_traceback()) if custom_redirect_to: @@ -153,6 +155,7 @@ def finalize_request(order_id, transaction_response): redirect_url = 'payment-success' else: + request.db_set('status', 'Failed') redirect_url = 'payment-failed' if redirect_to: From 6eaefb35c7137352f19f2c7d153aa5c78d740bba Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Wed, 22 Apr 2020 11:40:08 +0530 Subject: [PATCH 009/191] fix: add finalize request to complete the transaction --- .../doctype/paytm_settings/paytm_settings.py | 33 ++++++++------- .../includes/integrations/paytm_checkout.js | 42 ------------------- .../pages/integrations/paytm_checkout.html | 4 +- .../pages/integrations/paytm_checkout.py | 3 -- .../website_theme/standard/standard.json | 20 --------- 5 files changed, 22 insertions(+), 80 deletions(-) delete mode 100644 frappe/templates/includes/integrations/paytm_checkout.js delete mode 100644 frappe/website/website_theme/standard/standard.json diff --git a/frappe/integrations/doctype/paytm_settings/paytm_settings.py b/frappe/integrations/doctype/paytm_settings/paytm_settings.py index 39a9f42aa0..394cd9c334 100644 --- a/frappe/integrations/doctype/paytm_settings/paytm_settings.py +++ b/frappe/integrations/doctype/paytm_settings/paytm_settings.py @@ -71,8 +71,8 @@ def get_paytm_params(payment_details, order_id, paytm_config): # initialize a dictionary paytm_params = dict() - # redirect_uri = get_request_site_address(True) + "/api/method/frappe.integrations.doctype.paytm_settings.paytm_settings.get_transaction_status" - redirect_uri = "http://cf9b2bb1.ngrok.io/api/method/frappe.integrations.doctype.paytm_settings.paytm_settings.get_transaction_status" + redirect_uri = get_request_site_address(True) + "/api/method/frappe.integrations.doctype.paytm_settings.paytm_settings.verify_transaction" + paytm_params.update({ "MID" : paytm_config.merchant_id, @@ -99,24 +99,28 @@ def verify_transaction(**kwargs): '''Verify checksum for received data in the callback and then verify the transaction''' paytm_config = get_paytm_config() received_data = frappe._dict(kwargs) + is_valid_checksum = False paytm_params = {} for key, value in received_data.items(): if key == 'CHECKSUMHASH': paytm_checksum = value + elif key == 'cmd': + continue else: paytm_params[key] = value - # Verify checksum - is_valid_checksum = verify_checksum(paytm_params, paytm_config.merchant_key, paytm_checksum) + if paytm_params and paytm_config and paytm_checksum: + # Verify checksum + is_valid_checksum = verify_checksum(paytm_params, paytm_config.merchant_key, paytm_checksum) if is_valid_checksum and received_data['RESPCODE'] == '01': verify_transaction_status(paytm_config, received_data['ORDERID']) else: frappe.respond_as_web_page("Payment Failed", - "Transaction failed to complete. Don't worry, in case of failure amount will get refunded to your account.", + "Transaction failed to complete. In case of any deductions, deducted amount will get refunded to your account.", http_status_code=401, indicator_color='red') - frappe.log_error("Order unsuccessful, received data:"+received_data, 'Paytm Payment Failed') + frappe.log_error("Order unsuccessful. Failed Response:"+cstr(received_data), 'Paytm Payment Failed') def verify_transaction_status(paytm_config, order_id): '''Verify transaction completion after checksum has been verified''' @@ -136,15 +140,16 @@ def verify_transaction_status(paytm_config, order_id): def finalize_request(order_id, transaction_response): request = frappe.get_doc('Integration Request', order_id) - redirect_to = request.data.get('redirect_to') or None - redirect_message = request.data.get('redirect_message') or None + transaction_data = frappe._dict(json.loads(request.data)) + redirect_to = transaction_data.get('redirect_to') or None + redirect_message = transaction_data.get('redirect_message') or None if transaction_response['STATUS'] == "TXN_SUCCESS": - if request.data.reference_doctype and request.data.reference_docname: + if transaction_data.reference_doctype and transaction_data.reference_docname: custom_redirect_to = None try: - custom_redirect_to = frappe.get_doc(request.data.reference_doctype, - request.data.reference_docname).run_method("on_payment_authorized", 'Completed') + custom_redirect_to = frappe.get_doc(transaction_data.reference_doctype, + transaction_data.reference_docname).run_method("on_payment_authorized", 'Completed') request.db_set('status', 'Completed') except Exception: request.db_set('status', 'Failed') @@ -153,10 +158,10 @@ def finalize_request(order_id, transaction_response): if custom_redirect_to: redirect_to = custom_redirect_to - redirect_url = 'payment-success' + redirect_url = '/integrations/payment-success' else: request.db_set('status', 'Failed') - redirect_url = 'payment-failed' + redirect_url = '/integrations/payment-failed' if redirect_to: redirect_url += '?' + urlencode({'redirect_to': redirect_to}) @@ -164,7 +169,7 @@ def finalize_request(order_id, transaction_response): redirect_url += '&' + urlencode({'redirect_message': redirect_message}) frappe.local.response['type'] = 'redirect' - frappe.local.response['location'] = 'redirect_url' + frappe.local.response['location'] = redirect_url def get_gateway_controller(doctype, docname): reference_doc = frappe.get_doc(doctype, docname) diff --git a/frappe/templates/includes/integrations/paytm_checkout.js b/frappe/templates/includes/integrations/paytm_checkout.js deleted file mode 100644 index 4cf070b60b..0000000000 --- a/frappe/templates/includes/integrations/paytm_checkout.js +++ /dev/null @@ -1,42 +0,0 @@ -function onScriptLoad() { - console.log('inside on load') - var config = { - root: '', - flow: 'DEFAULT', - data: { - orderId: '{{ order_id}}', - token: '{{ token }}', - tokenType: 'TXN_TOKEN', - amount: '{{ amount }}' - }, - handler: { - notifyMerchant: function(eventName, data) { - // notify about the state of the payment page ( invalid token , session expire , cancel transaction) - console.log('notifyMerchant handler function called'); - console.log('eventName => ', eventName); - console.log('data => ', data); - }, - transactionStatus: function transactionStatus(paymentStatus) { - // provide information to merchant about the payment status. - console.log('transaction status handler function called'); - console.log('paymentStatus => ', paymentStatus); - } - } - }; - - $('.paytm-loading').addClass('hidden'); - if (window.Paytm && window.Paytm.CheckoutJS) { - window.Paytm.CheckoutJS.onLoad(function excecuteAfterCompleteLoad() { - // initialze configuration using init method - window.Paytm.CheckoutJS.init(config) - .then(function onSuccess() { - // after successfully updating configuration, invoke Blink Checkout - window.Paytm.CheckoutJS.invoke(); - }) - .catch(function onError(error) { - console.log('inside the error window') - console.log('error => ', error); - }); - }); - } -} diff --git a/frappe/templates/pages/integrations/paytm_checkout.html b/frappe/templates/pages/integrations/paytm_checkout.html index 772e16ecb8..77a2045edf 100644 --- a/frappe/templates/pages/integrations/paytm_checkout.html +++ b/frappe/templates/pages/integrations/paytm_checkout.html @@ -16,12 +16,14 @@ {%- block page_content -%} -

Please do not refresh this page...

+
+

Please do not refresh this page...

{% for name, value in payment_details.items() %} {% endfor %}
+
{% endblock %} diff --git a/frappe/templates/pages/integrations/paytm_checkout.py b/frappe/templates/pages/integrations/paytm_checkout.py index 1fa947a68f..5a55d02ce9 100644 --- a/frappe/templates/pages/integrations/paytm_checkout.py +++ b/frappe/templates/pages/integrations/paytm_checkout.py @@ -12,9 +12,6 @@ import requests no_cache = 1 -expected_keys = ('amount', 'title', 'description', 'reference_doctype', 'reference_docname', - 'payer_name', 'payer_email') - def get_context(context): context.no_cache = 1 paytm_config = get_paytm_config() diff --git a/frappe/website/website_theme/standard/standard.json b/frappe/website/website_theme/standard/standard.json deleted file mode 100644 index a799f25425..0000000000 --- a/frappe/website/website_theme/standard/standard.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "button_gradients": 0, - "button_rounded_corners": 1, - "button_shadows": 0, - "creation": "2015-02-19 13:37:33.925909", - "custom": 0, - "docstatus": 0, - "doctype": "Website Theme", - "font_properties": "300,600", - "font_size": "", - "idx": 27, - "modified": "2020-04-21 02:10:31.761219", - "modified_by": "Administrator", - "module": "Website", - "name": "Standard", - "owner": "Administrator", - "theme": "Standard", - "theme_scss": "$enable-shadows: false;\n$enable-gradients: false;\n$enable-rounded: true;\n\n@import \"frappe/public/scss/website\";\n\n", - "theme_url": "/assets/css/standard_style.css" -} \ No newline at end of file From 25c9ac002290d070ab596a872faeba926f8317ad Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Wed, 22 Apr 2020 16:12:49 +0530 Subject: [PATCH 010/191] chore: remove initial validation check --- .../integrations/doctype/paytm_settings/checksum.py | 1 - .../doctype/paytm_settings/paytm_settings.py | 13 +------------ .../templates/pages/integrations/paytm_checkout.py | 6 +----- 3 files changed, 2 insertions(+), 18 deletions(-) diff --git a/frappe/integrations/doctype/paytm_settings/checksum.py b/frappe/integrations/doctype/paytm_settings/checksum.py index 95fb7dc164..b2e67bb5ad 100644 --- a/frappe/integrations/doctype/paytm_settings/checksum.py +++ b/frappe/integrations/doctype/paytm_settings/checksum.py @@ -87,7 +87,6 @@ def __get_param_string__(params): params_string = [] for key in sorted(params.keys()): if("REFUND" in params[key] or "|" in params[key]): - respons_dict = {} exit() value = params[key] params_string.append('' if value == 'null' else str(value)) diff --git a/frappe/integrations/doctype/paytm_settings/paytm_settings.py b/frappe/integrations/doctype/paytm_settings/paytm_settings.py index 394cd9c334..ed71202ea6 100644 --- a/frappe/integrations/doctype/paytm_settings/paytm_settings.py +++ b/frappe/integrations/doctype/paytm_settings/paytm_settings.py @@ -11,7 +11,7 @@ import frappe from frappe.model.document import Document from frappe import _ from frappe.utils import get_url, call_hook_method, cint, flt, cstr -from frappe.integrations.utils import make_get_request, make_post_request, create_request_log, create_payment_gateway +from frappe.integrations.utils import create_request_log, create_payment_gateway from frappe.utils import get_request_site_address from frappe.integrations.doctype.paytm_settings.checksum import generate_checksum, verify_checksum from frappe.utils.password import get_decrypted_password @@ -22,17 +22,6 @@ class PaytmSettings(Document): def validate(self): create_payment_gateway('Paytm') call_hook_method('payment_gateway_enabled', gateway='Paytm') - if not self.flags.ignore_mandatory: - self.validate_paytm_credentails() - - def validate_paytm_credentails(self): - if self.merchant_id and self.merchant_key: - pass - # header = {"Authorization": "Bearer {0}".format(self.get_password(fieldname="secret_key", raise_exception=False))} - # try: - # make_get_request(url="https://api.stripe.com/v1/charges", headers=header) - # except Exception: - # frappe.throw(_("Seems Publishable Key or Secret Key is wrong !!!")) def validate_transaction_currency(self, currency): if currency not in self.supported_currencies: diff --git a/frappe/templates/pages/integrations/paytm_checkout.py b/frappe/templates/pages/integrations/paytm_checkout.py index 5a55d02ce9..3018a9d65b 100644 --- a/frappe/templates/pages/integrations/paytm_checkout.py +++ b/frappe/templates/pages/integrations/paytm_checkout.py @@ -3,12 +3,8 @@ from __future__ import unicode_literals import frappe from frappe import _ -from frappe.utils import flt, cint import json -from six import string_types -from frappe.utils import get_request_site_address from frappe.integrations.doctype.paytm_settings.paytm_settings import get_paytm_params, get_paytm_config -import requests no_cache = 1 @@ -23,7 +19,7 @@ def get_context(context): context.url = paytm_config.url - except Exception as e: + except Exception: frappe.log_error() frappe.redirect_to_message(_('Invalid Token'), _('Seems token you are using is invalid!'), From 7dbdb7c50bb37e0fa1e371032c4b2516d3089c4e Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Fri, 24 Apr 2020 11:18:35 +0530 Subject: [PATCH 011/191] fix: add pycryptodome for paytm checksum generation --- requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements.txt b/requirements.txt index 58f923d880..2587a2eceb 100644 --- a/requirements.txt +++ b/requirements.txt @@ -66,3 +66,4 @@ watchdog==0.8.0 Werkzeug==0.16.1 xlrd==1.2.0 zxcvbn-python==4.4.24 +pycryptodome==3.9.7 \ No newline at end of file From 7a15f6f61d121901aa08c1f0bd1c3627bda0fe34 Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Fri, 24 Apr 2020 15:09:11 +0530 Subject: [PATCH 012/191] revert: add pylint and standard json again --- .pylintrc | 2 ++ .../website_theme/standard/standard.json | 20 +++++++++++++++++++ 2 files changed, 22 insertions(+) create mode 100644 .pylintrc create mode 100644 frappe/website/website_theme/standard/standard.json diff --git a/.pylintrc b/.pylintrc new file mode 100644 index 0000000000..c11c0ab6a3 --- /dev/null +++ b/.pylintrc @@ -0,0 +1,2 @@ +disable=access-member-before-definition +disable=no-member \ No newline at end of file diff --git a/frappe/website/website_theme/standard/standard.json b/frappe/website/website_theme/standard/standard.json new file mode 100644 index 0000000000..a799f25425 --- /dev/null +++ b/frappe/website/website_theme/standard/standard.json @@ -0,0 +1,20 @@ +{ + "button_gradients": 0, + "button_rounded_corners": 1, + "button_shadows": 0, + "creation": "2015-02-19 13:37:33.925909", + "custom": 0, + "docstatus": 0, + "doctype": "Website Theme", + "font_properties": "300,600", + "font_size": "", + "idx": 27, + "modified": "2020-04-21 02:10:31.761219", + "modified_by": "Administrator", + "module": "Website", + "name": "Standard", + "owner": "Administrator", + "theme": "Standard", + "theme_scss": "$enable-shadows: false;\n$enable-gradients: false;\n$enable-rounded: true;\n\n@import \"frappe/public/scss/website\";\n\n", + "theme_url": "/assets/css/standard_style.css" +} \ No newline at end of file From f512fe8446f4fb8f9755099406a014ed0f04b8fd Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Thu, 7 May 2020 18:21:05 +0530 Subject: [PATCH 013/191] feat: help article feedback --- .../js/frappe/form/sidebar/form_sidebar.js | 13 ++++ .../frappe/form/templates/form_sidebar.html | 6 +- frappe/public/js/frappe/utils/common.js | 6 ++ frappe/public/less/sidebar.less | 3 +- frappe/templates/includes/feedback.html | 29 +++++++++ frappe/website/doctype/feedback/__init__.py | 0 frappe/website/doctype/feedback/feedback.js | 8 +++ frappe/website/doctype/feedback/feedback.json | 59 +++++++++++++++++++ frappe/website/doctype/feedback/feedback.py | 27 +++++++++ .../website/doctype/feedback/test_feedback.py | 10 ++++ .../help_article/templates/help_article.html | 5 +- 11 files changed, 163 insertions(+), 3 deletions(-) create mode 100644 frappe/templates/includes/feedback.html create mode 100644 frappe/website/doctype/feedback/__init__.py create mode 100644 frappe/website/doctype/feedback/feedback.js create mode 100644 frappe/website/doctype/feedback/feedback.json create mode 100644 frappe/website/doctype/feedback/feedback.py create mode 100644 frappe/website/doctype/feedback/test_feedback.py diff --git a/frappe/public/js/frappe/form/sidebar/form_sidebar.js b/frappe/public/js/frappe/form/sidebar/form_sidebar.js index a145e47149..9450fdc674 100644 --- a/frappe/public/js/frappe/form/sidebar/form_sidebar.js +++ b/frappe/public/js/frappe/form/sidebar/form_sidebar.js @@ -93,6 +93,19 @@ frappe.ui.form.Sidebar = Class.extend({ }); } + frappe.utils.get_feedback(this.frm.doc.name).then((res) => { + this.sidebar + .find(".helpful") + .html( + __("Helpful {0}", [String(res.message.helpful).bold()]) + ); + this.sidebar + .find(".not-helpful") + .html( + __("Not Helpful {0}", [String(res.message.not_helpful).bold()]) + ); + }); + this.sidebar .find(".modified-by") .html( diff --git a/frappe/public/js/frappe/form/templates/form_sidebar.html b/frappe/public/js/frappe/form/templates/form_sidebar.html index 30b2205bae..3e72684818 100644 --- a/frappe/public/js/frappe/form/templates/form_sidebar.html +++ b/frappe/public/js/frappe/form/templates/form_sidebar.html @@ -106,11 +106,15 @@ {% if(frappe.get_form_sidebar_extension) { %} - {{ frappe.get_form_sidebar_extension() }} + {{ frappe.get_form_sidebar_extension() }} {% } %}
    diff --git a/frappe/public/js/frappe/utils/common.js b/frappe/public/js/frappe/utils/common.js index 1cdabf23e0..2fda8ed27a 100644 --- a/frappe/public/js/frappe/utils/common.js +++ b/frappe/public/js/frappe/utils/common.js @@ -359,3 +359,9 @@ frappe.utils.get_page_view_count = function(route) { path: route }); }; + +frappe.utils.get_feedback = function(reference_name) { + return frappe.call("frappe.website.doctype.feedback.feedback.get_feedback_count", { + reference_name: reference_name + }); +}; \ No newline at end of file diff --git a/frappe/public/less/sidebar.less b/frappe/public/less/sidebar.less index 109b7a3209..b6ffb7e697 100644 --- a/frappe/public/less/sidebar.less +++ b/frappe/public/less/sidebar.less @@ -274,7 +274,8 @@ body[data-route^="Module"] .main-menu { .layout-side-section .form-sidebar { .modified-by, - .pageview-count { + .pageview-count, + .feedback { margin-bottom: 15px; } } diff --git a/frappe/templates/includes/feedback.html b/frappe/templates/includes/feedback.html new file mode 100644 index 0000000000..5d10c7b159 --- /dev/null +++ b/frappe/templates/includes/feedback.html @@ -0,0 +1,29 @@ + + + diff --git a/frappe/website/doctype/feedback/__init__.py b/frappe/website/doctype/feedback/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/frappe/website/doctype/feedback/feedback.js b/frappe/website/doctype/feedback/feedback.js new file mode 100644 index 0000000000..f5fa89b30e --- /dev/null +++ b/frappe/website/doctype/feedback/feedback.js @@ -0,0 +1,8 @@ +// Copyright (c) 2020, Frappe Technologies and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Feedback', { + // refresh: function(frm) { + + // } +}); diff --git a/frappe/website/doctype/feedback/feedback.json b/frappe/website/doctype/feedback/feedback.json new file mode 100644 index 0000000000..b04aefa462 --- /dev/null +++ b/frappe/website/doctype/feedback/feedback.json @@ -0,0 +1,59 @@ +{ + "actions": [], + "creation": "2020-05-07 16:42:27.234652", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "reference_doctype", + "reference_name", + "helpful" + ], + "fields": [ + { + "fieldname": "reference_doctype", + "fieldtype": "Link", + "label": "Reference DocType", + "options": "DocType" + }, + { + "fieldname": "helpful", + "fieldtype": "Select", + "label": "Helpful", + "options": "Yes\nNo" + }, + { + "fieldname": "reference_name", + "fieldtype": "Dynamic Link", + "label": "Reference Name", + "options": "reference_doctype", + "search_index": 1 + } + ], + "in_create": 1, + "links": [], + "modified": "2020-05-07 16:53:29.211653", + "modified_by": "Administrator", + "module": "Website", + "name": "Feedback", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, + "write": 1 + } + ], + "quick_entry": 1, + "read_only": 1, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/frappe/website/doctype/feedback/feedback.py b/frappe/website/doctype/feedback/feedback.py new file mode 100644 index 0000000000..3b92118ec3 --- /dev/null +++ b/frappe/website/doctype/feedback/feedback.py @@ -0,0 +1,27 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2020, 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 Feedback(Document): + pass + +@frappe.whitelist(allow_guest=True) +def add_feedback(reference_doctype, reference_name, helpful): + frappe.get_doc({ + "doctype": "Feedback", + "reference_doctype": reference_doctype, + "reference_name": reference_name, + "helpful": helpful + }).insert(ignore_permissions=True) + +@frappe.whitelist() +def get_feedback_count(reference_name): + + return { + "helpful": frappe.db.count("Feedback", {"reference_name": reference_name, "helpful": "Yes"}), + "not_helpful": frappe.db.count("Feedback", {"reference_name": reference_name, "helpful": "No"}), + } \ No newline at end of file diff --git a/frappe/website/doctype/feedback/test_feedback.py b/frappe/website/doctype/feedback/test_feedback.py new file mode 100644 index 0000000000..9a31738d75 --- /dev/null +++ b/frappe/website/doctype/feedback/test_feedback.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2020, Frappe Technologies and Contributors +# See license.txt +from __future__ import unicode_literals + +# import frappe +import unittest + +class TestFeedback(unittest.TestCase): + pass diff --git a/frappe/website/doctype/help_article/templates/help_article.html b/frappe/website/doctype/help_article/templates/help_article.html index 913e6d6a92..6c59359785 100644 --- a/frappe/website/doctype/help_article/templates/help_article.html +++ b/frappe/website/doctype/help_article/templates/help_article.html @@ -19,6 +19,9 @@


    {{ _("More articles on {0}").format(category.name) }}

    +
    + {% include 'templates/includes/feedback.html' %} +

    Comments

    @@ -26,7 +29,7 @@
    {% endblock %} From c3a45de794a23f96ba208add27c518765d8c9d80 Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Fri, 8 May 2020 11:39:40 +0530 Subject: [PATCH 014/191] fix: feedback restrict to help article --- .../js/frappe/form/sidebar/form_sidebar.js | 13 - .../frappe/form/templates/form_sidebar.html | 4 - frappe/public/js/frappe/utils/common.js | 6 - frappe/public/less/sidebar.less | 3 +- frappe/templates/includes/feedback.html | 29 - frappe/website/doctype/feedback/__init__.py | 0 frappe/website/doctype/feedback/feedback.js | 8 - frappe/website/doctype/feedback/feedback.json | 59 -- frappe/website/doctype/feedback/feedback.py | 27 - .../website/doctype/feedback/test_feedback.py | 10 - .../doctype/help_article/help_article.js | 15 + .../doctype/help_article/help_article.json | 559 +++++------------- .../doctype/help_article/help_article.py | 10 +- .../help_article/templates/help_article.html | 26 +- 14 files changed, 185 insertions(+), 584 deletions(-) delete mode 100644 frappe/templates/includes/feedback.html delete mode 100644 frappe/website/doctype/feedback/__init__.py delete mode 100644 frappe/website/doctype/feedback/feedback.js delete mode 100644 frappe/website/doctype/feedback/feedback.json delete mode 100644 frappe/website/doctype/feedback/feedback.py delete mode 100644 frappe/website/doctype/feedback/test_feedback.py diff --git a/frappe/public/js/frappe/form/sidebar/form_sidebar.js b/frappe/public/js/frappe/form/sidebar/form_sidebar.js index 9450fdc674..a145e47149 100644 --- a/frappe/public/js/frappe/form/sidebar/form_sidebar.js +++ b/frappe/public/js/frappe/form/sidebar/form_sidebar.js @@ -93,19 +93,6 @@ frappe.ui.form.Sidebar = Class.extend({ }); } - frappe.utils.get_feedback(this.frm.doc.name).then((res) => { - this.sidebar - .find(".helpful") - .html( - __("Helpful {0}", [String(res.message.helpful).bold()]) - ); - this.sidebar - .find(".not-helpful") - .html( - __("Not Helpful {0}", [String(res.message.not_helpful).bold()]) - ); - }); - this.sidebar .find(".modified-by") .html( diff --git a/frappe/public/js/frappe/form/templates/form_sidebar.html b/frappe/public/js/frappe/form/templates/form_sidebar.html index 3e72684818..497a6488e2 100644 --- a/frappe/public/js/frappe/form/templates/form_sidebar.html +++ b/frappe/public/js/frappe/form/templates/form_sidebar.html @@ -106,10 +106,6 @@
diff --git a/frappe/public/js/frappe/utils/common.js b/frappe/public/js/frappe/utils/common.js index 2fda8ed27a..1cdabf23e0 100644 --- a/frappe/public/js/frappe/utils/common.js +++ b/frappe/public/js/frappe/utils/common.js @@ -359,9 +359,3 @@ frappe.utils.get_page_view_count = function(route) { path: route }); }; - -frappe.utils.get_feedback = function(reference_name) { - return frappe.call("frappe.website.doctype.feedback.feedback.get_feedback_count", { - reference_name: reference_name - }); -}; \ No newline at end of file diff --git a/frappe/public/less/sidebar.less b/frappe/public/less/sidebar.less index b6ffb7e697..109b7a3209 100644 --- a/frappe/public/less/sidebar.less +++ b/frappe/public/less/sidebar.less @@ -274,8 +274,7 @@ body[data-route^="Module"] .main-menu { .layout-side-section .form-sidebar { .modified-by, - .pageview-count, - .feedback { + .pageview-count { margin-bottom: 15px; } } diff --git a/frappe/templates/includes/feedback.html b/frappe/templates/includes/feedback.html deleted file mode 100644 index 5d10c7b159..0000000000 --- a/frappe/templates/includes/feedback.html +++ /dev/null @@ -1,29 +0,0 @@ - - - diff --git a/frappe/website/doctype/feedback/__init__.py b/frappe/website/doctype/feedback/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/frappe/website/doctype/feedback/feedback.js b/frappe/website/doctype/feedback/feedback.js deleted file mode 100644 index f5fa89b30e..0000000000 --- a/frappe/website/doctype/feedback/feedback.js +++ /dev/null @@ -1,8 +0,0 @@ -// Copyright (c) 2020, Frappe Technologies and contributors -// For license information, please see license.txt - -frappe.ui.form.on('Feedback', { - // refresh: function(frm) { - - // } -}); diff --git a/frappe/website/doctype/feedback/feedback.json b/frappe/website/doctype/feedback/feedback.json deleted file mode 100644 index b04aefa462..0000000000 --- a/frappe/website/doctype/feedback/feedback.json +++ /dev/null @@ -1,59 +0,0 @@ -{ - "actions": [], - "creation": "2020-05-07 16:42:27.234652", - "doctype": "DocType", - "editable_grid": 1, - "engine": "InnoDB", - "field_order": [ - "reference_doctype", - "reference_name", - "helpful" - ], - "fields": [ - { - "fieldname": "reference_doctype", - "fieldtype": "Link", - "label": "Reference DocType", - "options": "DocType" - }, - { - "fieldname": "helpful", - "fieldtype": "Select", - "label": "Helpful", - "options": "Yes\nNo" - }, - { - "fieldname": "reference_name", - "fieldtype": "Dynamic Link", - "label": "Reference Name", - "options": "reference_doctype", - "search_index": 1 - } - ], - "in_create": 1, - "links": [], - "modified": "2020-05-07 16:53:29.211653", - "modified_by": "Administrator", - "module": "Website", - "name": "Feedback", - "owner": "Administrator", - "permissions": [ - { - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "print": 1, - "read": 1, - "report": 1, - "role": "System Manager", - "share": 1, - "write": 1 - } - ], - "quick_entry": 1, - "read_only": 1, - "sort_field": "modified", - "sort_order": "DESC", - "track_changes": 1 -} \ No newline at end of file diff --git a/frappe/website/doctype/feedback/feedback.py b/frappe/website/doctype/feedback/feedback.py deleted file mode 100644 index 3b92118ec3..0000000000 --- a/frappe/website/doctype/feedback/feedback.py +++ /dev/null @@ -1,27 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2020, 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 Feedback(Document): - pass - -@frappe.whitelist(allow_guest=True) -def add_feedback(reference_doctype, reference_name, helpful): - frappe.get_doc({ - "doctype": "Feedback", - "reference_doctype": reference_doctype, - "reference_name": reference_name, - "helpful": helpful - }).insert(ignore_permissions=True) - -@frappe.whitelist() -def get_feedback_count(reference_name): - - return { - "helpful": frappe.db.count("Feedback", {"reference_name": reference_name, "helpful": "Yes"}), - "not_helpful": frappe.db.count("Feedback", {"reference_name": reference_name, "helpful": "No"}), - } \ No newline at end of file diff --git a/frappe/website/doctype/feedback/test_feedback.py b/frappe/website/doctype/feedback/test_feedback.py deleted file mode 100644 index 9a31738d75..0000000000 --- a/frappe/website/doctype/feedback/test_feedback.py +++ /dev/null @@ -1,10 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2020, Frappe Technologies and Contributors -# See license.txt -from __future__ import unicode_literals - -# import frappe -import unittest - -class TestFeedback(unittest.TestCase): - pass diff --git a/frappe/website/doctype/help_article/help_article.js b/frappe/website/doctype/help_article/help_article.js index c56bda8e0b..0ca92aefbd 100644 --- a/frappe/website/doctype/help_article/help_article.js +++ b/frappe/website/doctype/help_article/help_article.js @@ -3,6 +3,21 @@ frappe.ui.form.on('Help Article', { refresh: function(frm) { + frm.dashboard.clear_headline(); + frm.dashboard.set_headline_alert(` +
+
+ + Helpful ${frm.doc.helpful} + +
+
+ + Not Helpful ${frm.doc.helpful} + +
+
+ `); } }); diff --git a/frappe/website/doctype/help_article/help_article.json b/frappe/website/doctype/help_article/help_article.json index 5957333724..ca659692c6 100644 --- a/frappe/website/doctype/help_article/help_article.json +++ b/frappe/website/doctype/help_article/help_article.json @@ -1,445 +1,158 @@ { - "allow_copy": 0, - "allow_guest_to_view": 1, - "allow_import": 1, - "allow_rename": 0, - "beta": 0, - "creation": "2014-10-30 14:25:53.780105", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "", - "editable_grid": 0, + "actions": [], + "allow_guest_to_view": 1, + "allow_import": 1, + "creation": "2014-10-30 14:25:53.780105", + "doctype": "DocType", + "field_order": [ + "title", + "category", + "published", + "column_break_4", + "author", + "level", + "section_break_7", + "content", + "likes", + "route", + "owner", + "feedback", + "helpful", + "cb_00", + "not_helpful" + ], "fields": [ { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "title", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 1, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Title", - "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, - "translatable": 0, - "unique": 0 - }, + "fieldname": "title", + "fieldtype": "Data", + "in_global_search": 1, + "label": "Title", + "reqd": 1, + "search_index": 1 + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "category", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 1, - "label": "Category", - "length": 0, - "no_copy": 0, - "options": "Help Category", - "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, - "translatable": 0, - "unique": 0 - }, + "fieldname": "category", + "fieldtype": "Link", + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Category", + "options": "Help Category", + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "published", - "fieldtype": "Check", - "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": "Published", - "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, - "translatable": 0, - "unique": 0 - }, + "default": "0", + "fieldname": "published", + "fieldtype": "Check", + "label": "Published" + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "column_break_4", - "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, - "translatable": 0, - "unique": 0 - }, + "fieldname": "column_break_4", + "fieldtype": "Column Break" + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "user_fullname", - "fieldname": "author", - "fieldtype": "Data", - "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": "Author", - "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, - "translatable": 0, - "unique": 0 - }, + "default": "user_fullname", + "fieldname": "author", + "fieldtype": "Data", + "label": "Author" + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "level", - "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": "Level", - "length": 0, - "no_copy": 0, - "options": "Beginner\nIntermediate\nExpert", - "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, - "translatable": 0, - "unique": 0 - }, + "fieldname": "level", + "fieldtype": "Select", + "label": "Level", + "options": "Beginner\nIntermediate\nExpert" + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "section_break_7", - "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, - "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, - "translatable": 0, - "unique": 0 - }, + "fieldname": "section_break_7", + "fieldtype": "Section Break" + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "content", - "fieldtype": "Text Editor", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 1, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Content", - "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, - "translatable": 0, - "unique": 0 - }, + "fieldname": "content", + "fieldtype": "Text Editor", + "in_global_search": 1, + "label": "Content", + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "likes", - "fieldtype": "Int", - "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": "Likes", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "likes", + "fieldtype": "Int", + "label": "Likes", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "route", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 1, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Route", - "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, - "translatable": 0, - "unique": 0 - }, + "fieldname": "route", + "fieldtype": "Data", + "in_global_search": 1, + "label": "Route" + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "user", - "fieldname": "owner", - "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": "Owner", - "length": 0, - "no_copy": 0, - "options": "User", - "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, - "translatable": 0, - "unique": 0 + "default": "user", + "fieldname": "owner", + "fieldtype": "Link", + "label": "Owner", + "options": "User" + }, + { + "collapsible": 1, + "fieldname": "feedback", + "fieldtype": "Section Break", + "label": "Feedback" + }, + { + "default": "0", + "fieldname": "helpful", + "fieldtype": "Int", + "in_list_view": 1, + "label": "Helpful", + "read_only": 1 + }, + { + "fieldname": "cb_00", + "fieldtype": "Column Break" + }, + { + "default": "0", + "fieldname": "not_helpful", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Not Helpful", + "read_only": 1 } - ], - "has_web_view": 1, - "hide_heading": 0, - "hide_toolbar": 0, - "icon": "icon-file-alt", - "idx": 0, - "image_view": 0, - "in_create": 0, - "is_published_field": "published", - "is_submittable": 0, - "issingle": 0, - "istable": 0, - "max_attachments": 0, - "modified": "2018-05-18 17:49:52.912440", - "modified_by": "Administrator", - "module": "Website", - "name": "Help Article", - "name_case": "", - "owner": "Administrator", + ], + "has_web_view": 1, + "icon": "icon-file-alt", + "is_published_field": "published", + "links": [], + "modified": "2020-05-08 10:48:19.997789", + "modified_by": "Administrator", + "module": "Website", + "name": "Help Article", + "owner": "Administrator", "permissions": [ { - "amend": 0, - "cancel": 0, - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "if_owner": 0, - "import": 1, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "Knowledge Base Editor", - "set_user_permissions": 0, - "share": 0, - "submit": 0, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "import": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Knowledge Base Editor", "write": 1 - }, + }, { - "amend": 0, - "cancel": 0, - "create": 1, - "delete": 0, - "email": 0, - "export": 0, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 0, - "read": 1, - "report": 0, - "role": "Knowledge Base Contributor", - "set_user_permissions": 0, - "share": 0, - "submit": 0, + "create": 1, + "read": 1, + "role": "Knowledge Base Contributor", "write": 1 - }, + }, { - "amend": 0, - "cancel": 0, - "create": 0, - "delete": 0, - "email": 0, - "export": 0, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 0, - "read": 1, - "report": 0, - "role": "Guest", - "set_user_permissions": 0, - "share": 0, - "submit": 0, - "write": 0 + "read": 1, + "role": "Guest" } - ], - "quick_entry": 0, - "read_only": 0, - "read_only_onload": 0, - "show_name_in_global_search": 0, - "sort_field": "modified", - "sort_order": "DESC", - "title_field": "title", - "track_changes": 1, - "track_seen": 0 + ], + "sort_field": "modified", + "sort_order": "DESC", + "title_field": "title", + "track_changes": 1 } \ No newline at end of file diff --git a/frappe/website/doctype/help_article/help_article.py b/frappe/website/doctype/help_article/help_article.py index 6220d0aff5..fa26cfef99 100644 --- a/frappe/website/doctype/help_article/help_article.py +++ b/frappe/website/doctype/help_article/help_article.py @@ -4,7 +4,7 @@ from __future__ import unicode_literals import frappe from frappe.website.website_generator import WebsiteGenerator -from frappe.utils import is_markdown, markdown +from frappe.utils import is_markdown, markdown, cint from frappe.website.utils import get_comment_list from frappe import _ @@ -100,3 +100,11 @@ def clear_website_cache(path=None): frappe.cache().delete_value("knowledge_base:category_sidebar") frappe.cache().delete_value("knowledge_base:faq") +@frappe.whitelist(allow_guest=True) +def add_feedback(article, helpful): + field = "helpful" + if helpful == "No": + field = "not_helpful" + + value = cint(frappe.db.get_value("Help Article", article, field)) + frappe.db.set_value("Help Article", article, field, value+1, update_modified=False) \ No newline at end of file diff --git a/frappe/website/doctype/help_article/templates/help_article.html b/frappe/website/doctype/help_article/templates/help_article.html index 6c59359785..4f71117dea 100644 --- a/frappe/website/doctype/help_article/templates/help_article.html +++ b/frappe/website/doctype/help_article/templates/help_article.html @@ -19,8 +19,13 @@


{{ _("More articles on {0}").format(category.name) }}

-
- {% include 'templates/includes/feedback.html' %} +
+
+

@@ -30,6 +35,23 @@ {% endblock %} From 8c65d3581ebb634d913cc81a02dbda60fd278c4d Mon Sep 17 00:00:00 2001 From: Himanshu Date: Wed, 27 May 2020 13:39:29 +0530 Subject: [PATCH 015/191] Update frappe/website/doctype/help_article/templates/help_article.html Co-authored-by: Prssanna Desai --- frappe/website/doctype/help_article/templates/help_article.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/website/doctype/help_article/templates/help_article.html b/frappe/website/doctype/help_article/templates/help_article.html index 4f71117dea..08c8fa342d 100644 --- a/frappe/website/doctype/help_article/templates/help_article.html +++ b/frappe/website/doctype/help_article/templates/help_article.html @@ -48,7 +48,7 @@ frappe.ready(function() { callback: function(r) { $(".feedback")[0].disabled = true; $(".feedback")[1].disabled = true; - frappe.msgprint(__("Feedback Submitted.")); + frappe.msgprint(__("Thank you for your feedback!")); } }) }); From 0781d048d178aea71ccfdeeeb5d6caac3db42299 Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Tue, 2 Jun 2020 12:33:01 +0530 Subject: [PATCH 016/191] fix: add docs link to the settings page dashboard --- .../integrations/doctype/paytm_settings/paytm_settings.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/frappe/integrations/doctype/paytm_settings/paytm_settings.js b/frappe/integrations/doctype/paytm_settings/paytm_settings.js index d51a6e4a25..fe2ee7c952 100644 --- a/frappe/integrations/doctype/paytm_settings/paytm_settings.js +++ b/frappe/integrations/doctype/paytm_settings/paytm_settings.js @@ -2,7 +2,7 @@ // For license information, please see license.txt frappe.ui.form.on('Paytm Settings', { - // refresh: function(frm) { - - // } + refresh: function(frm) { + frm.dashboard.set_headline(__("For more information, {0}.", [`${__('Click here')}`])); + } }); From 04c070d82d3bb1df4072a52f279478487a2511cc Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Thu, 4 Jun 2020 14:38:46 +0530 Subject: [PATCH 017/191] fix: add paytm settings link to the module page --- frappe/config/integrations.py | 5 +++++ frappe/templates/includes/login/login.js | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/frappe/config/integrations.py b/frappe/config/integrations.py index f41adc9ea4..a7ac20065f 100644 --- a/frappe/config/integrations.py +++ b/frappe/config/integrations.py @@ -27,6 +27,11 @@ def get_data(): "name": "Stripe Settings", "description": _("Stripe payment gateway settings"), }, + { + "type": "doctype", + "name": "Paytm Settings", + "description": _("Paytm payment gateway settings"), + }, ] }, { diff --git a/frappe/templates/includes/login/login.js b/frappe/templates/includes/login/login.js index 37b8acb328..793fcc1720 100644 --- a/frappe/templates/includes/login/login.js +++ b/frappe/templates/includes/login/login.js @@ -186,7 +186,7 @@ login.login_handlers = (function() { } else if(data.message == 'Password Reset'){ window.location.href = frappe.utils.sanitise_redirect(data.redirect_to); } else if(data.message=="No App") { - login.set_indicator("{{ _("Success") }}", 'green'); + login.set_indicator("{{ _('Success') }}", 'green'); if(localStorage) { var last_visited = localStorage.getItem("last_visited") From a9a8fc953fa6a90be8718a9d5c1f9b16fa890e87 Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Mon, 8 Jun 2020 17:06:48 +0530 Subject: [PATCH 018/191] fix: update the checksum logic --- .../doctype/paytm_settings/checksum.py | 172 ++++++------------ .../paytm_settings/paytm_settings.json | 40 ++-- .../doctype/paytm_settings/paytm_settings.py | 8 +- 3 files changed, 85 insertions(+), 135 deletions(-) diff --git a/frappe/integrations/doctype/paytm_settings/checksum.py b/frappe/integrations/doctype/paytm_settings/checksum.py index b2e67bb5ad..32f976ae18 100644 --- a/frappe/integrations/doctype/paytm_settings/checksum.py +++ b/frappe/integrations/doctype/paytm_settings/checksum.py @@ -2,141 +2,79 @@ import base64 import string import random import hashlib +import sys from Crypto.Cipher import AES -IV = "@@@@&&&&####$$$$" +iv = '@@@@&&&&####$$$$' BLOCK_SIZE = 16 +if (sys.version_info > (3, 0)): + __pad__ = lambda s: bytes(s + (BLOCK_SIZE - len(s) % BLOCK_SIZE) * chr(BLOCK_SIZE - len(s) % BLOCK_SIZE), 'utf-8') +else: + __pad__ = lambda s: s + (BLOCK_SIZE - len(s) % BLOCK_SIZE) * chr(BLOCK_SIZE - len(s) % BLOCK_SIZE) -def generate_checksum(param_dict, merchant_key, salt=None): - params_string = __get_param_string__(param_dict) - salt = salt if salt else __id_generator__(4) - final_string = '%s|%s' % (params_string, salt) +__unpad__ = lambda s: s[0:-ord(s[-1])] - hasher = hashlib.sha256(final_string.encode()) - hash_string = hasher.hexdigest() +def encrypt(input, key): + input = __pad__(input) + c = AES.new(key.encode("utf8"), AES.MODE_CBC, iv.encode("utf8")) + input = c.encrypt(input) + input = base64.b64encode(input) + return input.decode("UTF-8") - hash_string += salt +def decrypt(encrypted, key): + encrypted = base64.b64decode(encrypted) + c = AES.new(key.encode("utf8"), AES.MODE_CBC, iv.encode("utf8")) + param = c.decrypt(encrypted) + if type(param) == bytes: + param = param.decode() + return __unpad__(param) - return __encode__(hash_string, IV, merchant_key) +def generateSignature(params, key): + if not type(params) is dict and not type(params) is str: + raise Exception("string or dict expected, " + str(type(params)) + " given") + if type(params) is dict: + params = getStringByParams(params) + return generateSignatureByString(params, key) +def verifySignature(params, key, checksum): + if not type(params) is dict and not type(params) is str: + raise Exception("string or dict expected, " + str(type(params)) + " given") + if "CHECKSUMHASH" in params: + del params["CHECKSUMHASH"] + + if type(params) is dict: + params = getStringByParams(params) + return verifySignatureByString(params, key, checksum) -def generate_refund_checksum(param_dict, merchant_key, salt=None): - for i in param_dict: - if("|" in param_dict[i]): - param_dict = {} - exit() - params_string = __get_param_string__(param_dict) - salt = salt if salt else __id_generator__(4) - final_string = '%s|%s' % (params_string, salt) +def generateSignatureByString(params, key): + salt = generateRandomString(4) + return calculateChecksum(params, key, salt) - hasher = hashlib.sha256(final_string.encode()) - hash_string = hasher.hexdigest() - - hash_string += salt - - return __encode__(hash_string, IV, merchant_key) - - -def generate_checksum_by_str(param_str, merchant_key, salt=None): - params_string = param_str - salt = salt if salt else __id_generator__(4) - final_string = '%s|%s' % (params_string, salt) - - hasher = hashlib.sha256(final_string.encode()) - hash_string = hasher.hexdigest() - - hash_string += salt - - return __encode__(hash_string, IV, merchant_key) - - -def verify_checksum(param_dict, merchant_key, checksum): - # Remove checksum - if 'CHECKSUMHASH' in param_dict: - param_dict.pop('CHECKSUMHASH') - - # Get salt - paytm_hash = __decode__(checksum, IV, merchant_key) +def verifySignatureByString(params, key, checksum): + paytm_hash = decrypt(checksum, key) salt = paytm_hash[-4:] - calculated_checksum = generate_checksum( - param_dict, merchant_key, salt=salt) - return calculated_checksum == checksum + return paytm_hash == calculateHash(params, salt) +def generateRandomString(length): + chars = string.ascii_uppercase + string.digits + string.ascii_lowercase + return ''.join(random.choice(chars) for _ in range(length)) -def verify_checksum_by_str(param_str, merchant_key, checksum): - # Remove checksum - # if 'CHECKSUMHASH' in param_dict: - # param_dict.pop('CHECKSUMHASH') - - # Get salt - paytm_hash = __decode__(checksum, IV, merchant_key) - salt = paytm_hash[-4:] - calculated_checksum = generate_checksum_by_str( - param_str, merchant_key, salt=salt) - return calculated_checksum == checksum - - -def __id_generator__(size=6, chars=string.ascii_uppercase + string.digits + string.ascii_lowercase): - return ''.join(random.choice(chars) for _ in range(size)) - - -def __get_param_string__(params): +def getStringByParams(params): params_string = [] for key in sorted(params.keys()): - if("REFUND" in params[key] or "|" in params[key]): - exit() - value = params[key] - params_string.append('' if value == 'null' else str(value)) + value = params[key] if params[key] is not None and params[key].lower() != "null" else "" + params_string.append(str(value)) return '|'.join(params_string) +def calculateHash(params, salt): + finalString = '%s|%s' % (params, salt) + hasher = hashlib.sha256(finalString.encode()) + hashString = hasher.hexdigest() + salt + return hashString -def __pad__(s): return s + (BLOCK_SIZE - len(s) % - BLOCK_SIZE) * chr(BLOCK_SIZE - len(s) % BLOCK_SIZE) - - -def __unpad__(s): return s[0:-ord(s[-1])] - - -def __encode__(to_encode, iv, key): - # Pad - to_encode = __pad__(to_encode) - # Encrypt - c = AES.new(key, AES.MODE_CBC, iv) - to_encode = c.encrypt(to_encode) - # Encode - to_encode = base64.b64encode(to_encode) - return to_encode.decode("UTF-8") - - -def __decode__(to_decode, iv, key): - # Decode - to_decode = base64.b64decode(to_decode) - # Decrypt - c = AES.new(key, AES.MODE_CBC, iv) - to_decode = c.decrypt(to_decode) - if type(to_decode) == bytes: - # convert bytes array to str. - to_decode = to_decode.decode() - # remove pad - return __unpad__(to_decode) - - -if __name__ == "__main__": - params = { - "MID": "mid", - "ORDER_ID": "order_id", - "CUST_ID": "cust_id", - "TXN_AMOUNT": "1", - "CHANNEL_ID": "WEB", - "INDUSTRY_TYPE_ID": "Retail", - "WEBSITE": "xxxxxxxxxxx" - } - - print(verify_checksum( - params, 'xxxxxxxxxxxxxxxx', - "CD5ndX8VVjlzjWbbYoAtKQIlvtXPypQYOg0Fi2AUYKXZA5XSHiRF0FDj7vQu66S8MHx9NaDZ/uYm3WBOWHf+sDQAmTyxqUipA7i1nILlxrk=")) - - # print(generate_checksum(params, "xxxxxxxxxxxxxxxx")) +def calculateChecksum(params, key, salt): + hashString = calculateHash(params, salt) + return encrypt(hashString, key) \ No newline at end of file diff --git a/frappe/integrations/doctype/paytm_settings/paytm_settings.json b/frappe/integrations/doctype/paytm_settings/paytm_settings.json index 0540dc4948..93fbd0df09 100644 --- a/frappe/integrations/doctype/paytm_settings/paytm_settings.json +++ b/frappe/integrations/doctype/paytm_settings/paytm_settings.json @@ -9,7 +9,7 @@ "merchant_key", "staging", "column_break_4", - "industry_type", + "industry_type_id", "website" ], "fields": [ @@ -18,43 +18,55 @@ "fieldtype": "Data", "in_list_view": 1, "label": "Merchant ID", - "reqd": 1 + "reqd": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "merchant_key", "fieldtype": "Password", "in_list_view": 1, "label": "Merchant Key", - "reqd": 1 + "reqd": 1, + "show_days": 1, + "show_seconds": 1 }, { "default": "0", "fieldname": "staging", "fieldtype": "Check", - "label": "Staging" - }, - { - "depends_on": "eval: !doc.staging", - "fieldname": "industry_type", - "fieldtype": "Data", - "label": "Industry Type", - "mandatory_depends_on": "eval: !doc.staging" + "label": "Staging", + "show_days": 1, + "show_seconds": 1 }, { "depends_on": "eval: !doc.staging", "fieldname": "website", "fieldtype": "Data", "label": "Website", - "mandatory_depends_on": "eval: !doc.staging" + "mandatory_depends_on": "eval: !doc.staging", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "column_break_4", - "fieldtype": "Column Break" + "fieldtype": "Column Break", + "show_days": 1, + "show_seconds": 1 + }, + { + "depends_on": "eval: !doc.staging", + "fieldname": "industry_type_id", + "fieldtype": "Data", + "label": "Industry Type ID", + "mandatory_depends_on": "eval: !doc.staging", + "show_days": 1, + "show_seconds": 1 } ], "issingle": 1, "links": [], - "modified": "2020-04-16 14:16:19.687449", + "modified": "2020-06-08 13:36:09.703143", "modified_by": "Administrator", "module": "Integrations", "name": "Paytm Settings", diff --git a/frappe/integrations/doctype/paytm_settings/paytm_settings.py b/frappe/integrations/doctype/paytm_settings/paytm_settings.py index ed71202ea6..c169a53246 100644 --- a/frappe/integrations/doctype/paytm_settings/paytm_settings.py +++ b/frappe/integrations/doctype/paytm_settings/paytm_settings.py @@ -13,7 +13,7 @@ from frappe import _ from frappe.utils import get_url, call_hook_method, cint, flt, cstr from frappe.integrations.utils import create_request_log, create_payment_gateway from frappe.utils import get_request_site_address -from frappe.integrations.doctype.paytm_settings.checksum import generate_checksum, verify_checksum +from frappe.integrations.doctype.paytm_settings.checksum import generateSignature, verifySignature from frappe.utils.password import get_decrypted_password class PaytmSettings(Document): @@ -75,7 +75,7 @@ def get_paytm_params(payment_details, order_id, paytm_config): "CALLBACK_URL" : redirect_uri, }) - checksum = generate_checksum(paytm_params, paytm_config.merchant_key) + checksum = generateSignature(paytm_params, paytm_config.merchant_key) paytm_params.update({ "CHECKSUMHASH" : checksum @@ -101,7 +101,7 @@ def verify_transaction(**kwargs): if paytm_params and paytm_config and paytm_checksum: # Verify checksum - is_valid_checksum = verify_checksum(paytm_params, paytm_config.merchant_key, paytm_checksum) + is_valid_checksum = verifySignature(paytm_params, paytm_config.merchant_key, paytm_checksum) if is_valid_checksum and received_data['RESPCODE'] == '01': verify_transaction_status(paytm_config, received_data['ORDERID']) @@ -118,7 +118,7 @@ def verify_transaction_status(paytm_config, order_id): ORDERID= order_id ) - checksum = generate_checksum(paytm_params, paytm_config.merchant_key) + checksum = generateSignature(paytm_params, paytm_config.merchant_key) paytm_params["CHECKSUMHASH"] = checksum post_data = json.dumps(paytm_params) From 86508e7617e9b9d448599b6298dacf83f840d136 Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Mon, 15 Jun 2020 14:53:30 +0530 Subject: [PATCH 019/191] fix: change to primary for button --- .../help_article/templates/help_article.html | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/frappe/website/doctype/help_article/templates/help_article.html b/frappe/website/doctype/help_article/templates/help_article.html index 08c8fa342d..01271af8b9 100644 --- a/frappe/website/doctype/help_article/templates/help_article.html +++ b/frappe/website/doctype/help_article/templates/help_article.html @@ -23,8 +23,8 @@
@@ -41,13 +41,19 @@ frappe.ready(function() { helpful: this.getAttribute("data-value"), } + let disable_button = function(btn) { + btn.classList.remove("btn-outline-primary"); + btn.classList.add("btn-light"); + btn.disabled = true + } + frappe.call({ btn: this, method: "frappe.website.doctype.help_article.help_article.add_feedback", args: args, callback: function(r) { - $(".feedback")[0].disabled = true; - $(".feedback")[1].disabled = true; + disable_button($(".feedback")[0]); + disable_button($(".feedback")[1]); frappe.msgprint(__("Thank you for your feedback!")); } }) From 0aceebd5507d088410354e6bf55acdc54667b816 Mon Sep 17 00:00:00 2001 From: Himanshu Date: Wed, 24 Jun 2020 10:52:03 +0530 Subject: [PATCH 020/191] Update frappe/website/doctype/help_article/help_article.js Co-authored-by: Prssanna Desai --- frappe/website/doctype/help_article/help_article.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/website/doctype/help_article/help_article.js b/frappe/website/doctype/help_article/help_article.js index 0ca92aefbd..0a87b22e2c 100644 --- a/frappe/website/doctype/help_article/help_article.js +++ b/frappe/website/doctype/help_article/help_article.js @@ -14,7 +14,7 @@ frappe.ui.form.on('Help Article', {
- Not Helpful ${frm.doc.helpful} + Not Helpful ${frm.doc.not_helpful}
From 249d04420b9e32e8543c02bf8879d321747df5ba Mon Sep 17 00:00:00 2001 From: Himanshu Date: Fri, 26 Jun 2020 13:28:15 +0530 Subject: [PATCH 021/191] Update frappe/website/doctype/help_article/templates/help_article.html Co-authored-by: Shivam Mishra --- frappe/website/doctype/help_article/templates/help_article.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/website/doctype/help_article/templates/help_article.html b/frappe/website/doctype/help_article/templates/help_article.html index 01271af8b9..cdd830295f 100644 --- a/frappe/website/doctype/help_article/templates/help_article.html +++ b/frappe/website/doctype/help_article/templates/help_article.html @@ -22,7 +22,7 @@

From 548a5fa41640e6bf7dff1e19c011146ddfa7e65e Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Fri, 26 Jun 2020 15:38:07 +0530 Subject: [PATCH 022/191] fix: add new disabled state --- .../help_article/templates/help_article.html | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/frappe/website/doctype/help_article/templates/help_article.html b/frappe/website/doctype/help_article/templates/help_article.html index cdd830295f..a97154847b 100644 --- a/frappe/website/doctype/help_article/templates/help_article.html +++ b/frappe/website/doctype/help_article/templates/help_article.html @@ -25,6 +25,7 @@ {{ _("Was this article helpful?") }} +
@@ -41,20 +42,14 @@ frappe.ready(function() { helpful: this.getAttribute("data-value"), } - let disable_button = function(btn) { - btn.classList.remove("btn-outline-primary"); - btn.classList.add("btn-light"); - btn.disabled = true - } - frappe.call({ btn: this, method: "frappe.website.doctype.help_article.help_article.add_feedback", args: args, callback: function(r) { - disable_button($(".feedback")[0]); - disable_button($(".feedback")[1]); - frappe.msgprint(__("Thank you for your feedback!")); + $(".feedback")[0].classList.add("hide"); + $(".feedback")[1].classList.add("hide"); + $(".feedback-msg")[0].classList.remove("hide"); } }) }); From 8ddfd2344d6c7d8be3855d317602de2b7d2f8888 Mon Sep 17 00:00:00 2001 From: "Chinmay D. Pai" Date: Thu, 2 Jul 2020 12:53:16 +0530 Subject: [PATCH 023/191] chore: add search queries to whitelist these functions are called through search widget and need to be whitelisted to work Signed-off-by: Chinmay D. Pai --- frappe/contacts/address_and_contact.py | 1 + frappe/contacts/doctype/address/address.py | 3 ++- frappe/contacts/doctype/contact/contact.py | 1 + 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/frappe/contacts/address_and_contact.py b/frappe/contacts/address_and_contact.py index 5a004f153b..51f13fb1a1 100644 --- a/frappe/contacts/address_and_contact.py +++ b/frappe/contacts/address_and_contact.py @@ -146,6 +146,7 @@ def delete_contact_and_address(doctype, docname): if len(doc.links)==1: doc.delete() +@frappe.whitelist() def filter_dynamic_link_doctypes(doctype, txt, searchfield, start, page_len, filters): if not txt: txt = "" diff --git a/frappe/contacts/doctype/address/address.py b/frappe/contacts/doctype/address/address.py index b85d578353..79bdb42931 100644 --- a/frappe/contacts/doctype/address/address.py +++ b/frappe/contacts/doctype/address/address.py @@ -230,6 +230,7 @@ def get_company_address(company): return ret +@frappe.whitelist() def address_query(doctype, txt, searchfield, start, page_len, filters): from frappe.desk.reportview import get_match_cond @@ -289,4 +290,4 @@ def get_condensed_address(doc): return ", ".join([doc.get(d) for d in fields if doc.get(d)]) def update_preferred_address(address, field): - frappe.db.set_value('Address', address, field, 0) \ No newline at end of file + frappe.db.set_value('Address', address, field, 0) diff --git a/frappe/contacts/doctype/contact/contact.py b/frappe/contacts/doctype/contact/contact.py index 4cf209541c..598e186ee8 100644 --- a/frappe/contacts/doctype/contact/contact.py +++ b/frappe/contacts/doctype/contact/contact.py @@ -182,6 +182,7 @@ def update_contact(doc, method): contact.flags.ignore_mandatory = True contact.save(ignore_permissions=True) +@frappe.whitelist() def contact_query(doctype, txt, searchfield, start, page_len, filters): from frappe.desk.reportview import get_match_cond From 338151a7e5e067ec3e566025b1c99b04621432e3 Mon Sep 17 00:00:00 2001 From: "Chinmay D. Pai" Date: Tue, 7 Jul 2020 11:22:37 +0530 Subject: [PATCH 024/191] fix: correctly format sql query Signed-off-by: Chinmay D. Pai --- frappe/contacts/doctype/contact/contact.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/frappe/contacts/doctype/contact/contact.py b/frappe/contacts/doctype/contact/contact.py index 598e186ee8..e340c7e2d0 100644 --- a/frappe/contacts/doctype/contact/contact.py +++ b/frappe/contacts/doctype/contact/contact.py @@ -205,14 +205,14 @@ def contact_query(doctype, txt, searchfield, start, page_len, filters): `tabDynamic Link`.parenttype = 'Contact' and `tabDynamic Link`.link_doctype = %(link_doctype)s and `tabDynamic Link`.link_name = %(link_name)s and - `tabContact`.`{key}` like %(txt)s - {mcond} + `tabContact`.`%(key)s` like %(txt)s + %(mcond)s order by if(locate(%(_txt)s, `tabContact`.name), locate(%(_txt)s, `tabContact`.name), 99999), `tabContact`.idx desc, `tabContact`.name - limit %(start)s, %(page_len)s """.format( - mcond=get_match_cond(doctype), - key=searchfield), { + limit %(start)s, %(page_len)s """, { + 'mcond': get_match_cond(doctype), + 'key': searchfield, 'txt': '%' + txt + '%', '_txt': txt.replace("%", ""), 'start': start, From 36d1b5f0150799a08b5ca39c5efd35040e3a899d Mon Sep 17 00:00:00 2001 From: "Chinmay D. Pai" Date: Thu, 9 Jul 2020 12:27:39 +0530 Subject: [PATCH 025/191] fix: check if field exists in meta before executing query Signed-off-by: Chinmay D. Pai --- frappe/contacts/doctype/contact/contact.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/frappe/contacts/doctype/contact/contact.py b/frappe/contacts/doctype/contact/contact.py index e340c7e2d0..4af4362aff 100644 --- a/frappe/contacts/doctype/contact/contact.py +++ b/frappe/contacts/doctype/contact/contact.py @@ -186,6 +186,9 @@ def update_contact(doc, method): def contact_query(doctype, txt, searchfield, start, page_len, filters): from frappe.desk.reportview import get_match_cond + if not frappe.get_meta("Contact").get_field(searchfield): + return {} + link_doctype = filters.pop('link_doctype') link_name = filters.pop('link_name') @@ -205,14 +208,12 @@ def contact_query(doctype, txt, searchfield, start, page_len, filters): `tabDynamic Link`.parenttype = 'Contact' and `tabDynamic Link`.link_doctype = %(link_doctype)s and `tabDynamic Link`.link_name = %(link_name)s and - `tabContact`.`%(key)s` like %(txt)s - %(mcond)s + `tabContact`.`{key}` like %(txt)s + {mcond} order by if(locate(%(_txt)s, `tabContact`.name), locate(%(_txt)s, `tabContact`.name), 99999), `tabContact`.idx desc, `tabContact`.name - limit %(start)s, %(page_len)s """, { - 'mcond': get_match_cond(doctype), - 'key': searchfield, + limit %(start)s, %(page_len)s """.format(mcond=get_match_cond(doctype), key=searchfield), { 'txt': '%' + txt + '%', '_txt': txt.replace("%", ""), 'start': start, From 2220cb969bc973ca0dc4e0848e49a2f66d76eb15 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Fri, 10 Jul 2020 20:33:11 +0530 Subject: [PATCH 026/191] fix: Datetime field description --- frappe/public/js/frappe/form/controls/datetime.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/frappe/public/js/frappe/form/controls/datetime.js b/frappe/public/js/frappe/form/controls/datetime.js index b487be6eca..f44591ddcb 100644 --- a/frappe/public/js/frappe/form/controls/datetime.js +++ b/frappe/public/js/frappe/form/controls/datetime.js @@ -20,8 +20,6 @@ frappe.ui.form.ControlDatetime = frappe.ui.form.ControlDate.extend({ if (!frappe.datetime.is_timezone_same()) { if (!description) { this.df.description = time_zone; - } else if (!description.includes(time_zone)) { - this.df.description += '
' + time_zone; } } this._super(); From 817b2488271ef109d4f53ecf4b1856ff3635892e Mon Sep 17 00:00:00 2001 From: Himanshu Date: Sat, 11 Jul 2020 17:34:28 +0530 Subject: [PATCH 027/191] Update frappe/website/doctype/help_article/templates/help_article.html Co-authored-by: Prssanna Desai --- frappe/website/doctype/help_article/templates/help_article.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/website/doctype/help_article/templates/help_article.html b/frappe/website/doctype/help_article/templates/help_article.html index a97154847b..bfe2bc4529 100644 --- a/frappe/website/doctype/help_article/templates/help_article.html +++ b/frappe/website/doctype/help_article/templates/help_article.html @@ -22,7 +22,7 @@

From 19ff07b53b4927fca53faae2f9adb82ad3159305 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Wed, 22 Jul 2020 14:27:18 +0530 Subject: [PATCH 090/191] fix: Margin top for child table index checkboxes --- frappe/public/less/desk.less | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/frappe/public/less/desk.less b/frappe/public/less/desk.less index 36ceed9bfa..d951178be0 100644 --- a/frappe/public/less/desk.less +++ b/frappe/public/less/desk.less @@ -789,6 +789,10 @@ li.user-progress { position: relative; } +.row-index input[type=checkbox] { + margin-top: 12px; +} + @checkbox-height: 14px; // custom font awesome checkbox From 95aa4a72dcd81b98a9f318b35979964bdedbc032 Mon Sep 17 00:00:00 2001 From: prssanna Date: Wed, 22 Jul 2020 14:53:03 +0530 Subject: [PATCH 091/191] fix: reduce code complexity --- frappe/public/js/frappe/ui/filters/filter.js | 8 +++++--- frappe/public/js/frappe/ui/filters/filter_list.js | 3 +-- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/frappe/public/js/frappe/ui/filters/filter.js b/frappe/public/js/frappe/ui/filters/filter.js index d4fb6db436..7c6d532f5c 100644 --- a/frappe/public/js/frappe/ui/filters/filter.js +++ b/frappe/public/js/frappe/ui/filters/filter.js @@ -249,9 +249,7 @@ frappe.ui.Filter = class { let args = {}; if (this.filters_config[condition].depends_on) { const field_name = this.filters_config[condition].depends_on; - const filter_value = this.base_list - ? this.base_list.get_filter_value(field_name) - : this.filter_list.get_filter_value(field_name); + const filter_value = this.get_filter_value(field_name); args[field_name] = filter_value; } frappe @@ -267,6 +265,10 @@ frappe.ui.Filter = class { } } + get_filter_value(fieldname) { + return this.filter_list.get_filter_value(fieldname); + } + make_field(df, old_fieldtype) { let old_text = this.field ? this.field.get_value() : null; this.hide_invalid_conditions(df.fieldtype, df.original_type); diff --git a/frappe/public/js/frappe/ui/filters/filter_list.js b/frappe/public/js/frappe/ui/filters/filter_list.js index 7ae48bc7d9..9a35a9bb5f 100644 --- a/frappe/public/js/frappe/ui/filters/filter_list.js +++ b/frappe/public/js/frappe/ui/filters/filter_list.js @@ -104,8 +104,7 @@ frappe.ui.FilterGroup = class { filter_items: (doctype, fieldname) => { return !this.filter_exists([doctype, fieldname]); }, - base_list: this.base_list, - filter_list: this, + filter_list: this.base_list || this, }; let filter = new frappe.ui.Filter(args); this.filters.push(filter); From 0c475792cd3f1860af674c9f0073855de4753665 Mon Sep 17 00:00:00 2001 From: prssanna Date: Wed, 22 Jul 2020 14:55:02 +0530 Subject: [PATCH 092/191] refactor: delete unused file filters.js --- frappe/public/js/frappe/ui/filters/filters.js | 684 ------------------ 1 file changed, 684 deletions(-) delete mode 100644 frappe/public/js/frappe/ui/filters/filters.js diff --git a/frappe/public/js/frappe/ui/filters/filters.js b/frappe/public/js/frappe/ui/filters/filters.js deleted file mode 100644 index a775413d39..0000000000 --- a/frappe/public/js/frappe/ui/filters/filters.js +++ /dev/null @@ -1,684 +0,0 @@ -// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -// MIT License. See license.txt - -frappe.ui.FilterList = Class.extend({ - init: function(opts) { - $.extend(this, opts); - this.filters = []; - this.wrapper = this.parent; - this.stats = []; - this.make(); - this.set_events(); - }, - make: function() { - this.wrapper.find('.show_filters, .filter_area').remove(); - this.wrapper.append(` -
-
- -
-
-
`); - }, - set_events: function() { - var me = this; - // show filters - this.wrapper.find('.new-filter').bind('click', function() { - me.add_filter(); - }); - - this.wrapper.find('.clear-filters').bind('click', function() { - me.clear_filters(); - $('.date-range-picker').val('') - me.base_list.run(); - $(this).addClass("hide"); - }); - }, - - show_filters: function() { - this.wrapper.find('.show_filters').toggle(); - if(!this.filters.length) { - this.add_filter(this.doctype, 'name'); - this.filters[0].wrapper.find(".filter_field input").focus(); - } - }, - - clear_filters: function() { - $.each(this.filters, function(i, f) { f.remove(true); }); - if(this.base_list.page.fields_dict) { - $.each(this.base_list.page.fields_dict, (key, value) => { - value.set_input(''); - }); - } - this.filters = []; - }, - - add_filter: function(doctype, fieldname, condition, value, hidden) { - // adds a new filter, returns true if filter has been added - - // allow equal to be used as like - let base_filter = this.base_list.page.fields_dict[fieldname]; - if (base_filter - && (base_filter.df.condition==condition - || (condition==='=' && base_filter.df.condition==='like'))) { - // if filter exists in base_list, then exit - this.base_list.page.fields_dict[fieldname].set_input(value); - - return true; - } - - if(doctype && fieldname - && !frappe.meta.has_field(doctype, fieldname) - && !in_list(frappe.model.std_fields_list, fieldname)) { - frappe.msgprint({ - message: __('Filter {0} missing', [fieldname.bold()]), - title: 'Invalid Filter', - indicator: 'red' - }); - return false; - } - - this.wrapper.find('.show_filters').toggle(true); - var is_new_filter = arguments.length===0; - - if (is_new_filter && this.wrapper.find(".is-new-filter:visible").length) { - // only allow 1 new filter at a time! - return false; - } - - var filter = this.push_new_filter(doctype, fieldname, condition, value); - if (!filter) return; - - if(this.wrapper.find('.clear-filters').hasClass("hide")) { - this.wrapper.find('.clear-filters').removeClass("hide"); - } - - if (filter && is_new_filter) { - filter.wrapper.addClass("is-new-filter"); - } else { - filter.freeze(); - } - - if (hidden) { - filter.$btn_group.addClass("hide"); - } - - return true; - }, - push_new_filter: function(doctype, fieldname, condition, value) { - if(this.filter_exists(doctype, fieldname, condition, value)) { - return; - } - - // if standard filter exists, then clear it. - if(this.base_list.page.fields_dict[fieldname]) { - this.base_list.page.fields_dict[fieldname].set_input(''); - } - - var filter = new frappe.ui.Filter({ - flist: this, - _doctype: doctype, - fieldname: fieldname, - condition: condition, - value: value - }); - - this.filters.push(filter); - - return filter; - }, - - remove: function(filter) { - // remove `filter` from flist - for (var i in this.filters) { - if (this.filters[i] === filter) { - break; - } - } - if (i!==undefined) { - // remove index - this.filters.splice(i, 1); - } - }, - - filter_exists: function(doctype, fieldname, condition, value) { - var flag = false; - for(var i in this.filters) { - if(this.filters[i].field) { - var f = this.filters[i].get_value(); - - if(f[0]==doctype && f[1]==fieldname && f[2]==condition && f[3]==value) { - flag = true; - } else if($.isArray(value) && frappe.utils.arrays_equal(value, f[3])) { - flag = true; - } - } - } - return flag; - }, - - get_filters: function() { - // get filter values as dict - var values = []; - $.each(this.filters, function(i, filter) { - if(filter.field) { - filter.freeze(); - values.push(filter.get_value()); - } - }); - this.base_list.update_standard_filters(values); - - return values; - }, - - // remove hidden filters - update_filters: function() { - var fl = []; - $.each(this.filters, function(i, f) { - if(f.field) fl.push(f); - }) - this.filters = fl; - if(this.filters.length === 0) { - this.wrapper.find('.clear-filters').addClass("hide"); - } - }, - - get_filter: function(fieldname) { - for(var i in this.filters) { - if(this.filters[i].field && this.filters[i].field.df.fieldname==fieldname) - return this.filters[i]; - } - }, - - get_formatted_value: function(field, val){ - var value = val; - - if(field.df.fieldname==="docstatus") { - value = {0:"Draft", 1:"Submitted", 2:"Cancelled"}[value] || value; - } else if(field.df.original_type==="Check") { - value = {0:"No", 1:"Yes"}[cint(value)]; - } else if (field.df.original_type === "Duration") { - let duration_options = { - hide_days: field.df.hide_days, - hide_seconds: field.df.hide_seconds - }; - value = frappe.utils.get_formatted_duration(value, duration_options); - } - - value = frappe.format(value, field.df, {only_value: 1}); - return value; - } -}); - -frappe.ui.Filter = Class.extend({ - init: function(opts) { - $.extend(this, opts); - - this.doctype = this.flist.doctype; - this.make(); - this.make_select(); - this.set_events(); - }, - make: function() { - this.wrapper = $(frappe.render_template("edit_filter", {})) - .appendTo(this.flist.wrapper.find('.filter_area')); - }, - make_select: function() { - var me = this; - this.fieldselect = new frappe.ui.FieldSelect({ - parent: this.wrapper.find('.fieldname_select_area'), - doctype: this.doctype, - filter_fields: this.filter_fields, - select: function(doctype, fieldname) { - me.set_field(doctype, fieldname); - } - }); - if(this.fieldname) { - this.fieldselect.set_value(this._doctype || this.doctype, this.fieldname); - } - }, - set_events: function() { - var me = this; - - this.wrapper.find("a.remove-filter").on("click", function() { - me.remove(); - }); - - this.wrapper.find(".set-filter-and-run").on("click", function() { - me.wrapper.removeClass("is-new-filter"); - me.flist.base_list.run(); - me.apply(); - }); - - // add help for "in" codition - me.wrapper.find('.condition').change(function() { - if(!me.field) return; - var condition = $(this).val(); - if(in_list(["in", "like", "not in", "not like"], condition)) { - me.set_field(me.field.df.parent, me.field.df.fieldname, 'Data', condition); - if(!me.field.desc_area) { - me.field.desc_area = $('
').appendTo(me.field.wrapper); - } - // set description - me.field.desc_area.html((in_list(["in", "not in"], condition)==="in" - ? __("values separated by commas") - : __("use % as wildcard"))+'
'); - } else { - //if condition selected after refresh - me.set_field(me.field.df.parent, me.field.df.fieldname, null, condition); - } - }); - - // set the field - if(me.fieldname) { - // pre-sets given (could be via tags!) - return this.set_values(me._doctype, me.fieldname, me.condition, me.value); - } else { - me.set_field(me.doctype, 'name'); - } - }, - - apply: function() { - var f = this.get_value(); - - this.flist.remove(this); - this.flist.push_new_filter(f[0], f[1], f[2], f[3]); - this.remove(); - }, - - remove: function(dont_run) { - this.wrapper.remove(); - this.$btn_group && this.$btn_group.remove(); - this.field = null; - this.flist.update_filters(); - - if(!dont_run) { - this.flist.base_list.refresh(true); - } - }, - - set_values: function(doctype, fieldname, condition, value) { - // presents given (could be via tags!) - this.set_field(doctype, fieldname); - - // change 0,1 to Yes, No for check field type - if(this.field.df.original_type==='Check') { - if(value==0) value = 'No'; - else if(value==1) value = 'Yes'; - } - - if(condition) { - this.wrapper.find('.condition').val(condition).change(); - } - if(value!=null) { - return this.field.set_value(value); - } - }, - - set_field: function(doctype, fieldname, fieldtype, condition) { - var me = this; - - // set in fieldname (again) - var cur = me.field ? { - fieldname: me.field.df.fieldname, - fieldtype: me.field.df.fieldtype, - parent: me.field.df.parent, - } : {}; - - var original_docfield = me.fieldselect.fields_by_name[doctype][fieldname]; - if(!original_docfield) { - frappe.msgprint(__("Field {0} is not selectable.", [fieldname])); - return; - } - - var df = copy_dict(me.fieldselect.fields_by_name[doctype][fieldname]); - - // filter field shouldn't be read only or hidden - df.read_only = 0; - df.hidden = 0; - - if(!condition) this.set_default_condition(df, fieldtype); - this.set_fieldtype(df, fieldtype); - - // called when condition is changed, - // don't change if all is well - if(me.field && cur.fieldname == fieldname && df.fieldtype == cur.fieldtype && - df.parent == cur.parent) { - return; - } - - // clear field area and make field - me.fieldselect.selected_doctype = doctype; - me.fieldselect.selected_fieldname = fieldname; - - // save old text - var old_text = null; - if(me.field) { - old_text = me.field.get_value(); - } - - var field_area = me.wrapper.find('.filter_field').empty().get(0); - var f = frappe.ui.form.make_control({ - df: df, - parent: field_area, - only_input: true, - }) - f.refresh(); - - me.field = f; - if(old_text && me.field.df.fieldtype===cur.fieldtype) { - me.field.set_value(old_text); - } - - // run on enter - $(me.field.wrapper).find(':input').keydown(function(ev) { - if(ev.which==13) { - me.flist.base_list.run(); - } - }) - }, - - set_fieldtype: function(df, fieldtype) { - // reset - if(df.original_type) - df.fieldtype = df.original_type; - else - df.original_type = df.fieldtype; - - df.description = ''; df.reqd = 0; - df.ignore_link_validation = true; - - // given - if(fieldtype) { - df.fieldtype = fieldtype; - return; - } - - // scrub - if(df.fieldname=="docstatus") { - df.fieldtype="Select", - df.options=[ - {value:0, label:__("Draft")}, - {value:1, label:__("Submitted")}, - {value:2, label:__("Cancelled")} - ] - } else if(df.fieldtype=='Check') { - df.fieldtype='Select'; - df.options='No\nYes'; - } else if(['Text','Small Text','Text Editor','Code','Tag','Comments', - 'Dynamic Link','Read Only','Assign'].indexOf(df.fieldtype)!=-1) { - df.fieldtype = 'Data'; - } else if(df.fieldtype=='Link' && ['=', '!='].indexOf(this.wrapper.find('.condition').val())==-1) { - df.fieldtype = 'Data'; - } - if(df.fieldtype==="Data" && (df.options || "").toLowerCase()==="email") { - df.options = null; - } - if(this.wrapper.find('.condition').val()== "Between" && (df.fieldtype == 'Date' || df.fieldtype == 'Datetime')){ - df.fieldtype = 'DateRange'; - } - }, - - set_default_condition: function(df, fieldtype) { - if(!fieldtype) { - // set as "like" for data fields - if (df.fieldtype == 'Data') { - this.wrapper.find('.condition').val('like'); - } else if (df.fieldtype == 'Date' || df.fieldtype == 'Datetime'){ - this.wrapper.find('.condition').val('Between'); - }else{ - this.wrapper.find('.condition').val('='); - } - } - }, - - get_value: function() { - return [this.fieldselect.selected_doctype, - this.field.df.fieldname, this.get_condition(), this.get_selected_value()]; - }, - - get_selected_value: function() { - var val = this.field.get_value(); - - if(typeof val==='string') { - val = strip(val); - } - - if(this.field.df.original_type == 'Check') { - val = (val=='Yes' ? 1 :0); - } - - if(this.get_condition().indexOf('like', 'not like')!==-1) { - // automatically append wildcards - if(val) { - if(val.slice(0,1) !== "%") { - val = "%" + val; - } - if(val.slice(-1) !== "%") { - val = val + "%"; - } - } - } else if(in_list(["in", "not in"], this.get_condition())) { - if(val) { - val = $.map(val.split(","), function(v) { return strip(v); }); - } - } if(val === '%') { - val = ""; - } - - return val; - }, - - get_condition: function() { - return this.wrapper.find('.condition').val(); - }, - - freeze: function() { - if(this.$btn_group) { - // already made, just hide the condition setter - this.set_filter_button_text(); - this.wrapper.toggle(false); - return; - } - - var me = this; - - // add a button for new filter if missing - this.$btn_group = $(`
- -
`) - .insertAfter(this.flist.wrapper.find(".set-filters .new-filter")); - - this.set_filter_button_text(); - - this.$btn_group.find(".remove-filter").on("click", function() { - me.remove(); - }); - - this.$btn_group.find(".toggle-filter").on("click", function() { - $(this).closest('.show_filters').find('.filter_area').show(); - me.wrapper.toggle(); - }) - this.wrapper.toggle(false); - }, - - set_filter_button_text: function() { - var value = this.get_selected_value(); - value = this.flist.get_formatted_value(this.field, value); - - // for translations - // __("like"), __("not like"), __("in") - - this.$btn_group.find(".toggle-filter") - .html(repl('%(label)s %(condition)s "%(value)s"', { - label: __(this.field.df.label), - condition: __(this.get_condition()), - value: __(value), - })); - } - -}); - -// ') - .appendTo(this.parent) - .on("click", function () { $(this).select(); }); - this.select_input = this.$select.get(0); - this.awesomplete = new Awesomplete(this.select_input, { - minChars: 0, - maxItems: 99, - autoFirst: true, - list: me.options, - item: function(item, input) { - return $(repl('
  • %(label)s

  • ', item)) - .data("item.autocomplete", item) - .get(0); - } - }); - this.$select.on("awesomplete-select", function(e) { - var o = e.originalEvent; - var value = o.text.value; - var item = me.awesomplete.get_item(value); - me.selected_doctype = item.doctype; - me.selected_fieldname = item.fieldname; - if(me.select) me.select(item.doctype, item.fieldname); - }); - this.$select.on("awesomplete-selectcomplete", function(e) { - var o = e.originalEvent; - var value = o.text.value; - var item = me.awesomplete.get_item(value); - me.$select.val(item.label); - }); - - if(this.filter_fields) { - for(var i in this.filter_fields) - this.add_field_option(this.filter_fields[i]) - } else { - this.build_options(); - } - this.set_value(this.doctype, "name"); - window.last_filter = this; - }, - get_value: function() { - return this.selected_doctype ? this.selected_doctype + "." + this.selected_fieldname : null; - }, - val: function(value) { - if(value===undefined) { - return this.get_value(); - } else { - this.set_value(value); - } - }, - clear: function() { - this.selected_doctype = null; - this.selected_fieldname = null; - this.$select.val(""); - }, - set_value: function(doctype, fieldname) { - var me = this; - this.clear(); - if(!doctype) return; - - // old style - if(doctype.indexOf(".")!==-1) { - var parts = doctype.split("."); - doctype = parts[0]; - fieldname = parts[1]; - } - - $.each(this.options, function(i, v) { - if(v.doctype===doctype && v.fieldname===fieldname) { - me.selected_doctype = doctype; - me.selected_fieldname = fieldname; - me.$select.val(v.label); - return false; - } - }); - }, - build_options: function() { - var me = this; - me.table_fields = []; - var std_filters = $.map(frappe.model.std_fields, function(d) { - var opts = {parent: me.doctype} - if(d.fieldname=="name") opts.options = me.doctype; - return $.extend(copy_dict(d), opts); - }); - - // add parenttype column - var doctype_obj = locals['DocType'][me.doctype]; - if(doctype_obj && cint(doctype_obj.istable)) { - std_filters = std_filters.concat([{ - fieldname: 'parent', - fieldtype: 'Data', - label: 'Parent', - parent: me.doctype, - }]); - } - - // blank - if(this.with_blank) { - this.options.push({ - label:"", - value:"", - }) - } - - // main table - var main_table_fields = std_filters.concat(frappe.meta.docfield_list[me.doctype]); - $.each(frappe.utils.sort(main_table_fields, "label", "string"), function(i, df) { - // show fields where user has read access and if report hide flag is not set - if(frappe.perm.has_perm(me.doctype, df.permlevel, "read") && !df.report_hide) - me.add_field_option(df); - }); - - // child tables - $.each(me.table_fields, function(i, table_df) { - if(table_df.options) { - var child_table_fields = [].concat(frappe.meta.docfield_list[table_df.options]); - $.each(frappe.utils.sort(child_table_fields, "label", "string"), function(i, df) { - // show fields where user has read access and if report hide flag is not set - if(frappe.perm.has_perm(me.doctype, df.permlevel, "read") && !df.report_hide) - me.add_field_option(df); - }); - } - }); - }, - - add_field_option: function(df) { - var me = this; - if(me.doctype && df.parent==me.doctype) { - var label = __(df.label); - var table = me.doctype; - if(frappe.model.table_fields.includes(df.fieldtype)) me.table_fields.push(df); - } else { - var label = __(df.label) + ' (' + __(df.parent) + ')'; - var table = df.parent; - } - if(frappe.model.no_value_type.indexOf(df.fieldtype) == -1 && - !(me.fields_by_name[df.parent] && me.fields_by_name[df.parent][df.fieldname])) { - this.options.push({ - label: label, - value: table + "." + df.fieldname, - fieldname: df.fieldname, - doctype: df.parent - }); - if(!me.fields_by_name[df.parent]) me.fields_by_name[df.parent] = {}; - me.fields_by_name[df.parent][df.fieldname] = df; - } - }, -}) From 64a03125460fec1ac6e6c7352a3f5e68c8f7ee08 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Wed, 22 Jul 2020 15:51:07 +0530 Subject: [PATCH 093/191] feat: always render footer links layout --- frappe/templates/includes/footer/footer_links.html | 3 --- 1 file changed, 3 deletions(-) diff --git a/frappe/templates/includes/footer/footer_links.html b/frappe/templates/includes/footer/footer_links.html index fe9f69fed3..e8bfdadb7f 100644 --- a/frappe/templates/includes/footer/footer_links.html +++ b/frappe/templates/includes/footer/footer_links.html @@ -7,8 +7,6 @@ {%- endif -%} {% endmacro %} - -{% if footer_items -%} -{% endif %} From 0ba3fb5a92d3bfca9b781f5cebb984bf08328392 Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Wed, 22 Jul 2020 15:53:10 +0530 Subject: [PATCH 094/191] fix: proper sequence of backups --- frappe/utils/backups.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frappe/utils/backups.py b/frappe/utils/backups.py index 707a9ac511..56f3b9cafb 100644 --- a/frappe/utils/backups.py +++ b/frappe/utils/backups.py @@ -224,8 +224,8 @@ def fetch_latest_backups(with_files=True, recent=3): odb.get_backup(older_than=recent, ignore_files=not with_files) return { - "database": odb.backup_path_files, - "public": odb.backup_path_db, + "database": odb.backup_path_db, + "public": odb.backup_path_files, "private": odb.backup_path_private_files, "config": odb.site_config_backup_path } From c64df2787f8328e6d7e46c4cb722ec27820eb722 Mon Sep 17 00:00:00 2001 From: prssanna Date: Wed, 22 Jul 2020 16:51:17 +0530 Subject: [PATCH 095/191] fix: use get_filter_value in filter_list --- frappe/public/js/frappe/ui/filters/filter.js | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/frappe/public/js/frappe/ui/filters/filter.js b/frappe/public/js/frappe/ui/filters/filter.js index 7c6d532f5c..bfc0b1449d 100644 --- a/frappe/public/js/frappe/ui/filters/filter.js +++ b/frappe/public/js/frappe/ui/filters/filter.js @@ -249,7 +249,7 @@ frappe.ui.Filter = class { let args = {}; if (this.filters_config[condition].depends_on) { const field_name = this.filters_config[condition].depends_on; - const filter_value = this.get_filter_value(field_name); + const filter_value = this.filter_list.get_filter_value(fieldname); args[field_name] = filter_value; } frappe @@ -265,10 +265,6 @@ frappe.ui.Filter = class { } } - get_filter_value(fieldname) { - return this.filter_list.get_filter_value(fieldname); - } - make_field(df, old_fieldtype) { let old_text = this.field ? this.field.get_value() : null; this.hide_invalid_conditions(df.fieldtype, df.original_type); From a2b2c9c926b1fd2be8192499dc8a21b250019eaa Mon Sep 17 00:00:00 2001 From: Faris Ansari Date: Wed, 22 Jul 2020 17:54:48 +0530 Subject: [PATCH 096/191] fix: Validate select values in Column --- frappe/core/doctype/data_import/importer.py | 29 ++++++++++++++++----- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/frappe/core/doctype/data_import/importer.py b/frappe/core/doctype/data_import/importer.py index 910e42af1a..729f4e8956 100644 --- a/frappe/core/doctype/data_import/importer.py +++ b/frappe/core/doctype/data_import/importer.py @@ -615,7 +615,7 @@ class Row: def validate_value(self, value, col): df = col.df if df.fieldtype == "Select": - select_options = [d for d in (df.options or '').split('\n') if d] + select_options = get_select_options(df) if select_options and value not in select_options: options_string = ", ".join([frappe.bold(d) for d in select_options]) msg = _("Value must be one of {0}").format(options_string) @@ -971,12 +971,23 @@ class Column: # guess date format self.date_format = self.guess_date_format_for_column() if not self.date_format: - self.date_format = '%Y-%m-%d' - self.warnings.append({ - 'col': self.column_number, - 'message': _("Date format could not determined from the values in this column. Defaulting to yyyy-mm-dd."), - 'type': 'info' - }) + elif self.df.fieldtype == "Select": + options = get_select_options(self.df) + if options: + values = list(set([cstr(v) for v in self.column_values[1:] if v])) + invalid = list(set(values) - set(options)) + if invalid: + valid_values = ", ".join([frappe.bold(o) for o in options]) + invalid_values = ", ".join([frappe.bold(i) for i in invalid]) + self.warnings.append( + { + "col": self.column_number, + "message": ( + "The following values are invalid: {0}. Values must be" + " one of {1}".format(invalid_values, valid_values) + ), + } + ) def as_dict(self): d = frappe._dict() @@ -1170,3 +1181,7 @@ def df_as_json(df): 'parent': df.parent, 'default': df.default } + + +def get_select_options(df): + return [d for d in (df.options or "").split("\n") if d] From 5dcb910ae9e040f76b1e7e18354e31c2dd7ce729 Mon Sep 17 00:00:00 2001 From: Faris Ansari Date: Wed, 22 Jul 2020 17:57:01 +0530 Subject: [PATCH 097/191] fix: Set default date format for parsed date values --- frappe/core/doctype/data_import/importer.py | 32 ++++++++++++++++----- 1 file changed, 25 insertions(+), 7 deletions(-) diff --git a/frappe/core/doctype/data_import/importer.py b/frappe/core/doctype/data_import/importer.py index 729f4e8956..47113d35cd 100644 --- a/frappe/core/doctype/data_import/importer.py +++ b/frappe/core/doctype/data_import/importer.py @@ -7,7 +7,7 @@ import io import frappe import timeit import json -from datetime import datetime +from datetime import datetime, date from frappe import _ from frappe.utils import cint, flt, update_progress_bar, cstr from frappe.utils.csvutils import read_csv_content, get_csv_content_from_google_sheets @@ -668,7 +668,7 @@ class Row: def parse_value(self, value, col): df = col.df - if isinstance(value, datetime) and df.fieldtype in ["Date", "Datetime"]: + if isinstance(value, (datetime, date)) and df.fieldtype in ["Date", "Datetime"]: return value value = cstr(value) @@ -689,7 +689,7 @@ class Row: return value def get_date(self, value, column): - if isinstance(value, datetime): + if isinstance(value, (datetime, date)): return value date_format = column.date_format @@ -918,13 +918,20 @@ class Column: self.skip_import = skip_import def guess_date_format_for_column(self): - """ Guesses date format for a column by parsing all the values in the column, + """Guesses date format for a column by parsing all the values in the column, getting the date format and then returning the one which has the maximum frequency """ - date_formats = [ - frappe.utils.guess_date_format(d) for d in self.column_values if isinstance(d, str) - ] + def guess_date_format(d): + if isinstance(d, (datetime, date)): + if self.df.fieldtype == "Date": + return "%Y-%m-%d" + if self.df.fieldtype == "Datetime": + return "%Y-%m-%d %H:%M:%S" + if isinstance(d, str): + return frappe.utils.guess_date_format(d) + + date_formats = [guess_date_format(d) for d in self.column_values] date_formats = [d for d in date_formats if d] if not date_formats: return @@ -971,6 +978,17 @@ class Column: # guess date format self.date_format = self.guess_date_format_for_column() if not self.date_format: + self.date_format = "%Y-%m-%d" + self.warnings.append( + { + "col": self.column_number, + "message": _( + "Date format could not be determined from the values in" + " this column. Defaulting to yyyy-mm-dd." + ), + "type": "info", + } + ) elif self.df.fieldtype == "Select": options = get_select_options(self.df) if options: From 015ab803a36355cd02df0e640dba0897515d8ebf Mon Sep 17 00:00:00 2001 From: Faris Ansari Date: Wed, 22 Jul 2020 17:57:13 +0530 Subject: [PATCH 098/191] style: formatting --- frappe/core/doctype/data_import/importer.py | 63 ++++++++++----------- 1 file changed, 31 insertions(+), 32 deletions(-) diff --git a/frappe/core/doctype/data_import/importer.py b/frappe/core/doctype/data_import/importer.py index 47113d35cd..6bc8b0586f 100644 --- a/frappe/core/doctype/data_import/importer.py +++ b/frappe/core/doctype/data_import/importer.py @@ -233,7 +233,7 @@ class Importer: return updated_doc else: # throw if no changes - frappe.throw('No changes to update') + frappe.throw("No changes to update") def get_eta(self, current, total, processing_time): self.last_eta = getattr(self, "last_eta", 0) @@ -322,7 +322,7 @@ class ImportFile: if isinstance(file, frappe.string_types): if frappe.db.exists("File", {"file_url": file}): self.file_doc = frappe.get_doc("File", {"file_url": file}) - elif 'docs.google.com/spreadsheets' in file: + elif "docs.google.com/spreadsheets" in file: self.google_sheets_url = file elif os.path.exists(file): self.file_path = file @@ -348,7 +348,7 @@ class ImportFile: elif self.google_sheets_url: content = get_csv_content_from_google_sheets(self.google_sheets_url) - extension = 'csv' + extension = "csv" if not content: frappe.throw(_("Invalid or corrupted content for import")) @@ -620,11 +620,7 @@ class Row: options_string = ", ".join([frappe.bold(d) for d in select_options]) msg = _("Value must be one of {0}").format(options_string) self.warnings.append( - { - "row": self.row_number, - "field": df_as_json(df), - "message": msg, - } + {"row": self.row_number, "field": df_as_json(df), "message": msg,} ) return @@ -635,11 +631,7 @@ class Row: frappe.bold(value), frappe.bold(df.options) ) self.warnings.append( - { - "row": self.row_number, - "field": df_as_json(df), - "message": msg, - } + {"row": self.row_number, "field": df_as_json(df), "message": msg,} ) return elif df.fieldtype in ["Date", "Datetime"]: @@ -786,9 +778,7 @@ class Header(Row): for j, header in enumerate(row): column_values = [get_item_at_index(r, j) for r in raw_data] map_to_field = column_to_field_map.get(str(j)) - column = Column( - j, header, self.doctype, column_values, map_to_field, self.seen - ) + column = Column(j, header, self.doctype, column_values, map_to_field, self.seen) self.seen.append(header) self.columns.append(column) @@ -962,18 +952,26 @@ class Column: if not self.df: return - if self.df.fieldtype == 'Link': + if self.df.fieldtype == "Link": # find all values that dont exist values = list(set([cstr(v) for v in self.column_values[1:] if v])) - exists = [d.name for d in frappe.db.get_all(self.df.options, filters={'name': ('in', values)})] + exists = [ + d.name for d in frappe.db.get_all(self.df.options, filters={"name": ("in", values)}) + ] not_exists = list(set(values) - set(exists)) if not_exists: - missing_values = ', '.join(not_exists) - self.warnings.append({ - 'col': self.column_number, - 'message': "The following values do not exist for {}: {}".format(self.df.options, missing_values), - 'type': 'warning' - }) + missing_values = ", ".join(not_exists) + self.warnings.append( + { + "col": self.column_number, + "message": ( + "The following values do not exist for {}: {}".format( + self.df.options, missing_values + ) + ), + "type": "warning", + } + ) elif self.df.fieldtype in ("Date", "Time", "Datetime"): # guess date format self.date_format = self.guess_date_format_for_column() @@ -1016,7 +1014,7 @@ class Column: d.map_to_field = self.map_to_field d.date_format = self.date_format d.df = self.df - if hasattr(self.df, 'is_child_table_field'): + if hasattr(self.df, "is_child_table_field"): d.is_child_table_field = self.df.is_child_table_field d.child_table_df = self.df.child_table_df d.skip_import = self.skip_import @@ -1096,7 +1094,7 @@ def build_fields_dict_for_column_matching(parent_doctype): # other fields fields = get_standard_fields(doctype) + frappe.get_meta(doctype).fields for df in fields: - label = (df.label or '').strip() + label = (df.label or "").strip() fieldtype = df.fieldtype or "Data" parent = df.parent or parent_doctype if fieldtype not in no_value_fields: @@ -1190,14 +1188,15 @@ def get_user_format(date_format): .replace("%d", "dd") ) + def df_as_json(df): return { - 'fieldname': df.fieldname, - 'fieldtype': df.fieldtype, - 'label': df.label, - 'options': df.options, - 'parent': df.parent, - 'default': df.default + "fieldname": df.fieldname, + "fieldtype": df.fieldtype, + "label": df.label, + "options": df.options, + "parent": df.parent, + "default": df.default, } From e34e48bb2f503c20899b4f4e6f56ca74ffbbb1ee Mon Sep 17 00:00:00 2001 From: Faris Ansari Date: Wed, 22 Jul 2020 17:57:57 +0530 Subject: [PATCH 099/191] fix: Show Column Name in warnings header --- frappe/core/doctype/data_import/data_import.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/frappe/core/doctype/data_import/data_import.js b/frappe/core/doctype/data_import/data_import.js index 6a922618cb..0e827a42d8 100644 --- a/frappe/core/doctype/data_import/data_import.js +++ b/frappe/core/doctype/data_import/data_import.js @@ -317,6 +317,7 @@ frappe.ui.form.on('Data Import', { }, show_import_warnings(frm, preview_data) { + let columns = preview_data.columns; let warnings = JSON.parse(frm.doc.template_warnings || '[]'); warnings = warnings.concat(preview_data.warnings || []); @@ -367,11 +368,13 @@ frappe.ui.form.on('Data Import', { .map(warning => { let header = ''; if (warning.col) { - header = __('Column {0}', [warning.col]); + let column_number = `${__('Column {0}', [warning.col])}`; + let column_header = columns[warning.col].header_title; + header = `${column_number} (${column_header})`; } return `
    -
    ${header}
    +
    ${header}
    ${warning.message}
    `; From de07437015dd3a21cb1a4db1bc0fdf68230a35a3 Mon Sep 17 00:00:00 2001 From: Faris Ansari Date: Wed, 22 Jul 2020 17:58:16 +0530 Subject: [PATCH 100/191] fix: Show Pending imports as Not Started --- frappe/core/doctype/data_import/data_import_list.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/frappe/core/doctype/data_import/data_import_list.js b/frappe/core/doctype/data_import/data_import_list.js index 1dee4319f9..0eb05aa354 100644 --- a/frappe/core/doctype/data_import/data_import_list.js +++ b/frappe/core/doctype/data_import/data_import_list.js @@ -17,6 +17,7 @@ frappe.listview_settings['Data Import'] = { get_indicator: function(doc) { var colors = { 'Pending': 'orange', + 'Not Started': 'orange', 'Partial Success': 'orange', 'Success': 'green', 'In Progress': 'orange', @@ -26,6 +27,9 @@ frappe.listview_settings['Data Import'] = { if (imports_in_progress.includes(doc.name)) { status = 'In Progress'; } + if (status == 'Pending') { + status = 'Not Started'; + } return [__(status), colors[status], 'status,=,' + doc.status]; }, formatters: { From b0786fd5e5ade05555f16d4e0a1b50a6bbe611d0 Mon Sep 17 00:00:00 2001 From: Faris Ansari Date: Wed, 22 Jul 2020 17:58:36 +0530 Subject: [PATCH 101/191] fix: Human friendly time format --- frappe/public/js/frappe/data_import/import_preview.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/frappe/public/js/frappe/data_import/import_preview.js b/frappe/public/js/frappe/data_import/import_preview.js index 4edcb87aeb..6c17cb4351 100644 --- a/frappe/public/js/frappe/data_import/import_preview.js +++ b/frappe/public/js/frappe/data_import/import_preview.js @@ -98,6 +98,9 @@ frappe.data_import.ImportPreview = class ImportPreview { .replace('%y', 'yy') .replace('%m', 'mm') .replace('%d', 'dd') + .replace('%H', 'HH') + .replace('%M', 'mm') + .replace('%S', 'ss') : null; let column_title = ` @@ -354,4 +357,4 @@ function get_fields_as_options(doctype, column_map) { }); }) ); -} \ No newline at end of file +} From b541ffac68d0ff140c937d40a2a2648ff754b0c2 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Wed, 22 Jul 2020 19:30:11 +0530 Subject: [PATCH 102/191] Revert "fix: set page actions after setting read only" --- frappe/public/js/frappe/form/form.js | 1 - 1 file changed, 1 deletion(-) diff --git a/frappe/public/js/frappe/form/form.js b/frappe/public/js/frappe/form/form.js index 43128065bc..c0b76ee94d 100644 --- a/frappe/public/js/frappe/form/form.js +++ b/frappe/public/js/frappe/form/form.js @@ -1409,7 +1409,6 @@ frappe.ui.form.Form = class FrappeForm { }; } this.perm = perm; - this.toolbar.set_page_action(); } trigger(event, doctype, docname) { From 4b7e36f90da10a8c49fdbb827ea922e1f548e1e5 Mon Sep 17 00:00:00 2001 From: Faris Ansari Date: Wed, 22 Jul 2020 19:30:17 +0530 Subject: [PATCH 103/191] fix: Handle child table row additions --- frappe/core/doctype/data_import/importer.py | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/frappe/core/doctype/data_import/importer.py b/frappe/core/doctype/data_import/importer.py index 6bc8b0586f..34e53488e5 100644 --- a/frappe/core/doctype/data_import/importer.py +++ b/frappe/core/doctype/data_import/importer.py @@ -602,12 +602,20 @@ class Row: is_table = frappe.get_meta(doctype).istable is_update = self.import_type == UPDATE - if is_table and is_update and doc.get("name") in INVALID_VALUES: - # for table rows being inserted in update - # create a new doc with defaults set - new_doc = frappe.new_doc(doctype, as_dict=True) - new_doc.update(doc) - doc = new_doc + if is_table and is_update: + # check if the row already exists + # if yes, fetch the original doc so that it is not updated + # if no, create a new doc + id_field = get_id_field(doctype) + id_value = doc.get(id_field.fieldname) + if id_value and frappe.db.exists(doctype, id_value): + doc = frappe.get_doc(doctype, id_value) + else: + # for table rows being inserted in update + # create a new doc with defaults set + new_doc = frappe.new_doc(doctype, as_dict=True) + new_doc.update(doc) + doc = new_doc self.check_mandatory_fields(doctype, doc, table_df) return doc From dd038a0cc201f0b5061864b8960892a419b4c0d0 Mon Sep 17 00:00:00 2001 From: Faris Ansari Date: Wed, 22 Jul 2020 19:30:41 +0530 Subject: [PATCH 104/191] fix: Show autoname column instead of ID in export --- .../js/frappe/data_import/data_exporter.js | 22 ++++++++++++------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/frappe/public/js/frappe/data_import/data_exporter.js b/frappe/public/js/frappe/data_import/data_exporter.js index f6af338235..8651f2083d 100644 --- a/frappe/public/js/frappe/data_import/data_exporter.js +++ b/frappe/public/js/frappe/data_import/data_exporter.js @@ -274,23 +274,29 @@ frappe.data_import.DataExporter = class DataExporter { ? this.column_map[child_fieldname] : this.column_map[doctype]; - let is_field_mandatory = df => (df.fieldname === 'name' && !child_fieldname) - || (df.reqd && this.exporting_for == 'Insert New Records'); + let is_field_mandatory = df => { + if (df.reqd && this.exporting_for == 'Insert New Records') { + return true; + } + if (autoname_field && df.fieldname == autoname_field.fieldname) { + return true; + } + if (df.fieldname === 'name') { + return true; + } + return false; + } return fields .filter(df => { - if (autoname_field && df.fieldname === autoname_field.fieldname) { + if (autoname_field && df.fieldname === 'name') { return false; } return true; }) .map(df => { - let label = __(df.label); - if (autoname_field && df.fieldname === 'name') { - label = label + ` (${__(autoname_field.label)})`; - } return { - label, + label: __(df.label), value: df.fieldname, danger: is_field_mandatory(df), checked: false, From 5558b20834fa0800da42a6dbab71b1e308c8e863 Mon Sep 17 00:00:00 2001 From: Faris Ansari Date: Wed, 22 Jul 2020 19:52:56 +0530 Subject: [PATCH 105/191] style: missing semicolon --- frappe/public/js/frappe/data_import/data_exporter.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/public/js/frappe/data_import/data_exporter.js b/frappe/public/js/frappe/data_import/data_exporter.js index 8651f2083d..dee4839b34 100644 --- a/frappe/public/js/frappe/data_import/data_exporter.js +++ b/frappe/public/js/frappe/data_import/data_exporter.js @@ -285,7 +285,7 @@ frappe.data_import.DataExporter = class DataExporter { return true; } return false; - } + }; return fields .filter(df => { From 51a473336a56fa559ba2b3b7438495b3dcc6604c Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Wed, 22 Jul 2020 21:55:56 +0530 Subject: [PATCH 106/191] fix: Margin top for child table index checkboxes --- frappe/public/less/desk.less | 4 ---- frappe/public/less/form_grid.less | 4 ++++ 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/frappe/public/less/desk.less b/frappe/public/less/desk.less index d951178be0..36ceed9bfa 100644 --- a/frappe/public/less/desk.less +++ b/frappe/public/less/desk.less @@ -789,10 +789,6 @@ li.user-progress { position: relative; } -.row-index input[type=checkbox] { - margin-top: 12px; -} - @checkbox-height: 14px; // custom font awesome checkbox diff --git a/frappe/public/less/form_grid.less b/frappe/public/less/form_grid.less index 5cb04a252c..d9e7d8bceb 100644 --- a/frappe/public/less/form_grid.less +++ b/frappe/public/less/form_grid.less @@ -98,6 +98,10 @@ text-align: right; } +.grid-row .grid-row-check { + margin-top: 12px; +} + .grid-row > .row { .col:last-child { margin-right: -10px; From 6187de8e3da34cc4f0f74198f1148170c3d9d83d Mon Sep 17 00:00:00 2001 From: prssanna Date: Thu, 23 Jul 2020 11:12:13 +0530 Subject: [PATCH 107/191] fix: make is_standard read only if not in developer mode --- frappe/desk/doctype/dashboard/dashboard.json | 6 +++--- frappe/desk/doctype/dashboard_chart/dashboard_chart.json | 6 +++--- frappe/desk/doctype/number_card/number_card.json | 6 +++--- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/frappe/desk/doctype/dashboard/dashboard.json b/frappe/desk/doctype/dashboard/dashboard.json index 0c54f1ee7f..95b0846452 100644 --- a/frappe/desk/doctype/dashboard/dashboard.json +++ b/frappe/desk/doctype/dashboard/dashboard.json @@ -52,10 +52,10 @@ }, { "default": "0", - "depends_on": "eval: frappe.boot.developer_mode", "fieldname": "is_standard", "fieldtype": "Check", - "label": "Is Standard" + "label": "Is Standard", + "read_only_depends_on": "eval: !frappe.boot.developer_mode" }, { "depends_on": "eval: doc.is_standard", @@ -67,7 +67,7 @@ } ], "links": [], - "modified": "2020-07-22 11:23:37.858568", + "modified": "2020-07-23 11:05:41.890459", "modified_by": "Administrator", "module": "Desk", "name": "Dashboard", diff --git a/frappe/desk/doctype/dashboard_chart/dashboard_chart.json b/frappe/desk/doctype/dashboard_chart/dashboard_chart.json index f0616dddd0..d4bba53068 100644 --- a/frappe/desk/doctype/dashboard_chart/dashboard_chart.json +++ b/frappe/desk/doctype/dashboard_chart/dashboard_chart.json @@ -238,10 +238,10 @@ }, { "default": "0", - "depends_on": "eval: frappe.boot.developer_mode", "fieldname": "is_standard", "fieldtype": "Check", - "label": "Is Standard" + "label": "Is Standard", + "read_only_depends_on": "eval: !frappe.boot.developer_mode" }, { "depends_on": "eval: doc.is_standard", @@ -271,7 +271,7 @@ } ], "links": [], - "modified": "2020-07-22 11:32:51.185499", + "modified": "2020-07-23 11:10:33.509497", "modified_by": "Administrator", "module": "Desk", "name": "Dashboard Chart", diff --git a/frappe/desk/doctype/number_card/number_card.json b/frappe/desk/doctype/number_card/number_card.json index 0d1cc279c8..d3e9598eb7 100644 --- a/frappe/desk/doctype/number_card/number_card.json +++ b/frappe/desk/doctype/number_card/number_card.json @@ -113,10 +113,10 @@ }, { "default": "0", - "depends_on": "eval: frappe.boot.developer_mode", "fieldname": "is_standard", "fieldtype": "Check", - "label": "Is Standard" + "label": "Is Standard", + "read_only_depends_on": "eval: !frappe.boot.developer_mode" }, { "depends_on": "eval: doc.is_standard", @@ -191,7 +191,7 @@ } ], "links": [], - "modified": "2020-07-22 11:26:32.039023", + "modified": "2020-07-23 11:11:03.391719", "modified_by": "Administrator", "module": "Desk", "name": "Number Card", From 33e0b785764f873c75b20e43a1e940c6bc25e715 Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Thu, 23 Jul 2020 15:06:46 +0530 Subject: [PATCH 108/191] fix: validate recipients before sending newsletter --- frappe/email/doctype/newsletter/newsletter.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/frappe/email/doctype/newsletter/newsletter.py b/frappe/email/doctype/newsletter/newsletter.py index a82b52a663..48688afdb6 100755 --- a/frappe/email/doctype/newsletter/newsletter.py +++ b/frappe/email/doctype/newsletter/newsletter.py @@ -107,6 +107,9 @@ class Newsletter(WebsiteGenerator): if self.get("__islocal"): throw(_("Please save the Newsletter before sending")) + if not self.recipients: + frappe.throw(_("Newsletter should have at least one recipient")) + def get_context(self, context): newsletters = get_newsletter_list("Newsletter", None, None, 0) if newsletters: From 24adc8c3625f146ecae895ac57bdd8ff081d92b8 Mon Sep 17 00:00:00 2001 From: Suraj Shetty <13928957+surajshetty3416@users.noreply.github.com> Date: Thu, 23 Jul 2020 17:34:18 +0530 Subject: [PATCH 109/191] fix: Use find instead of filter --- frappe/public/js/frappe/ui/filters/filter_list.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/frappe/public/js/frappe/ui/filters/filter_list.js b/frappe/public/js/frappe/ui/filters/filter_list.js index 9a35a9bb5f..ee04fb6077 100644 --- a/frappe/public/js/frappe/ui/filters/filter_list.js +++ b/frappe/public/js/frappe/ui/filters/filter_list.js @@ -112,7 +112,8 @@ frappe.ui.FilterGroup = class { } get_filter_value(fieldname) { - return this.filters.filter(f => f.fieldname == fieldname)[0].value; + let filter_obj = this.filters.find(f => f.fieldname == filename) || {}; + return filter_obj.value; } filter_exists(filter_value) { From 5f5facfb0a0c6d7725c309708eadb90af50c7278 Mon Sep 17 00:00:00 2001 From: prssanna Date: Thu, 23 Jul 2020 17:40:51 +0530 Subject: [PATCH 110/191] fix: set frm.filters to empty array for number card --- frappe/desk/doctype/dashboard_chart/dashboard_chart.js | 2 +- frappe/desk/doctype/number_card/number_card.js | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/frappe/desk/doctype/dashboard_chart/dashboard_chart.js b/frappe/desk/doctype/dashboard_chart/dashboard_chart.js index 65db4839a1..8d89cc2f31 100644 --- a/frappe/desk/doctype/dashboard_chart/dashboard_chart.js +++ b/frappe/desk/doctype/dashboard_chart/dashboard_chart.js @@ -291,7 +291,7 @@ frappe.ui.form.on('Dashboard Chart', { set_filters && frm.set_value('filters_json', JSON.stringify(filters)); } - let fields; + let fields = []; if (is_document_type) { fields = [ { diff --git a/frappe/desk/doctype/number_card/number_card.js b/frappe/desk/doctype/number_card/number_card.js index 2ae163d7aa..63b41b956e 100644 --- a/frappe/desk/doctype/number_card/number_card.js +++ b/frappe/desk/doctype/number_card/number_card.js @@ -116,6 +116,7 @@ frappe.ui.form.on('Number Card', { }, report_name: function(frm) { + frm.filters = []; frm.set_value('filters_json', '{}'); frm.set_value('dynamic_filters_json', '{}'); frm.set_df_property('report_field', 'options', []); @@ -247,7 +248,7 @@ frappe.ui.form.on('Number Card', { set_filters && frm.set_value('filters_json', JSON.stringify(filters)); } - let fields; + let fields = []; if (is_document_type) { fields = [ { From 771feca0730a563cd3278fc4de11b10060389b27 Mon Sep 17 00:00:00 2001 From: prssanna Date: Thu, 23 Jul 2020 17:41:55 +0530 Subject: [PATCH 111/191] feat: remove restricted button in dashboard --- frappe/core/page/dashboard/dashboard.js | 7 ------- frappe/public/js/frappe/views/dashboard/dashboard_view.js | 4 ---- 2 files changed, 11 deletions(-) diff --git a/frappe/core/page/dashboard/dashboard.js b/frappe/core/page/dashboard/dashboard.js index cee230265f..7e45163a7e 100644 --- a/frappe/core/page/dashboard/dashboard.js +++ b/frappe/core/page/dashboard/dashboard.js @@ -26,13 +26,6 @@ class Dashboard {
    `).appendTo(this.wrapper.find(".page-content").empty()); this.container = this.wrapper.find(".dashboard-graph"); this.page = wrapper.page; - - this.page.set_title_sub( - $(``) - ); } show() { diff --git a/frappe/public/js/frappe/views/dashboard/dashboard_view.js b/frappe/public/js/frappe/views/dashboard/dashboard_view.js index 83f45da5be..6f6279fd08 100644 --- a/frappe/public/js/frappe/views/dashboard/dashboard_view.js +++ b/frappe/public/js/frappe/views/dashboard/dashboard_view.js @@ -44,10 +44,6 @@ frappe.views.DashboardView = class DashboardView extends frappe.views.ListView {
    ${dashboard_name}
    -
    ${__('Customize')}
    ${__('Reset')} From b95d28d5f126785e2198eae1b5eba556599dd814 Mon Sep 17 00:00:00 2001 From: prssanna Date: Thu, 23 Jul 2020 17:43:01 +0530 Subject: [PATCH 112/191] fix: remove unused style --- frappe/core/page/dashboard/dashboard.css | 5 ----- frappe/public/less/dashboard_view.less | 7 ------- 2 files changed, 12 deletions(-) delete mode 100644 frappe/core/page/dashboard/dashboard.css diff --git a/frappe/core/page/dashboard/dashboard.css b/frappe/core/page/dashboard/dashboard.css deleted file mode 100644 index b319cc1ed2..0000000000 --- a/frappe/core/page/dashboard/dashboard.css +++ /dev/null @@ -1,5 +0,0 @@ -.restricted-button { - cursor: default; - position: relative; - right: -5px; -} \ No newline at end of file diff --git a/frappe/public/less/dashboard_view.less b/frappe/public/less/dashboard_view.less index ab78fa5b2a..17fa52cc0f 100644 --- a/frappe/public/less/dashboard_view.less +++ b/frappe/public/less/dashboard_view.less @@ -33,13 +33,6 @@ line-height: 1.5em; vertical-align: text-bottom; } - - .restricted-button { - cursor: default; - position: relative; - right: 5px; - top: -3px; - } } .customize-dashboard { From 160a84cee4177cf4fa828fd098ebdae89d307040 Mon Sep 17 00:00:00 2001 From: Prssanna Desai Date: Thu, 23 Jul 2020 17:55:23 +0530 Subject: [PATCH 113/191] fix: use correct variable --- frappe/public/js/frappe/ui/filters/filter_list.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/public/js/frappe/ui/filters/filter_list.js b/frappe/public/js/frappe/ui/filters/filter_list.js index ee04fb6077..6c577aa0bc 100644 --- a/frappe/public/js/frappe/ui/filters/filter_list.js +++ b/frappe/public/js/frappe/ui/filters/filter_list.js @@ -112,7 +112,7 @@ frappe.ui.FilterGroup = class { } get_filter_value(fieldname) { - let filter_obj = this.filters.find(f => f.fieldname == filename) || {}; + let filter_obj = this.filters.find(f => f.fieldname == fieldname) || {}; return filter_obj.value; } From 047280013dcc6b8c55fdf5ad4bf44270fc81b3bc Mon Sep 17 00:00:00 2001 From: gavin Date: Thu, 23 Jul 2020 18:42:44 +0530 Subject: [PATCH 114/191] fix: Fetch latest files filtered by names (#11056) --- frappe/utils/backups.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/frappe/utils/backups.py b/frappe/utils/backups.py index bb03c85bf9..9f9a64f296 100644 --- a/frappe/utils/backups.py +++ b/frappe/utils/backups.py @@ -104,11 +104,11 @@ class BackupGenerator: this_file = cstr(this_file) this_file_path = os.path.join(get_backup_path(), this_file) if not is_file_old(this_file_path, older_than): - if "_private_files" in this_file_path: + if "-private-files" in this_file_path: backup_path_private_files = this_file_path - elif "_files" in this_file_path: + elif "-files" in this_file_path: backup_path_files = this_file_path - elif "_database" in this_file_path: + elif "-database" in this_file_path: backup_path_db = this_file_path elif "site_config" in this_file_path: site_config_backup_path = this_file_path From 441630acc48bc58b71bd0fe100b73559e5180150 Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Thu, 23 Jul 2020 23:43:35 +0530 Subject: [PATCH 115/191] fix: check file backup config before backing up files --- .../doctype/google_drive/google_drive.py | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/frappe/integrations/doctype/google_drive/google_drive.py b/frappe/integrations/doctype/google_drive/google_drive.py index c110694dff..58f28f882a 100644 --- a/frappe/integrations/doctype/google_drive/google_drive.py +++ b/frappe/integrations/doctype/google_drive/google_drive.py @@ -189,14 +189,17 @@ def upload_system_backup_to_google_drive(): if frappe.flags.create_new_backup: set_progress(1, "Backing up Data.") backup = new_backup() - fileurl_backup = backup.backup_path_db - fileurl_site_config = backup.site_config_backup_path - fileurl_public_files = backup.backup_path_files - fileurl_private_files = backup.backup_path_private_files - else: - fileurl_backup, fileurl_site_config, fileurl_public_files, fileurl_private_files = get_latest_backup_file(with_files=True) + file_urls = [] + file_urls.append(backup.backup_path_db) + file_urls.append(backup.site_config_backup_path) - for fileurl in [fileurl_backup, fileurl_site_config, fileurl_public_files, fileurl_private_files]: + if account.file_backup: + file_urls.append(backup.backup_path_files) + file_urls.append(backup.backup_path_private_files) + else: + file_urls = get_latest_backup_file(with_files=account.file_backup) + + for fileurl in file_urls: if not fileurl: continue From 47b0fb3de4882787a910938feff605f880cc44b3 Mon Sep 17 00:00:00 2001 From: Anupam K Date: Fri, 24 Jul 2020 03:31:58 +0530 Subject: [PATCH 116/191] fix: print format custom button --- .../doctype/print_format/print_format.js | 21 ++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/frappe/printing/doctype/print_format/print_format.js b/frappe/printing/doctype/print_format/print_format.js index 252c706e51..bb0ba27e38 100644 --- a/frappe/printing/doctype/print_format/print_format.js +++ b/frappe/printing/doctype/print_format/print_format.js @@ -35,13 +35,20 @@ frappe.ui.form.on("Print Format", { else if (frm.doc.custom_format && !frm.doc.raw_printing) { frm.set_df_property("html", "reqd", 1); } - frm.add_custom_button(__("Make Default"), function () { - frappe.call({ - method: "frappe.printing.doctype.print_format.print_format.make_default", - args: { - name: frm.doc.name - } - }) + frappe.db.get_value('DocType', frm.doc.doc_type, ['default_print_format', 'custom'], (r) => { + if (r.default_print_format != frm.doc.name && r.custom) { + frm.add_custom_button(__("Set as Default"), function () { + frappe.call({ + method: "frappe.printing.doctype.print_format.print_format.make_default", + args: { + name: frm.doc.name + }, + callback: function() { + frm.refresh(); + } + }); + }); + } }); } }, From 35f3b5b4051b9d0054c540e93e04606937048977 Mon Sep 17 00:00:00 2001 From: Wolfram Schmidt Date: Fri, 24 Jul 2020 14:11:24 +0200 Subject: [PATCH 117/191] fix: Allowing to rename DocType Tag (#11100) --- frappe/desk/doctype/tag/tag.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/frappe/desk/doctype/tag/tag.json b/frappe/desk/doctype/tag/tag.json index 895516594e..ad9838d10f 100644 --- a/frappe/desk/doctype/tag/tag.json +++ b/frappe/desk/doctype/tag/tag.json @@ -1,4 +1,5 @@ { + "allow_rename": 1, "autoname": "Prompt", "creation": "2016-05-25 09:43:44.767581", "doctype": "DocType", @@ -46,4 +47,4 @@ ], "sort_field": "modified", "sort_order": "DESC" -} \ No newline at end of file +} From bc56a596715c47ed73cb2ad76db8e090ac8536fd Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Fri, 24 Jul 2020 20:19:35 +0530 Subject: [PATCH 118/191] fix(paytm-integration): simplify code and remove additional loop --- .../doctype/paytm_settings/paytm_settings.py | 20 +++++++------------ .../pages/integrations/paytm_checkout.py | 2 -- 2 files changed, 7 insertions(+), 15 deletions(-) diff --git a/frappe/integrations/doctype/paytm_settings/paytm_settings.py b/frappe/integrations/doctype/paytm_settings/paytm_settings.py index bfa9b6b3eb..a166d245f5 100644 --- a/frappe/integrations/doctype/paytm_settings/paytm_settings.py +++ b/frappe/integrations/doctype/paytm_settings/paytm_settings.py @@ -25,7 +25,7 @@ class PaytmSettings(Document): def validate_transaction_currency(self, currency): if currency not in self.supported_currencies: - frappe.throw(_("Please select another payment method. Stripe does not support transactions in currency '{0}'").format(currency)) + frappe.throw(_("Please select another payment method. Paytm does not support transactions in currency '{0}'").format(currency)) def get_payment_url(self, **kwargs): '''Return payment url with several params''' @@ -87,29 +87,23 @@ def get_paytm_params(payment_details, order_id, paytm_config): def verify_transaction(**kwargs): '''Verify checksum for received data in the callback and then verify the transaction''' paytm_config = get_paytm_config() - received_data = frappe._dict(kwargs) + paytm_params = frappe._dict(kwargs) is_valid_checksum = False - paytm_params = {} - for key, value in received_data.items(): - if key == 'CHECKSUMHASH': - paytm_checksum = value - elif key == 'cmd': - continue - else: - paytm_params[key] = value + paytm_params.pop('cmd', None) + paytm_checksum = paytm_params.pop('CHECKSUMHASH', None) if paytm_params and paytm_config and paytm_checksum: # Verify checksum is_valid_checksum = verifySignature(paytm_params, paytm_config.merchant_key, paytm_checksum) - if is_valid_checksum and received_data['RESPCODE'] == '01': - verify_transaction_status(paytm_config, received_data['ORDERID']) + if is_valid_checksum and paytm_params.get('RESPCODE') == '01': + verify_transaction_status(paytm_config, paytm_params['ORDERID']) else: frappe.respond_as_web_page("Payment Failed", "Transaction failed to complete. In case of any deductions, deducted amount will get refunded to your account.", http_status_code=401, indicator_color='red') - frappe.log_error("Order unsuccessful. Failed Response:"+cstr(received_data), 'Paytm Payment Failed') + frappe.log_error("Order unsuccessful. Failed Response:"+cstr(paytm_params), 'Paytm Payment Failed') def verify_transaction_status(paytm_config, order_id): '''Verify transaction completion after checksum has been verified''' diff --git a/frappe/templates/pages/integrations/paytm_checkout.py b/frappe/templates/pages/integrations/paytm_checkout.py index 3018a9d65b..bc385b5784 100644 --- a/frappe/templates/pages/integrations/paytm_checkout.py +++ b/frappe/templates/pages/integrations/paytm_checkout.py @@ -6,8 +6,6 @@ from frappe import _ import json from frappe.integrations.doctype.paytm_settings.paytm_settings import get_paytm_params, get_paytm_config -no_cache = 1 - def get_context(context): context.no_cache = 1 paytm_config = get_paytm_config() From ab8a63d6fe0fe892ac578edb7b4deeae29fcc4cf Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Fri, 24 Jul 2020 22:59:16 +0530 Subject: [PATCH 119/191] fix: minor changes --- frappe/integrations/doctype/paytm_settings/paytm_settings.py | 3 +-- frappe/www/message.html | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/frappe/integrations/doctype/paytm_settings/paytm_settings.py b/frappe/integrations/doctype/paytm_settings/paytm_settings.py index a166d245f5..616c3837d4 100644 --- a/frappe/integrations/doctype/paytm_settings/paytm_settings.py +++ b/frappe/integrations/doctype/paytm_settings/paytm_settings.py @@ -84,10 +84,9 @@ def get_paytm_params(payment_details, order_id, paytm_config): return paytm_params @frappe.whitelist(allow_guest=True) -def verify_transaction(**kwargs): +def verify_transaction(**paytm_params): '''Verify checksum for received data in the callback and then verify the transaction''' paytm_config = get_paytm_config() - paytm_params = frappe._dict(kwargs) is_valid_checksum = False paytm_params.pop('cmd', None) diff --git a/frappe/www/message.html b/frappe/www/message.html index 87d311d9cc..2919592901 100644 --- a/frappe/www/message.html +++ b/frappe/www/message.html @@ -24,7 +24,7 @@ html, body { {{ title or _("Message") }} -
    +
    {% block message_body %}

    {{ message or "" }}

    {% if primary_action %} From 8844c2b690d2e6a1011963b1a63fd24c40445b22 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Mon, 27 Jul 2020 15:37:01 +0530 Subject: [PATCH 120/191] fix: route to new doc 1 explicitly --- frappe/public/js/frappe/widgets/onboarding_widget.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frappe/public/js/frappe/widgets/onboarding_widget.js b/frappe/public/js/frappe/widgets/onboarding_widget.js index 9b4c9973e2..8c1d2cbb5b 100644 --- a/frappe/public/js/frappe/widgets/onboarding_widget.js +++ b/frappe/public/js/frappe/widgets/onboarding_widget.js @@ -136,7 +136,7 @@ export default class OnboardingWidget extends Widget { if (step.is_single) { route = `Form/${step.reference_document}`; } else { - route = `Form/${step.reference_document}/${__('New')} ${__(step.reference_document)}`; + route = `Form/${step.reference_document}/${__('New')} ${__(step.reference_document)} 1`; } let current_route = frappe.get_route(); @@ -262,7 +262,7 @@ export default class OnboardingWidget extends Widget { frappe.route_hooks.after_save = callback; } - frappe.set_route(`Form/${step.reference_document}/${__('New')} ${__(step.reference_document)}`); + frappe.set_route(`Form/${step.reference_document}/${__('New')} ${__(step.reference_document)} 1`); } show_quick_entry(step) { From 28605191b1f1359b4b71ae9fd9435d47d26e9ad1 Mon Sep 17 00:00:00 2001 From: prssanna Date: Mon, 27 Jul 2020 16:59:49 +0530 Subject: [PATCH 121/191] fix(Number Card): return value in promise --- frappe/public/js/frappe/widgets/number_card_widget.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/public/js/frappe/widgets/number_card_widget.js b/frappe/public/js/frappe/widgets/number_card_widget.js index 220394919c..6b38412ebd 100644 --- a/frappe/public/js/frappe/widgets/number_card_widget.js +++ b/frappe/public/js/frappe/widgets/number_card_widget.js @@ -164,7 +164,7 @@ export default class NumberCardWidget extends Widget { get_number() { return frappe.xcall(this.settings.method, this.settings.args).then(res => { - this.settings.get_number(res); + return this.settings.get_number(res); }); } From ad1152f8e8367f8d7c46d0e4e9a14ee9e2d0df9b Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Mon, 27 Jul 2020 17:13:18 +0530 Subject: [PATCH 122/191] feat: override site name via _site added filter parameter for logger --- frappe/__init__.py | 4 ++-- frappe/utils/logger.py | 16 +++++++++------- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/frappe/__init__.py b/frappe/__init__.py index 8c24980189..504ac96eb9 100644 --- a/frappe/__init__.py +++ b/frappe/__init__.py @@ -1559,10 +1559,10 @@ def get_doctype_app(doctype): loggers = {} log_level = None -def logger(module=None, with_more_info=False, _site=None): +def logger(module=None, with_more_info=False, _site=None, filter=None): '''Returns a python logger that uses StreamHandler''' from frappe.utils.logger import get_logger - return get_logger(module=module, with_more_info=with_more_info, _site=_site) + return get_logger(module=module, with_more_info=with_more_info, _site=_site, filter=filter) def log_error(message=None, title=_("Error")): '''Log error to Error Log''' diff --git a/frappe/utils/logger.py b/frappe/utils/logger.py index 9254c0aedd..56fcf9857a 100755 --- a/frappe/utils/logger.py +++ b/frappe/utils/logger.py @@ -17,22 +17,20 @@ default_log_level = logging.DEBUG site = getattr(frappe.local, 'site', None) -def get_logger(module, with_more_info=False, _site=None): +def get_logger(module, with_more_info=False, _site=None, filter=None): """Application Logger for your given module Args: module (str): Name of your logger and consequently your log file. with_more_info (bool, optional): Will log the form dict using the SiteContextFilter. Defaults to False. - _site (str, optional): If set, validates the current site context with the passed value. The `frappe.web` logger uses this to determine that the application is logging information related to the logger called. Defaults to None. + _site (str, optional): Overrides the global site variable set in this scope. Defaults to None. + filter (function, optional): Add a filter function for your logger Returns: : Returns a Python logger object with Site and Bench level logging capabilities. """ global site - def allow_site(): - return (_site and _site == site) or bool(site) - if module in frappe.loggers: return frappe.loggers[module] @@ -42,6 +40,7 @@ def get_logger(module, with_more_info=False, _site=None): logfile = module + '.log' site = getattr(frappe.local, 'site', None) + equivalent_site = _site or site LOG_FILENAME = os.path.join('..', 'logs', logfile) logger = logging.getLogger(module) @@ -52,8 +51,8 @@ def get_logger(module, with_more_info=False, _site=None): handler = RotatingFileHandler(LOG_FILENAME, maxBytes=100_000, backupCount=20) logger.addHandler(handler) - if allow_site(): - SITELOG_FILENAME = os.path.join(site, 'logs', logfile) + if equivalent_site: + SITELOG_FILENAME = os.path.join(equivalent_site, 'logs', logfile) site_handler = RotatingFileHandler(SITELOG_FILENAME, maxBytes=100_000, backupCount=20) site_handler.setFormatter(formatter) logger.addHandler(site_handler) @@ -61,6 +60,9 @@ def get_logger(module, with_more_info=False, _site=None): if with_more_info: handler.addFilter(SiteContextFilter()) + if filter: + logger.addFilter(filter) + handler.setFormatter(formatter) frappe.loggers[module] = logger From 1345c2feb311bf7abb06b3f1969c4a2a43dc9b94 Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Mon, 27 Jul 2020 17:31:12 +0530 Subject: [PATCH 123/191] feat: Allow_site flag in frappe.logger --- frappe/__init__.py | 4 ++-- frappe/app.py | 2 +- frappe/utils/logger.py | 22 +++++++++++++--------- 3 files changed, 16 insertions(+), 12 deletions(-) diff --git a/frappe/__init__.py b/frappe/__init__.py index 504ac96eb9..7ea95ec3db 100644 --- a/frappe/__init__.py +++ b/frappe/__init__.py @@ -1559,10 +1559,10 @@ def get_doctype_app(doctype): loggers = {} log_level = None -def logger(module=None, with_more_info=False, _site=None, filter=None): +def logger(module=None, with_more_info=False, allow_site=True, filter=None): '''Returns a python logger that uses StreamHandler''' from frappe.utils.logger import get_logger - return get_logger(module=module, with_more_info=with_more_info, _site=_site, filter=filter) + return get_logger(module=module, with_more_info=with_more_info, allow_site=allow_site, filter=filter) def log_error(message=None, title=_("Error")): '''Log error to Error Log''' diff --git a/frappe/app.py b/frappe/app.py index 83a9b8c149..100b10e9ef 100644 --- a/frappe/app.py +++ b/frappe/app.py @@ -100,7 +100,7 @@ def application(request): frappe.recorder.dump() _site = get_site_name(request.host) - frappe.logger("frappe.web", _site=_site).info({ + frappe.logger("frappe.web", allow_site=_site).info({ "site": _site, "remote_addr": getattr(request, "remote_addr", "NOTFOUND"), "base_url": getattr(request, "base_url", "NOTFOUND"), diff --git a/frappe/utils/logger.py b/frappe/utils/logger.py index 56fcf9857a..e7058ce42d 100755 --- a/frappe/utils/logger.py +++ b/frappe/utils/logger.py @@ -14,22 +14,20 @@ import frappe default_log_level = logging.DEBUG -site = getattr(frappe.local, 'site', None) -def get_logger(module, with_more_info=False, _site=None, filter=None): +def get_logger(module, with_more_info=False, allow_site=True, filter=None): """Application Logger for your given module Args: module (str): Name of your logger and consequently your log file. with_more_info (bool, optional): Will log the form dict using the SiteContextFilter. Defaults to False. - _site (str, optional): Overrides the global site variable set in this scope. Defaults to None. + allow_site ((str, bool), optional): Pass site name to explicitly log under it's logs. If True and unspecified, guesses which site the logs would be saved under. Defaults to True. filter (function, optional): Add a filter function for your logger Returns: : Returns a Python logger object with Site and Bench level logging capabilities. """ - global site if module in frappe.loggers: return frappe.loggers[module] @@ -39,8 +37,14 @@ def get_logger(module, with_more_info=False, _site=None, filter=None): with_more_info = True logfile = module + '.log' - site = getattr(frappe.local, 'site', None) - equivalent_site = _site or site + + if allow_site == True: + site = getattr(frappe.local, 'site', None) + elif allow_site: + site = allow_site + else: + site = False + LOG_FILENAME = os.path.join('..', 'logs', logfile) logger = logging.getLogger(module) @@ -51,8 +55,8 @@ def get_logger(module, with_more_info=False, _site=None, filter=None): handler = RotatingFileHandler(LOG_FILENAME, maxBytes=100_000, backupCount=20) logger.addHandler(handler) - if equivalent_site: - SITELOG_FILENAME = os.path.join(equivalent_site, 'logs', logfile) + if site: + SITELOG_FILENAME = os.path.join(site, 'logs', logfile) site_handler = RotatingFileHandler(SITELOG_FILENAME, maxBytes=100_000, backupCount=20) site_handler.setFormatter(formatter) logger.addHandler(site_handler) @@ -73,7 +77,7 @@ class SiteContextFilter(logging.Filter): """This is a filter which injects request information (if available) into the log.""" def filter(self, record): if "Form Dict" not in text_type(record.msg): - record.msg = text_type(record.msg) + "\nSite: {0}\nForm Dict: {1}".format(site, getattr(frappe.local, 'form_dict', None)) + record.msg = text_type(record.msg) + "\nSite: {0}\nForm Dict: {1}".format(getattr(frappe.local, 'site', None), getattr(frappe.local, 'form_dict', None)) return True def set_log_level(level): From dc46450f89a2d95163eda8002f047ba3e6b04630 Mon Sep 17 00:00:00 2001 From: prssanna Date: Mon, 27 Jul 2020 18:14:02 +0530 Subject: [PATCH 124/191] fix: push fieldtype in invalid conditions map --- frappe/public/js/frappe/ui/filters/filter.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/public/js/frappe/ui/filters/filter.js b/frappe/public/js/frappe/ui/filters/filter.js index bfc0b1449d..4dedfb32fe 100644 --- a/frappe/public/js/frappe/ui/filters/filter.js +++ b/frappe/public/js/frappe/ui/filters/filter.js @@ -57,7 +57,7 @@ frappe.ui.Filter = class { this.conditions.push([key, __(`{0}`, [filter.label])]); for (let fieldtype of Object.keys(this.invalid_condition_map)) { if (!filter.valid_for_fieldtypes.includes(fieldtype)) { - this.invalid_condition_map[fieldtype].push(filter.label); + this.invalid_condition_map[fieldtype].push(key); } } } From 59ab9249cc3a9e7abeb5f6e599133f705ea6d8e6 Mon Sep 17 00:00:00 2001 From: Anupam K Date: Mon, 27 Jul 2020 19:40:11 +0530 Subject: [PATCH 125/191] fix: print format custom button --- frappe/printing/doctype/print_format/print_format.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frappe/printing/doctype/print_format/print_format.js b/frappe/printing/doctype/print_format/print_format.js index bb0ba27e38..e6599b2496 100644 --- a/frappe/printing/doctype/print_format/print_format.js +++ b/frappe/printing/doctype/print_format/print_format.js @@ -35,8 +35,8 @@ frappe.ui.form.on("Print Format", { else if (frm.doc.custom_format && !frm.doc.raw_printing) { frm.set_df_property("html", "reqd", 1); } - frappe.db.get_value('DocType', frm.doc.doc_type, ['default_print_format', 'custom'], (r) => { - if (r.default_print_format != frm.doc.name && r.custom) { + frappe.db.get_value('DocType', frm.doc.doc_type, 'default_print_format', (r) => { + if (r.default_print_format != frm.doc.name) { frm.add_custom_button(__("Set as Default"), function () { frappe.call({ method: "frappe.printing.doctype.print_format.print_format.make_default", From 786625a92a94536b749f0b02acc9fc4cf56516c7 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Mon, 27 Jul 2020 21:34:56 +0530 Subject: [PATCH 126/191] fix: regex for youtube video help link --- frappe/public/js/frappe/utils/help.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/public/js/frappe/utils/help.js b/frappe/public/js/frappe/utils/help.js index 336d1ef526..f88849d212 100644 --- a/frappe/public/js/frappe/utils/help.js +++ b/frappe/public/js/frappe/utils/help.js @@ -17,7 +17,7 @@ frappe.help.show = function(doctype) { frappe.help.show_video = function(youtube_id, title) { if (frappe.utils.is_url(youtube_id)) { - const expression = '(?:youtube.com/(?:[^/]+/.+/|(?:v|e(?:mbed)?)/|.*[?&]v=)|youtu.be/)([^\"&?/s]{11})'; + const expression = '(?:youtube.com/(?:[^/]+/.+/|(?:v|e(?:mbed)?)/|.*[?&]v=)|youtu.be/)([^\"&?\\s]{11})'; youtube_id = youtube_id.match(expression)[1]; } From c8817e52210f526c8b71a4989bc65ad05e2e4e79 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Mon, 27 Jul 2020 21:39:00 +0530 Subject: [PATCH 127/191] fix: check for empty report filters while creating report type dashboard chart --- frappe/public/js/frappe/widgets/chart_widget.js | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/frappe/public/js/frappe/widgets/chart_widget.js b/frappe/public/js/frappe/widgets/chart_widget.js index 4aa0b23a63..5d2c89b739 100644 --- a/frappe/public/js/frappe/widgets/chart_widget.js +++ b/frappe/public/js/frappe/widgets/chart_widget.js @@ -656,14 +656,15 @@ export default class ChartWidget extends Widget { } update_default_date_filters(report_filters, chart_filters) { - report_filters.map(f => { - if (['Date', 'DateRange'].includes(f.fieldtype) && f.default) { - if (f.reqd || chart_filters[f.fieldname]) { - chart_filters[f.fieldname] = f.default; + if (report_filters) { + report_filters.map(f => { + if (['Date', 'DateRange'].includes(f.fieldtype) && f.default) { + if (f.reqd || chart_filters[f.fieldname]) { + chart_filters[f.fieldname] = f.default; + } } - } - }); - + }); + } return chart_filters; } From a11496f05ca1270abf51574489dd19f0b6ffbb6f Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Mon, 27 Jul 2020 23:35:19 +0530 Subject: [PATCH 128/191] fix: translation syntax --- frappe/public/js/frappe/widgets/chart_widget.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/frappe/public/js/frappe/widgets/chart_widget.js b/frappe/public/js/frappe/widgets/chart_widget.js index 5d2c89b739..ccec5b3ef4 100644 --- a/frappe/public/js/frappe/widgets/chart_widget.js +++ b/frappe/public/js/frappe/widgets/chart_widget.js @@ -615,9 +615,7 @@ export default class ChartWidget extends Widget { } update_last_synced() { - let last_synced_text = __("Last synced {0}", [ - comment_when(this.chart_doc.last_synced_on) - ]); + let last_synced_text = __("Last synced {0}", [comment_when(this.chart_doc.last_synced_on)]); this.footer.html(last_synced_text); } From 7383a55c9adb9af62590ba18738609784b4ea8b3 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Tue, 28 Jul 2020 11:29:22 +0530 Subject: [PATCH 129/191] fix: remove useless escape character --- frappe/public/js/frappe/utils/help.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/public/js/frappe/utils/help.js b/frappe/public/js/frappe/utils/help.js index f88849d212..ec6f7c8158 100644 --- a/frappe/public/js/frappe/utils/help.js +++ b/frappe/public/js/frappe/utils/help.js @@ -17,7 +17,7 @@ frappe.help.show = function(doctype) { frappe.help.show_video = function(youtube_id, title) { if (frappe.utils.is_url(youtube_id)) { - const expression = '(?:youtube.com/(?:[^/]+/.+/|(?:v|e(?:mbed)?)/|.*[?&]v=)|youtu.be/)([^\"&?\\s]{11})'; + const expression = '(?:youtube.com/(?:[^/]+/.+/|(?:v|e(?:mbed)?)/|.*[?&]v=)|youtu.be/)([^"&?\\s]{11})'; youtube_id = youtube_id.match(expression)[1]; } From d9c8b6b66281946ee504c240c84a8819e60bc903 Mon Sep 17 00:00:00 2001 From: Faris Ansari Date: Tue, 28 Jul 2020 13:09:24 +0530 Subject: [PATCH 130/191] fix: Don't render meta tags if no value --- frappe/templates/includes/meta_block.html | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/frappe/templates/includes/meta_block.html b/frappe/templates/includes/meta_block.html index 3d867aaef3..1aef8a160e 100644 --- a/frappe/templates/includes/meta_block.html +++ b/frappe/templates/includes/meta_block.html @@ -1,5 +1,8 @@ {%- if metatags -%} {%- for name in metatags %} - +{%- set content = metatags.get(name, '') -%} +{%- if content -%} + +{%- endif -%} {%- endfor -%} {%- endif -%} From 8ce340046ee27fccb1232e80d1aa3d3d5f8d4128 Mon Sep 17 00:00:00 2001 From: Sahil Khan Date: Tue, 28 Jul 2020 19:33:44 +0530 Subject: [PATCH 131/191] fix: do not sanitize code field Co-authored-by: Chinmay D. Pai --- frappe/model/base_document.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frappe/model/base_document.py b/frappe/model/base_document.py index 7d56736cdc..09d303bec7 100644 --- a/frappe/model/base_document.py +++ b/frappe/model/base_document.py @@ -703,8 +703,8 @@ class BaseDocument(object): sanitized_value = value if df and (df.get("ignore_xss_filter") - or (df.get("fieldtype")=="Code" and df.get("options")!="Email") - or df.get("fieldtype") in ("Attach", "Attach Image", "Barcode") + or (df.get("fieldtype") in ("Data", "Small Text", "Text") and df.get("options")=="Email") + or df.get("fieldtype") in ("Attach", "Attach Image", "Barcode", "Code") # cancelled and submit but not update after submit should be ignored or self.docstatus==2 From ac2493b391a4213f3412390b88b157a2fcf2bbd7 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Wed, 29 Jul 2020 11:00:59 +0530 Subject: [PATCH 132/191] style: use 1tbs braces for if-else --- frappe/public/js/frappe/views/reports/query_report.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/frappe/public/js/frappe/views/reports/query_report.js b/frappe/public/js/frappe/views/reports/query_report.js index 609e1feb46..0701a34b76 100644 --- a/frappe/public/js/frappe/views/reports/query_report.js +++ b/frappe/public/js/frappe/views/reports/query_report.js @@ -869,8 +869,7 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { if (column.colIndex === index && !value) { value = "Total"; column.fieldtype = "Data"; // avoid type issues for value if Date column - } - else if (in_list(["Currency", "Float"], column.fieldtype)) { + } else if (in_list(["Currency", "Float"], column.fieldtype)) { // proxy for currency and float data = this.data[0]; } From ae3ee5b2eb5c64fa00f4461ad1a11841ced65c54 Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Wed, 29 Jul 2020 13:33:04 +0530 Subject: [PATCH 133/191] fix: save frappe loggers site wise --- frappe/utils/logger.py | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/frappe/utils/logger.py b/frappe/utils/logger.py index e7058ce42d..c38e0e6997 100755 --- a/frappe/utils/logger.py +++ b/frappe/utils/logger.py @@ -29,8 +29,18 @@ def get_logger(module, with_more_info=False, allow_site=True, filter=None): : Returns a Python logger object with Site and Bench level logging capabilities. """ + if allow_site == True: + site = getattr(frappe.local, 'site', None) + elif allow_site: + site = allow_site + else: + site = False + if module in frappe.loggers: - return frappe.loggers[module] + try: + return frappe.loggers[module][site or "all"] + except: + pass if not module: module = "frappe" @@ -38,12 +48,6 @@ def get_logger(module, with_more_info=False, allow_site=True, filter=None): logfile = module + '.log' - if allow_site == True: - site = getattr(frappe.local, 'site', None) - elif allow_site: - site = allow_site - else: - site = False LOG_FILENAME = os.path.join('..', 'logs', logfile) @@ -69,7 +73,11 @@ def get_logger(module, with_more_info=False, allow_site=True, filter=None): handler.setFormatter(formatter) - frappe.loggers[module] = logger + try: + frappe.loggers[module][site or "all"] = logger + except KeyError: + frappe.loggers[module] = {} + frappe.loggers[module][site or "all"] = logger return logger From 727a2b13bf3050c8dd4e2bed9bf6f1911864dcd5 Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Wed, 29 Jul 2020 14:28:45 +0530 Subject: [PATCH 134/191] fix: fetch latest backups updated behaviour of fetch latest backups. doesnt take new backups and accepts no args --- frappe/utils/backups.py | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/frappe/utils/backups.py b/frappe/utils/backups.py index 3b905de6bd..7eb00ceccd 100644 --- a/frappe/utils/backups.py +++ b/frappe/utils/backups.py @@ -208,26 +208,22 @@ def get_backup(): @frappe.whitelist() -def fetch_latest_backups(with_files=True, recent=3): - """Takes backup on-demand if doesnt exist satisfying the `recent` parameter +def fetch_latest_backups(): + """Fetches paths of the latest backup taken in the last 30 days Only for: System Managers - Args: - with_files (bool, optional): If set, files will backuped up. Defaults to True. - recent (int, optional): Won't take a new backup if backup exists within this paramter. Defaults to 3 hours - Returns: dict: relative Backup Paths """ frappe.only_for("System Manager") odb = BackupGenerator(frappe.conf.db_name, frappe.conf.db_name, frappe.conf.db_password, db_host=frappe.db.host, db_type=frappe.conf.db_type, db_port=frappe.conf.db_port) - odb.get_backup(older_than=recent, ignore_files=not with_files) + database, public, private, config = odb.get_recent_backup(older_than=24 * 30) return { - "database": odb.backup_path_db, - "public": odb.backup_path_files, - "private": odb.backup_path_private_files, - "config": odb.site_config_backup_path + "database": database, + "public": public, + "private": private, + "config": config } From d3e4a97b6fc96f2003c1fad3c14a99b105ade5ed Mon Sep 17 00:00:00 2001 From: Suraj Shetty <13928957+surajshetty3416@users.noreply.github.com> Date: Wed, 29 Jul 2020 17:17:53 +0530 Subject: [PATCH 135/191] test: Make email account is email account test (#11139) --- frappe/core/doctype/server_script/test_server_script.py | 7 ++++--- frappe/email/doctype/email_account/test_email_account.py | 5 ++++- frappe/model/workflow.py | 2 +- frappe/tests/test_naming.py | 1 + frappe/workflow/doctype/workflow/test_workflow.py | 3 ++- 5 files changed, 12 insertions(+), 6 deletions(-) diff --git a/frappe/core/doctype/server_script/test_server_script.py b/frappe/core/doctype/server_script/test_server_script.py index 3e6b7a3a98..5c12858e8a 100644 --- a/frappe/core/doctype/server_script/test_server_script.py +++ b/frappe/core/doctype/server_script/test_server_script.py @@ -52,9 +52,10 @@ class TestServerScript(unittest.TestCase): frappe.db.commit() - # @classmethod - # def tearDownClass(cls): - # frappe.db.sql('truncate `tabServer Script`') + @classmethod + def tearDownClass(cls): + frappe.db.commit() + frappe.db.sql('truncate `tabServer Script`') def setUp(self): frappe.cache().delete_value('server_script_map') diff --git a/frappe/email/doctype/email_account/test_email_account.py b/frappe/email/doctype/email_account/test_email_account.py index 29b54d7f8b..f87ee32bb1 100644 --- a/frappe/email/doctype/email_account/test_email_account.py +++ b/frappe/email/doctype/email_account/test_email_account.py @@ -5,7 +5,10 @@ from __future__ import unicode_literals import frappe, os import unittest, email -test_records = frappe.get_test_records('Email Account') +from frappe.test_runner import make_test_records + +make_test_records("User") +make_test_records("Email Account") from frappe.core.doctype.communication.email import make from frappe.desk.form.load import get_attachments diff --git a/frappe/model/workflow.py b/frappe/model/workflow.py index ea563dfc13..32919b3333 100644 --- a/frappe/model/workflow.py +++ b/frappe/model/workflow.py @@ -307,4 +307,4 @@ def set_workflow_state_on_action(doc, workflow_name, action): for state in workflow.states: if state.doc_status == docstatus: doc.set(workflow_state_field, state.state) - return \ No newline at end of file + return diff --git a/frappe/tests/test_naming.py b/frappe/tests/test_naming.py index 90ab6a6a94..b47fb809ca 100644 --- a/frappe/tests/test_naming.py +++ b/frappe/tests/test_naming.py @@ -88,6 +88,7 @@ class TestNaming(unittest.TestCase): series = 'TEST-' key = 'TEST-' name = 'TEST-00003' + frappe.db.sql("DELETE FROM `tabSeries` WHERE `name`=%s", series) frappe.db.sql("""INSERT INTO `tabSeries` (name, current) values (%s, 3)""", (series,)) revert_series_if_last(key, name) count = frappe.db.sql("""SELECT current from `tabSeries` where name = %s""", series, as_dict=True)[0] diff --git a/frappe/workflow/doctype/workflow/test_workflow.py b/frappe/workflow/doctype/workflow/test_workflow.py index 9999df40cb..2719bc7cf0 100644 --- a/frappe/workflow/doctype/workflow/test_workflow.py +++ b/frappe/workflow/doctype/workflow/test_workflow.py @@ -10,6 +10,7 @@ from frappe.model.workflow import apply_workflow, WorkflowTransitionError, Workf class TestWorkflow(unittest.TestCase): def setUp(self): frappe.db.sql('DELETE FROM `tabToDo`') + frappe.db.sql("DELETE FROM `tabHas Role` WHERE `role`='Test Approver'") if not getattr(self, 'workflow', None): self.workflow = create_todo_workflow() frappe.set_user('Administrator') @@ -146,4 +147,4 @@ def create_todo_workflow(): return workflow def create_new_todo(): - return frappe.get_doc(dict(doctype='ToDo', description='workflow ' + random_string(10))).insert() \ No newline at end of file + return frappe.get_doc(dict(doctype='ToDo', description='workflow ' + random_string(10))).insert() From 219fbc8e88d8505391fe8259b0fbeda86844f42d Mon Sep 17 00:00:00 2001 From: Matteo D Date: Wed, 29 Jul 2020 17:28:46 +0200 Subject: [PATCH 136/191] fix: Added AWS S3 missing regions (#11109) --- .gitignore | 5 ++++- .../doctype/s3_backup_settings/s3_backup_settings.json | 8 ++++---- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/.gitignore b/.gitignore index 7df673c1f1..900ae1c7b7 100644 --- a/.gitignore +++ b/.gitignore @@ -188,4 +188,7 @@ typings/ # cypress cypress/screenshots -cypress/videos \ No newline at end of file +cypress/videos + +# JetBrains IDEs +.idea/ diff --git a/frappe/integrations/doctype/s3_backup_settings/s3_backup_settings.json b/frappe/integrations/doctype/s3_backup_settings/s3_backup_settings.json index 830afbae53..123bb21e88 100755 --- a/frappe/integrations/doctype/s3_backup_settings/s3_backup_settings.json +++ b/frappe/integrations/doctype/s3_backup_settings/s3_backup_settings.json @@ -74,11 +74,11 @@ }, { "default": "us-east-1", - "description": "See https://docs.aws.amazon.com/de_de/general/latest/gr/rande.html#s3_region for details.", + "description": "See https://docs.aws.amazon.com/general/latest/gr/s3.html for details.", "fieldname": "region", "fieldtype": "Select", "label": "Region", - "options": "us-east-1\nus-east-2\nus-west-1\nus-west-2\nap-south-1\nap-southeast-1\nap-southeast-2\nap-northeast-1\nap-northeast-2\nap-northeast-3\nca-central-1\ncn-north-1\ncn-northwest-1\neu-central-1\neu-west-1\neu-west-2\neu-west-3\neu-north-1\nsa-east-1" + "options": "us-east-1\nus-east-2\nus-west-1\nus-west-2\naf-south-1\nap-east-1\nap-south-1\nap-southeast-1\nap-southeast-2\nap-northeast-1\nap-northeast-2\nap-northeast-3\nca-central-1\ncn-north-1\ncn-northwest-1\neu-central-1\neu-west-1\neu-west-2\neu-west-3\neu-south-1\neu-north-1\nme-south-1\nsa-east-1" }, { "fieldname": "endpoint_url", @@ -151,7 +151,7 @@ "hide_toolbar": 1, "issingle": 1, "links": [], - "modified": "2020-04-13 20:57:24.432183", + "modified": "2020-07-27 17:27:21.400000", "modified_by": "Administrator", "module": "Integrations", "name": "S3 Backup Settings", @@ -172,4 +172,4 @@ "sort_field": "modified", "sort_order": "DESC", "track_changes": 1 -} \ No newline at end of file +} From 8d18fb43230c80b40c00732850b5631c4346d54a Mon Sep 17 00:00:00 2001 From: Raffael Meyer <14891507+barredterra@users.noreply.github.com> Date: Wed, 29 Jul 2020 17:48:39 +0200 Subject: [PATCH 137/191] fix(oauth provider): parse cookies correctly (#11066) --- frappe/oauth.py | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/frappe/oauth.py b/frappe/oauth.py index 4dc50366be..122c806072 100644 --- a/frappe/oauth.py +++ b/frappe/oauth.py @@ -4,6 +4,7 @@ import pytz from frappe import _ from frappe.auth import LoginManager +from http import cookies from oauthlib.oauth2.rfc6749.tokens import BearerToken from oauthlib.oauth2.rfc6749.grant_types import AuthorizationCodeGrant, ImplicitGrant, ResourceOwnerPasswordCredentialsGrant, ClientCredentialsGrant, RefreshTokenGrant from oauthlib.oauth2 import RequestValidator @@ -130,15 +131,12 @@ class OAuthWebRequestValidator(RequestValidator): oac.scopes = get_url_delimiter().join(request.scopes) oac.redirect_uri_bound_to_authorization_code = request.redirect_uri oac.client = client_id - oac.user = unquote(cookie_dict['user_id']) + oac.user = unquote(cookie_dict['user_id'].value) oac.authorization_code = code['code'] oac.save(ignore_permissions=True) frappe.db.commit() def authenticate_client(self, request, *args, **kwargs): - - cookie_dict = get_cookie_dict_from_headers(request) - #Get ClientID in URL if request.client_id: oc = frappe.get_doc("OAuth Client", request.client_id) @@ -155,7 +153,9 @@ class OAuthWebRequestValidator(RequestValidator): except Exception as e: print("Failed body authentication: Application %s does not exist".format(cid=request.client_id)) - return frappe.session.user == unquote(cookie_dict.get('user_id', "Guest")) + cookie_dict = get_cookie_dict_from_headers(request) + user_id = unquote(cookie_dict['user_id']) if 'user_id' in cookie_dict else "Guest" + return frappe.session.user == user_id def authenticate_client_id(self, client_id, request, *args, **kwargs): cli_id = frappe.db.get_value('OAuth Client', client_id, 'name') @@ -400,13 +400,10 @@ class OAuthWebRequestValidator(RequestValidator): return True def get_cookie_dict_from_headers(r): + cookie = cookies.BaseCookie() if r.headers.get('Cookie'): - cookie = r.headers.get('Cookie') - cookie = cookie.split("; ") - cookie_dict = {k:v for k,v in (x.split('=') for x in cookie)} - return cookie_dict - else: - return {} + cookie.load(r.headers.get('Cookie')) + return cookie def calculate_at_hash(access_token, hash_alg): """Helper method for calculating an access token From 9cef385005e68221cc2d4b628f9080b81a434da2 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Thu, 30 Jul 2020 14:37:22 +0530 Subject: [PATCH 138/191] refactor: refer to frappe.boot.single_types instead of db call --- .../public/js/frappe/widgets/widget_dialog.js | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/frappe/public/js/frappe/widgets/widget_dialog.js b/frappe/public/js/frappe/widgets/widget_dialog.js index 054159116f..1089d7e629 100644 --- a/frappe/public/js/frappe/widgets/widget_dialog.js +++ b/frappe/public/js/frappe/widgets/widget_dialog.js @@ -174,18 +174,12 @@ class ShortcutDialog extends WidgetDialog { onchange: () => { if (this.dialog.get_value("type") == "DocType") { let doctype = this.dialog.get_value("link_to"); - - doctype && - frappe.db - .get_value("DocType", doctype, "issingle") - .then((res) => { - if (res.message && res.message.issingle) { - this.hide_filters(); - } else { - this.setup_filter(doctype); - this.show_filters(); - } - }); + if (frappe.boot.single_types.includes("Stock Settings")) { + this.hide_filters(); + } else { + this.setup_filter(doctype); + this.show_filters(); + } } else { this.hide_filters(); } From 2ad15ee9f75dca2391e45b83f5298b1e7e90e163 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Thu, 30 Jul 2020 14:57:01 +0530 Subject: [PATCH 139/191] fix: use doctype variable --- frappe/public/js/frappe/widgets/widget_dialog.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/public/js/frappe/widgets/widget_dialog.js b/frappe/public/js/frappe/widgets/widget_dialog.js index 1089d7e629..e3b4cc1d8c 100644 --- a/frappe/public/js/frappe/widgets/widget_dialog.js +++ b/frappe/public/js/frappe/widgets/widget_dialog.js @@ -174,7 +174,7 @@ class ShortcutDialog extends WidgetDialog { onchange: () => { if (this.dialog.get_value("type") == "DocType") { let doctype = this.dialog.get_value("link_to"); - if (frappe.boot.single_types.includes("Stock Settings")) { + if (frappe.boot.single_types.includes(doctype)) { this.hide_filters(); } else { this.setup_filter(doctype); From 7427fb9cd3b9589d8366f330b968a9f5e8c6f310 Mon Sep 17 00:00:00 2001 From: Faris Ansari Date: Thu, 30 Jul 2020 15:56:38 +0530 Subject: [PATCH 140/191] fix: Dont validate skipped columns --- frappe/core/doctype/data_import/importer.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/frappe/core/doctype/data_import/importer.py b/frappe/core/doctype/data_import/importer.py index 910e42af1a..14626eb5e3 100644 --- a/frappe/core/doctype/data_import/importer.py +++ b/frappe/core/doctype/data_import/importer.py @@ -955,6 +955,9 @@ class Column: if not self.df: return + if self.skip_import: + return + if self.df.fieldtype == 'Link': # find all values that dont exist values = list(set([cstr(v) for v in self.column_values[1:] if v])) From 84a15407a733a57ce5d1b13c2c1fefd80f12b198 Mon Sep 17 00:00:00 2001 From: Faris Ansari Date: Thu, 30 Jul 2020 16:06:10 +0530 Subject: [PATCH 141/191] fix: Respect hostname config in sitemap.xml --- frappe/www/sitemap.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/frappe/www/sitemap.py b/frappe/www/sitemap.py index 8b93270ab5..f8f03c45f8 100644 --- a/frappe/www/sitemap.py +++ b/frappe/www/sitemap.py @@ -5,7 +5,7 @@ from __future__ import unicode_literals import frappe from frappe.model.document import get_controller -from frappe.utils import get_request_site_address, get_datetime, nowdate +from frappe.utils import get_datetime, nowdate, get_url from frappe.website.router import get_pages, get_all_page_context_from_doctypes from six import iteritems from six.moves.urllib.parse import quote, urljoin @@ -25,13 +25,13 @@ def get_context(context): for route, page in iteritems(get_pages()): if page.sitemap: links.append({ - "loc": urljoin(host, quote(page.name.encode("utf-8"))), + "loc": get_url(quote(page.name.encode("utf-8"))), "lastmod": nowdate() }) for route, data in iteritems(get_public_pages_from_doctypes()): links.append({ - "loc": urljoin(host, quote((route or "").encode("utf-8"))), + "loc": get_url(quote((route or "").encode("utf-8"))), "lastmod": get_datetime(data.get("modified")).strftime("%Y-%m-%d") }) From 7bf69c62c1cc942d394d40484502c51b96e98b9f Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Thu, 30 Jul 2020 17:47:01 +0530 Subject: [PATCH 142/191] fix: suffix logger name with site --- frappe/utils/logger.py | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/frappe/utils/logger.py b/frappe/utils/logger.py index c38e0e6997..1fbd54dd38 100755 --- a/frappe/utils/logger.py +++ b/frappe/utils/logger.py @@ -36,9 +36,11 @@ def get_logger(module, with_more_info=False, allow_site=True, filter=None): else: site = False - if module in frappe.loggers: + LOGGER_NAME = "{}-{}".format(module, site or "all") + + if LOGGER_NAME in frappe.loggers: try: - return frappe.loggers[module][site or "all"] + return frappe.loggers[LOGGER_NAME] except: pass @@ -51,7 +53,7 @@ def get_logger(module, with_more_info=False, allow_site=True, filter=None): LOG_FILENAME = os.path.join('..', 'logs', logfile) - logger = logging.getLogger(module) + logger = logging.getLogger(LOGGER_NAME) logger.setLevel(frappe.log_level or default_log_level) logger.propagate = False @@ -73,11 +75,7 @@ def get_logger(module, with_more_info=False, allow_site=True, filter=None): handler.setFormatter(formatter) - try: - frappe.loggers[module][site or "all"] = logger - except KeyError: - frappe.loggers[module] = {} - frappe.loggers[module][site or "all"] = logger + frappe.loggers[LOGGER_NAME] = logger return logger From 090b81f2f268447581eac496bb245af3a17224c5 Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Thu, 30 Jul 2020 17:59:01 +0530 Subject: [PATCH 143/191] style: black + restructure --- frappe/utils/logger.py | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/frappe/utils/logger.py b/frappe/utils/logger.py index 1fbd54dd38..79ea93e3bd 100755 --- a/frappe/utils/logger.py +++ b/frappe/utils/logger.py @@ -30,7 +30,7 @@ def get_logger(module, with_more_info=False, allow_site=True, filter=None): """ if allow_site == True: - site = getattr(frappe.local, 'site', None) + site = getattr(frappe.local, "site", None) elif allow_site: site = allow_site else: @@ -48,21 +48,21 @@ def get_logger(module, with_more_info=False, allow_site=True, filter=None): module = "frappe" with_more_info = True - logfile = module + '.log' + logfile = module + ".log" - - LOG_FILENAME = os.path.join('..', 'logs', logfile) + LOG_FILENAME = os.path.join("..", "logs", logfile) logger = logging.getLogger(LOGGER_NAME) logger.setLevel(frappe.log_level or default_log_level) logger.propagate = False - formatter = logging.Formatter('%(asctime)s %(levelname)s %(name)s %(message)s') + formatter = logging.Formatter("%(asctime)s %(levelname)s %(name)s %(message)s") handler = RotatingFileHandler(LOG_FILENAME, maxBytes=100_000, backupCount=20) + handler.setFormatter(formatter) logger.addHandler(handler) if site: - SITELOG_FILENAME = os.path.join(site, 'logs', logfile) + SITELOG_FILENAME = os.path.join(site, "logs", logfile) site_handler = RotatingFileHandler(SITELOG_FILENAME, maxBytes=100_000, backupCount=20) site_handler.setFormatter(formatter) logger.addHandler(site_handler) @@ -73,20 +73,23 @@ def get_logger(module, with_more_info=False, allow_site=True, filter=None): if filter: logger.addFilter(filter) - handler.setFormatter(formatter) - frappe.loggers[LOGGER_NAME] = logger return logger + class SiteContextFilter(logging.Filter): """This is a filter which injects request information (if available) into the log.""" + def filter(self, record): if "Form Dict" not in text_type(record.msg): - record.msg = text_type(record.msg) + "\nSite: {0}\nForm Dict: {1}".format(getattr(frappe.local, 'site', None), getattr(frappe.local, 'form_dict', None)) + site = getattr(frappe.local, "site", None) + form_dict = getattr(frappe.local, "form_dict", None) + record.msg = text_type(record.msg) + "\nSite: {0}\nForm Dict: {1}".format(site, form_dict) return True + def set_log_level(level): - '''Use this method to set log level to something other than the default DEBUG''' - frappe.log_level = getattr(logging, (level or '').upper(), None) or default_log_level + """Use this method to set log level to something other than the default DEBUG""" + frappe.log_level = getattr(logging, (level or "").upper(), None) or default_log_level frappe.loggers = {} From fac7f359ae0b49064003e7424989579d580b7535 Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Thu, 30 Jul 2020 18:49:59 +0530 Subject: [PATCH 144/191] fix: original log format + EAFP --- frappe/utils/logger.py | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/frappe/utils/logger.py b/frappe/utils/logger.py index 79ea93e3bd..46269fe909 100755 --- a/frappe/utils/logger.py +++ b/frappe/utils/logger.py @@ -38,26 +38,24 @@ def get_logger(module, with_more_info=False, allow_site=True, filter=None): LOGGER_NAME = "{}-{}".format(module, site or "all") - if LOGGER_NAME in frappe.loggers: - try: - return frappe.loggers[LOGGER_NAME] - except: - pass + try: + return frappe.loggers[LOGGER_NAME] + except KeyError: + pass if not module: module = "frappe" with_more_info = True logfile = module + ".log" - - LOG_FILENAME = os.path.join("..", "logs", logfile) + log_filename = os.path.join("..", "logs", logfile) logger = logging.getLogger(LOGGER_NAME) logger.setLevel(frappe.log_level or default_log_level) logger.propagate = False - formatter = logging.Formatter("%(asctime)s %(levelname)s %(name)s %(message)s") - handler = RotatingFileHandler(LOG_FILENAME, maxBytes=100_000, backupCount=20) + formatter = logging.Formatter("%(asctime)s %(levelname)s {0} %(message)s".format(module)) + handler = RotatingFileHandler(log_filename, maxBytes=100_000, backupCount=20) handler.setFormatter(formatter) logger.addHandler(handler) From faa9a5466eacc4d07e19286309f36aeb8468f7ec Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Thu, 30 Jul 2020 19:04:04 +0530 Subject: [PATCH 145/191] fix: Slider --- frappe/utils/logger.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/utils/logger.py b/frappe/utils/logger.py index 46269fe909..6d24eef9d3 100755 --- a/frappe/utils/logger.py +++ b/frappe/utils/logger.py @@ -29,7 +29,7 @@ def get_logger(module, with_more_info=False, allow_site=True, filter=None): : Returns a Python logger object with Site and Bench level logging capabilities. """ - if allow_site == True: + if allow_site is True: site = getattr(frappe.local, "site", None) elif allow_site: site = allow_site From 444e8225a46188137028f99cb12efcab195559b5 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Thu, 30 Jul 2020 19:50:36 +0530 Subject: [PATCH 146/191] fix: setup filters only if doctype is available --- frappe/public/js/frappe/widgets/widget_dialog.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frappe/public/js/frappe/widgets/widget_dialog.js b/frappe/public/js/frappe/widgets/widget_dialog.js index e3b4cc1d8c..2ca26d59e1 100644 --- a/frappe/public/js/frappe/widgets/widget_dialog.js +++ b/frappe/public/js/frappe/widgets/widget_dialog.js @@ -174,9 +174,9 @@ class ShortcutDialog extends WidgetDialog { onchange: () => { if (this.dialog.get_value("type") == "DocType") { let doctype = this.dialog.get_value("link_to"); - if (frappe.boot.single_types.includes(doctype)) { + if (doctype && frappe.boot.single_types.includes(doctype)) { this.hide_filters(); - } else { + } else if (doctype) { this.setup_filter(doctype); this.show_filters(); } From 94116ae93c416958619e98aa47ad979481ec9dd1 Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Fri, 31 Jul 2020 16:07:51 +0530 Subject: [PATCH 147/191] feat(logger): allow max_size and file_count params --- frappe/__init__.py | 4 ++-- frappe/utils/logger.py | 20 +++++++++++--------- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/frappe/__init__.py b/frappe/__init__.py index 7ea95ec3db..45f40f7783 100644 --- a/frappe/__init__.py +++ b/frappe/__init__.py @@ -1559,10 +1559,10 @@ def get_doctype_app(doctype): loggers = {} log_level = None -def logger(module=None, with_more_info=False, allow_site=True, filter=None): +def logger(module=None, with_more_info=False, allow_site=True, filter=None, max_size=100_000, file_count=20): '''Returns a python logger that uses StreamHandler''' from frappe.utils.logger import get_logger - return get_logger(module=module, with_more_info=with_more_info, allow_site=allow_site, filter=filter) + return get_logger(module=module, with_more_info=with_more_info, allow_site=allow_site, filter=filter, max_size=max_size, file_count=file_count) def log_error(message=None, title=_("Error")): '''Log error to Error Log''' diff --git a/frappe/utils/logger.py b/frappe/utils/logger.py index 6d24eef9d3..b3295cccdf 100755 --- a/frappe/utils/logger.py +++ b/frappe/utils/logger.py @@ -16,14 +16,16 @@ import frappe default_log_level = logging.DEBUG -def get_logger(module, with_more_info=False, allow_site=True, filter=None): +def get_logger(module, with_more_info=False, allow_site=True, filter=None, max_size=100_000, file_count=20): """Application Logger for your given module Args: module (str): Name of your logger and consequently your log file. with_more_info (bool, optional): Will log the form dict using the SiteContextFilter. Defaults to False. allow_site ((str, bool), optional): Pass site name to explicitly log under it's logs. If True and unspecified, guesses which site the logs would be saved under. Defaults to True. - filter (function, optional): Add a filter function for your logger + filter (function, optional): Add a filter function for your logger. Defaults to None. + max_size (int, optional): Max file size of each log file in kB. Defaults to 100_000. + file_count (int, optional): Max count of log files to be retained via Log Rotation. Defaults to 20. Returns: : Returns a Python logger object with Site and Bench level logging capabilities. @@ -36,10 +38,10 @@ def get_logger(module, with_more_info=False, allow_site=True, filter=None): else: site = False - LOGGER_NAME = "{}-{}".format(module, site or "all") + logger_name = "{0}-{1}".format(module, site or "all") try: - return frappe.loggers[LOGGER_NAME] + return frappe.loggers[logger_name] except KeyError: pass @@ -50,18 +52,18 @@ def get_logger(module, with_more_info=False, allow_site=True, filter=None): logfile = module + ".log" log_filename = os.path.join("..", "logs", logfile) - logger = logging.getLogger(LOGGER_NAME) + logger = logging.getLogger(logger_name) logger.setLevel(frappe.log_level or default_log_level) logger.propagate = False formatter = logging.Formatter("%(asctime)s %(levelname)s {0} %(message)s".format(module)) - handler = RotatingFileHandler(log_filename, maxBytes=100_000, backupCount=20) + handler = RotatingFileHandler(log_filename, maxBytes=max_size, backupCount=file_count) handler.setFormatter(formatter) logger.addHandler(handler) if site: - SITELOG_FILENAME = os.path.join(site, "logs", logfile) - site_handler = RotatingFileHandler(SITELOG_FILENAME, maxBytes=100_000, backupCount=20) + sitelog_filename = os.path.join(site, "logs", logfile) + site_handler = RotatingFileHandler(sitelog_filename, maxBytes=max_size, backupCount=file_count) site_handler.setFormatter(formatter) logger.addHandler(site_handler) @@ -71,7 +73,7 @@ def get_logger(module, with_more_info=False, allow_site=True, filter=None): if filter: logger.addFilter(filter) - frappe.loggers[LOGGER_NAME] = logger + frappe.loggers[logger_name] = logger return logger From f04ce5dd7a7957d46ef12d2d5e2216bb93e753e1 Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Fri, 31 Jul 2020 16:11:39 +0530 Subject: [PATCH 148/191] chore: Typo --- frappe/utils/logger.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/utils/logger.py b/frappe/utils/logger.py index b3295cccdf..451211b74b 100755 --- a/frappe/utils/logger.py +++ b/frappe/utils/logger.py @@ -24,7 +24,7 @@ def get_logger(module, with_more_info=False, allow_site=True, filter=None, max_s with_more_info (bool, optional): Will log the form dict using the SiteContextFilter. Defaults to False. allow_site ((str, bool), optional): Pass site name to explicitly log under it's logs. If True and unspecified, guesses which site the logs would be saved under. Defaults to True. filter (function, optional): Add a filter function for your logger. Defaults to None. - max_size (int, optional): Max file size of each log file in kB. Defaults to 100_000. + max_size (int, optional): Max file size of each log file in bytes. Defaults to 100_000. file_count (int, optional): Max count of log files to be retained via Log Rotation. Defaults to 20. Returns: From a87531e1d0a808f69ce9d12a7e2fc9a9bb962641 Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Fri, 31 Jul 2020 17:26:37 +0530 Subject: [PATCH 149/191] fix(get_logger): allow None for module --- frappe/utils/logger.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frappe/utils/logger.py b/frappe/utils/logger.py index 451211b74b..398cdd0bfc 100755 --- a/frappe/utils/logger.py +++ b/frappe/utils/logger.py @@ -16,11 +16,11 @@ import frappe default_log_level = logging.DEBUG -def get_logger(module, with_more_info=False, allow_site=True, filter=None, max_size=100_000, file_count=20): +def get_logger(module=None, with_more_info=False, allow_site=True, filter=None, max_size=100_000, file_count=20): """Application Logger for your given module Args: - module (str): Name of your logger and consequently your log file. + module (str, optional): Name of your logger and consequently your log file. Defaults to None. with_more_info (bool, optional): Will log the form dict using the SiteContextFilter. Defaults to False. allow_site ((str, bool), optional): Pass site name to explicitly log under it's logs. If True and unspecified, guesses which site the logs would be saved under. Defaults to True. filter (function, optional): Add a filter function for your logger. Defaults to None. From c423eb7288623fb770216d712e923ee96c22e88f Mon Sep 17 00:00:00 2001 From: prssanna Date: Fri, 31 Jul 2020 18:05:23 +0530 Subject: [PATCH 150/191] fix: text in MultiSelectList doesn't get truncated --- frappe/public/js/frappe/form/controls/multiselect_list.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/public/js/frappe/form/controls/multiselect_list.js b/frappe/public/js/frappe/form/controls/multiselect_list.js index cd86bdd767..2a7ee5cb10 100644 --- a/frappe/public/js/frappe/form/controls/multiselect_list.js +++ b/frappe/public/js/frappe/form/controls/multiselect_list.js @@ -3,7 +3,7 @@ frappe.ui.form.ControlMultiSelectList = frappe.ui.form.ControlData.extend({ let template = `