From c0d210206fb37624888d6f7b8aba92a6e7366063 Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Thu, 2 Apr 2020 18:12:43 +0530 Subject: [PATCH 001/361] 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/361] 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/361] 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/361] 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/361] 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/361] 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/361] 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/361] 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/361] 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/361] 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/361] 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/361] 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 0781d048d178aea71ccfdeeeb5d6caac3db42299 Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Tue, 2 Jun 2020 12:33:01 +0530 Subject: [PATCH 013/361] 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 014/361] 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 015/361] 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 26063d88a2ae063fb30aebd1dd370feac7a158bf Mon Sep 17 00:00:00 2001 From: Ronel Cabrera Date: Fri, 26 Jun 2020 12:29:38 +0800 Subject: [PATCH 016/361] feat(controls/text_editor): resize image for markdown editor --- .../js/frappe/form/controls/text_editor.js | 5 +- package.json | 1 + yarn.lock | 52 ++++++++++++++++++- 3 files changed, 56 insertions(+), 2 deletions(-) diff --git a/frappe/public/js/frappe/form/controls/text_editor.js b/frappe/public/js/frappe/form/controls/text_editor.js index 3c0f7d5110..3b887aba0e 100644 --- a/frappe/public/js/frappe/form/controls/text_editor.js +++ b/frappe/public/js/frappe/form/controls/text_editor.js @@ -1,5 +1,7 @@ import Quill from 'quill'; +import ImageResize from 'quill-image-resize'; +Quill.register('modules/imageResize', ImageResize); const CodeBlockContainer = Quill.import('formats/code-block-container'); CodeBlockContainer.tagName = 'PRE'; Quill.register(CodeBlockContainer, true); @@ -146,7 +148,8 @@ frappe.ui.form.ControlTextEditor = frappe.ui.form.ControlCode.extend({ return { modules: { toolbar: this.get_toolbar_options(), - table: true + table: true, + imageResize: {} }, theme: 'snow' }; diff --git a/package.json b/package.json index 3167db9e13..8326e88e71 100644 --- a/package.json +++ b/package.json @@ -39,6 +39,7 @@ "moment-timezone": "^0.5.28", "quagga": "^0.12.1", "quill": "2.0.0-dev.4", + "quill-image-resize": "^3.0.9", "qz-tray": "^2.0.8", "redis": "^2.8.0", "showdown": "^1.9.1", diff --git a/yarn.lock b/yarn.lock index 45a33c5d42..de8ba197c2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1052,7 +1052,7 @@ cliui@^5.0.0: strip-ansi "^5.2.0" wrap-ansi "^5.1.0" -clone@^2.1.2: +clone@^2.1.1, clone@^2.1.2: version "2.1.2" resolved "https://registry.yarnpkg.com/clone/-/clone-2.1.2.tgz#1b7f4b9f591f1e8f83670401600345a02887435f" integrity sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18= @@ -2015,6 +2015,11 @@ eventemitter2@4.1.2: resolved "https://registry.yarnpkg.com/eventemitter2/-/eventemitter2-4.1.2.tgz#0e1a8477af821a6ef3995b311bf74c23a5247f15" integrity sha1-DhqEd6+CGm7zmVsxG/dMI6UkfxU= +eventemitter3@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-2.0.3.tgz#b5e1079b59fb5e1ba2771c0a993be060a58c99ba" + integrity sha1-teEHm1n7XhuidxwKmTvgYKWMmbo= + eventemitter3@^4.0.0: version "4.0.4" resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.4.tgz#b5463ace635a083d018bdc7c917b4c5f10a85384" @@ -2200,6 +2205,11 @@ fast-deep-equal@^3.1.1: resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.1.tgz#545145077c501491e33b15ec408c294376e94ae4" integrity sha512-8UEa58QDLauDNfpbrX55Q9jrGHThw2ZMdOky5Gl1CDtVeJDPVrG4Jxx1N8jw2gkWaff5UUuX1KJd+9zGe2B+ZA== +fast-diff@1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/fast-diff/-/fast-diff-1.1.2.tgz#4b62c42b8e03de3f848460b639079920695d0154" + integrity sha512-KaJUt+M9t1qaIteSvjc6P3RbMdXsNhK61GRftR6SNxqmhthcd9MGIi4T+o0jD8LUSpSnSKXE20nLtJ3fOHxQig== + fast-diff@1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/fast-diff/-/fast-diff-1.2.0.tgz#73ee11982d86caaf7959828d519cfe927fac5f03" @@ -4433,6 +4443,11 @@ parchment@2.0.0-dev.2: resolved "https://registry.yarnpkg.com/parchment/-/parchment-2.0.0-dev.2.tgz#9d6fe57b3721317bd1c481ea38ffa9b287d496b8" integrity sha512-4fgRny4pPISoML08Zp7poi52Dff3E2G1ORTi2D/acJ/RiROdDAMDB6VcQNfBcmehrX5Wixp6dxh6JjLyE5yUNQ== +parchment@^1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/parchment/-/parchment-1.1.4.tgz#aeded7ab938fe921d4c34bc339ce1168bc2ffde5" + integrity sha512-J5FBQt/pM2inLzg4hEWmzQx/8h8D0CiDxaG3vyp9rKrQRSDgBlhjdP5jQGgosEajXPSQouXGHOmVdgo7QmJuOg== + parse-data-uri@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/parse-data-uri/-/parse-data-uri-0.2.0.tgz#bf04d851dd5c87b0ab238e5d01ace494b604b4c9" @@ -5124,6 +5139,24 @@ quill-delta@4.2.1: extend "^3.0.2" fast-diff "1.2.0" +quill-delta@^3.6.2: + version "3.6.3" + resolved "https://registry.yarnpkg.com/quill-delta/-/quill-delta-3.6.3.tgz#b19fd2b89412301c60e1ff213d8d860eac0f1032" + integrity sha512-wdIGBlcX13tCHOXGMVnnTVFtGRLoP0imqxM696fIPwIf5ODIYUHIvHbZcyvGlZFiFhK5XzDC2lpjbxRhnM05Tg== + dependencies: + deep-equal "^1.0.1" + extend "^3.0.2" + fast-diff "1.1.2" + +quill-image-resize@^3.0.9: + version "3.0.9" + resolved "https://registry.yarnpkg.com/quill-image-resize/-/quill-image-resize-3.0.9.tgz#5677fb6257560bff951153ddbdb836758e49f804" + integrity sha512-5Dk0nixhbFsCwSWtPU9qqqtfM2gURfaP+pbBhQvAoMJoF4p99xbAibfAI3gsZJkbWUodoK2iAPf1V5oTSpv9ww== + dependencies: + lodash "^4.17.4" + quill "^1.2.2" + raw-loader "^0.5.1" + quill@2.0.0-dev.4: version "2.0.0-dev.4" resolved "https://registry.yarnpkg.com/quill/-/quill-2.0.0-dev.4.tgz#130e38efe7a16b3766d767d750c8aacc038945e7" @@ -5136,6 +5169,18 @@ quill@2.0.0-dev.4: parchment "2.0.0-dev.2" quill-delta "4.2.1" +quill@^1.2.2: + version "1.3.7" + resolved "https://registry.yarnpkg.com/quill/-/quill-1.3.7.tgz#da5b2f3a2c470e932340cdbf3668c9f21f9286e8" + integrity sha512-hG/DVzh/TiknWtE6QmWAF/pxoZKYxfe3J/d/+ShUWkDvvkZQVTPeVmUJVu1uE6DDooC4fWTiCLh84ul89oNz5g== + dependencies: + clone "^2.1.1" + deep-equal "^1.0.1" + eventemitter3 "^2.0.3" + extend "^3.0.2" + parchment "^1.1.4" + quill-delta "^3.6.2" + qz-tray@^2.0.8: version "2.0.8" resolved "https://registry.yarnpkg.com/qz-tray/-/qz-tray-2.0.8.tgz#5e15d102cf3a11a31ddb332891c7f8a6af8f6ad5" @@ -5171,6 +5216,11 @@ raw-body@^2.2.0: iconv-lite "0.4.24" unpipe "1.0.0" +raw-loader@^0.5.1: + version "0.5.1" + resolved "https://registry.yarnpkg.com/raw-loader/-/raw-loader-0.5.1.tgz#0c3d0beaed8a01c966d9787bf778281252a979aa" + integrity sha1-DD0L6u2KAclm2Xh793goElKpeao= + rc@^1.0.1, rc@^1.1.6: version "1.2.8" resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed" From 99596e9c3c4112b66fc664829726dd91176bfe99 Mon Sep 17 00:00:00 2001 From: prssanna Date: Thu, 9 Jul 2020 11:53:44 +0530 Subject: [PATCH 017/361] fix: auto email report for group by reports --- frappe/core/doctype/report/report.py | 27 ++++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/frappe/core/doctype/report/report.py b/frappe/core/doctype/report/report.py index 967b28b8b2..3228fec13a 100644 --- a/frappe/core/doctype/report/report.py +++ b/frappe/core/doctype/report/report.py @@ -173,8 +173,6 @@ class Report(Document): columns = params.get('fields') elif params.get('columns'): columns = params.get('columns') - elif params.get('fields'): - columns = params.get('fields') else: columns = [['name', self.ref_doctype]] for df in frappe.get_meta(self.ref_doctype).fields: @@ -204,12 +202,23 @@ class Report(Document): if params.get('sort_by_next'): order_by += ', ' + _format(params.get('sort_by_next').split('.')) + ' ' + params.get('sort_order_next') + group_by = None + if params.get('group_by'): + group_by_args = frappe._dict(params['group_by']) + group_by = group_by_args['group_by'] + order_by = '_aggregate_column desc' + result = frappe.get_list(self.ref_doctype, - fields = [_format([c[1], c[0]]) for c in columns], + fields = [ + get_group_by_field(group_by_args, c[1]) if c[0] == '_aggregate_column' and group_by_args + else _format([c[1], c[0]]) + for c in columns + ], filters=_filters, order_by = order_by, as_list=True, limit=limit, + group_by=group_by, user=user) _columns = [] @@ -262,3 +271,15 @@ def is_prepared_report_disabled(report): def get_report_module_dotted_path(module, report_name): return frappe.local.module_app[scrub(module)] + "." + scrub(module) \ + ".report." + scrub(report_name) + "." + scrub(report_name) + +def get_group_by_field(args, doctype): + if args['aggregate_function'] == 'count': + group_by_field = 'count(*) as _aggregate_column' + else: + group_by_field = '{0}(`tab{1}`.{2}) as _aggregate_column'.format( + args.aggregate_function, + doctype, + args.aggregate_on + ) + + return group_by_field \ No newline at end of file From ccde503c5a0b81887eba33e0a17069d9ba08099b Mon Sep 17 00:00:00 2001 From: marination Date: Mon, 13 Jul 2020 19:27:46 +0530 Subject: [PATCH 018/361] fix: Total Row in Reports --- .../js/frappe/views/reports/query_report.js | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 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 f82956adac..609e1feb46 100644 --- a/frappe/public/js/frappe/views/reports/query_report.js +++ b/frappe/public/js/frappe/views/reports/query_report.js @@ -861,8 +861,19 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { if (column.isHeader && !data && this.data) { // totalRow doesn't have a data object // proxy it using the first data object - // this is needed only for currency formatting - data = this.data[0]; + // applied to Float, Currency fields, needed only for currency formatting. + // make first data column have value 'Total' + let index = 1; + if (this.datatable && this.datatable.options.checkboxColumn) index = 2; + + 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)) { + // proxy for currency and float + data = this.data[0]; + } } return frappe.format(value, column, {for_print: false, always_show_decimals: true}, data); From f057260e90a5e8c7888ac9b62431bb19d09063ef Mon Sep 17 00:00:00 2001 From: prssanna Date: Tue, 14 Jul 2020 17:49:34 +0530 Subject: [PATCH 019/361] fix: reorder result according to custom columns for script reports --- frappe/desk/query_report.py | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/frappe/desk/query_report.py b/frappe/desk/query_report.py index 0edfd57d4f..d0a32ef076 100644 --- a/frappe/desk/query_report.py +++ b/frappe/desk/query_report.py @@ -67,8 +67,7 @@ def generate_report_result(report, filters=None, user=None, custom_columns=None) # Reordered columns columns = json.loads(report.custom_columns) - if report.report_type == 'Query Report': - result = reorder_data_for_custom_columns(columns, query_columns, result) + result = reorder_data_for_custom_columns(columns, query_columns, result, report.report_type) result = add_data_to_custom_columns(columns, result) @@ -216,15 +215,21 @@ def add_data_to_custom_columns(columns, result): return data -def reorder_data_for_custom_columns(custom_columns, columns, result): +def reorder_data_for_custom_columns(custom_columns, columns, result, report_type): + custom_column_labels = [col["label"] for col in custom_columns] + + if report_type == 'Query Report': + original_column_labels = [col.split(":")[0] for col in columns] + else: + original_column_labels = [col["label"] for col in columns] + reordered_result = [] - columns = [col.split(":")[0] for col in columns] for res in result: r = [] - for col in custom_columns: + for col_name in custom_column_labels: try: - idx = columns.index(col.get("label")) + idx = original_column_labels.index(col_name) r.append(res[idx]) except ValueError: pass From 476e625261ab4954b7429978b24840e52967c9c0 Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Thu, 16 Jul 2020 16:26:20 +0530 Subject: [PATCH 020/361] fix: Add site validation for logger --- frappe/__init__.py | 4 ++-- frappe/app.py | 5 +++-- frappe/utils/logger.py | 4 ++-- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/frappe/__init__.py b/frappe/__init__.py index d644d2a473..8c24980189 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): +def logger(module=None, with_more_info=False, _site=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) + return get_logger(module=module, with_more_info=with_more_info, _site=_site) def log_error(message=None, title=_("Error")): '''Log error to Error Log''' diff --git a/frappe/app.py b/frappe/app.py index 57db867882..83a9b8c149 100644 --- a/frappe/app.py +++ b/frappe/app.py @@ -99,8 +99,9 @@ def application(request): frappe.monitor.stop(response) frappe.recorder.dump() - frappe.logger("frappe.web").info({ - "site": get_site_name(request.host), + _site = get_site_name(request.host) + frappe.logger("frappe.web", _site=_site).info({ + "site": _site, "remote_addr": getattr(request, "remote_addr", "NOTFOUND"), "base_url": getattr(request, "base_url", "NOTFOUND"), "full_path": getattr(request, "full_path", "NOTFOUND"), diff --git a/frappe/utils/logger.py b/frappe/utils/logger.py index 89e3711b0f..bea83297d2 100755 --- a/frappe/utils/logger.py +++ b/frappe/utils/logger.py @@ -17,7 +17,7 @@ default_log_level = logging.DEBUG site = getattr(frappe.local, 'site', None) -def get_logger(module, with_more_info=False): +def get_logger(module, with_more_info=False, _site=None): global site if module in frappe.loggers: return frappe.loggers[module] @@ -38,7 +38,7 @@ def get_logger(module, with_more_info=False): handler = RotatingFileHandler(LOG_FILENAME, maxBytes=100_000, backupCount=20) logger.addHandler(handler) # - if site: + if site == _site: SITELOG_FILENAME = os.path.join(site, 'logs', logfile) site_handler = RotatingFileHandler(SITELOG_FILENAME, maxBytes=100_000, backupCount=20) site_handler.setFormatter(formatter) From e92a612ba231eebb8dbe7ac42d24ac002a89fbe1 Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Thu, 16 Jul 2020 16:26:53 +0530 Subject: [PATCH 021/361] docs(get_logger): add docstring --- frappe/utils/logger.py | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/frappe/utils/logger.py b/frappe/utils/logger.py index bea83297d2..afe4335aea 100755 --- a/frappe/utils/logger.py +++ b/frappe/utils/logger.py @@ -18,7 +18,24 @@ site = getattr(frappe.local, 'site', None) def get_logger(module, with_more_info=False, _site=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. + + Returns: + : Returns a Python logger object with Site and Bench level logging capabilities. + """ global site + + def allow_site(): + allow = False + if site: allow = True + if _site: allow = site == _site + return allow + if module in frappe.loggers: return frappe.loggers[module] @@ -37,8 +54,8 @@ def get_logger(module, with_more_info=False, _site=None): formatter = logging.Formatter('%(asctime)s %(levelname)s %(name)s %(message)s') handler = RotatingFileHandler(LOG_FILENAME, maxBytes=100_000, backupCount=20) logger.addHandler(handler) -# - if site == _site: + + if allow_site(): SITELOG_FILENAME = os.path.join(site, 'logs', logfile) site_handler = RotatingFileHandler(SITELOG_FILENAME, maxBytes=100_000, backupCount=20) site_handler.setFormatter(formatter) From c87d598480e006a88a4374d0ab17d15429701441 Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Fri, 17 Jul 2020 17:35:08 +0530 Subject: [PATCH 022/361] refactor: less word do trick Co-authored-by: Chinmay Pai --- frappe/utils/logger.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/frappe/utils/logger.py b/frappe/utils/logger.py index afe4335aea..9254c0aedd 100755 --- a/frappe/utils/logger.py +++ b/frappe/utils/logger.py @@ -31,10 +31,7 @@ def get_logger(module, with_more_info=False, _site=None): global site def allow_site(): - allow = False - if site: allow = True - if _site: allow = site == _site - return allow + return (_site and _site == site) or bool(site) if module in frappe.loggers: return frappe.loggers[module] From 0ae7d40ebe9268e9f08928f475bda7a920aa52db Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Mon, 20 Jul 2020 23:08:50 +0530 Subject: [PATCH 023/361] fix(paytm-integration): use checksum library to generate/verify checksum --- .../doctype/paytm_settings/checksum.py | 80 ------------------- .../doctype/paytm_settings/paytm_settings.py | 2 +- requirements.txt | 2 +- 3 files changed, 2 insertions(+), 82 deletions(-) delete mode 100644 frappe/integrations/doctype/paytm_settings/checksum.py diff --git a/frappe/integrations/doctype/paytm_settings/checksum.py b/frappe/integrations/doctype/paytm_settings/checksum.py deleted file mode 100644 index 32f976ae18..0000000000 --- a/frappe/integrations/doctype/paytm_settings/checksum.py +++ /dev/null @@ -1,80 +0,0 @@ -import base64 -import string -import random -import hashlib -import sys - -from Crypto.Cipher import AES - - -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) - -__unpad__ = lambda s: s[0:-ord(s[-1])] - -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") - -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) - -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 generateSignatureByString(params, key): - salt = generateRandomString(4) - return calculateChecksum(params, key, salt) - -def verifySignatureByString(params, key, checksum): - paytm_hash = decrypt(checksum, key) - salt = paytm_hash[-4:] - 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 getStringByParams(params): - params_string = [] - for key in sorted(params.keys()): - 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 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.py b/frappe/integrations/doctype/paytm_settings/paytm_settings.py index c169a53246..bfa9b6b3eb 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 generateSignature, verifySignature +from paytmchecksum import generateSignature, verifySignature from frappe.utils.password import get_decrypted_password class PaytmSettings(Document): diff --git a/requirements.txt b/requirements.txt index e6f6f9fff9..d053f8f48c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -66,5 +66,5 @@ watchdog==0.8.0 Werkzeug==0.16.1 xlrd==1.2.0 zxcvbn-python==4.4.24 -pycryptodome==3.9.7 Whoosh==2.7.4 +paytmchecksum==1.7.0 \ No newline at end of file From 158da07c6ab6b125086d60f83082d8d208272d8f Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Tue, 21 Jul 2020 00:12:06 +0530 Subject: [PATCH 024/361] fix: make the checkout page responsive for mobile view --- .../pages/integrations/paytm_checkout.html | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/frappe/templates/pages/integrations/paytm_checkout.html b/frappe/templates/pages/integrations/paytm_checkout.html index 77a2045edf..168f6597e5 100644 --- a/frappe/templates/pages/integrations/paytm_checkout.html +++ b/frappe/templates/pages/integrations/paytm_checkout.html @@ -16,8 +16,9 @@ {%- block page_content -%} -
-

Please do not refresh this page...

+
+

Please do not refresh this page...

+
{% for name, value in payment_details.items() %} @@ -29,9 +30,14 @@ {% block style %} {% endblock %} \ No newline at end of file From 1d9903bfdd51a42dae0034c1a0ceecde4b7fe5f2 Mon Sep 17 00:00:00 2001 From: prssanna Date: Tue, 21 Jul 2020 20:45:27 +0530 Subject: [PATCH 025/361] fix: get value from filter lsit if base list doesn't exist --- frappe/public/js/frappe/ui/filters/filter.js | 4 +++- frappe/public/js/frappe/ui/filters/filter_list.js | 7 ++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/frappe/public/js/frappe/ui/filters/filter.js b/frappe/public/js/frappe/ui/filters/filter.js index 5e41ed645e..d4fb6db436 100644 --- a/frappe/public/js/frappe/ui/filters/filter.js +++ b/frappe/public/js/frappe/ui/filters/filter.js @@ -249,7 +249,9 @@ 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.get_filter_value(field_name); + const filter_value = this.base_list + ? this.base_list.get_filter_value(field_name) + : this.filter_list.get_filter_value(field_name); args[field_name] = filter_value; } frappe diff --git a/frappe/public/js/frappe/ui/filters/filter_list.js b/frappe/public/js/frappe/ui/filters/filter_list.js index ed9ddefe64..7ae48bc7d9 100644 --- a/frappe/public/js/frappe/ui/filters/filter_list.js +++ b/frappe/public/js/frappe/ui/filters/filter_list.js @@ -104,13 +104,18 @@ frappe.ui.FilterGroup = class { filter_items: (doctype, fieldname) => { return !this.filter_exists([doctype, fieldname]); }, - base_list: this.base_list + base_list: this.base_list, + filter_list: this, }; let filter = new frappe.ui.Filter(args); this.filters.push(filter); return filter; } + get_filter_value(fieldname) { + return this.filters.filter(f => f.fieldname == fieldname)[0].value; + } + filter_exists(filter_value) { // filter_value of form: [doctype, fieldname, condition, value] let exists = false; From 3c4e1ea3a5fabb62f465a432760044b6db7529d2 Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Tue, 21 Jul 2020 22:20:27 +0530 Subject: [PATCH 026/361] fix: add pycryptodome for checksum generation --- requirements.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 0c57da9eac..33cd292bf6 100644 --- a/requirements.txt +++ b/requirements.txt @@ -68,4 +68,5 @@ Werkzeug==0.16.1 Whoosh==2.7.4 xlrd==1.2.0 zxcvbn-python==4.4.24 -paytmchecksum==1.7.0 +pycryptodome==3.9.8 +paytmchecksum==1.7.0 \ No newline at end of file From 1e56fcc85765681c79631116dd48bd13c1ff3368 Mon Sep 17 00:00:00 2001 From: prssanna Date: Wed, 22 Jul 2020 11:40:28 +0530 Subject: [PATCH 027/361] fix: filter out standard dashboards if not in developer mode --- .../dashboard_chart/dashboard_chart.js | 36 +++----------- .../dashboard_chart/dashboard_chart.py | 8 ++- .../desk/doctype/number_card/number_card.js | 36 +++----------- .../desk/doctype/number_card/number_card.py | 6 +++ .../public/js/frappe/utils/dashboard_utils.js | 49 +++++++++++++++++++ 5 files changed, 75 insertions(+), 60 deletions(-) diff --git a/frappe/desk/doctype/dashboard_chart/dashboard_chart.js b/frappe/desk/doctype/dashboard_chart/dashboard_chart.js index ee6eb3289d..65db4839a1 100644 --- a/frappe/desk/doctype/dashboard_chart/dashboard_chart.js +++ b/frappe/desk/doctype/dashboard_chart/dashboard_chart.js @@ -28,38 +28,16 @@ frappe.ui.form.on('Dashboard Chart', { } frm.add_custom_button('Add Chart to Dashboard', () => { - const d = new frappe.ui.Dialog({ - title: __('Add to Dashboard'), - fields: [ - { - label: __('Select Dashboard'), - fieldtype: 'Link', - fieldname: 'dashboard', - options: 'Dashboard', - } - ], - primary_action: (values) => { - values.chart_name = frm.doc.chart_name; - frappe.xcall( - 'frappe.desk.doctype.dashboard_chart.dashboard_chart.add_chart_to_dashboard', - {args: values} - ).then(()=> { - let dashboard_route_html = - `${values.dashboard}`; - let message = - __(`Dashboard Chart ${values.chart_name} add to Dashboard ` + dashboard_route_html); - - frappe.msgprint(message); - }); - - d.hide(); - } - }); + const dialog = frappe.dashboard_utils.get_add_to_dashboard_dialog( + frm.doc.name, + 'Dashboard Chart', + 'frappe.desk.doctype.dashboard_chart.dashboard_chart.add_chart_to_dashboard' + ); if (!frm.doc.chart_name) { frappe.msgprint(__('Please create chart first')); } else { - d.show(); + dialog.show(); } }); @@ -338,7 +316,7 @@ frappe.ui.form.on('Dashboard Chart', { } else if (frm.chart_filters.length) { fields = frm.chart_filters.filter(f => f.fieldname); - fields.map( f => { + fields.map(f => { if (filters[f.fieldname]) { let condition = '='; const filter_row = diff --git a/frappe/desk/doctype/dashboard_chart/dashboard_chart.py b/frappe/desk/doctype/dashboard_chart/dashboard_chart.py index 70aece3ee7..a25e0cab9e 100644 --- a/frappe/desk/doctype/dashboard_chart/dashboard_chart.py +++ b/frappe/desk/doctype/dashboard_chart/dashboard_chart.py @@ -128,7 +128,13 @@ def add_chart_to_dashboard(args): dashboard = frappe.get_doc('Dashboard', args.dashboard) dashboard_link = frappe.new_doc('Dashboard Chart Link') - dashboard_link.chart = args.chart_name + dashboard_link.chart = args.chart_name or args.name + + if args.set_standard: + chart = frappe.get_doc('Dashboard Chart', dashboard_link.chart) + chart.is_standard = 1 + chart.module = dashboard.module + chart.save() dashboard.append('charts', dashboard_link) dashboard.save() diff --git a/frappe/desk/doctype/number_card/number_card.js b/frappe/desk/doctype/number_card/number_card.js index 42899a4ebb..2ae163d7aa 100644 --- a/frappe/desk/doctype/number_card/number_card.js +++ b/frappe/desk/doctype/number_card/number_card.js @@ -32,40 +32,16 @@ frappe.ui.form.on('Number Card', { create_add_to_dashboard_button: function(frm) { frm.add_custom_button('Add Card to Dashboard', () => { - const d = new frappe.ui.Dialog({ - title: __('Add to Dashboard'), - fields: [ - { - label: __('Select Dashboard'), - fieldtype: 'Link', - fieldname: 'dashboard', - options: 'Dashboard', - } - ], - primary_action: (values) => { - values.name = frm.doc.name; - frappe.xcall( - 'frappe.desk.doctype.number_card.number_card.add_card_to_dashboard', - { - args: values - } - ).then(()=> { - let dashboard_route_html = - `${values.dashboard}`; - let message = - __(`Number Card ${values.name} add to Dashboard ` + dashboard_route_html); - - frappe.msgprint(message); - }); - - d.hide(); - } - }); + const dialog = frappe.dashboard_utils.get_add_to_dashboard_dialog( + frm.doc.name, + 'Number Card', + 'frappe.desk.doctype.number_card.number_card.add_card_to_dashboard' + ); if (!frm.doc.name) { frappe.msgprint(__('Please create Card first')); } else { - d.show(); + dialog.show(); } }); }, diff --git a/frappe/desk/doctype/number_card/number_card.py b/frappe/desk/doctype/number_card/number_card.py index 5b52b60474..3eb08ec00a 100644 --- a/frappe/desk/doctype/number_card/number_card.py +++ b/frappe/desk/doctype/number_card/number_card.py @@ -172,5 +172,11 @@ def add_card_to_dashboard(args): dashboard_link = frappe.new_doc('Number Card Link') dashboard_link.card = args.name + if args.set_standard: + card = frappe.get_doc('Number Card', dashboard_link.card) + card.is_standard = 1 + card.module = dashboard.module + card.save() + dashboard.append('cards', dashboard_link) dashboard.save() \ No newline at end of file diff --git a/frappe/public/js/frappe/utils/dashboard_utils.js b/frappe/public/js/frappe/utils/dashboard_utils.js index 8618f6dd59..4074404aa3 100644 --- a/frappe/public/js/frappe/utils/dashboard_utils.js +++ b/frappe/public/js/frappe/utils/dashboard_utils.js @@ -199,6 +199,55 @@ frappe.dashboard_utils = { } return filters; + }, + + get_dashboard_link_field() { + let field = { + label: __('Select Dashboard'), + fieldtype: 'Link', + fieldname: 'dashboard', + options: 'Dashboard', + } + + if (!frappe.boot.developer_mode) { + field.get_query = () => { + return { + filters: { + is_standard: 0 + } + }; + }; + } + + return field; + }, + + get_add_to_dashboard_dialog(docname, doctype, method) { + const field = this.get_dashboard_link_field(); + + const dialog = new frappe.ui.Dialog({ + title: __('Add to Dashboard'), + fields: [field], + primary_action: (values) => { + values.name = docname; + values.set_standard = frappe.boot.developer_mode; + frappe.xcall( + method, + {args: values} + ).then(()=> { + let dashboard_route_html = + `${values.dashboard}`; + let message = + __(`${doctype} ${values.name} added to Dashboard ` + dashboard_route_html); + + frappe.msgprint(message); + }); + + dialog.hide(); + } + }); + + return dialog; } }; \ No newline at end of file From cdd7cb2f177492e24423f3ac15bb575bbf94e22a Mon Sep 17 00:00:00 2001 From: prssanna Date: Wed, 22 Jul 2020 11:42:13 +0530 Subject: [PATCH 028/361] fix: filter out standard dashboards while creating from reports --- .../js/frappe/views/reports/query_report.js | 35 +++++++++---------- 1 file changed, 17 insertions(+), 18 deletions(-) diff --git a/frappe/public/js/frappe/views/reports/query_report.js b/frappe/public/js/frappe/views/reports/query_report.js index 9b094149b8..777be2aaa1 100644 --- a/frappe/public/js/frappe/views/reports/query_report.js +++ b/frappe/public/js/frappe/views/reports/query_report.js @@ -157,6 +157,9 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { add_card_to_dashboard() { let field_options = frappe.report_utils.get_field_options_from_report(this.columns, this.raw_data); + const dashboard_field = frappe.dashboard_utils.get_dashboard_link_field(); + const set_standard = frappe.boot.developer_mode; + const dialog = new frappe.ui.Dialog({ title: __('Create Card'), fields: [ @@ -181,12 +184,7 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { label: __('Add to Dashboard'), fieldtype: 'Section Break' }, - { - fieldname: 'dashboard', - label: __('Choose Dashboard'), - fieldtype: 'Link', - options: 'Dashboard', - }, + dashboard_field, { fieldname: 'cb_2', fieldtype: 'Column Break' @@ -202,7 +200,7 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { if (!values.label) { values.label = `${values.report_function} of ${toTitle(values.report_field)}`; } - this.create_number_card(values, values.dashboard, values.label); + this.create_number_card(values, values.dashboard, values.label, set_standard); dialog.hide(); } }); @@ -213,27 +211,26 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { add_chart_to_dashboard() { if (this.chart_fields || this.chart_options) { + const dashboard_field = frappe.dashboard_utils.get_dashboard_link_field(); + const set_standard = frappe.boot.developer_mode; + const dialog = new frappe.ui.Dialog({ title: __('Create Chart'), fields: [ - { - fieldname: 'dashboard', - label: 'Choose Dashboard', - fieldtype: 'Link', - options: 'Dashboard', - }, { fieldname: 'dashboard_chart_name', label: 'Chart Name', fieldtype: 'Data', - } + }, + dashboard_field, ], primary_action_label: __('Add'), primary_action: (values) => { this.create_dashboard_chart( this.chart_fields || this.chart_options, values.dashboard, - values.dashboard_chart_name + values.dashboard_chart_name, + set_standard ); dialog.hide(); } @@ -245,12 +242,13 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { } } - create_number_card(values, dashboard_name, card_name) { + create_number_card(values, dashboard_name, card_name, set_standard) { let args = { 'dashboard': dashboard_name || null, 'type': 'Report', 'report_name': this.report_name, 'filters_json': JSON.stringify(this.get_filter_values()), + set_standard: set_standard, }; Object.assign(args, values); @@ -263,7 +261,7 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { ); } - create_dashboard_chart(chart_args, dashboard_name, chart_name) { + create_dashboard_chart(chart_args, dashboard_name, chart_name, set_standard) { let args = { 'dashboard': dashboard_name || null, 'chart_type': 'Report', @@ -271,7 +269,8 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { 'type': chart_args.chart_type || frappe.model.unscrub(chart_args.type), 'color': chart_args.color, 'filters_json': JSON.stringify(this.get_filter_values()), - 'custom_options': {} + 'custom_options': {}, + 'set_standard': set_standard, }; for (let key in chart_args) { From 7f1a5adb0e221452019076aa05b3dafae7d153e0 Mon Sep 17 00:00:00 2001 From: prssanna Date: Wed, 22 Jul 2020 11:43:37 +0530 Subject: [PATCH 029/361] fix: disable dashboard form only if doc is standard --- frappe/desk/doctype/dashboard/dashboard.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/desk/doctype/dashboard/dashboard.js b/frappe/desk/doctype/dashboard/dashboard.js index 237b549433..61300b920b 100644 --- a/frappe/desk/doctype/dashboard/dashboard.js +++ b/frappe/desk/doctype/dashboard/dashboard.js @@ -5,7 +5,7 @@ frappe.ui.form.on('Dashboard', { refresh: function(frm) { frm.add_custom_button(__("Show Dashboard"), () => frappe.set_route('dashboard', frm.doc.name)); - if (!frappe.boot.developer_mode) { + if (!frappe.boot.developer_mode && frm.doc.is_standard) { frm.disable_form(); } From dd6a361058e425ec03d162f8e4f88a3251f5537a Mon Sep 17 00:00:00 2001 From: prssanna Date: Wed, 22 Jul 2020 11:44:21 +0530 Subject: [PATCH 030/361] fix: don't show is_standard if not in developer mode --- frappe/desk/doctype/dashboard/dashboard.json | 3 ++- frappe/desk/doctype/dashboard_chart/dashboard_chart.json | 3 ++- frappe/desk/doctype/number_card/number_card.json | 3 ++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/frappe/desk/doctype/dashboard/dashboard.json b/frappe/desk/doctype/dashboard/dashboard.json index c7128823fe..0c54f1ee7f 100644 --- a/frappe/desk/doctype/dashboard/dashboard.json +++ b/frappe/desk/doctype/dashboard/dashboard.json @@ -52,6 +52,7 @@ }, { "default": "0", + "depends_on": "eval: frappe.boot.developer_mode", "fieldname": "is_standard", "fieldtype": "Check", "label": "Is Standard" @@ -66,7 +67,7 @@ } ], "links": [], - "modified": "2020-07-10 17:48:19.468813", + "modified": "2020-07-22 11:23:37.858568", "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 a25d0cc6c5..f0616dddd0 100644 --- a/frappe/desk/doctype/dashboard_chart/dashboard_chart.json +++ b/frappe/desk/doctype/dashboard_chart/dashboard_chart.json @@ -238,6 +238,7 @@ }, { "default": "0", + "depends_on": "eval: frappe.boot.developer_mode", "fieldname": "is_standard", "fieldtype": "Check", "label": "Is Standard" @@ -270,7 +271,7 @@ } ], "links": [], - "modified": "2020-07-21 16:37:07.763482", + "modified": "2020-07-22 11:32:51.185499", "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 333885a7f9..0d1cc279c8 100644 --- a/frappe/desk/doctype/number_card/number_card.json +++ b/frappe/desk/doctype/number_card/number_card.json @@ -113,6 +113,7 @@ }, { "default": "0", + "depends_on": "eval: frappe.boot.developer_mode", "fieldname": "is_standard", "fieldtype": "Check", "label": "Is Standard" @@ -190,7 +191,7 @@ } ], "links": [], - "modified": "2020-07-18 17:08:22.882538", + "modified": "2020-07-22 11:26:32.039023", "modified_by": "Administrator", "module": "Desk", "name": "Number Card", From 1bd610f858a29a0d32681d175b418bc675a2817d Mon Sep 17 00:00:00 2001 From: prssanna Date: Wed, 22 Jul 2020 11:51:32 +0530 Subject: [PATCH 031/361] fix: separate patch to rename is_custom field --- frappe/patches.txt | 1 + .../create_custom_dashboards_cards_and_charts.py | 5 ----- .../rename_is_custom_field_in_dashboard_chart.py | 11 +++++++++++ 3 files changed, 12 insertions(+), 5 deletions(-) create mode 100644 frappe/patches/v13_0/rename_is_custom_field_in_dashboard_chart.py diff --git a/frappe/patches.txt b/frappe/patches.txt index f8c767f5a3..b207a325cb 100644 --- a/frappe/patches.txt +++ b/frappe/patches.txt @@ -295,3 +295,4 @@ frappe.patches.v13_0.update_date_filters_in_user_settings frappe.patches.v13_0.update_duration_options frappe.patches.v13_0.replace_old_data_import # 2020-06-24 frappe.patches.v13_0.create_custom_dashboards_cards_and_charts +frappe.patches.v13_0.rename_is_custom_field_in_dashboard_chart diff --git a/frappe/patches/v13_0/create_custom_dashboards_cards_and_charts.py b/frappe/patches/v13_0/create_custom_dashboards_cards_and_charts.py index f61941bf01..9a075a22cc 100644 --- a/frappe/patches/v13_0/create_custom_dashboards_cards_and_charts.py +++ b/frappe/patches/v13_0/create_custom_dashboards_cards_and_charts.py @@ -1,7 +1,6 @@ import frappe from frappe.model.naming import append_number_if_name_exists from frappe.utils.dashboard import get_dashboards_with_link -from frappe.model.utils.rename_field import rename_field def execute(): if not frappe.db.table_exists('Dashboard Chart')\ @@ -9,14 +8,10 @@ def execute(): or not frappe.db.table_exists('Dashboard'): return - frappe.reload_doc('desk', 'doctype', 'dashboard_chart') frappe.reload_doc('desk', 'doctype', 'number_card') frappe.reload_doc('desk', 'doctype', 'dashboard') - if frappe.db.has_column('Dashboard Chart', 'is_custom'): - rename_field('Dashboard Chart', 'is_custom', 'use_report_chart') - modified_charts = get_modified_docs('Dashboard Chart') modified_cards = get_modified_docs('Number Card') modified_dashboards = [doc.name for doc in get_modified_docs('Dashboard')] diff --git a/frappe/patches/v13_0/rename_is_custom_field_in_dashboard_chart.py b/frappe/patches/v13_0/rename_is_custom_field_in_dashboard_chart.py new file mode 100644 index 0000000000..90860e211b --- /dev/null +++ b/frappe/patches/v13_0/rename_is_custom_field_in_dashboard_chart.py @@ -0,0 +1,11 @@ +import frappe +from frappe.model.utils.rename_field import rename_field + +def execute(): + if not frappe.db.table_exists('Dashboard Chart') + return + + frappe.reload_doc('desk', 'doctype', 'dashboard_chart') + + if frappe.db.has_column('Dashboard Chart', 'is_custom'): + rename_field('Dashboard Chart', 'is_custom', 'use_report_chart') \ No newline at end of file From c25100cb47260f2e898f85cd820b952735cd97e7 Mon Sep 17 00:00:00 2001 From: prssanna Date: Wed, 22 Jul 2020 11:59:17 +0530 Subject: [PATCH 032/361] style: fix formatting --- .../patches/v13_0/rename_is_custom_field_in_dashboard_chart.py | 2 +- frappe/public/js/frappe/utils/dashboard_utils.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/frappe/patches/v13_0/rename_is_custom_field_in_dashboard_chart.py b/frappe/patches/v13_0/rename_is_custom_field_in_dashboard_chart.py index 90860e211b..4da0f8164a 100644 --- a/frappe/patches/v13_0/rename_is_custom_field_in_dashboard_chart.py +++ b/frappe/patches/v13_0/rename_is_custom_field_in_dashboard_chart.py @@ -2,7 +2,7 @@ import frappe from frappe.model.utils.rename_field import rename_field def execute(): - if not frappe.db.table_exists('Dashboard Chart') + if not frappe.db.table_exists('Dashboard Chart'): return frappe.reload_doc('desk', 'doctype', 'dashboard_chart') diff --git a/frappe/public/js/frappe/utils/dashboard_utils.js b/frappe/public/js/frappe/utils/dashboard_utils.js index 4074404aa3..fca26d6ece 100644 --- a/frappe/public/js/frappe/utils/dashboard_utils.js +++ b/frappe/public/js/frappe/utils/dashboard_utils.js @@ -207,7 +207,7 @@ frappe.dashboard_utils = { fieldtype: 'Link', fieldname: 'dashboard', options: 'Dashboard', - } + }; if (!frappe.boot.developer_mode) { field.get_query = () => { From 95aa4a72dcd81b98a9f318b35979964bdedbc032 Mon Sep 17 00:00:00 2001 From: prssanna Date: Wed, 22 Jul 2020 14:53:03 +0530 Subject: [PATCH 033/361] 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 034/361] 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 c64df2787f8328e6d7e46c4cb722ec27820eb722 Mon Sep 17 00:00:00 2001 From: prssanna Date: Wed, 22 Jul 2020 16:51:17 +0530 Subject: [PATCH 035/361] 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 036/361] 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 037/361] 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 038/361] 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 039/361] 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 040/361] 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 041/361] 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 4b7e36f90da10a8c49fdbb827ea922e1f548e1e5 Mon Sep 17 00:00:00 2001 From: Faris Ansari Date: Wed, 22 Jul 2020 19:30:17 +0530 Subject: [PATCH 042/361] 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 043/361] 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 044/361] 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 6187de8e3da34cc4f0f74198f1148170c3d9d83d Mon Sep 17 00:00:00 2001 From: prssanna Date: Thu, 23 Jul 2020 11:12:13 +0530 Subject: [PATCH 045/361] 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 046/361] 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 047/361] 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 048/361] 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 049/361] 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 050/361] 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 051/361] 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 b0b53e03f6a602c5cddcf2c3de5db580dba197af Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Thu, 23 Jul 2020 19:01:18 +0530 Subject: [PATCH 052/361] feat: Add Bulk restore API --- .../deleted_document/deleted_document.py | 34 +++++++++++++++++-- frappe/exceptions.py | 1 + 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/frappe/core/doctype/deleted_document/deleted_document.py b/frappe/core/doctype/deleted_document/deleted_document.py index bc2962ab3f..a1786e0a3e 100644 --- a/frappe/core/doctype/deleted_document/deleted_document.py +++ b/frappe/core/doctype/deleted_document/deleted_document.py @@ -4,6 +4,7 @@ from __future__ import unicode_literals import frappe, json +from frappe.desk.doctype.bulk_update.bulk_update import show_progress from frappe.model.document import Document from frappe import _ @@ -11,9 +12,14 @@ class DeletedDocument(Document): pass @frappe.whitelist() -def restore(name): +def restore(name, alert=True): deleted = frappe.get_doc('Deleted Document', name) + + if deleted.restored: + frappe.throw(_("Document {0} Already Restored".format(name)), exc=frappe.DocumentAlreadyRestored) + doc = frappe.get_doc(json.loads(deleted.data)) + try: doc.insert() except frappe.DocstatusTransitionError: @@ -27,4 +33,28 @@ def restore(name): deleted.restored = 1 deleted.db_update() - frappe.msgprint(_('Document Restored')) + if alert: + frappe.msgprint(_('Document Restored')) + + +@frappe.whitelist() +def bulk_restore(docnames): + docnames = frappe.parse_json(docnames) + doctype = "Deleted Document" + restored = [] + failed = [] + + for i, d in enumerate(docnames): + try: + restore(d, alert=False) + message = _('Restoring {0}').format(doctype) + frappe.db.commit() + show_progress(docnames, message, i+1, d) + restored.append(d) + except frappe.DocumentAlreadyRestored: + failed.append(d) + except Exception as e: + failed.append(d) + frappe.db.rollback() + + return restored \ No newline at end of file diff --git a/frappe/exceptions.py b/frappe/exceptions.py index 8ebda9c7b8..88428b875c 100644 --- a/frappe/exceptions.py +++ b/frappe/exceptions.py @@ -104,6 +104,7 @@ class IncompatibleApp(ValidationError): pass class InvalidDates(ValidationError): pass class DataTooLongException(ValidationError): pass class FileAlreadyAttachedException(Exception): pass +class DocumentAlreadyRestored(Exception): pass # OAuth exceptions class InvalidAuthorizationHeader(CSRFTokenError): pass class InvalidAuthorizationPrefix(CSRFTokenError): pass From 70e966d5b691b8b9d3889fb21e65d828d35549af Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Thu, 23 Jul 2020 19:01:51 +0530 Subject: [PATCH 053/361] style: formatting + dropped unused variables --- .../deleted_document/deleted_document.py | 20 ++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/frappe/core/doctype/deleted_document/deleted_document.py b/frappe/core/doctype/deleted_document/deleted_document.py index a1786e0a3e..636e3c4f37 100644 --- a/frappe/core/doctype/deleted_document/deleted_document.py +++ b/frappe/core/doctype/deleted_document/deleted_document.py @@ -3,14 +3,17 @@ # For license information, please see license.txt from __future__ import unicode_literals -import frappe, json +import frappe +import json from frappe.desk.doctype.bulk_update.bulk_update import show_progress from frappe.model.document import Document from frappe import _ + class DeletedDocument(Document): pass + @frappe.whitelist() def restore(name, alert=True): deleted = frappe.get_doc('Deleted Document', name) @@ -40,21 +43,20 @@ def restore(name, alert=True): @frappe.whitelist() def bulk_restore(docnames): docnames = frappe.parse_json(docnames) - doctype = "Deleted Document" + message = _('Restoring Deleted Document') restored = [] - failed = [] for i, d in enumerate(docnames): try: + show_progress(docnames, message, i + 1, d) restore(d, alert=False) - message = _('Restoring {0}').format(doctype) frappe.db.commit() - show_progress(docnames, message, i+1, d) restored.append(d) + except frappe.DocumentAlreadyRestored: - failed.append(d) - except Exception as e: - failed.append(d) + pass + + except Exception: frappe.db.rollback() - return restored \ No newline at end of file + return restored From 9dd0304773c10fb363351cf1e0f07634082ac624 Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Thu, 23 Jul 2020 19:02:25 +0530 Subject: [PATCH 054/361] feat: Bulk Restore action under Deleted Document --- .../deleted_document/deleted_document_list.js | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 frappe/core/doctype/deleted_document/deleted_document_list.js diff --git a/frappe/core/doctype/deleted_document/deleted_document_list.js b/frappe/core/doctype/deleted_document/deleted_document_list.js new file mode 100644 index 0000000000..eb5a9e087d --- /dev/null +++ b/frappe/core/doctype/deleted_document/deleted_document_list.js @@ -0,0 +1,33 @@ +frappe.listview_settings["Deleted Document"] = { + onload: function (doclist) { + const action = () => { + const selected_docs = doclist.get_checked_items(); + if (selected_docs.length > 0) { + let docnames = selected_docs.map((doc) => { + return doc.name; + }); + frappe.call({ + method: + "frappe.core.doctype.deleted_document.deleted_document.bulk_restore", + args: { docnames: docnames }, + callback: function (r) { + if (r.message) { + let num = r.message.length; + frappe.msgprint( + __("{0} Document{1} {2} Restored", [ + num, + num == 1 ? "" : "s", + num == 1 ? "was" : "were", + ]) + ); + if (num > 0) { + doclist.refresh(); + } + } + }, + }); + } + }; + doclist.page.add_actions_menu_item(__("Restore"), action, false); + }, +}; From 0fdb7953bb8b86b7fabe3f34cbf9987bf9d107c9 Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Thu, 23 Jul 2020 19:12:08 +0530 Subject: [PATCH 055/361] fix: translation syntax and style fixes --- frappe/core/doctype/deleted_document/deleted_document.py | 2 +- .../core/doctype/deleted_document/deleted_document_list.js | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/frappe/core/doctype/deleted_document/deleted_document.py b/frappe/core/doctype/deleted_document/deleted_document.py index 636e3c4f37..598bd00cf6 100644 --- a/frappe/core/doctype/deleted_document/deleted_document.py +++ b/frappe/core/doctype/deleted_document/deleted_document.py @@ -19,7 +19,7 @@ def restore(name, alert=True): deleted = frappe.get_doc('Deleted Document', name) if deleted.restored: - frappe.throw(_("Document {0} Already Restored".format(name)), exc=frappe.DocumentAlreadyRestored) + frappe.throw(_("Document {0} Already Restored").format(name), exc=frappe.DocumentAlreadyRestored) doc = frappe.get_doc(json.loads(deleted.data)) diff --git a/frappe/core/doctype/deleted_document/deleted_document_list.js b/frappe/core/doctype/deleted_document/deleted_document_list.js index eb5a9e087d..12ed62f6de 100644 --- a/frappe/core/doctype/deleted_document/deleted_document_list.js +++ b/frappe/core/doctype/deleted_document/deleted_document_list.js @@ -7,9 +7,8 @@ frappe.listview_settings["Deleted Document"] = { return doc.name; }); frappe.call({ - method: - "frappe.core.doctype.deleted_document.deleted_document.bulk_restore", - args: { docnames: docnames }, + method: "frappe.core.doctype.deleted_document.deleted_document.bulk_restore", + args: { "docnames": docnames }, callback: function (r) { if (r.message) { let num = r.message.length; From 441630acc48bc58b71bd0fe100b73559e5180150 Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Thu, 23 Jul 2020 23:43:35 +0530 Subject: [PATCH 056/361] 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 057/361] 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 ac1f82af9aeb457a202bb8a2907f3aed1df3e7aa Mon Sep 17 00:00:00 2001 From: gavin Date: Fri, 24 Jul 2020 11:41:10 +0530 Subject: [PATCH 058/361] fix: translation syntax Co-authored-by: Shivam Mishra --- .../deleted_document/deleted_document_list.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/frappe/core/doctype/deleted_document/deleted_document_list.js b/frappe/core/doctype/deleted_document/deleted_document_list.js index 12ed62f6de..4443dc883d 100644 --- a/frappe/core/doctype/deleted_document/deleted_document_list.js +++ b/frappe/core/doctype/deleted_document/deleted_document_list.js @@ -12,13 +12,13 @@ frappe.listview_settings["Deleted Document"] = { callback: function (r) { if (r.message) { let num = r.message.length; - frappe.msgprint( - __("{0} Document{1} {2} Restored", [ - num, - num == 1 ? "" : "s", - num == 1 ? "was" : "were", - ]) - ); + let message; + if (num === 1) { + message = "{0} Document was Restored" + } else { + message = "{0} Documents were Restored" + } + frappe.msgprint(__(message, [num]) if (num > 0) { doclist.refresh(); } From cfa286c5b03a01d9a6898f4bc90058c1470a08d1 Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Fri, 24 Jul 2020 11:58:50 +0530 Subject: [PATCH 059/361] style: formatting, syntax and spacing --- .../core/doctype/deleted_document/deleted_document_list.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/frappe/core/doctype/deleted_document/deleted_document_list.js b/frappe/core/doctype/deleted_document/deleted_document_list.js index 4443dc883d..a49d8125e3 100644 --- a/frappe/core/doctype/deleted_document/deleted_document_list.js +++ b/frappe/core/doctype/deleted_document/deleted_document_list.js @@ -14,11 +14,11 @@ frappe.listview_settings["Deleted Document"] = { let num = r.message.length; let message; if (num === 1) { - message = "{0} Document was Restored" + message = "{0} Document was Restored"; } else { - message = "{0} Documents were Restored" + message = "{0} Documents were Restored"; } - frappe.msgprint(__(message, [num]) + frappe.msgprint(__(message, [num])); if (num > 0) { doclist.refresh(); } From 85f8c712dcfba4f9c643e9d2f1dc4efd85aff010 Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Fri, 24 Jul 2020 11:59:29 +0530 Subject: [PATCH 060/361] fix: Show files restore summary --- .../deleted_document/deleted_document.py | 31 ++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/frappe/core/doctype/deleted_document/deleted_document.py b/frappe/core/doctype/deleted_document/deleted_document.py index 598bd00cf6..e637f4f000 100644 --- a/frappe/core/doctype/deleted_document/deleted_document.py +++ b/frappe/core/doctype/deleted_document/deleted_document.py @@ -45,6 +45,8 @@ def bulk_restore(docnames): docnames = frappe.parse_json(docnames) message = _('Restoring Deleted Document') restored = [] + invalid = [] + failed = [] for i, d in enumerate(docnames): try: @@ -54,9 +56,36 @@ def bulk_restore(docnames): restored.append(d) except frappe.DocumentAlreadyRestored: - pass + frappe.message_log.pop() + invalid.append(d) except Exception: + failed.append(d) frappe.db.rollback() + frappe.message_log.pop() + + if failed or invalid: + tail = "" + + restored_data = "" + restored_head = _("Documents restored successfully") + "
      " + for docname in restored: + restored_data += "
    • {0}
    • ".format(docname) + restored_body = restored_head + restored_data + tail + + invalid_data = "" + invalid_head = _("Documents that were already Restored") + "
        " + for docname in invalid: + invalid_data += "
      • {0}
      • ".format(docname) + invalid_body = invalid_head + invalid_data + tail + + failed_data = "" + failed_head = _("Documents that Failed to Restore") + "
          " + for docname in failed: + failed_data += "
        • {0}
        • ".format(docname) + failed_body = failed_head + failed_data + tail + + summary = (restored_body if restored else "") + (invalid_body if invalid else "") + (failed_body if failed else "") + frappe.msgprint(summary, title="Document Restoration Summary", indicator="orange", is_minimizable=True) return restored From 9d24389c096f1dd2c4edfdfd588f9192bf94e437 Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Fri, 24 Jul 2020 12:21:02 +0530 Subject: [PATCH 061/361] style: commonify summary generation --- .../deleted_document/deleted_document.py | 35 +++++++------------ 1 file changed, 13 insertions(+), 22 deletions(-) diff --git a/frappe/core/doctype/deleted_document/deleted_document.py b/frappe/core/doctype/deleted_document/deleted_document.py index e637f4f000..d8f39ca139 100644 --- a/frappe/core/doctype/deleted_document/deleted_document.py +++ b/frappe/core/doctype/deleted_document/deleted_document.py @@ -44,9 +44,7 @@ def restore(name, alert=True): def bulk_restore(docnames): docnames = frappe.parse_json(docnames) message = _('Restoring Deleted Document') - restored = [] - invalid = [] - failed = [] + restored, invalid, failed = [], [], [] for i, d in enumerate(docnames): try: @@ -60,32 +58,25 @@ def bulk_restore(docnames): invalid.append(d) except Exception: + frappe.message_log.pop() failed.append(d) frappe.db.rollback() - frappe.message_log.pop() if failed or invalid: - tail = "
        " + def body(docnames): + href = "
      • {0}
      • " + return "
          " + "".join([href.format(docname) for docname in docnames]) - restored_data = "" - restored_head = _("Documents restored successfully") + "
            " - for docname in restored: - restored_data += "
          • {0}
          • ".format(docname) - restored_body = restored_head + restored_data + tail + def message(title, docnames): + if docnames: + return _(title) + body(docnames) + "
          " + return "" - invalid_data = "" - invalid_head = _("Documents that were already Restored") + "
            " - for docname in invalid: - invalid_data += "
          • {0}
          • ".format(docname) - invalid_body = invalid_head + invalid_data + tail + restored_summary = message("Documents restored successfully", restored) + invalid_summary = message("Documents that were already Restored", invalid) + failed_summary = message("Documents that Failed to Restore", failed) - failed_data = "" - failed_head = _("Documents that Failed to Restore") + "
              " - for docname in failed: - failed_data += "
            • {0}
            • ".format(docname) - failed_body = failed_head + failed_data + tail - - summary = (restored_body if restored else "") + (invalid_body if invalid else "") + (failed_body if failed else "") + summary = restored_summary + invalid_summary + failed_summary frappe.msgprint(summary, title="Document Restoration Summary", indicator="orange", is_minimizable=True) return restored From 35f3b5b4051b9d0054c540e93e04606937048977 Mon Sep 17 00:00:00 2001 From: Wolfram Schmidt Date: Fri, 24 Jul 2020 14:11:24 +0200 Subject: [PATCH 062/361] 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 063/361] 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 064/361] 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 065/361] 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 af8771d5dccd8c882f74d5cca84e3275a6ae84a2 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Mon, 27 Jul 2020 15:51:44 +0530 Subject: [PATCH 066/361] fix: throw message for invalid date filter --- frappe/utils/data.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/frappe/utils/data.py b/frappe/utils/data.py index d1b409d1fc..f36fa656b0 100644 --- a/frappe/utils/data.py +++ b/frappe/utils/data.py @@ -94,7 +94,10 @@ def add_to_date(date, years=0, months=0, weeks=0, days=0, hours=0, minutes=0, se as_string = True if " " in date: as_datetime = True - date = parser.parse(date) + try: + date = parser.parse(date) + except: + frappe.throw(frappe._("Please select a valid date filter"), title=frappe._("Invalid Date")) date = date + relativedelta(years=years, months=months, weeks=weeks, days=days, hours=hours, minutes=minutes, seconds=seconds) From 28605191b1f1359b4b71ae9fd9435d47d26e9ad1 Mon Sep 17 00:00:00 2001 From: prssanna Date: Mon, 27 Jul 2020 16:59:49 +0530 Subject: [PATCH 067/361] 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 068/361] 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 069/361] 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 589a9b1d0f58e00fac8a6f4750157288363dbe02 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Mon, 27 Jul 2020 18:12:29 +0530 Subject: [PATCH 070/361] fix: OAUTH redirect fixes --- frappe/utils/oauth.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/frappe/utils/oauth.py b/frappe/utils/oauth.py index 969623c369..b06227e289 100644 --- a/frappe/utils/oauth.py +++ b/frappe/utils/oauth.py @@ -213,6 +213,7 @@ def login_oauth_user(data=None, provider=None, state=None, email_id=None, key=No redirect_post_login( desk_user=frappe.local.response.get('message') == 'Logged In', redirect_to=redirect_to, + provider=provider ) def update_oauth_user(user, data, provider): @@ -300,12 +301,13 @@ def get_last_name(data): def get_email(data): return data.get("email") or data.get("upn") or data.get("unique_name") -def redirect_post_login(desk_user, redirect_to=None): +def redirect_post_login(desk_user, redirect_to=None, provider=None): # redirect! frappe.local.response["type"] = "redirect" if not redirect_to: # the #desktop is added to prevent a facebook redirect bug - redirect_to = "/desk#desktop" if desk_user else "/me" + desk_uri = "/desk#desktop" if provider == 'facebook' else '/desk' + redirect_to = desk_uri if desk_user else "/me" - frappe.local.response["location"] = redirect_to + frappe.local.response["location"] = frappe.utils.get_url(redirect_to) From dc46450f89a2d95163eda8002f047ba3e6b04630 Mon Sep 17 00:00:00 2001 From: prssanna Date: Mon, 27 Jul 2020 18:14:02 +0530 Subject: [PATCH 071/361] 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 072/361] 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 10a5900ed3d792653623d97da4a3128737f03f63 Mon Sep 17 00:00:00 2001 From: Faris Ansari Date: Mon, 27 Jul 2020 19:47:46 +0530 Subject: [PATCH 073/361] fix: Attach files to the document if they are set programmatically --- frappe/core/doctype/file/file.py | 37 ++++++++++++++++++++++++++++++++ frappe/hooks.py | 3 ++- 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/frappe/core/doctype/file/file.py b/frappe/core/doctype/file/file.py index 1748c60020..316915e43a 100755 --- a/frappe/core/doctype/file/file.py +++ b/frappe/core/doctype/file/file.py @@ -922,3 +922,40 @@ def update_existing_file_docs(doc): content_hash=doc.content_hash, file_name=doc.name )) + +def attach_files_to_document(doc, event): + """ Runs on on_update hook of all documents. + Goes through every Attach and Attach Image field and attaches + the file url to the document if it is not already attached. + """ + + attach_fields = doc.meta.get( + "fields", {"fieldtype": ["in", ["Attach", "Attach Image"]]} + ) + + for df in attach_fields: + # this method runs in on_update hook of all documents + # we dont want the update to fail if file cannot be attached for some reason + try: + value = doc.get(df.fieldname) + if not value.startswith(("/files", "/private/files")): + return + + if frappe.db.exists("File", { + "file_url": value, + "attached_to_name": doc.name, + "attached_to_doctype": doc.doctype, + "attached_to_field": df.fieldname, + }): + return + + frappe.get_doc( + doctype="File", + file_url=value, + attached_to_name=doc.name, + attached_to_doctype=doc.doctype, + attached_to_field=df.fieldname, + folder="Home/Attachments", + ).insert() + except Exception: + frappe.log_error(title=_("Error Attaching File")) diff --git a/frappe/hooks.py b/frappe/hooks.py index 1f209f00a2..0a3d5d011c 100644 --- a/frappe/hooks.py +++ b/frappe/hooks.py @@ -132,7 +132,8 @@ doc_events = { "frappe.core.doctype.activity_log.feed.update_feed", "frappe.workflow.doctype.workflow_action.workflow_action.process_workflow_actions", "frappe.automation.doctype.assignment_rule.assignment_rule.apply", - "frappe.automation.doctype.milestone_tracker.milestone_tracker.evaluate_milestone" + "frappe.automation.doctype.milestone_tracker.milestone_tracker.evaluate_milestone", + "frappe.core.doctype.file.file.attach_files_to_document" ], "after_rename": "frappe.desk.notifications.clear_doctype_notifications", "on_cancel": [ From 786625a92a94536b749f0b02acc9fc4cf56516c7 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Mon, 27 Jul 2020 21:34:56 +0530 Subject: [PATCH 074/361] 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 075/361] 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 cb6208b19be27f72e05c017ef395588755a1aba1 Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Mon, 27 Jul 2020 22:49:42 +0530 Subject: [PATCH 076/361] fix(field-group): strip html to check for null values --- frappe/public/js/frappe/ui/field_group.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/public/js/frappe/ui/field_group.js b/frappe/public/js/frappe/ui/field_group.js index 4d827354d8..ec7234c9bb 100644 --- a/frappe/public/js/frappe/ui/field_group.js +++ b/frappe/public/js/frappe/ui/field_group.js @@ -86,7 +86,7 @@ frappe.ui.FieldGroup = frappe.ui.form.Layout.extend({ var f = this.fields_dict[key]; if(f.get_value) { var v = f.get_value(); - if(f.df.reqd && is_null(v)) + if(f.df.reqd && is_null(strip_html(cstr(v)))) errors.push(__(f.df.label)); if(!is_null(v)) ret[f.df.fieldname] = v; From a11496f05ca1270abf51574489dd19f0b6ffbb6f Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Mon, 27 Jul 2020 23:35:19 +0530 Subject: [PATCH 077/361] 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 078/361] 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 079/361] 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 080/361] 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 081/361] 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 1903e169dfa464704f2360bd7e1ccb8c03962424 Mon Sep 17 00:00:00 2001 From: gavin Date: Wed, 29 Jul 2020 11:16:24 +0530 Subject: [PATCH 082/361] fix: Update translations ref: https://github.com/frappe/frappe/pull/11094#discussion_r462050750 --- .../doctype/deleted_document/deleted_document_list.js | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/frappe/core/doctype/deleted_document/deleted_document_list.js b/frappe/core/doctype/deleted_document/deleted_document_list.js index a49d8125e3..ac21d0ff42 100644 --- a/frappe/core/doctype/deleted_document/deleted_document_list.js +++ b/frappe/core/doctype/deleted_document/deleted_document_list.js @@ -13,12 +13,14 @@ frappe.listview_settings["Deleted Document"] = { if (r.message) { let num = r.message.length; let message; - if (num === 1) { - message = "{0} Document was Restored"; + if (num === 0) { + message = __("No Documents were Restored"); + } else if (num === 1) { + message = __("Document was Restored"); } else { - message = "{0} Documents were Restored"; + message = __("{0} Documents were Restored", [num]); } - frappe.msgprint(__(message, [num])); + frappe.msgprint(message); if (num > 0) { doclist.refresh(); } From ae3ee5b2eb5c64fa00f4461ad1a11841ced65c54 Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Wed, 29 Jul 2020 13:33:04 +0530 Subject: [PATCH 083/361] 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 084/361] 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 085/361] 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 3df3065c4e966690e8c0c88683581377ab0350fd Mon Sep 17 00:00:00 2001 From: prssanna Date: Fri, 20 Mar 2020 20:58:43 +0530 Subject: [PATCH 086/361] fix: disable generate report button till report is generated --- .../js/frappe/views/reports/query_report.js | 49 ++++++++++++------- 1 file changed, 30 insertions(+), 19 deletions(-) diff --git a/frappe/public/js/frappe/views/reports/query_report.js b/frappe/public/js/frappe/views/reports/query_report.js index 53486ea3d6..f926fdb868 100644 --- a/frappe/public/js/frappe/views/reports/query_report.js +++ b/frappe/public/js/frappe/views/reports/query_report.js @@ -66,6 +66,7 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { setup_events() { frappe.realtime.on("report_generated", (data) => { + this.toggle_primary_button_disabled(false); if(data.report_name) { this.prepared_report_action = "Rebuild"; // If generated report and currently active Prepared Report has same fiters @@ -660,29 +661,37 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { // 1. First time with given filters, no data. // 2. Showing data from specific report // 3. Showing data from an old report without specific report name - if(this.prepared_report_action == "New") { - this.page.set_primary_action( - __("Generate New Report"), - () => { - this.generate_background_report(); - } - ); - } else if(this.prepared_report_action == "Edit") { - this.page.set_primary_action( - __("Edit"), - () => { - frappe.set_route(frappe.get_route()); - } - ); - } else if(this.prepared_report_action == "Rebuild"){ - this.page.set_primary_action( - __("Rebuild"), - this.generate_background_report.bind(this) + this.primary_action_map = { + "New": { + label: __("Generate New Report"), + action: () => this.generate_background_report() + }, + "Edit": { + label: __("Edit"), + action: () => frappe.set_route(frappe.get_route()) + }, + "Rebuild": { + label: __("Rebuild"), + action: () => this.generate_background_report() + } + } + let primary_action = this.primary_action_map[this.prepared_report_action]; + + if (!this.primary_button || this.primary_button.text() !== primary_action.label) { + this.primary_button = this.page.set_primary_action( + primary_action.label, + primary_action.action ); } } + toggle_primary_button_disabled(disable) { + this.primary_button.prop('disabled', disable); + } + generate_background_report() { + this.toggle_primary_button_disabled(true); + let mandatory = this.filters.filter(f => f.df.reqd); let missing_mandatory = mandatory.filter(f => !f.get_value()); if (!missing_mandatory.length){ @@ -1619,7 +1628,9 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { if (flag && this.prepared_report) { this.prepared_report_action = "New"; - this.add_prepared_report_buttons(); + if (!this.primary_button.is(':visible')) { + this.add_prepared_report_buttons(); + } } } From f3b8b9bf5047feab14864e0b01032269b5af1bd0 Mon Sep 17 00:00:00 2001 From: prssanna Date: Fri, 5 Jun 2020 13:33:18 +0530 Subject: [PATCH 087/361] fix: don't return response --- .../public/js/frappe/views/reports/query_report.js | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/frappe/public/js/frappe/views/reports/query_report.js b/frappe/public/js/frappe/views/reports/query_report.js index f926fdb868..1cf9faf96a 100644 --- a/frappe/public/js/frappe/views/reports/query_report.js +++ b/frappe/public/js/frappe/views/reports/query_report.js @@ -664,15 +664,21 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { this.primary_action_map = { "New": { label: __("Generate New Report"), - action: () => this.generate_background_report() + click: () => { + this.generate_background_report() + }, }, "Edit": { label: __("Edit"), - action: () => frappe.set_route(frappe.get_route()) + click: () => { + frappe.set_route(frappe.get_route()) + } }, "Rebuild": { label: __("Rebuild"), - action: () => this.generate_background_report() + click: () => { + this.generate_background_report() + } } } let primary_action = this.primary_action_map[this.prepared_report_action]; @@ -680,7 +686,7 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { if (!this.primary_button || this.primary_button.text() !== primary_action.label) { this.primary_button = this.page.set_primary_action( primary_action.label, - primary_action.action + primary_action.click ); } } From 212ee269d6b1f2309c985afd8fa4d4b8ab25adaf Mon Sep 17 00:00:00 2001 From: prssanna Date: Wed, 29 Jul 2020 17:31:07 +0530 Subject: [PATCH 088/361] feat(Prepared Report): system setting to automatically delete prepared reports --- .../system_settings/system_settings.js | 8 ++++++ .../system_settings/system_settings.json | 26 +++++++++++++++---- 2 files changed, 29 insertions(+), 5 deletions(-) diff --git a/frappe/core/doctype/system_settings/system_settings.js b/frappe/core/doctype/system_settings/system_settings.js index aa60ea7e95..d2fe3cba69 100644 --- a/frappe/core/doctype/system_settings/system_settings.js +++ b/frappe/core/doctype/system_settings/system_settings.js @@ -27,3 +27,11 @@ frappe.ui.form.on("System Settings", "enable_two_factor_auth", function(frm) { frm.set_value("bypass_restrict_ip_check_if_2fa_enabled", 0); } }); + +frappe.ui.form.on("System Settings", "enable_prepared_report_auto_deletion", function(frm) { + if(frm.doc.enable_prepared_report_auto_deletion){ + if (!frm.doc.prepared_report_expiry_period) { + frm.set_value('prepared_report_expiry_period', 30); + } + } +}); diff --git a/frappe/core/doctype/system_settings/system_settings.json b/frappe/core/doctype/system_settings/system_settings.json index b2cb67dbc9..ffc353a6ea 100644 --- a/frappe/core/doctype/system_settings/system_settings.json +++ b/frappe/core/doctype/system_settings/system_settings.json @@ -59,7 +59,9 @@ "column_break_18", "disable_standard_email_footer", "hide_footer_in_auto_email_reports", - "attach_view_link", + "prepared_report_section", + "enable_prepared_report_auto_deletion", + "prepared_report_expiry_period", "chat", "enable_chat", "use_socketio_to_upload_file" @@ -425,16 +427,30 @@ "label": "Enable Onboarding" }, { - "default": "1", - "fieldname": "attach_view_link", + "default": "30", + "depends_on": "enable_prepared_report_auto_deletion", + "description": "System will automatically delete Prepared Reports after these many days since creation", + "fieldname": "prepared_report_expiry_period", + "fieldtype": "Int", + "label": "Prepared Report Expiry Period (Days)" + }, + { + "default": "0", + "fieldname": "enable_prepared_report_auto_deletion", "fieldtype": "Check", - "label": "Send document Web View link in email" + "label": "Enable Auto-deletion of Prepared Reports" + }, + { + "collapsible": 1, + "fieldname": "prepared_report_section", + "fieldtype": "Section Break", + "label": "Prepared Report" } ], "icon": "fa fa-cog", "issingle": 1, "links": [], - "modified": "2020-07-02 16:13:00.166382", + "modified": "2020-07-26 15:57:34.197718", "modified_by": "Administrator", "module": "Core", "name": "System Settings", From a19d045f885818408b75ee13bc187a15ce13c9fd Mon Sep 17 00:00:00 2001 From: prssanna Date: Wed, 29 Jul 2020 17:33:50 +0530 Subject: [PATCH 089/361] feat: add scheduler event to delete old prepared reports --- .../prepared_report/prepared_report.py | 34 +++++++++++++++++++ frappe/hooks.py | 3 +- 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/frappe/core/doctype/prepared_report/prepared_report.py b/frappe/core/doctype/prepared_report/prepared_report.py index 4e87bb92dd..2c02d99dad 100644 --- a/frappe/core/doctype/prepared_report/prepared_report.py +++ b/frappe/core/doctype/prepared_report/prepared_report.py @@ -69,6 +69,40 @@ def run_background(prepared_report): user=frappe.session.user ) +@frappe.whitelist() +def get_reports_in_queued_state(report_name, filters): + reports = frappe.get_all('Prepared Report', + filters = { + 'report_name': report_name, + 'filters': json.dumps(json.loads(filters)), + 'status': 'Queued' + }) + return reports + +def delete_expired_prepared_reports(): + system_settings = frappe.get_single('System Settings') + enable_auto_deletion = system_settings.enable_prepared_report_auto_deletion + if enable_auto_deletion: + expiry_period = system_settings.prepared_report_expiry_period + prepared_reports_to_delete = frappe.get_all('Prepared Report', + filters = { + 'creation': ['<', frappe.utils.add_days(frappe.utils.now(), -expiry_period)] + }) + + args = { + 'reports': prepared_reports_to_delete, + 'limit': 50 + } + + enqueue(method=delete_prepared_reports, job_name="delete_prepared_reports", **args) + +@frappe.whitelist() +def delete_prepared_reports(reports, limit=None): + reports = frappe.parse_json(reports) + for index, doc in enumerate(reports): + if limit and index == limit: + return + frappe.delete_doc('Prepared Report', doc['name'], ignore_permissions=True) def create_json_gz_file(data, dt, dn): # Storing data in CSV file causes information loss diff --git a/frappe/hooks.py b/frappe/hooks.py index 1f209f00a2..132d54150f 100644 --- a/frappe/hooks.py +++ b/frappe/hooks.py @@ -212,7 +212,8 @@ scheduler_events = { "frappe.integrations.doctype.google_contacts.google_contacts.sync", "frappe.automation.doctype.auto_repeat.auto_repeat.make_auto_repeat_entry", "frappe.automation.doctype.auto_repeat.auto_repeat.set_auto_repeat_as_completed", - "frappe.email.doctype.unhandled_email.unhandled_email.remove_old_unhandled_emails" + "frappe.email.doctype.unhandled_email.unhandled_email.remove_old_unhandled_emails", + "frappe.core.doctype.prepared_report.prepared_report.delete_expired_prepared_reports" ], "daily_long": [ "frappe.integrations.doctype.dropbox_settings.dropbox_settings.take_backups_daily", From c5e988dcf1d60492d26c905739b0410a31e23be1 Mon Sep 17 00:00:00 2001 From: prssanna Date: Wed, 29 Jul 2020 17:34:15 +0530 Subject: [PATCH 090/361] feat: show warning dialog if reports are already queued --- frappe/public/js/frappe/ui/messages.js | 12 ++-- .../js/frappe/views/reports/query_report.js | 72 +++++++++++++++++-- 2 files changed, 74 insertions(+), 10 deletions(-) diff --git a/frappe/public/js/frappe/ui/messages.js b/frappe/public/js/frappe/ui/messages.js index eb5f4f09a2..c37cc41650 100644 --- a/frappe/public/js/frappe/ui/messages.js +++ b/frappe/public/js/frappe/ui/messages.js @@ -53,7 +53,7 @@ frappe.confirm = function(message, ifyes, ifno) { return d; } -frappe.warn = function(title, message_html, proceed_action, primary_label) { +frappe.warn = function(title, message_html, proceed_action, primary_label, is_minimizable) { const d = new frappe.ui.Dialog({ title: title, indicator: 'red', @@ -70,11 +70,15 @@ frappe.warn = function(title, message_html, proceed_action, primary_label) { d.hide(); }, secondary_action_label: __("Cancel"), + minimizable: is_minimizable }); - d.buttons.find('.btn-primary').removeClass('btn-primary').addClass('btn-danger'); - const modal_footer = $(``).insertAfter($(d.modal_body)); - modal_footer.html(d.buttons); + d.footer = $(``).insertAfter($(d.modal_body)); + + d.get_close_btn().appendTo(d.footer); + d.get_primary_btn().appendTo(d.footer); + + d.footer.find('.btn-primary').removeClass('btn-primary').addClass('btn-danger'); d.show(); return d; diff --git a/frappe/public/js/frappe/views/reports/query_report.js b/frappe/public/js/frappe/views/reports/query_report.js index 1cf9faf96a..30e2021045 100644 --- a/frappe/public/js/frappe/views/reports/query_report.js +++ b/frappe/public/js/frappe/views/reports/query_report.js @@ -665,19 +665,19 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { "New": { label: __("Generate New Report"), click: () => { - this.generate_background_report() + this.show_warning_or_generate_report(); }, }, "Edit": { label: __("Edit"), click: () => { - frappe.set_route(frappe.get_route()) + frappe.set_route(frappe.get_route()); } }, "Rebuild": { label: __("Rebuild"), click: () => { - this.generate_background_report() + this.show_warning_or_generate_report(); } } } @@ -695,9 +695,69 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { this.primary_button.prop('disabled', disable); } + show_warning_or_generate_report() { + frappe.xcall( + 'frappe.core.doctype.prepared_report.prepared_report.get_reports_in_queued_state', + { + filters: this.get_filter_values(), + report_name: this.report_name, + } + ).then(reports => { + this.queued_prepared_reports = reports; + + if (reports.length) { + const message = this.get_queued_prepared_reports_warning_message(reports); + this.prepared_report_dialog = frappe.warn( + __('Reports already in Queue'), + message, + () => this.generate_background_report(), + __('Proceed Anyway'), + true + ); + + this.prepared_report_dialog.footer.prepend(` + `); + + frappe.utils.bind_actions_with_object(this.prepared_report_dialog.wrapper, this); + } else { + this.generate_background_report(); + } + }); + } + + get_queued_prepared_reports_warning_message(reports) { + const route = `#List/Prepared Report/List?status=Queued&report_name=${this.report_name}`; + const no_of_reports_html = reports.length == 1 + ? `${__('There is already ')}${__('1 Report')}` + : `${__('There are already ')}${__(`${reports.length} Reports`)}`; + + let warning_message = ` +

              + ${__(`Are you sure you want to generate a new report? + ${no_of_reports_html} with the same filters already in the queue:`)} +

              `; + + let get_item_html = item => `${item.name}`; + + warning_message += reports.map(get_item_html).join(', '); + + return warning_message; + } + + delete_old_queued_reports() { + this.prepared_report_dialog.hide(); + frappe.xcall( + 'frappe.core.doctype.prepared_report.prepared_report.delete_prepared_reports', + { + reports: this.queued_prepared_reports, + } + ).then(() => this.generate_background_report()); + } + generate_background_report() { this.toggle_primary_button_disabled(true); - let mandatory = this.filters.filter(f => f.df.reqd); let missing_mandatory = mandatory.filter(f => !f.get_value()); if (!missing_mandatory.length){ @@ -715,8 +775,8 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { // Rememeber the name of Prepared Report doc this.prepared_report_doc_name = data.name; let alert_message = `Report initiated. You can track its status - here`; - frappe.show_alert({message: alert_message, indicator: 'orange'}); + here`; + frappe.show_alert({message: alert_message, indicator: 'orange'}, 10); this.toggle_nothing_to_show(true); }); } From 7bfc8a9b874e56bf5fc0b5864e3e0e9b53bf0f6d Mon Sep 17 00:00:00 2001 From: prssanna Date: Wed, 29 Jul 2020 17:49:26 +0530 Subject: [PATCH 091/361] style: fix formatting --- frappe/core/doctype/system_settings/system_settings.js | 2 +- frappe/public/js/frappe/form/toolbar.js | 4 ++-- frappe/public/js/frappe/views/reports/query_report.js | 3 ++- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/frappe/core/doctype/system_settings/system_settings.js b/frappe/core/doctype/system_settings/system_settings.js index d2fe3cba69..eebc984a2f 100644 --- a/frappe/core/doctype/system_settings/system_settings.js +++ b/frappe/core/doctype/system_settings/system_settings.js @@ -29,7 +29,7 @@ frappe.ui.form.on("System Settings", "enable_two_factor_auth", function(frm) { }); frappe.ui.form.on("System Settings", "enable_prepared_report_auto_deletion", function(frm) { - if(frm.doc.enable_prepared_report_auto_deletion){ + if (frm.doc.enable_prepared_report_auto_deletion) { if (!frm.doc.prepared_report_expiry_period) { frm.set_value('prepared_report_expiry_period', 30); } diff --git a/frappe/public/js/frappe/form/toolbar.js b/frappe/public/js/frappe/form/toolbar.js index 01e8fdbb98..be69775b26 100644 --- a/frappe/public/js/frappe/form/toolbar.js +++ b/frappe/public/js/frappe/form/toolbar.js @@ -185,8 +185,8 @@ frappe.ui.form.Toolbar = Class.extend({ return this.page.add_dropdown(label); }, set_indicator: function() { - if(this.frm.save_disabled) - return; + // if(this.frm.save_disabled) + // return; var indicator = frappe.get_indicator(this.frm.doc); if(indicator) { diff --git a/frappe/public/js/frappe/views/reports/query_report.js b/frappe/public/js/frappe/views/reports/query_report.js index 30e2021045..9f2378e109 100644 --- a/frappe/public/js/frappe/views/reports/query_report.js +++ b/frappe/public/js/frappe/views/reports/query_report.js @@ -680,7 +680,8 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { this.show_warning_or_generate_report(); } } - } + }; + let primary_action = this.primary_action_map[this.prepared_report_action]; if (!this.primary_button || this.primary_button.text() !== primary_action.label) { From 6ca654c93c3e0c3ac9c0d9fe3497dfe24026a61a Mon Sep 17 00:00:00 2001 From: prssanna Date: Wed, 29 Jul 2020 18:28:47 +0530 Subject: [PATCH 092/361] fix: revert removed field attach_view_link --- frappe/core/doctype/system_settings/system_settings.json | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/frappe/core/doctype/system_settings/system_settings.json b/frappe/core/doctype/system_settings/system_settings.json index ffc353a6ea..45ab84e62b 100644 --- a/frappe/core/doctype/system_settings/system_settings.json +++ b/frappe/core/doctype/system_settings/system_settings.json @@ -59,6 +59,7 @@ "column_break_18", "disable_standard_email_footer", "hide_footer_in_auto_email_reports", + "attach_view_link", "prepared_report_section", "enable_prepared_report_auto_deletion", "prepared_report_expiry_period", @@ -426,6 +427,12 @@ "fieldtype": "Check", "label": "Enable Onboarding" }, + { + "default": "1", + "fieldname": "attach_view_link", + "fieldtype": "Check", + "label": "Send document Web View link in email" + }, { "default": "30", "depends_on": "enable_prepared_report_auto_deletion", @@ -450,7 +457,7 @@ "icon": "fa fa-cog", "issingle": 1, "links": [], - "modified": "2020-07-26 15:57:34.197718", + "modified": "2020-07-26 17:58:34.197718", "modified_by": "Administrator", "module": "Core", "name": "System Settings", From 02a9a3220b81400cf97e4949ec0fd179ffeb33d3 Mon Sep 17 00:00:00 2001 From: prssanna Date: Wed, 29 Jul 2020 19:27:23 +0530 Subject: [PATCH 093/361] fix: fix message --- frappe/public/js/frappe/views/reports/query_report.js | 4 ++-- 1 file changed, 2 insertions(+), 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 9f2378e109..83926c18be 100644 --- a/frappe/public/js/frappe/views/reports/query_report.js +++ b/frappe/public/js/frappe/views/reports/query_report.js @@ -731,8 +731,8 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { get_queued_prepared_reports_warning_message(reports) { const route = `#List/Prepared Report/List?status=Queued&report_name=${this.report_name}`; const no_of_reports_html = reports.length == 1 - ? `${__('There is already ')}${__('1 Report')}` - : `${__('There are already ')}${__(`${reports.length} Reports`)}`; + ? `${__('There is ')}${__('1 Report')}` + : `${__('There are ')}${__(`${reports.length} Reports`)}`; let warning_message = `

              From 219fbc8e88d8505391fe8259b0fbeda86844f42d Mon Sep 17 00:00:00 2001 From: Matteo D Date: Wed, 29 Jul 2020 17:28:46 +0200 Subject: [PATCH 094/361] 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 095/361] 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 096/361] 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 097/361] 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 098/361] 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 099/361] 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 100/361] 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 101/361] 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 102/361] 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 103/361] 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 104/361] 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 0c16533cd21eb5ee3b95297fcab32cb7e7dd70f6 Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Fri, 31 Jul 2020 12:32:17 +0530 Subject: [PATCH 105/361] fix(field-group): consider mandatory separately for text editor --- frappe/public/js/frappe/ui/field_group.js | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/frappe/public/js/frappe/ui/field_group.js b/frappe/public/js/frappe/ui/field_group.js index ec7234c9bb..d432e553f1 100644 --- a/frappe/public/js/frappe/ui/field_group.js +++ b/frappe/public/js/frappe/ui/field_group.js @@ -82,17 +82,22 @@ frappe.ui.FieldGroup = frappe.ui.form.Layout.extend({ get_values: function(ignore_errors) { var ret = {}; var errors = []; - for(var key in this.fields_dict) { + for (var key in this.fields_dict) { var f = this.fields_dict[key]; - if(f.get_value) { + if (f.get_value) { var v = f.get_value(); - if(f.df.reqd && is_null(strip_html(cstr(v)))) + if (f.df.reqd && is_null(v)) errors.push(__(f.df.label)); - if(!is_null(v)) ret[f.df.fieldname] = v; + if (f.df.reqd + && f.df.fieldtype === 'Text Editor' + && is_null(strip_html(cstr(v)))) + errors.push(__(f.df.label)); + + if (!is_null(v)) ret[f.df.fieldname] = v; } } - if(errors.length && !ignore_errors) { + if (errors.length && !ignore_errors) { frappe.msgprint({ title: __('Missing Values Required'), message: __('Following fields have missing values:') + From 94116ae93c416958619e98aa47ad979481ec9dd1 Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Fri, 31 Jul 2020 16:07:51 +0530 Subject: [PATCH 106/361] 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 107/361] 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 108/361] 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 109/361] 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 = `

              -